diff --git a/modules/angular2/change_detection.js b/modules/angular2/change_detection.js index 9e62690601..e36ae08fda 100644 --- a/modules/angular2/change_detection.js +++ b/modules/angular2/change_detection.js @@ -23,7 +23,7 @@ export {DynamicChangeDetector} from './src/change_detection/dynamic_change_detec export {ChangeDetectorRef} from './src/change_detection/change_detector_ref'; export {PipeRegistry} from './src/change_detection/pipes/pipe_registry'; export {uninitialized} from './src/change_detection/change_detection_util'; -export {NO_CHANGE, Pipe} from './src/change_detection/pipes/pipe'; +export {WrappedValue, Pipe} from './src/change_detection/pipes/pipe'; export { defaultPipes, DynamicChangeDetection, JitChangeDetection, defaultPipeRegistry } from './src/change_detection/change_detection'; diff --git a/modules/angular2/src/change_detection/change_detection_jit_generator.es6 b/modules/angular2/src/change_detection/change_detection_jit_generator.es6 index 07bdd8f570..88bb1f4f6e 100644 --- a/modules/angular2/src/change_detection/change_detection_jit_generator.es6 +++ b/modules/angular2/src/change_detection/change_detection_jit_generator.es6 @@ -156,7 +156,8 @@ if (${pipe} === ${UTIL}.unitialized()) { } ${newValue} = ${pipe}.transform(${context}); -if (! ${UTIL}.noChangeMarker(${newValue})) { +if (${oldValue} !== ${newValue}) { + ${newValue} = ${UTIL}.unwrapValue(${newValue}); ${change} = true; ${update} ${addToChanges} diff --git a/modules/angular2/src/change_detection/change_detection_util.js b/modules/angular2/src/change_detection/change_detection_util.js index ead901dadf..effecb7c46 100644 --- a/modules/angular2/src/change_detection/change_detection_util.js +++ b/modules/angular2/src/change_detection/change_detection_util.js @@ -2,7 +2,7 @@ import {isPresent, isBlank, BaseException, Type} from 'angular2/src/facade/lang' import {List, ListWrapper, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection'; import {ProtoRecord} from './proto_record'; import {ExpressionChangedAfterItHasBeenChecked} from './exceptions'; -import {NO_CHANGE} from './pipes/pipe'; +import {WrappedValue} from './pipes/pipe'; import {CHECK_ALWAYS, CHECK_ONCE, CHECKED, DETACHED, ON_PUSH} from './constants'; export var uninitialized = new Object(); @@ -109,8 +109,12 @@ export class ChangeDetectionUtil { return obj[args[0]]; } - static noChangeMarker(value):boolean { - return value === NO_CHANGE; + static unwrapValue(value:any):any { + if (value instanceof WrappedValue) { + return value.wrapped; + } else { + return value; + } } static throwOnChange(proto:ProtoRecord, change) { diff --git a/modules/angular2/src/change_detection/dynamic_change_detector.js b/modules/angular2/src/change_detection/dynamic_change_detector.js index 301fe10605..db2ec0e8f2 100644 --- a/modules/angular2/src/change_detection/dynamic_change_detector.js +++ b/modules/angular2/src/change_detection/dynamic_change_detector.js @@ -233,11 +233,13 @@ export class DynamicChangeDetector extends AbstractChangeDetector { _pipeCheck(proto:ProtoRecord) { var context = this._readContext(proto); var pipe = this._pipeFor(proto, context); + var prevValue = this._readSelf(proto); var newValue = pipe.transform(context); - if (! ChangeDetectionUtil.noChangeMarker(newValue)) { - var prevValue = this._readSelf(proto); + if (!isSame(prevValue, newValue)) { + newValue = ChangeDetectionUtil.unwrapValue(newValue); + this._writeSelf(proto, newValue); this._setChanged(proto, true); diff --git a/modules/angular2/src/change_detection/pipes/async_pipe.js b/modules/angular2/src/change_detection/pipes/async_pipe.js index 9f844a6790..3b0b7a9900 100644 --- a/modules/angular2/src/change_detection/pipes/async_pipe.js +++ b/modules/angular2/src/change_detection/pipes/async_pipe.js @@ -1,6 +1,6 @@ import {Observable, ObservableWrapper} from 'angular2/src/facade/async'; import {isBlank, isPresent} from 'angular2/src/facade/lang'; -import {Pipe, NO_CHANGE} from './pipe'; +import {Pipe, WrappedValue} from './pipe'; import {ChangeDetectorRef} from '../change_detector_ref'; /** @@ -67,10 +67,10 @@ export class AsyncPipe extends Pipe { } if (this._latestValue === this._latestReturnedValue) { - return NO_CHANGE; + return this._latestReturnedValue; } else { this._latestReturnedValue = this._latestValue; - return this._latestValue; + return WrappedValue.wrap(this._latestValue); } } diff --git a/modules/angular2/src/change_detection/pipes/iterable_changes.js b/modules/angular2/src/change_detection/pipes/iterable_changes.js index cfc1ad2e56..e11064db4d 100644 --- a/modules/angular2/src/change_detection/pipes/iterable_changes.js +++ b/modules/angular2/src/change_detection/pipes/iterable_changes.js @@ -14,7 +14,7 @@ import { looseIdentical, } from 'angular2/src/facade/lang'; -import {NO_CHANGE, Pipe} from './pipe'; +import {WrappedValue, Pipe} from './pipe'; export class IterableChangesFactory { supports(obj):boolean { @@ -117,9 +117,9 @@ export class IterableChanges extends Pipe { transform(collection){ if (this.check(collection)) { - return this; + return WrappedValue.wrap(this); } else { - return NO_CHANGE; + return this; } } diff --git a/modules/angular2/src/change_detection/pipes/keyvalue_changes.js b/modules/angular2/src/change_detection/pipes/keyvalue_changes.js index 9cff9b9f8e..0e314bef70 100644 --- a/modules/angular2/src/change_detection/pipes/keyvalue_changes.js +++ b/modules/angular2/src/change_detection/pipes/keyvalue_changes.js @@ -1,7 +1,7 @@ import {ListWrapper, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection'; import {stringify, looseIdentical, isJsObject} from 'angular2/src/facade/lang'; -import {NO_CHANGE, Pipe} from './pipe'; +import {WrappedValue, Pipe} from './pipe'; /** * @exportedAs angular2/pipes @@ -54,9 +54,9 @@ export class KeyValueChanges extends Pipe { transform(map){ if (this.check(map)) { - return this; + return WrappedValue.wrap(this); } else { - return NO_CHANGE; + return this; } } diff --git a/modules/angular2/src/change_detection/pipes/null_pipe.js b/modules/angular2/src/change_detection/pipes/null_pipe.js index 1f78551ff6..f566957e04 100644 --- a/modules/angular2/src/change_detection/pipes/null_pipe.js +++ b/modules/angular2/src/change_detection/pipes/null_pipe.js @@ -1,5 +1,5 @@ import {isBlank} from 'angular2/src/facade/lang'; -import {Pipe, NO_CHANGE} from './pipe'; +import {Pipe, WrappedValue} from './pipe'; /** * @exportedAs angular2/pipes @@ -35,9 +35,9 @@ export class NullPipe extends Pipe { transform(value) { if (! this.called) { this.called = true; - return null; + return WrappedValue.wrap(null); } else { - return NO_CHANGE; + return null; } } } diff --git a/modules/angular2/src/change_detection/pipes/pipe.js b/modules/angular2/src/change_detection/pipes/pipe.js index d39ee32af7..3c7d75121a 100644 --- a/modules/angular2/src/change_detection/pipes/pipe.js +++ b/modules/angular2/src/change_detection/pipes/pipe.js @@ -1,13 +1,33 @@ /** - * Indicates that the result of a {@link Pipe} transformation has not changed since the last time the pipe was called. + * Indicates that the result of a {@link Pipe} transformation has changed even though the reference has not changed. * - * Suppose we have a pipe that computes changes in an array by performing a simple diff operation. If - * we call this pipe with the same array twice, it will return `NO_CHANGE` the second time. + * The wrapped value will be unwrapped by change detection, and the unwrapped value will be stored. * * @exportedAs angular2/pipes */ +export class WrappedValue { + wrapped:any; -export var NO_CHANGE = new Object(); + constructor(wrapped:any) { + this.wrapped = wrapped; + } + + static wrap(value:any):WrappedValue { + var w = _wrappedValues[_wrappedIndex++ % 5]; + w.wrapped = value; + return w; + } +} + +var _wrappedValues = [ + new WrappedValue(null), + new WrappedValue(null), + new WrappedValue(null), + new WrappedValue(null), + new WrappedValue(null) +]; + +var _wrappedIndex = 0; /** * An interface for extending the list of pipes known to Angular. diff --git a/modules/angular2/test/change_detection/change_detection_spec.js b/modules/angular2/test/change_detection/change_detection_spec.js index 823a568088..29f393d4f2 100644 --- a/modules/angular2/test/change_detection/change_detection_spec.js +++ b/modules/angular2/test/change_detection/change_detection_spec.js @@ -8,7 +8,7 @@ import {Lexer} from 'angular2/src/change_detection/parser/lexer'; import {Locals} from 'angular2/src/change_detection/parser/locals'; import {ChangeDispatcher, DynamicChangeDetector, ChangeDetectionError, BindingRecord, DirectiveRecord, DirectiveIndex, - PipeRegistry, Pipe, NO_CHANGE, CHECK_ALWAYS, CHECK_ONCE, CHECKED, DETACHED, ON_PUSH, DEFAULT} from 'angular2/change_detection'; + PipeRegistry, Pipe, CHECK_ALWAYS, CHECK_ONCE, CHECKED, DETACHED, ON_PUSH, DEFAULT, WrappedValue} from 'angular2/change_detection'; import {JitProtoChangeDetector, DynamicProtoChangeDetector} from 'angular2/src/change_detection/proto_change_detector'; @@ -733,7 +733,7 @@ export function main() { }); }); - it("should do nothing when returns NO_CHANGE", () => { + it("should do nothing when no change", () => { var registry = new FakePipeRegistry('pipe', () => new IdentityPipe()) var ctx = new Person("Megatron"); @@ -741,16 +741,27 @@ export function main() { var cd = c["changeDetector"]; var dispatcher = c["dispatcher"]; - cd.detectChanges(); cd.detectChanges(); expect(dispatcher.log).toEqual(['memo=Megatron']); - ctx.name = "Optimus Prime"; dispatcher.clear(); cd.detectChanges(); - expect(dispatcher.log).toEqual(['memo=Optimus Prime']); + expect(dispatcher.log).toEqual([]); + }); + + it("should unwrap the wrapped value", () => { + var registry = new FakePipeRegistry('pipe', () => new WrappedPipe()) + var ctx = new Person("Megatron"); + + var c = createChangeDetector("memo", "name | pipe", ctx, null, registry); + var cd = c["changeDetector"]; + var dispatcher = c["dispatcher"]; + + cd.detectChanges(); + + expect(dispatcher.log).toEqual(['memo=Megatron']); }); }); }); @@ -798,19 +809,14 @@ class OncePipe extends Pipe { } class IdentityPipe extends Pipe { - state:any; - - supports(newValue) { - return true; - } - transform(value) { - if (this.state === value) { - return NO_CHANGE; - } else { - this.state = value; - return value; - } + return value; + } +} + +class WrappedPipe extends Pipe { + transform(value) { + return WrappedValue.wrap(value); } } diff --git a/modules/angular2/test/change_detection/pipes/async_pipe_spec.js b/modules/angular2/test/change_detection/pipes/async_pipe_spec.js index 218ce31e27..7a73b19099 100644 --- a/modules/angular2/test/change_detection/pipes/async_pipe_spec.js +++ b/modules/angular2/test/change_detection/pipes/async_pipe_spec.js @@ -2,8 +2,8 @@ import {ddescribe, describe, it, iit, xit, expect, beforeEach, afterEach, AsyncTestCompleter, inject, proxy, SpyObject} from 'angular2/test_lib'; import {IMPLEMENTS} from 'angular2/src/facade/lang'; +import {WrappedValue} from 'angular2/src/change_detection/pipes/pipe'; import {AsyncPipe} from 'angular2/src/change_detection/pipes/async_pipe'; -import {NO_CHANGE} from 'angular2/src/change_detection/pipes/pipe'; import {ChangeDetectorRef} from 'angular2/src/change_detection/change_detector_ref'; import {EventEmitter, Observable, ObservableWrapper, PromiseWrapper} from 'angular2/src/facade/async'; @@ -36,25 +36,25 @@ export function main() { expect(pipe.transform(emitter)).toBe(null); }); - it("should return the latest available value", inject([AsyncTestCompleter], (async) => { + it("should return the latest available value wrapped", inject([AsyncTestCompleter], (async) => { pipe.transform(emitter); ObservableWrapper.callNext(emitter, message); PromiseWrapper.setTimeout(() => { - expect(pipe.transform(emitter)).toEqual(message); + expect(pipe.transform(emitter)).toEqual(new WrappedValue(message)); async.done(); }, 0) })); - it("should return NO_CHANGE when nothing has changed since the last call", + it("should return same value when nothing has changed since the last call", inject([AsyncTestCompleter], (async) => { pipe.transform(emitter); ObservableWrapper.callNext(emitter, message); PromiseWrapper.setTimeout(() => { pipe.transform(emitter); - expect(pipe.transform(emitter)).toBe(NO_CHANGE); + expect(pipe.transform(emitter)).toBe(message); async.done(); }, 0) })); @@ -66,11 +66,11 @@ export function main() { var newEmitter = new EventEmitter(); expect(pipe.transform(newEmitter)).toBe(null); - // this should not affect the pipe, so it should return NO_CHANGE + // this should not affect the pipe ObservableWrapper.callNext(emitter, message); PromiseWrapper.setTimeout(() => { - expect(pipe.transform(newEmitter)).toBe(NO_CHANGE); + expect(pipe.transform(newEmitter)).toBe(null); async.done(); }, 0) }));