feat(pipe): added the Pipe decorator and the pipe property to View

BREAKING CHANGE:
    Instead of configuring pipes via a Pipes object, now you can configure them by providing the pipes property to the View decorator.

    @Pipe({
      name: 'double'
    })
    class DoublePipe {
      transform(value, args) { return value * 2; }
    }

    @View({
      template: '{{ 10 | double}}'
      pipes: [DoublePipe]
    })
    class CustomComponent {}

Closes #3572
This commit is contained in:
vsavkin
2015-08-07 11:41:38 -07:00
committed by Victor Savkin
parent 02b7e61ef7
commit 5b5d31fa9a
62 changed files with 627 additions and 524 deletions
@@ -22,7 +22,8 @@ import {Compiler, CompilerCache} from 'angular2/src/core/compiler/compiler';
import {AppProtoView} from 'angular2/src/core/compiler/view';
import {ElementBinder} from 'angular2/src/core/compiler/element_binder';
import {DirectiveResolver} from 'angular2/src/core/compiler/directive_resolver';
import {Attribute, View, Component, Directive} from 'angular2/annotations';
import {PipeResolver} from 'angular2/src/core/compiler/pipe_resolver';
import {Attribute, View, Component, Directive, Pipe} from 'angular2/annotations';
import * as viewAnn from 'angular2/src/core/annotations_impl/view';
import {internalProtoView} from 'angular2/src/core/compiler/view_ref';
import {DirectiveBinding} from 'angular2/src/core/compiler/element_injector';
@@ -38,11 +39,14 @@ import {AppRootUrl} from 'angular2/src/services/app_root_url';
import * as renderApi from 'angular2/src/render/api';
// TODO(tbosch): Spys don't support named modules...
import {RenderCompiler} from 'angular2/src/render/api';
import {PipeBinding} from 'angular2/src/core/pipes/pipe_binding';
export function main() {
describe('compiler', function() {
var directiveResolver, tplResolver, renderCompiler, protoViewFactory, cmpUrlMapper,
rootProtoView;
var directiveResolver, pipeResolver, tplResolver, renderCompiler, protoViewFactory,
cmpUrlMapper, rootProtoView;
var renderCompileRequests: any[];
function createCompiler(renderCompileResults:
@@ -57,13 +61,14 @@ export function main() {
});
protoViewFactory = new FakeProtoViewFactory(protoViewFactoryResults);
return new Compiler(directiveResolver, new CompilerCache(), tplResolver, cmpUrlMapper,
urlResolver, renderCompiler, protoViewFactory,
return new Compiler(directiveResolver, pipeResolver, [SomeDefaultPipe], new CompilerCache(),
tplResolver, cmpUrlMapper, urlResolver, renderCompiler, protoViewFactory,
new AppRootUrl("http://www.app.com"));
}
beforeEach(() => {
directiveResolver = new DirectiveResolver();
pipeResolver = new PipeResolver();
tplResolver = new FakeViewResolver();
cmpUrlMapper = new RuntimeComponentUrlMapper();
renderCompiler = new SpyRenderCompiler();
@@ -304,6 +309,20 @@ export function main() {
});
}));
it('should pass the pipe bindings', inject([AsyncTestCompleter], (async) => {
tplResolver.setView(MainComponent,
new viewAnn.View({template: '<div></div>', pipes: [SomePipe]}));
var compiler =
createCompiler([createRenderProtoView()], [rootProtoView, createProtoView()]);
compiler.compileInHost(MainComponent)
.then((_) => {
var request = protoViewFactory.requests[1];
expect(request[3][0].key.token).toBe(SomeDefaultPipe);
expect(request[3][1].key.token).toBe(SomePipe);
async.done();
});
}));
it('should use the protoView of the ProtoViewFactory',
inject([AsyncTestCompleter], (async) => {
tplResolver.setView(MainComponent, new viewAnn.View({template: '<div></div>'}));
@@ -399,9 +418,9 @@ export function main() {
var reader: any = new SpyDirectiveResolver();
// create the compiler
var compiler =
new Compiler(reader, cache, tplResolver, cmpUrlMapper, new UrlResolver(),
renderCompiler, protoViewFactory, new AppRootUrl("http://www.app.com"));
var compiler = new Compiler(reader, pipeResolver, [], cache, tplResolver, cmpUrlMapper,
new UrlResolver(), renderCompiler, protoViewFactory,
new AppRootUrl("http://www.app.com"));
compiler.compileInHost(MainComponent)
.then((protoViewRef) => {
// the test should have failed if the resolver was called, so we're good
@@ -570,7 +589,7 @@ function createProtoView(elementBinders = null, type: renderApi.ViewType = null,
type = renderApi.ViewType.COMPONENT;
}
var pv = new AppProtoView(type, isEmbeddedFragment, new renderApi.RenderProtoViewRef(), null,
null, new Map(), null);
null, new Map(), null, null);
if (isBlank(elementBinders)) {
elementBinders = [];
}
@@ -653,6 +672,14 @@ class DirectiveWithProperties {
class DirectiveWithBind {
}
@Pipe({name: 'some-default-pipe'})
class SomeDefaultPipe {
}
@Pipe({name: 'some-pipe'})
class SomePipe {
}
@Directive({selector: 'directive-with-accts'})
class DirectiveWithAttributes {
constructor(@Attribute('someAttr') someAttr: String) {}
@@ -694,8 +721,8 @@ class FakeProtoViewFactory extends ProtoViewFactory {
}
createAppProtoViews(componentBinding: DirectiveBinding, renderProtoView: renderApi.ProtoViewDto,
directives: List<DirectiveBinding>): AppProtoView[] {
this.requests.push([componentBinding, renderProtoView, directives]);
directives: List<DirectiveBinding>, pipes: PipeBinding[]): AppProtoView[] {
this.requests.push([componentBinding, renderProtoView, directives, pipes]);
return collectEmbeddedPvs(ListWrapper.removeAt(this.results, 0));
}
}
@@ -192,14 +192,17 @@ class OptionallyInjectsTemplateRef {
@Injectable()
class DirectiveNeedsChangeDetectorRef {
changeDetectorRef;
constructor(cdr: ChangeDetectorRef) { this.changeDetectorRef = cdr; }
constructor(public changeDetectorRef: ChangeDetectorRef) {}
}
@Injectable()
class ComponentNeedsChangeDetectorRef {
changeDetectorRef;
constructor(cdr: ChangeDetectorRef) { this.changeDetectorRef = cdr; }
constructor(public changeDetectorRef: ChangeDetectorRef) {}
}
@Injectable()
class PipeNeedsChangeDetectorRef {
constructor(public changeDetectorRef: ChangeDetectorRef) {}
}
class A_Needs_B {
@@ -55,15 +55,14 @@ import {
SkipSelf,
SkipSelfMetadata
} from 'angular2/di';
import {
Pipes,
defaultPipes,
Pipe,
PipeTransform,
ChangeDetectorRef,
ON_PUSH
} from 'angular2/src/change_detection/change_detection';
import {Directive, Component, View, Attribute, Query} from 'angular2/annotations';
import {Directive, Component, View, Attribute, Query, Pipe} from 'angular2/annotations';
import * as viewAnn from 'angular2/src/core/annotations_impl/view';
import {QueryList} from 'angular2/src/core/compiler/query_list';
@@ -98,6 +97,7 @@ export function main() {
rootTC.detectChanges();
expect(rootTC.nativeElement).toHaveText('Hello World!');
async.done();
});
}));
@@ -242,12 +242,13 @@ export function main() {
it("should support pipes in bindings",
inject([TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async) => {
tcb.overrideView(MyCompWithPipes, new viewAnn.View({
tcb.overrideView(MyComp, new viewAnn.View({
template: '<div my-dir #dir="mydir" [elprop]="ctxProp | double"></div>',
directives: [MyDir]
directives: [MyDir],
pipes: [DoublePipe]
}))
.createAsync(MyCompWithPipes)
.createAsync(MyComp)
.then((rootTC) => {
rootTC.componentInstance.ctxProp = 'a';
rootTC.detectChanges();
@@ -1661,21 +1662,6 @@ class PushCmpWithAsyncPipe {
resolve(value) { this.completer.resolve(value); }
}
@Injectable()
class PipesWithDouble extends Pipes {
constructor(injector: Injector) { super({"double": DoublePipe}, injector); }
}
@Component({
selector: 'my-comp-with-pipes',
viewBindings: [new Binding(Pipes, {toClass: PipesWithDouble})]
})
@View({directives: []})
@Injectable()
class MyCompWithPipes {
ctxProp: string = "initial value";
}
@Component({selector: 'my-comp'})
@View({directives: []})
@Injectable()
@@ -1754,8 +1740,8 @@ class SomeViewport {
}
}
@Injectable()
class DoublePipe implements Pipe {
@Pipe({name: 'double'})
class DoublePipe implements PipeTransform {
onDestroy() {}
transform(value, args = null) { return `${value}${value}`; }
}
@@ -70,7 +70,7 @@ export function main() {
varBindings.set('a', 'b');
var renderPv = createRenderProtoView([], null, varBindings);
var appPvs =
protoViewFactory.createAppProtoViews(bindDirective(MainComponent), renderPv, []);
protoViewFactory.createAppProtoViews(bindDirective(MainComponent), renderPv, [], []);
expect(appPvs[0].variableBindings.get('a')).toEqual('b');
expect(appPvs.length).toBe(1);
});
@@ -318,7 +318,7 @@ function _createProtoView(type: ViewType, binders: ElementBinder[] = null) {
}
var protoChangeDetector = <any>new SpyProtoChangeDetector();
protoChangeDetector.spy('instantiate').andReturn(new SpyChangeDetector());
var res = new AppProtoView(type, null, null, protoChangeDetector, null, null, 0);
var res = new AppProtoView(type, null, null, protoChangeDetector, null, null, 0, null);
res.elementBinders = binders;
var mappedElementIndices = ListWrapper.createFixedSize(countNestedElementBinders(res));
for (var i = 0; i < binders.length; i++) {
@@ -25,7 +25,7 @@ export function main() {
function createViewPool({capacity}): AppViewPool { return new AppViewPool(capacity); }
function createProtoView() {
return new AppProtoView(null, null, null, null, null, null, null);
return new AppProtoView(null, null, null, null, null, null, null, null);
}
function createView(pv) {