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…
Reference in New Issue