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++) { | ||||
|         elementInjectors[i] = createElementInjector(); | ||||
|         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 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 { | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user