X Tutup
Skip to content

Commit d449ea5

Browse files
committed
feat(change_detection): added support for ObservableList from package:observe
1 parent 583c5ff commit d449ea5

File tree

8 files changed

+244
-1
lines changed

8 files changed

+244
-1
lines changed

modules/angular2/pubspec.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ dependencies:
1919
source_span: '^1.0.0'
2020
stack_trace: '^1.1.1'
2121
quiver: '^0.21.4'
22+
observe: '0.13.1'
2223
dev_dependencies:
2324
guinness: '^0.1.17'
2425
transformers:

modules/angular2/src/core/compiler/element_injector.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -603,6 +603,8 @@ export class ElementInjector extends TreeNode<ElementInjector> implements Depend
603603
if (isPresent(dirDep.queryDecorator)) return this._findQuery(dirDep.queryDecorator).list;
604604

605605
if (dirDep.key.id === StaticKeys.instance().changeDetectorRefId) {
606+
// We provide the component's view change detector to components and
607+
// the surrounding component's change detector to directives.
606608
if (dirBin.metadata.type === DirectiveMetadata.COMPONENT_TYPE) {
607609
var componentView = this._preBuiltObjects.view.componentChildViews[this._proto.index];
608610
return componentView.changeDetector.ref;
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
library angular2.directives.observable_list_iterable_diff;
2+
3+
import 'package:observe/observe.dart' show ObservableList;
4+
import 'package:angular2/change_detection.dart';
5+
import 'package:angular2/src/change_detection/pipes/iterable_changes.dart';
6+
import 'dart:async';
7+
8+
class ObservableListDiff extends IterableChanges {
9+
ChangeDetectorRef _ref;
10+
ObservableListDiff(this._ref);
11+
12+
bool _updated = true;
13+
ObservableList _collection;
14+
StreamSubscription _subscription;
15+
16+
bool supports(obj) {
17+
if (obj is ObservableList) return true;
18+
throw "Cannot change the type of a collection";
19+
}
20+
21+
onDestroy() {
22+
if (this._subscription != null) {
23+
this._subscription.cancel();
24+
this._subscription = null;
25+
this._collection = null;
26+
}
27+
}
28+
29+
dynamic transform(ObservableList collection, [List args]) {
30+
// A new collection instance is passed in.
31+
// - We need to set up a listener.
32+
// - We need to transform collection.
33+
if (!identical(_collection, collection)) {
34+
_collection = collection;
35+
36+
if (_subscription != null) _subscription.cancel();
37+
_subscription = collection.changes.listen((_) {
38+
_updated = true;
39+
_ref.requestCheck();
40+
});
41+
_updated = false;
42+
return super.transform(collection, args);
43+
44+
// An update has been registered since the last change detection check.
45+
// - We reset the flag.
46+
// - We diff the collection.
47+
} else if (_updated){
48+
_updated = false;
49+
return super.transform(collection, args);
50+
51+
// No updates has been registered.
52+
// Returning this tells change detection that object has not change,
53+
// so it should NOT update the binding.
54+
} else {
55+
return this;
56+
}
57+
}
58+
}
59+
60+
class ObservableListDiffFactory implements PipeFactory {
61+
const ObservableListDiffFactory();
62+
bool supports(obj) => obj is ObservableList;
63+
Pipe create(ChangeDetectorRef cdRef) {
64+
return new ObservableListDiff(cdRef);
65+
}
66+
}

modules/angular2/src/test_lib/spies.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,9 @@ class SpyPipeFactory extends SpyObject implements PipeFactory {
2727
@proxy
2828
class SpyDependencyProvider extends SpyObject implements DependencyProvider {
2929
noSuchMethod(m) => super.noSuchMethod(m);
30+
}
31+
32+
@proxy
33+
class SpyChangeDetectorRef extends SpyObject implements ChangeDetectorRef {
34+
noSuchMethod(m) => super.noSuchMethod(m);
3035
}

modules/angular2/src/test_lib/test_injector.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ import {
5656
DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES
5757
} from 'angular2/src/render/dom/dom_renderer';
5858
import {DefaultDomCompiler} from 'angular2/src/render/dom/compiler/compiler';
59+
import {Log} from './utils';
5960

6061
/**
6162
* Returns the root injector bindings.
@@ -107,6 +108,7 @@ function _getAppBindings() {
107108
CompilerCache,
108109
bind(ViewResolver).toClass(MockViewResolver),
109110
bind(Pipes).toValue(defaultPipes),
111+
Log,
110112
bind(ChangeDetection).toClass(DynamicChangeDetection),
111113
ViewLoader,
112114
DynamicComponentLoader,

modules/angular2/test/core/compiler/integration_dart_spec.dart

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ library angular2.test.di.integration_dart_spec;
44
import 'package:angular2/angular2.dart';
55
import 'package:angular2/di.dart';
66
import 'package:angular2/test_lib.dart';
7+
import 'package:observe/observe.dart';
8+
import 'package:angular2/src/directives/observable_list_diff.dart';
9+
import 'package:angular2/src/change_detection/pipes/iterable_changes.dart';
710

811
class MockException implements Error {
912
var message;
@@ -136,10 +139,52 @@ main() {
136139
});
137140
}));
138141
});
142+
143+
describe("ObservableListDiff", () {
144+
it('should be notified of changes', inject([TestComponentBuilder, Log], fakeAsync((TestComponentBuilder tcb, Log log) {
145+
tcb.overrideView(Dummy, new View(
146+
template: '''<component-with-observable-list [list]="value"></component-with-observable-list>''',
147+
directives: [ComponentWithObservableList]))
148+
149+
.createAsync(Dummy).then((tc) {
150+
tc.componentInstance.value = new ObservableList.from([1,2]);
151+
152+
tc.detectChanges();
153+
154+
expect(log.result()).toEqual("check");
155+
expect(asNativeElements(tc.componentViewChildren)).toHaveText('12');
156+
157+
tc.detectChanges();
158+
159+
// we did not change the list => no checks
160+
expect(log.result()).toEqual("check");
161+
162+
tc.componentInstance.value.add(3);
163+
164+
flushMicrotasks();
165+
166+
tc.detectChanges();
167+
168+
// we changed the list => a check
169+
expect(log.result()).toEqual("check; check");
170+
expect(asNativeElements(tc.componentViewChildren)).toHaveText('123');
171+
172+
// we replaced the list => a check
173+
tc.componentInstance.value = new ObservableList.from([5,6,7]);
174+
175+
tc.detectChanges();
176+
177+
expect(log.result()).toEqual("check; check; check");
178+
expect(asNativeElements(tc.componentViewChildren)).toHaveText('567');
179+
});
180+
})));
181+
});
139182
}
140183

141184
@Component(selector: 'dummy')
142-
class Dummy {}
185+
class Dummy {
186+
dynamic value;
187+
}
143188

144189
@Component(
145190
selector: 'type-literal-component',
@@ -206,3 +251,28 @@ class OnChangeComponent implements OnChange {
206251
this.changes = changes;
207252
}
208253
}
254+
255+
@Component(
256+
selector: 'component-with-observable-list',
257+
changeDetection: ON_PUSH,
258+
properties: const ['list'],
259+
hostInjector: const [
260+
const Binding(Pipes, toValue: const Pipes (const {"iterableDiff": const [const ObservableListDiffFactory(), const IterableChangesFactory(), const NullPipeFactory()]}))
261+
]
262+
)
263+
@View(template: '<span *ng-for="#item of list">{{item}}</span><directive-logging-checks></directive-logging-checks>', directives: const [NgFor, DirectiveLoggingChecks])
264+
class ComponentWithObservableList {
265+
Iterable list;
266+
}
267+
268+
@Directive(
269+
selector: 'directive-logging-checks',
270+
lifecycle: const [LifecycleEvent.onCheck]
271+
)
272+
class DirectiveLoggingChecks implements OnCheck {
273+
Log log;
274+
275+
DirectiveLoggingChecks(this.log);
276+
277+
onCheck() => log.add("check");
278+
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
library angular2.test.directives.observable_list_iterable_diff_spec;
2+
3+
import 'package:angular2/test_lib.dart';
4+
import 'package:observe/observe.dart' show ObservableList;
5+
import 'package:angular2/src/directives/observable_list_diff.dart';
6+
7+
main() {
8+
describe('ObservableListDiff', () {
9+
var pipeFactory, changeDetectorRef;
10+
11+
beforeEach(() {
12+
pipeFactory = const ObservableListDiffFactory();
13+
changeDetectorRef = new SpyChangeDetectorRef();
14+
});
15+
16+
describe("supports", () {
17+
it("should be true for ObservableList", () {
18+
expect(pipeFactory.supports(new ObservableList())).toBe(true);
19+
});
20+
21+
it("should be false otherwise", () {
22+
expect(pipeFactory.supports([1,2,3])).toBe(false);
23+
});
24+
});
25+
26+
it("should return the wrapped value to trigger change detection on first invocation of transform", () {
27+
final pipe = pipeFactory.create(changeDetectorRef);
28+
final c = new ObservableList.from([1,2]);
29+
expect(pipe.transform(c, []).wrapped).toBe(pipe);
30+
});
31+
32+
it("should return itself when no changes between the calls", () {
33+
final pipe = pipeFactory.create(changeDetectorRef);
34+
35+
final c = new ObservableList.from([1,2]);
36+
37+
pipe.transform(c, []);
38+
39+
expect(pipe.transform(c, [])).toBe(pipe);
40+
});
41+
42+
it("should return the wrapped value once a change has been trigger", fakeAsync(() {
43+
final pipe = pipeFactory.create(changeDetectorRef);
44+
45+
final c = new ObservableList.from([1,2]);
46+
47+
pipe.transform(c, []);
48+
49+
c.add(3);
50+
51+
// same value, because we have not detected the change yet
52+
expect(pipe.transform(c, [])).toBe(pipe);
53+
54+
// now we detect the change
55+
flushMicrotasks();
56+
expect(pipe.transform(c, []).wrapped).toBe(pipe);
57+
}));
58+
59+
it("should request a change detection check upon receiving a change", fakeAsync(() {
60+
final pipe = pipeFactory.create(changeDetectorRef);
61+
62+
final c = new ObservableList.from([1,2]);
63+
pipe.transform(c, []);
64+
65+
c.add(3);
66+
flushMicrotasks();
67+
68+
expect(changeDetectorRef.spy("requestCheck")).toHaveBeenCalledOnce();
69+
}));
70+
71+
it("should return the wrapped value after changing a collection", () {
72+
final pipe = pipeFactory.create(changeDetectorRef);
73+
74+
final c1 = new ObservableList.from([1,2]);
75+
final c2 = new ObservableList.from([3,4]);
76+
77+
expect(pipe.transform(c1, []).wrapped).toBe(pipe);
78+
expect(pipe.transform(c2, []).wrapped).toBe(pipe);
79+
});
80+
81+
it("should not unbsubscribe from the stream of chagnes after changing a collection", () {
82+
final pipe = pipeFactory.create(changeDetectorRef);
83+
84+
final c1 = new ObservableList.from([1,2]);
85+
expect(pipe.transform(c1, []).wrapped).toBe(pipe);
86+
87+
final c2 = new ObservableList.from([3,4]);
88+
expect(pipe.transform(c2, []).wrapped).toBe(pipe);
89+
90+
// pushing into the first collection has no effect, and we do not see the change
91+
c1.add(3);
92+
expect(pipe.transform(c2, [])).toBe(pipe);
93+
});
94+
});
95+
}

pubspec.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
name: angular
22
environment:
33
sdk: '>=1.9.0 <2.0.0'
4+
dependencies:
5+
observe: '0.13.1'
46
dev_dependencies:
57
guinness: '^0.1.17'
68
intl: '^0.12.4'

0 commit comments

Comments
 (0)
X Tutup