feat(view): hook watch group instantiation in the view.

This commit is contained in:
Rado Kirov 2014-10-29 15:41:50 -07:00
parent 01e6c7b70c
commit 91f50b67b7
3 changed files with 111 additions and 42 deletions

View File

@ -39,10 +39,12 @@ export class ProtoWatchGroup {
this.tailRecord = tail;
}
instantiate(dispatcher:WatchGroupDispatcher):WatchGroup {
// TODO(rado): the type annotation should be dispatcher:WatchGroupDispatcher.
// but @Implements is not ready yet.
instantiate(dispatcher):WatchGroup {
var watchGroup:WatchGroup = new WatchGroup(this, dispatcher);
var tail:Record = null;
var proto:ProtoRecord;
var proto:ProtoRecord = null;
var prevRecord:Record = null;
if (this.headRecord !== null) {
@ -70,7 +72,9 @@ export class WatchGroup {
@FIELD('final dispatcher:WatchGroupDispatcher')
@FIELD('final headRecord:Record')
@FIELD('final tailRecord:Record')
constructor(protoWatchGroup:ProtoWatchGroup, dispatcher:WatchGroupDispatcher) {
// TODO(rado): the type annotation should be dispatcher:WatchGroupDispatcher.
// but @Implements is not ready yet.
constructor(protoWatchGroup:ProtoWatchGroup, dispatcher) {
this.protoWatchGroup = protoWatchGroup;
this.dispatcher = dispatcher;
this.headRecord = null;

View File

@ -6,6 +6,7 @@ import {ProtoElementInjector, ElementInjector} from './element_injector';
import {SetterFn} from 'change_detection/facade';
import {FIELD, IMPLEMENTS, int, isPresent, isBlank} from 'facade/lang';
import {List} from 'facade/collection';
import {Injector} from 'di/di';
/***
* Const of making objects: http://jsperf.com/instantiate-size-of-object
@ -24,7 +25,8 @@ export class View {
@FIELD('final nodes:List<Node>')
@FIELD('final onChangeDispatcher:OnChangeDispatcher')
constructor(fragment:DocumentFragment, elementInjector:List,
rootElementInjectors:List, textNodes:List, bindElements:List) {
rootElementInjectors:List, textNodes:List, bindElements:List,
protoWatchGroup:ProtoWatchGroup, context) {
this.fragment = fragment;
this.nodes = ListWrapper.clone(fragment.childNodes);
this.elementInjectors = elementInjector;
@ -32,6 +34,8 @@ export class View {
this.onChangeDispatcher = null;
this.textNodes = textNodes;
this.bindElements = bindElements;
this.watchGroup = protoWatchGroup.instantiate(this);
this.watchGroup.setContext(context);
}
onRecordChange(record:Record, target) {
@ -66,28 +70,29 @@ export class ProtoView {
this._template = template;
this._bindings = bindings;
this._protoElementInjectors = protoElementInjectors;
this._protoWatchGroup = protoWatchGroup;
// not implemented
this._protoWatchGroup = protoWatchGroup;
this._useRootElement = useRootElement;
}
instantiate():View {
instantiate(context, appInjector:Injector):View {
var fragment = DOM.clone(this._template.content);
var elements = DOM.querySelectorAll(fragment, ".ng-binding");
var protos = this._protoElementInjectors;
/**
* TODO: vsavkin: benchmark
* If this performs poorly, the three loops can be collapsed into one.
* If this performs poorly, the five loops can be collapsed into one.
*/
var elementInjectors = ProtoView._createElementInjectors(elements, protos);
var rootElementInjectors = ProtoView._rootElementInjectors(elementInjectors);
var textNodes = ProtoView._textNodes(elements, protos);
var bindElements = ProtoView._bindElements(elements, protos);
ProtoView._instantiateDirectives(elementInjectors, appInjector);
return new View(fragment, elementInjectors, rootElementInjectors, textNodes,
bindElements);
bindElements, this._protoWatchGroup, context);
}
static _createElementInjectors(elements, protos) {
@ -103,6 +108,13 @@ export class ProtoView {
return injectors;
}
static _instantiateDirectives(
injectors:List<ElementInjectors>, appInjector:Injector) {
for (var i = 0; i < injectors.length; ++i) {
if (injectors[i] != null) injectors[i].instantiateDirectives(appInjector);
}
}
static _createElementInjector(element, proto) {
//TODO: vsavkin: pass element to `proto.instantiate()` once https://github.com/angular/angular/pull/98 is merged
return proto.hasBindings ? proto.instantiate({view:null}) : null;

View File

@ -2,8 +2,12 @@ import {describe, xit, it, expect, beforeEach} from 'test_lib/test_lib';
import {ProtoView, ElementPropertyMemento, DirectivePropertyMemento} from 'core/compiler/view';
import {Record} from 'change_detection/record';
import {ProtoElementInjector, ElementInjector} from 'core/compiler/element_injector';
import {ProtoWatchGroup} from 'change_detection/watch_group';
import {ChangeDetector} from 'change_detection/change_detector';
import {DOM, Element} from 'facade/dom';
import {FIELD} from 'facade/lang';
import {ImplicitReceiver, FieldRead} from 'change_detection/parser/ast';
import {ClosureMap} from 'change_detection/parser/closure_map';
class Directive {
@FIELD('prop')
@ -13,32 +17,36 @@ class Directive {
}
export function main() {
var oneFieldAst = (fieldName) =>
new FieldRead(new ImplicitReceiver(), fieldName,
(new ClosureMap()).getter(fieldName));
describe('view', function() {
var tempalteWithThreeTypesOfBindings =
'<section class="ng-binding">' +
'Hello {}!' +
'<div directive class="ng-binding">' +
'<span class="ng-binding" [hidden]="exp">don\'t show me</span>' +
'<span class="ng-binding" [id]="exp">don\'t show me</span>' +
'</div>' +
'</section>';
function templateElInj() {
var sectionPI = new ProtoElementInjector(null, [], [0], false);
var divPI = new ProtoElementInjector(sectionPI, [Directive], [], false);
var spanPI = new ProtoElementInjector(divPI, [], [], true);
return [sectionPI, divPI, spanPI];
}
describe('ProtoView', function() {
it('should create view instance and locate basic parts', function() {
var template = DOM.createTemplate(tempalteWithThreeTypesOfBindings);
var diBindings = [];
var sectionPI = new ProtoElementInjector(null, [], [0], false);
var divPI = new ProtoElementInjector(sectionPI, [Directive], [], false);
var spanPI = new ProtoElementInjector(divPI, [], [], true);
var protoElementInjectors = [sectionPI, divPI, spanPI];
var protoWatchGroup = null;
var hasSingleRoot = false;
var pv = new ProtoView(template, diBindings, protoElementInjectors,
protoWatchGroup, hasSingleRoot);
var pv = new ProtoView(template, diBindings, templateElInj(),
new ProtoWatchGroup(), hasSingleRoot);
var view = pv.instantiate();
var view = pv.instantiate(null, null);
var section = DOM.firstChild(template.content);
@ -63,8 +71,9 @@ export function main() {
var sectionPI = new ProtoElementInjector(null, [Directive], [], false);
var divPI = new ProtoElementInjector(sectionPI, [Directive], [], false);
var pv = new ProtoView(template, [], [sectionPI, divPI], null, false);
var view = pv.instantiate();
var pv = new ProtoView(template, [], [sectionPI, divPI],
new ProtoWatchGroup(), false);
var view = pv.instantiate(null, null);
expect(view.rootElementInjectors.length).toEqual(1);
});
@ -73,20 +82,9 @@ export function main() {
var view;
beforeEach(() => {
var template = DOM.createTemplate(tempalteWithThreeTypesOfBindings);
var diBindings = [];
var sectionPI = new ProtoElementInjector(null, [], [0], false);
var divPI = new ProtoElementInjector(sectionPI, [Directive], [], false);
var spanPI = new ProtoElementInjector(divPI, [], [], true);
var protoElementInjectors = [sectionPI, divPI, spanPI];
var protoWatchGroup = null;
var hasSingleRoot = false;
var pv = new ProtoView(template, diBindings, protoElementInjectors,
protoWatchGroup, hasSingleRoot);
view = pv.instantiate();
var pv = new ProtoView(template, [], templateElInj(),
new ProtoWatchGroup(), false);
view = pv.instantiate(null, null);
});
it('should consume text node changes', () => {
@ -98,20 +96,17 @@ export function main() {
it('should consume element binding changes', () => {
var elementWithBinding = view.bindElements[0];
expect(elementWithBinding.hidden).toEqual(false);
expect(elementWithBinding.id).toEqual('');
var record = new Record(null, null);
var memento = new ElementPropertyMemento(0, 'hidden');
record.currentValue = true;
var memento = new ElementPropertyMemento(0, 'id');
record.currentValue = 'foo';
view.onRecordChange(record, memento);
expect(elementWithBinding.hidden).toEqual(true);
expect(elementWithBinding.id).toEqual('foo');
});
it('should consume directive watch expression change.', () => {
var elInj = view.elementInjectors[1];
// TODO(rado): hook-up instantiateDirectives in implementation and
// remove from here.
elInj.instantiateDirectives(null);
expect(elInj.get(Directive).prop).toEqual('foo');
var record = new Record(null, null);
var memento = new DirectivePropertyMemento(1, 0, 'prop',
@ -121,6 +116,64 @@ export function main() {
expect(elInj.get(Directive).prop).toEqual('bar');
});
});
describe('integration view update with change detector', () => {
var view, cd, ctx;
function setUp(memento) {
var template = DOM.createTemplate(tempalteWithThreeTypesOfBindings);
var protoWatchGroup = new ProtoWatchGroup();
protoWatchGroup.watch(oneFieldAst('foo'), memento);
var pv = new ProtoView(template, [], templateElInj(),
protoWatchGroup, false);
ctx = new MyEvaluationContext();
view = pv.instantiate(ctx, null);
cd = new ChangeDetector(view.watchGroup);
}
it('should consume text node changes', () => {
setUp(0);
ctx.foo = 'buz';
cd.detectChanges();
expect(view.textNodes[0].nodeValue).toEqual('buz');
});
it('should consume element binding changes', () => {
setUp(new ElementPropertyMemento(0, 'id'));
var elementWithBinding = view.bindElements[0];
expect(elementWithBinding.id).toEqual('');
ctx.foo = 'buz';
cd.detectChanges();
expect(elementWithBinding.id).toEqual('buz');
});
it('should consume directive watch expression change.', () => {
var memento = new DirectivePropertyMemento(1, 0, 'prop',
(o, v) => o.prop = v);
setUp(memento);
var elInj = view.elementInjectors[1];
expect(elInj.get(Directive).prop).toEqual('foo');
ctx.foo = 'buz';
cd.detectChanges();
expect(elInj.get(Directive).prop).toEqual('buz');
});
});
});
});
}
class MyEvaluationContext {
@FIELD('foo')
constructor() {
this.foo = 'bar';
};
}