feat: support queries for elements with local names (#20855)
PR Close #20855
This commit is contained in:
		
							parent
							
								
									1f5049f30c
								
							
						
					
					
						commit
						147aec43bd
					
				| @ -11,7 +11,7 @@ | ||||
| import * as viewEngine from '../core'; | ||||
| 
 | ||||
| import {BLOOM_SIZE, NG_ELEMENT_ID, getOrCreateNodeInjector} from './instructions'; | ||||
| import {LContainer, LNodeFlags, LNodeInjector} from './l_node'; | ||||
| import {LContainer, LElement, LNodeFlags, LNodeInjector} from './l_node'; | ||||
| import {ComponentTemplate, DirectiveDef} from './public_interfaces'; | ||||
| import {notImplemented, stringify} from './util'; | ||||
| 
 | ||||
| @ -200,16 +200,24 @@ export function bloomFindPossibleInjector( | ||||
|   return null; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Creates an ElementRef for a given node and stores it on the injector. | ||||
|  * Or, if the ElementRef already exists, retrieves the existing ElementRef. | ||||
|  * | ||||
|  * @returns The ElementRef instance to use | ||||
|  */ | ||||
| export function injectElementRefForNode(node?: LElement | LContainer): viewEngine.ElementRef { | ||||
|   let di = getOrCreateNodeInjector(node); | ||||
|   return di.elementRef || (di.elementRef = new ElementRef(di.node.native)); | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Creates an ElementRef and stores it on the injector. Or, if the ElementRef already | ||||
|  * exists, retrieves the existing ElementRef. | ||||
|  * | ||||
|  * @returns The ElementRef instance to use | ||||
|  */ | ||||
| export function injectElementRef(): viewEngine.ElementRef { | ||||
|   let di = getOrCreateNodeInjector(); | ||||
|   return di.elementRef || (di.elementRef = new ElementRef(di.node.native)); | ||||
| } | ||||
| export const injectElementRef: () => viewEngine.ElementRef = injectElementRefForNode; | ||||
| 
 | ||||
| /** A ref to a node's native element. */ | ||||
| class ElementRef implements viewEngine.ElementRef { | ||||
|  | ||||
| @ -339,9 +339,9 @@ export function bloomAdd(injector: LNodeInjector, type: Type<any>): void { | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export function getOrCreateNodeInjector(): LNodeInjector { | ||||
|   ngDevMode && assertPreviousIsParent(); | ||||
|   const node = previousOrParentNode as LElement | LContainer; | ||||
| export function getOrCreateNodeInjector(node?: LElement | LContainer): LNodeInjector { | ||||
|   ngDevMode && !node && assertPreviousIsParent(); | ||||
|   node = node || previousOrParentNode as LElement | LContainer; | ||||
|   const nodeInjector = node.nodeInjector; | ||||
|   const parentInjector = node.parent && node.parent.nodeInjector; | ||||
|   if (nodeInjector != parentInjector) { | ||||
| @ -376,13 +376,15 @@ export function getOrCreateNodeInjector(): LNodeInjector { | ||||
|  * @param index Index of the element in the data array | ||||
|  * @param nameOrComponentDef Name of the DOM Node or `ComponentDef`. | ||||
|  * @param attrs Statically bound set of attributes to be written into the DOM element on creation. | ||||
|  * @param localName A name under which a given element is exported. | ||||
|  * | ||||
|  * Attributes are passed as an array of strings where elements with an even index hold an attribute | ||||
|  * name and elements with an odd index hold an attribute value, ex.: | ||||
|  * ['id', 'warning5', 'class', 'alert'] | ||||
|  */ | ||||
| export function elementStart( | ||||
|     index: number, nameOrComponentDef?: string | ComponentDef<any>, attrs?: string[]): RElement { | ||||
|     index: number, nameOrComponentDef?: string | ComponentDef<any>, attrs?: string[] | null, | ||||
|     localName?: string): RElement { | ||||
|   let node: LElement; | ||||
|   let native: RElement; | ||||
| 
 | ||||
| @ -413,7 +415,8 @@ export function elementStart( | ||||
| 
 | ||||
|       if (node.staticData == null) { | ||||
|         ngDevMode && assertDataInRange(index - 1); | ||||
|         node.staticData = ngStaticData[index] = createNodeStatic(name, attrs || null, null); | ||||
|         node.staticData = ngStaticData[index] = | ||||
|             createNodeStatic(name, attrs || null, null, localName || null); | ||||
|       } | ||||
| 
 | ||||
|       if (attrs) setUpAttributes(native, attrs); | ||||
| @ -610,10 +613,11 @@ export function elementProperty<T>(index: number, propName: string, value: T | N | ||||
|  */ | ||||
| function createNodeStatic( | ||||
|     tagName: string | null, attrs: string[] | null, | ||||
|     containerStatic: (LNodeStatic | null)[][] | null): LNodeStatic { | ||||
|     containerStatic: (LNodeStatic | null)[][] | null, localName: string | null): LNodeStatic { | ||||
|   return { | ||||
|     tagName, | ||||
|     attrs, | ||||
|     tagName: tagName, | ||||
|     attrs: attrs, | ||||
|     localName: localName, | ||||
|     initialInputs: undefined, | ||||
|     inputs: undefined, | ||||
|     outputs: undefined, | ||||
| @ -973,7 +977,8 @@ export function containerStart( | ||||
|   }); | ||||
| 
 | ||||
|   if (node.staticData == null) { | ||||
|     node.staticData = ngStaticData[index] = createNodeStatic(tagName || null, attrs || null, []); | ||||
|     node.staticData = ngStaticData[index] = | ||||
|         createNodeStatic(tagName || null, attrs || null, [], null); | ||||
|   } | ||||
| 
 | ||||
|   // Containers are added to the current view tree instead of their embedded views
 | ||||
| @ -1696,7 +1701,7 @@ function valueInData<T>(data: any[], index: number, value?: T): T { | ||||
|   return value !; | ||||
| } | ||||
| 
 | ||||
| export function query<T>(predicate: Type<any>| any[], descend?: boolean): QueryList<T> { | ||||
| export function query<T>(predicate: Type<any>| string[], descend?: boolean): QueryList<T> { | ||||
|   ngDevMode && assertPreviousIsParent(); | ||||
|   const queryList = new QueryList<T>(); | ||||
|   const query = currentQuery || (currentQuery = new QueryState_()); | ||||
|  | ||||
| @ -38,6 +38,11 @@ export interface LNodeStatic { | ||||
|    */ | ||||
|   attrs: string[]|null; | ||||
| 
 | ||||
|   /** | ||||
|    * A local name under which a given element is exported in a view. | ||||
|    */ | ||||
|   localName: string|null; | ||||
| 
 | ||||
|   /** | ||||
|    * This property contains information about input properties that | ||||
|    * need to be set once from attribute data. | ||||
|  | ||||
| @ -12,8 +12,9 @@ import {Observable} from 'rxjs/Observable'; | ||||
| import * as viewEngine from '../core'; | ||||
| 
 | ||||
| import {assertNotNull} from './assert'; | ||||
| import {injectElementRefForNode} from './di'; | ||||
| import {QueryState} from './interfaces'; | ||||
| import {LContainer, LNode, LNodeFlags, LView} from './l_node'; | ||||
| import {LContainer, LElement, LNode, LNodeFlags, LView} from './l_node'; | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| @ -37,16 +38,9 @@ export interface QueryPredicate<T> { | ||||
|   type: viewEngine.Type<T>|null; | ||||
| 
 | ||||
|   /** | ||||
|    * If selector then contains the selector parts where: | ||||
|    * - even index: | ||||
|    *    - `null`: represents a tag name | ||||
|    *    - `"#""`: represents a reference name | ||||
|    *    - `string`: name of the attribute | ||||
|    * - odd index: | ||||
|    *    - `null`: any value will match | ||||
|    *    - `string`: the value which mast match. | ||||
|    * If selector then contains local names to query for. | ||||
|    */ | ||||
|   selector: any[]|null; | ||||
|   selector: string[]|null; | ||||
| 
 | ||||
|   /** | ||||
|    * Values which have been located. | ||||
| @ -63,7 +57,7 @@ export class QueryState_ implements QueryState { | ||||
|   constructor(deep?: QueryPredicate<any>) { this.deep = deep == null ? null : deep; } | ||||
| 
 | ||||
|   track<T>( | ||||
|       queryList: viewEngine.QueryList<T>, predicate: viewEngine.Type<T>|any[], | ||||
|       queryList: viewEngine.QueryList<T>, predicate: viewEngine.Type<T>|string[], | ||||
|       descend?: boolean): void { | ||||
|     // TODO(misko): This is not right. In case of inherited state, a calling track will incorrectly
 | ||||
|     // mutate parent.
 | ||||
| @ -117,6 +111,16 @@ function add(predicate: QueryPredicate<any>| null, node: LNode) { | ||||
|           predicate.values.push(node.view.data[i]); | ||||
|         } | ||||
|       } | ||||
|     } else { | ||||
|       const staticData = node.staticData; | ||||
|       if (staticData && staticData.localName) { | ||||
|         const selector = predicate.selector !; | ||||
|         for (let i = 0; i < selector.length; i++) { | ||||
|           if (selector[i] === staticData.localName) { | ||||
|             predicate.values.push(injectElementRefForNode(node as LElement | LContainer)); | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|     predicate = predicate.next; | ||||
|   } | ||||
| @ -124,7 +128,7 @@ function add(predicate: QueryPredicate<any>| null, node: LNode) { | ||||
| 
 | ||||
| function createPredicate<T>( | ||||
|     previous: QueryPredicate<any>| null, queryList: QueryList<T>, | ||||
|     predicate: viewEngine.Type<T>| any[]): QueryPredicate<T> { | ||||
|     predicate: viewEngine.Type<T>| string[]): QueryPredicate<T> { | ||||
|   const isArray = Array.isArray(predicate); | ||||
|   const values = <any>[]; | ||||
|   if ((queryList as any as QueryList_<T>)._valuesTree === null) { | ||||
| @ -134,7 +138,7 @@ function createPredicate<T>( | ||||
|     next: previous, | ||||
|     list: queryList, | ||||
|     type: isArray ? null : predicate as viewEngine.Type<T>, | ||||
|     selector: isArray ? predicate as any[] : null, | ||||
|     selector: isArray ? predicate as string[] : null, | ||||
|     values: values | ||||
|   }; | ||||
| } | ||||
|  | ||||
| @ -14,6 +14,7 @@ function testLStaticData(tagName: string, attrs: string[] | null): LNodeStatic { | ||||
|   return { | ||||
|     tagName, | ||||
|     attrs, | ||||
|     localName: null, | ||||
|     initialInputs: undefined, | ||||
|     inputs: undefined, | ||||
|     outputs: undefined, | ||||
|  | ||||
| @ -48,4 +48,69 @@ describe('query', () => { | ||||
|     expect((parent.query0 as QueryList<any>).toArray()).toEqual([child1]); | ||||
|     expect((parent.query1 as QueryList<any>).toArray()).toEqual([child1, child2]); | ||||
|   }); | ||||
| 
 | ||||
|   describe('local names', () => { | ||||
| 
 | ||||
|     it('should query for a single element', () => { | ||||
| 
 | ||||
|       let elToQuery; | ||||
|       /** | ||||
|        * <div #foo></div> | ||||
|        * <div></div> | ||||
|        * class Cmpt { | ||||
|        *  @ViewChildren('foo') query; | ||||
|        * } | ||||
|        */ | ||||
|       const Cmpt = createComponent('cmpt', function(ctx: any, cm: boolean) { | ||||
|         let tmp: any; | ||||
|         if (cm) { | ||||
|           m(0, Q(['foo'])); | ||||
|           elToQuery = E(1, 'div', [], 'foo'); | ||||
|           e(); | ||||
|           E(2, 'div'); | ||||
|           e(); | ||||
|         } | ||||
|         qR(tmp = m<QueryList<any>>(0)) && (ctx.query = tmp as QueryList<any>); | ||||
|       }); | ||||
| 
 | ||||
|       const cmptInstance = renderComponent(Cmpt); | ||||
|       const query = (cmptInstance.query as QueryList<any>); | ||||
|       expect(query.length).toBe(1); | ||||
|       expect(query.first.nativeElement).toEqual(elToQuery); | ||||
|     }); | ||||
| 
 | ||||
|     it('should query for multiple elements', () => { | ||||
| 
 | ||||
|       let el1ToQuery; | ||||
|       let el2ToQuery; | ||||
|       /** | ||||
|        * <div #foo></div> | ||||
|        * <div></div> | ||||
|        * <div #bar></div> | ||||
|        * class Cmpt { | ||||
|        *  @ViewChildren('foo,bar') query; | ||||
|        * } | ||||
|        */ | ||||
|       const Cmpt = createComponent('cmpt', function(ctx: any, cm: boolean) { | ||||
|         let tmp: any; | ||||
|         if (cm) { | ||||
|           m(0, Q(['foo', 'bar'])); | ||||
|           el1ToQuery = E(1, 'div', null, 'foo'); | ||||
|           e(); | ||||
|           E(2, 'div'); | ||||
|           e(); | ||||
|           el2ToQuery = E(3, 'div', null, 'bar'); | ||||
|           e(); | ||||
|         } | ||||
|         qR(tmp = m<QueryList<any>>(0)) && (ctx.query = tmp as QueryList<any>); | ||||
|       }); | ||||
| 
 | ||||
|       const cmptInstance = renderComponent(Cmpt); | ||||
|       const query = (cmptInstance.query as QueryList<any>); | ||||
|       expect(query.length).toBe(2); | ||||
|       expect(query.first.nativeElement).toEqual(el1ToQuery); | ||||
|       expect(query.last.nativeElement).toEqual(el2ToQuery); | ||||
|     }); | ||||
| 
 | ||||
|   }); | ||||
| }); | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user