Skip to content

Commit dd6e0cf

Browse files
committed
fix(compiler): fix where pipes live
Impure pipes need to live on the view that used them and need a new instance for each call site. Impure pipes need to live on the component view, cached across all child views, and need a new pure proxy for each for each call site that lives on the view of the call site. Fixes #8408 This bug was introduced not long ago by 152a117
1 parent 52a6ba7 commit dd6e0cf

File tree

4 files changed

+71
-36
lines changed

4 files changed

+71
-36
lines changed

modules/@angular/compiler/src/view_compiler/compile_pipe.ts

Lines changed: 37 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,35 @@ import {Identifiers, identifierToken} from '../identifiers';
77
import {injectFromViewParentInjector, createPureProxy, getPropertyInView} from './util';
88

99
class _PurePipeProxy {
10-
constructor(public instance: o.ReadPropExpr, public argCount: number) {}
10+
constructor(public view: CompileView, public instance: o.ReadPropExpr, public argCount: number) {}
1111
}
1212

1313
export class CompilePipe {
14-
meta: CompilePipeMetadata;
14+
static call(view: CompileView, name: string, args: o.Expression[]): o.Expression {
15+
var compView = view.componentView;
16+
var meta = _findPipeMeta(compView, name);
17+
var pipe: CompilePipe;
18+
if (meta.pure) {
19+
// pure pipes live on the component view
20+
pipe = compView.purePipes.get(name);
21+
if (isBlank(pipe)) {
22+
pipe = new CompilePipe(compView, meta);
23+
compView.purePipes.set(name, pipe);
24+
compView.pipes.push(pipe);
25+
}
26+
} else {
27+
// Non pure pipes live on the view that called it
28+
pipe = new CompilePipe(view, meta);
29+
view.pipes.push(pipe);
30+
}
31+
return pipe._call(view, args);
32+
}
33+
1534
instance: o.ReadPropExpr;
1635
private _purePipeProxies: _PurePipeProxy[] = [];
1736

18-
constructor(public view: CompileView, name: string) {
19-
this.meta = _findPipeMeta(view, name);
20-
this.instance = o.THIS_EXPR.prop(`_pipe_${name}_${view.pipeCount++}`);
37+
constructor(public view: CompileView, public meta: CompilePipeMetadata) {
38+
this.instance = o.THIS_EXPR.prop(`_pipe_${meta.name}_${view.pipeCount++}`);
2139
}
2240

2341
get pure(): boolean { return this.meta.pure; }
@@ -35,29 +53,33 @@ export class CompilePipe {
3553
.set(o.importExpr(this.meta.type).instantiate(deps))
3654
.toStmt());
3755
this._purePipeProxies.forEach((purePipeProxy) => {
38-
createPureProxy(
39-
this.instance.prop('transform').callMethod(o.BuiltinMethod.bind, [this.instance]),
40-
purePipeProxy.argCount, purePipeProxy.instance, this.view);
56+
var pipeInstanceSeenFromPureProxy =
57+
getPropertyInView(this.instance, purePipeProxy.view, this.view);
58+
createPureProxy(pipeInstanceSeenFromPureProxy.prop('transform')
59+
.callMethod(o.BuiltinMethod.bind, [pipeInstanceSeenFromPureProxy]),
60+
purePipeProxy.argCount, purePipeProxy.instance, purePipeProxy.view);
4161
});
4262
}
4363

44-
call(callingView: CompileView, args: o.Expression[]): o.Expression {
64+
private _call(callingView: CompileView, args: o.Expression[]): o.Expression {
4565
if (this.meta.pure) {
66+
// PurePipeProxies live on the view that called them.
4667
var purePipeProxy = new _PurePipeProxy(
47-
o.THIS_EXPR.prop(`${this.instance.name}_${this._purePipeProxies.length}`), args.length);
68+
callingView, o.THIS_EXPR.prop(`${this.instance.name}_${this._purePipeProxies.length}`),
69+
args.length);
4870
this._purePipeProxies.push(purePipeProxy);
49-
return getPropertyInView(
50-
o.importExpr(Identifiers.castByValue)
51-
.callFn([purePipeProxy.instance, this.instance.prop('transform')]),
52-
callingView, this.view)
71+
return o.importExpr(Identifiers.castByValue)
72+
.callFn([
73+
purePipeProxy.instance,
74+
getPropertyInView(this.instance.prop('transform'), callingView, this.view)
75+
])
5376
.callFn(args);
5477
} else {
5578
return getPropertyInView(this.instance, callingView, this.view).callMethod('transform', args);
5679
}
5780
}
5881
}
5982

60-
6183
function _findPipeMeta(view: CompileView, name: string): CompilePipeMetadata {
6284
var pipeMeta: CompilePipeMetadata = null;
6385
for (var i = view.pipeMetas.length - 1; i >= 0; i--) {

modules/@angular/compiler/src/view_compiler/compile_view.ts

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -127,16 +127,7 @@ export class CompileView implements NameResolver {
127127
}
128128

129129
callPipe(name: string, input: o.Expression, args: o.Expression[]): o.Expression {
130-
var compView = this.componentView;
131-
var pipe = compView.purePipes.get(name);
132-
if (isBlank(pipe)) {
133-
pipe = new CompilePipe(compView, name);
134-
if (pipe.pure) {
135-
compView.purePipes.set(name, pipe);
136-
}
137-
compView.pipes.push(pipe);
138-
}
139-
return pipe.call(this, [input].concat(args));
130+
return CompilePipe.call(this, name, [input].concat(args));
140131
}
141132

142133
getLocal(name: string): o.Expression {

modules/@angular/core/test/linker/change_detection_integration_spec.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ import {
6868
AfterViewInit,
6969
AfterViewChecked
7070
} from '@angular/core';
71+
import {NgFor} from '@angular/common';
7172
import {By} from '@angular/platform-browser/src/dom/debug/by';
7273
import {AsyncPipe} from '@angular/common';
7374

@@ -544,16 +545,23 @@ export function main() {
544545

545546
it('should call pure pipes that are used multiple times only when the arguments change',
546547
fakeAsync(() => {
547-
var ctx = createCompFixture(`<div [someProp]="name | countingPipe"></div><div [someProp]="age | countingPipe"></div>`, Person);
548+
var ctx = createCompFixture(
549+
`<div [someProp]="name | countingPipe"></div><div [someProp]="age | countingPipe"></div>` +
550+
'<div *ngFor="let x of [1,2]" [someProp]="address.city | countingPipe"></div>',
551+
Person);
548552
ctx.componentInstance.name = 'a';
549553
ctx.componentInstance.age = 10;
554+
ctx.componentInstance.address = new Address('mtv');
550555
ctx.detectChanges(false);
551-
expect(renderLog.loggedValues).toEqual(['a state:0', '10 state:1']);
556+
expect(renderLog.loggedValues)
557+
.toEqual(['mtv state:0', 'mtv state:1', 'a state:2', '10 state:3']);
552558
ctx.detectChanges(false);
553-
expect(renderLog.loggedValues).toEqual(['a state:0', '10 state:1']);
559+
expect(renderLog.loggedValues)
560+
.toEqual(['mtv state:0', 'mtv state:1', 'a state:2', '10 state:3']);
554561
ctx.componentInstance.age = 11;
555562
ctx.detectChanges(false);
556-
expect(renderLog.loggedValues).toEqual(['a state:0', '10 state:1', '11 state:2']);
563+
expect(renderLog.loggedValues)
564+
.toEqual(['mtv state:0', 'mtv state:1', 'a state:2', '10 state:3', '11 state:4']);
557565
}));
558566

559567
it('should call impure pipes on each change detection run', fakeAsync(() => {
@@ -1098,6 +1106,7 @@ const ALL_DIRECTIVES = /*@ts2dart_const*/[
10981106
forwardRef(() => OrderCheckDirective2),
10991107
forwardRef(() => OrderCheckDirective0),
11001108
forwardRef(() => OrderCheckDirective1),
1109+
NgFor
11011110
];
11021111

11031112
const ALL_PIPES = /*@ts2dart_const*/[

modules/@angular/core/test/linker/view_injector_integration_spec.ts

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ import {
3737
Host,
3838
SkipSelfMetadata
3939
} from '@angular/core';
40-
import {NgIf} from '@angular/common';
40+
import {NgIf, NgFor} from '@angular/common';
4141
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
4242

4343
const ALL_DIRECTIVES = /*@ts2dart_const*/[
@@ -71,7 +71,8 @@ const ALL_DIRECTIVES = /*@ts2dart_const*/[
7171
forwardRef(() => DirectiveNeedsChangeDetectorRef),
7272
forwardRef(() => PushComponentNeedsChangeDetectorRef),
7373
forwardRef(() => NeedsHostAppService),
74-
NgIf
74+
NgIf,
75+
NgFor
7576
];
7677

7778
const ALL_PIPES = /*@ts2dart_const*/[
@@ -670,23 +671,35 @@ export function main() {
670671

671672
it('should cache pure pipes', fakeAsync(() => {
672673
var el = createComp(
673-
'<div [simpleDirective]="true | purePipe"></div><div *ngIf="true" [simpleDirective]="true | purePipe"></div>',
674+
'<div [simpleDirective]="true | purePipe"></div><div [simpleDirective]="true | purePipe"></div>' +
675+
'<div *ngFor="let x of [1,2]" [simpleDirective]="true | purePipe"></div>',
674676
tcb);
675677
var purePipe1 = el.children[0].inject(SimpleDirective).value;
676678
var purePipe2 = el.children[1].inject(SimpleDirective).value;
679+
var purePipe3 = el.children[2].inject(SimpleDirective).value;
680+
var purePipe4 = el.children[3].inject(SimpleDirective).value;
677681
expect(purePipe1).toBeAnInstanceOf(PurePipe);
678-
expect(purePipe1).toBe(purePipe2);
682+
expect(purePipe2).toBe(purePipe1);
683+
expect(purePipe3).toBe(purePipe1);
684+
expect(purePipe4).toBe(purePipe1);
679685
}));
680686

681-
it('should not cache pure pipes', fakeAsync(() => {
687+
it('should not cache impure pipes', fakeAsync(() => {
682688
var el = createComp(
683-
'<div [simpleDirective]="true | impurePipe"></div><div [simpleDirective]="true | impurePipe"></div>',
689+
'<div [simpleDirective]="true | impurePipe"></div><div [simpleDirective]="true | impurePipe"></div>' +
690+
'<div *ngFor="let x of [1,2]" [simpleDirective]="true | impurePipe"></div>',
684691
tcb);
685692
var purePipe1 = el.children[0].inject(SimpleDirective).value;
686693
var purePipe2 = el.children[1].inject(SimpleDirective).value;
694+
var purePipe3 = el.children[2].inject(SimpleDirective).value;
695+
var purePipe4 = el.children[3].inject(SimpleDirective).value;
687696
expect(purePipe1).toBeAnInstanceOf(ImpurePipe);
688697
expect(purePipe2).toBeAnInstanceOf(ImpurePipe);
689-
expect(purePipe1).not.toBe(purePipe2);
698+
expect(purePipe2).not.toBe(purePipe1);
699+
expect(purePipe3).toBeAnInstanceOf(ImpurePipe);
700+
expect(purePipe3).not.toBe(purePipe1);
701+
expect(purePipe4).toBeAnInstanceOf(ImpurePipe);
702+
expect(purePipe4).not.toBe(purePipe1);
690703
}));
691704
});
692705
});

0 commit comments

Comments
 (0)