/**
* @license
* Copyright Google LLC 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 {Injector} from '../di';
import {ErrorHandler} from '../error_handler';
import {Type} from '../interface/type';
import {ComponentFactory} from '../linker/component_factory';
import {NgModuleRef} from '../linker/ng_module_factory';
import {QueryList} from '../linker/query_list';
import {TemplateRef} from '../linker/template_ref';
import {ViewContainerRef} from '../linker/view_container_ref';
import {Renderer2, RendererFactory2} from '../render/api';
import {RendererType2} from '../render/api_flags';
import {Sanitizer} from '../sanitization/sanitizer';
import {SecurityContext} from '../sanitization/security';
// -------------------------------------
// Defs
// -------------------------------------
/**
* Factory for ViewDefinitions/NgModuleDefinitions.
* We use a function so we can reexeute it in case an error happens and use the given logger
* function to log the error from the definition of the node, which is shown in all browser
* logs.
*/
export interface DefinitionFactory<D extends Definition<any>> {
(logger: NodeLogger): D;
}
/**
* Function to call console.error at the right source location. This is an indirection
* via another function as browser will log the location that actually called
* `console.error`.
*/
export interface NodeLogger {
(): () => void;
}
export interface Definition<DF extends DefinitionFactory<any>> {
factory: DF|null;
}
export interface NgModuleDefinition extends Definition<NgModuleDefinitionFactory> {
providers: NgModuleProviderDef[];
providersByKey: {[tokenKey: string]: NgModuleProviderDef};
modules: any[];
scope: 'root'|'platform'|null;
}
export interface NgModuleDefinitionFactory extends DefinitionFactory<NgModuleDefinition> {}
export interface ViewDefinition extends Definition<ViewDefinitionFactory> {
flags: ViewFlags;
updateDirectives: ViewUpdateFn;
updateRenderer: ViewUpdateFn;
handleEvent: ViewHandleEventFn;
/**
* Order: Depth first.
* Especially providers are before elements / anchors.
*/
nodes: NodeDef[];
/** aggregated NodeFlags for all nodes **/
nodeFlags: NodeFlags;
rootNodeFlags: NodeFlags;
lastRenderRootNode: NodeDef|null;
bindingCount: number;
outputCount: number;
/**
* Binary or of all query ids that are matched by one of the nodes.
* This includes query ids from templates as well.
* Used as a bloom filter.
*/
nodeMatchedQueries: number;
}
export interface ViewDefinitionFactory extends DefinitionFactory<ViewDefinition> {}
export interface ViewUpdateFn {
(check: NodeCheckFn, view: ViewData): void;
}
// helper functions to create an overloaded function type.
export interface NodeCheckFn {
(view: ViewData, nodeIndex: number, argStyle: ArgumentType.Dynamic, values: any[]): any;
(view: ViewData, nodeIndex: number, argStyle: ArgumentType.Inline, v0?: any, v1?: any, v2?: any,
v3?: any, v4?: any, v5?: any, v6?: any, v7?: any, v8?: any, v9?: any): any;
}
export const enum ArgumentType {
Inline = 0,
Dynamic = 1
}
export interface ViewHandleEventFn {
(view: ViewData, nodeIndex: number, eventName: string, event: any): boolean;
}
/**
* Bitmask for ViewDefinition.flags.
*/
export const enum ViewFlags {
None = 0,
OnPush = 1 << 1,
}
/**
* A node definition in the view.
*
* Note: We use one type for all nodes so that loops that loop over all nodes
* of a ViewDefinition stay monomorphic!
*/
export interface NodeDef {
flags: NodeFlags;
// Index of the node in view data and view definition (those are the same)
nodeIndex: number;
// Index of the node in the check functions
// Differ from nodeIndex when nodes are added or removed at runtime (ie after compilation)
checkIndex: number;
parent: NodeDef|null;
renderParent: NodeDef|null;
/** this is checked against NgContentDef.index to find matched nodes */
ngContentIndex: number|null;
/** number of transitive children */
childCount: number;
/** aggregated NodeFlags for all transitive children (does not include self) **/
childFlags: NodeFlags;
/** aggregated NodeFlags for all direct children (does not include self) **/
directChildFlags: NodeFlags;
bindingIndex: number;
bindings: BindingDef[];
bindingFlags: BindingFlags;
outputIndex: number;
outputs: OutputDef[];
/**
* references that the user placed on the element
*/
references: {[refId: string]: QueryValueType};
/**
* ids and value types of all queries that are matched by this node.
*/
matchedQueries: {[queryId: number]: QueryValueType};
/** Binary or of all matched query ids of this node. */
matchedQueryIds: number;
/**
* Binary or of all query ids that are matched by one of the children.
* This includes query ids from templates as well.
* Used as a bloom filter.
*/
childMatchedQueries: number;
element: ElementDef|null;
provider: ProviderDef|null;
text: TextDef|null;
query: QueryDef|null;
ngContent: NgContentDef|null;
}
/**
* Bitmask for NodeDef.flags.
* Naming convention:
* - `Type...`: flags that are mutually exclusive
* - `Cat...`: union of multiple `Type...` (short for category).
*/
export const enum NodeFlags {
None = 0,
TypeElement = 1 << 0,
TypeText = 1 << 1,
ProjectedTemplate = 1 << 2,
CatRenderNode = TypeElement | TypeText,
TypeNgContent = 1 << 3,
TypePipe = 1 << 4,
TypePureArray = 1 << 5,
TypePureObject = 1 << 6,
TypePurePipe = 1 << 7,
CatPureExpression = TypePureArray | TypePureObject | TypePurePipe,
TypeValueProvider = 1 << 8,
TypeClassProvider = 1 << 9,
TypeFactoryProvider = 1 << 10,
TypeUseExistingProvider = 1 << 11,
LazyProvider = 1 << 12,
PrivateProvider = 1 << 13,
TypeDirective = 1 << 14,
Component = 1 << 15,
CatProviderNoDirective =
TypeValueProvider | TypeClassProvider | TypeFactoryProvider | TypeUseExistingProvider,
CatProvider = CatProviderNoDirective | TypeDirective,
OnInit = 1 << 16,
OnDestroy = 1 << 17,
DoCheck = 1 << 18,
OnChanges = 1 << 19,
AfterContentInit = 1 << 20,
AfterContentChecked = 1 << 21,
AfterViewInit = 1 << 22,
AfterViewChecked = 1 << 23,
EmbeddedViews = 1 << 24,
ComponentView = 1 << 25,
TypeContentQuery = 1 << 26,
TypeViewQuery = 1 << 27,
StaticQuery = 1 << 28,
DynamicQuery = 1 << 29,
TypeNgModule = 1 << 30,
EmitDistinctChangesOnly = 1 << 31,
CatQuery = TypeContentQuery | TypeViewQuery,
// mutually exclusive values...
Types = CatRenderNode | TypeNgContent | TypePipe | CatPureExpression | CatProvider | CatQuery
}
export interface BindingDef {
flags: BindingFlags;
ns: string|null;
name: string|null;
nonMinifiedName: string|null;
securityContext: SecurityContext|null;
suffix: string|null;
}
export const enum BindingFlags {
TypeElementAttribute = 1 << 0,
TypeElementClass = 1 << 1,
TypeElementStyle = 1 << 2,
TypeProperty = 1 << 3,
SyntheticProperty = 1 << 4,
SyntheticHostProperty = 1 << 5,
CatSyntheticProperty = SyntheticProperty | SyntheticHostProperty,
// mutually exclusive values...
Types = TypeElementAttribute | TypeElementClass | TypeElementStyle | TypeProperty
}
export interface OutputDef {
type: OutputType;
target: 'window'|'document'|'body'|'component'|null;
eventName: string;
propName: string|null;
}
export const enum OutputType {
ElementOutput,
DirectiveOutput
}
export const enum QueryValueType {
ElementRef = 0,
RenderElement = 1,
TemplateRef = 2,
ViewContainerRef = 3,
Provider = 4
}
export interface ElementDef {
// set to null for `<ng-container>`
name: string|null;
ns: string|null;
/** ns, name, value */
attrs: [string, string, string][]|null;
template: ViewDefinition|null;
componentProvider: NodeDef|null;
componentRendererType: RendererType2|null;
// closure to allow recursive components
componentView: ViewDefinitionFactory|null;
/**
* visible public providers for DI in the view,
* as see from this element. This does not include private providers.
*/
publicProviders: {[tokenKey: string]: NodeDef}|null;
/**
* same as visiblePublicProviders, but also includes private providers
* that are located on this element.
*/
allProviders: {[tokenKey: string]: NodeDef}|null;
handleEvent: ElementHandleEventFn|null;
}
export interface ElementHandleEventFn {
(view: ViewData, eventName: string, event: any): boolean;
}
export interface ProviderDef {
token: any;
value: any;
deps: DepDef[];
}
export interface NgModuleProviderDef {
flags: NodeFlags;
index: number;
token: any;
value: any;
deps: DepDef[];
}
export interface DepDef {
flags: DepFlags;
token: any;
tokenKey: string;
}
/**
* Bitmask for DI flags
*/
export const enum DepFlags {
None = 0,
SkipSelf = 1 << 0,
Optional = 1 << 1,
Self = 1 << 2,
Value = 1 << 3,
}
export interface TextDef {
prefix: string;
}
export interface QueryDef {
id: number;
// variant of the id that can be used to check against NodeDef.matchedQueryIds, ...
filterId: number;
bindings: QueryBindingDef[];
}
export interface QueryBindingDef {
propName: string;
bindingType: QueryBindingType;
}
export const enum QueryBindingType {
First = 0,
All = 1
}
export interface NgContentDef {
/**
* this index is checked against NodeDef.ngContentIndex to find the nodes
* that are matched by this ng-content.
* Note that a NodeDef with an ng-content can be reprojected, i.e.
* have a ngContentIndex on its own.
*/
index: number;
}
// -------------------------------------
// Data
// -------------------------------------
export interface NgModuleData extends Injector, NgModuleRef<any> {
// Note: we are using the prefix _ as NgModuleData is an NgModuleRef and therefore directly
// exposed to the user.
_def: NgModuleDefinition;
_parent: Injector;
_providers: any[];
}
/**
* View instance data.
* Attention: Adding fields to this is performance sensitive!
*/
export interface ViewData {
def: ViewDefinition;
root: RootData;
renderer: Renderer2;
// index of component provider / anchor.
parentNodeDef: NodeDef|null;
parent: ViewData|null;
viewContainerParent: ViewData|null;
component: any;
context: any;
// Attention: Never loop over this, as this will
// create a polymorphic usage site.
// Instead: Always loop over ViewDefinition.nodes,
// and call the right accessor (e.g. `elementData`) based on
// the NodeType.
nodes: {[key: number]: NodeData};
state: ViewState;
oldValues: any[];
disposables: DisposableFn[]|null;
initIndex: number;
}
/**
* Bitmask of states
*/
export const enum ViewState {
BeforeFirstCheck = 1 << 0,
FirstCheck = 1 << 1,
Attached = 1 << 2,
ChecksEnabled = 1 << 3,
IsProjectedView = 1 << 4,
CheckProjectedView = 1 << 5,
CheckProjectedViews = 1 << 6,
Destroyed = 1 << 7,
// InitState Uses 3 bits
InitState_Mask = 7 << 8,
InitState_BeforeInit = 0 << 8,
InitState_CallingOnInit = 1 << 8,
InitState_CallingAfterContentInit = 2 << 8,
InitState_CallingAfterViewInit = 3 << 8,
InitState_AfterInit = 4 << 8,
CatDetectChanges = Attached | ChecksEnabled,
CatInit = BeforeFirstCheck | CatDetectChanges | InitState_BeforeInit
}
// Called before each cycle of a view's check to detect whether this is in the
// initState for which we need to call ngOnInit, ngAfterContentInit or ngAfterViewInit
// lifecycle methods. Returns true if this check cycle should call lifecycle
// methods.
export function shiftInitState(
view: ViewData, priorInitState: ViewState, newInitState: ViewState): boolean {
// Only update the InitState if we are currently in the prior state.
// For example, only move into CallingInit if we are in BeforeInit. Only
// move into CallingContentInit if we are in CallingInit. Normally this will
// always be true because of how checkCycle is called in checkAndUpdateView.
// However, if checkAndUpdateView is called recursively or if an exception is
// thrown while checkAndUpdateView is running, checkAndUpdateView starts over
// from the beginning. This ensures the state is monotonically increasing,
// terminating in the AfterInit state, which ensures the Init methods are called
// at least once and only once.
const state = view.state;
const initState = state & ViewState.InitState_Mask;
if (initState === priorInitState) {
view.state = (state & ~ViewState.InitState_Mask) | newInitState;
view.initIndex = -1;
return true;
}
return initState === newInitState;
}
// Returns true if the lifecycle init method should be called for the node with
// the given init index.
export function shouldCallLifecycleInitHook(
view: ViewData, initState: ViewState, index: number): boolean {
if ((view.state & ViewState.InitState_Mask) === initState && view.initIndex <= index) {
view.initIndex = index + 1;
return true;
}
return false;
}
export interface DisposableFn {
(): void;
}
/**
* Node instance data.
*
* We have a separate type per NodeType to save memory
* (TextData | ElementData | ProviderData | PureExpressionData | QueryList<any>)
*
* To keep our code monomorphic,
* we prohibit using `NodeData` directly but enforce the use of accessors (`asElementData`, ...).
* This way, no usage site can get a `NodeData` from view.nodes and then use it for different
* purposes.
*/
export class NodeData {
private __brand: any;
}
/**
* Data for an instantiated NodeType.Text.
*
* Attention: Adding fields to this is performance sensitive!
*/
export interface TextData {
renderText: any;
}
/**
* Accessor for view.nodes, enforcing that every usage site stays monomorphic.
*/
export function asTextData(view: ViewData, index: number): TextData {
return <any>view.nodes[index];
}
/**
* Data for an instantiated NodeType.Element.
*
* Attention: Adding fields to this is performance sensitive!
*/
export interface ElementData {
renderElement: any;
componentView: ViewData;
viewContainer: ViewContainerData|null;
template: TemplateData;
}
export interface ViewContainerData extends ViewContainerRef {
// Note: we are using the prefix _ as ViewContainerData is a ViewContainerRef and therefore
// directly
// exposed to the user.
_embeddedViews: ViewData[];
}
export interface TemplateData extends TemplateRef<any> {
// views that have been created from the template
// of this element,
// but inserted into the embeddedViews of another element.
// By default, this is undefined.
// Note: we are using the prefix _ as TemplateData is a TemplateRef and therefore directly
// exposed to the user.
_projectedViews: ViewData[];
}
/**
* Accessor for view.nodes, enforcing that every usage site stays monomorphic.
*/
export function asElementData(view: ViewData, index: number): ElementData {
return <any>view.nodes[index];
}
/**
* Data for an instantiated NodeType.Provider.
*
* Attention: Adding fields to this is performance sensitive!
*/
export interface ProviderData {
instance: any;
}
/**
* Accessor for view.nodes, enforcing that every usage site stays monomorphic.
*/
export function asProviderData(view: ViewData, index: number): ProviderData {
return <any>view.nodes[index];
}
/**
* Data for an instantiated NodeType.PureExpression.
*
* Attention: Adding fields to this is performance sensitive!
*/
export interface PureExpressionData {
value: any;
}
/**
* Accessor for view.nodes, enforcing that every usage site stays monomorphic.
*/
export function asPureExpressionData(view: ViewData, index: number): PureExpressionData {
return <any>view.nodes[index];
}
/**
* Accessor for view.nodes, enforcing that every usage site stays monomorphic.
*/
export function asQueryList(view: ViewData, index: number): QueryList<any> {
return <any>view.nodes[index];
}
export interface RootData {
injector: Injector;
ngModule: NgModuleRef<any>;
projectableNodes: any[][];
selectorOrNode: any;
renderer: Renderer2;
rendererFactory: RendererFactory2;
errorHandler: ErrorHandler;
sanitizer: Sanitizer;
}
export abstract class DebugContext {
abstract get view(): ViewData;
abstract get nodeIndex(): number|null;
abstract get injector(): Injector;
abstract get component(): any;
abstract get providerTokens(): any[];
abstract get references(): {[key: string]: any};
abstract get context(): any;
abstract get componentRenderElement(): any;
abstract get renderNode(): any;
abstract logError(console: Console, ...values: any[]): void;
}
// -------------------------------------
// Other
// -------------------------------------
export const enum CheckType {
CheckAndUpdate,
CheckNoChanges
}
export interface ProviderOverride {
token: any;
flags: NodeFlags;
value: any;
deps: ([DepFlags, any]|any)[];
deprecatedBehavior: boolean;
}
export interface Services {
setCurrentNode(view: ViewData, nodeIndex: number): void;
createRootView(
injector: Injector, projectableNodes: any[][], rootSelectorOrNode: string|any,
def: ViewDefinition, ngModule: NgModuleRef<any>, context?: any): ViewData;
createEmbeddedView(parent: ViewData, anchorDef: NodeDef, viewDef: ViewDefinition, context?: any):
ViewData;
createComponentView(
parentView: ViewData, nodeDef: NodeDef, viewDef: ViewDefinition, hostElement: any): ViewData;
createNgModuleRef(
moduleType: Type<any>, parent: Injector, bootstrapComponents: Type<any>[],
def: NgModuleDefinition): NgModuleRef<any>;
overrideProvider(override: ProviderOverride): void;
overrideComponentView(compType: Type<any>, compFactory: ComponentFactory<any>): void;
clearOverrides(): void;
checkAndUpdateView(view: ViewData): void;
checkNoChangesView(view: ViewData): void;
destroyView(view: ViewData): void;
resolveDep(
view: ViewData, elDef: NodeDef|null, allowPrivateServices: boolean, depDef: DepDef,
notFoundValue?: any): any;
createDebugContext(view: ViewData, nodeIndex: number): DebugContext;
handleEvent: ViewHandleEventFn;
updateDirectives: (view: ViewData, checkType: CheckType) => void;
updateRenderer: (view: ViewData, checkType: CheckType) => void;
dirtyParentQueries: (view: ViewData) => void;
}
/**
* This object is used to prevent cycles in the source files and to have a place where
* debug mode can hook it. It is lazily filled when `isDevMode` is known.
*/
export const Services: Services = {
setCurrentNode: undefined!,
createRootView: undefined!,
createEmbeddedView: undefined!,
createComponentView: undefined!,
createNgModuleRef: undefined!,
overrideProvider: undefined!,
overrideComponentView: undefined!,
clearOverrides: undefined!,
checkAndUpdateView: undefined!,
checkNoChangesView: undefined!,
destroyView: undefined!,
resolveDep: undefined!,
createDebugContext: undefined!,
handleEvent: undefined!,
updateDirectives: undefined!,
updateRenderer: undefined!,
dirtyParentQueries: undefined!,
};