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 * as viewEngine from '../core'; | ||||||
| 
 | 
 | ||||||
| import {BLOOM_SIZE, NG_ELEMENT_ID, getOrCreateNodeInjector} from './instructions'; | 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 {ComponentTemplate, DirectiveDef} from './public_interfaces'; | ||||||
| import {notImplemented, stringify} from './util'; | import {notImplemented, stringify} from './util'; | ||||||
| 
 | 
 | ||||||
| @ -200,16 +200,24 @@ export function bloomFindPossibleInjector( | |||||||
|   return null; |   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 |  * Creates an ElementRef and stores it on the injector. Or, if the ElementRef already | ||||||
|  * exists, retrieves the existing ElementRef. |  * exists, retrieves the existing ElementRef. | ||||||
|  * |  * | ||||||
|  * @returns The ElementRef instance to use |  * @returns The ElementRef instance to use | ||||||
|  */ |  */ | ||||||
| export function injectElementRef(): viewEngine.ElementRef { | export const injectElementRef: () => viewEngine.ElementRef = injectElementRefForNode; | ||||||
|   let di = getOrCreateNodeInjector(); |  | ||||||
|   return di.elementRef || (di.elementRef = new ElementRef(di.node.native)); |  | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| /** A ref to a node's native element. */ | /** A ref to a node's native element. */ | ||||||
| class ElementRef implements viewEngine.ElementRef { | class ElementRef implements viewEngine.ElementRef { | ||||||
|  | |||||||
| @ -339,9 +339,9 @@ export function bloomAdd(injector: LNodeInjector, type: Type<any>): void { | |||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export function getOrCreateNodeInjector(): LNodeInjector { | export function getOrCreateNodeInjector(node?: LElement | LContainer): LNodeInjector { | ||||||
|   ngDevMode && assertPreviousIsParent(); |   ngDevMode && !node && assertPreviousIsParent(); | ||||||
|   const node = previousOrParentNode as LElement | LContainer; |   node = node || previousOrParentNode as LElement | LContainer; | ||||||
|   const nodeInjector = node.nodeInjector; |   const nodeInjector = node.nodeInjector; | ||||||
|   const parentInjector = node.parent && node.parent.nodeInjector; |   const parentInjector = node.parent && node.parent.nodeInjector; | ||||||
|   if (nodeInjector != parentInjector) { |   if (nodeInjector != parentInjector) { | ||||||
| @ -376,13 +376,15 @@ export function getOrCreateNodeInjector(): LNodeInjector { | |||||||
|  * @param index Index of the element in the data array |  * @param index Index of the element in the data array | ||||||
|  * @param nameOrComponentDef Name of the DOM Node or `ComponentDef`. |  * @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 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 |  * 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.: |  * name and elements with an odd index hold an attribute value, ex.: | ||||||
|  * ['id', 'warning5', 'class', 'alert'] |  * ['id', 'warning5', 'class', 'alert'] | ||||||
|  */ |  */ | ||||||
| export function elementStart( | 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 node: LElement; | ||||||
|   let native: RElement; |   let native: RElement; | ||||||
| 
 | 
 | ||||||
| @ -413,7 +415,8 @@ export function elementStart( | |||||||
| 
 | 
 | ||||||
|       if (node.staticData == null) { |       if (node.staticData == null) { | ||||||
|         ngDevMode && assertDataInRange(index - 1); |         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); |       if (attrs) setUpAttributes(native, attrs); | ||||||
| @ -610,10 +613,11 @@ export function elementProperty<T>(index: number, propName: string, value: T | N | |||||||
|  */ |  */ | ||||||
| function createNodeStatic( | function createNodeStatic( | ||||||
|     tagName: string | null, attrs: string[] | null, |     tagName: string | null, attrs: string[] | null, | ||||||
|     containerStatic: (LNodeStatic | null)[][] | null): LNodeStatic { |     containerStatic: (LNodeStatic | null)[][] | null, localName: string | null): LNodeStatic { | ||||||
|   return { |   return { | ||||||
|     tagName, |     tagName: tagName, | ||||||
|     attrs, |     attrs: attrs, | ||||||
|  |     localName: localName, | ||||||
|     initialInputs: undefined, |     initialInputs: undefined, | ||||||
|     inputs: undefined, |     inputs: undefined, | ||||||
|     outputs: undefined, |     outputs: undefined, | ||||||
| @ -973,7 +977,8 @@ export function containerStart( | |||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
|   if (node.staticData == null) { |   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
 |   // 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 !; |   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(); |   ngDevMode && assertPreviousIsParent(); | ||||||
|   const queryList = new QueryList<T>(); |   const queryList = new QueryList<T>(); | ||||||
|   const query = currentQuery || (currentQuery = new QueryState_()); |   const query = currentQuery || (currentQuery = new QueryState_()); | ||||||
|  | |||||||
| @ -38,6 +38,11 @@ export interface LNodeStatic { | |||||||
|    */ |    */ | ||||||
|   attrs: string[]|null; |   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 |    * This property contains information about input properties that | ||||||
|    * need to be set once from attribute data. |    * need to be set once from attribute data. | ||||||
|  | |||||||
| @ -12,8 +12,9 @@ import {Observable} from 'rxjs/Observable'; | |||||||
| import * as viewEngine from '../core'; | import * as viewEngine from '../core'; | ||||||
| 
 | 
 | ||||||
| import {assertNotNull} from './assert'; | import {assertNotNull} from './assert'; | ||||||
|  | import {injectElementRefForNode} from './di'; | ||||||
| import {QueryState} from './interfaces'; | 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; |   type: viewEngine.Type<T>|null; | ||||||
| 
 | 
 | ||||||
|   /** |   /** | ||||||
|    * If selector then contains the selector parts where: |    * If selector then contains local names to query for. | ||||||
|    * - 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. |  | ||||||
|    */ |    */ | ||||||
|   selector: any[]|null; |   selector: string[]|null; | ||||||
| 
 | 
 | ||||||
|   /** |   /** | ||||||
|    * Values which have been located. |    * Values which have been located. | ||||||
| @ -63,7 +57,7 @@ export class QueryState_ implements QueryState { | |||||||
|   constructor(deep?: QueryPredicate<any>) { this.deep = deep == null ? null : deep; } |   constructor(deep?: QueryPredicate<any>) { this.deep = deep == null ? null : deep; } | ||||||
| 
 | 
 | ||||||
|   track<T>( |   track<T>( | ||||||
|       queryList: viewEngine.QueryList<T>, predicate: viewEngine.Type<T>|any[], |       queryList: viewEngine.QueryList<T>, predicate: viewEngine.Type<T>|string[], | ||||||
|       descend?: boolean): void { |       descend?: boolean): void { | ||||||
|     // TODO(misko): This is not right. In case of inherited state, a calling track will incorrectly
 |     // TODO(misko): This is not right. In case of inherited state, a calling track will incorrectly
 | ||||||
|     // mutate parent.
 |     // mutate parent.
 | ||||||
| @ -117,6 +111,16 @@ function add(predicate: QueryPredicate<any>| null, node: LNode) { | |||||||
|           predicate.values.push(node.view.data[i]); |           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; |     predicate = predicate.next; | ||||||
|   } |   } | ||||||
| @ -124,7 +128,7 @@ function add(predicate: QueryPredicate<any>| null, node: LNode) { | |||||||
| 
 | 
 | ||||||
| function createPredicate<T>( | function createPredicate<T>( | ||||||
|     previous: QueryPredicate<any>| null, queryList: QueryList<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 isArray = Array.isArray(predicate); | ||||||
|   const values = <any>[]; |   const values = <any>[]; | ||||||
|   if ((queryList as any as QueryList_<T>)._valuesTree === null) { |   if ((queryList as any as QueryList_<T>)._valuesTree === null) { | ||||||
| @ -134,7 +138,7 @@ function createPredicate<T>( | |||||||
|     next: previous, |     next: previous, | ||||||
|     list: queryList, |     list: queryList, | ||||||
|     type: isArray ? null : predicate as viewEngine.Type<T>, |     type: isArray ? null : predicate as viewEngine.Type<T>, | ||||||
|     selector: isArray ? predicate as any[] : null, |     selector: isArray ? predicate as string[] : null, | ||||||
|     values: values |     values: values | ||||||
|   }; |   }; | ||||||
| } | } | ||||||
|  | |||||||
| @ -14,6 +14,7 @@ function testLStaticData(tagName: string, attrs: string[] | null): LNodeStatic { | |||||||
|   return { |   return { | ||||||
|     tagName, |     tagName, | ||||||
|     attrs, |     attrs, | ||||||
|  |     localName: null, | ||||||
|     initialInputs: undefined, |     initialInputs: undefined, | ||||||
|     inputs: undefined, |     inputs: undefined, | ||||||
|     outputs: undefined, |     outputs: undefined, | ||||||
|  | |||||||
| @ -48,4 +48,69 @@ describe('query', () => { | |||||||
|     expect((parent.query0 as QueryList<any>).toArray()).toEqual([child1]); |     expect((parent.query0 as QueryList<any>).toArray()).toEqual([child1]); | ||||||
|     expect((parent.query1 as QueryList<any>).toArray()).toEqual([child1, child2]); |     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