diff --git a/modules/angular2/change_detection.ts b/modules/angular2/change_detection.ts index ee30b7af35..a6efae9908 100644 --- a/modules/angular2/change_detection.ts +++ b/modules/angular2/change_detection.ts @@ -23,6 +23,12 @@ export { defaultPipes, Pipe, Pipes, + IterableDiffers, + IterableDiffer, + IterableDifferFactory, + KeyValueDiffers, + KeyValueDiffer, + KeyValueDifferFactory, PipeFactory, BasePipe, NullPipe, diff --git a/modules/angular2/pipes.ts b/modules/angular2/pipes.ts index 89ef0dc5a5..256ce582fd 100644 --- a/modules/angular2/pipes.ts +++ b/modules/angular2/pipes.ts @@ -9,8 +9,6 @@ export {UpperCasePipe} from './src/change_detection/pipes/uppercase_pipe'; export {LowerCasePipe} from './src/change_detection/pipes/lowercase_pipe'; export {ObservablePipe} from './src/change_detection/pipes/observable_pipe'; export {JsonPipe} from './src/change_detection/pipes/json_pipe'; -export {IterableChanges} from './src/change_detection/pipes/iterable_changes'; -export {KeyValueChanges} from './src/change_detection/pipes/keyvalue_changes'; export {DatePipe} from './src/change_detection/pipes/date_pipe'; export {DecimalPipe, PercentPipe, CurrencyPipe} from './src/change_detection/pipes/number_pipe'; export {LimitToPipe} from './src/change_detection/pipes/limit_to_pipe'; diff --git a/modules/angular2/src/change_detection/change_detection.ts b/modules/angular2/src/change_detection/change_detection.ts index 7f8f467311..caa512cdf7 100644 --- a/modules/angular2/src/change_detection/change_detection.ts +++ b/modules/angular2/src/change_detection/change_detection.ts @@ -3,8 +3,10 @@ import {PregenProtoChangeDetector} from './pregen_proto_change_detector'; import {DynamicProtoChangeDetector} from './proto_change_detector'; import {PipeFactory, Pipe} from './pipes/pipe'; import {Pipes} from './pipes/pipes'; -import {IterableChangesFactory} from './pipes/iterable_changes'; -import {KeyValueChangesFactory} from './pipes/keyvalue_changes'; +import {IterableDiffers, IterableDifferFactory} from './differs/iterable_differs'; +import {DefaultIterableDifferFactory} from './differs/default_iterable_differ'; +import {KeyValueDiffers, KeyValueDifferFactory} from './differs/keyvalue_differs'; +import {DefaultKeyValueDifferFactory} from './differs/default_keyvalue_differ'; import {ObservablePipeFactory} from './pipes/observable_pipe'; import {PromisePipeFactory} from './pipes/promise_pipe'; import {UpperCasePipe} from './pipes/uppercase_pipe'; @@ -52,6 +54,8 @@ export {DirectiveIndex, DirectiveRecord} from './directive_record'; export {DynamicChangeDetector} from './dynamic_change_detector'; export {ChangeDetectorRef} from './change_detector_ref'; export {Pipes} from './pipes/pipes'; +export {IterableDiffers, IterableDiffer, IterableDifferFactory} from './differs/iterable_differs'; +export {KeyValueDiffers, KeyValueDiffer, KeyValueDifferFactory} from './differs/keyvalue_differs'; export {WrappedValue, Pipe, PipeFactory, BasePipe} from './pipes/pipe'; export {NullPipe, NullPipeFactory} from './pipes/null_pipe'; @@ -59,14 +63,14 @@ export {NullPipe, NullPipeFactory} from './pipes/null_pipe'; /** * Structural diffing for `Object`s and `Map`s. */ -export const keyValDiff: List = - CONST_EXPR([CONST_EXPR(new KeyValueChangesFactory()), CONST_EXPR(new NullPipeFactory())]); +export const keyValDiff: KeyValueDifferFactory[] = + CONST_EXPR([CONST_EXPR(new DefaultKeyValueDifferFactory())]); /** * Structural diffing for `Iterable` types such as `Array`s. */ -export const iterableDiff: List = - CONST_EXPR([CONST_EXPR(new IterableChangesFactory()), CONST_EXPR(new NullPipeFactory())]); +export const iterableDiff: IterableDifferFactory[] = + CONST_EXPR([CONST_EXPR(new DefaultIterableDifferFactory())]); /** * Async binding to such types as Observable. @@ -127,8 +131,6 @@ export const date: List = export const defaultPipes: Pipes = CONST_EXPR(new Pipes({ - "iterableDiff": iterableDiff, - "keyValDiff": keyValDiff, "async": async, "uppercase": uppercase, "lowercase": lowercase, @@ -140,6 +142,10 @@ export const defaultPipes: Pipes = CONST_EXPR(new Pipes({ "date": date })); +export const defaultIterableDiffers = CONST_EXPR(new IterableDiffers(iterableDiff)); + +export const defaultKeyValueDiffers = CONST_EXPR(new KeyValueDiffers(keyValDiff)); + /** * Map from {@link ChangeDetectorDefinition#id} to a factory method which takes a * {@link Pipes} and a {@link ChangeDetectorDefinition} and generates a diff --git a/modules/angular2/src/change_detection/pipes/iterable_changes.ts b/modules/angular2/src/change_detection/differs/default_iterable_differ.ts similarity index 96% rename from modules/angular2/src/change_detection/pipes/iterable_changes.ts rename to modules/angular2/src/change_detection/differs/default_iterable_differ.ts index 22a5e4717e..2485328761 100644 --- a/modules/angular2/src/change_detection/pipes/iterable_changes.ts +++ b/modules/angular2/src/change_detection/differs/default_iterable_differ.ts @@ -1,4 +1,4 @@ -import {CONST} from 'angular2/src/facade/lang'; +import {CONST, BaseException} from 'angular2/src/facade/lang'; import { isListLikeIterable, iterateListLike, @@ -15,17 +15,16 @@ import { isArray } from 'angular2/src/facade/lang'; -import {WrappedValue, Pipe, BasePipe, PipeFactory} from './pipe'; import {ChangeDetectorRef} from '../change_detector_ref'; +import {IterableDiffer, IterableDifferFactory} from '../differs/iterable_differs'; @CONST() -export class IterableChangesFactory implements PipeFactory { - supports(obj: any): boolean { return IterableChanges.supportsObj(obj); } - - create(cdRef: ChangeDetectorRef): Pipe { return new IterableChanges(); } +export class DefaultIterableDifferFactory implements IterableDifferFactory { + supports(obj: Object): boolean { return isListLikeIterable(obj); } + create(cdRef: ChangeDetectorRef): any { return new DefaultIterableDiffer(); } } -export class IterableChanges extends BasePipe { +export class DefaultIterableDiffer implements IterableDiffer { private _collection = null; private _length: int = null; // Keeps track of the used records at any point in time (during & across `_check()` calls) @@ -42,12 +41,6 @@ export class IterableChanges extends BasePipe { private _removalsHead: CollectionChangeRecord = null; private _removalsTail: CollectionChangeRecord = null; - constructor() { super(); } - - static supportsObj(obj: Object): boolean { return isListLikeIterable(obj); } - - supports(obj: Object): boolean { return IterableChanges.supportsObj(obj); } - get collection() { return this._collection; } get length(): int { return this._length; } @@ -87,14 +80,21 @@ export class IterableChanges extends BasePipe { } } - transform(collection: any, args: List = null): any { + diff(collection: any): DefaultIterableDiffer { + if (isBlank(collection)) collection = []; + if (!isListLikeIterable(collection)) { + throw new BaseException(`Error trying to diff '${collection}'`); + } + if (this.check(collection)) { - return WrappedValue.wrap(this); + return this; } else { return null; } } + onDestroy() {} + // todo(vicb): optim for UnmodifiableListView (frozen arrays) check(collection: any): boolean { this._reset(); diff --git a/modules/angular2/src/change_detection/pipes/keyvalue_changes.ts b/modules/angular2/src/change_detection/differs/default_keyvalue_differ.ts similarity index 92% rename from modules/angular2/src/change_detection/pipes/keyvalue_changes.ts rename to modules/angular2/src/change_detection/differs/default_keyvalue_differ.ts index 9fa998b381..5d6371d445 100644 --- a/modules/angular2/src/change_detection/pipes/keyvalue_changes.ts +++ b/modules/angular2/src/change_detection/differs/default_keyvalue_differ.ts @@ -1,16 +1,23 @@ import {ListWrapper, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection'; -import {stringify, looseIdentical, isJsObject, CONST} from 'angular2/src/facade/lang'; +import { + stringify, + looseIdentical, + isJsObject, + CONST, + isBlank, + BaseException +} from 'angular2/src/facade/lang'; import {ChangeDetectorRef} from '../change_detector_ref'; -import {WrappedValue, BasePipe, Pipe, PipeFactory} from './pipe'; +import {KeyValueDiffer, KeyValueDifferFactory} from '../differs/keyvalue_differs'; @CONST() -export class KeyValueChangesFactory implements PipeFactory { - supports(obj: any): boolean { return KeyValueChanges.supportsObj(obj); } +export class DefaultKeyValueDifferFactory implements KeyValueDifferFactory { + supports(obj: any): boolean { return obj instanceof Map || isJsObject(obj); } - create(cdRef: ChangeDetectorRef): Pipe { return new KeyValueChanges(); } + create(cdRef: ChangeDetectorRef): KeyValueDiffer { return new DefaultKeyValueDiffer(); } } -export class KeyValueChanges extends BasePipe { +export class DefaultKeyValueDiffer implements KeyValueDiffer { private _records: Map = new Map(); private _mapHead: KVChangeRecord = null; private _previousMapHead: KVChangeRecord = null; @@ -21,18 +28,6 @@ export class KeyValueChanges extends BasePipe { private _removalsHead: KVChangeRecord = null; private _removalsTail: KVChangeRecord = null; - static supportsObj(obj: any): boolean { return obj instanceof Map || isJsObject(obj); } - - supports(obj: any): boolean { return KeyValueChanges.supportsObj(obj); } - - transform(map: Map, args: List = null): any { - if (this.check(map)) { - return WrappedValue.wrap(this); - } else { - return null; - } - } - get isDirty(): boolean { return this._additionsHead !== null || this._changesHead !== null || this._removalsHead !== null; @@ -73,6 +68,21 @@ export class KeyValueChanges extends BasePipe { } } + diff(map: Map): any { + if (isBlank(map)) map = MapWrapper.createFromPairs([]); + if (!(map instanceof Map || isJsObject(map))) { + throw new BaseException(`Error trying to diff '${map}'`); + } + + if (this.check(map)) { + return this; + } else { + return null; + } + } + + onDestroy() {} + check(map: Map): boolean { this._reset(); var records = this._records; diff --git a/modules/angular2/src/change_detection/differs/iterable_differs.ts b/modules/angular2/src/change_detection/differs/iterable_differs.ts new file mode 100644 index 0000000000..4ec889daeb --- /dev/null +++ b/modules/angular2/src/change_detection/differs/iterable_differs.ts @@ -0,0 +1,80 @@ +import {isBlank, isPresent, BaseException, CONST} from 'angular2/src/facade/lang'; +import {ListWrapper} from 'angular2/src/facade/collection'; +import {ChangeDetectorRef} from '../change_detector_ref'; +import {Binding, SkipSelfMetadata, OptionalMetadata, Injectable} from 'angular2/di'; + +export interface IterableDiffer { + diff(object: Object): any; + onDestroy(); +} + +/** + * Provides a factory for {@link IterableDiffer}. + */ +export interface IterableDifferFactory { + supports(objects: Object): boolean; + create(cdRef: ChangeDetectorRef): IterableDiffer; +} + +/** + * A repository of different iterable diffing strategies used by NgFor, NgClass, and others. + */ +@Injectable() +@CONST() +export class IterableDiffers { + constructor(public factories: IterableDifferFactory[]) {} + + static create(factories: IterableDifferFactory[], parent?: IterableDiffers): IterableDiffers { + if (isPresent(parent)) { + var copied = ListWrapper.clone(parent.factories); + factories = factories.concat(copied); + return new IterableDiffers(factories); + } else { + return new IterableDiffers(factories); + } + } + + /** + * Takes an array of {@link IterableDifferFactory} and returns a binding used to extend the + * inherited {@link IterableDiffers} instance with the provided factories and return a new + * {@link IterableDiffers} instance. + * + * The following example shows how to extend an existing list of factories, + * which will only be applied to the injector for this component and its children. + * This step is all that's required to make a new {@link IterableDiffer} available. + * + * # Example + * + * ``` + * @Component({ + * viewBindings: [ + * IterableDiffers.extend([new ImmutableListDiffer()]) + * ] + * }) + * ``` + */ + static extend(factories: IterableDifferFactory[]): Binding { + return new Binding(IterableDiffers, { + toFactory: (parent: IterableDiffers) => { + if (isBlank(parent)) { + // Typically would occur when calling IterableDiffers.extend inside of dependencies passed + // to + // bootstrap(), which would override default pipes instead of extending them. + throw new BaseException('Cannot extend IterableDiffers without a parent injector'); + } + return IterableDiffers.create(factories, parent); + }, + // Dependency technically isn't optional, but we can provide a better error message this way. + deps: [[IterableDiffers, new SkipSelfMetadata(), new OptionalMetadata()]] + }); + } + + find(iterable: Object): IterableDifferFactory { + var factory = ListWrapper.find(this.factories, f => f.supports(iterable)); + if (isPresent(factory)) { + return factory; + } else { + throw new BaseException(`Cannot find a differ supporting object '${iterable}'`); + } + } +} diff --git a/modules/angular2/src/change_detection/differs/keyvalue_differs.ts b/modules/angular2/src/change_detection/differs/keyvalue_differs.ts new file mode 100644 index 0000000000..5fd68fd140 --- /dev/null +++ b/modules/angular2/src/change_detection/differs/keyvalue_differs.ts @@ -0,0 +1,80 @@ +import {isBlank, isPresent, BaseException, CONST} from 'angular2/src/facade/lang'; +import {ListWrapper} from 'angular2/src/facade/collection'; +import {ChangeDetectorRef} from '../change_detector_ref'; +import {Binding, SkipSelfMetadata, OptionalMetadata, Injectable} from 'angular2/di'; + +export interface KeyValueDiffer { + diff(object: Object); + onDestroy(); +} + +/** + * Provides a factory for {@link KeyValueDiffer}. + */ +export interface KeyValueDifferFactory { + supports(objects: Object): boolean; + create(cdRef: ChangeDetectorRef): KeyValueDiffer; +} + +/** + * A repository of different Map diffing strategies used by NgClass, NgStyle, and others. + */ +@Injectable() +@CONST() +export class KeyValueDiffers { + constructor(public factories: KeyValueDifferFactory[]) {} + + static create(factories: KeyValueDifferFactory[], parent?: KeyValueDiffers): KeyValueDiffers { + if (isPresent(parent)) { + var copied = ListWrapper.clone(parent.factories); + factories = factories.concat(copied); + return new KeyValueDiffers(factories); + } else { + return new KeyValueDiffers(factories); + } + } + + /** + * Takes an array of {@link KeyValueDifferFactory} and returns a binding used to extend the + * inherited {@link KeyValueDiffers} instance with the provided factories and return a new + * {@link KeyValueDiffers} instance. + * + * The following example shows how to extend an existing list of factories, + * which will only be applied to the injector for this component and its children. + * This step is all that's required to make a new {@link KeyValueDiffer} available. + * + * # Example + * + * ``` + * @Component({ + * viewBindings: [ + * KeyValueDiffers.extend([new ImmutableMapDiffer()]) + * ] + * }) + * ``` + */ + static extend(factories: KeyValueDifferFactory[]): Binding { + return new Binding(KeyValueDiffers, { + toFactory: (parent: KeyValueDiffers) => { + if (isBlank(parent)) { + // Typically would occur when calling KeyValueDiffers.extend inside of dependencies passed + // to + // bootstrap(), which would override default pipes instead of extending them. + throw new BaseException('Cannot extend KeyValueDiffers without a parent injector'); + } + return KeyValueDiffers.create(factories, parent); + }, + // Dependency technically isn't optional, but we can provide a better error message this way. + deps: [[KeyValueDiffers, new SkipSelfMetadata(), new OptionalMetadata()]] + }); + } + + find(kv: Object): KeyValueDifferFactory { + var factory = ListWrapper.find(this.factories, f => f.supports(kv)); + if (isPresent(factory)) { + return factory; + } else { + throw new BaseException(`Cannot find a differ supporting object '${kv}'`); + } + } +} diff --git a/modules/angular2/src/core/application_common.ts b/modules/angular2/src/core/application_common.ts index da1e95d54c..3205ba65bc 100644 --- a/modules/angular2/src/core/application_common.ts +++ b/modules/angular2/src/core/application_common.ts @@ -22,7 +22,11 @@ import { JitChangeDetection, PreGeneratedChangeDetection, Pipes, - defaultPipes + defaultPipes, + IterableDiffers, + defaultIterableDiffers, + KeyValueDiffers, + defaultKeyValueDiffers } from 'angular2/src/change_detection/change_detection'; import {ExceptionHandler} from './exception_handler'; import {ViewLoader} from 'angular2/src/render/dom/compiler/view_loader'; @@ -134,6 +138,8 @@ function _injectorBindings(appComponentType): List> { CompilerCache, ViewResolver, bind(Pipes).toValue(defaultPipes), + bind(IterableDiffers).toValue(defaultIterableDiffers), + bind(KeyValueDiffers).toValue(defaultKeyValueDiffers), bind(ChangeDetection).toClass(bestChangeDetection), ViewLoader, DirectiveResolver, diff --git a/modules/angular2/src/directives/class.ts b/modules/angular2/src/directives/class.ts index 188c4e1ed7..2a9e0ab865 100644 --- a/modules/angular2/src/directives/class.ts +++ b/modules/angular2/src/directives/class.ts @@ -1,11 +1,13 @@ +import {isPresent, isString, StringWrapper, isBlank} from 'angular2/src/facade/lang'; import {Directive, LifecycleEvent} from 'angular2/annotations'; import {ElementRef} from 'angular2/core'; -import {Pipes} from 'angular2/src/change_detection/pipes/pipes'; -import {Pipe} from 'angular2/src/change_detection/pipes/pipe'; import {Renderer} from 'angular2/src/render/api'; -import {KeyValueChanges} from 'angular2/src/change_detection/pipes/keyvalue_changes'; -import {IterableChanges} from 'angular2/src/change_detection/pipes/iterable_changes'; -import {isPresent, isString, StringWrapper} from 'angular2/src/facade/lang'; +import { + KeyValueDiffer, + IterableDiffer, + IterableDiffers, + KeyValueDiffers +} from 'angular2/change_detection'; import {ListWrapper, StringMapWrapper, isListLikeIterable} from 'angular2/src/facade/collection'; /** @@ -34,10 +36,12 @@ import {ListWrapper, StringMapWrapper, isListLikeIterable} from 'angular2/src/fa properties: ['rawClass: class'] }) export class CSSClass { - _pipe: Pipe; + private _differ: any; + private _mode: string; _rawClass; - constructor(private _pipes: Pipes, private _ngEl: ElementRef, private _renderer: Renderer) {} + constructor(private _iterableDiffers: IterableDiffers, private _keyValueDiffers: KeyValueDiffers, + private _ngEl: ElementRef, private _renderer: Renderer) {} set rawClass(v) { this._cleanupClasses(this._rawClass); @@ -47,16 +51,26 @@ export class CSSClass { } this._rawClass = v; - this._pipe = this._pipes.get(isListLikeIterable(v) ? 'iterableDiff' : 'keyValDiff', v); + if (isPresent(v)) { + if (isListLikeIterable(v)) { + this._differ = this._iterableDiffers.find(v).create(null); + this._mode = 'iterable'; + } else { + this._differ = this._keyValueDiffers.find(v).create(null); + this._mode = 'keyValue'; + } + } } onCheck(): void { - var diff = this._pipe.transform(this._rawClass, null); - if (isPresent(diff) && isPresent(diff.wrapped)) { - if (diff.wrapped instanceof IterableChanges) { - this._applyArrayChanges(diff.wrapped); - } else { - this._applyObjectChanges(diff.wrapped); + if (isPresent(this._differ)) { + var changes = this._differ.diff(this._rawClass); + if (isPresent(changes)) { + if (this._mode == 'iterable') { + this._applyIterableChanges(changes); + } else { + this._applyKeyValueChanges(changes); + } } } } @@ -75,19 +89,19 @@ export class CSSClass { } } - private _applyObjectChanges(diff: KeyValueChanges): void { - diff.forEachAddedItem((record) => { this._toggleClass(record.key, record.currentValue); }); - diff.forEachChangedItem((record) => { this._toggleClass(record.key, record.currentValue); }); - diff.forEachRemovedItem((record) => { + private _applyKeyValueChanges(changes: any): void { + changes.forEachAddedItem((record) => { this._toggleClass(record.key, record.currentValue); }); + changes.forEachChangedItem((record) => { this._toggleClass(record.key, record.currentValue); }); + changes.forEachRemovedItem((record) => { if (record.previousValue) { this._toggleClass(record.key, false); } }); } - private _applyArrayChanges(diff: IterableChanges): void { - diff.forEachAddedItem((record) => { this._toggleClass(record.item, true); }); - diff.forEachRemovedItem((record) => { this._toggleClass(record.item, false); }); + private _applyIterableChanges(changes: any): void { + changes.forEachAddedItem((record) => { this._toggleClass(record.item, true); }); + changes.forEachRemovedItem((record) => { this._toggleClass(record.item, false); }); } private _toggleClass(className: string, enabled): void { diff --git a/modules/angular2/src/directives/ng_for.ts b/modules/angular2/src/directives/ng_for.ts index 035c511099..af6f126702 100644 --- a/modules/angular2/src/directives/ng_for.ts +++ b/modules/angular2/src/directives/ng_for.ts @@ -1,6 +1,6 @@ import {Directive, LifecycleEvent} from 'angular2/annotations'; import {ViewContainerRef, ViewRef, TemplateRef} from 'angular2/core'; -import {ChangeDetectorRef, Pipe, Pipes} from 'angular2/change_detection'; +import {ChangeDetectorRef, IterableDiffer, IterableDiffers} from 'angular2/change_detection'; import {isPresent, isBlank} from 'angular2/src/facade/lang'; /** @@ -37,27 +37,26 @@ import {isPresent, isBlank} from 'angular2/src/facade/lang'; {selector: '[ng-for][ng-for-of]', properties: ['ngForOf'], lifecycle: [LifecycleEvent.onCheck]}) export class NgFor { _ngForOf: any; - _pipe: Pipe; + private _differ: IterableDiffer; constructor(private viewContainer: ViewContainerRef, private templateRef: TemplateRef, - private pipes: Pipes, private cdr: ChangeDetectorRef) {} + private iterableDiffers: IterableDiffers, private cdr: ChangeDetectorRef) {} set ngForOf(value: any) { this._ngForOf = value; - this._pipe = this.pipes.get("iterableDiff", value, this.cdr, this._pipe); + if (isBlank(this._differ) && isPresent(value)) { + this._differ = this.iterableDiffers.find(value).create(this.cdr); + } } onCheck() { - var diff = this._pipe.transform(this._ngForOf, null); - if (isPresent(diff)) this._applyChanges(diff.wrapped); + if (isPresent(this._differ)) { + var changes = this._differ.diff(this._ngForOf); + if (isPresent(changes)) this._applyChanges(changes); + } } private _applyChanges(changes) { - if (isBlank(changes)) { - this.viewContainer.clear(); - return; - } - // TODO(rado): check if change detection can produce a change record that is // easier to consume than current. var recordViewTuples = []; diff --git a/modules/angular2/src/directives/ng_style.ts b/modules/angular2/src/directives/ng_style.ts index f1a60df0f9..2408d6fd5c 100644 --- a/modules/angular2/src/directives/ng_style.ts +++ b/modules/angular2/src/directives/ng_style.ts @@ -1,9 +1,7 @@ import {Directive, LifecycleEvent} from 'angular2/annotations'; import {ElementRef} from 'angular2/core'; -import {Pipe} from 'angular2/src/change_detection/pipes/pipe'; -import {Pipes} from 'angular2/src/change_detection/pipes/pipes'; -import {KeyValueChanges} from 'angular2/src/change_detection/pipes/keyvalue_changes'; -import {isPresent, print} from 'angular2/src/facade/lang'; +import {KeyValueDiffer, KeyValueDiffers} from 'angular2/change_detection'; +import {isPresent, isBlank, print} from 'angular2/src/facade/lang'; import {Renderer} from 'angular2/src/render/api'; /** @@ -33,27 +31,32 @@ import {Renderer} from 'angular2/src/render/api'; properties: ['rawStyle: ng-style'] }) export class NgStyle { - _pipe: Pipe; _rawStyle; + _differ: KeyValueDiffer; - constructor(private _pipes: Pipes, private _ngEl: ElementRef, private _renderer: Renderer) {} + constructor(private _differs: KeyValueDiffers, private _ngEl: ElementRef, + private _renderer: Renderer) {} set rawStyle(v) { this._rawStyle = v; - this._pipe = this._pipes.get('keyValDiff', this._rawStyle); - } - - onCheck() { - var diff = this._pipe.transform(this._rawStyle, null); - if (isPresent(diff) && isPresent(diff.wrapped)) { - this._applyChanges(diff.wrapped); + if (isBlank(this._differ) && isPresent(v)) { + this._differ = this._differs.find(this._rawStyle).create(null); } } - private _applyChanges(diff: KeyValueChanges): void { - diff.forEachAddedItem((record) => { this._setStyle(record.key, record.currentValue); }); - diff.forEachChangedItem((record) => { this._setStyle(record.key, record.currentValue); }); - diff.forEachRemovedItem((record) => { this._setStyle(record.key, null); }); + onCheck() { + if (isPresent(this._differ)) { + var changes = this._differ.diff(this._rawStyle); + if (isPresent(changes)) { + this._applyChanges(changes); + } + } + } + + private _applyChanges(changes: any): void { + changes.forEachAddedItem((record) => { this._setStyle(record.key, record.currentValue); }); + changes.forEachChangedItem((record) => { this._setStyle(record.key, record.currentValue); }); + changes.forEachRemovedItem((record) => { this._setStyle(record.key, null); }); } private _setStyle(name: string, val: string): void { diff --git a/modules/angular2/src/directives/observable_list_diff.dart b/modules/angular2/src/directives/observable_list_diff.dart index b0a076758d..1bf446575c 100644 --- a/modules/angular2/src/directives/observable_list_diff.dart +++ b/modules/angular2/src/directives/observable_list_diff.dart @@ -2,10 +2,10 @@ library angular2.directives.observable_list_iterable_diff; import 'package:observe/observe.dart' show ObservableList; import 'package:angular2/change_detection.dart'; -import 'package:angular2/src/change_detection/pipes/iterable_changes.dart'; +import 'package:angular2/src/change_detection/differs/default_iterable_differ.dart'; import 'dart:async'; -class ObservableListDiff extends IterableChanges { +class ObservableListDiff extends DefaultIterableDiffer { ChangeDetectorRef _ref; ObservableListDiff(this._ref); @@ -13,11 +13,6 @@ class ObservableListDiff extends IterableChanges { ObservableList _collection; StreamSubscription _subscription; - bool supports(obj) { - if (obj is ObservableList) return true; - throw "Cannot change the type of a collection"; - } - onDestroy() { if (this._subscription != null) { this._subscription.cancel(); @@ -26,10 +21,14 @@ class ObservableListDiff extends IterableChanges { } } - dynamic transform(ObservableList collection, [List args]) { + dynamic diff(ObservableList collection) { + if (collection is! ObservableList) { + throw "Cannot change the type of a collection"; + } + // A new collection instance is passed in. // - We need to set up a listener. - // - We need to transform collection. + // - We need to diff collection. if (!identical(_collection, collection)) { _collection = collection; @@ -39,14 +38,14 @@ class ObservableListDiff extends IterableChanges { _ref.requestCheck(); }); _updated = false; - return super.transform(collection, args); + return super.diff(collection); // An update has been registered since the last change detection check. // - We reset the flag. // - We diff the collection. } else if (_updated) { _updated = false; - return super.transform(collection, args); + return super.diff(collection); // No updates has been registered. // Returning this tells change detection that object has not change, @@ -57,10 +56,10 @@ class ObservableListDiff extends IterableChanges { } } -class ObservableListDiffFactory implements PipeFactory { +class ObservableListDiffFactory implements IterableDifferFactory { const ObservableListDiffFactory(); bool supports(obj) => obj is ObservableList; - Pipe create(ChangeDetectorRef cdRef) { + IterableDiffer create(ChangeDetectorRef cdRef) { return new ObservableListDiff(cdRef); } } diff --git a/modules/angular2/src/test_lib/spies.dart b/modules/angular2/src/test_lib/spies.dart index a5e67df025..3aa7e07ce2 100644 --- a/modules/angular2/src/test_lib/spies.dart +++ b/modules/angular2/src/test_lib/spies.dart @@ -33,3 +33,8 @@ class SpyDependencyProvider extends SpyObject implements DependencyProvider { class SpyChangeDetectorRef extends SpyObject implements ChangeDetectorRef { noSuchMethod(m) => super.noSuchMethod(m); } + +@proxy +class SpyIterableDifferFactory extends SpyObject implements IterableDifferFactory { + noSuchMethod(m) => super.noSuchMethod(m); +} diff --git a/modules/angular2/src/test_lib/spies.ts b/modules/angular2/src/test_lib/spies.ts index 3a086cac11..391bd9e1d2 100644 --- a/modules/angular2/src/test_lib/spies.ts +++ b/modules/angular2/src/test_lib/spies.ts @@ -24,3 +24,5 @@ export class SpyPipe extends SpyObject { export class SpyPipeFactory extends SpyObject {} export class SpyDependencyProvider extends SpyObject {} + +export class SpyIterableDifferFactory extends SpyObject {} diff --git a/modules/angular2/src/test_lib/test_injector.ts b/modules/angular2/src/test_lib/test_injector.ts index d7c4a9ea2e..14294e7428 100644 --- a/modules/angular2/src/test_lib/test_injector.ts +++ b/modules/angular2/src/test_lib/test_injector.ts @@ -8,7 +8,11 @@ import { ChangeDetection, DynamicChangeDetection, Pipes, - defaultPipes + defaultPipes, + IterableDiffers, + defaultIterableDiffers, + KeyValueDiffers, + defaultKeyValueDiffers } from 'angular2/src/change_detection/change_detection'; import {ExceptionHandler} from 'angular2/src/core/exception_handler'; import {ViewLoader} from 'angular2/src/render/dom/compiler/view_loader'; @@ -119,8 +123,10 @@ function _getAppBindings() { CompilerCache, bind(ViewResolver).toClass(MockViewResolver), bind(Pipes).toValue(defaultPipes), - Log, + bind(IterableDiffers).toValue(defaultIterableDiffers), + bind(KeyValueDiffers).toValue(defaultKeyValueDiffers), bind(ChangeDetection).toClass(DynamicChangeDetection), + Log, ViewLoader, DynamicComponentLoader, DirectiveResolver, diff --git a/modules/angular2/test/change_detection/pipes/iterable_changes_spec.ts b/modules/angular2/test/change_detection/differs/default_iterable_differ_spec.ts similarity index 68% rename from modules/angular2/test/change_detection/pipes/iterable_changes_spec.ts rename to modules/angular2/test/change_detection/differs/default_iterable_differ_spec.ts index a1cc9d57ff..fb0040054c 100644 --- a/modules/angular2/test/change_detection/pipes/iterable_changes_spec.ts +++ b/modules/angular2/test/change_detection/differs/default_iterable_differ_spec.ts @@ -1,5 +1,8 @@ -import {describe, it, iit, xit, expect, beforeEach, afterEach} from 'angular2/test_lib'; -import {IterableChanges} from 'angular2/src/change_detection/pipes/iterable_changes'; +import {ddescribe, describe, it, iit, xit, expect, beforeEach, afterEach} from 'angular2/test_lib'; +import { + DefaultIterableDiffer, + DefaultIterableDifferFactory +} from 'angular2/src/change_detection/differs/default_iterable_differ'; import {NumberWrapper} from 'angular2/src/facade/lang'; import {ListWrapper, MapWrapper} from 'angular2/src/facade/collection'; @@ -9,36 +12,35 @@ import {iterableChangesAsString} from '../util'; // todo(vicb): UnmodifiableListView / frozen object when implemented export function main() { - describe('collection_changes', function() { - describe('CollectionChanges', function() { - var changes; + describe('iterable differ', function() { + describe('DefaultIterableDiffer', function() { + var differ; - beforeEach(() => { changes = new IterableChanges(); }); - - afterEach(() => { changes = null; }); + beforeEach(() => { differ = new DefaultIterableDiffer(); }); it('should support list and iterables', () => { - expect(IterableChanges.supportsObj([])).toBeTruthy(); - expect(IterableChanges.supportsObj(new TestIterable())).toBeTruthy(); - expect(IterableChanges.supportsObj(new Map())).toBeFalsy(); - expect(IterableChanges.supportsObj(null)).toBeFalsy(); + var f = new DefaultIterableDifferFactory(); + expect(f.supports([])).toBeTruthy(); + expect(f.supports(new TestIterable())).toBeTruthy(); + expect(f.supports(new Map())).toBeFalsy(); + expect(f.supports(null)).toBeFalsy(); }); it('should support iterables', () => { let l = new TestIterable(); - changes.check(l); - expect(changes.toString()).toEqual(iterableChangesAsString({collection: []})); + differ.check(l); + expect(differ.toString()).toEqual(iterableChangesAsString({collection: []})); l.list = [1]; - changes.check(l); - expect(changes.toString()) + differ.check(l); + expect(differ.toString()) .toEqual( iterableChangesAsString({collection: ['1[null->0]'], additions: ['1[null->0]']})); l.list = [2, 1]; - changes.check(l); - expect(changes.toString()) + differ.check(l); + expect(differ.toString()) .toEqual(iterableChangesAsString({ collection: ['2[null->0]', '1[0->1]'], previous: ['1[0->1]'], @@ -49,29 +51,29 @@ export function main() { it('should detect additions', () => { let l = []; - changes.check(l); - expect(changes.toString()).toEqual(iterableChangesAsString({collection: []})); + differ.check(l); + expect(differ.toString()).toEqual(iterableChangesAsString({collection: []})); l.push('a'); - changes.check(l); - expect(changes.toString()) + differ.check(l); + expect(differ.toString()) .toEqual( iterableChangesAsString({collection: ['a[null->0]'], additions: ['a[null->0]']})); l.push('b'); - changes.check(l); - expect(changes.toString()) + differ.check(l); + expect(differ.toString()) .toEqual(iterableChangesAsString( {collection: ['a', 'b[null->1]'], previous: ['a'], additions: ['b[null->1]']})); }); it('should support changing the reference', () => { let l = [0]; - changes.check(l); + differ.check(l); l = [1, 0]; - changes.check(l); - expect(changes.toString()) + differ.check(l); + expect(differ.toString()) .toEqual(iterableChangesAsString({ collection: ['1[null->0]', '0[0->1]'], previous: ['0[0->1]'], @@ -80,8 +82,8 @@ export function main() { })); l = [2, 1, 0]; - changes.check(l); - expect(changes.toString()) + differ.check(l); + expect(differ.toString()) .toEqual(iterableChangesAsString({ collection: ['2[null->0]', '1[0->1]', '0[1->2]'], previous: ['1[0->1]', '0[1->2]'], @@ -92,13 +94,13 @@ export function main() { it('should handle swapping element', () => { let l = [1, 2]; - changes.check(l); + differ.check(l); ListWrapper.clear(l); l.push(2); l.push(1); - changes.check(l); - expect(changes.toString()) + differ.check(l); + expect(differ.toString()) .toEqual(iterableChangesAsString({ collection: ['2[1->0]', '1[0->1]'], previous: ['1[0->1]', '2[1->0]'], @@ -108,12 +110,12 @@ export function main() { it('should handle swapping element', () => { let l = ['a', 'b', 'c']; - changes.check(l); + differ.check(l); ListWrapper.removeAt(l, 1); ListWrapper.insert(l, 0, 'b'); - changes.check(l); - expect(changes.toString()) + differ.check(l); + expect(differ.toString()) .toEqual(iterableChangesAsString({ collection: ['b[1->0]', 'a[0->1]', 'c'], previous: ['a[0->1]', 'b[1->0]', 'c'], @@ -122,8 +124,8 @@ export function main() { ListWrapper.removeAt(l, 1); l.push('a'); - changes.check(l); - expect(changes.toString()) + differ.check(l); + expect(differ.toString()) .toEqual(iterableChangesAsString({ collection: ['b', 'c[2->1]', 'a[1->2]'], previous: ['b', 'a[1->2]', 'c[2->1]'], @@ -133,24 +135,24 @@ export function main() { it('should detect changes in list', () => { let l = []; - changes.check(l); + differ.check(l); l.push('a'); - changes.check(l); - expect(changes.toString()) + differ.check(l); + expect(differ.toString()) .toEqual( iterableChangesAsString({collection: ['a[null->0]'], additions: ['a[null->0]']})); l.push('b'); - changes.check(l); - expect(changes.toString()) + differ.check(l); + expect(differ.toString()) .toEqual(iterableChangesAsString( {collection: ['a', 'b[null->1]'], previous: ['a'], additions: ['b[null->1]']})); l.push('c'); l.push('d'); - changes.check(l); - expect(changes.toString()) + differ.check(l); + expect(differ.toString()) .toEqual(iterableChangesAsString({ collection: ['a', 'b', 'c[null->2]', 'd[null->3]'], previous: ['a', 'b'], @@ -158,8 +160,8 @@ export function main() { })); ListWrapper.removeAt(l, 2); - changes.check(l); - expect(changes.toString()) + differ.check(l); + expect(differ.toString()) .toEqual(iterableChangesAsString({ collection: ['a', 'b', 'd[3->2]'], previous: ['a', 'b', 'c[2->null]', 'd[3->2]'], @@ -172,8 +174,8 @@ export function main() { l.push('c'); l.push('b'); l.push('a'); - changes.check(l); - expect(changes.toString()) + differ.check(l); + expect(differ.toString()) .toEqual(iterableChangesAsString({ collection: ['d[2->0]', 'c[null->1]', 'b[1->2]', 'a[0->3]'], previous: ['a[0->3]', 'b[1->2]', 'd[2->0]'], @@ -184,32 +186,32 @@ export function main() { it('should test string by value rather than by reference (Dart)', () => { let l = ['a', 'boo']; - changes.check(l); + differ.check(l); var b = 'b'; var oo = 'oo'; ListWrapper.set(l, 1, b + oo); - changes.check(l); - expect(changes.toString()) + differ.check(l); + expect(differ.toString()) .toEqual(iterableChangesAsString({collection: ['a', 'boo'], previous: ['a', 'boo']})); }); it('should ignore [NaN] != [NaN] (JS)', () => { let l = [NumberWrapper.NaN]; - changes.check(l); - changes.check(l); - expect(changes.toString()) + differ.check(l); + differ.check(l); + expect(differ.toString()) .toEqual(iterableChangesAsString( {collection: [NumberWrapper.NaN], previous: [NumberWrapper.NaN]})); }); it('should detect [NaN] moves', () => { let l = [NumberWrapper.NaN, NumberWrapper.NaN]; - changes.check(l); + differ.check(l); ListWrapper.insert(l, 0, 'foo'); - changes.check(l); - expect(changes.toString()) + differ.check(l); + expect(differ.toString()) .toEqual(iterableChangesAsString({ collection: ['foo[null->0]', 'NaN[0->1]', 'NaN[1->2]'], previous: ['NaN[0->1]', 'NaN[1->2]'], @@ -220,11 +222,11 @@ export function main() { it('should remove and add same item', () => { let l = ['a', 'b', 'c']; - changes.check(l); + differ.check(l); ListWrapper.removeAt(l, 1); - changes.check(l); - expect(changes.toString()) + differ.check(l); + expect(differ.toString()) .toEqual(iterableChangesAsString({ collection: ['a', 'c[2->1]'], previous: ['a', 'b[1->null]', 'c[2->1]'], @@ -233,8 +235,8 @@ export function main() { })); ListWrapper.insert(l, 1, 'b'); - changes.check(l); - expect(changes.toString()) + differ.check(l); + expect(differ.toString()) .toEqual(iterableChangesAsString({ collection: ['a', 'b[null->1]', 'c[1->2]'], previous: ['a', 'c[1->2]'], @@ -245,11 +247,11 @@ export function main() { it('should support duplicates', () => { let l = ['a', 'a', 'a', 'b', 'b']; - changes.check(l); + differ.check(l); ListWrapper.removeAt(l, 0); - changes.check(l); - expect(changes.toString()) + differ.check(l); + expect(differ.toString()) .toEqual(iterableChangesAsString({ collection: ['a', 'a', 'b[3->2]', 'b[4->3]'], previous: ['a', 'a', 'a[2->null]', 'b[3->2]', 'b[4->3]'], @@ -260,11 +262,11 @@ export function main() { it('should support insertions/moves', () => { let l = ['a', 'a', 'b', 'b']; - changes.check(l); + differ.check(l); ListWrapper.insert(l, 0, 'b'); - changes.check(l); - expect(changes.toString()) + differ.check(l); + expect(differ.toString()) .toEqual(iterableChangesAsString({ collection: ['b[2->0]', 'a[0->1]', 'a[1->2]', 'b', 'b[null->4]'], previous: ['a[0->1]', 'a[1->2]', 'b[2->0]', 'b'], @@ -275,20 +277,43 @@ export function main() { it('should not report unnecessary moves', () => { let l = ['a', 'b', 'c']; - changes.check(l); + differ.check(l); ListWrapper.clear(l); l.push('b'); l.push('a'); l.push('c'); - changes.check(l); - expect(changes.toString()) + differ.check(l); + expect(differ.toString()) .toEqual(iterableChangesAsString({ collection: ['b[1->0]', 'a[0->1]', 'c'], previous: ['a[0->1]', 'b[1->0]', 'c'], moves: ['b[1->0]', 'a[0->1]'] })); }); + + describe('diff', () => { + it('should return self when there is a change', + () => { expect(differ.diff(['a', 'b'])).toBe(differ); }); + + it('should return null when there is no change', () => { + differ.diff(['a', 'b']); + expect(differ.diff(['a', 'b'])).toEqual(null); + }); + + it('should treat null as an empty list', () => { + differ.diff(['a', 'b']); + expect(differ.diff(null).toString()) + .toEqual(iterableChangesAsString({ + previous: ['a[0->null]', 'b[1->null]'], + removals: ['a[0->null]', 'b[1->null]'] + })); + }); + + it('should throw when given an invalid collection', () => { + expect(() => differ.diff("invalid")).toThrowErrorWith("Error trying to diff 'invalid'"); + }); + }); }); }); } diff --git a/modules/angular2/test/change_detection/pipes/keyvalue_changes_spec.ts b/modules/angular2/test/change_detection/differs/default_keyvalue_differ_spec.ts similarity index 58% rename from modules/angular2/test/change_detection/pipes/keyvalue_changes_spec.ts rename to modules/angular2/test/change_detection/differs/default_keyvalue_differ_spec.ts index 6edf7d1b60..4c6d348bf5 100644 --- a/modules/angular2/test/change_detection/pipes/keyvalue_changes_spec.ts +++ b/modules/angular2/test/change_detection/differs/default_keyvalue_differ_spec.ts @@ -1,34 +1,37 @@ -import {describe, it, iit, xit, expect, beforeEach, afterEach} from 'angular2/test_lib'; -import {KeyValueChanges} from 'angular2/src/change_detection/pipes/keyvalue_changes'; +import {ddescribe, describe, it, iit, xit, expect, beforeEach, afterEach} from 'angular2/test_lib'; +import { + DefaultKeyValueDiffer, + DefaultKeyValueDifferFactory +} from 'angular2/src/change_detection/differs/default_keyvalue_differ'; import {NumberWrapper, isJsObject} from 'angular2/src/facade/lang'; import {MapWrapper} from 'angular2/src/facade/collection'; import {kvChangesAsString} from '../util'; // todo(vicb): Update the code & tests for object equality export function main() { - describe('keyvalue_changes', function() { - describe('KeyValueChanges', function() { - var changes; + describe('keyvalue differ', function() { + describe('DefaultKeyValueDiffer', function() { + var differ; var m: Map; beforeEach(() => { - changes = new KeyValueChanges(); + differ = new DefaultKeyValueDiffer(); m = new Map(); }); - afterEach(() => { changes = null; }); + afterEach(() => { differ = null; }); it('should detect additions', () => { - changes.check(m); + differ.check(m); m.set('a', 1); - changes.check(m); - expect(changes.toString()) + differ.check(m); + expect(differ.toString()) .toEqual(kvChangesAsString({map: ['a[null->1]'], additions: ['a[null->1]']})); m.set('b', 2); - changes.check(m); - expect(changes.toString()) + differ.check(m); + expect(differ.toString()) .toEqual(kvChangesAsString( {map: ['a', 'b[null->2]'], previous: ['a'], additions: ['b[null->2]']})); }); @@ -36,12 +39,12 @@ export function main() { it('should handle changing key/values correctly', () => { m.set(1, 10); m.set(2, 20); - changes.check(m); + differ.check(m); m.set(2, 10); m.set(1, 20); - changes.check(m); - expect(changes.toString()) + differ.check(m); + expect(differ.toString()) .toEqual(kvChangesAsString({ map: ['1[10->20]', '2[20->10]'], previous: ['1[10->20]', '2[20->10]'], @@ -53,12 +56,12 @@ export function main() { var previous, current; m.set(1, 10); - changes.check(m); + differ.check(m); m.set(1, 20); - changes.check(m); + differ.check(m); - changes.forEachChangedItem((record) => { + differ.forEachChangedItem((record) => { previous = record.previousValue; current = record.currentValue; }); @@ -68,23 +71,23 @@ export function main() { }); it('should do basic map watching', () => { - changes.check(m); + differ.check(m); m.set('a', 'A'); - changes.check(m); - expect(changes.toString()) + differ.check(m); + expect(differ.toString()) .toEqual(kvChangesAsString({map: ['a[null->A]'], additions: ['a[null->A]']})); m.set('b', 'B'); - changes.check(m); - expect(changes.toString()) + differ.check(m); + expect(differ.toString()) .toEqual(kvChangesAsString( {map: ['a', 'b[null->B]'], previous: ['a'], additions: ['b[null->B]']})); m.set('b', 'BB'); m.set('d', 'D'); - changes.check(m); - expect(changes.toString()) + differ.check(m); + expect(differ.toString()) .toEqual(kvChangesAsString({ map: ['a', 'b[B->BB]', 'd[null->D]'], previous: ['a', 'b[B->BB]'], @@ -93,21 +96,21 @@ export function main() { })); MapWrapper.delete(m, 'b'); - changes.check(m); - expect(changes.toString()) + differ.check(m); + expect(differ.toString()) .toEqual(kvChangesAsString( {map: ['a', 'd'], previous: ['a', 'b[BB->null]', 'd'], removals: ['b[BB->null]']})); m.clear(); - changes.check(m); - expect(changes.toString()) + differ.check(m); + expect(differ.toString()) .toEqual(kvChangesAsString( {previous: ['a[A->null]', 'd[D->null]'], removals: ['a[A->null]', 'd[D->null]']})); }); it('should test string by value rather than by reference (DART)', () => { m.set('foo', 'bar'); - changes.check(m); + differ.check(m); var f = 'f'; var oo = 'oo'; @@ -115,48 +118,49 @@ export function main() { var ar = 'ar'; m.set(f + oo, b + ar); - changes.check(m); + differ.check(m); - expect(changes.toString()).toEqual(kvChangesAsString({map: ['foo'], previous: ['foo']})); + expect(differ.toString()).toEqual(kvChangesAsString({map: ['foo'], previous: ['foo']})); }); it('should not see a NaN value as a change (JS)', () => { m.set('foo', NumberWrapper.NaN); - changes.check(m); + differ.check(m); - changes.check(m); - expect(changes.toString()).toEqual(kvChangesAsString({map: ['foo'], previous: ['foo']})); + differ.check(m); + expect(differ.toString()).toEqual(kvChangesAsString({map: ['foo'], previous: ['foo']})); }); // JS specific tests (JS Objects) if (isJsObject({})) { describe('JsObject changes', () => { it('should support JS Object', () => { - expect(KeyValueChanges.supportsObj({})).toBeTruthy(); - expect(KeyValueChanges.supportsObj("not supported")).toBeFalsy(); - expect(KeyValueChanges.supportsObj(0)).toBeFalsy(); - expect(KeyValueChanges.supportsObj(null)).toBeFalsy(); + var f = new DefaultKeyValueDifferFactory(); + expect(f.supports({})).toBeTruthy(); + expect(f.supports("not supported")).toBeFalsy(); + expect(f.supports(0)).toBeFalsy(); + expect(f.supports(null)).toBeFalsy(); }); it('should do basic object watching', () => { let m = {}; - changes.check(m); + differ.check(m); m['a'] = 'A'; - changes.check(m); - expect(changes.toString()) + differ.check(m); + expect(differ.toString()) .toEqual(kvChangesAsString({map: ['a[null->A]'], additions: ['a[null->A]']})); m['b'] = 'B'; - changes.check(m); - expect(changes.toString()) + differ.check(m); + expect(differ.toString()) .toEqual(kvChangesAsString( {map: ['a', 'b[null->B]'], previous: ['a'], additions: ['b[null->B]']})); m['b'] = 'BB'; m['d'] = 'D'; - changes.check(m); - expect(changes.toString()) + differ.check(m); + expect(differ.toString()) .toEqual(kvChangesAsString({ map: ['a', 'b[B->BB]', 'd[null->D]'], previous: ['a', 'b[B->BB]'], @@ -167,8 +171,8 @@ export function main() { m = {}; m['a'] = 'A'; m['d'] = 'D'; - changes.check(m); - expect(changes.toString()) + differ.check(m); + expect(differ.toString()) .toEqual(kvChangesAsString({ map: ['a', 'd'], previous: ['a', 'b[BB->null]', 'd'], @@ -176,14 +180,38 @@ export function main() { })); m = {}; - changes.check(m); - expect(changes.toString()) + differ.check(m); + expect(differ.toString()) .toEqual(kvChangesAsString({ previous: ['a[A->null]', 'd[D->null]'], removals: ['a[A->null]', 'd[D->null]'] })); }); }); + + describe('diff', () => { + it('should return self when there is a change', () => { + m.set('a', 'A'); + expect(differ.diff(m)).toBe(differ); + }); + + it('should return null when there is no change', () => { + m.set('a', 'A'); + differ.diff(m); + expect(differ.diff(m)).toEqual(null); + }); + + it('should treat null as an empty list', () => { + m.set('a', 'A'); + differ.diff(m); + expect(differ.diff(null).toString()) + .toEqual(kvChangesAsString({previous: ['a[A->null]'], removals: ['a[A->null]']})); + }); + + it('should throw when given an invalid collection', () => { + expect(() => differ.diff("invalid")).toThrowErrorWith("Error trying to diff 'invalid'"); + }); + }); } }); }); diff --git a/modules/angular2/test/change_detection/differs/iterable_differs_spec.ts b/modules/angular2/test/change_detection/differs/iterable_differs_spec.ts new file mode 100644 index 0000000000..493d086a83 --- /dev/null +++ b/modules/angular2/test/change_detection/differs/iterable_differs_spec.ts @@ -0,0 +1,70 @@ +import { + ddescribe, + describe, + it, + iit, + xit, + expect, + beforeEach, + afterEach, + SpyIterableDifferFactory +} from 'angular2/test_lib'; +import {IterableDiffers} from 'angular2/src/change_detection/differs/iterable_differs'; +import {Injector, bind} from 'angular2/di'; + +export function main() { + describe('IterableDiffers', function() { + var factory1; + var factory2; + var factory3; + + beforeEach(() => { + factory1 = new SpyIterableDifferFactory(); + factory2 = new SpyIterableDifferFactory(); + factory3 = new SpyIterableDifferFactory(); + }); + + it('should throw when no suitable implementation found', () => { + var differs = new IterableDiffers([]); + expect(() => differs.find("some object")) + .toThrowErrorWith("Cannot find a differ supporting object 'some object'") + }); + + it('should return the first suitable implementation', () => { + factory1.spy("supports").andReturn(false); + factory2.spy("supports").andReturn(true); + factory3.spy("supports").andReturn(true); + + var differs = IterableDiffers.create([factory1, factory2, factory3]); + expect(differs.find("some object")).toBe(factory2); + }); + + it('should copy over differs from the parent repo', () => { + factory1.spy("supports").andReturn(true); + factory2.spy("supports").andReturn(false); + + var parent = IterableDiffers.create([factory1]); + var child = IterableDiffers.create([factory2], parent); + + expect(child.factories).toEqual([factory2, factory1]); + }); + + describe(".extend()", () => { + it('should throw if calling extend when creating root injector', () => { + var injector = Injector.resolveAndCreate([IterableDiffers.extend([])]); + + expect(() => injector.get(IterableDiffers)) + .toThrowErrorWith("Cannot extend IterableDiffers without a parent injector"); + }); + + it('should extend di-inherited diffesr', () => { + var parent = new IterableDiffers([factory1]); + var injector = Injector.resolveAndCreate([bind(IterableDiffers).toValue(parent)]); + var childInjector = injector.resolveAndCreateChild([IterableDiffers.extend([factory2])]); + + expect(injector.get(IterableDiffers).factories).toEqual([factory1]); + expect(childInjector.get(IterableDiffers).factories).toEqual([factory2, factory1]); + }); + }); + }); +} diff --git a/modules/angular2/test/core/compiler/integration_dart_spec.dart b/modules/angular2/test/core/compiler/integration_dart_spec.dart index afb798559e..bca7093158 100644 --- a/modules/angular2/test/core/compiler/integration_dart_spec.dart +++ b/modules/angular2/test/core/compiler/integration_dart_spec.dart @@ -6,7 +6,7 @@ import 'package:angular2/di.dart'; import 'package:angular2/test_lib.dart'; import 'package:observe/observe.dart'; import 'package:angular2/src/directives/observable_list_diff.dart'; -import 'package:angular2/src/change_detection/pipes/iterable_changes.dart'; +import 'package:angular2/src/change_detection/differs/default_iterable_differ.dart'; class MockException implements Error { var message; @@ -269,14 +269,12 @@ class OnChangeComponent implements OnChange { changeDetection: ON_PUSH, properties: const ['list'], bindings: const [ - const Binding(Pipes, - toValue: const Pipes(const { - "iterableDiff": const [ + const Binding(IterableDiffers, + toValue: const IterableDiffers(const [ const ObservableListDiffFactory(), - const IterableChangesFactory(), - const NullPipeFactory() + const DefaultIterableDifferFactory() ] - })) + )) ]) @View( template: '{{item}}', diff --git a/modules/angular2/test/directives/observable_list_diff_spec.dart b/modules/angular2/test/directives/observable_list_diff_spec.dart index 019fa13ae3..faad5ed774 100644 --- a/modules/angular2/test/directives/observable_list_diff_spec.dart +++ b/modules/angular2/test/directives/observable_list_diff_spec.dart @@ -6,64 +6,64 @@ import 'package:angular2/src/directives/observable_list_diff.dart'; main() { describe('ObservableListDiff', () { - var pipeFactory, changeDetectorRef; + var factory, changeDetectorRef; beforeEach(() { - pipeFactory = const ObservableListDiffFactory(); + factory = const ObservableListDiffFactory(); changeDetectorRef = new SpyChangeDetectorRef(); }); describe("supports", () { it("should be true for ObservableList", () { - expect(pipeFactory.supports(new ObservableList())).toBe(true); + expect(factory.supports(new ObservableList())).toBe(true); }); it("should be false otherwise", () { - expect(pipeFactory.supports([1, 2, 3])).toBe(false); + expect(factory.supports([1, 2, 3])).toBe(false); }); }); - it("should return the wrapped value to trigger change detection on first invocation of transform", + it("should return itself when called the first time", () { - final pipe = pipeFactory.create(changeDetectorRef); + final d = factory.create(changeDetectorRef); final c = new ObservableList.from([1, 2]); - expect(pipe.transform(c, []).wrapped).toBe(pipe); + expect(d.diff(c)).toBe(d); }); it("should return itself when no changes between the calls", () { - final pipe = pipeFactory.create(changeDetectorRef); + final d = factory.create(changeDetectorRef); final c = new ObservableList.from([1, 2]); - pipe.transform(c, []); + d.diff(c); - expect(pipe.transform(c, [])).toBe(pipe); + expect(d.diff(c)).toBe(d); }); - it("should return the wrapped value once a change has been trigger", + it("should return the wrapped value once a change has been triggered", fakeAsync(() { - final pipe = pipeFactory.create(changeDetectorRef); + final d = factory.create(changeDetectorRef); final c = new ObservableList.from([1, 2]); - pipe.transform(c, []); + d.diff(c); c.add(3); // same value, because we have not detected the change yet - expect(pipe.transform(c, [])).toBe(pipe); + expect(d.diff(c)).toBe(d); // now we detect the change flushMicrotasks(); - expect(pipe.transform(c, []).wrapped).toBe(pipe); + expect(d.diff(c)).toBe(d); })); it("should request a change detection check upon receiving a change", fakeAsync(() { - final pipe = pipeFactory.create(changeDetectorRef); + final d = factory.create(changeDetectorRef); final c = new ObservableList.from([1, 2]); - pipe.transform(c, []); + d.diff(c); c.add(3); flushMicrotasks(); @@ -72,28 +72,28 @@ main() { })); it("should return the wrapped value after changing a collection", () { - final pipe = pipeFactory.create(changeDetectorRef); + final d = factory.create(changeDetectorRef); final c1 = new ObservableList.from([1, 2]); final c2 = new ObservableList.from([3, 4]); - expect(pipe.transform(c1, []).wrapped).toBe(pipe); - expect(pipe.transform(c2, []).wrapped).toBe(pipe); + expect(d.diff(c1)).toBe(d); + expect(d.diff(c2)).toBe(d); }); it("should not unbsubscribe from the stream of chagnes after changing a collection", () { - final pipe = pipeFactory.create(changeDetectorRef); + final d = factory.create(changeDetectorRef); final c1 = new ObservableList.from([1, 2]); - expect(pipe.transform(c1, []).wrapped).toBe(pipe); + expect(d.diff(c1)).toBe(d); final c2 = new ObservableList.from([3, 4]); - expect(pipe.transform(c2, []).wrapped).toBe(pipe); + expect(d.diff(c2)).toBe(d); // pushing into the first collection has no effect, and we do not see the change c1.add(3); - expect(pipe.transform(c2, [])).toBe(pipe); + expect(d.diff(c2)).toBe(d); }); }); }