/** * @license * Copyright Google Inc. All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ import {ElementRef} from '../linker/element_ref'; import {QueryList} from '../linker/query_list'; import {NodeDef, NodeFlags, QueryBindingDef, QueryBindingType, QueryDef, QueryValueType, ViewData, asElementData, asProviderData, asQueryList} from './types'; import {declaredViewContainer, filterQueryId, isEmbeddedView} from './util'; export function queryDef( flags: NodeFlags, id: number, bindings: {[propName: string]: QueryBindingType}): NodeDef { let bindingDefs: QueryBindingDef[] = []; for (let propName in bindings) { const bindingType = bindings[propName]; bindingDefs.push({propName, bindingType}); } return { // will bet set by the view definition index: -1, parent: null, renderParent: null, bindingIndex: -1, outputIndex: -1, // regular values flags, childFlags: 0, directChildFlags: 0, childMatchedQueries: 0, ngContentIndex: -1, matchedQueries: {}, matchedQueryIds: 0, references: {}, childCount: 0, bindings: [], bindingFlags: 0, outputs: [], element: null, provider: null, text: null, query: {id, filterId: filterQueryId(id), bindings: bindingDefs}, ngContent: null }; } export function createQuery(): QueryList { return new QueryList(); } export function dirtyParentQueries(view: ViewData) { const queryIds = view.def.nodeMatchedQueries; while (view.parent && isEmbeddedView(view)) { let tplDef = view.parentNodeDef !; view = view.parent; // content queries const end = tplDef.index + tplDef.childCount; for (let i = 0; i <= end; i++) { const nodeDef = view.def.nodes[i]; if ((nodeDef.flags & NodeFlags.TypeContentQuery) && (nodeDef.flags & NodeFlags.DynamicQuery) && (nodeDef.query !.filterId & queryIds) === nodeDef.query !.filterId) { asQueryList(view, i).setDirty(); } if ((nodeDef.flags & NodeFlags.TypeElement && i + nodeDef.childCount < tplDef.index) || !(nodeDef.childFlags & NodeFlags.TypeContentQuery) || !(nodeDef.childFlags & NodeFlags.DynamicQuery)) { // skip elements that don't contain the template element or no query. i += nodeDef.childCount; } } } // view queries if (view.def.nodeFlags & NodeFlags.TypeViewQuery) { for (let i = 0; i < view.def.nodes.length; i++) { const nodeDef = view.def.nodes[i]; if ((nodeDef.flags & NodeFlags.TypeViewQuery) && (nodeDef.flags & NodeFlags.DynamicQuery)) { asQueryList(view, i).setDirty(); } // only visit the root nodes i += nodeDef.childCount; } } } export function checkAndUpdateQuery(view: ViewData, nodeDef: NodeDef) { const queryList = asQueryList(view, nodeDef.index); if (!queryList.dirty) { return; } let directiveInstance: any; let newValues: any[] = undefined !; if (nodeDef.flags & NodeFlags.TypeContentQuery) { const elementDef = nodeDef.parent !.parent !; newValues = calcQueryValues( view, elementDef.index, elementDef.index + elementDef.childCount, nodeDef.query !, []); directiveInstance = asProviderData(view, nodeDef.parent !.index).instance; } else if (nodeDef.flags & NodeFlags.TypeViewQuery) { newValues = calcQueryValues(view, 0, view.def.nodes.length - 1, nodeDef.query !, []); directiveInstance = view.component; } queryList.reset(newValues); const bindings = nodeDef.query !.bindings; let notify = false; for (let i = 0; i < bindings.length; i++) { const binding = bindings[i]; let boundValue: any; switch (binding.bindingType) { case QueryBindingType.First: boundValue = queryList.first; break; case QueryBindingType.All: boundValue = queryList; notify = true; break; } directiveInstance[binding.propName] = boundValue; } if (notify) { queryList.notifyOnChanges(); } } function calcQueryValues( view: ViewData, startIndex: number, endIndex: number, queryDef: QueryDef, values: any[]): any[] { for (let i = startIndex; i <= endIndex; i++) { const nodeDef = view.def.nodes[i]; const valueType = nodeDef.matchedQueries[queryDef.id]; if (valueType != null) { values.push(getQueryValue(view, nodeDef, valueType)); } if (nodeDef.flags & NodeFlags.TypeElement && nodeDef.element !.template && (nodeDef.element !.template !.nodeMatchedQueries & queryDef.filterId) === queryDef.filterId) { // check embedded views that were attached at the place of their template. const elementData = asElementData(view, i); if (nodeDef.flags & NodeFlags.EmbeddedViews) { const embeddedViews = elementData.viewContainer !._embeddedViews; for (let k = 0; k < embeddedViews.length; k++) { const embeddedView = embeddedViews[k]; const dvc = declaredViewContainer(embeddedView); if (dvc && dvc === elementData) { calcQueryValues(embeddedView, 0, embeddedView.def.nodes.length - 1, queryDef, values); } } } const projectedViews = elementData.template._projectedViews; if (projectedViews) { for (let k = 0; k < projectedViews.length; k++) { const projectedView = projectedViews[k]; calcQueryValues(projectedView, 0, projectedView.def.nodes.length - 1, queryDef, values); } } } if ((nodeDef.childMatchedQueries & queryDef.filterId) !== queryDef.filterId) { // if no child matches the query, skip the children. i += nodeDef.childCount; } } return values; } export function getQueryValue( view: ViewData, nodeDef: NodeDef, queryValueType: QueryValueType): any { if (queryValueType != null) { // a match let value: any; switch (queryValueType) { case QueryValueType.RenderElement: value = asElementData(view, nodeDef.index).renderElement; break; case QueryValueType.ElementRef: value = new ElementRef(asElementData(view, nodeDef.index).renderElement); break; case QueryValueType.TemplateRef: value = asElementData(view, nodeDef.index).template; break; case QueryValueType.ViewContainerRef: value = asElementData(view, nodeDef.index).viewContainer; break; case QueryValueType.Provider: value = asProviderData(view, nodeDef.index).instance; break; } return value; } }