fix(injectors): sync injector tree with dom element tree.

Changes adds createGrowableSize method to allow for growable lists with fixed
start.

Closes: #2498
This commit is contained in:
Rado Kirov 2015-06-22 17:52:34 -07:00
parent 24646e7eb8
commit d800d2f5d7
7 changed files with 64 additions and 26 deletions

View File

@ -1027,7 +1027,8 @@ export class ElementInjector extends TreeNode<ElementInjector> {
if (queryRef.query.descendants == false) {
if (this == queryRef.originator) {
this._addQueryToTreeSelfAndRecurse(queryRef);
} else if (this.parent == queryRef.originator && this._proto.distanceToParent == 1) {
// TODO(rado): add check for distance to parent = 1 when issue #2707 is fixed.
} else if (this.parent == queryRef.originator) {
this._assignQueryRef(queryRef);
}
} else {

View File

@ -86,6 +86,7 @@ export class AppViewManagerUtils {
this._hydrateView(hostView, injector, null, new Object(), null);
}
// Misnomer: this method is attaching next to the view container.
attachViewInContainer(parentView: viewModule.AppView, boundElementIndex: number,
contextView: viewModule.AppView, contextBoundElementIndex: number,
atIndex: number, view: viewModule.AppView) {
@ -104,7 +105,11 @@ export class AppViewManagerUtils {
}
var elementInjector = contextView.elementInjectors[contextBoundElementIndex];
for (var i = view.rootElementInjectors.length - 1; i >= 0; i--) {
view.rootElementInjectors[i].linkAfter(elementInjector, sibling);
if (isPresent(elementInjector.parent)) {
view.rootElementInjectors[i].linkAfter(elementInjector.parent, sibling);
} else {
contextView.rootElementInjectors.push(view.rootElementInjectors[i]);
}
}
}
@ -115,7 +120,13 @@ export class AppViewManagerUtils {
view.changeDetector.remove();
ListWrapper.removeAt(viewContainer.views, atIndex);
for (var i = 0; i < view.rootElementInjectors.length; ++i) {
view.rootElementInjectors[i].unlink();
var inj = view.rootElementInjectors[i];
if (isPresent(inj.parent)) {
inj.unlink();
} else {
var removeIdx = ListWrapper.indexOf(parentView.rootElementInjectors, inj);
ListWrapper.removeAt(parentView.rootElementInjectors, removeIdx);
}
}
}

View File

@ -107,7 +107,7 @@ export class NgFor {
return movedTuples;
}
static bulkInsert(tuples, viewContainer, protoViewRef) {
static bulkInsert(tuples, viewContainer: ViewContainerRef, protoViewRef: ProtoViewRef) {
tuples.sort((a, b) => a.record.currentIndex - b.record.currentIndex);
for (var i = 0; i < tuples.length; i++) {
var tuple = tuples[i];

View File

@ -93,6 +93,8 @@ class StringMapWrapper {
class ListWrapper {
static List clone(Iterable l) => new List.from(l);
static List createFixedSize(int size) => new List(size);
static List createGrowableSize(int size) =>
new List.generate(size, (_) => null, growable: true);
static get(List m, int k) => m[k];
static void set(List m, int k, v) {
m[k] = v;

View File

@ -140,7 +140,10 @@ export class StringMapWrapper {
}
export class ListWrapper {
// JS has no way to express a staticly fixed size list, but dart does so we
// keep both methods.
static createFixedSize(size): List<any> { return new List(size); }
static createGrowableSize(size): List<any> { return new List(size); }
static get(m, k) { return m[k]; }
static set(m, k, v) { m[k] = v; }
static clone(array: List<any>) { return array.slice(0); }

View File

@ -76,14 +76,11 @@ export function main() {
});
}));
// TODO(rado): The test below should be using descendants: false,
// but due to a bug with how injectors are hooked up query considers the
// directives to be distances 2 instead of direct children.
it('should reflect dynamically inserted directives',
inject([TestBed, AsyncTestCompleter], (tb: TestBed, async) => {
var template =
'<div text="1"></div>' +
'<needs-query-desc text="2"><div *ng-if="shouldShow" [text]="\'3\'"></div></needs-query-desc>' +
'<needs-query text="2"><div *ng-if="shouldShow" [text]="\'3\'"></div></needs-query>' +
'<div text="4"></div>';
tb.createView(MyComp, {html: template})
@ -104,7 +101,7 @@ export function main() {
inject([TestBed, AsyncTestCompleter], (tb: TestBed, async) => {
var template =
'<div text="1"></div>' +
'<needs-query-desc text="2"><div *ng-for="var i of list" [text]="i"></div></needs-query-desc>' +
'<needs-query text="2"><div *ng-for="var i of list" [text]="i"></div></needs-query>' +
'<div text="4"></div>';
tb.createView(MyComp, {html: template})
@ -126,10 +123,10 @@ export function main() {
describe("onChange", () => {
it('should notify query on change',
inject([TestBed, AsyncTestCompleter], (tb: TestBed, async) => {
var template = '<needs-query-desc #q>' +
var template = '<needs-query #q>' +
'<div text="1"></div>' +
'<div *ng-if="shouldShow" text="2"></div>' +
'</needs-query-desc>';
'</needs-query>';
tb.createView(MyComp, {html: template})
.then((view) => {

View File

@ -27,7 +27,8 @@ import {ElementBinder} from 'angular2/src/core/compiler/element_binder';
import {
DirectiveBinding,
ElementInjector,
PreBuiltObjects
PreBuiltObjects,
ProtoElementInjector
} from 'angular2/src/core/compiler/element_injector';
import {DirectiveResolver} from 'angular2/src/core/compiler/directive_resolver';
import {Component} from 'angular2/annotations';
@ -66,10 +67,12 @@ export function main() {
return res;
}
function createElementInjector() {
function createElementInjector(parent = null) {
var host = new SpyElementInjector();
var appInjector = new SpyInjector();
return SpyObject.stub(new SpyElementInjector(),
var elementInjector =
isPresent(parent) ? new SpyElementInjectorWithParent(parent) : new SpyElementInjector();
return SpyObject.stub(elementInjector,
{
'isExportingComponent': false,
'isExportingElement': false,
@ -82,15 +85,19 @@ export function main() {
{});
}
function createView(pv = null) {
function createView(pv = null, nestedInjectors = false) {
if (isBlank(pv)) {
pv = createProtoView();
}
var view = new AppView(null, pv, new Map());
var elementInjectors = ListWrapper.createFixedSize(pv.elementBinders.length);
var elementInjectors = ListWrapper.createGrowableSize(pv.elementBinders.length);
var preBuiltObjects = ListWrapper.createFixedSize(pv.elementBinders.length);
for (var i = 0; i < pv.elementBinders.length; i++) {
if (nestedInjectors && i > 0) {
elementInjectors[i] = createElementInjector(elementInjectors[i - 1]);
} else {
elementInjectors[i] = createElementInjector();
}
preBuiltObjects[i] = new SpyPreBuiltObjects();
}
view.init(<any>new SpyChangeDetector(), elementInjectors, elementInjectors, preBuiltObjects,
@ -118,10 +125,9 @@ export function main() {
var spyCd = <any>componentView.changeDetector;
spyCd.spy('hydrate').andCallFake(log.fn('hydrateCD'));
utils.hydrateComponentView(hostView, 0)
utils.hydrateComponentView(hostView, 0);
expect(log.result())
.toEqual('hydrate; hydrateCD');
expect(log.result()).toEqual('hydrate; hydrateCD');
});
});
@ -187,25 +193,32 @@ export function main() {
describe('attachViewInContainer', () => {
var parentView, contextView, childView;
function createViews() {
function createViews(numInj = 1) {
var parentPv = createProtoView([createEmptyElBinder()]);
parentView = createView(parentPv);
var contextPv = createProtoView([createEmptyElBinder()]);
contextView = createView(contextPv);
var binders = [];
for (var i = 0; i < numInj; i++) binders.push(createEmptyElBinder());
var contextPv = createProtoView(binders);
contextView = createView(contextPv, true);
var childPv = createProtoView([createEmptyElBinder()]);
childView = createView(childPv);
}
it('should link the views rootElementInjectors after the elementInjector at the given context',
() => {
it('should link the views rootElementInjectors at the given context', () => {
createViews();
utils.attachViewInContainer(parentView, 0, contextView, 0, 0, childView);
expect(contextView.elementInjectors.length).toEqual(2);
});
it('should link the views rootElementInjectors after the elementInjector at the given context',
() => {
createViews(2);
utils.attachViewInContainer(parentView, 0, contextView, 1, 0, childView);
expect(childView.rootElementInjectors[0].spy('linkAfter'))
.toHaveBeenCalledWith(contextView.elementInjectors[0], null);
});
});
describe('hydrateViewInContainer', () => {
@ -279,6 +292,17 @@ class SpyElementInjector extends SpyObject {
noSuchMethod(m) { return super.noSuchMethod(m) }
}
@proxy
@IMPLEMENTS(ElementInjector)
class SpyElementInjectorWithParent extends SpyObject {
parent: ElementInjector;
constructor(parent) {
super(ElementInjector);
this.parent = parent;
}
noSuchMethod(m) { return super.noSuchMethod(m) }
}
@proxy
@IMPLEMENTS(PreBuiltObjects)
class SpyPreBuiltObjects extends SpyObject {