diff --git a/modules/angular2/src/core/compiler/element_injector.ts b/modules/angular2/src/core/compiler/element_injector.ts index 821d5e736a..aecdc6edf4 100644 --- a/modules/angular2/src/core/compiler/element_injector.ts +++ b/modules/angular2/src/core/compiler/element_injector.ts @@ -1027,7 +1027,8 @@ export class ElementInjector extends TreeNode { 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 { diff --git a/modules/angular2/src/core/compiler/view_manager_utils.ts b/modules/angular2/src/core/compiler/view_manager_utils.ts index 9f7cba1626..2ba7b27b00 100644 --- a/modules/angular2/src/core/compiler/view_manager_utils.ts +++ b/modules/angular2/src/core/compiler/view_manager_utils.ts @@ -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); + } } } diff --git a/modules/angular2/src/directives/ng_for.ts b/modules/angular2/src/directives/ng_for.ts index e4a982a575..5041c477ad 100644 --- a/modules/angular2/src/directives/ng_for.ts +++ b/modules/angular2/src/directives/ng_for.ts @@ -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]; diff --git a/modules/angular2/src/facade/collection.dart b/modules/angular2/src/facade/collection.dart index fa1f93f87a..2cd79c08cb 100644 --- a/modules/angular2/src/facade/collection.dart +++ b/modules/angular2/src/facade/collection.dart @@ -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; diff --git a/modules/angular2/src/facade/collection.ts b/modules/angular2/src/facade/collection.ts index 175083d788..d0cf91b711 100644 --- a/modules/angular2/src/facade/collection.ts +++ b/modules/angular2/src/facade/collection.ts @@ -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 { return new List(size); } + static createGrowableSize(size): List { return new List(size); } static get(m, k) { return m[k]; } static set(m, k, v) { m[k] = v; } static clone(array: List) { return array.slice(0); } diff --git a/modules/angular2/test/core/compiler/query_integration_spec.ts b/modules/angular2/test/core/compiler/query_integration_spec.ts index ca7852da69..72a7932410 100644 --- a/modules/angular2/test/core/compiler/query_integration_spec.ts +++ b/modules/angular2/test/core/compiler/query_integration_spec.ts @@ -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 = '
' + - '
' + + '
' + '
'; tb.createView(MyComp, {html: template}) @@ -104,7 +101,7 @@ export function main() { inject([TestBed, AsyncTestCompleter], (tb: TestBed, async) => { var template = '
' + - '
' + + '
' + '
'; 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 = '' + + var template = '' + '
' + '
' + - '
'; + ''; tb.createView(MyComp, {html: template}) .then((view) => { diff --git a/modules/angular2/test/core/compiler/view_manager_utils_spec.ts b/modules/angular2/test/core/compiler/view_manager_utils_spec.ts index ec74c4ff48..0c67fb0a2c 100644 --- a/modules/angular2/test/core/compiler/view_manager_utils_spec.ts +++ b/modules/angular2/test/core/compiler/view_manager_utils_spec.ts @@ -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++) { - elementInjectors[i] = createElementInjector(); + if (nestedInjectors && i > 0) { + elementInjectors[i] = createElementInjector(elementInjectors[i - 1]); + } else { + elementInjectors[i] = createElementInjector(); + } preBuiltObjects[i] = new SpyPreBuiltObjects(); } view.init(new SpyChangeDetector(), elementInjectors, elementInjectors, preBuiltObjects, @@ -118,10 +125,9 @@ export function main() { var spyCd = 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 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(); - utils.attachViewInContainer(parentView, 0, contextView, 0, 0, childView); + 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 {