Ivy uses R3Injector, but we are currently pulling in both the StaticInjector (View Engine injector) and the R3Injector when running with Ivy. This commit adds an ivy switch so calling Injector.create() pulls in the correct implementation of the injector depending on whether you are using VE or Ivy. This saves us about 3KB in the bundle. PR Close #30219
443 lines
16 KiB
TypeScript
443 lines
16 KiB
TypeScript
/**
|
|
* @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 {WrappedValue, devModeEqual} from '../change_detection/change_detection';
|
|
import {SOURCE} from '../di/injector_compatibility';
|
|
import {ViewEncapsulation} from '../metadata/view';
|
|
import {RendererType2} from '../render/api';
|
|
import {looseIdentical} from '../util/comparison';
|
|
import {stringify} from '../util/stringify';
|
|
import {expressionChangedAfterItHasBeenCheckedError} from './errors';
|
|
import {BindingDef, BindingFlags, Definition, DefinitionFactory, DepDef, DepFlags, ElementData, NodeDef, NodeFlags, QueryValueType, Services, ViewData, ViewDefinition, ViewDefinitionFactory, ViewFlags, ViewState, asElementData, asTextData} from './types';
|
|
|
|
export const NOOP: any = () => {};
|
|
|
|
const _tokenKeyCache = new Map<any, string>();
|
|
|
|
export function tokenKey(token: any): string {
|
|
let key = _tokenKeyCache.get(token);
|
|
if (!key) {
|
|
key = stringify(token) + '_' + _tokenKeyCache.size;
|
|
_tokenKeyCache.set(token, key);
|
|
}
|
|
return key;
|
|
}
|
|
|
|
export function unwrapValue(view: ViewData, nodeIdx: number, bindingIdx: number, value: any): any {
|
|
if (WrappedValue.isWrapped(value)) {
|
|
value = WrappedValue.unwrap(value);
|
|
const globalBindingIdx = view.def.nodes[nodeIdx].bindingIndex + bindingIdx;
|
|
const oldValue = WrappedValue.unwrap(view.oldValues[globalBindingIdx]);
|
|
view.oldValues[globalBindingIdx] = new WrappedValue(oldValue);
|
|
}
|
|
return value;
|
|
}
|
|
|
|
const UNDEFINED_RENDERER_TYPE_ID = '$$undefined';
|
|
const EMPTY_RENDERER_TYPE_ID = '$$empty';
|
|
|
|
// Attention: this function is called as top level function.
|
|
// Putting any logic in here will destroy closure tree shaking!
|
|
export function createRendererType2(values: {
|
|
styles: (string | any[])[],
|
|
encapsulation: ViewEncapsulation,
|
|
data: {[kind: string]: any[]}
|
|
}): RendererType2 {
|
|
return {
|
|
id: UNDEFINED_RENDERER_TYPE_ID,
|
|
styles: values.styles,
|
|
encapsulation: values.encapsulation,
|
|
data: values.data
|
|
};
|
|
}
|
|
|
|
let _renderCompCount = 0;
|
|
|
|
export function resolveRendererType2(type?: RendererType2 | null): RendererType2|null {
|
|
if (type && type.id === UNDEFINED_RENDERER_TYPE_ID) {
|
|
// first time we see this RendererType2. Initialize it...
|
|
const isFilled =
|
|
((type.encapsulation != null && type.encapsulation !== ViewEncapsulation.None) ||
|
|
type.styles.length || Object.keys(type.data).length);
|
|
if (isFilled) {
|
|
type.id = `c${_renderCompCount++}`;
|
|
} else {
|
|
type.id = EMPTY_RENDERER_TYPE_ID;
|
|
}
|
|
}
|
|
if (type && type.id === EMPTY_RENDERER_TYPE_ID) {
|
|
type = null;
|
|
}
|
|
return type || null;
|
|
}
|
|
|
|
export function checkBinding(
|
|
view: ViewData, def: NodeDef, bindingIdx: number, value: any): boolean {
|
|
const oldValues = view.oldValues;
|
|
if ((view.state & ViewState.FirstCheck) ||
|
|
!looseIdentical(oldValues[def.bindingIndex + bindingIdx], value)) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
export function checkAndUpdateBinding(
|
|
view: ViewData, def: NodeDef, bindingIdx: number, value: any): boolean {
|
|
if (checkBinding(view, def, bindingIdx, value)) {
|
|
view.oldValues[def.bindingIndex + bindingIdx] = value;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
export function checkBindingNoChanges(
|
|
view: ViewData, def: NodeDef, bindingIdx: number, value: any) {
|
|
const oldValue = view.oldValues[def.bindingIndex + bindingIdx];
|
|
if ((view.state & ViewState.BeforeFirstCheck) || !devModeEqual(oldValue, value)) {
|
|
const bindingName = def.bindings[bindingIdx].name;
|
|
throw expressionChangedAfterItHasBeenCheckedError(
|
|
Services.createDebugContext(view, def.nodeIndex), `${bindingName}: ${oldValue}`,
|
|
`${bindingName}: ${value}`, (view.state & ViewState.BeforeFirstCheck) !== 0);
|
|
}
|
|
}
|
|
|
|
export function markParentViewsForCheck(view: ViewData) {
|
|
let currView: ViewData|null = view;
|
|
while (currView) {
|
|
if (currView.def.flags & ViewFlags.OnPush) {
|
|
currView.state |= ViewState.ChecksEnabled;
|
|
}
|
|
currView = currView.viewContainerParent || currView.parent;
|
|
}
|
|
}
|
|
|
|
export function markParentViewsForCheckProjectedViews(view: ViewData, endView: ViewData) {
|
|
let currView: ViewData|null = view;
|
|
while (currView && currView !== endView) {
|
|
currView.state |= ViewState.CheckProjectedViews;
|
|
currView = currView.viewContainerParent || currView.parent;
|
|
}
|
|
}
|
|
|
|
export function dispatchEvent(
|
|
view: ViewData, nodeIndex: number, eventName: string, event: any): boolean|undefined {
|
|
try {
|
|
const nodeDef = view.def.nodes[nodeIndex];
|
|
const startView = nodeDef.flags & NodeFlags.ComponentView ?
|
|
asElementData(view, nodeIndex).componentView :
|
|
view;
|
|
markParentViewsForCheck(startView);
|
|
return Services.handleEvent(view, nodeIndex, eventName, event);
|
|
} catch (e) {
|
|
// Attention: Don't rethrow, as it would cancel Observable subscriptions!
|
|
view.root.errorHandler.handleError(e);
|
|
}
|
|
}
|
|
|
|
export function declaredViewContainer(view: ViewData): ElementData|null {
|
|
if (view.parent) {
|
|
const parentView = view.parent;
|
|
return asElementData(parentView, view.parentNodeDef !.nodeIndex);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* for component views, this is the host element.
|
|
* for embedded views, this is the index of the parent node
|
|
* that contains the view container.
|
|
*/
|
|
export function viewParentEl(view: ViewData): NodeDef|null {
|
|
const parentView = view.parent;
|
|
if (parentView) {
|
|
return view.parentNodeDef !.parent;
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export function renderNode(view: ViewData, def: NodeDef): any {
|
|
switch (def.flags & NodeFlags.Types) {
|
|
case NodeFlags.TypeElement:
|
|
return asElementData(view, def.nodeIndex).renderElement;
|
|
case NodeFlags.TypeText:
|
|
return asTextData(view, def.nodeIndex).renderText;
|
|
}
|
|
}
|
|
|
|
export function elementEventFullName(target: string | null, name: string): string {
|
|
return target ? `${target}:${name}` : name;
|
|
}
|
|
|
|
export function isComponentView(view: ViewData): boolean {
|
|
return !!view.parent && !!(view.parentNodeDef !.flags & NodeFlags.Component);
|
|
}
|
|
|
|
export function isEmbeddedView(view: ViewData): boolean {
|
|
return !!view.parent && !(view.parentNodeDef !.flags & NodeFlags.Component);
|
|
}
|
|
|
|
export function filterQueryId(queryId: number): number {
|
|
return 1 << (queryId % 32);
|
|
}
|
|
|
|
export function splitMatchedQueriesDsl(
|
|
matchedQueriesDsl: [string | number, QueryValueType][] | null): {
|
|
matchedQueries: {[queryId: string]: QueryValueType},
|
|
references: {[refId: string]: QueryValueType},
|
|
matchedQueryIds: number
|
|
} {
|
|
const matchedQueries: {[queryId: string]: QueryValueType} = {};
|
|
let matchedQueryIds = 0;
|
|
const references: {[refId: string]: QueryValueType} = {};
|
|
if (matchedQueriesDsl) {
|
|
matchedQueriesDsl.forEach(([queryId, valueType]) => {
|
|
if (typeof queryId === 'number') {
|
|
matchedQueries[queryId] = valueType;
|
|
matchedQueryIds |= filterQueryId(queryId);
|
|
} else {
|
|
references[queryId] = valueType;
|
|
}
|
|
});
|
|
}
|
|
return {matchedQueries, references, matchedQueryIds};
|
|
}
|
|
|
|
export function splitDepsDsl(deps: ([DepFlags, any] | any)[], sourceName?: string): DepDef[] {
|
|
return deps.map(value => {
|
|
let token: any;
|
|
let flags: DepFlags;
|
|
if (Array.isArray(value)) {
|
|
[flags, token] = value;
|
|
} else {
|
|
flags = DepFlags.None;
|
|
token = value;
|
|
}
|
|
if (token && (typeof token === 'function' || typeof token === 'object') && sourceName) {
|
|
Object.defineProperty(token, SOURCE, {value: sourceName, configurable: true});
|
|
}
|
|
return {flags, token, tokenKey: tokenKey(token)};
|
|
});
|
|
}
|
|
|
|
export function getParentRenderElement(view: ViewData, renderHost: any, def: NodeDef): any {
|
|
let renderParent = def.renderParent;
|
|
if (renderParent) {
|
|
if ((renderParent.flags & NodeFlags.TypeElement) === 0 ||
|
|
(renderParent.flags & NodeFlags.ComponentView) === 0 ||
|
|
(renderParent.element !.componentRendererType &&
|
|
renderParent.element !.componentRendererType !.encapsulation ===
|
|
ViewEncapsulation.Native)) {
|
|
// only children of non components, or children of components with native encapsulation should
|
|
// be attached.
|
|
return asElementData(view, def.renderParent !.nodeIndex).renderElement;
|
|
}
|
|
} else {
|
|
return renderHost;
|
|
}
|
|
}
|
|
|
|
const DEFINITION_CACHE = new WeakMap<any, Definition<any>>();
|
|
|
|
export function resolveDefinition<D extends Definition<any>>(factory: DefinitionFactory<D>): D {
|
|
let value = DEFINITION_CACHE.get(factory) !as D;
|
|
if (!value) {
|
|
value = factory(() => NOOP);
|
|
value.factory = factory;
|
|
DEFINITION_CACHE.set(factory, value);
|
|
}
|
|
return value;
|
|
}
|
|
|
|
export function rootRenderNodes(view: ViewData): any[] {
|
|
const renderNodes: any[] = [];
|
|
visitRootRenderNodes(view, RenderNodeAction.Collect, undefined, undefined, renderNodes);
|
|
return renderNodes;
|
|
}
|
|
|
|
export const enum RenderNodeAction {Collect, AppendChild, InsertBefore, RemoveChild}
|
|
|
|
export function visitRootRenderNodes(
|
|
view: ViewData, action: RenderNodeAction, parentNode: any, nextSibling: any, target?: any[]) {
|
|
// We need to re-compute the parent node in case the nodes have been moved around manually
|
|
if (action === RenderNodeAction.RemoveChild) {
|
|
parentNode = view.renderer.parentNode(renderNode(view, view.def.lastRenderRootNode !));
|
|
}
|
|
visitSiblingRenderNodes(
|
|
view, action, 0, view.def.nodes.length - 1, parentNode, nextSibling, target);
|
|
}
|
|
|
|
export function visitSiblingRenderNodes(
|
|
view: ViewData, action: RenderNodeAction, startIndex: number, endIndex: number, parentNode: any,
|
|
nextSibling: any, target?: any[]) {
|
|
for (let i = startIndex; i <= endIndex; i++) {
|
|
const nodeDef = view.def.nodes[i];
|
|
if (nodeDef.flags & (NodeFlags.TypeElement | NodeFlags.TypeText | NodeFlags.TypeNgContent)) {
|
|
visitRenderNode(view, nodeDef, action, parentNode, nextSibling, target);
|
|
}
|
|
// jump to next sibling
|
|
i += nodeDef.childCount;
|
|
}
|
|
}
|
|
|
|
export function visitProjectedRenderNodes(
|
|
view: ViewData, ngContentIndex: number, action: RenderNodeAction, parentNode: any,
|
|
nextSibling: any, target?: any[]) {
|
|
let compView: ViewData|null = view;
|
|
while (compView && !isComponentView(compView)) {
|
|
compView = compView.parent;
|
|
}
|
|
const hostView = compView !.parent;
|
|
const hostElDef = viewParentEl(compView !);
|
|
const startIndex = hostElDef !.nodeIndex + 1;
|
|
const endIndex = hostElDef !.nodeIndex + hostElDef !.childCount;
|
|
for (let i = startIndex; i <= endIndex; i++) {
|
|
const nodeDef = hostView !.def.nodes[i];
|
|
if (nodeDef.ngContentIndex === ngContentIndex) {
|
|
visitRenderNode(hostView !, nodeDef, action, parentNode, nextSibling, target);
|
|
}
|
|
// jump to next sibling
|
|
i += nodeDef.childCount;
|
|
}
|
|
if (!hostView !.parent) {
|
|
// a root view
|
|
const projectedNodes = view.root.projectableNodes[ngContentIndex];
|
|
if (projectedNodes) {
|
|
for (let i = 0; i < projectedNodes.length; i++) {
|
|
execRenderNodeAction(view, projectedNodes[i], action, parentNode, nextSibling, target);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function visitRenderNode(
|
|
view: ViewData, nodeDef: NodeDef, action: RenderNodeAction, parentNode: any, nextSibling: any,
|
|
target?: any[]) {
|
|
if (nodeDef.flags & NodeFlags.TypeNgContent) {
|
|
visitProjectedRenderNodes(
|
|
view, nodeDef.ngContent !.index, action, parentNode, nextSibling, target);
|
|
} else {
|
|
const rn = renderNode(view, nodeDef);
|
|
if (action === RenderNodeAction.RemoveChild && (nodeDef.flags & NodeFlags.ComponentView) &&
|
|
(nodeDef.bindingFlags & BindingFlags.CatSyntheticProperty)) {
|
|
// Note: we might need to do both actions.
|
|
if (nodeDef.bindingFlags & (BindingFlags.SyntheticProperty)) {
|
|
execRenderNodeAction(view, rn, action, parentNode, nextSibling, target);
|
|
}
|
|
if (nodeDef.bindingFlags & (BindingFlags.SyntheticHostProperty)) {
|
|
const compView = asElementData(view, nodeDef.nodeIndex).componentView;
|
|
execRenderNodeAction(compView, rn, action, parentNode, nextSibling, target);
|
|
}
|
|
} else {
|
|
execRenderNodeAction(view, rn, action, parentNode, nextSibling, target);
|
|
}
|
|
if (nodeDef.flags & NodeFlags.EmbeddedViews) {
|
|
const embeddedViews = asElementData(view, nodeDef.nodeIndex).viewContainer !._embeddedViews;
|
|
for (let k = 0; k < embeddedViews.length; k++) {
|
|
visitRootRenderNodes(embeddedViews[k], action, parentNode, nextSibling, target);
|
|
}
|
|
}
|
|
if (nodeDef.flags & NodeFlags.TypeElement && !nodeDef.element !.name) {
|
|
visitSiblingRenderNodes(
|
|
view, action, nodeDef.nodeIndex + 1, nodeDef.nodeIndex + nodeDef.childCount, parentNode,
|
|
nextSibling, target);
|
|
}
|
|
}
|
|
}
|
|
|
|
function execRenderNodeAction(
|
|
view: ViewData, renderNode: any, action: RenderNodeAction, parentNode: any, nextSibling: any,
|
|
target?: any[]) {
|
|
const renderer = view.renderer;
|
|
switch (action) {
|
|
case RenderNodeAction.AppendChild:
|
|
renderer.appendChild(parentNode, renderNode);
|
|
break;
|
|
case RenderNodeAction.InsertBefore:
|
|
renderer.insertBefore(parentNode, renderNode, nextSibling);
|
|
break;
|
|
case RenderNodeAction.RemoveChild:
|
|
renderer.removeChild(parentNode, renderNode);
|
|
break;
|
|
case RenderNodeAction.Collect:
|
|
target !.push(renderNode);
|
|
break;
|
|
}
|
|
}
|
|
|
|
const NS_PREFIX_RE = /^:([^:]+):(.+)$/;
|
|
|
|
export function splitNamespace(name: string): string[] {
|
|
if (name[0] === ':') {
|
|
const match = name.match(NS_PREFIX_RE) !;
|
|
return [match[1], match[2]];
|
|
}
|
|
return ['', name];
|
|
}
|
|
|
|
export function calcBindingFlags(bindings: BindingDef[]): BindingFlags {
|
|
let flags = 0;
|
|
for (let i = 0; i < bindings.length; i++) {
|
|
flags |= bindings[i].flags;
|
|
}
|
|
return flags;
|
|
}
|
|
|
|
export function interpolate(valueCount: number, constAndInterp: string[]): string {
|
|
let result = '';
|
|
for (let i = 0; i < valueCount * 2; i = i + 2) {
|
|
result = result + constAndInterp[i] + _toStringWithNull(constAndInterp[i + 1]);
|
|
}
|
|
return result + constAndInterp[valueCount * 2];
|
|
}
|
|
|
|
export function inlineInterpolate(
|
|
valueCount: number, c0: string, a1: any, c1: string, a2?: any, c2?: string, a3?: any,
|
|
c3?: string, a4?: any, c4?: string, a5?: any, c5?: string, a6?: any, c6?: string, a7?: any,
|
|
c7?: string, a8?: any, c8?: string, a9?: any, c9?: string): string {
|
|
switch (valueCount) {
|
|
case 1:
|
|
return c0 + _toStringWithNull(a1) + c1;
|
|
case 2:
|
|
return c0 + _toStringWithNull(a1) + c1 + _toStringWithNull(a2) + c2;
|
|
case 3:
|
|
return c0 + _toStringWithNull(a1) + c1 + _toStringWithNull(a2) + c2 + _toStringWithNull(a3) +
|
|
c3;
|
|
case 4:
|
|
return c0 + _toStringWithNull(a1) + c1 + _toStringWithNull(a2) + c2 + _toStringWithNull(a3) +
|
|
c3 + _toStringWithNull(a4) + c4;
|
|
case 5:
|
|
return c0 + _toStringWithNull(a1) + c1 + _toStringWithNull(a2) + c2 + _toStringWithNull(a3) +
|
|
c3 + _toStringWithNull(a4) + c4 + _toStringWithNull(a5) + c5;
|
|
case 6:
|
|
return c0 + _toStringWithNull(a1) + c1 + _toStringWithNull(a2) + c2 + _toStringWithNull(a3) +
|
|
c3 + _toStringWithNull(a4) + c4 + _toStringWithNull(a5) + c5 + _toStringWithNull(a6) + c6;
|
|
case 7:
|
|
return c0 + _toStringWithNull(a1) + c1 + _toStringWithNull(a2) + c2 + _toStringWithNull(a3) +
|
|
c3 + _toStringWithNull(a4) + c4 + _toStringWithNull(a5) + c5 + _toStringWithNull(a6) +
|
|
c6 + _toStringWithNull(a7) + c7;
|
|
case 8:
|
|
return c0 + _toStringWithNull(a1) + c1 + _toStringWithNull(a2) + c2 + _toStringWithNull(a3) +
|
|
c3 + _toStringWithNull(a4) + c4 + _toStringWithNull(a5) + c5 + _toStringWithNull(a6) +
|
|
c6 + _toStringWithNull(a7) + c7 + _toStringWithNull(a8) + c8;
|
|
case 9:
|
|
return c0 + _toStringWithNull(a1) + c1 + _toStringWithNull(a2) + c2 + _toStringWithNull(a3) +
|
|
c3 + _toStringWithNull(a4) + c4 + _toStringWithNull(a5) + c5 + _toStringWithNull(a6) +
|
|
c6 + _toStringWithNull(a7) + c7 + _toStringWithNull(a8) + c8 + _toStringWithNull(a9) + c9;
|
|
default:
|
|
throw new Error(`Does not support more than 9 expressions`);
|
|
}
|
|
}
|
|
|
|
function _toStringWithNull(v: any): string {
|
|
return v != null ? v.toString() : '';
|
|
}
|
|
|
|
export const EMPTY_ARRAY: any[] = [];
|
|
export const EMPTY_MAP: {[key: string]: any} = {};
|