fix(compiler): properly implement pure pipes and change pipe syntax
Pure pipes as well as arrays and maps are implemented via proxy functions. This is faster than the previous implementation and also generates less code. BREAKING CHANGE: - pipes now take a variable number of arguments, and not an array that contains all arguments.
This commit is contained in:
@ -460,6 +460,13 @@ export function main() {
|
||||
}));
|
||||
|
||||
it('should associate pipes right-to-left', fakeAsync(() => {
|
||||
var ctx = _bindSimpleValue('name | multiArgPipe:"a":"b" | multiArgPipe:0:1', Person);
|
||||
ctx.componentInstance.name = 'value';
|
||||
ctx.detectChanges(false);
|
||||
expect(renderLog.loggedValues).toEqual(['value a b default 0 1 default']);
|
||||
}));
|
||||
|
||||
it('should support calling pure pipes with different number of arguments', fakeAsync(() => {
|
||||
var ctx = _bindSimpleValue('name | multiArgPipe:"a":"b" | multiArgPipe:0:1:2', Person);
|
||||
ctx.componentInstance.name = 'value';
|
||||
ctx.detectChanges(false);
|
||||
@ -491,6 +498,56 @@ export function main() {
|
||||
|
||||
expect(renderLog.log).toEqual(['someProp=Megatron']);
|
||||
}));
|
||||
|
||||
it('should call pure pipes only if the arguments change', fakeAsync(() => {
|
||||
var ctx = _bindSimpleValue('name | countingPipe', Person);
|
||||
// change from undefined -> null
|
||||
ctx.componentInstance.name = null;
|
||||
ctx.detectChanges(false);
|
||||
expect(renderLog.loggedValues).toEqual(['null state:0']);
|
||||
ctx.detectChanges(false);
|
||||
expect(renderLog.loggedValues).toEqual(['null state:0']);
|
||||
|
||||
// change from null -> some value
|
||||
ctx.componentInstance.name = 'bob';
|
||||
ctx.detectChanges(false);
|
||||
expect(renderLog.loggedValues).toEqual(['null state:0', 'bob state:1']);
|
||||
ctx.detectChanges(false);
|
||||
expect(renderLog.loggedValues).toEqual(['null state:0', 'bob state:1']);
|
||||
|
||||
// change from some value -> some other value
|
||||
ctx.componentInstance.name = 'bart';
|
||||
ctx.detectChanges(false);
|
||||
expect(renderLog.loggedValues)
|
||||
.toEqual(['null state:0', 'bob state:1', 'bart state:2']);
|
||||
ctx.detectChanges(false);
|
||||
expect(renderLog.loggedValues)
|
||||
.toEqual(['null state:0', 'bob state:1', 'bart state:2']);
|
||||
|
||||
}));
|
||||
|
||||
it('should call pure pipes that are used multiple times only when the arguments change',
|
||||
fakeAsync(() => {
|
||||
var ctx = createCompFixture(`<div [someProp]="name | countingPipe"></div><div [someProp]="age | countingPipe"></div>`, Person);
|
||||
ctx.componentInstance.name = 'a';
|
||||
ctx.componentInstance.age = 10;
|
||||
ctx.detectChanges(false);
|
||||
expect(renderLog.loggedValues).toEqual(['a state:0', '10 state:1']);
|
||||
ctx.detectChanges(false);
|
||||
expect(renderLog.loggedValues).toEqual(['a state:0', '10 state:1']);
|
||||
ctx.componentInstance.age = 11;
|
||||
ctx.detectChanges(false);
|
||||
expect(renderLog.loggedValues).toEqual(['a state:0', '10 state:1', '11 state:2']);
|
||||
}));
|
||||
|
||||
it('should call impure pipes on each change detection run', fakeAsync(() => {
|
||||
var ctx = _bindSimpleValue('name | countingImpurePipe', Person);
|
||||
ctx.componentInstance.name = 'bob';
|
||||
ctx.detectChanges(false);
|
||||
expect(renderLog.loggedValues).toEqual(['bob state:0']);
|
||||
ctx.detectChanges(false);
|
||||
expect(renderLog.loggedValues).toEqual(['bob state:0', 'bob state:1']);
|
||||
}));
|
||||
});
|
||||
|
||||
describe('event expressions', () => {
|
||||
@ -1014,6 +1071,7 @@ const ALL_DIRECTIVES = CONST_EXPR([
|
||||
|
||||
const ALL_PIPES = CONST_EXPR([
|
||||
forwardRef(() => CountingPipe),
|
||||
forwardRef(() => CountingImpurePipe),
|
||||
forwardRef(() => MultiArgPipe),
|
||||
forwardRef(() => PipeWithOnDestroy),
|
||||
forwardRef(() => IdentityPipe),
|
||||
@ -1086,7 +1144,13 @@ class DirectiveLog {
|
||||
@Pipe({name: 'countingPipe'})
|
||||
class CountingPipe implements PipeTransform {
|
||||
state: number = 0;
|
||||
transform(value, args = null) { return `${value} state:${this.state ++}`; }
|
||||
transform(value) { return `${value} state:${this.state ++}`; }
|
||||
}
|
||||
|
||||
@Pipe({name: 'countingImpurePipe', pure: false})
|
||||
class CountingImpurePipe implements PipeTransform {
|
||||
state: number = 0;
|
||||
transform(value) { return `${value} state:${this.state ++}`; }
|
||||
}
|
||||
|
||||
@Pipe({name: 'pipeWithOnDestroy'})
|
||||
@ -1095,27 +1159,22 @@ class PipeWithOnDestroy implements PipeTransform, OnDestroy {
|
||||
|
||||
ngOnDestroy() { this.directiveLog.add('pipeWithOnDestroy', 'ngOnDestroy'); }
|
||||
|
||||
transform(value, args = null) { return null; }
|
||||
transform(value) { return null; }
|
||||
}
|
||||
|
||||
@Pipe({name: 'identityPipe'})
|
||||
class IdentityPipe implements PipeTransform {
|
||||
transform(value, args = null) { return value; }
|
||||
transform(value) { return value; }
|
||||
}
|
||||
|
||||
@Pipe({name: 'wrappedPipe'})
|
||||
class WrappedPipe implements PipeTransform {
|
||||
transform(value, args = null) { return WrappedValue.wrap(value); }
|
||||
transform(value) { return WrappedValue.wrap(value); }
|
||||
}
|
||||
|
||||
@Pipe({name: 'multiArgPipe'})
|
||||
class MultiArgPipe implements PipeTransform {
|
||||
transform(value, args = null) {
|
||||
var arg1 = args[0];
|
||||
var arg2 = args[1];
|
||||
var arg3 = args.length > 2 ? args[2] : 'default';
|
||||
return `${value} ${arg1} ${arg2} ${arg3}`;
|
||||
}
|
||||
transform(value, arg1, arg2, arg3 = 'default') { return `${value} ${arg1} ${arg2} ${arg3}`; }
|
||||
}
|
||||
|
||||
@Component({selector: 'test-cmp', template: '', directives: ALL_DIRECTIVES, pipes: ALL_PIPES})
|
||||
|
@ -2136,7 +2136,7 @@ class SomeViewport {
|
||||
@Pipe({name: 'double'})
|
||||
class DoublePipe implements PipeTransform, OnDestroy {
|
||||
ngOnDestroy() {}
|
||||
transform(value, args = null) { return `${value}${value}`; }
|
||||
transform(value) { return `${value}${value}`; }
|
||||
}
|
||||
|
||||
@Directive({selector: '[emitter]', outputs: ['event']})
|
||||
|
@ -152,10 +152,10 @@ class MyComp {
|
||||
|
||||
@Pipe({name: 'somePipe', pure: true})
|
||||
class PlatformPipe implements PipeTransform {
|
||||
transform(value: any, args: any[]): any { return 'somePlatformPipe'; }
|
||||
transform(value: any): any { return 'somePlatformPipe'; }
|
||||
}
|
||||
|
||||
@Pipe({name: 'somePipe', pure: true})
|
||||
class CustomPipe implements PipeTransform {
|
||||
transform(value: any, args: any[]): any { return 'someCustomPipe'; }
|
||||
transform(value: any): any { return 'someCustomPipe'; }
|
||||
}
|
||||
|
@ -232,38 +232,38 @@ class PushComponentNeedsChangeDetectorRef {
|
||||
}
|
||||
|
||||
@Pipe({name: 'purePipe', pure: true})
|
||||
class PurePipe {
|
||||
class PurePipe implements PipeTransform {
|
||||
constructor() {}
|
||||
transform(value: any, args: any[] = null): any { return this; }
|
||||
transform(value: any): any { return this; }
|
||||
}
|
||||
|
||||
@Pipe({name: 'impurePipe', pure: false})
|
||||
class ImpurePipe {
|
||||
class ImpurePipe implements PipeTransform {
|
||||
constructor() {}
|
||||
transform(value: any, args: any[] = null): any { return this; }
|
||||
transform(value: any): any { return this; }
|
||||
}
|
||||
|
||||
@Pipe({name: 'pipeNeedsChangeDetectorRef'})
|
||||
class PipeNeedsChangeDetectorRef {
|
||||
constructor(public changeDetectorRef: ChangeDetectorRef) {}
|
||||
transform(value: any, args: any[] = null): any { return this; }
|
||||
transform(value: any): any { return this; }
|
||||
}
|
||||
|
||||
@Pipe({name: 'pipeNeedsService'})
|
||||
export class PipeNeedsService implements PipeTransform {
|
||||
service: any;
|
||||
constructor(@Inject("service") service) { this.service = service; }
|
||||
transform(value: any, args: any[] = null): any { return this; }
|
||||
transform(value: any): any { return this; }
|
||||
}
|
||||
|
||||
@Pipe({name: 'duplicatePipe'})
|
||||
export class DuplicatePipe1 implements PipeTransform {
|
||||
transform(value: any, args: any[] = null): any { return this; }
|
||||
transform(value: any): any { return this; }
|
||||
}
|
||||
|
||||
@Pipe({name: 'duplicatePipe'})
|
||||
export class DuplicatePipe2 implements PipeTransform {
|
||||
transform(value: any, args: any[] = null): any { return this; }
|
||||
transform(value: any): any { return this; }
|
||||
}
|
||||
|
||||
@Component({selector: 'root'})
|
||||
@ -654,7 +654,7 @@ export function main() {
|
||||
|
||||
it('should cache pure pipes', fakeAsync(() => {
|
||||
var el = createComp(
|
||||
'<div [simpleDirective]="true | purePipe"></div><div [simpleDirective]="true | purePipe"></div>',
|
||||
'<div [simpleDirective]="true | purePipe"></div><div *ngIf="true" [simpleDirective]="true | purePipe"></div>',
|
||||
tcb);
|
||||
var purePipe1 = el.children[0].inject(SimpleDirective).value;
|
||||
var purePipe2 = el.children[1].inject(SimpleDirective).value;
|
||||
|
Reference in New Issue
Block a user