Skip to content

Commit 91f50b6

Browse files
committed
feat(view): hook watch group instantiation in the view.
1 parent 01e6c7b commit 91f50b6

3 files changed

Lines changed: 111 additions & 42 deletions

File tree

modules/change_detection/src/watch_group.js

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,12 @@ export class ProtoWatchGroup {
3939
this.tailRecord = tail;
4040
}
4141

42-
instantiate(dispatcher:WatchGroupDispatcher):WatchGroup {
42+
// TODO(rado): the type annotation should be dispatcher:WatchGroupDispatcher.
43+
// but @Implements is not ready yet.
44+
instantiate(dispatcher):WatchGroup {
4345
var watchGroup:WatchGroup = new WatchGroup(this, dispatcher);
4446
var tail:Record = null;
45-
var proto:ProtoRecord;
47+
var proto:ProtoRecord = null;
4648
var prevRecord:Record = null;
4749

4850
if (this.headRecord !== null) {
@@ -70,7 +72,9 @@ export class WatchGroup {
7072
@FIELD('final dispatcher:WatchGroupDispatcher')
7173
@FIELD('final headRecord:Record')
7274
@FIELD('final tailRecord:Record')
73-
constructor(protoWatchGroup:ProtoWatchGroup, dispatcher:WatchGroupDispatcher) {
75+
// TODO(rado): the type annotation should be dispatcher:WatchGroupDispatcher.
76+
// but @Implements is not ready yet.
77+
constructor(protoWatchGroup:ProtoWatchGroup, dispatcher) {
7478
this.protoWatchGroup = protoWatchGroup;
7579
this.dispatcher = dispatcher;
7680
this.headRecord = null;

modules/core/src/compiler/view.js

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {ProtoElementInjector, ElementInjector} from './element_injector';
66
import {SetterFn} from 'change_detection/facade';
77
import {FIELD, IMPLEMENTS, int, isPresent, isBlank} from 'facade/lang';
88
import {List} from 'facade/collection';
9+
import {Injector} from 'di/di';
910

1011
/***
1112
* Const of making objects: http://jsperf.com/instantiate-size-of-object
@@ -24,14 +25,17 @@ export class View {
2425
@FIELD('final nodes:List<Node>')
2526
@FIELD('final onChangeDispatcher:OnChangeDispatcher')
2627
constructor(fragment:DocumentFragment, elementInjector:List,
27-
rootElementInjectors:List, textNodes:List, bindElements:List) {
28+
rootElementInjectors:List, textNodes:List, bindElements:List,
29+
protoWatchGroup:ProtoWatchGroup, context) {
2830
this.fragment = fragment;
2931
this.nodes = ListWrapper.clone(fragment.childNodes);
3032
this.elementInjectors = elementInjector;
3133
this.rootElementInjectors = rootElementInjectors;
3234
this.onChangeDispatcher = null;
3335
this.textNodes = textNodes;
3436
this.bindElements = bindElements;
37+
this.watchGroup = protoWatchGroup.instantiate(this);
38+
this.watchGroup.setContext(context);
3539
}
3640

3741
onRecordChange(record:Record, target) {
@@ -66,28 +70,29 @@ export class ProtoView {
6670
this._template = template;
6771
this._bindings = bindings;
6872
this._protoElementInjectors = protoElementInjectors;
73+
this._protoWatchGroup = protoWatchGroup;
6974

7075
// not implemented
71-
this._protoWatchGroup = protoWatchGroup;
7276
this._useRootElement = useRootElement;
7377
}
7478

75-
instantiate():View {
79+
instantiate(context, appInjector:Injector):View {
7680
var fragment = DOM.clone(this._template.content);
7781
var elements = DOM.querySelectorAll(fragment, ".ng-binding");
7882
var protos = this._protoElementInjectors;
7983

8084
/**
8185
* TODO: vsavkin: benchmark
82-
* If this performs poorly, the three loops can be collapsed into one.
86+
* If this performs poorly, the five loops can be collapsed into one.
8387
*/
8488
var elementInjectors = ProtoView._createElementInjectors(elements, protos);
8589
var rootElementInjectors = ProtoView._rootElementInjectors(elementInjectors);
8690
var textNodes = ProtoView._textNodes(elements, protos);
8791
var bindElements = ProtoView._bindElements(elements, protos);
92+
ProtoView._instantiateDirectives(elementInjectors, appInjector);
8893

8994
return new View(fragment, elementInjectors, rootElementInjectors, textNodes,
90-
bindElements);
95+
bindElements, this._protoWatchGroup, context);
9196
}
9297

9398
static _createElementInjectors(elements, protos) {
@@ -103,6 +108,13 @@ export class ProtoView {
103108
return injectors;
104109
}
105110

111+
static _instantiateDirectives(
112+
injectors:List<ElementInjectors>, appInjector:Injector) {
113+
for (var i = 0; i < injectors.length; ++i) {
114+
if (injectors[i] != null) injectors[i].instantiateDirectives(appInjector);
115+
}
116+
}
117+
106118
static _createElementInjector(element, proto) {
107119
//TODO: vsavkin: pass element to `proto.instantiate()` once https://github.com/angular/angular/pull/98 is merged
108120
return proto.hasBindings ? proto.instantiate({view:null}) : null;

modules/core/test/compiler/view_spec.js

Lines changed: 87 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,12 @@ import {describe, xit, it, expect, beforeEach} from 'test_lib/test_lib';
22
import {ProtoView, ElementPropertyMemento, DirectivePropertyMemento} from 'core/compiler/view';
33
import {Record} from 'change_detection/record';
44
import {ProtoElementInjector, ElementInjector} from 'core/compiler/element_injector';
5+
import {ProtoWatchGroup} from 'change_detection/watch_group';
6+
import {ChangeDetector} from 'change_detection/change_detector';
57
import {DOM, Element} from 'facade/dom';
68
import {FIELD} from 'facade/lang';
9+
import {ImplicitReceiver, FieldRead} from 'change_detection/parser/ast';
10+
import {ClosureMap} from 'change_detection/parser/closure_map';
711

812
class Directive {
913
@FIELD('prop')
@@ -13,32 +17,36 @@ class Directive {
1317
}
1418

1519
export function main() {
20+
var oneFieldAst = (fieldName) =>
21+
new FieldRead(new ImplicitReceiver(), fieldName,
22+
(new ClosureMap()).getter(fieldName));
23+
1624
describe('view', function() {
1725
var tempalteWithThreeTypesOfBindings =
1826
'<section class="ng-binding">' +
1927
'Hello {}!' +
2028
'<div directive class="ng-binding">' +
21-
'<span class="ng-binding" [hidden]="exp">don\'t show me</span>' +
29+
'<span class="ng-binding" [id]="exp">don\'t show me</span>' +
2230
'</div>' +
2331
'</section>';
2432

33+
function templateElInj() {
34+
var sectionPI = new ProtoElementInjector(null, [], [0], false);
35+
var divPI = new ProtoElementInjector(sectionPI, [Directive], [], false);
36+
var spanPI = new ProtoElementInjector(divPI, [], [], true);
37+
return [sectionPI, divPI, spanPI];
38+
}
39+
2540
describe('ProtoView', function() {
2641
it('should create view instance and locate basic parts', function() {
2742
var template = DOM.createTemplate(tempalteWithThreeTypesOfBindings);
2843

2944
var diBindings = [];
30-
31-
var sectionPI = new ProtoElementInjector(null, [], [0], false);
32-
var divPI = new ProtoElementInjector(sectionPI, [Directive], [], false);
33-
var spanPI = new ProtoElementInjector(divPI, [], [], true);
34-
var protoElementInjectors = [sectionPI, divPI, spanPI];
35-
36-
var protoWatchGroup = null;
3745
var hasSingleRoot = false;
38-
var pv = new ProtoView(template, diBindings, protoElementInjectors,
39-
protoWatchGroup, hasSingleRoot);
46+
var pv = new ProtoView(template, diBindings, templateElInj(),
47+
new ProtoWatchGroup(), hasSingleRoot);
4048

41-
var view = pv.instantiate();
49+
var view = pv.instantiate(null, null);
4250

4351
var section = DOM.firstChild(template.content);
4452

@@ -63,8 +71,9 @@ export function main() {
6371
var sectionPI = new ProtoElementInjector(null, [Directive], [], false);
6472
var divPI = new ProtoElementInjector(sectionPI, [Directive], [], false);
6573

66-
var pv = new ProtoView(template, [], [sectionPI, divPI], null, false);
67-
var view = pv.instantiate();
74+
var pv = new ProtoView(template, [], [sectionPI, divPI],
75+
new ProtoWatchGroup(), false);
76+
var view = pv.instantiate(null, null);
6877

6978
expect(view.rootElementInjectors.length).toEqual(1);
7079
});
@@ -73,20 +82,9 @@ export function main() {
7382
var view;
7483
beforeEach(() => {
7584
var template = DOM.createTemplate(tempalteWithThreeTypesOfBindings);
76-
77-
var diBindings = [];
78-
79-
var sectionPI = new ProtoElementInjector(null, [], [0], false);
80-
var divPI = new ProtoElementInjector(sectionPI, [Directive], [], false);
81-
var spanPI = new ProtoElementInjector(divPI, [], [], true);
82-
var protoElementInjectors = [sectionPI, divPI, spanPI];
83-
84-
var protoWatchGroup = null;
85-
var hasSingleRoot = false;
86-
var pv = new ProtoView(template, diBindings, protoElementInjectors,
87-
protoWatchGroup, hasSingleRoot);
88-
89-
view = pv.instantiate();
85+
var pv = new ProtoView(template, [], templateElInj(),
86+
new ProtoWatchGroup(), false);
87+
view = pv.instantiate(null, null);
9088
});
9189

9290
it('should consume text node changes', () => {
@@ -98,20 +96,17 @@ export function main() {
9896

9997
it('should consume element binding changes', () => {
10098
var elementWithBinding = view.bindElements[0];
101-
expect(elementWithBinding.hidden).toEqual(false);
99+
expect(elementWithBinding.id).toEqual('');
102100
var record = new Record(null, null);
103-
var memento = new ElementPropertyMemento(0, 'hidden');
104-
record.currentValue = true;
101+
var memento = new ElementPropertyMemento(0, 'id');
102+
record.currentValue = 'foo';
105103
view.onRecordChange(record, memento);
106-
expect(elementWithBinding.hidden).toEqual(true);
104+
expect(elementWithBinding.id).toEqual('foo');
107105
});
108106

109107
it('should consume directive watch expression change.', () => {
110108
var elInj = view.elementInjectors[1];
111109

112-
// TODO(rado): hook-up instantiateDirectives in implementation and
113-
// remove from here.
114-
elInj.instantiateDirectives(null);
115110
expect(elInj.get(Directive).prop).toEqual('foo');
116111
var record = new Record(null, null);
117112
var memento = new DirectivePropertyMemento(1, 0, 'prop',
@@ -121,6 +116,64 @@ export function main() {
121116
expect(elInj.get(Directive).prop).toEqual('bar');
122117
});
123118
});
119+
120+
describe('integration view update with change detector', () => {
121+
var view, cd, ctx;
122+
function setUp(memento) {
123+
var template = DOM.createTemplate(tempalteWithThreeTypesOfBindings);
124+
125+
var protoWatchGroup = new ProtoWatchGroup();
126+
protoWatchGroup.watch(oneFieldAst('foo'), memento);
127+
128+
var pv = new ProtoView(template, [], templateElInj(),
129+
protoWatchGroup, false);
130+
131+
ctx = new MyEvaluationContext();
132+
view = pv.instantiate(ctx, null);
133+
134+
cd = new ChangeDetector(view.watchGroup);
135+
}
136+
137+
it('should consume text node changes', () => {
138+
setUp(0);
139+
140+
ctx.foo = 'buz';
141+
cd.detectChanges();
142+
expect(view.textNodes[0].nodeValue).toEqual('buz');
143+
});
144+
145+
it('should consume element binding changes', () => {
146+
setUp(new ElementPropertyMemento(0, 'id'));
147+
148+
var elementWithBinding = view.bindElements[0];
149+
expect(elementWithBinding.id).toEqual('');
150+
151+
ctx.foo = 'buz';
152+
cd.detectChanges();
153+
expect(elementWithBinding.id).toEqual('buz');
154+
});
155+
156+
it('should consume directive watch expression change.', () => {
157+
var memento = new DirectivePropertyMemento(1, 0, 'prop',
158+
(o, v) => o.prop = v);
159+
setUp(memento);
160+
161+
var elInj = view.elementInjectors[1];
162+
expect(elInj.get(Directive).prop).toEqual('foo');
163+
164+
ctx.foo = 'buz';
165+
cd.detectChanges();
166+
expect(elInj.get(Directive).prop).toEqual('buz');
167+
});
168+
169+
});
124170
});
125171
});
126172
}
173+
174+
class MyEvaluationContext {
175+
@FIELD('foo')
176+
constructor() {
177+
this.foo = 'bar';
178+
};
179+
}

0 commit comments

Comments
 (0)