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 #3966
This commit is contained in:
vsavkin
2015-09-08 09:17:58 -07:00
committed by Victor Savkin
parent 5a59e8be82
commit a8bdb693b9
16 changed files with 167 additions and 74 deletions

View File

@ -236,13 +236,18 @@ export class ChangeDetectorJITGenerator {
var newValue = this._names.getLocalName(r.selfIndex);
var pipe = this._names.getPipeName(r.selfIndex);
var pipeType = r.name;
var read = `
var pipeName = r.name;
var init = `
if (${pipe} === ${UTIL}.uninitialized) {
${pipe} = ${this._names.getPipesAccessorName()}.get('${pipeType}');
${pipe} = ${this._names.getPipesAccessorName()}.get('${pipeName}');
}
${newValue} = ${pipe}.transform(${context}, [${argString}]);
`;
var read = `${newValue} = ${pipe}.pipe.transform(${context}, [${argString}]);`;
var contexOrArgCheck = r.args.map((a) => this._names.getChangeName(a));
contexOrArgCheck.push(this._names.getChangeName(r.contextIndex));
var condition = `!${pipe}.pure || (${contexOrArgCheck.join(" || ")})`;
var check = `
if (${oldValue} !== ${newValue}) {
@ -254,7 +259,13 @@ export class ChangeDetectorJITGenerator {
}
`;
return r.shouldBeChecked() ? `${read}${check}` : read;
var genCode = r.shouldBeChecked() ? `${read}${check}` : read;
if (r.isUsedByOtherRecord()) {
return `${init} if (${condition}) { ${genCode} } else { ${newValue} = ${oldValue}; }`;
} else {
return `${init} if (${condition}) { ${genCode} }`;
}
}
_genReferenceCheck(r: ProtoRecord): string {

View File

@ -12,6 +12,7 @@ import {ChangeDetectionStrategy, isDefaultChangeDetectionStrategy} from './const
import {implementsOnDestroy} from './pipe_lifecycle_reflector';
import {BindingTarget} from './binding_record';
import {DirectiveIndex} from './directive_record';
import {SelectedPipe} from './pipes';
/**
@ -193,9 +194,9 @@ export class ChangeDetectionUtil {
protos[selfIndex - 1]; // self index is shifted by one because of context
}
static callPipeOnDestroy(pipe: any): void {
if (implementsOnDestroy(pipe)) {
pipe.onDestroy();
static callPipeOnDestroy(selectedPipe: SelectedPipe): void {
if (implementsOnDestroy(selectedPipe.pipe)) {
(<any>selectedPipe.pipe).onDestroy();
}
}

View File

@ -333,38 +333,39 @@ export class DynamicChangeDetector extends AbstractChangeDetector<any> {
_pipeCheck(proto: ProtoRecord, throwOnChange: boolean, values: any[]) {
var context = this._readContext(proto, values);
var args = this._readArgs(proto, values);
var selectedPipe = this._pipeFor(proto, context);
if (!selectedPipe.pure || this._argsOrContextChanged(proto)) {
var args = this._readArgs(proto, values);
var currValue = selectedPipe.pipe.transform(context, args);
var pipe = this._pipeFor(proto, context);
var currValue = pipe.transform(context, args);
if (proto.shouldBeChecked()) {
var prevValue = this._readSelf(proto, values);
if (!isSame(prevValue, currValue)) {
currValue = ChangeDetectionUtil.unwrapValue(currValue);
if (proto.shouldBeChecked()) {
var prevValue = this._readSelf(proto, values);
if (!isSame(prevValue, currValue)) {
currValue = ChangeDetectionUtil.unwrapValue(currValue);
if (proto.lastInBinding) {
var change = ChangeDetectionUtil.simpleChange(prevValue, currValue);
if (throwOnChange) this.throwOnChangeError(prevValue, currValue);
if (proto.lastInBinding) {
var change = ChangeDetectionUtil.simpleChange(prevValue, currValue);
if (throwOnChange) this.throwOnChangeError(prevValue, currValue);
this._writeSelf(proto, currValue, values);
this._setChanged(proto, true);
this._writeSelf(proto, currValue, values);
this._setChanged(proto, true);
return change;
return change;
} else {
this._writeSelf(proto, currValue, values);
this._setChanged(proto, true);
return null;
}
} else {
this._writeSelf(proto, currValue, values);
this._setChanged(proto, true);
this._setChanged(proto, false);
return null;
}
} else {
this._setChanged(proto, false);
this._writeSelf(proto, currValue, values);
this._setChanged(proto, true);
return null;
}
} else {
this._writeSelf(proto, currValue, values);
this._setChanged(proto, true);
return null;
}
}
@ -413,6 +414,10 @@ export class DynamicChangeDetector extends AbstractChangeDetector<any> {
return false;
}
_argsOrContextChanged(proto: ProtoRecord): boolean {
return this._argsChanged(proto) || this.changes[proto.contextIndex];
}
_readArgs(proto: ProtoRecord, values: any[]) {
var res = ListWrapper.createFixedSize(proto.args.length);
var args = proto.args;

View File

@ -1,3 +1,7 @@
import {PipeTransform} from './pipe_transform';
export interface Pipes { get(name: string): PipeTransform; }
export interface Pipes { get(name: string): SelectedPipe; }
export class SelectedPipe {
constructor(public pipe: PipeTransform, public pure: boolean) {}
}

View File

@ -102,6 +102,11 @@ export class ProtoRecordBuilder {
rec.args.forEach(recordIndex => this.records[recordIndex - 1].argumentToPureFunction =
true);
}
if (rec.mode === RecordType.Pipe) {
rec.args.forEach(recordIndex => this.records[recordIndex - 1].argumentToPureFunction =
true);
this.records[rec.contextIndex - 1].argumentToPureFunction = true;
}
}
}

View File

@ -36,7 +36,8 @@ export class ProtoRecord {
isUsedByOtherRecord(): boolean { return !this.lastInBinding || this.referencedBySelf; }
shouldBeChecked(): boolean {
return this.argumentToPureFunction || this.lastInBinding || this.isPureFunction();
return this.argumentToPureFunction || this.lastInBinding || this.isPureFunction() ||
this.isPipeRecord();
}
isPipeRecord(): boolean { return this.mode === RecordType.Pipe; }