X Tutup
Skip to content

Commit 3aa2047

Browse files
committed
feat(query): make QueryList notify on changes via an observable
BREAKING CHANGE: Before: query.onChange(() => ...); After: query.changes.subscribe((iterable) => {}); Closes #4395
1 parent 9b7378d commit 3aa2047

File tree

8 files changed

+128
-177
lines changed

8 files changed

+128
-177
lines changed

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

Lines changed: 39 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -383,7 +383,7 @@ export class ElementInjector extends TreeNode<ElementInjector> implements Depend
383383
this._preBuiltObjects = null;
384384
this._strategy.callOnDestroy();
385385
this._strategy.dehydrate();
386-
this._queryStrategy.clearQueryLists();
386+
this._queryStrategy.dehydrate();
387387
}
388388

389389
hydrate(imperativelyCreatedInjector: Injector, host: ElementInjector,
@@ -392,6 +392,7 @@ export class ElementInjector extends TreeNode<ElementInjector> implements Depend
392392
this._preBuiltObjects = preBuiltObjects;
393393

394394
this._reattachInjectors(imperativelyCreatedInjector);
395+
this._queryStrategy.hydrate();
395396
this._strategy.hydrate();
396397

397398
this.hydrated = true;
@@ -604,7 +605,8 @@ export class ElementInjector extends TreeNode<ElementInjector> implements Depend
604605
interface _QueryStrategy {
605606
setContentQueriesAsDirty(): void;
606607
setViewQueriesAsDirty(): void;
607-
clearQueryLists(): void;
608+
hydrate(): void;
609+
dehydrate(): void;
608610
updateContentQueries(): void;
609611
updateViewQueries(): void;
610612
findQuery(query: QueryMetadata): QueryRef;
@@ -613,7 +615,8 @@ interface _QueryStrategy {
613615
class _EmptyQueryStrategy implements _QueryStrategy {
614616
setContentQueriesAsDirty(): void {}
615617
setViewQueriesAsDirty(): void {}
616-
clearQueryLists(): void {}
618+
hydrate(): void {}
619+
dehydrate(): void {}
617620
updateContentQueries(): void {}
618621
updateViewQueries(): void {}
619622
findQuery(query: QueryMetadata): QueryRef {
@@ -632,9 +635,9 @@ class InlineQueryStrategy implements _QueryStrategy {
632635

633636
constructor(ei: ElementInjector) {
634637
var protoRefs = ei._proto.protoQueryRefs;
635-
if (protoRefs.length > 0) this.query0 = new QueryRef(protoRefs[0], new QueryList<any>(), ei);
636-
if (protoRefs.length > 1) this.query1 = new QueryRef(protoRefs[1], new QueryList<any>(), ei);
637-
if (protoRefs.length > 2) this.query2 = new QueryRef(protoRefs[2], new QueryList<any>(), ei);
638+
if (protoRefs.length > 0) this.query0 = new QueryRef(protoRefs[0], ei);
639+
if (protoRefs.length > 1) this.query1 = new QueryRef(protoRefs[1], ei);
640+
if (protoRefs.length > 2) this.query2 = new QueryRef(protoRefs[2], ei);
638641
}
639642

640643
setContentQueriesAsDirty(): void {
@@ -649,39 +652,39 @@ class InlineQueryStrategy implements _QueryStrategy {
649652
if (isPresent(this.query2) && this.query2.isViewQuery) this.query2.dirty = true;
650653
}
651654

652-
clearQueryLists(): void {
653-
if (isPresent(this.query0)) this.query0.reset();
654-
if (isPresent(this.query1)) this.query1.reset();
655-
if (isPresent(this.query2)) this.query2.reset();
655+
hydrate(): void {
656+
if (isPresent(this.query0)) this.query0.hydrate();
657+
if (isPresent(this.query1)) this.query1.hydrate();
658+
if (isPresent(this.query2)) this.query2.hydrate();
659+
}
660+
661+
dehydrate(): void {
662+
if (isPresent(this.query0)) this.query0.dehydrate();
663+
if (isPresent(this.query1)) this.query1.dehydrate();
664+
if (isPresent(this.query2)) this.query2.dehydrate();
656665
}
657666

658667
updateContentQueries() {
659668
if (isPresent(this.query0) && !this.query0.isViewQuery) {
660669
this.query0.update();
661-
this.query0.list.fireCallbacks();
662670
}
663671
if (isPresent(this.query1) && !this.query1.isViewQuery) {
664672
this.query1.update();
665-
this.query1.list.fireCallbacks();
666673
}
667674
if (isPresent(this.query2) && !this.query2.isViewQuery) {
668675
this.query2.update();
669-
this.query2.list.fireCallbacks();
670676
}
671677
}
672678

673679
updateViewQueries() {
674680
if (isPresent(this.query0) && this.query0.isViewQuery) {
675681
this.query0.update();
676-
this.query0.list.fireCallbacks();
677682
}
678683
if (isPresent(this.query1) && this.query1.isViewQuery) {
679684
this.query1.update();
680-
this.query1.list.fireCallbacks();
681685
}
682686
if (isPresent(this.query2) && this.query2.isViewQuery) {
683687
this.query2.update();
684-
this.query2.list.fireCallbacks();
685688
}
686689
}
687690

@@ -703,7 +706,7 @@ class DynamicQueryStrategy implements _QueryStrategy {
703706
queries: QueryRef[];
704707

705708
constructor(ei: ElementInjector) {
706-
this.queries = ei._proto.protoQueryRefs.map(p => new QueryRef(p, new QueryList<any>(), ei));
709+
this.queries = ei._proto.protoQueryRefs.map(p => new QueryRef(p, ei));
707710
}
708711

709712
setContentQueriesAsDirty(): void {
@@ -720,10 +723,17 @@ class DynamicQueryStrategy implements _QueryStrategy {
720723
}
721724
}
722725

723-
clearQueryLists(): void {
726+
hydrate(): void {
724727
for (var i = 0; i < this.queries.length; ++i) {
725728
var q = this.queries[i];
726-
q.reset();
729+
q.hydrate();
730+
}
731+
}
732+
733+
dehydrate(): void {
734+
for (var i = 0; i < this.queries.length; ++i) {
735+
var q = this.queries[i];
736+
q.dehydrate();
727737
}
728738
}
729739

@@ -732,7 +742,6 @@ class DynamicQueryStrategy implements _QueryStrategy {
732742
var q = this.queries[i];
733743
if (!q.isViewQuery) {
734744
q.update();
735-
q.list.fireCallbacks();
736745
}
737746
}
738747
}
@@ -742,7 +751,6 @@ class DynamicQueryStrategy implements _QueryStrategy {
742751
var q = this.queries[i];
743752
if (q.isViewQuery) {
744753
q.update();
745-
q.list.fireCallbacks();
746754
}
747755
}
748756
}
@@ -972,8 +980,10 @@ export class ProtoQueryRef {
972980
}
973981

974982
export class QueryRef {
975-
constructor(public protoQueryRef: ProtoQueryRef, public list: QueryList<any>,
976-
private originator: ElementInjector, public dirty: boolean = true) {}
983+
public list: QueryList<any>;
984+
public dirty: boolean;
985+
986+
constructor(public protoQueryRef: ProtoQueryRef, private originator: ElementInjector) {}
977987

978988
get isViewQuery(): boolean { return this.protoQueryRef.query.isViewQuery; }
979989

@@ -991,6 +1001,8 @@ export class QueryRef {
9911001
this.protoQueryRef.setter(dir, this.list);
9921002
}
9931003
}
1004+
1005+
this.list.notifyOnChanges();
9941006
}
9951007

9961008
private _update(): void {
@@ -1073,9 +1085,10 @@ export class QueryRef {
10731085
inj.addDirectivesMatchingQuery(this.protoQueryRef.query, aggregator);
10741086
}
10751087

1076-
reset(): void {
1077-
this.list.reset([]);
1078-
this.list.removeAllCallbacks();
1088+
dehydrate(): void { this.list = null; }
1089+
1090+
hydrate(): void {
1091+
this.list = new QueryList<any>();
10791092
this.dirty = true;
10801093
}
10811094
}
Lines changed: 10 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,19 @@
11
library angular2.src.core.compiler.query_list;
22

33
import 'dart:collection';
4+
import 'package:angular2/src/core/facade/async.dart';
45

56
/**
67
* See query_list.ts
78
*/
89
class QueryList<T> extends Object
910
with IterableMixin<T> {
1011
List<T> _results = [];
11-
List _callbacks = [];
12-
bool _dirty = false;
12+
EventEmitter _emitter = new EventEmitter();
1313

1414
Iterator<T> get iterator => _results.iterator;
1515

16-
/** @private */
17-
void reset(List<T> newList) {
18-
_results = newList;
19-
_dirty = true;
20-
}
21-
22-
void add(T obj) {
23-
_results.add(obj);
24-
_dirty = true;
25-
}
26-
27-
void onChange(callback) {
28-
_callbacks.add(callback);
29-
}
30-
31-
void removeCallback(callback) {
32-
_callbacks.remove(callback);
33-
}
34-
35-
void removeAllCallbacks() {
36-
this._callbacks = [];
37-
}
16+
Stream<Iterable<T>> get changes => _emitter;
3817

3918
int get length => _results.length;
4019
T get first => _results.first;
@@ -49,10 +28,12 @@ class QueryList<T> extends Object
4928
}
5029

5130
/** @private */
52-
void fireCallbacks() {
53-
if (_dirty) {
54-
_callbacks.forEach((c) => c());
55-
_dirty = false;
56-
}
31+
void reset(List<T> newList) {
32+
_results = newList;
33+
}
34+
35+
/** @private */
36+
void notifyOnChanges() {
37+
_emitter.add(this);
5738
}
5839
}
Lines changed: 14 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {ListWrapper, MapWrapper} from 'angular2/src/core/facade/collection';
22
import {getSymbolIterator} from 'angular2/src/core/facade/lang';
3+
import {Observable, EventEmitter} from 'angular2/src/core/facade/async';
34

45

56
/**
@@ -12,7 +13,7 @@ import {getSymbolIterator} from 'angular2/src/core/facade/lang';
1213
* javascript `for (var i of items)` loops as well as in Angular templates with
1314
* `*ng-for="#i of myList"`.
1415
*
15-
* Changes can be observed by attaching callbacks.
16+
* Changes can be observed by subscribing to the changes `Observable`.
1617
*
1718
* NOTE: In the future this class will implement an `Observable` interface.
1819
*
@@ -21,45 +22,16 @@ import {getSymbolIterator} from 'angular2/src/core/facade/lang';
2122
* @Component({...})
2223
* class Container {
2324
* constructor(@Query(Item) items: QueryList<Item>) {
24-
* items.onChange(() => console.log(items.length));
25+
* items.changes.subscribe(_ => console.log(items.length));
2526
* }
2627
* }
2728
* ```
2829
*/
2930
export class QueryList<T> {
30-
protected _results: Array < T >= [];
31-
protected _callbacks: Array < () => void >= [];
32-
protected _dirty: boolean = false;
33-
34-
/** @private */
35-
reset(newList: T[]): void {
36-
this._results = newList;
37-
this._dirty = true;
38-
}
39-
40-
/** @private */
41-
add(obj: T): void {
42-
this._results.push(obj);
43-
this._dirty = true;
44-
}
45-
46-
/**
47-
* registers a callback that is called upon each change.
48-
*/
49-
onChange(callback: () => void): void { this._callbacks.push(callback); }
50-
51-
/**
52-
* removes a given callback.
53-
*/
54-
removeCallback(callback: () => void): void { ListWrapper.remove(this._callbacks, callback); }
55-
56-
/**
57-
* removes all callback that have been attached.
58-
*/
59-
removeAllCallbacks(): void { this._callbacks = []; }
60-
61-
toString(): string { return this._results.toString(); }
31+
private _results: Array<T> = [];
32+
private _emitter = new EventEmitter();
6233

34+
get changes(): Observable { return this._emitter; }
6335
get length(): number { return this._results.length; }
6436
get first(): T { return ListWrapper.first(this._results); }
6537
get last(): T { return ListWrapper.last(this._results); }
@@ -71,11 +43,13 @@ export class QueryList<T> {
7143

7244
[getSymbolIterator()](): any { return this._results[getSymbolIterator()](); }
7345

46+
toString(): string { return this._results.toString(); }
47+
48+
/**
49+
* @private
50+
*/
51+
reset(res: T[]): void { this._results = res; }
52+
7453
/** @private */
75-
fireCallbacks(): void {
76-
if (this._dirty) {
77-
ListWrapper.forEach(this._callbacks, (c) => c());
78-
this._dirty = false;
79-
}
80-
}
54+
notifyOnChanges(): void { this._emitter.next(this); }
8155
}

modules/angular2/src/core/forms/directives/select_control_value_accessor.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {Query, Directive} from 'angular2/src/core/metadata';
66
import {NgControl} from './ng_control';
77
import {ControlValueAccessor} from './control_value_accessor';
88
import {isPresent} from 'angular2/src/core/facade/lang';
9+
import {ObservableWrapper} from 'angular2/src/core/facade/async';
910
import {setProperty} from './shared';
1011

1112
/**
@@ -81,6 +82,6 @@ export class SelectControlValueAccessor implements ControlValueAccessor {
8182
registerOnTouched(fn: () => any): void { this.onTouched = fn; }
8283

8384
private _updateValueWhenListOfOptionsChanges(query: QueryList<NgSelectOption>) {
84-
query.onChange(() => this.writeValue(this.value));
85+
ObservableWrapper.subscribe(query.changes, (_) => this.writeValue(this.value));
8586
}
8687
}

modules/angular2/test/core/compiler/query_integration_spec.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
} from 'angular2/test_lib';
1414

1515
import {isPresent} from 'angular2/src/core/facade/lang';
16+
import {ObservableWrapper} from 'angular2/src/core/facade/async';
1617

1718
import {
1819
Component,
@@ -263,7 +264,7 @@ export function main() {
263264

264265
});
265266

266-
describe("onChange", () => {
267+
describe("changes", () => {
267268
it('should notify query on change',
268269
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
269270
var template = '<needs-query #q>' +
@@ -277,7 +278,7 @@ export function main() {
277278
var q = view.debugElement.componentViewChildren[0].getLocal("q");
278279
view.detectChanges();
279280

280-
q.query.onChange(() => {
281+
ObservableWrapper.subscribe(q.query.changes, (_) => {
281282
expect(q.query.first.text).toEqual("1");
282283
expect(q.query.last.text).toEqual("2");
283284
async.done();
@@ -304,8 +305,8 @@ export function main() {
304305

305306
var firedQ2 = false;
306307

307-
q2.query.onChange(() => { firedQ2 = true; });
308-
q1.query.onChange(() => {
308+
ObservableWrapper.subscribe(q2.query.changes, (_) => { firedQ2 = true; });
309+
ObservableWrapper.subscribe(q1.query.changes, (_) => {
309310
expect(firedQ2).toBe(true);
310311
async.done();
311312
});

0 commit comments

Comments
 (0)
X Tutup