feat(core): more read options for ngIvy queries (#21187)

PR Close #21187
This commit is contained in:
Pawel Kozlowski
2017-12-19 16:51:42 +01:00
committed by Miško Hevery
parent c516bc3b35
commit a62371c0eb
7 changed files with 221 additions and 24 deletions

View File

@ -8,8 +8,7 @@
import {createComponentRef, detectChanges, getHostElement, markDirty, renderComponent} from './component';
import {NgOnChangesFeature, PublicFeature, defineComponent, defineDirective} from './definition';
import {ComponentDef, ComponentTemplate, ComponentType, DirectiveDef, DirectiveDefFlags} from './definition_interfaces';
import {ComponentDef, ComponentTemplate, ComponentType, DirectiveDef, DirectiveDefFlags, DirectiveType} from './definition_interfaces';
// Naming scheme:
// - Capital letters are for creating things: T(Text), E(Element), D(Directive), V(View),
@ -78,6 +77,7 @@ export {
ComponentType,
DirectiveDef,
DirectiveDefFlags,
DirectiveType,
NgOnChangesFeature,
PublicFeature,
defineComponent,

View File

@ -35,7 +35,7 @@ export {queryRefresh} from './query';
export const enum LifecycleHook {ON_INIT = 1, ON_DESTROY = 2, ON_CHANGES = 4}
/**
* directive (D) sets a property on all component instances using this constant as a key and the
* Directive (D) sets a property on all component instances using this constant as a key and the
* component's host node (LElement) as the value. This is used in methods like detectChanges to
* facilitate jumping from an instance to the host node.
*/
@ -645,7 +645,7 @@ function createNodeStatic(
return {
tagName: tagName,
attrs: attrs,
localName: localName,
localNames: localName ? [localName, -1] : null,
initialInputs: undefined,
inputs: undefined,
outputs: undefined,
@ -821,8 +821,10 @@ export function textBinding<T>(index: number, value: T | NO_CHANGE): void {
* @param directiveDef DirectiveDef object which contains information about the template.
*/
export function directive<T>(index: number): T;
export function directive<T>(index: number, directive: T, directiveDef: DirectiveDef<T>): T;
export function directive<T>(index: number, directive?: T, directiveDef?: DirectiveDef<T>): T {
export function directive<T>(
index: number, directive: T, directiveDef: DirectiveDef<T>, localName?: string): T;
export function directive<T>(
index: number, directive?: T, directiveDef?: DirectiveDef<T>, localName?: string): T {
let instance;
if (directive == null) {
// return existing
@ -844,10 +846,17 @@ export function directive<T>(index: number, directive?: T, directiveDef?: Direct
ngDevMode && assertDataInRange(index - 1);
Object.defineProperty(
directive, NG_HOST_SYMBOL, {enumerable: false, value: previousOrParentNode});
data[index] = instance = directive;
if (index >= ngStaticData.length) {
ngStaticData[index] = directiveDef !;
if (localName) {
ngDevMode &&
assertNotNull(previousOrParentNode.staticData, 'previousOrParentNode.staticData');
const nodeStaticData = previousOrParentNode !.staticData !;
(nodeStaticData.localNames || (nodeStaticData.localNames = [])).push(localName, index);
}
}
const diPublic = directiveDef !.diPublic;

View File

@ -41,9 +41,23 @@ export interface LNodeStatic {
attrs: string[]|null;
/**
* A local name under which a given element is exported in a view.
* A set of local names under which a given element is exported in a template and
* visible to queries. An entry in this array can be created for different reasons:
* - an element itself is referenced, ex.: `<div #foo>`
* - a component is referenced, ex.: `<my-cmpt #foo>`
* - a directive is referenced, ex.: `<my-cmpt #foo="directiveExportAs">`.
*
* A given element might have different local names and those names can be associated
* with a directive. We store local names at even indexes while odd indexes are reserved
* for directive index in a view (or `-1` if there is no associated directive).
*
* Some examples:
* - `<div #foo>` => `["foo", -1]`
* - `<my-cmpt #foo>` => `["foo", myCmptIdx]`
* - `<my-cmpt #foo #bar="directiveExportAs">` => `["foo", myCmptIdx, "bar", directiveIdx]`
* - `<div #foo #bar="directiveExportAs">` => `["foo", -1, "bar", directiveIdx]`
*/
localName: string|null;
localNames: (string|number)[]|null;
/**
* This property contains information about input properties that

View File

@ -20,10 +20,9 @@ import {assertNotNull} from './assert';
import {DirectiveDef} from './definition_interfaces';
import {getOrCreateContainerRef, getOrCreateElementRef, getOrCreateNodeInjectorForNode, getOrCreateTemplateRef} from './di';
import {LContainer, LElement, LNode, LNodeFlags, LNodeInjector, LView, QueryReadType, QueryState} from './interfaces';
import {LNodeStatic} from './l_node_static';
import {assertNodeOfPossibleTypes} from './node_assert';
/**
* A predicate which determines if a given element/directive should be included in the query
*/
@ -121,9 +120,7 @@ function readDefaultInjectable(nodeInjector: LNodeInjector, node: LNode): viewEn
function readFromNodeInjector(nodeInjector: LNodeInjector, node: LNode, read: QueryReadType | null):
viewEngine_ElementRef|viewEngine_ViewContainerRef|viewEngine_TemplateRef<any>|undefined {
if (read === null) {
return readDefaultInjectable(nodeInjector, node);
} else if (read === QueryReadType.ElementRef) {
if (read === QueryReadType.ElementRef) {
return getOrCreateElementRef(nodeInjector);
} else if (read === QueryReadType.ViewContainerRef) {
return getOrCreateContainerRef(nodeInjector);
@ -136,6 +133,26 @@ function readFromNodeInjector(nodeInjector: LNodeInjector, node: LNode, read: Qu
}
}
/**
* Goes over local names for a given node and returns directive index
* (or -1 if a local name points to an element).
*
* @param staticData static data of a node to check
* @param selector selector to match
* @returns directive index, -1 or null if a selector didn't match any of the local names
*/
function getIdxOfMatchingSelector(staticData: LNodeStatic, selector: string): number|null {
const localNames = staticData.localNames;
if (localNames) {
for (let i = 0; i < localNames.length; i += 2) {
if (localNames[i] === selector) {
return localNames[i + 1] as number;
}
}
}
return null;
}
function add(predicate: QueryPredicate<any>| null, node: LNode) {
while (predicate) {
const type = predicate.type;
@ -151,15 +168,22 @@ function add(predicate: QueryPredicate<any>| null, node: LNode) {
}
}
} else {
const staticData = node.staticData;
const nodeInjector = getOrCreateNodeInjectorForNode(node as LElement | LContainer);
if (staticData && staticData.localName) {
const selector = predicate.selector !;
for (let i = 0; i < selector.length; i++) {
if (selector[i] === staticData.localName) {
const injectable = readFromNodeInjector(nodeInjector, node, predicate.read);
assertNotNull(injectable, 'injectable');
predicate.values.push(injectable);
const selector = predicate.selector !;
for (let i = 0; i < selector.length; i++) {
ngDevMode && assertNotNull(node.staticData, 'node.staticData');
const directiveIdx = getIdxOfMatchingSelector(node.staticData !, selector[i]);
// is anything on a node matching a selector?
if (directiveIdx !== null) {
if (predicate.read != null) {
predicate.values.push(readFromNodeInjector(nodeInjector, node, predicate.read));
} else {
// is local name pointing to a directive?
if (directiveIdx > -1) {
predicate.values.push(node.view.data[directiveIdx]);
} else {
predicate.values.push(readDefaultInjectable(nodeInjector, node));
}
}
}
}