feat(view): hook watch group instantiation in the view.
This commit is contained in:
parent
01e6c7b70c
commit
91f50b67b7
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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';
|
||||
};
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue