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:
parent
24646e7eb8
commit
d800d2f5d7
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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); }
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in New Issue