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:
parent
5a59e8be82
commit
a8bdb693b9
|
@ -1,4 +1,4 @@
|
||||||
export * from './core';
|
export * from './core';
|
||||||
export * from './profile';
|
export * from './profile';
|
||||||
export * from './lifecycle_hooks';
|
export * from './lifecycle_hooks';
|
||||||
export * from './bootstrap';
|
export * from './bootstrap';
|
|
@ -236,13 +236,18 @@ export class ChangeDetectorJITGenerator {
|
||||||
var newValue = this._names.getLocalName(r.selfIndex);
|
var newValue = this._names.getLocalName(r.selfIndex);
|
||||||
|
|
||||||
var pipe = this._names.getPipeName(r.selfIndex);
|
var pipe = this._names.getPipeName(r.selfIndex);
|
||||||
var pipeType = r.name;
|
var pipeName = r.name;
|
||||||
var read = `
|
|
||||||
|
var init = `
|
||||||
if (${pipe} === ${UTIL}.uninitialized) {
|
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 = `
|
var check = `
|
||||||
if (${oldValue} !== ${newValue}) {
|
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 {
|
_genReferenceCheck(r: ProtoRecord): string {
|
||||||
|
|
|
@ -12,6 +12,7 @@ import {ChangeDetectionStrategy, isDefaultChangeDetectionStrategy} from './const
|
||||||
import {implementsOnDestroy} from './pipe_lifecycle_reflector';
|
import {implementsOnDestroy} from './pipe_lifecycle_reflector';
|
||||||
import {BindingTarget} from './binding_record';
|
import {BindingTarget} from './binding_record';
|
||||||
import {DirectiveIndex} from './directive_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
|
protos[selfIndex - 1]; // self index is shifted by one because of context
|
||||||
}
|
}
|
||||||
|
|
||||||
static callPipeOnDestroy(pipe: any): void {
|
static callPipeOnDestroy(selectedPipe: SelectedPipe): void {
|
||||||
if (implementsOnDestroy(pipe)) {
|
if (implementsOnDestroy(selectedPipe.pipe)) {
|
||||||
pipe.onDestroy();
|
(<any>selectedPipe.pipe).onDestroy();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -333,38 +333,39 @@ export class DynamicChangeDetector extends AbstractChangeDetector<any> {
|
||||||
|
|
||||||
_pipeCheck(proto: ProtoRecord, throwOnChange: boolean, values: any[]) {
|
_pipeCheck(proto: ProtoRecord, throwOnChange: boolean, values: any[]) {
|
||||||
var context = this._readContext(proto, values);
|
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);
|
if (proto.shouldBeChecked()) {
|
||||||
var currValue = pipe.transform(context, args);
|
var prevValue = this._readSelf(proto, values);
|
||||||
|
if (!isSame(prevValue, currValue)) {
|
||||||
|
currValue = ChangeDetectionUtil.unwrapValue(currValue);
|
||||||
|
|
||||||
if (proto.shouldBeChecked()) {
|
if (proto.lastInBinding) {
|
||||||
var prevValue = this._readSelf(proto, values);
|
var change = ChangeDetectionUtil.simpleChange(prevValue, currValue);
|
||||||
if (!isSame(prevValue, currValue)) {
|
if (throwOnChange) this.throwOnChangeError(prevValue, currValue);
|
||||||
currValue = ChangeDetectionUtil.unwrapValue(currValue);
|
|
||||||
|
|
||||||
if (proto.lastInBinding) {
|
this._writeSelf(proto, currValue, values);
|
||||||
var change = ChangeDetectionUtil.simpleChange(prevValue, currValue);
|
this._setChanged(proto, true);
|
||||||
if (throwOnChange) this.throwOnChangeError(prevValue, currValue);
|
|
||||||
|
|
||||||
this._writeSelf(proto, currValue, values);
|
return change;
|
||||||
this._setChanged(proto, true);
|
|
||||||
|
|
||||||
return change;
|
|
||||||
|
|
||||||
|
} else {
|
||||||
|
this._writeSelf(proto, currValue, values);
|
||||||
|
this._setChanged(proto, true);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
this._writeSelf(proto, currValue, values);
|
this._setChanged(proto, false);
|
||||||
this._setChanged(proto, true);
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this._setChanged(proto, false);
|
this._writeSelf(proto, currValue, values);
|
||||||
|
this._setChanged(proto, true);
|
||||||
return null;
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_argsOrContextChanged(proto: ProtoRecord): boolean {
|
||||||
|
return this._argsChanged(proto) || this.changes[proto.contextIndex];
|
||||||
|
}
|
||||||
|
|
||||||
_readArgs(proto: ProtoRecord, values: any[]) {
|
_readArgs(proto: ProtoRecord, values: any[]) {
|
||||||
var res = ListWrapper.createFixedSize(proto.args.length);
|
var res = ListWrapper.createFixedSize(proto.args.length);
|
||||||
var args = proto.args;
|
var args = proto.args;
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
import {PipeTransform} from './pipe_transform';
|
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) {}
|
||||||
|
}
|
|
@ -102,6 +102,11 @@ export class ProtoRecordBuilder {
|
||||||
rec.args.forEach(recordIndex => this.records[recordIndex - 1].argumentToPureFunction =
|
rec.args.forEach(recordIndex => this.records[recordIndex - 1].argumentToPureFunction =
|
||||||
true);
|
true);
|
||||||
}
|
}
|
||||||
|
if (rec.mode === RecordType.Pipe) {
|
||||||
|
rec.args.forEach(recordIndex => this.records[recordIndex - 1].argumentToPureFunction =
|
||||||
|
true);
|
||||||
|
this.records[rec.contextIndex - 1].argumentToPureFunction = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -36,7 +36,8 @@ export class ProtoRecord {
|
||||||
isUsedByOtherRecord(): boolean { return !this.lastInBinding || this.referencedBySelf; }
|
isUsedByOtherRecord(): boolean { return !this.lastInBinding || this.referencedBySelf; }
|
||||||
|
|
||||||
shouldBeChecked(): boolean {
|
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; }
|
isPipeRecord(): boolean { return this.mode === RecordType.Pipe; }
|
||||||
|
|
|
@ -69,7 +69,7 @@ class View extends ViewMetadata {
|
||||||
* See: [PipeMetadata] for docs.
|
* See: [PipeMetadata] for docs.
|
||||||
*/
|
*/
|
||||||
class Pipe extends PipeMetadata {
|
class Pipe extends PipeMetadata {
|
||||||
const Pipe({name}) : super(name: name);
|
const Pipe({name, pure}) : super(name: name, pure: pure);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -398,10 +398,8 @@ export interface QueryFactory {
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
export interface PipeFactory {
|
export interface PipeFactory {
|
||||||
(obj: {name: string}): any;
|
(obj: {name: string, pure?: boolean}): any;
|
||||||
new (obj: {
|
new (obj: {name: string, pure?: boolean}): any;
|
||||||
name: string,
|
|
||||||
}): any;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import {CONST, CONST_EXPR} from 'angular2/src/core/facade/lang';
|
import {isPresent, CONST, CONST_EXPR} from 'angular2/src/core/facade/lang';
|
||||||
import {ChangeDetectionStrategy} from 'angular2/src/core/change_detection';
|
|
||||||
import {InjectableMetadata} from 'angular2/src/core/di/metadata';
|
import {InjectableMetadata} from 'angular2/src/core/di/metadata';
|
||||||
|
import {ChangeDetectionStrategy} from 'angular2/src/core/change_detection';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Directives allow you to attach behavior to elements in the DOM.
|
* Directives allow you to attach behavior to elements in the DOM.
|
||||||
|
@ -861,11 +861,15 @@ export class ComponentMetadata extends DirectiveMetadata {
|
||||||
@CONST()
|
@CONST()
|
||||||
export class PipeMetadata extends InjectableMetadata {
|
export class PipeMetadata extends InjectableMetadata {
|
||||||
name: string;
|
name: string;
|
||||||
|
_pure: boolean;
|
||||||
|
|
||||||
constructor({name}: {name: string}) {
|
constructor({name, pure}: {name: string, pure: boolean}) {
|
||||||
super();
|
super();
|
||||||
this.name = name;
|
this.name = name;
|
||||||
|
this._pure = pure;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get pure(): boolean { return isPresent(this._pure) ? this._pure : true; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -59,7 +59,7 @@ var _observableStrategy = new ObservableStrategy();
|
||||||
*
|
*
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
@Pipe({name: 'async'})
|
@Pipe({name: 'async', pure: false})
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AsyncPipe implements PipeTransform, PipeOnDestroy {
|
export class AsyncPipe implements PipeTransform, PipeOnDestroy {
|
||||||
_latestValue: Object = null;
|
_latestValue: Object = null;
|
||||||
|
|
|
@ -4,14 +4,15 @@ import {Key, ResolvedBinding, Binding} from 'angular2/src/core/di';
|
||||||
import {PipeMetadata} from '../metadata/directives';
|
import {PipeMetadata} from '../metadata/directives';
|
||||||
|
|
||||||
export class PipeBinding extends ResolvedBinding {
|
export class PipeBinding extends ResolvedBinding {
|
||||||
constructor(public name: string, key: Key, resolvedFactories: ResolvedFactory[],
|
constructor(public name: string, public pure: boolean, key: Key,
|
||||||
multiBinding: boolean) {
|
resolvedFactories: ResolvedFactory[], multiBinding: boolean) {
|
||||||
super(key, resolvedFactories, multiBinding);
|
super(key, resolvedFactories, multiBinding);
|
||||||
}
|
}
|
||||||
|
|
||||||
static createFromType(type: Type, metadata: PipeMetadata): PipeBinding {
|
static createFromType(type: Type, metadata: PipeMetadata): PipeBinding {
|
||||||
var binding = new Binding(type, {toClass: type});
|
var binding = new Binding(type, {toClass: type});
|
||||||
var rb = resolveBinding(binding);
|
var rb = resolveBinding(binding);
|
||||||
return new PipeBinding(metadata.name, rb.key, rb.resolvedFactories, rb.multiBinding);
|
return new PipeBinding(metadata.name, metadata.pure, rb.key, rb.resolvedFactories,
|
||||||
|
rb.multiBinding);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import {isBlank, isPresent, BaseException, CONST, Type} from 'angular2/src/core/facade/lang';
|
import {isBlank, isPresent, BaseException, CONST, Type} from 'angular2/src/core/facade/lang';
|
||||||
|
import {StringMapWrapper} from 'angular2/src/core/facade/collection';
|
||||||
import {
|
import {
|
||||||
Injectable,
|
Injectable,
|
||||||
OptionalMetadata,
|
OptionalMetadata,
|
||||||
|
@ -25,11 +26,25 @@ export class ProtoPipes {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export class Pipes implements cd.Pipes {
|
export class Pipes implements cd.Pipes {
|
||||||
|
_config: StringMap<string, cd.SelectedPipe> = {};
|
||||||
|
|
||||||
constructor(public proto: ProtoPipes, public injector: Injector) {}
|
constructor(public proto: ProtoPipes, public injector: Injector) {}
|
||||||
|
|
||||||
get(name: string): any {
|
get(name: string): cd.SelectedPipe {
|
||||||
var b = this.proto.get(name);
|
var cached = StringMapWrapper.get(this._config, name);
|
||||||
return this.injector.instantiateResolved(b);
|
if (isPresent(cached)) return cached;
|
||||||
|
|
||||||
|
var p = this.proto.get(name);
|
||||||
|
var transform = this.injector.instantiateResolved(p);
|
||||||
|
var res = new cd.SelectedPipe(transform, p.pure);
|
||||||
|
|
||||||
|
if (p.pure) {
|
||||||
|
StringMapWrapper.set(this._config, name, res);
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,8 @@ import {
|
||||||
isBlank,
|
isBlank,
|
||||||
isJsObject,
|
isJsObject,
|
||||||
BaseException,
|
BaseException,
|
||||||
FunctionWrapper
|
FunctionWrapper,
|
||||||
|
normalizeBool
|
||||||
} from 'angular2/src/core/facade/lang';
|
} from 'angular2/src/core/facade/lang';
|
||||||
import {ListWrapper, MapWrapper, StringMapWrapper} from 'angular2/src/core/facade/collection';
|
import {ListWrapper, MapWrapper, StringMapWrapper} from 'angular2/src/core/facade/collection';
|
||||||
|
|
||||||
|
@ -41,7 +42,7 @@ import {
|
||||||
ProtoChangeDetector
|
ProtoChangeDetector
|
||||||
} from 'angular2/src/core/change_detection/change_detection';
|
} from 'angular2/src/core/change_detection/change_detection';
|
||||||
|
|
||||||
import {Pipes} from 'angular2/src/core/change_detection/pipes';
|
import {SelectedPipe, Pipes} from 'angular2/src/core/change_detection/pipes';
|
||||||
import {JitProtoChangeDetector} from 'angular2/src/core/change_detection/jit_proto_change_detector';
|
import {JitProtoChangeDetector} from 'angular2/src/core/change_detection/jit_proto_change_detector';
|
||||||
|
|
||||||
import {getDefinition} from './change_detector_config';
|
import {getDefinition} from './change_detector_config';
|
||||||
|
@ -351,6 +352,36 @@ export function main() {
|
||||||
expect(val.dispatcher.loggedValues).toEqual(['value one two default']);
|
expect(val.dispatcher.loggedValues).toEqual(['value one two default']);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should not reevaluate pure pipes unless its context or arg changes', () => {
|
||||||
|
var pipe = new CountingPipe();
|
||||||
|
var registry = new FakePipes('pipe', () => pipe, {pure: true});
|
||||||
|
var person = new Person('bob');
|
||||||
|
var val = _createChangeDetector('name | pipe', person, registry);
|
||||||
|
|
||||||
|
val.changeDetector.detectChanges();
|
||||||
|
expect(pipe.state).toEqual(1);
|
||||||
|
|
||||||
|
val.changeDetector.detectChanges();
|
||||||
|
expect(pipe.state).toEqual(1);
|
||||||
|
|
||||||
|
person.name = 'jim';
|
||||||
|
val.changeDetector.detectChanges();
|
||||||
|
expect(pipe.state).toEqual(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reevaluate impure pipes neither context nor arg changes', () => {
|
||||||
|
var pipe = new CountingPipe();
|
||||||
|
var registry = new FakePipes('pipe', () => pipe, {pure: false});
|
||||||
|
var person = new Person('bob');
|
||||||
|
var val = _createChangeDetector('name | pipe', person, registry);
|
||||||
|
|
||||||
|
val.changeDetector.detectChanges();
|
||||||
|
expect(pipe.state).toEqual(1);
|
||||||
|
|
||||||
|
val.changeDetector.detectChanges();
|
||||||
|
expect(pipe.state).toEqual(2);
|
||||||
|
});
|
||||||
|
|
||||||
it('should support pipes as arguments to pure functions', () => {
|
it('should support pipes as arguments to pure functions', () => {
|
||||||
var registry = new FakePipes('pipe', () => new IdentityPipe());
|
var registry = new FakePipes('pipe', () => new IdentityPipe());
|
||||||
var person = new Person('bob');
|
var person = new Person('bob');
|
||||||
|
@ -1116,24 +1147,6 @@ export function main() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('pipes', () => {
|
|
||||||
it('should support pipes', () => {
|
|
||||||
var registry = new FakePipes('pipe', () => new CountingPipe());
|
|
||||||
var ctx = new Person('Megatron');
|
|
||||||
|
|
||||||
var val = _createChangeDetector('name | pipe', ctx, registry);
|
|
||||||
|
|
||||||
val.changeDetector.detectChanges();
|
|
||||||
|
|
||||||
expect(val.dispatcher.log).toEqual(['propName=Megatron state:0']);
|
|
||||||
|
|
||||||
val.dispatcher.clear();
|
|
||||||
val.changeDetector.detectChanges();
|
|
||||||
|
|
||||||
expect(val.dispatcher.log).toEqual(['propName=Megatron state:1']);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should do nothing when no change', () => {
|
it('should do nothing when no change', () => {
|
||||||
var registry = new FakePipes('pipe', () => new IdentityPipe());
|
var registry = new FakePipes('pipe', () => new IdentityPipe());
|
||||||
var ctx = new Person('Megatron');
|
var ctx = new Person('Megatron');
|
||||||
|
@ -1256,13 +1269,16 @@ class MultiArgPipe implements PipeTransform {
|
||||||
|
|
||||||
class FakePipes implements Pipes {
|
class FakePipes implements Pipes {
|
||||||
numberOfLookups = 0;
|
numberOfLookups = 0;
|
||||||
|
pure: boolean;
|
||||||
|
|
||||||
constructor(public pipeType: string, public factory: Function) {}
|
constructor(public pipeType: string, public factory: Function, {pure}: {pure?: boolean} = {}) {
|
||||||
|
this.pure = normalizeBool(pure);
|
||||||
|
}
|
||||||
|
|
||||||
get(type: string) {
|
get(type: string) {
|
||||||
if (type != this.pipeType) return null;
|
if (type != this.pipeType) return null;
|
||||||
this.numberOfLookups++;
|
this.numberOfLookups++;
|
||||||
return this.factory();
|
return new SelectedPipe(this.factory(), this.pure);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -36,7 +36,8 @@ export function main() {
|
||||||
it('should instantiate a pipe', () => {
|
it('should instantiate a pipe', () => {
|
||||||
var proto = new ProtoPipes([PipeBinding.createFromType(PipeA, new Pipe({name: 'a'}))]);
|
var proto = new ProtoPipes([PipeBinding.createFromType(PipeA, new Pipe({name: 'a'}))]);
|
||||||
var pipes = new Pipes(proto, injector);
|
var pipes = new Pipes(proto, injector);
|
||||||
expect(pipes.get("a")).toBeAnInstanceOf(PipeA);
|
|
||||||
|
expect(pipes.get("a").pipe).toBeAnInstanceOf(PipeA);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw when no pipe found', () => {
|
it('should throw when no pipe found', () => {
|
||||||
|
@ -48,7 +49,25 @@ export function main() {
|
||||||
it('should inject dependencies from the provided injector', () => {
|
it('should inject dependencies from the provided injector', () => {
|
||||||
var proto = new ProtoPipes([PipeBinding.createFromType(PipeB, new Pipe({name: 'b'}))]);
|
var proto = new ProtoPipes([PipeBinding.createFromType(PipeB, new Pipe({name: 'b'}))]);
|
||||||
var pipes = new Pipes(proto, injector);
|
var pipes = new Pipes(proto, injector);
|
||||||
expect(pipes.get("b").dep).toEqual("dependency");
|
expect((<any>pipes.get("b").pipe).dep).toEqual("dependency");
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should cache pure pipes', () => {
|
||||||
|
var proto =
|
||||||
|
new ProtoPipes([PipeBinding.createFromType(PipeA, new Pipe({name: 'a', pure: true}))]);
|
||||||
|
var pipes = new Pipes(proto, injector);
|
||||||
|
|
||||||
|
expect(pipes.get("a").pure).toEqual(true);
|
||||||
|
expect(pipes.get("a")).toBe(pipes.get("a"));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should NOT cache impure pipes', () => {
|
||||||
|
var proto =
|
||||||
|
new ProtoPipes([PipeBinding.createFromType(PipeA, new Pipe({name: 'a', pure: false}))]);
|
||||||
|
var pipes = new Pipes(proto, injector);
|
||||||
|
|
||||||
|
expect(pipes.get("a").pure).toEqual(false);
|
||||||
|
expect(pipes.get("a")).not.toBe(pipes.get("a"));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -335,13 +335,20 @@ class _CodegenState {
|
||||||
var pipe = _names.getPipeName(r.selfIndex);
|
var pipe = _names.getPipeName(r.selfIndex);
|
||||||
var pipeType = r.name;
|
var pipeType = r.name;
|
||||||
|
|
||||||
var read = '''
|
var init = '''
|
||||||
if ($_IDENTICAL_CHECK_FN($pipe, $_UTIL.uninitialized)) {
|
if ($_IDENTICAL_CHECK_FN($pipe, $_UTIL.uninitialized)) {
|
||||||
$pipe = ${_names.getPipesAccessorName()}.get('$pipeType');
|
$pipe = ${_names.getPipesAccessorName()}.get('$pipeType');
|
||||||
}
|
}
|
||||||
$newValue = $pipe.transform($context, [$argString]);
|
|
||||||
''';
|
''';
|
||||||
|
|
||||||
|
var read = '''
|
||||||
|
$newValue = $pipe.pipe.transform($context, [$argString]);
|
||||||
|
''';
|
||||||
|
|
||||||
|
var contexOrArgCheck = r.args.map((a) => _names.getChangeName(a)).toList();
|
||||||
|
contexOrArgCheck.add(_names.getChangeName(r.contextIndex));
|
||||||
|
var condition = '''!${pipe}.pure || (${contexOrArgCheck.join(" || ")})''';
|
||||||
|
|
||||||
var check = '''
|
var check = '''
|
||||||
if ($_NOT_IDENTICAL_CHECK_FN($oldValue, $newValue)) {
|
if ($_NOT_IDENTICAL_CHECK_FN($oldValue, $newValue)) {
|
||||||
$newValue = $_UTIL.unwrapValue($newValue);
|
$newValue = $_UTIL.unwrapValue($newValue);
|
||||||
|
@ -352,7 +359,13 @@ class _CodegenState {
|
||||||
}
|
}
|
||||||
''';
|
''';
|
||||||
|
|
||||||
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} }''';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String _genReferenceCheck(ProtoRecord r) {
|
String _genReferenceCheck(ProtoRecord r) {
|
||||||
|
|
Loading…
Reference in New Issue