feat(change_detection): added support for ObservableList from package:observe
This commit is contained in:
parent
583c5ffcb5
commit
d449ea5ca4
@ -19,6 +19,7 @@ dependencies:
|
|||||||
source_span: '^1.0.0'
|
source_span: '^1.0.0'
|
||||||
stack_trace: '^1.1.1'
|
stack_trace: '^1.1.1'
|
||||||
quiver: '^0.21.4'
|
quiver: '^0.21.4'
|
||||||
|
observe: '0.13.1'
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
guinness: '^0.1.17'
|
guinness: '^0.1.17'
|
||||||
transformers:
|
transformers:
|
||||||
|
@ -603,6 +603,8 @@ export class ElementInjector extends TreeNode<ElementInjector> implements Depend
|
|||||||
if (isPresent(dirDep.queryDecorator)) return this._findQuery(dirDep.queryDecorator).list;
|
if (isPresent(dirDep.queryDecorator)) return this._findQuery(dirDep.queryDecorator).list;
|
||||||
|
|
||||||
if (dirDep.key.id === StaticKeys.instance().changeDetectorRefId) {
|
if (dirDep.key.id === StaticKeys.instance().changeDetectorRefId) {
|
||||||
|
// We provide the component's view change detector to components and
|
||||||
|
// the surrounding component's change detector to directives.
|
||||||
if (dirBin.metadata.type === DirectiveMetadata.COMPONENT_TYPE) {
|
if (dirBin.metadata.type === DirectiveMetadata.COMPONENT_TYPE) {
|
||||||
var componentView = this._preBuiltObjects.view.componentChildViews[this._proto.index];
|
var componentView = this._preBuiltObjects.view.componentChildViews[this._proto.index];
|
||||||
return componentView.changeDetector.ref;
|
return componentView.changeDetector.ref;
|
||||||
|
66
modules/angular2/src/directives/observable_list_diff.dart
Normal file
66
modules/angular2/src/directives/observable_list_diff.dart
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
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 'dart:async';
|
||||||
|
|
||||||
|
class ObservableListDiff extends IterableChanges {
|
||||||
|
ChangeDetectorRef _ref;
|
||||||
|
ObservableListDiff(this._ref);
|
||||||
|
|
||||||
|
bool _updated = true;
|
||||||
|
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();
|
||||||
|
this._subscription = null;
|
||||||
|
this._collection = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dynamic transform(ObservableList collection, [List args]) {
|
||||||
|
// A new collection instance is passed in.
|
||||||
|
// - We need to set up a listener.
|
||||||
|
// - We need to transform collection.
|
||||||
|
if (!identical(_collection, collection)) {
|
||||||
|
_collection = collection;
|
||||||
|
|
||||||
|
if (_subscription != null) _subscription.cancel();
|
||||||
|
_subscription = collection.changes.listen((_) {
|
||||||
|
_updated = true;
|
||||||
|
_ref.requestCheck();
|
||||||
|
});
|
||||||
|
_updated = false;
|
||||||
|
return super.transform(collection, args);
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
// No updates has been registered.
|
||||||
|
// Returning this tells change detection that object has not change,
|
||||||
|
// so it should NOT update the binding.
|
||||||
|
} else {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ObservableListDiffFactory implements PipeFactory {
|
||||||
|
const ObservableListDiffFactory();
|
||||||
|
bool supports(obj) => obj is ObservableList;
|
||||||
|
Pipe create(ChangeDetectorRef cdRef) {
|
||||||
|
return new ObservableListDiff(cdRef);
|
||||||
|
}
|
||||||
|
}
|
@ -27,4 +27,9 @@ class SpyPipeFactory extends SpyObject implements PipeFactory {
|
|||||||
@proxy
|
@proxy
|
||||||
class SpyDependencyProvider extends SpyObject implements DependencyProvider {
|
class SpyDependencyProvider extends SpyObject implements DependencyProvider {
|
||||||
noSuchMethod(m) => super.noSuchMethod(m);
|
noSuchMethod(m) => super.noSuchMethod(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
@proxy
|
||||||
|
class SpyChangeDetectorRef extends SpyObject implements ChangeDetectorRef {
|
||||||
|
noSuchMethod(m) => super.noSuchMethod(m);
|
||||||
}
|
}
|
@ -56,6 +56,7 @@ import {
|
|||||||
DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES
|
DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES
|
||||||
} from 'angular2/src/render/dom/dom_renderer';
|
} from 'angular2/src/render/dom/dom_renderer';
|
||||||
import {DefaultDomCompiler} from 'angular2/src/render/dom/compiler/compiler';
|
import {DefaultDomCompiler} from 'angular2/src/render/dom/compiler/compiler';
|
||||||
|
import {Log} from './utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the root injector bindings.
|
* Returns the root injector bindings.
|
||||||
@ -107,6 +108,7 @@ function _getAppBindings() {
|
|||||||
CompilerCache,
|
CompilerCache,
|
||||||
bind(ViewResolver).toClass(MockViewResolver),
|
bind(ViewResolver).toClass(MockViewResolver),
|
||||||
bind(Pipes).toValue(defaultPipes),
|
bind(Pipes).toValue(defaultPipes),
|
||||||
|
Log,
|
||||||
bind(ChangeDetection).toClass(DynamicChangeDetection),
|
bind(ChangeDetection).toClass(DynamicChangeDetection),
|
||||||
ViewLoader,
|
ViewLoader,
|
||||||
DynamicComponentLoader,
|
DynamicComponentLoader,
|
||||||
|
@ -4,6 +4,9 @@ library angular2.test.di.integration_dart_spec;
|
|||||||
import 'package:angular2/angular2.dart';
|
import 'package:angular2/angular2.dart';
|
||||||
import 'package:angular2/di.dart';
|
import 'package:angular2/di.dart';
|
||||||
import 'package:angular2/test_lib.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';
|
||||||
|
|
||||||
class MockException implements Error {
|
class MockException implements Error {
|
||||||
var message;
|
var message;
|
||||||
@ -136,10 +139,52 @@ main() {
|
|||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("ObservableListDiff", () {
|
||||||
|
it('should be notified of changes', inject([TestComponentBuilder, Log], fakeAsync((TestComponentBuilder tcb, Log log) {
|
||||||
|
tcb.overrideView(Dummy, new View(
|
||||||
|
template: '''<component-with-observable-list [list]="value"></component-with-observable-list>''',
|
||||||
|
directives: [ComponentWithObservableList]))
|
||||||
|
|
||||||
|
.createAsync(Dummy).then((tc) {
|
||||||
|
tc.componentInstance.value = new ObservableList.from([1,2]);
|
||||||
|
|
||||||
|
tc.detectChanges();
|
||||||
|
|
||||||
|
expect(log.result()).toEqual("check");
|
||||||
|
expect(asNativeElements(tc.componentViewChildren)).toHaveText('12');
|
||||||
|
|
||||||
|
tc.detectChanges();
|
||||||
|
|
||||||
|
// we did not change the list => no checks
|
||||||
|
expect(log.result()).toEqual("check");
|
||||||
|
|
||||||
|
tc.componentInstance.value.add(3);
|
||||||
|
|
||||||
|
flushMicrotasks();
|
||||||
|
|
||||||
|
tc.detectChanges();
|
||||||
|
|
||||||
|
// we changed the list => a check
|
||||||
|
expect(log.result()).toEqual("check; check");
|
||||||
|
expect(asNativeElements(tc.componentViewChildren)).toHaveText('123');
|
||||||
|
|
||||||
|
// we replaced the list => a check
|
||||||
|
tc.componentInstance.value = new ObservableList.from([5,6,7]);
|
||||||
|
|
||||||
|
tc.detectChanges();
|
||||||
|
|
||||||
|
expect(log.result()).toEqual("check; check; check");
|
||||||
|
expect(asNativeElements(tc.componentViewChildren)).toHaveText('567');
|
||||||
|
});
|
||||||
|
})));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component(selector: 'dummy')
|
@Component(selector: 'dummy')
|
||||||
class Dummy {}
|
class Dummy {
|
||||||
|
dynamic value;
|
||||||
|
}
|
||||||
|
|
||||||
@Component(
|
@Component(
|
||||||
selector: 'type-literal-component',
|
selector: 'type-literal-component',
|
||||||
@ -206,3 +251,28 @@ class OnChangeComponent implements OnChange {
|
|||||||
this.changes = changes;
|
this.changes = changes;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Component(
|
||||||
|
selector: 'component-with-observable-list',
|
||||||
|
changeDetection: ON_PUSH,
|
||||||
|
properties: const ['list'],
|
||||||
|
hostInjector: const [
|
||||||
|
const Binding(Pipes, toValue: const Pipes (const {"iterableDiff": const [const ObservableListDiffFactory(), const IterableChangesFactory(), const NullPipeFactory()]}))
|
||||||
|
]
|
||||||
|
)
|
||||||
|
@View(template: '<span *ng-for="#item of list">{{item}}</span><directive-logging-checks></directive-logging-checks>', directives: const [NgFor, DirectiveLoggingChecks])
|
||||||
|
class ComponentWithObservableList {
|
||||||
|
Iterable list;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Directive(
|
||||||
|
selector: 'directive-logging-checks',
|
||||||
|
lifecycle: const [LifecycleEvent.onCheck]
|
||||||
|
)
|
||||||
|
class DirectiveLoggingChecks implements OnCheck {
|
||||||
|
Log log;
|
||||||
|
|
||||||
|
DirectiveLoggingChecks(this.log);
|
||||||
|
|
||||||
|
onCheck() => log.add("check");
|
||||||
|
}
|
@ -0,0 +1,95 @@
|
|||||||
|
library angular2.test.directives.observable_list_iterable_diff_spec;
|
||||||
|
|
||||||
|
import 'package:angular2/test_lib.dart';
|
||||||
|
import 'package:observe/observe.dart' show ObservableList;
|
||||||
|
import 'package:angular2/src/directives/observable_list_diff.dart';
|
||||||
|
|
||||||
|
main() {
|
||||||
|
describe('ObservableListDiff', () {
|
||||||
|
var pipeFactory, changeDetectorRef;
|
||||||
|
|
||||||
|
beforeEach(() {
|
||||||
|
pipeFactory = const ObservableListDiffFactory();
|
||||||
|
changeDetectorRef = new SpyChangeDetectorRef();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("supports", () {
|
||||||
|
it("should be true for ObservableList", () {
|
||||||
|
expect(pipeFactory.supports(new ObservableList())).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should be false otherwise", () {
|
||||||
|
expect(pipeFactory.supports([1,2,3])).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return the wrapped value to trigger change detection on first invocation of transform", () {
|
||||||
|
final pipe = pipeFactory.create(changeDetectorRef);
|
||||||
|
final c = new ObservableList.from([1,2]);
|
||||||
|
expect(pipe.transform(c, []).wrapped).toBe(pipe);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return itself when no changes between the calls", () {
|
||||||
|
final pipe = pipeFactory.create(changeDetectorRef);
|
||||||
|
|
||||||
|
final c = new ObservableList.from([1,2]);
|
||||||
|
|
||||||
|
pipe.transform(c, []);
|
||||||
|
|
||||||
|
expect(pipe.transform(c, [])).toBe(pipe);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return the wrapped value once a change has been trigger", fakeAsync(() {
|
||||||
|
final pipe = pipeFactory.create(changeDetectorRef);
|
||||||
|
|
||||||
|
final c = new ObservableList.from([1,2]);
|
||||||
|
|
||||||
|
pipe.transform(c, []);
|
||||||
|
|
||||||
|
c.add(3);
|
||||||
|
|
||||||
|
// same value, because we have not detected the change yet
|
||||||
|
expect(pipe.transform(c, [])).toBe(pipe);
|
||||||
|
|
||||||
|
// now we detect the change
|
||||||
|
flushMicrotasks();
|
||||||
|
expect(pipe.transform(c, []).wrapped).toBe(pipe);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it("should request a change detection check upon receiving a change", fakeAsync(() {
|
||||||
|
final pipe = pipeFactory.create(changeDetectorRef);
|
||||||
|
|
||||||
|
final c = new ObservableList.from([1,2]);
|
||||||
|
pipe.transform(c, []);
|
||||||
|
|
||||||
|
c.add(3);
|
||||||
|
flushMicrotasks();
|
||||||
|
|
||||||
|
expect(changeDetectorRef.spy("requestCheck")).toHaveBeenCalledOnce();
|
||||||
|
}));
|
||||||
|
|
||||||
|
it("should return the wrapped value after changing a collection", () {
|
||||||
|
final pipe = pipeFactory.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);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not unbsubscribe from the stream of chagnes after changing a collection", () {
|
||||||
|
final pipe = pipeFactory.create(changeDetectorRef);
|
||||||
|
|
||||||
|
final c1 = new ObservableList.from([1,2]);
|
||||||
|
expect(pipe.transform(c1, []).wrapped).toBe(pipe);
|
||||||
|
|
||||||
|
final c2 = new ObservableList.from([3,4]);
|
||||||
|
expect(pipe.transform(c2, []).wrapped).toBe(pipe);
|
||||||
|
|
||||||
|
// pushing into the first collection has no effect, and we do not see the change
|
||||||
|
c1.add(3);
|
||||||
|
expect(pipe.transform(c2, [])).toBe(pipe);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
@ -1,6 +1,8 @@
|
|||||||
name: angular
|
name: angular
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=1.9.0 <2.0.0'
|
sdk: '>=1.9.0 <2.0.0'
|
||||||
|
dependencies:
|
||||||
|
observe: '0.13.1'
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
guinness: '^0.1.17'
|
guinness: '^0.1.17'
|
||||||
intl: '^0.12.4'
|
intl: '^0.12.4'
|
||||||
|
Loading…
x
Reference in New Issue
Block a user