Skip to content

Commit a8bdb69

Browse files
committed
feat(pipes): add support for pure pipes
By default, pipes are pure. This means that an instance of a pipe will be reused and the pipe will be called only when its arguments change. BREAKING CHANGE Before: @pipe({name: 'date'}) class DatePipe {} defines an impure pipe. After: @pipe({name: 'date'}) class DatePipe {} defines a pure pipe. @pipe({name: 'date', pure: false}) class DatePipe {} defines an impure pipe. Closes angular#3966
1 parent 5a59e8b commit a8bdb69

File tree

16 files changed

+167
-74
lines changed

16 files changed

+167
-74
lines changed

modules/angular2/angular2.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
export * from './core';
22
export * from './profile';
33
export * from './lifecycle_hooks';
4-
export * from './bootstrap';
4+
export * from './bootstrap';

modules/angular2/src/core/change_detection/change_detection_jit_generator.ts

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -236,13 +236,18 @@ export class ChangeDetectorJITGenerator {
236236
var newValue = this._names.getLocalName(r.selfIndex);
237237

238238
var pipe = this._names.getPipeName(r.selfIndex);
239-
var pipeType = r.name;
240-
var read = `
239+
var pipeName = r.name;
240+
241+
var init = `
241242
if (${pipe} === ${UTIL}.uninitialized) {
242-
${pipe} = ${this._names.getPipesAccessorName()}.get('${pipeType}');
243+
${pipe} = ${this._names.getPipesAccessorName()}.get('${pipeName}');
243244
}
244-
${newValue} = ${pipe}.transform(${context}, [${argString}]);
245245
`;
246+
var read = `${newValue} = ${pipe}.pipe.transform(${context}, [${argString}]);`;
247+
248+
var contexOrArgCheck = r.args.map((a) => this._names.getChangeName(a));
249+
contexOrArgCheck.push(this._names.getChangeName(r.contextIndex));
250+
var condition = `!${pipe}.pure || (${contexOrArgCheck.join(" || ")})`;
246251

247252
var check = `
248253
if (${oldValue} !== ${newValue}) {
@@ -254,7 +259,13 @@ export class ChangeDetectorJITGenerator {
254259
}
255260
`;
256261

257-
return r.shouldBeChecked() ? `${read}${check}` : read;
262+
var genCode = r.shouldBeChecked() ? `${read}${check}` : read;
263+
264+
if (r.isUsedByOtherRecord()) {
265+
return `${init} if (${condition}) { ${genCode} } else { ${newValue} = ${oldValue}; }`;
266+
} else {
267+
return `${init} if (${condition}) { ${genCode} }`;
268+
}
258269
}
259270

260271
_genReferenceCheck(r: ProtoRecord): string {

modules/angular2/src/core/change_detection/change_detection_util.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {ChangeDetectionStrategy, isDefaultChangeDetectionStrategy} from './const
1212
import {implementsOnDestroy} from './pipe_lifecycle_reflector';
1313
import {BindingTarget} from './binding_record';
1414
import {DirectiveIndex} from './directive_record';
15+
import {SelectedPipe} from './pipes';
1516

1617

1718
/**
@@ -193,9 +194,9 @@ export class ChangeDetectionUtil {
193194
protos[selfIndex - 1]; // self index is shifted by one because of context
194195
}
195196

196-
static callPipeOnDestroy(pipe: any): void {
197-
if (implementsOnDestroy(pipe)) {
198-
pipe.onDestroy();
197+
static callPipeOnDestroy(selectedPipe: SelectedPipe): void {
198+
if (implementsOnDestroy(selectedPipe.pipe)) {
199+
(<any>selectedPipe.pipe).onDestroy();
199200
}
200201
}
201202

modules/angular2/src/core/change_detection/dynamic_change_detector.ts

Lines changed: 26 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -333,38 +333,39 @@ export class DynamicChangeDetector extends AbstractChangeDetector<any> {
333333

334334
_pipeCheck(proto: ProtoRecord, throwOnChange: boolean, values: any[]) {
335335
var context = this._readContext(proto, values);
336-
var args = this._readArgs(proto, values);
336+
var selectedPipe = this._pipeFor(proto, context);
337+
if (!selectedPipe.pure || this._argsOrContextChanged(proto)) {
338+
var args = this._readArgs(proto, values);
339+
var currValue = selectedPipe.pipe.transform(context, args);
337340

338-
var pipe = this._pipeFor(proto, context);
339-
var currValue = pipe.transform(context, args);
341+
if (proto.shouldBeChecked()) {
342+
var prevValue = this._readSelf(proto, values);
343+
if (!isSame(prevValue, currValue)) {
344+
currValue = ChangeDetectionUtil.unwrapValue(currValue);
340345

341-
if (proto.shouldBeChecked()) {
342-
var prevValue = this._readSelf(proto, values);
343-
if (!isSame(prevValue, currValue)) {
344-
currValue = ChangeDetectionUtil.unwrapValue(currValue);
345-
346-
if (proto.lastInBinding) {
347-
var change = ChangeDetectionUtil.simpleChange(prevValue, currValue);
348-
if (throwOnChange) this.throwOnChangeError(prevValue, currValue);
346+
if (proto.lastInBinding) {
347+
var change = ChangeDetectionUtil.simpleChange(prevValue, currValue);
348+
if (throwOnChange) this.throwOnChangeError(prevValue, currValue);
349349

350-
this._writeSelf(proto, currValue, values);
351-
this._setChanged(proto, true);
350+
this._writeSelf(proto, currValue, values);
351+
this._setChanged(proto, true);
352352

353-
return change;
353+
return change;
354354

355+
} else {
356+
this._writeSelf(proto, currValue, values);
357+
this._setChanged(proto, true);
358+
return null;
359+
}
355360
} else {
356-
this._writeSelf(proto, currValue, values);
357-
this._setChanged(proto, true);
361+
this._setChanged(proto, false);
358362
return null;
359363
}
360364
} else {
361-
this._setChanged(proto, false);
365+
this._writeSelf(proto, currValue, values);
366+
this._setChanged(proto, true);
362367
return null;
363368
}
364-
} else {
365-
this._writeSelf(proto, currValue, values);
366-
this._setChanged(proto, true);
367-
return null;
368369
}
369370
}
370371

@@ -413,6 +414,10 @@ export class DynamicChangeDetector extends AbstractChangeDetector<any> {
413414
return false;
414415
}
415416

417+
_argsOrContextChanged(proto: ProtoRecord): boolean {
418+
return this._argsChanged(proto) || this.changes[proto.contextIndex];
419+
}
420+
416421
_readArgs(proto: ProtoRecord, values: any[]) {
417422
var res = ListWrapper.createFixedSize(proto.args.length);
418423
var args = proto.args;
Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
11
import {PipeTransform} from './pipe_transform';
22

3-
export interface Pipes { get(name: string): PipeTransform; }
3+
export interface Pipes { get(name: string): SelectedPipe; }
4+
5+
export class SelectedPipe {
6+
constructor(public pipe: PipeTransform, public pure: boolean) {}
7+
}

modules/angular2/src/core/change_detection/proto_change_detector.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,11 @@ export class ProtoRecordBuilder {
102102
rec.args.forEach(recordIndex => this.records[recordIndex - 1].argumentToPureFunction =
103103
true);
104104
}
105+
if (rec.mode === RecordType.Pipe) {
106+
rec.args.forEach(recordIndex => this.records[recordIndex - 1].argumentToPureFunction =
107+
true);
108+
this.records[rec.contextIndex - 1].argumentToPureFunction = true;
109+
}
105110
}
106111
}
107112

modules/angular2/src/core/change_detection/proto_record.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ export class ProtoRecord {
3636
isUsedByOtherRecord(): boolean { return !this.lastInBinding || this.referencedBySelf; }
3737

3838
shouldBeChecked(): boolean {
39-
return this.argumentToPureFunction || this.lastInBinding || this.isPureFunction();
39+
return this.argumentToPureFunction || this.lastInBinding || this.isPureFunction() ||
40+
this.isPipeRecord();
4041
}
4142

4243
isPipeRecord(): boolean { return this.mode === RecordType.Pipe; }

modules/angular2/src/core/metadata.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ class View extends ViewMetadata {
6969
* See: [PipeMetadata] for docs.
7070
*/
7171
class Pipe extends PipeMetadata {
72-
const Pipe({name}) : super(name: name);
72+
const Pipe({name, pure}) : super(name: name, pure: pure);
7373
}
7474

7575
/**

modules/angular2/src/core/metadata.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -398,10 +398,8 @@ export interface QueryFactory {
398398
* ```
399399
*/
400400
export interface PipeFactory {
401-
(obj: {name: string}): any;
402-
new (obj: {
403-
name: string,
404-
}): any;
401+
(obj: {name: string, pure?: boolean}): any;
402+
new (obj: {name: string, pure?: boolean}): any;
405403
}
406404

407405
/**

modules/angular2/src/core/metadata/directives.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import {CONST, CONST_EXPR} from 'angular2/src/core/facade/lang';
2-
import {ChangeDetectionStrategy} from 'angular2/src/core/change_detection';
1+
import {isPresent, CONST, CONST_EXPR} from 'angular2/src/core/facade/lang';
32
import {InjectableMetadata} from 'angular2/src/core/di/metadata';
3+
import {ChangeDetectionStrategy} from 'angular2/src/core/change_detection';
44

55
/**
66
* Directives allow you to attach behavior to elements in the DOM.
@@ -861,11 +861,15 @@ export class ComponentMetadata extends DirectiveMetadata {
861861
@CONST()
862862
export class PipeMetadata extends InjectableMetadata {
863863
name: string;
864+
_pure: boolean;
864865

865-
constructor({name}: {name: string}) {
866+
constructor({name, pure}: {name: string, pure: boolean}) {
866867
super();
867868
this.name = name;
869+
this._pure = pure;
868870
}
871+
872+
get pure(): boolean { return isPresent(this._pure) ? this._pure : true; }
869873
}
870874

871875
/**

0 commit comments

Comments
 (0)