diff --git a/packages/core/src/render3/di.ts b/packages/core/src/render3/di.ts index e24e9fad94..74dc9004b1 100644 --- a/packages/core/src/render3/di.ts +++ b/packages/core/src/render3/di.ts @@ -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 { diff --git a/packages/core/src/render3/instructions.ts b/packages/core/src/render3/instructions.ts index 2479caf775..be9b2b9046 100644 --- a/packages/core/src/render3/instructions.ts +++ b/packages/core/src/render3/instructions.ts @@ -339,9 +339,9 @@ export function bloomAdd(injector: LNodeInjector, type: Type): 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, attrs?: string[]): RElement { + index: number, nameOrComponentDef?: string | ComponentDef, 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(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(data: any[], index: number, value?: T): T { return value !; } -export function query(predicate: Type| any[], descend?: boolean): QueryList { +export function query(predicate: Type| string[], descend?: boolean): QueryList { ngDevMode && assertPreviousIsParent(); const queryList = new QueryList(); const query = currentQuery || (currentQuery = new QueryState_()); diff --git a/packages/core/src/render3/l_node_static.ts b/packages/core/src/render3/l_node_static.ts index 4d6b367ca2..b8bcf1f788 100644 --- a/packages/core/src/render3/l_node_static.ts +++ b/packages/core/src/render3/l_node_static.ts @@ -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. diff --git a/packages/core/src/render3/query.ts b/packages/core/src/render3/query.ts index f569fba451..11e52c7340 100644 --- a/packages/core/src/render3/query.ts +++ b/packages/core/src/render3/query.ts @@ -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 { type: viewEngine.Type|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) { this.deep = deep == null ? null : deep; } track( - queryList: viewEngine.QueryList, predicate: viewEngine.Type|any[], + queryList: viewEngine.QueryList, predicate: viewEngine.Type|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| 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| null, node: LNode) { function createPredicate( previous: QueryPredicate| null, queryList: QueryList, - predicate: viewEngine.Type| any[]): QueryPredicate { + predicate: viewEngine.Type| string[]): QueryPredicate { const isArray = Array.isArray(predicate); const values = []; if ((queryList as any as QueryList_)._valuesTree === null) { @@ -134,7 +138,7 @@ function createPredicate( next: previous, list: queryList, type: isArray ? null : predicate as viewEngine.Type, - selector: isArray ? predicate as any[] : null, + selector: isArray ? predicate as string[] : null, values: values }; } diff --git a/packages/core/test/render3/node_selector_matcher_spec.ts b/packages/core/test/render3/node_selector_matcher_spec.ts index 138ef8ddf6..c69dfa096c 100644 --- a/packages/core/test/render3/node_selector_matcher_spec.ts +++ b/packages/core/test/render3/node_selector_matcher_spec.ts @@ -14,6 +14,7 @@ function testLStaticData(tagName: string, attrs: string[] | null): LNodeStatic { return { tagName, attrs, + localName: null, initialInputs: undefined, inputs: undefined, outputs: undefined, diff --git a/packages/core/test/render3/query_spec.ts b/packages/core/test/render3/query_spec.ts index 1097f7fb32..9828822a52 100644 --- a/packages/core/test/render3/query_spec.ts +++ b/packages/core/test/render3/query_spec.ts @@ -48,4 +48,69 @@ describe('query', () => { expect((parent.query0 as QueryList).toArray()).toEqual([child1]); expect((parent.query1 as QueryList).toArray()).toEqual([child1, child2]); }); + + describe('local names', () => { + + it('should query for a single element', () => { + + let elToQuery; + /** + *
+ *
+ * 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>(0)) && (ctx.query = tmp as QueryList); + }); + + const cmptInstance = renderComponent(Cmpt); + const query = (cmptInstance.query as QueryList); + expect(query.length).toBe(1); + expect(query.first.nativeElement).toEqual(elToQuery); + }); + + it('should query for multiple elements', () => { + + let el1ToQuery; + let el2ToQuery; + /** + *
+ *
+ *
+ * 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>(0)) && (ctx.query = tmp as QueryList); + }); + + const cmptInstance = renderComponent(Cmpt); + const query = (cmptInstance.query as QueryList); + expect(query.length).toBe(2); + expect(query.first.nativeElement).toEqual(el1ToQuery); + expect(query.last.nativeElement).toEqual(el2ToQuery); + }); + + }); });