fix(ivy): query nodes from different TemplateRefs inserted into one ViewContainerRef (#24254)
PR Close #24254
This commit is contained in:
		
							parent
							
								
									5cbcb5680b
								
							
						
					
					
						commit
						0561b66a2b
					
				| @ -706,7 +706,7 @@ export function getOrCreateTemplateRef<T>(di: LInjector): viewEngine_TemplateRef | ||||
|     ngDevMode && assertNotNull(hostTNode.tViews, 'TView must be allocated'); | ||||
|     di.templateRef = new TemplateRef<any>( | ||||
|         getOrCreateElementRef(di), hostTNode.tViews as TView, hostNode.data.template !, | ||||
|         getRenderer(), hostNode.queries); | ||||
|         getRenderer(), hostNode.data.queries); | ||||
|   } | ||||
|   return di.templateRef; | ||||
| } | ||||
|  | ||||
| @ -1514,16 +1514,20 @@ export function container( | ||||
|   // Containers are added to the current view tree instead of their embedded views
 | ||||
|   // because views can be removed and re-inserted.
 | ||||
|   addToViewTree(currentView, index, node.data); | ||||
| 
 | ||||
|   const queries = node.queries; | ||||
|   if (queries) { | ||||
|     // prepare place for matching nodes from views inserted into a given container
 | ||||
|     lContainer.queries = queries.container(); | ||||
|   } | ||||
| 
 | ||||
|   createDirectivesAndLocals(localRefs); | ||||
| 
 | ||||
|   isParent = false; | ||||
|   ngDevMode && assertNodeType(previousOrParentNode, TNodeType.Container); | ||||
|   const queries = node.queries; | ||||
|   if (queries) { | ||||
|     // check if a given container node matches
 | ||||
|     queries.addNode(node); | ||||
|     // prepare place for matching nodes from views inserted into a given container
 | ||||
|     lContainer.queries = queries.container(); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -7,9 +7,10 @@ | ||||
|  */ | ||||
| 
 | ||||
| import {NgForOfContext} from '@angular/common'; | ||||
| import {TemplateRef, ViewContainerRef} from '@angular/core'; | ||||
| 
 | ||||
| import {QUERY_READ_CONTAINER_REF, QUERY_READ_ELEMENT_REF, QUERY_READ_FROM_NODE, QUERY_READ_TEMPLATE_REF} from '../../src/render3/di'; | ||||
| import {QueryList, defineComponent, detectChanges} from '../../src/render3/index'; | ||||
| import {QUERY_READ_CONTAINER_REF, QUERY_READ_ELEMENT_REF, QUERY_READ_FROM_NODE, QUERY_READ_TEMPLATE_REF, getOrCreateNodeInjectorForNode, getOrCreateTemplateRef} from '../../src/render3/di'; | ||||
| import {AttributeMarker, QueryList, defineComponent, defineDirective, detectChanges, injectViewContainerRef} from '../../src/render3/index'; | ||||
| import {bind, container, containerRefreshEnd, containerRefreshStart, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, load, loadDirective} from '../../src/render3/instructions'; | ||||
| import {RenderFlags} from '../../src/render3/interfaces/definition'; | ||||
| import {query, queryRefresh} from '../../src/render3/query'; | ||||
| @ -18,6 +19,7 @@ import {NgForOf, NgIf} from './common_with_def'; | ||||
| import {ComponentFixture, createComponent, createDirective, renderComponent} from './render_util'; | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| /** | ||||
|  * Helper function to check if a given candidate object resembles ElementRef | ||||
|  * @param candidate | ||||
| @ -690,6 +692,29 @@ describe('query', () => { | ||||
| 
 | ||||
|     describe('ViewContainerRef', () => { | ||||
| 
 | ||||
|       let directiveInstance: ViewContainerManipulatorDirective|null = null; | ||||
| 
 | ||||
|       class ViewContainerManipulatorDirective { | ||||
|         static ngDirectiveDef = defineDirective({ | ||||
|           type: ViewContainerManipulatorDirective, | ||||
|           selectors: [['', 'vc', '']], | ||||
|           factory: () => { | ||||
|             return directiveInstance = | ||||
|                        new ViewContainerManipulatorDirective(injectViewContainerRef()); | ||||
|           } | ||||
|         }); | ||||
| 
 | ||||
|         constructor(private _vcRef: ViewContainerRef) {} | ||||
| 
 | ||||
|         insertTpl(tpl: TemplateRef<{}>, ctx: {}, idx?: number) { | ||||
|           this._vcRef.createEmbeddedView(tpl, ctx, idx); | ||||
|         } | ||||
| 
 | ||||
|         remove(index?: number) { this._vcRef.remove(index); } | ||||
|       } | ||||
| 
 | ||||
|       beforeEach(() => { directiveInstance = null; }); | ||||
| 
 | ||||
|       it('should report results in views inserted / removed by ngIf', () => { | ||||
| 
 | ||||
|         /** | ||||
| @ -782,6 +807,97 @@ describe('query', () => { | ||||
| 
 | ||||
|       }); | ||||
| 
 | ||||
|       // https://stackblitz.com/edit/angular-rrmmuf?file=src/app/app.component.ts
 | ||||
|       it('should report results when different instances of TemplateRef are inserted into one ViewContainerRefs', | ||||
|          () => { | ||||
|            let tpl1: TemplateRef<{}>; | ||||
|            let tpl2: TemplateRef<{}>; | ||||
| 
 | ||||
| 
 | ||||
|            /** | ||||
|             * <ng-template #tpl1 let-idx="idx"> | ||||
|             *   <div #foo [id]="'foo1_'+idx"></div> | ||||
|             * </ng-template> | ||||
|             * | ||||
|             * <div #foo id="middle"></div> | ||||
|             * | ||||
|             * <ng-template #tpl2 let-idx="idx"> | ||||
|             *   <div #foo [id]="'foo2_'+idx"></div> | ||||
|             * </ng-template> | ||||
|             * | ||||
|             * <ng-template viewInserter #vi="vi"></ng-template> | ||||
|             */ | ||||
|            const Cmpt = createComponent('cmpt', function(rf: RenderFlags, ctx: any) { | ||||
|              let tmp: any; | ||||
|              if (rf & RenderFlags.Create) { | ||||
|                query(0, ['foo'], true, QUERY_READ_FROM_NODE); | ||||
| 
 | ||||
|                container(1, (rf: RenderFlags, ctx: {idx: number}) => { | ||||
|                  if (rf & RenderFlags.Create) { | ||||
|                    elementStart(0, 'div', null, ['foo', '']); | ||||
|                    elementEnd(); | ||||
|                  } | ||||
|                  if (rf & RenderFlags.Update) { | ||||
|                    elementProperty(0, 'id', bind('foo1_' + ctx.idx)); | ||||
|                  } | ||||
|                }, null, []); | ||||
| 
 | ||||
|                elementStart(2, 'div', ['id', 'middle'], ['foo', '']); | ||||
|                elementEnd(); | ||||
| 
 | ||||
|                container(4, (rf: RenderFlags, ctx: {idx: number}) => { | ||||
|                  if (rf & RenderFlags.Create) { | ||||
|                    elementStart(0, 'div', null, ['foo', '']); | ||||
|                    elementEnd(); | ||||
|                  } | ||||
|                  if (rf & RenderFlags.Update) { | ||||
|                    elementProperty(0, 'id', bind('foo2_' + ctx.idx)); | ||||
|                  } | ||||
|                }, null, []); | ||||
| 
 | ||||
|                container(5, undefined, null, [AttributeMarker.SELECT_ONLY, 'vc']); | ||||
|              } | ||||
| 
 | ||||
|              if (rf & RenderFlags.Update) { | ||||
|                tpl1 = getOrCreateTemplateRef(getOrCreateNodeInjectorForNode(load(1))); | ||||
|                tpl2 = getOrCreateTemplateRef(getOrCreateNodeInjectorForNode(load(4))); | ||||
|                queryRefresh(tmp = load<QueryList<any>>(0)) && (ctx.query = tmp as QueryList<any>); | ||||
|              } | ||||
| 
 | ||||
|            }, [ViewContainerManipulatorDirective]); | ||||
| 
 | ||||
|            const fixture = new ComponentFixture(Cmpt); | ||||
|            const qList = fixture.component.query; | ||||
| 
 | ||||
|            expect(qList.length).toBe(1); | ||||
|            expect(qList.first.nativeElement.getAttribute('id')).toBe('middle'); | ||||
| 
 | ||||
|            directiveInstance !.insertTpl(tpl1 !, {idx: 0}, 0); | ||||
|            directiveInstance !.insertTpl(tpl2 !, {idx: 1}, 1); | ||||
|            fixture.update(); | ||||
|            expect(qList.length).toBe(3); | ||||
|            let qListArr = qList.toArray(); | ||||
|            expect(qListArr[0].nativeElement.getAttribute('id')).toBe('foo1_0'); | ||||
|            expect(qListArr[1].nativeElement.getAttribute('id')).toBe('middle'); | ||||
|            expect(qListArr[2].nativeElement.getAttribute('id')).toBe('foo2_1'); | ||||
| 
 | ||||
|            directiveInstance !.insertTpl(tpl1 !, {idx: 1}, 1); | ||||
|            fixture.update(); | ||||
|            expect(qList.length).toBe(4); | ||||
|            qListArr = qList.toArray(); | ||||
|            expect(qListArr[0].nativeElement.getAttribute('id')).toBe('foo1_0'); | ||||
|            expect(qListArr[1].nativeElement.getAttribute('id')).toBe('foo1_1'); | ||||
|            expect(qListArr[2].nativeElement.getAttribute('id')).toBe('middle'); | ||||
|            expect(qListArr[3].nativeElement.getAttribute('id')).toBe('foo2_1'); | ||||
| 
 | ||||
|            directiveInstance !.remove(1); | ||||
|            fixture.update(); | ||||
|            expect(qList.length).toBe(3); | ||||
|            qListArr = qList.toArray(); | ||||
|            expect(qListArr[0].nativeElement.getAttribute('id')).toBe('foo1_0'); | ||||
|            expect(qListArr[1].nativeElement.getAttribute('id')).toBe('middle'); | ||||
|            expect(qListArr[2].nativeElement.getAttribute('id')).toBe('foo2_1'); | ||||
|          }); | ||||
|     }); | ||||
| 
 | ||||
|     describe('JS blocks', () => { | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user