2017-12-01 14:23:03 -08:00
/ * *
* @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 './ng_dev_mode' ;
2018-02-12 22:46:15 -08:00
import { assertEqual , assertLessThan , assertNotEqual , assertNotNull , assertNull , assertSame } from './assert' ;
2018-01-10 18:19:16 -08:00
import { LContainer , TContainer } from './interfaces/container' ;
2018-02-26 16:58:15 -08:00
import { LInjector } from './interfaces/injector' ;
2018-03-29 16:41:45 -07:00
import { CssSelectorList , LProjection , NG_PROJECT_AS_ATTR_NAME } from './interfaces/projection' ;
2018-01-29 14:51:37 +01:00
import { LQueries } from './interfaces/query' ;
2018-02-23 13:17:20 -08:00
import { LView , LViewFlags , LifecycleStage , RootContext , TData , TView } from './interfaces/view' ;
2017-12-14 15:03:46 -08:00
2018-03-20 19:06:49 -07:00
import { LContainerNode , LElementNode , LNode , LNodeType , TNodeFlags , LProjectionNode , LTextNode , LViewNode , TNode , TContainerNode , InitialInputData , InitialInputs , PropertyAliases , PropertyAliasValue , } from './interfaces/node' ;
2018-01-31 15:50:24 +01:00
import { assertNodeType } from './node_assert' ;
2018-01-26 11:36:31 +01:00
import { appendChild , insertChild , insertView , appendProjectedNode , removeView , canInsertNativeNode } from './node_manipulation' ;
2018-03-29 16:41:45 -07:00
import { isNodeMatchingSelectorList , matchingSelectorIndex } from './node_selector_matcher' ;
2018-03-27 15:53:48 -07:00
import { ComponentDef , ComponentTemplate , ComponentType , DirectiveDef , DirectiveDefList , DirectiveDefListOrFactory , DirectiveType , PipeDef , PipeDefListOrFactory } from './interfaces/definition' ;
2018-02-16 16:23:27 +01:00
import { RElement , RText , Renderer3 , RendererFactory3 , ProceduralRenderer3 , ObjectOrientedRenderer3 , RendererStyleFlags3 , isProceduralRenderer } from './interfaces/renderer' ;
2017-12-01 14:23:03 -08:00
import { isDifferent , stringify } from './util' ;
2018-03-13 11:48:09 -07:00
import { executeHooks , queueLifecycleHooks , queueInitHooks , executeInitHooks } from './hooks' ;
2018-02-26 16:58:15 -08:00
import { ViewRef } from './view_ref' ;
2017-12-01 14:23:03 -08:00
2017-12-14 15:50:01 -08:00
/ * *
2017-12-19 16:51:42 +01:00
* Directive ( D ) sets a property on all component instances using this constant as a key and the
2017-12-14 16:26:28 -08:00
* 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 .
2017-12-14 15:50:01 -08:00
* /
2017-12-01 14:23:03 -08:00
export const NG_HOST_SYMBOL = '__ngHostLNode__' ;
2018-02-23 13:17:20 -08:00
/ * *
* A permanent marker promise which signifies that the current CD tree is
* clean .
* /
const _CLEAN_PROMISE = Promise . resolve ( null ) ;
2018-03-01 17:14:01 -08:00
/ * *
* Function used to sanitize the value before writing it into the renderer .
* /
export type Sanitizer = ( value : any ) = > string ;
2018-03-16 16:42:13 -07:00
/ * *
* Directive and element indices for top - level directive .
*
* Saved here to avoid re - instantiating an array on every change detection run .
* /
2018-03-25 21:32:39 -07:00
export const _ROOT_DIRECTIVE_INDICES = [ 0 , 0 ] ;
2018-03-16 16:42:13 -07:00
2018-02-23 13:17:20 -08:00
2017-12-01 14:23:03 -08:00
/ * *
* This property gets set before entering a template .
2017-12-14 15:50:01 -08:00
*
* This renderer can be one of two varieties of Renderer3 :
*
* - ObjectedOrientedRenderer3
*
2017-12-14 16:26:28 -08:00
* This is the native browser API style , e . g . operations are methods on individual objects
2017-12-14 15:03:46 -08:00
* like HTMLElement . With this style , no additional code is needed as a facade ( reducing payload
* size ) .
2017-12-14 15:50:01 -08:00
*
* - ProceduralRenderer3
*
* In non - native browser environments ( e . g . platforms such as web - workers ) , this is the facade
2017-12-14 16:26:28 -08:00
* that enables element manipulation . This also facilitates backwards compatibility with
2017-12-14 15:50:01 -08:00
* Renderer2 .
2017-12-01 14:23:03 -08:00
* /
let renderer : Renderer3 ;
2017-12-11 16:30:46 +01:00
let rendererFactory : RendererFactory3 ;
2017-12-01 14:23:03 -08:00
2018-02-16 21:06:23 -08:00
export function getRenderer ( ) : Renderer3 {
// top level variables should not be exported for performance reason (PERF_NOTES.md)
return renderer ;
}
2017-12-01 14:23:03 -08:00
/** Used to set the parent property when nodes are created. */
let previousOrParentNode : LNode ;
2018-02-16 21:06:23 -08:00
export function getPreviousOrParentNode ( ) : LNode {
// top level variables should not be exported for performance reason (PERF_NOTES.md)
return previousOrParentNode ;
}
2017-12-01 14:23:03 -08:00
/ * *
* If ` isParent ` is :
* - ` true ` : then ` previousOrParentNode ` points to a parent node .
* - ` false ` : then ` previousOrParentNode ` points to previous node ( sibling ) .
* /
let isParent : boolean ;
/ * *
2018-01-10 18:19:16 -08:00
* Static data that corresponds to the instance - specific data array on an LView .
2017-12-01 14:23:03 -08:00
*
2018-01-10 18:19:16 -08:00
* Each node 's static data is stored in tData at the same index that it' s stored
2018-03-21 15:10:34 -07:00
* in the data array . Any nodes that do not have static data store a null value in
* tData to avoid a sparse array .
2017-12-01 14:23:03 -08:00
* /
2018-01-10 18:19:16 -08:00
let tData : TData ;
2017-12-01 14:23:03 -08:00
2018-02-03 20:34:30 -08:00
/ * *
* State of the current view being processed .
*
* NOTE : we cheat here and initialize it to ` null ` even thought the type does not
* contain ` null ` . This is because we expect this value to be not ` null ` as soon
* as we enter the view . Declaring the type as ` null ` would require us to place ` ! `
* in most instructions since they all assume that ` currentView ` is defined .
* /
let currentView : LView = null ! ;
2017-12-01 14:23:03 -08:00
2018-01-29 14:51:37 +01:00
let currentQueries : LQueries | null ;
2017-12-01 14:23:03 -08:00
2018-02-16 21:06:23 -08:00
export function getCurrentQueries ( QueryType : { new ( ) : LQueries } ) : LQueries {
// top level variables should not be exported for performance reason (PERF_NOTES.md)
return currentQueries || ( currentQueries = new QueryType ( ) ) ;
}
2017-12-01 14:23:03 -08:00
/ * *
* This property gets set before entering a template .
* /
let creationMode : boolean ;
2018-02-16 21:06:23 -08:00
export function getCreationMode ( ) : boolean {
// top level variables should not be exported for performance reason (PERF_NOTES.md)
return creationMode ;
}
2017-12-01 14:23:03 -08:00
/ * *
2018-02-16 16:23:27 +01:00
* An array of nodes ( text , element , container , etc ) , pipes , their bindings , and
2017-12-08 11:48:54 -08:00
* any local variables that need to be stored between invocations .
2017-12-01 14:23:03 -08:00
* /
2017-12-08 11:48:54 -08:00
let data : any [ ] ;
2017-12-01 14:23:03 -08:00
2018-03-21 15:10:34 -07:00
/ * *
* An array of directive instances in the current view .
*
* These must be stored separately from LNodes because their presence is
* unknown at compile - time and thus space cannot be reserved in data [ ] .
* /
let directives : any [ ] | null ;
2017-12-01 14:23:03 -08:00
/ * *
* Points to the next binding index to read or write to .
* /
let bindingIndex : number ;
/ * *
2018-01-23 10:57:48 -08:00
* When a view is destroyed , listeners need to be released and outputs need to be
* unsubscribed . This cleanup array stores both listener data ( in chunks of 4 )
* and output data ( in chunks of 2 ) for a particular view . Combining the arrays
* saves on memory ( 70 bytes per array ) and on a few bytes of code size ( for two
* separate for loops ) .
2017-12-01 14:23:03 -08:00
*
* If it ' s a listener being stored :
* 1 st index is : event name to remove
* 2 nd index is : native element
* 3 rd index is : listener function
* 4 th index is : useCapture boolean
*
2018-01-23 10:57:48 -08:00
* If it ' s an output subscription :
* 1 st index is : unsubscribe function
2017-12-01 14:23:03 -08:00
* 2 nd index is : context for function
* /
let cleanup : any [ ] | null ;
2018-03-09 20:22:18 -08:00
/ * *
* In this mode , any changes in bindings will throw an ExpressionChangedAfterChecked error .
*
* Necessary to support ChangeDetectorRef . checkNoChanges ( ) .
* /
let checkNoChangesMode = false ;
2018-03-16 16:42:13 -07:00
/** Whether or not this is the first time the current view has been processed. */
let firstTemplatePass = true ;
2018-02-07 22:19:24 -08:00
const enum BindingDirection {
Input ,
Output ,
}
2017-12-01 14:23:03 -08:00
/ * *
* Swap the current state with a new state .
*
* For performance reasons we store the state in the top level of the module .
* This way we minimize the number of properties to read . Whenever a new view
* is entered we have to store the state for later , and when the view is
* exited the state has to be restored
*
2018-01-08 20:17:13 -08:00
* @param newView New state to become active
2017-12-01 14:23:03 -08:00
* @param host Element to which the View is a child of
* @returns the previous state ;
* /
2018-01-08 20:17:13 -08:00
export function enterView ( newView : LView , host : LElementNode | LViewNode | null ) : LView {
const oldView = currentView ;
2018-02-03 20:34:30 -08:00
data = newView && newView . data ;
2018-03-21 15:10:34 -07:00
directives = newView && newView . directives ;
2018-02-03 20:34:30 -08:00
bindingIndex = newView && newView . bindingStartIndex || 0 ;
tData = newView && newView . tView . data ;
2018-02-23 13:17:20 -08:00
creationMode = newView && ( newView . flags & LViewFlags . CreationMode ) === LViewFlags . CreationMode ;
2018-03-16 16:42:13 -07:00
firstTemplatePass = newView && newView . tView . firstTemplatePass ;
2017-12-01 14:23:03 -08:00
2018-02-03 20:34:30 -08:00
cleanup = newView && newView . cleanup ;
renderer = newView && newView . renderer ;
2017-12-01 14:23:03 -08:00
if ( host != null ) {
previousOrParentNode = host ;
isParent = true ;
}
2018-01-08 20:17:13 -08:00
currentView = newView ;
2018-02-03 20:34:30 -08:00
currentQueries = newView && newView . queries ;
2018-01-17 17:55:55 +01:00
2018-01-08 20:17:13 -08:00
return oldView ! ;
2017-12-01 14:23:03 -08:00
}
2017-12-14 15:50:01 -08:00
/ * *
* Used in lieu of enterView to make it clear when we are exiting a child view . This makes
* the direction of traversal ( up or down the view tree ) a bit clearer .
* /
2018-01-08 20:17:13 -08:00
export function leaveView ( newView : LView ) : void {
2018-03-09 20:22:18 -08:00
if ( ! checkNoChangesMode ) {
executeHooks (
2018-03-21 15:10:34 -07:00
directives ! , currentView . tView . viewHooks , currentView . tView . viewCheckHooks , creationMode ) ;
2018-03-09 20:22:18 -08:00
}
2018-02-23 13:17:20 -08:00
// Views should be clean and in update mode after being checked, so these bits are cleared
currentView . flags &= ~ ( LViewFlags . CreationMode | LViewFlags . Dirty ) ;
2018-01-23 18:39:09 -08:00
currentView . lifecycleStage = LifecycleStage . INIT ;
2018-01-08 20:17:13 -08:00
enterView ( newView , null ) ;
2017-12-22 16:41:34 -08:00
}
2017-12-01 14:23:03 -08:00
2018-03-16 16:42:13 -07:00
/** Refreshes directives in this view and triggers any init/content hooks. */
function refreshDirectives() {
2018-03-13 11:48:09 -07:00
executeInitAndContentHooks ( ) ;
2018-03-16 16:42:13 -07:00
const tView = currentView . tView ;
2018-03-13 11:48:09 -07:00
// This needs to be set before children are processed to support recursive components
2018-03-16 16:42:13 -07:00
tView . firstTemplatePass = firstTemplatePass = false ;
setHostBindings ( tView . hostBindings ) ;
refreshChildComponents ( tView . components ) ;
}
2018-03-13 11:48:09 -07:00
2018-03-16 16:42:13 -07:00
/** Sets the host bindings for the current view. */
2018-03-25 21:32:39 -07:00
export function setHostBindings ( bindings : number [ ] | null ) : void {
2018-03-16 16:42:13 -07:00
if ( bindings != null ) {
2018-03-21 15:10:34 -07:00
const defs = currentView . tView . directives ! ;
2018-03-16 16:42:13 -07:00
for ( let i = 0 ; i < bindings . length ; i += 2 ) {
const dirIndex = bindings [ i ] ;
2018-03-21 15:10:34 -07:00
const def = defs [ dirIndex ] as DirectiveDef < any > ;
def . hostBindings && def . hostBindings ( dirIndex , bindings [ i | 1 ] ) ;
2018-03-16 16:42:13 -07:00
}
}
}
/** Refreshes child components in the current view. */
function refreshChildComponents ( components : number [ ] | null ) : void {
2018-03-13 11:48:09 -07:00
if ( components != null ) {
2018-03-21 15:10:34 -07:00
for ( let i = 0 ; i < components . length ; i += 2 ) {
componentRefresh ( components [ i ] , components [ i | 1 ] ) ;
2018-03-13 11:48:09 -07:00
}
}
}
2018-03-25 21:32:39 -07:00
export function executeInitAndContentHooks ( ) : void {
2018-03-13 11:48:09 -07:00
if ( ! checkNoChangesMode ) {
const tView = currentView . tView ;
executeInitHooks ( currentView , tView , creationMode ) ;
2018-03-21 15:10:34 -07:00
executeHooks ( directives ! , tView . contentHooks , tView . contentCheckHooks , creationMode ) ;
2018-03-13 11:48:09 -07:00
}
}
2018-01-17 10:09:05 -08:00
export function createLView (
2018-02-03 20:34:30 -08:00
viewId : number , renderer : Renderer3 , tView : TView , template : ComponentTemplate < any > | null ,
2018-02-23 13:17:20 -08:00
context : any | null , flags : LViewFlags ) : LView {
2017-12-01 14:23:03 -08:00
const newView = {
parent : currentView ,
2018-02-20 13:31:31 -08:00
id : viewId , // -1 for component views
2018-03-08 16:55:47 -08:00
flags : flags | LViewFlags . CreationMode | LViewFlags . Attached ,
2017-12-08 11:48:54 -08:00
node : null ! , // until we initialize it in createNode.
2017-12-22 16:41:34 -08:00
data : [ ] ,
2018-03-21 15:10:34 -07:00
directives : null ,
2018-01-10 18:19:16 -08:00
tView : tView ,
2017-12-01 14:23:03 -08:00
cleanup : null ,
renderer : renderer ,
child : null ,
tail : null ,
next : null ,
2017-12-22 16:41:34 -08:00
bindingStartIndex : null ,
2018-01-17 10:09:05 -08:00
template : template ,
context : context ,
dynamicViewCount : 0 ,
2018-01-17 17:55:55 +01:00
lifecycleStage : LifecycleStage.INIT ,
2018-01-29 14:51:37 +01:00
queries : null ,
2017-12-01 14:23:03 -08:00
} ;
return newView ;
}
/ * *
* A common way of creating the LNode to make sure that all of them have same shape to
* keep the execution code monomorphic and fast .
* /
2017-12-08 11:48:54 -08:00
export function createLNode (
2018-03-20 19:06:49 -07:00
index : number | null , type : LNodeType . Element , native : RElement | RText | null ,
2018-01-08 20:17:13 -08:00
lView? : LView | null ) : LElementNode ;
2017-12-08 11:48:54 -08:00
export function createLNode (
2018-03-20 19:06:49 -07:00
index : null , type : LNodeType . View , native : null , lView : LView ) : LViewNode ;
2017-12-08 11:48:54 -08:00
export function createLNode (
2018-03-20 19:06:49 -07:00
index : number , type : LNodeType . Container , native : undefined ,
2018-01-08 20:17:13 -08:00
lContainer : LContainer ) : LContainerNode ;
2017-12-08 11:48:54 -08:00
export function createLNode (
2018-03-20 19:06:49 -07:00
index : number , type : LNodeType . Projection , native : null ,
2018-01-08 20:17:13 -08:00
lProjection : LProjection ) : LProjectionNode ;
2017-12-08 11:48:54 -08:00
export function createLNode (
2018-03-20 19:06:49 -07:00
index : number | null , type : LNodeType , native : RText | RElement | null | undefined ,
2018-01-26 11:36:31 +01:00
state? : null | LView | LContainer | LProjection ) : LElementNode & LTextNode & LViewNode &
LContainerNode & LProjectionNode {
2017-12-01 14:23:03 -08:00
const parent = isParent ? previousOrParentNode :
previousOrParentNode && previousOrParentNode . parent as LNode ;
2018-01-29 14:51:37 +01:00
let queries =
( isParent ? currentQueries : previousOrParentNode && previousOrParentNode . queries ) ||
parent && parent . queries && parent . queries . child ( ) ;
2017-12-01 14:23:03 -08:00
const isState = state != null ;
2018-01-08 20:17:13 -08:00
const node : LElementNode & LTextNode & LViewNode & LContainerNode & LProjectionNode = {
2018-03-20 19:06:49 -07:00
type : type ,
2017-12-01 14:23:03 -08:00
native : native as any ,
view : currentView ,
parent : parent as any ,
child : null ,
next : null ,
nodeInjector : parent ? parent.nodeInjector : null ,
data : isState ? state as any : null ,
2018-01-29 14:51:37 +01:00
queries : queries ,
2018-01-25 15:32:21 +01:00
tNode : null ,
pNextOrParent : null
2017-12-01 14:23:03 -08:00
} ;
2018-03-20 19:06:49 -07:00
if ( ( type & LNodeType . ViewOrElement ) === LNodeType . ViewOrElement && isState ) {
2017-12-01 14:23:03 -08:00
// Bit of a hack to bust through the readonly because there is a circular dep between
2018-01-08 20:17:13 -08:00
// LView and LNode.
2018-02-12 22:46:15 -08:00
ngDevMode && assertNull ( ( state as LView ) . node , 'LView.node should not have been initialized' ) ;
2018-01-08 20:17:13 -08:00
( state as LView as { node : LNode } ) . node = node ;
2017-12-01 14:23:03 -08:00
}
if ( index != null ) {
// We are Element or Container
2018-01-18 13:27:01 -08:00
ngDevMode && assertDataNext ( index ) ;
2017-12-08 11:48:54 -08:00
data [ index ] = node ;
// Every node adds a value to the static data array to avoid a sparse array
2018-01-10 18:19:16 -08:00
if ( index >= tData . length ) {
tData [ index ] = null ;
2017-12-11 14:08:52 -08:00
} else {
2018-01-10 18:19:16 -08:00
node . tNode = tData [ index ] as TNode ;
2017-12-01 14:23:03 -08:00
}
// Now link ourselves into the tree.
if ( isParent ) {
2018-01-29 14:51:37 +01:00
currentQueries = null ;
2017-12-08 11:48:54 -08:00
if ( previousOrParentNode . view === currentView ||
2018-03-20 19:06:49 -07:00
previousOrParentNode . type === LNodeType . View ) {
2017-12-01 14:23:03 -08:00
// We are in the same view, which means we are adding content node to the parent View.
2018-02-12 22:46:15 -08:00
ngDevMode && assertNull (
previousOrParentNode . child ,
` previousOrParentNode's child should not have been set. ` ) ;
2017-12-01 14:23:03 -08:00
previousOrParentNode . child = node ;
} else {
// We are adding component view, so we don't link parent node child to this node.
}
} else if ( previousOrParentNode ) {
2018-02-12 22:46:15 -08:00
ngDevMode && assertNull (
previousOrParentNode . next ,
` previousOrParentNode's next property should not have been set. ` ) ;
2017-12-01 14:23:03 -08:00
previousOrParentNode . next = node ;
}
}
previousOrParentNode = node ;
isParent = true ;
return node ;
}
//////////////////////////
//// Render
//////////////////////////
2018-01-03 10:45:09 +01:00
/ * *
* Resets the application state .
* /
function resetApplicationState() {
isParent = false ;
previousOrParentNode = null ! ;
}
2017-12-01 14:23:03 -08:00
/ * *
*
2018-03-25 21:32:39 -07:00
* @param hostNode Existing node to render into .
2017-12-01 14:23:03 -08:00
* @param template Template function with the instructions .
* @param context to pass into the template .
2018-03-25 21:32:39 -07:00
* @param providedRendererFactory renderer factory to use
* @param host The host element node to use
2018-03-27 15:53:48 -07:00
* @param defs Any directive or pipe defs that should be used for matching
2017-12-01 14:23:03 -08:00
* /
2017-12-11 16:30:46 +01:00
export function renderTemplate < T > (
hostNode : RElement , template : ComponentTemplate < T > , context : T ,
2018-03-25 21:32:39 -07:00
providedRendererFactory : RendererFactory3 , host : LElementNode | null ,
2018-03-27 15:53:48 -07:00
directives? : DirectiveDefListOrFactory | null ,
pipes? : PipeDefListOrFactory | null ) : LElementNode {
2017-12-11 16:30:46 +01:00
if ( host == null ) {
2018-01-03 10:45:09 +01:00
resetApplicationState ( ) ;
2017-12-11 16:30:46 +01:00
rendererFactory = providedRendererFactory ;
2018-03-27 15:53:48 -07:00
const tView = getOrCreateTView ( template , directives || null , pipes || null ) ;
2017-12-11 16:30:46 +01:00
host = createLNode (
2018-03-20 19:06:49 -07:00
null , LNodeType . Element , hostNode ,
2018-01-10 18:19:16 -08:00
createLView (
2018-03-27 15:53:48 -07:00
- 1 , providedRendererFactory . createRenderer ( null , null ) , tView , null , { } ,
LViewFlags . CheckAlways ) ) ;
2017-12-11 16:30:46 +01:00
}
2017-12-01 14:23:03 -08:00
const hostView = host . data ! ;
2018-02-12 22:46:15 -08:00
ngDevMode && assertNotNull ( hostView , 'Host node should have an LView defined in host.data.' ) ;
2017-12-11 16:30:46 +01:00
renderComponentOrTemplate ( host , hostView , context , template ) ;
return host ;
}
2018-01-17 10:09:05 -08:00
export function renderEmbeddedTemplate < T > (
viewNode : LViewNode | null , template : ComponentTemplate < T > , context : T ,
renderer : Renderer3 ) : LViewNode {
const _isParent = isParent ;
const _previousOrParentNode = previousOrParentNode ;
try {
isParent = true ;
previousOrParentNode = null ! ;
let cm : boolean = false ;
if ( viewNode == null ) {
2018-03-26 23:41:38 -07:00
// TODO: revisit setting currentView when re-writing view containers
2018-03-27 15:53:48 -07:00
const directives = currentView && currentView . tView . directiveRegistry ;
const pipes = currentView && currentView . tView . pipeRegistry ;
2018-03-25 21:32:39 -07:00
const view = createLView (
2018-03-27 15:53:48 -07:00
- 1 , renderer , createTView ( directives , pipes ) , template , context , LViewFlags . CheckAlways ) ;
2018-03-20 19:06:49 -07:00
viewNode = createLNode ( null , LNodeType . View , null , view ) ;
2018-01-17 10:09:05 -08:00
cm = true ;
}
enterView ( viewNode . data , viewNode ) ;
template ( context , cm ) ;
refreshDynamicChildren ( ) ;
2018-03-16 16:42:13 -07:00
refreshDirectives ( ) ;
2018-03-13 11:48:09 -07:00
} finally {
2018-03-26 23:41:38 -07:00
leaveView ( currentView && currentView ! . parent ! ) ;
2018-01-17 10:09:05 -08:00
isParent = _isParent ;
previousOrParentNode = _previousOrParentNode ;
}
return viewNode ;
}
2017-12-11 16:30:46 +01:00
export function renderComponentOrTemplate < T > (
2018-01-10 18:19:16 -08:00
node : LElementNode , hostView : LView , componentOrContext : T , template? : ComponentTemplate < T > ) {
const oldView = enterView ( hostView , node ) ;
2017-12-01 14:23:03 -08:00
try {
2017-12-11 16:30:46 +01:00
if ( rendererFactory . begin ) {
rendererFactory . begin ( ) ;
}
if ( template ) {
template ( componentOrContext ! , creationMode ) ;
2018-03-16 16:42:13 -07:00
refreshDirectives ( ) ;
2017-12-11 16:30:46 +01:00
} else {
2018-03-13 11:48:09 -07:00
executeInitAndContentHooks ( ) ;
2018-03-16 16:42:13 -07:00
2018-03-21 15:10:34 -07:00
// Element was stored at 0 in data and directive was stored at 0 in directives
// in renderComponent()
2018-03-25 21:32:39 -07:00
setHostBindings ( _ROOT_DIRECTIVE_INDICES ) ;
2018-03-21 15:10:34 -07:00
componentRefresh ( 0 , 0 ) ;
2017-12-11 16:30:46 +01:00
}
2017-12-01 14:23:03 -08:00
} finally {
2017-12-11 16:30:46 +01:00
if ( rendererFactory . end ) {
rendererFactory . end ( ) ;
}
2017-12-01 14:23:03 -08:00
leaveView ( oldView ) ;
}
}
//////////////////////////
2017-12-14 15:50:01 -08:00
//// Element
2017-12-01 14:23:03 -08:00
//////////////////////////
/ * *
* Create DOM element . The instruction must later be followed by ` elementEnd() ` call .
*
2017-12-08 11:48:54 -08:00
* @param index Index of the element in the data array
2018-03-25 21:32:39 -07:00
* @param name Name of the DOM Node
2017-12-01 14:23:03 -08:00
* @param attrs Statically bound set of attributes to be written into the DOM element on creation .
2018-01-08 21:57:50 -08:00
* @param localRefs A set of local reference bindings on the element .
2017-12-01 14:23:03 -08:00
*
2018-01-08 21:57:50 -08:00
* Attributes and localRefs 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 . :
2017-12-01 14:23:03 -08:00
* [ 'id' , 'warning5' , 'class' , 'alert' ]
* /
2017-12-14 16:26:28 -08:00
export function elementStart (
2018-03-27 11:01:52 -07:00
index : number , name : string , attrs? : string [ ] | null , localRefs? : string [ ] | null ) : RElement {
2018-01-08 20:17:13 -08:00
let node : LElementNode ;
2017-12-01 14:23:03 -08:00
let native : RElement ;
2018-03-27 11:01:52 -07:00
ngDevMode &&
assertNull ( currentView . bindingStartIndex , 'elements should be created before any bindings' ) ;
2017-12-01 14:23:03 -08:00
2018-03-27 11:01:52 -07:00
native = renderer . createElement ( name ) ;
node = createLNode ( index , LNodeType . Element , native ! , null ) ;
2018-03-21 15:10:34 -07:00
2018-03-27 11:01:52 -07:00
if ( attrs ) setUpAttributes ( native , attrs ) ;
appendChild ( node . parent ! , native , currentView ) ;
2018-03-21 15:10:34 -07:00
2018-03-27 11:01:52 -07:00
if ( firstTemplatePass ) {
const tNode = createTNode ( name , attrs || null , null ) ;
cacheMatchingDirectivesForNode ( tNode ) ;
2018-03-14 12:17:47 -07:00
2018-03-27 11:01:52 -07:00
ngDevMode && assertDataInRange ( index - 1 ) ;
node . tNode = tData [ index ] = tNode ;
2018-03-25 21:32:39 -07:00
}
2018-03-27 11:01:52 -07:00
hack_declareDirectives ( index , localRefs || null ) ;
2018-03-25 21:32:39 -07:00
return native ;
}
2017-12-11 14:08:52 -08:00
2018-03-25 21:32:39 -07:00
function cacheMatchingDirectivesForNode ( tNode : TNode ) : void {
const registry = currentView . tView . directiveRegistry ;
const startIndex = directives ? directives.length : 0 ;
2017-12-01 14:23:03 -08:00
2018-03-25 21:32:39 -07:00
if ( registry ) {
let componentFlag = 0 ;
let size = 0 ;
2017-12-01 14:23:03 -08:00
2018-03-25 21:32:39 -07:00
for ( let i = 0 ; i < registry . length ; i ++ ) {
const def = registry [ i ] ;
2018-03-29 16:41:45 -07:00
if ( isNodeMatchingSelectorList ( tNode , def . selectors ! ) ) {
2018-03-25 21:32:39 -07:00
if ( ( def as ComponentDef < any > ) . template ) {
if ( componentFlag ) throwMultipleComponentError ( tNode ) ;
componentFlag |= TNodeFlags . Component ;
}
( currentView . tView . directives || ( currentView . tView . directives = [ ] ) ) . push ( def ) ;
size ++ ;
2018-01-08 21:57:50 -08:00
}
2017-12-01 14:23:03 -08:00
}
2018-03-25 21:32:39 -07:00
if ( size > 0 ) buildTNodeFlags ( tNode , startIndex , size , componentFlag ) ;
2017-12-01 14:23:03 -08:00
}
2018-03-25 21:32:39 -07:00
}
function buildTNodeFlags ( tNode : TNode , index : number , size : number , component : number ) : void {
tNode . flags = ( index << TNodeFlags . INDX_SHIFT ) | ( size << TNodeFlags . SIZE_SHIFT ) | component ;
}
function throwMultipleComponentError ( tNode : TNode ) : never {
throw new Error ( ` Multiple components match node with tagname ${ tNode . tagName } ` ) ;
2017-12-01 14:23:03 -08:00
}
2018-03-16 16:42:13 -07:00
/** Stores index of component's host element so it will be queued for view refresh during CD. */
2018-03-21 15:10:34 -07:00
function queueComponentIndexForCheck ( dirIndex : number , elIndex : number ) : void {
2018-03-16 16:42:13 -07:00
if ( firstTemplatePass ) {
2018-03-21 15:10:34 -07:00
( currentView . tView . components || ( currentView . tView . components = [ ] ) ) . push ( dirIndex , elIndex ) ;
2018-03-16 16:42:13 -07:00
}
}
/ * * S t o r e s i n d e x o f d i r e c t i v e a n d h o s t e l e m e n t s o i t w i l l b e q u e u e d f o r b i n d i n g r e f r e s h d u r i n g C D .
* /
function queueHostBindingForCheck ( dirIndex : number , elIndex : number ) : void {
2018-03-21 15:10:34 -07:00
ngDevMode &&
assertEqual ( firstTemplatePass , true , 'Should only be called in first template pass.' ) ;
( currentView . tView . hostBindings || ( currentView . tView . hostBindings = [ ] ) ) . push ( dirIndex , elIndex ) ;
2018-03-13 11:48:09 -07:00
}
2018-02-26 16:58:15 -08:00
/** Sets the context for a ChangeDetectorRef to the given instance. */
2018-03-25 21:32:39 -07:00
export function initChangeDetectorIfExisting (
injector : LInjector | null , instance : any , view : LView ) : void {
2018-02-26 16:58:15 -08:00
if ( injector && injector . changeDetectorRef != null ) {
2018-03-25 21:32:39 -07:00
( injector . changeDetectorRef as ViewRef < any > ) . _setComponentContext ( view , instance ) ;
2018-02-26 16:58:15 -08:00
}
}
2018-03-25 21:32:39 -07:00
export function isComponent ( tNode : TNode ) : boolean {
return ( tNode . flags & TNodeFlags . Component ) === TNodeFlags . Component ;
}
2018-01-08 21:57:50 -08:00
/ * *
2018-03-15 12:18:31 -07:00
* This function instantiates the given directives . It is a hack since it assumes the directives
* come in the correct order for DI .
2018-01-08 21:57:50 -08:00
* /
2018-03-27 11:01:52 -07:00
function hack_declareDirectives ( elementIndex : number , localRefs : string [ ] | null ) {
const tNode = previousOrParentNode . tNode ! ;
const size = ( tNode . flags & TNodeFlags . SIZE_MASK ) >> TNodeFlags . SIZE_SHIFT ;
const exportsMap : { [ key : string ] : number } | null = firstTemplatePass && localRefs ? { '' : - 1 } : null ;
2018-03-25 21:32:39 -07:00
if ( size > 0 ) {
2018-03-27 11:01:52 -07:00
let startIndex = tNode . flags >> TNodeFlags . INDX_SHIFT ;
2018-03-25 21:32:39 -07:00
const endIndex = startIndex + size ;
const tDirectives = currentView . tView . directives ! ;
2018-03-21 15:10:34 -07:00
2018-01-08 21:57:50 -08:00
// TODO(mhevery): This assumes that the directives come in correct order, which
// is not guaranteed. Must be refactored to take it into account.
2018-03-25 21:32:39 -07:00
for ( let i = startIndex ; i < endIndex ; i ++ ) {
const def = tDirectives [ i ] as DirectiveDef < any > ;
2018-03-27 11:01:52 -07:00
directiveCreate ( elementIndex , def . factory ( ) , def ) ;
saveNameToExportMap ( startIndex , def , exportsMap ) ;
2018-03-25 21:32:39 -07:00
startIndex ++ ;
2018-01-08 21:57:50 -08:00
}
}
2018-03-27 11:01:52 -07:00
if ( firstTemplatePass ) cacheMatchingLocalNames ( tNode , localRefs , exportsMap ! ) ;
saveResolvedLocalsInData ( ) ;
}
/** Caches local names and their matching directive indices for query and template lookups. */
function cacheMatchingLocalNames (
tNode : TNode , localRefs : string [ ] | null , exportsMap : { [ key : string ] : number } ) : void {
if ( localRefs ) {
const localNames : ( string | number ) [ ] = tNode . localNames = [ ] ;
// Local names must be stored in tNode in the same order that localRefs are defined
// in the template to ensure the data is loaded in the same slots as their refs
// in the template (for template queries).
for ( let i = 0 ; i < localRefs . length ; i += 2 ) {
const index = exportsMap [ localRefs [ i | 1 ] ] ;
if ( index == null ) throw new Error ( ` Export of name ' ${ localRefs [ i | 1 ] } ' not found! ` ) ;
localNames . push ( localRefs [ i ] , index ) ;
}
}
2018-01-08 21:57:50 -08:00
}
/ * *
2018-03-27 11:01:52 -07:00
* Builds up an export map as directives are created , so local refs can be quickly mapped
* to their directive instances .
2018-01-08 21:57:50 -08:00
* /
2018-03-27 11:01:52 -07:00
function saveNameToExportMap (
index : number , def : DirectiveDef < any > | ComponentDef < any > ,
exportsMap : { [ key : string ] : number } | null ) {
if ( exportsMap ) {
if ( def . exportAs ) exportsMap [ def . exportAs ] = index ;
if ( ( def as ComponentDef < any > ) . template ) exportsMap [ '' ] = index ;
}
}
/ * *
* Takes a list of local names and indices and pushes the resolved local variable values
* to data [ ] in the same order as they are loaded in the template with load ( ) .
* /
function saveResolvedLocalsInData ( ) : void {
const localNames = previousOrParentNode . tNode ! . localNames ;
if ( localNames ) {
for ( let i = 0 ; i < localNames . length ; i += 2 ) {
const index = localNames [ i | 1 ] as number ;
const value = index === - 1 ? previousOrParentNode.native : directives ! [ index ] ;
data . push ( value ) ;
2018-01-08 21:57:50 -08:00
}
}
}
2017-12-11 14:08:52 -08:00
/ * *
2018-01-10 18:19:16 -08:00
* Gets TView from a template function or creates a new TView
* if it doesn ' t already exist .
2017-12-11 14:08:52 -08:00
*
* @param template The template from which to get static data
2018-03-27 15:53:48 -07:00
* @param directives Directive defs that should be saved on TView
* @param pipes Pipe defs that should be saved on TView
2018-01-10 18:19:16 -08:00
* @returns TView
2017-12-11 14:08:52 -08:00
* /
2018-03-25 21:32:39 -07:00
function getOrCreateTView (
2018-03-27 15:53:48 -07:00
template : ComponentTemplate < any > , directives : DirectiveDefListOrFactory | null ,
pipes : PipeDefListOrFactory | null ) : TView {
return template . ngPrivateData ||
( template . ngPrivateData = createTView ( directives , pipes ) as never ) ;
2018-01-22 17:43:52 -08:00
}
2018-01-23 10:57:48 -08:00
/** Creates a TView instance */
2018-03-27 15:53:48 -07:00
export function createTView (
defs : DirectiveDefListOrFactory | null , pipes : PipeDefListOrFactory | null ) : TView {
2018-01-23 10:57:48 -08:00
return {
data : [ ] ,
2018-03-21 15:10:34 -07:00
directives : null ,
2018-01-23 10:57:48 -08:00
firstTemplatePass : true ,
initHooks : null ,
2018-01-25 20:41:57 -08:00
checkHooks : null ,
2018-01-23 10:57:48 -08:00
contentHooks : null ,
2018-01-25 20:41:57 -08:00
contentCheckHooks : null ,
2018-01-23 10:57:48 -08:00
viewHooks : null ,
2018-01-25 20:41:57 -08:00
viewCheckHooks : null ,
2018-03-13 11:48:09 -07:00
destroyHooks : null ,
2018-03-21 15:10:34 -07:00
pipeDestroyHooks : null ,
2018-03-16 16:42:13 -07:00
hostBindings : null ,
2018-03-25 21:32:39 -07:00
components : null ,
2018-03-27 15:53:48 -07:00
directiveRegistry : typeof defs === 'function' ? defs ( ) : defs ,
pipeRegistry : typeof pipes === 'function' ? pipes ( ) : pipes
2018-01-23 10:57:48 -08:00
} ;
2017-12-11 14:08:52 -08:00
}
2017-12-01 14:23:03 -08:00
function setUpAttributes ( native : RElement , attrs : string [ ] ) : void {
2018-02-12 22:46:15 -08:00
ngDevMode && assertEqual ( attrs . length % 2 , 0 , 'each attribute should have a key and a value' ) ;
2018-02-07 22:57:11 -08:00
const isProc = isProceduralRenderer ( renderer ) ;
2017-12-01 14:23:03 -08:00
for ( let i = 0 ; i < attrs . length ; i += 2 ) {
2018-02-28 15:00:58 +01:00
const attrName = attrs [ i ] ;
if ( attrName !== NG_PROJECT_AS_ATTR_NAME ) {
const attrVal = attrs [ i + 1 ] ;
isProc ? ( renderer as ProceduralRenderer3 ) . setAttribute ( native , attrName , attrVal ) :
native . setAttribute ( attrName , attrVal ) ;
}
2017-12-01 14:23:03 -08:00
}
}
export function createError ( text : string , token : any ) {
return new Error ( ` Renderer: ${ text } [ ${ stringify ( token ) } ] ` ) ;
}
/ * *
2017-12-11 16:30:46 +01:00
* Locates the host native element , used for bootstrapping existing nodes into rendering pipeline .
2017-12-01 14:23:03 -08:00
*
* @param elementOrSelector Render element or CSS selector to locate the element .
* /
2017-12-11 16:30:46 +01:00
export function locateHostElement (
factory : RendererFactory3 , elementOrSelector : RElement | string ) : RElement | null {
2017-12-08 11:48:54 -08:00
ngDevMode && assertDataInRange ( - 1 ) ;
2017-12-11 16:30:46 +01:00
rendererFactory = factory ;
const defaultRenderer = factory . createRenderer ( null , null ) ;
2017-12-01 14:23:03 -08:00
const rNode = typeof elementOrSelector === 'string' ?
2018-02-07 22:57:11 -08:00
( isProceduralRenderer ( defaultRenderer ) ?
defaultRenderer . selectRootElement ( elementOrSelector ) :
defaultRenderer . querySelector ( elementOrSelector ) ) :
2017-12-01 14:23:03 -08:00
elementOrSelector ;
if ( ngDevMode && ! rNode ) {
if ( typeof elementOrSelector === 'string' ) {
throw createError ( 'Host node with selector not found:' , elementOrSelector ) ;
} else {
throw createError ( 'Host node is required:' , elementOrSelector ) ;
}
}
2017-12-11 16:30:46 +01:00
return rNode ;
}
/ * *
2018-01-10 18:19:16 -08:00
* Creates the host LNode .
2017-12-11 16:30:46 +01:00
*
* @param rNode Render host element .
2018-01-10 18:19:16 -08:00
* @param def ComponentDef
2018-02-26 16:58:15 -08:00
*
* @returns LElementNode created
2017-12-11 16:30:46 +01:00
* /
2018-03-25 21:32:39 -07:00
export function hostElement (
tag : string , rNode : RElement | null , def : ComponentDef < any > ) : LElementNode {
2018-01-03 10:45:09 +01:00
resetApplicationState ( ) ;
2018-03-20 19:06:49 -07:00
const node = createLNode (
2018-03-25 21:32:39 -07:00
0 , LNodeType . Element , rNode ,
createLView (
2018-03-27 15:53:48 -07:00
- 1 , renderer , getOrCreateTView ( def . template , def . directiveDefs , def . pipeDefs ) , null , null ,
2018-03-25 21:32:39 -07:00
def . onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways ) ) ;
if ( firstTemplatePass ) {
2018-03-27 11:01:52 -07:00
node . tNode = createTNode ( tag as string , null , null ) ;
2018-03-25 21:32:39 -07:00
// Root directive is stored at index 0, size 1
buildTNodeFlags ( node . tNode , 0 , 1 , TNodeFlags . Component ) ;
currentView . tView . directives = [ def ] ;
}
2018-03-20 19:06:49 -07:00
return node ;
2017-12-01 14:23:03 -08:00
}
/ * *
* Adds an event listener to the current node .
*
* If an output exists on one of the node ' s directives , it also subscribes to the output
* and saves the subscription for later cleanup .
*
* @param eventName Name of the event
2018-03-01 09:46:39 -08:00
* @param listenerFn The function to be called when event emits
2017-12-01 14:23:03 -08:00
* @param useCapture Whether or not to use capture in event listener .
* /
2018-03-01 09:46:39 -08:00
export function listener (
eventName : string , listenerFn : ( e? : any ) = > any , useCapture = false ) : void {
2017-12-01 14:23:03 -08:00
ngDevMode && assertPreviousIsParent ( ) ;
const node = previousOrParentNode ;
const native = node . native as RElement ;
2017-12-14 15:50:01 -08:00
// In order to match current behavior, native DOM event listeners must be added for all
// events (including outputs).
2018-02-23 13:17:20 -08:00
const cleanupFns = cleanup || ( cleanup = currentView . cleanup = [ ] ) ;
2018-02-06 20:08:46 -08:00
if ( isProceduralRenderer ( renderer ) ) {
2018-03-01 09:46:39 -08:00
const wrappedListener = wrapListenerWithDirtyLogic ( currentView , listenerFn ) ;
2018-02-23 13:17:20 -08:00
const cleanupFn = renderer . listen ( native , eventName , wrappedListener ) ;
cleanupFns . push ( cleanupFn , null ) ;
2017-12-01 14:23:03 -08:00
} else {
2018-03-01 09:46:39 -08:00
const wrappedListener = wrapListenerWithDirtyAndDefault ( currentView , listenerFn ) ;
2018-02-23 13:17:20 -08:00
native . addEventListener ( eventName , wrappedListener , useCapture ) ;
cleanupFns . push ( eventName , native , wrappedListener , useCapture ) ;
2017-12-01 14:23:03 -08:00
}
2018-01-08 20:17:13 -08:00
let tNode : TNode | null = node . tNode ! ;
if ( tNode . outputs === undefined ) {
// if we create TNode here, inputs must be undefined so we know they still need to be
2017-12-01 14:23:03 -08:00
// checked
2018-03-20 19:06:49 -07:00
tNode . outputs = generatePropertyAliases ( node . tNode ! . flags , BindingDirection . Output ) ;
2017-12-01 14:23:03 -08:00
}
2018-01-08 20:17:13 -08:00
const outputs = tNode . outputs ;
2018-02-07 22:19:24 -08:00
let outputData : PropertyAliasValue | undefined ;
2017-12-01 14:23:03 -08:00
if ( outputs && ( outputData = outputs [ eventName ] ) ) {
2018-03-01 09:46:39 -08:00
createOutput ( outputData , listenerFn ) ;
2017-12-01 14:23:03 -08:00
}
}
/ * *
* Iterates through the outputs associated with a particular event name and subscribes to
* each output .
* /
2018-02-07 22:19:24 -08:00
function createOutput ( outputs : PropertyAliasValue , listener : Function ) : void {
2017-12-01 14:23:03 -08:00
for ( let i = 0 ; i < outputs . length ; i += 2 ) {
2018-03-21 15:10:34 -07:00
ngDevMode && assertDataInRange ( outputs [ i ] as number , directives ! ) ;
const subscription = directives ! [ outputs [ i ] as number ] [ outputs [ i | 1 ] ] . subscribe ( listener ) ;
2017-12-01 14:23:03 -08:00
cleanup ! . push ( subscription . unsubscribe , subscription ) ;
}
}
2017-12-14 16:26:28 -08:00
/** Mark the end of the element. */
2017-12-01 14:23:03 -08:00
export function elementEnd() {
if ( isParent ) {
isParent = false ;
} else {
ngDevMode && assertHasParent ( ) ;
previousOrParentNode = previousOrParentNode . parent ! ;
}
2018-03-20 19:06:49 -07:00
ngDevMode && assertNodeType ( previousOrParentNode , LNodeType . Element ) ;
2018-01-29 14:51:37 +01:00
const queries = previousOrParentNode . queries ;
queries && queries . addNode ( previousOrParentNode ) ;
2018-03-20 19:06:49 -07:00
queueLifecycleHooks ( previousOrParentNode . tNode ! . flags , currentView ) ;
2017-12-01 14:23:03 -08:00
}
/ * *
2018-02-16 17:27:19 -08:00
* Updates the value of removes an attribute on an Element .
2017-12-01 14:23:03 -08:00
*
2018-02-16 17:27:19 -08:00
* @param number index The index of the element in the data array
2018-03-01 17:14:01 -08:00
* @param name name The name of the attribute .
* @param value value The attribute is removed when value is ` null ` or ` undefined ` .
2018-02-16 17:27:19 -08:00
* Otherwise the attribute value is set to the stringified value .
2018-03-01 17:14:01 -08:00
* @param sanitizer An optional function used to sanitize the value .
2017-12-01 14:23:03 -08:00
* /
2018-03-01 17:14:01 -08:00
export function elementAttribute (
index : number , name : string , value : any , sanitizer? : Sanitizer ) : void {
2017-12-01 14:23:03 -08:00
if ( value !== NO_CHANGE ) {
2018-02-16 17:27:19 -08:00
const element : LElementNode = data [ index ] ;
2017-12-01 14:23:03 -08:00
if ( value == null ) {
2018-02-16 17:27:19 -08:00
isProceduralRenderer ( renderer ) ? renderer . removeAttribute ( element . native , name ) :
element . native . removeAttribute ( name ) ;
2017-12-01 14:23:03 -08:00
} else {
2018-03-01 17:14:01 -08:00
const strValue = sanitizer == null ? stringify ( value ) : sanitizer ( value ) ;
isProceduralRenderer ( renderer ) ? renderer . setAttribute ( element . native , name , strValue ) :
element . native . setAttribute ( name , strValue ) ;
2017-12-01 14:23:03 -08:00
}
}
}
/ * *
* Update a property on an Element .
*
* If the property name also exists as an input property on one of the element ' s directives ,
2017-12-14 16:26:28 -08:00
* the component property will be set instead of the element property . This check must
* be conducted at runtime so child components that add new @Inputs don ' t have to be re - compiled .
2017-12-01 14:23:03 -08:00
*
2017-12-08 11:48:54 -08:00
* @param index The index of the element to update in the data array
2017-12-01 14:23:03 -08:00
* @param propName Name of property . Because it is going to DOM , this is not subject to
* renaming as part of minification .
* @param value New value to write .
2018-03-01 17:14:01 -08:00
* @param sanitizer An optional function used to sanitize the value .
2017-12-01 14:23:03 -08:00
* /
2018-03-01 17:14:01 -08:00
export function elementProperty < T > (
index : number , propName : string , value : T | NO_CHANGE , sanitizer? : Sanitizer ) : void {
2017-12-01 14:23:03 -08:00
if ( value === NO_CHANGE ) return ;
2018-01-08 20:17:13 -08:00
const node = data [ index ] as LElementNode ;
2018-02-07 22:19:24 -08:00
const tNode = node . tNode ! ;
2018-01-08 20:17:13 -08:00
// if tNode.inputs is undefined, a listener has created outputs, but inputs haven't
2017-12-11 14:08:52 -08:00
// yet been checked
2018-03-16 16:42:13 -07:00
if ( tNode && tNode . inputs === undefined ) {
2017-12-01 14:23:03 -08:00
// mark inputs as checked
2018-03-20 19:06:49 -07:00
tNode . inputs = generatePropertyAliases ( node . tNode ! . flags , BindingDirection . Input ) ;
2017-12-01 14:23:03 -08:00
}
2018-03-16 16:42:13 -07:00
const inputData = tNode && tNode . inputs ;
2018-02-07 22:19:24 -08:00
let dataValue : PropertyAliasValue | undefined ;
2017-12-01 14:23:03 -08:00
if ( inputData && ( dataValue = inputData [ propName ] ) ) {
setInputsForProperty ( dataValue , value ) ;
2018-02-23 13:17:20 -08:00
markDirtyIfOnPush ( node ) ;
2017-12-01 14:23:03 -08:00
} else {
2018-03-09 18:32:32 +01:00
// It is assumed that the sanitizer is only added when the compiler determines that the property
// is risky, so sanitization can be done without further checks.
value = sanitizer != null ? ( sanitizer ( value ) as any ) : value ;
2017-12-01 14:23:03 -08:00
const native = node . native ;
2018-02-07 22:57:11 -08:00
isProceduralRenderer ( renderer ) ? renderer . setProperty ( native , propName , value ) :
( native . setProperty ? native . setProperty ( propName , value ) :
( native as any ) [ propName ] = value ) ;
2017-12-01 14:23:03 -08:00
}
}
2017-12-14 15:50:01 -08:00
/ * *
2018-01-08 20:17:13 -08:00
* Constructs a TNode object from the arguments .
2017-12-14 15:50:01 -08:00
*
* @param tagName
* @param attrs
2018-01-10 18:19:16 -08:00
* @param data
2018-03-15 12:18:31 -07:00
* @param localNames A list of local names and their matching indices
2018-01-08 20:17:13 -08:00
* @returns the TNode object
2017-12-14 15:50:01 -08:00
* /
2018-01-08 20:17:13 -08:00
function createTNode (
2018-03-27 11:01:52 -07:00
tagName : string | null , attrs : string [ ] | null , data : TContainer | null ) : TNode {
2017-12-08 11:48:54 -08:00
return {
2018-03-20 19:06:49 -07:00
flags : 0 ,
2017-12-12 14:42:28 +01:00
tagName : tagName ,
attrs : attrs ,
2018-03-27 11:01:52 -07:00
localNames : null ,
2017-12-08 11:48:54 -08:00
initialInputs : undefined ,
inputs : undefined ,
2017-12-11 14:08:52 -08:00
outputs : undefined ,
2018-01-10 18:19:16 -08:00
data : data
2017-12-08 11:48:54 -08:00
} ;
2017-12-01 14:23:03 -08:00
}
/ * *
* Given a list of directive indices and minified input names , sets the
* input properties on the corresponding directives .
* /
2018-02-07 22:19:24 -08:00
function setInputsForProperty ( inputs : PropertyAliasValue , value : any ) : void {
2017-12-01 14:23:03 -08:00
for ( let i = 0 ; i < inputs . length ; i += 2 ) {
2018-03-21 15:10:34 -07:00
ngDevMode && assertDataInRange ( inputs [ i ] as number , directives ! ) ;
directives ! [ inputs [ i ] as number ] [ inputs [ i | 1 ] ] = value ;
2017-12-01 14:23:03 -08:00
}
}
/ * *
2018-02-07 22:19:24 -08:00
* Consolidates all inputs or outputs of all directives on this logical node .
2017-12-01 14:23:03 -08:00
*
2018-02-07 22:19:24 -08:00
* @param number lNodeFlags logical node flags
* @param Direction direction whether to consider inputs or outputs
* @returns PropertyAliases | null aggregate of all properties if any , ` null ` otherwise
2017-12-01 14:23:03 -08:00
* /
2018-03-20 19:06:49 -07:00
function generatePropertyAliases (
tNodeFlags : TNodeFlags , direction : BindingDirection ) : PropertyAliases | null {
2018-03-25 21:32:39 -07:00
const size = ( tNodeFlags & TNodeFlags . SIZE_MASK ) >> TNodeFlags . SIZE_SHIFT ;
2018-02-07 22:19:24 -08:00
let propStore : PropertyAliases | null = null ;
if ( size > 0 ) {
2018-03-20 19:06:49 -07:00
const start = tNodeFlags >> TNodeFlags . INDX_SHIFT ;
2018-02-07 22:19:24 -08:00
const isInput = direction === BindingDirection . Input ;
2018-03-21 15:10:34 -07:00
const defs = currentView . tView . directives ! ;
2018-02-07 22:19:24 -08:00
for ( let i = start , ii = start + size ; i < ii ; i ++ ) {
2018-03-21 15:10:34 -07:00
const directiveDef = defs [ i ] as DirectiveDef < any > ;
2018-02-07 22:19:24 -08:00
const propertyAliasMap : { [ publicName : string ] : string } =
isInput ? directiveDef.inputs : directiveDef.outputs ;
for ( let publicName in propertyAliasMap ) {
if ( propertyAliasMap . hasOwnProperty ( publicName ) ) {
propStore = propStore || { } ;
const internalName = propertyAliasMap [ publicName ] ;
const hasProperty = propStore . hasOwnProperty ( publicName ) ;
hasProperty ? propStore [ publicName ] . push ( i , internalName ) :
( propStore [ publicName ] = [ i , internalName ] ) ;
}
2017-12-01 14:23:03 -08:00
}
}
}
2018-02-07 22:19:24 -08:00
return propStore ;
2017-12-01 14:23:03 -08:00
}
/ * *
2018-03-08 13:57:56 -08:00
* Add or remove a class in a ` classList ` on a DOM element .
2017-12-01 14:23:03 -08:00
*
* This instruction is meant to handle the [ class . foo ] = "exp" case
*
2017-12-08 11:48:54 -08:00
* @param index The index of the element to update in the data array
2017-12-01 14:23:03 -08:00
* @param className Name of class to toggle . Because it is going to DOM , this is not subject to
* renaming as part of minification .
* @param value A value indicating if a given class should be added or removed .
* /
2018-03-07 16:25:18 -08:00
export function elementClassNamed < T > ( index : number , className : string , value : T | NO_CHANGE ) : void {
2017-12-01 14:23:03 -08:00
if ( value !== NO_CHANGE ) {
2018-01-08 20:17:13 -08:00
const lElement = data [ index ] as LElementNode ;
2017-12-01 14:23:03 -08:00
if ( value ) {
2018-02-07 22:57:11 -08:00
isProceduralRenderer ( renderer ) ? renderer . addClass ( lElement . native , className ) :
lElement . native . classList . add ( className ) ;
2017-12-01 14:23:03 -08:00
} else {
2018-02-07 22:57:11 -08:00
isProceduralRenderer ( renderer ) ? renderer . removeClass ( lElement . native , className ) :
lElement . native . classList . remove ( className ) ;
2017-12-01 14:23:03 -08:00
}
}
}
2018-03-08 13:57:56 -08:00
/ * *
* Set the ` className ` property on a DOM element .
*
* This instruction is meant to handle the ` [class]="exp" ` usage .
*
* ` elementClass ` instruction writes the value to the "element's" ` className ` property .
*
* @param index The index of the element to update in the data array
* @param value A value indicating a set of classes which should be applied . The method overrides
* any existing classes . The value is stringified ( ` toString ` ) before it is applied to the
* element .
* /
export function elementClass < T > ( index : number , value : T | NO_CHANGE ) : void {
if ( value !== NO_CHANGE ) {
// TODO: This is a naive implementation which simply writes value to the `className`. In the
// future
// we will add logic here which would work with the animation code.
const lElement : LElementNode = data [ index ] ;
isProceduralRenderer ( renderer ) ? renderer . setProperty ( lElement . native , 'className' , value ) :
lElement . native [ 'className' ] = stringify ( value ) ;
}
}
2017-12-01 14:23:03 -08:00
/ * *
* Update a given style on an Element .
*
2017-12-08 11:48:54 -08:00
* @param index Index of the element to change in the data array
2017-12-01 14:23:03 -08:00
* @param styleName Name of property . Because it is going to DOM this is not subject to
* renaming as part of minification .
* @param value New value to write ( null to remove ) .
2018-03-01 17:14:01 -08:00
* @param suffix Optional suffix . Used with scalar values to add unit such as ` px ` .
* @param sanitizer An optional function used to transform the value typically used for
* sanitization .
2017-12-01 14:23:03 -08:00
* /
2018-03-07 16:25:18 -08:00
export function elementStyleNamed < T > (
2018-03-01 17:14:01 -08:00
index : number , styleName : string , value : T | NO_CHANGE , suffix? : string ) : void ;
2018-03-07 16:25:18 -08:00
export function elementStyleNamed < T > (
2018-03-01 17:14:01 -08:00
index : number , styleName : string , value : T | NO_CHANGE , sanitizer? : Sanitizer ) : void ;
2018-03-07 16:25:18 -08:00
export function elementStyleNamed < T > (
2018-03-01 17:14:01 -08:00
index : number , styleName : string , value : T | NO_CHANGE ,
suffixOrSanitizer? : string | Sanitizer ) : void {
2017-12-01 14:23:03 -08:00
if ( value !== NO_CHANGE ) {
2018-03-08 13:57:56 -08:00
const lElement : LElementNode = data [ index ] ;
2017-12-01 14:23:03 -08:00
if ( value == null ) {
2018-02-07 22:57:11 -08:00
isProceduralRenderer ( renderer ) ?
renderer . removeStyle ( lElement . native , styleName , RendererStyleFlags3 . DashCase ) :
2018-03-08 13:57:56 -08:00
lElement . native [ 'style' ] . removeProperty ( styleName ) ;
2017-12-01 14:23:03 -08:00
} else {
2018-03-01 17:14:01 -08:00
let strValue =
typeof suffixOrSanitizer == 'function' ? suffixOrSanitizer ( value ) : stringify ( value ) ;
if ( typeof suffixOrSanitizer == 'string' ) strValue = strValue + suffixOrSanitizer ;
2018-02-07 22:57:11 -08:00
isProceduralRenderer ( renderer ) ?
renderer . setStyle ( lElement . native , styleName , strValue , RendererStyleFlags3 . DashCase ) :
2018-03-08 13:57:56 -08:00
lElement . native [ 'style' ] . setProperty ( styleName , strValue ) ;
2017-12-01 14:23:03 -08:00
}
}
}
2018-03-08 13:57:56 -08:00
/ * *
* Set the ` style ` property on a DOM element .
*
* This instruction is meant to handle the ` [style]="exp" ` usage .
*
*
* @param index The index of the element to update in the data array
* @param value A value indicating if a given style should be added or removed .
* The expected shape of ` value ` is an object where keys are style names and the values
* are their corresponding values to set . If value is falsy than the style is remove . An absence
* of style does not cause that style to be removed . ` NO_CHANGE ` implies that no update should be
* performed .
* /
export function elementStyle < T > (
index : number , value : { [ styleName : string ] : any } | NO_CHANGE ) : void {
if ( value !== NO_CHANGE ) {
// TODO: This is a naive implementation which simply writes value to the `style`. In the future
// we will add logic here which would work with the animation code.
const lElement = data [ index ] as LElementNode ;
if ( isProceduralRenderer ( renderer ) ) {
renderer . setProperty ( lElement . native , 'style' , value ) ;
} else {
const style = lElement . native [ 'style' ] ;
for ( let i = 0 , keys = Object . keys ( value ) ; i < keys . length ; i ++ ) {
const styleName : string = keys [ i ] ;
const styleValue : any = ( value as any ) [ styleName ] ;
styleValue == null ? style . removeProperty ( styleName ) :
style . setProperty ( styleName , styleValue ) ;
}
}
}
}
2017-12-01 14:23:03 -08:00
//////////////////////////
2017-12-14 15:50:01 -08:00
//// Text
2017-12-01 14:23:03 -08:00
//////////////////////////
/ * *
* Create static text node
*
2017-12-08 11:48:54 -08:00
* @param index Index of the node in the data array .
2017-12-01 14:23:03 -08:00
* @param value Value to write . This value will be stringified .
* If value is not provided than the actual creation of the text node is delayed .
* /
2017-12-14 16:26:28 -08:00
export function text ( index : number , value? : any ) : void {
2018-02-12 22:46:15 -08:00
ngDevMode &&
assertNull ( currentView . bindingStartIndex , 'text nodes should be created before bindings' ) ;
2017-12-01 14:23:03 -08:00
const textNode = value != null ?
2018-02-07 22:57:11 -08:00
( isProceduralRenderer ( renderer ) ? renderer . createText ( stringify ( value ) ) :
renderer . createTextNode ( stringify ( value ) ) ) :
2017-12-01 14:23:03 -08:00
null ;
2018-03-20 19:06:49 -07:00
const node = createLNode ( index , LNodeType . Element , textNode ) ;
2017-12-01 14:23:03 -08:00
// Text nodes are self closing.
isParent = false ;
appendChild ( node . parent ! , textNode , currentView ) ;
}
/ * *
* Create text node with binding
* Bindings should be handled externally with the proper bind ( 1 - 8 ) method
*
2017-12-08 11:48:54 -08:00
* @param index Index of the node in the data array .
2017-12-01 14:23:03 -08:00
* @param value Stringified value to write .
* /
2017-12-14 16:26:28 -08:00
export function textBinding < T > ( index : number , value : T | NO_CHANGE ) : void {
2018-01-23 18:39:09 -08:00
ngDevMode && assertDataInRange ( index ) ;
let existingNode = data [ index ] as LTextNode ;
ngDevMode && assertNotNull ( existingNode , 'existing node' ) ;
if ( existingNode . native ) {
2017-12-01 14:23:03 -08:00
// If DOM node exists and value changed, update textContent
value !== NO_CHANGE &&
2018-02-07 22:57:11 -08:00
( isProceduralRenderer ( renderer ) ? renderer . setValue ( existingNode . native , stringify ( value ) ) :
existingNode . native . textContent = stringify ( value ) ) ;
2018-01-23 18:39:09 -08:00
} else {
2017-12-01 14:23:03 -08:00
// Node was created but DOM node creation was delayed. Create and append now.
2018-02-07 22:57:11 -08:00
existingNode . native = isProceduralRenderer ( renderer ) ?
renderer . createText ( stringify ( value ) ) :
renderer . createTextNode ( stringify ( value ) ) ;
2017-12-01 14:23:03 -08:00
insertChild ( existingNode , currentView ) ;
}
}
//////////////////////////
//// Directive
//////////////////////////
2018-01-08 21:57:50 -08:00
/ * *
* Create a directive .
2017-12-01 14:23:03 -08:00
*
* NOTE : directives can be created in order other than the index order . They can also
* be retrieved before they are created in which case the value will be null .
*
2018-03-21 15:10:34 -07:00
* @param elementIndex Index of the host element in the data array
2017-12-01 14:23:03 -08:00
* @param directive The directive instance .
* @param directiveDef DirectiveDef object which contains information about the template .
2018-03-21 15:10:34 -07:00
* @param localRefs Names under which a query can retrieve the directive instance
2017-12-01 14:23:03 -08:00
* /
2018-01-08 21:57:50 -08:00
export function directiveCreate < T > (
2018-03-27 11:01:52 -07:00
elementIndex : number , directive : T , directiveDef : DirectiveDef < T > | ComponentDef < T > ) : T {
2018-03-25 21:32:39 -07:00
const index = directives ? directives.length : 0 ;
2018-03-16 20:31:24 -07:00
const instance = baseDirectiveCreate ( index , directive , directiveDef ) ;
ngDevMode && assertNotNull ( previousOrParentNode . tNode , 'previousOrParentNode.tNode' ) ;
const tNode : TNode | null = previousOrParentNode . tNode ! ;
2018-03-25 21:32:39 -07:00
const isComponent = ( directiveDef as ComponentDef < any > ) . template ;
if ( isComponent ) {
addComponentLogic ( index , elementIndex , directive , directiveDef as ComponentDef < any > ) ;
}
2018-03-21 15:10:34 -07:00
if ( firstTemplatePass ) {
// Init hooks are queued now so ngOnInit is called in host components before
// any projected components.
queueInitHooks ( index , directiveDef . onInit , directiveDef . doCheck , currentView . tView ) ;
if ( directiveDef . hostBindings ) queueHostBindingForCheck ( index , elementIndex ) ;
2018-03-16 20:31:24 -07:00
}
if ( tNode && tNode . attrs ) {
2018-03-21 15:10:34 -07:00
setInputsFromAttrs < T > ( index , instance , directiveDef . inputs , tNode ) ;
2018-03-16 20:31:24 -07:00
}
return instance ;
}
2018-03-25 21:32:39 -07:00
function addComponentLogic < T > (
index : number , elementIndex : number , instance : T , def : ComponentDef < any > ) : void {
2018-03-27 15:53:48 -07:00
const tView = getOrCreateTView ( def . template , def . directiveDefs , def . pipeDefs ) ;
2018-03-25 21:32:39 -07:00
// Only component views should be added to the view tree directly. Embedded views are
// accessed through their containers because they may be removed / re-added later.
const hostView = addToViewTree ( createLView (
- 1 , rendererFactory . createRenderer ( previousOrParentNode . native as RElement , def . rendererType ) ,
tView , null , null , def . onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways ) ) ;
( previousOrParentNode . data as any ) = hostView ;
( hostView . node as any ) = previousOrParentNode ;
initChangeDetectorIfExisting ( previousOrParentNode . nodeInjector , instance , hostView ) ;
2018-03-27 11:01:52 -07:00
if ( firstTemplatePass ) queueComponentIndexForCheck ( index , elementIndex ) ;
2018-03-25 21:32:39 -07:00
}
2018-03-16 20:31:24 -07:00
/ * *
* A lighter version of directiveCreate ( ) that is used for the root component
*
* This version does not contain features that we don ' t already support at root in
* current Angular . Example : local refs and inputs on root component .
* /
export function baseDirectiveCreate < T > (
2018-03-25 21:32:39 -07:00
index : number , directive : T , directiveDef : DirectiveDef < T > | ComponentDef < any > ) : T {
2018-02-12 22:46:15 -08:00
ngDevMode &&
assertNull ( currentView . bindingStartIndex , 'directives should be created before any bindings' ) ;
2018-01-08 21:57:50 -08:00
ngDevMode && assertPreviousIsParent ( ) ;
2018-03-20 19:06:49 -07:00
2018-01-08 21:57:50 -08:00
Object . defineProperty (
directive , NG_HOST_SYMBOL , { enumerable : false , value : previousOrParentNode } ) ;
2017-12-19 16:51:42 +01:00
2018-03-21 15:10:34 -07:00
if ( directives == null ) currentView . directives = directives = [ ] ;
2017-12-11 14:08:52 -08:00
2018-03-21 15:10:34 -07:00
ngDevMode && assertDataNext ( index , directives ) ;
directives [ index ] = directive ;
2018-01-08 21:57:50 -08:00
const diPublic = directiveDef ! . diPublic ;
if ( diPublic ) {
diPublic ( directiveDef ! ) ;
2017-12-01 14:23:03 -08:00
}
2017-12-11 14:08:52 -08:00
2018-03-20 19:06:49 -07:00
if ( directiveDef ! . attributes != null && previousOrParentNode . type == LNodeType . Element ) {
2018-02-16 12:09:47 -08:00
setUpAttributes (
( previousOrParentNode as LElementNode ) . native , directiveDef ! . attributes as string [ ] ) ;
}
2018-03-21 15:10:34 -07:00
return directive ;
2017-12-01 14:23:03 -08:00
}
/ * *
* Sets initial input properties on directive instances from attribute data
*
2018-03-21 15:10:34 -07:00
* @param directiveIndex Index of the directive in directives array
2017-12-01 14:23:03 -08:00
* @param instance Instance of the directive on which to set the initial inputs
* @param inputs The list of inputs from the directive def
2018-01-08 20:17:13 -08:00
* @param tNode The static data for this node
2017-12-01 14:23:03 -08:00
* /
2018-03-21 15:10:34 -07:00
function setInputsFromAttrs < T > (
directiveIndex : number , instance : T , inputs : { [ key : string ] : string } , tNode : TNode ) : void {
2018-01-08 20:17:13 -08:00
let initialInputData = tNode . initialInputs as InitialInputData | undefined ;
2017-12-01 14:23:03 -08:00
if ( initialInputData === undefined || directiveIndex >= initialInputData . length ) {
2018-01-08 20:17:13 -08:00
initialInputData = generateInitialInputs ( directiveIndex , inputs , tNode ) ;
2017-12-01 14:23:03 -08:00
}
const initialInputs : InitialInputs | null = initialInputData [ directiveIndex ] ;
if ( initialInputs ) {
for ( let i = 0 ; i < initialInputs . length ; i += 2 ) {
( instance as any ) [ initialInputs [ i ] ] = initialInputs [ i | 1 ] ;
}
}
}
/ * *
2017-12-14 15:50:01 -08:00
* Generates initialInputData for a node and stores it in the template ' s static storage
* so subsequent template invocations don ' t have to recalculate it .
*
* initialInputData is an array containing values that need to be set as input properties
* for directives on this node , but only once on creation . We need this array to support
* the case where you set an @Input property of a directive using attribute - like syntax .
* e . g . if you have a ` name ` @Input , you can set it once like this :
*
* < my - component name = "Bess" > < / m y - c o m p o n e n t >
2017-12-01 14:23:03 -08:00
*
* @param directiveIndex Index to store the initial input data
* @param inputs The list of inputs from the directive def
2018-01-08 20:17:13 -08:00
* @param tNode The static data on this node
2017-12-01 14:23:03 -08:00
* /
function generateInitialInputs (
2018-01-08 20:17:13 -08:00
directiveIndex : number , inputs : { [ key : string ] : string } , tNode : TNode ) : InitialInputData {
const initialInputData : InitialInputData = tNode . initialInputs || ( tNode . initialInputs = [ ] ) ;
2017-12-01 14:23:03 -08:00
initialInputData [ directiveIndex ] = null ;
2018-01-08 20:17:13 -08:00
const attrs = tNode . attrs ! ;
2017-12-01 14:23:03 -08:00
for ( let i = 0 ; i < attrs . length ; i += 2 ) {
const attrName = attrs [ i ] ;
const minifiedInputName = inputs [ attrName ] ;
if ( minifiedInputName !== undefined ) {
const inputsToStore : InitialInputs =
initialInputData [ directiveIndex ] || ( initialInputData [ directiveIndex ] = [ ] ) ;
inputsToStore . push ( minifiedInputName , attrs [ i | 1 ] ) ;
}
}
return initialInputData ;
}
//////////////////////////
//// ViewContainer & View
//////////////////////////
/ * *
2018-01-08 20:17:13 -08:00
* Creates an LContainerNode .
2017-12-01 14:23:03 -08:00
*
2018-01-08 20:17:13 -08:00
* Only ` LViewNodes ` can go into ` LContainerNodes ` .
2017-12-01 14:23:03 -08:00
*
2017-12-08 11:48:54 -08:00
* @param index The index of the container in the data array
2017-12-01 14:23:03 -08:00
* @param template Optional inline template
2017-12-14 15:50:01 -08:00
* @param tagName The name of the container element , if applicable
* @param attrs The attrs attached to the container , if applicable
2018-01-08 21:57:50 -08:00
* @param localRefs A set of local reference bindings on the element .
2017-12-01 14:23:03 -08:00
* /
2018-01-09 13:32:24 -08:00
export function container (
2018-03-25 21:32:39 -07:00
index : number , template? : ComponentTemplate < any > , tagName? : string , attrs? : string [ ] ,
localRefs? : string [ ] | null ) : void {
2018-02-12 22:46:15 -08:00
ngDevMode &&
assertNull (
currentView . bindingStartIndex , 'container nodes should be created before any bindings' ) ;
2017-12-01 14:23:03 -08:00
const currentParent = isParent ? previousOrParentNode : previousOrParentNode.parent ! ;
2018-02-12 22:46:15 -08:00
ngDevMode && assertNotNull ( currentParent , 'containers should have a parent' ) ;
2018-01-25 15:32:21 +01:00
2018-01-17 17:55:55 +01:00
const lContainer = < LContainer > {
2017-12-13 19:34:46 -08:00
views : [ ] ,
2018-01-26 11:36:31 +01:00
nextIndex : 0 ,
// If the direct parent of the container is a view, its views will need to be added
// through insertView() when its parent view is being inserted:
renderParent : canInsertNativeNode ( currentParent , currentView ) ? currentParent : null ,
2017-12-01 14:23:03 -08:00
template : template == null ? null : template ,
next : null ,
2018-01-17 10:09:05 -08:00
parent : currentView ,
dynamicViewCount : 0 ,
2018-01-29 14:51:37 +01:00
queries : null
2018-01-17 17:55:55 +01:00
} ;
2018-03-20 19:06:49 -07:00
const node = createLNode ( index , LNodeType . Container , undefined , lContainer ) ;
2017-12-01 14:23:03 -08:00
2018-01-08 20:17:13 -08:00
if ( node . tNode == null ) {
2018-03-27 11:01:52 -07:00
node . tNode = tData [ index ] = createTNode ( tagName || null , attrs || null , [ ] ) ;
2017-12-01 14:23:03 -08:00
}
// Containers are added to the current view tree instead of their embedded views
// because views can be removed and re-inserted.
addToViewTree ( node . data ) ;
2018-03-25 21:32:39 -07:00
if ( firstTemplatePass ) cacheMatchingDirectivesForNode ( node . tNode ) ;
2018-03-27 11:01:52 -07:00
// TODO: handle TemplateRef!
hack_declareDirectives ( index , localRefs || null ) ;
2017-12-01 14:23:03 -08:00
2018-01-09 13:32:24 -08:00
isParent = false ;
2018-03-20 19:06:49 -07:00
ngDevMode && assertNodeType ( previousOrParentNode , LNodeType . Container ) ;
2018-01-29 14:51:37 +01:00
const queries = node . queries ;
if ( queries ) {
2018-01-17 17:55:55 +01:00
// check if a given container node matches
2018-01-29 14:51:37 +01:00
queries . addNode ( node ) ;
2018-01-17 17:55:55 +01:00
// prepare place for matching nodes from views inserted into a given container
2018-01-29 14:51:37 +01:00
lContainer . queries = queries . container ( ) ;
2018-01-17 17:55:55 +01:00
}
2017-12-01 14:23:03 -08:00
}
/ * *
* Sets a container up to receive views .
*
2017-12-08 11:48:54 -08:00
* @param index The index of the container in the data array
2017-12-01 14:23:03 -08:00
* /
2017-12-14 16:26:28 -08:00
export function containerRefreshStart ( index : number ) : void {
2017-12-08 11:48:54 -08:00
ngDevMode && assertDataInRange ( index ) ;
previousOrParentNode = data [ index ] as LNode ;
2018-03-20 19:06:49 -07:00
ngDevMode && assertNodeType ( previousOrParentNode , LNodeType . Container ) ;
2017-12-01 14:23:03 -08:00
isParent = true ;
2018-01-26 11:36:31 +01:00
( previousOrParentNode as LContainerNode ) . data . nextIndex = 0 ;
2018-02-12 22:46:15 -08:00
ngDevMode && assertSame (
( previousOrParentNode as LContainerNode ) . native , undefined ,
` the container's native element should not have been set yet. ` ) ;
2018-01-22 17:43:52 -08:00
2018-03-09 20:22:18 -08:00
if ( ! checkNoChangesMode ) {
// We need to execute init hooks here so ngOnInit hooks are called in top level views
// before they are called in embedded views (for backwards compatibility).
executeInitHooks ( currentView , currentView . tView , creationMode ) ;
}
2017-12-01 14:23:03 -08:00
}
/ * *
2018-01-08 20:17:13 -08:00
* Marks the end of the LContainerNode .
2017-12-01 14:23:03 -08:00
*
2018-01-08 20:17:13 -08:00
* Marking the end of LContainerNode is the time when to child Views get inserted or removed .
2017-12-01 14:23:03 -08:00
* /
2017-12-14 16:26:28 -08:00
export function containerRefreshEnd ( ) : void {
2017-12-01 14:23:03 -08:00
if ( isParent ) {
isParent = false ;
} else {
2018-03-20 19:06:49 -07:00
ngDevMode && assertNodeType ( previousOrParentNode , LNodeType . View ) ;
2017-12-01 14:23:03 -08:00
ngDevMode && assertHasParent ( ) ;
previousOrParentNode = previousOrParentNode . parent ! ;
}
2018-03-20 19:06:49 -07:00
ngDevMode && assertNodeType ( previousOrParentNode , LNodeType . Container ) ;
2018-01-08 20:17:13 -08:00
const container = previousOrParentNode as LContainerNode ;
2018-01-26 11:36:31 +01:00
container . native = undefined ;
2018-03-20 19:06:49 -07:00
ngDevMode && assertNodeType ( container , LNodeType . Container ) ;
2017-12-01 14:23:03 -08:00
const nextIndex = container . data . nextIndex ;
2018-03-08 12:10:20 +01:00
// remove extra views at the end of the container
2017-12-13 19:34:46 -08:00
while ( nextIndex < container . data . views . length ) {
2017-12-01 14:23:03 -08:00
removeView ( container , nextIndex ) ;
}
}
2018-01-17 10:09:05 -08:00
function refreshDynamicChildren() {
for ( let current = currentView . child ; current !== null ; current = current . next ) {
if ( current . dynamicViewCount !== 0 && ( current as LContainer ) . views ) {
const container = current as LContainer ;
for ( let i = 0 ; i < container . views . length ; i ++ ) {
const view = container . views [ i ] ;
renderEmbeddedTemplate ( view , view . data . template ! , view . data . context ! , renderer ) ;
}
}
}
}
2018-03-08 12:10:20 +01:00
/ * *
* Looks for a view with a given view block id inside a provided LContainer .
* Removes views that need to be deleted in the process .
*
* @param containerNode where to search for views
* @param startIdx starting index in the views array to search from
* @param viewBlockId exact view block id to look for
* @returns index of a found view or - 1 if not found
* /
function scanForView (
containerNode : LContainerNode , startIdx : number , viewBlockId : number ) : LViewNode | null {
const views = containerNode . data . views ;
for ( let i = startIdx ; i < views . length ; i ++ ) {
const viewAtPositionId = views [ i ] . data . id ;
if ( viewAtPositionId === viewBlockId ) {
return views [ i ] ;
} else if ( viewAtPositionId < viewBlockId ) {
// found a view that should not be at this position - remove
removeView ( containerNode , i ) ;
} else {
// found a view with id grater than the one we are searching for
// which means that required view doesn't exist and can't be found at
// later positions in the views array - stop the search here
break ;
}
}
return null ;
}
2017-12-01 14:23:03 -08:00
/ * *
2018-02-06 17:27:16 -08:00
* Marks the start of an embedded view .
2017-12-01 14:23:03 -08:00
*
* @param viewBlockId The ID of this view
2018-02-06 17:27:16 -08:00
* @return boolean Whether or not this view is in creation mode
2017-12-01 14:23:03 -08:00
* /
2018-02-06 17:27:16 -08:00
export function embeddedViewStart ( viewBlockId : number ) : boolean {
2018-01-08 20:17:13 -08:00
const container =
( isParent ? previousOrParentNode : previousOrParentNode.parent ! ) as LContainerNode ;
2018-03-20 19:06:49 -07:00
ngDevMode && assertNodeType ( container , LNodeType . Container ) ;
2018-01-08 20:17:13 -08:00
const lContainer = container . data ;
2018-03-08 12:10:20 +01:00
const existingViewNode = scanForView ( container , lContainer . nextIndex , viewBlockId ) ;
2017-12-01 14:23:03 -08:00
2018-03-08 12:10:20 +01:00
if ( existingViewNode ) {
previousOrParentNode = existingViewNode ;
2018-03-20 19:06:49 -07:00
ngDevMode && assertNodeType ( previousOrParentNode , LNodeType . View ) ;
2017-12-01 14:23:03 -08:00
isParent = true ;
2018-03-08 12:10:20 +01:00
enterView ( ( existingViewNode as LViewNode ) . data , existingViewNode as LViewNode ) ;
2017-12-01 14:23:03 -08:00
} else {
2018-01-08 20:17:13 -08:00
// When we create a new LView, we always reset the state of the instructions.
2018-02-03 20:34:30 -08:00
const newView = createLView (
2018-02-23 13:17:20 -08:00
viewBlockId , renderer , getOrCreateEmbeddedTView ( viewBlockId , container ) , null , null ,
LViewFlags . CheckAlways ) ;
2018-01-29 14:51:37 +01:00
if ( lContainer . queries ) {
newView . queries = lContainer . queries . enterView ( lContainer . nextIndex ) ;
2018-01-17 17:55:55 +01:00
}
2018-03-20 19:06:49 -07:00
enterView ( newView , createLNode ( null , LNodeType . View , null , newView ) ) ;
2017-12-01 14:23:03 -08:00
}
2018-03-08 12:10:20 +01:00
return ! existingViewNode ;
2017-12-01 14:23:03 -08:00
}
2017-12-08 11:48:54 -08:00
/ * *
2018-01-10 18:19:16 -08:00
* Initialize the TView ( e . g . static data ) for the active embedded view .
2017-12-11 14:08:52 -08:00
*
2018-01-10 18:19:16 -08:00
* Each embedded view needs to set the global tData variable to the static data for
2017-12-11 14:08:52 -08:00
* that view . Otherwise , the view ' s static data for a particular node would overwrite
2018-01-10 18:19:16 -08:00
* the static data for a node in the view above it with the same index ( since it ' s in the
2017-12-11 14:08:52 -08:00
* same template ) .
2017-12-08 11:48:54 -08:00
*
2018-01-10 18:19:16 -08:00
* @param viewIndex The index of the TView in TContainer
2017-12-11 14:08:52 -08:00
* @param parent The parent container in which to look for the view ' s static data
2018-01-10 18:19:16 -08:00
* @returns TView
2017-12-08 11:48:54 -08:00
* /
2018-01-10 18:19:16 -08:00
function getOrCreateEmbeddedTView ( viewIndex : number , parent : LContainerNode ) : TView {
2018-03-20 19:06:49 -07:00
ngDevMode && assertNodeType ( parent , LNodeType . Container ) ;
2018-01-10 18:19:16 -08:00
const tContainer = ( parent ! . tNode as TContainerNode ) . data ;
if ( viewIndex >= tContainer . length || tContainer [ viewIndex ] == null ) {
2018-03-27 15:53:48 -07:00
const tView = currentView . tView ;
tContainer [ viewIndex ] = createTView ( tView . directiveRegistry , tView . pipeRegistry ) ;
2017-12-08 11:48:54 -08:00
}
2018-01-10 18:19:16 -08:00
return tContainer [ viewIndex ] ;
2017-12-08 11:48:54 -08:00
}
2018-02-06 17:27:16 -08:00
/** Marks the end of an embedded view. */
export function embeddedViewEnd ( ) : void {
2018-03-16 16:42:13 -07:00
refreshDirectives ( ) ;
2017-12-01 14:23:03 -08:00
isParent = false ;
2018-01-08 20:17:13 -08:00
const viewNode = previousOrParentNode = currentView . node as LViewNode ;
2018-03-08 12:10:20 +01:00
const containerNode = previousOrParentNode . parent as LContainerNode ;
if ( containerNode ) {
2018-03-20 19:06:49 -07:00
ngDevMode && assertNodeType ( viewNode , LNodeType . View ) ;
ngDevMode && assertNodeType ( containerNode , LNodeType . Container ) ;
2018-03-08 12:10:20 +01:00
const lContainer = containerNode . data ;
if ( creationMode ) {
// it is a new view, insert it into collection of views for a given container
insertView ( containerNode , viewNode , lContainer . nextIndex ) ;
2018-01-17 10:09:05 -08:00
}
2018-03-08 12:10:20 +01:00
lContainer . nextIndex ++ ;
2017-12-01 14:23:03 -08:00
}
leaveView ( currentView ! . parent ! ) ;
ngDevMode && assertEqual ( isParent , false , 'isParent' ) ;
2018-03-20 19:06:49 -07:00
ngDevMode && assertNodeType ( previousOrParentNode , LNodeType . View ) ;
2017-12-01 14:23:03 -08:00
}
2018-01-17 10:09:05 -08:00
2017-12-01 14:23:03 -08:00
/////////////
2017-12-14 16:26:28 -08:00
/ * *
2018-03-16 16:42:13 -07:00
* Refreshes components by entering the component view and processing its bindings , queries , etc .
2017-12-14 16:26:28 -08:00
*
* @param directiveIndex
* @param elementIndex
* /
2018-03-13 11:48:09 -07:00
export function componentRefresh < T > ( directiveIndex : number , elementIndex : number ) : void {
2018-03-16 16:42:13 -07:00
ngDevMode && assertDataInRange ( elementIndex ) ;
const element = data ! [ elementIndex ] as LElementNode ;
2018-03-20 19:06:49 -07:00
ngDevMode && assertNodeType ( element , LNodeType . Element ) ;
2018-03-16 16:42:13 -07:00
ngDevMode && assertNotNull ( element . data , ` Component's host node should have an LView attached. ` ) ;
const hostView = element . data ! ;
2018-02-23 13:17:20 -08:00
2018-03-16 16:42:13 -07:00
// Only attached CheckAlways components or attached, dirty OnPush components should be checked
if ( viewAttached ( hostView ) && hostView . flags & ( LViewFlags . CheckAlways | LViewFlags . Dirty ) ) {
2018-03-21 15:10:34 -07:00
ngDevMode && assertDataInRange ( directiveIndex , directives ! ) ;
2018-03-25 21:32:39 -07:00
const def = currentView . tView . directives ! [ directiveIndex ] as ComponentDef < any > ;
2018-03-20 19:06:49 -07:00
detectChangesInternal (
2018-03-25 21:32:39 -07:00
hostView , element , def , getDirectiveInstance < T > ( directives ! [ directiveIndex ] ) ) ;
2017-12-01 14:23:03 -08:00
}
2018-01-23 10:57:48 -08:00
}
2017-12-01 14:23:03 -08:00
2018-03-08 16:55:47 -08:00
/** Returns a boolean for whether the view is attached */
function viewAttached ( view : LView ) : boolean {
return ( view . flags & LViewFlags . Attached ) === LViewFlags . Attached ;
}
2017-12-01 14:23:03 -08:00
/ * *
* Instruction to distribute projectable nodes among < ng - content > occurrences in a given template .
* It takes all the selectors from the entire component ' s template and decides where
* each projected node belongs ( it re - distributes nodes among "buckets" where each "bucket" is
* backed by a selector ) .
*
2018-02-28 15:00:58 +01:00
* This function requires CSS selectors to be provided in 2 forms : parsed ( by a compiler ) and text ,
* un - parsed form .
*
* The parsed form is needed for efficient matching of a node against a given CSS selector .
* The un - parsed , textual form is needed for support of the ngProjectAs attribute .
*
* Having a CSS selector in 2 different formats is not ideal , but alternatives have even more
* drawbacks :
* - having only a textual form would require runtime parsing of CSS selectors ;
* - we can 't have only a parsed as we can' t re - construct textual form from it ( as entered by a
* template author ) .
*
* @param selectors A collection of parsed CSS selectors
* @param rawSelectors A collection of CSS selectors in the raw , un - parsed form
2017-12-01 14:23:03 -08:00
* /
2018-02-28 15:00:58 +01:00
export function projectionDef (
2018-03-29 16:41:45 -07:00
index : number , selectors? : CssSelectorList [ ] , textSelectors? : string [ ] ) : void {
2017-12-01 14:23:03 -08:00
const noOfNodeBuckets = selectors ? selectors . length + 1 : 1 ;
const distributedNodes = new Array < LNode [ ] > ( noOfNodeBuckets ) ;
for ( let i = 0 ; i < noOfNodeBuckets ; i ++ ) {
distributedNodes [ i ] = [ ] ;
}
const componentNode = findComponentHost ( currentView ) ;
let componentChild = componentNode . child ;
while ( componentChild !== null ) {
2018-01-31 15:50:24 +01:00
// execute selector matching logic if and only if:
// - there are selectors defined
// - a node has a tag name / attributes that can be matched
if ( selectors && componentChild . tNode ) {
2018-02-28 15:00:58 +01:00
const matchedIdx = matchingSelectorIndex ( componentChild . tNode , selectors , textSelectors ! ) ;
2018-01-31 15:50:24 +01:00
distributedNodes [ matchedIdx ] . push ( componentChild ) ;
} else {
2017-12-01 14:23:03 -08:00
distributedNodes [ 0 ] . push ( componentChild ) ;
}
2018-01-31 15:50:24 +01:00
2017-12-01 14:23:03 -08:00
componentChild = componentChild . next ;
}
2018-01-18 13:27:01 -08:00
ngDevMode && assertDataNext ( index ) ;
data [ index ] = distributedNodes ;
2017-12-01 14:23:03 -08:00
}
2018-01-25 15:32:21 +01:00
/ * *
* Updates the linked list of a projection node , by appending another linked list .
*
* @param projectionNode Projection node whose projected nodes linked list has to be updated
* @param appendedFirst First node of the linked list to append .
* @param appendedLast Last node of the linked list to append .
* /
function appendToProjectionNode (
projectionNode : LProjectionNode ,
appendedFirst : LElementNode | LTextNode | LContainerNode | null ,
appendedLast : LElementNode | LTextNode | LContainerNode | null ) {
2018-02-12 22:46:15 -08:00
ngDevMode && assertEqual (
! ! appendedFirst , ! ! appendedLast ,
'appendedFirst can be null if and only if appendedLast is also null' ) ;
2018-01-25 15:32:21 +01:00
if ( ! appendedLast ) {
// nothing to append
return ;
}
const projectionNodeData = projectionNode . data ;
2018-01-26 11:36:31 +01:00
if ( projectionNodeData . tail ) {
projectionNodeData . tail . pNextOrParent = appendedFirst ;
2018-01-25 15:32:21 +01:00
} else {
2018-01-26 11:36:31 +01:00
projectionNodeData . head = appendedFirst ;
2018-01-25 15:32:21 +01:00
}
2018-01-26 11:36:31 +01:00
projectionNodeData . tail = appendedLast ;
2018-01-25 15:32:21 +01:00
appendedLast . pNextOrParent = projectionNode ;
}
2017-12-01 14:23:03 -08:00
/ * *
* Inserts previously re - distributed projected nodes . This instruction must be preceded by a call
2017-12-14 16:26:28 -08:00
* to the projectionDef instruction .
2017-12-01 14:23:03 -08:00
*
2017-12-19 15:01:05 -08:00
* @param nodeIndex
* @param localIndex - index under which distribution of projected nodes was memorized
* @param selectorIndex - 0 means < ng - content > without any selector
2018-01-31 15:50:24 +01:00
* @param attrs - attributes attached to the ng - content node , if present
2017-12-01 14:23:03 -08:00
* /
2018-01-31 15:50:24 +01:00
export function projection (
nodeIndex : number , localIndex : number , selectorIndex : number = 0 , attrs? : string [ ] ) : void {
2018-03-20 19:06:49 -07:00
const node = createLNode ( nodeIndex , LNodeType . Projection , null , { head : null , tail : null } ) ;
2018-01-31 15:50:24 +01:00
if ( node . tNode == null ) {
2018-03-27 11:01:52 -07:00
node . tNode = createTNode ( null , attrs || null , null ) ;
2018-01-31 15:50:24 +01:00
}
2017-12-01 14:23:03 -08:00
isParent = false ; // self closing
const currentParent = node . parent ;
// re-distribution of projectable nodes is memorized on a component's view level
const componentNode = findComponentHost ( currentView ) ;
// make sure that nodes to project were memorized
2018-02-16 16:58:07 -08:00
const nodesForSelector = componentNode . data ! . data ! [ localIndex ] [ selectorIndex ] ;
2017-12-01 14:23:03 -08:00
2018-01-25 15:32:21 +01:00
// build the linked list of projected nodes:
2017-12-01 14:23:03 -08:00
for ( let i = 0 ; i < nodesForSelector . length ; i ++ ) {
const nodeToProject = nodesForSelector [ i ] ;
2018-03-20 19:06:49 -07:00
if ( nodeToProject . type === LNodeType . Projection ) {
2018-01-25 15:32:21 +01:00
const previouslyProjected = ( nodeToProject as LProjectionNode ) . data ;
2018-01-26 11:36:31 +01:00
appendToProjectionNode ( node , previouslyProjected . head , previouslyProjected . tail ) ;
2017-12-01 14:23:03 -08:00
} else {
2018-01-25 15:32:21 +01:00
appendToProjectionNode (
node , nodeToProject as LTextNode | LElementNode | LContainerNode ,
nodeToProject as LTextNode | LElementNode | LContainerNode ) ;
2017-12-01 14:23:03 -08:00
}
}
2018-01-25 15:32:21 +01:00
2018-01-26 11:36:31 +01:00
if ( canInsertNativeNode ( currentParent , currentView ) ) {
// process each node in the list of projected nodes:
let nodeToProject : LNode | null = node . data . head ;
const lastNodeToProject = node . data . tail ;
while ( nodeToProject ) {
appendProjectedNode (
nodeToProject as LTextNode | LElementNode | LContainerNode , currentParent , currentView ) ;
nodeToProject = nodeToProject === lastNodeToProject ? null : nodeToProject . pNextOrParent ;
}
2018-01-25 15:32:21 +01:00
}
2017-12-01 14:23:03 -08:00
}
/ * *
* Given a current view , finds the nearest component ' s host ( LElement ) .
*
2018-01-08 20:17:13 -08:00
* @param lView LView for which we want a host element node
2017-12-14 16:26:28 -08:00
* @returns The host node
2017-12-01 14:23:03 -08:00
* /
2018-01-08 20:17:13 -08:00
function findComponentHost ( lView : LView ) : LElementNode {
let viewRootLNode = lView . node ;
2018-03-20 19:06:49 -07:00
while ( viewRootLNode . type === LNodeType . View ) {
2018-01-08 20:17:13 -08:00
ngDevMode && assertNotNull ( lView . parent , 'lView.parent' ) ;
lView = lView . parent ! ;
viewRootLNode = lView . node ;
2017-12-01 14:23:03 -08:00
}
2018-03-20 19:06:49 -07:00
ngDevMode && assertNodeType ( viewRootLNode , LNodeType . Element ) ;
2017-12-01 14:23:03 -08:00
ngDevMode && assertNotNull ( viewRootLNode . data , 'node.data' ) ;
2018-01-08 20:17:13 -08:00
return viewRootLNode as LElementNode ;
2017-12-01 14:23:03 -08:00
}
/ * *
2018-01-08 20:17:13 -08:00
* Adds a LView or a LContainer to the end of the current view tree .
2017-12-01 14:23:03 -08:00
*
2017-12-14 16:26:28 -08:00
* This structure will be used to traverse through nested views to remove listeners
* and call onDestroy callbacks .
2017-12-01 14:23:03 -08:00
*
2018-01-08 20:17:13 -08:00
* @param state The LView or LContainer to add to the view tree
2017-12-14 16:26:28 -08:00
* @returns The state passed in
2017-12-01 14:23:03 -08:00
* /
2018-01-08 20:17:13 -08:00
export function addToViewTree < T extends LView | LContainer > ( state : T ) : T {
2017-12-01 14:23:03 -08:00
currentView . tail ? ( currentView . tail . next = state ) : ( currentView . child = state ) ;
currentView . tail = state ;
return state ;
}
2018-02-23 13:17:20 -08:00
///////////////////////////////
//// Change detection
///////////////////////////////
/** If node is an OnPush component, marks its LView dirty. */
export function markDirtyIfOnPush ( node : LElementNode ) : void {
// Because data flows down the component tree, ancestors do not need to be marked dirty
if ( node . data && ! ( node . data . flags & LViewFlags . CheckAlways ) ) {
node . data . flags |= LViewFlags . Dirty ;
}
}
/ * *
* Wraps an event listener so its host view and its ancestor views will be marked dirty
* whenever the event fires . Necessary to support OnPush components .
* /
2018-03-01 09:46:39 -08:00
export function wrapListenerWithDirtyLogic ( view : LView , listenerFn : ( e? : any ) = > any ) : ( e : Event ) = >
any {
return function ( e : any ) {
markViewDirty ( view ) ;
return listenerFn ( e ) ;
} ;
}
/ * *
* Wraps an event listener so its host view and its ancestor views will be marked dirty
* whenever the event fires . Also wraps with preventDefault behavior .
* /
export function wrapListenerWithDirtyAndDefault (
view : LView , listenerFn : ( e? : any ) = > any ) : EventListener {
2018-02-23 13:17:20 -08:00
return function ( e : Event ) {
markViewDirty ( view ) ;
2018-03-01 09:46:39 -08:00
if ( listenerFn ( e ) === false ) {
e . preventDefault ( ) ;
// Necessary for legacy browsers that don't support preventDefault (e.g. IE)
e . returnValue = false ;
}
2018-02-23 13:17:20 -08:00
} ;
}
/** Marks current view and all ancestors dirty */
2018-03-09 12:45:31 -08:00
export function markViewDirty ( view : LView ) : void {
2018-02-23 13:17:20 -08:00
let currentView : LView | null = view ;
while ( currentView . parent != null ) {
currentView . flags |= LViewFlags . Dirty ;
currentView = currentView . parent ;
}
currentView . flags |= LViewFlags . Dirty ;
ngDevMode && assertNotNull ( currentView ! . context , 'rootContext' ) ;
2018-03-06 11:58:08 -08:00
scheduleTick ( currentView ! . context as RootContext ) ;
2018-02-23 13:17:20 -08:00
}
2018-03-06 11:58:08 -08:00
/ * *
* Used to schedule change detection on the whole application .
*
* Unlike ` tick ` , ` scheduleTick ` coalesces multiple calls into one change detection run .
* It is usually called indirectly by calling ` markDirty ` when the view needs to be
* re - rendered .
*
* Typically ` scheduleTick ` uses ` requestAnimationFrame ` to coalesce multiple
* ` scheduleTick ` requests . The scheduling function can be overridden in
* ` renderComponent ` ' s ` scheduler ` option .
* /
export function scheduleTick < T > ( rootContext : RootContext ) {
2018-02-23 13:17:20 -08:00
if ( rootContext . clean == _CLEAN_PROMISE ) {
let res : null | ( ( val : null ) = > void ) ;
rootContext . clean = new Promise < null > ( ( r ) = > res = r ) ;
rootContext . scheduler ( ( ) = > {
2018-03-06 11:58:08 -08:00
tick ( rootContext . component ) ;
2018-02-23 13:17:20 -08:00
res ! ( null ) ;
rootContext . clean = _CLEAN_PROMISE ;
} ) ;
}
}
2018-03-06 11:58:08 -08:00
/ * *
* Used to perform change detection on the whole application .
*
* This is equivalent to ` detectChanges ` , but invoked on root component . Additionally , ` tick `
* executes lifecycle hooks and conditionally checks components based on their
* ` ChangeDetectionStrategy ` and dirtiness .
*
* The preferred way to trigger change detection is to call ` markDirty ` . ` markDirty ` internally
* schedules ` tick ` using a scheduler in order to coalesce multiple ` markDirty ` calls into a
* single change detection run . By default , the scheduler is ` requestAnimationFrame ` , but can
* be changed when calling ` renderComponent ` and providing the ` scheduler ` option .
* /
export function tick < T > ( component : T ) : void {
const rootView = getRootView ( component ) ;
const rootComponent = ( rootView . context as RootContext ) . component ;
const hostNode = _getComponentHostLElementNode ( rootComponent ) ;
ngDevMode && assertNotNull ( hostNode . data , 'Component host node should be attached to an LView' ) ;
renderComponentOrTemplate ( hostNode , rootView , rootComponent ) ;
}
/ * *
* Retrieve the root view from any component by walking the parent ` LView ` until
* reaching the root ` LView ` .
*
* @param component any component
* /
export function getRootView ( component : any ) : LView {
ngDevMode && assertNotNull ( component , 'component' ) ;
const lElementNode = _getComponentHostLElementNode ( component ) ;
let lView = lElementNode . view ;
while ( lView . parent ) {
lView = lView . parent ;
}
return lView ;
}
2018-02-23 13:17:20 -08:00
/ * *
* Synchronously perform change detection on a component ( and possibly its sub - components ) .
*
* This function triggers change detection in a synchronous way on a component . There should
* be very little reason to call this function directly since a preferred way to do change
* detection is to { @link markDirty } the component and wait for the scheduler to call this method
* at some future point in time . This is because a single user action often results in many
* components being invalidated and calling change detection on each component synchronously
* would be inefficient . It is better to wait until all components are marked as dirty and
* then perform single change detection across all of the components
*
* @param component The component which the change detection should be performed on .
* /
export function detectChanges < T > ( component : T ) : void {
const hostNode = _getComponentHostLElementNode ( component ) ;
ngDevMode && assertNotNull ( hostNode . data , 'Component host node should be attached to an LView' ) ;
2018-03-20 19:06:49 -07:00
const componentIndex = hostNode . tNode ! . flags >> TNodeFlags . INDX_SHIFT ;
2018-03-25 21:32:39 -07:00
const def = hostNode . view . tView . directives ! [ componentIndex ] as ComponentDef < T > ;
detectChangesInternal ( hostNode . data as LView , hostNode , def , component ) ;
2018-03-06 11:58:08 -08:00
}
2018-03-09 20:22:18 -08:00
/ * *
* Checks the change detector and its children , and throws if any changes are detected .
*
* This is used in development mode to verify that running change detection doesn ' t
* introduce other changes .
* /
export function checkNoChanges < T > ( component : T ) : void {
checkNoChangesMode = true ;
try {
detectChanges ( component ) ;
} finally {
checkNoChangesMode = false ;
}
}
/** Throws an ExpressionChangedAfterChecked error if checkNoChanges mode is on. */
function throwErrorIfNoChangesMode ( oldValue : any , currValue : any ) : never | void {
if ( checkNoChangesMode ) {
let msg =
` ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: ' ${ oldValue } '. Current value: ' ${ currValue } '. ` ;
if ( creationMode ) {
msg +=
` It seems like the view has been created after its parent and its children have been dirty checked. ` +
` Has it been created in a change detection hook ? ` ;
}
// TODO: include debug context
throw new Error ( msg ) ;
}
}
2018-03-06 11:58:08 -08:00
/** Checks the view of the component provided. Does not gate on dirty checks or execute doCheck. */
2018-03-20 19:06:49 -07:00
export function detectChangesInternal < T > (
2018-03-25 21:32:39 -07:00
hostView : LView , hostNode : LElementNode , def : ComponentDef < any > , component : T ) {
2018-03-16 16:42:13 -07:00
const oldView = enterView ( hostView , hostNode ) ;
2018-03-25 21:32:39 -07:00
const template = def . template ;
2018-03-16 16:42:13 -07:00
try {
template ( component , creationMode ) ;
refreshDynamicChildren ( ) ;
refreshDirectives ( ) ;
} finally {
leaveView ( oldView ) ;
2018-03-06 11:58:08 -08:00
}
2018-02-23 13:17:20 -08:00
}
/ * *
* Mark the component as dirty ( needing change detection ) .
*
* Marking a component dirty will schedule a change detection on this
* component at some point in the future . Marking an already dirty
* component as dirty is a noop . Only one outstanding change detection
* can be scheduled per component tree . ( Two components bootstrapped with
* separate ` renderComponent ` will have separate schedulers )
*
* When the root component is bootstrapped with ` renderComponent ` , a scheduler
* can be provided .
*
* @param component Component to mark as dirty .
* /
export function markDirty < T > ( component : T ) {
ngDevMode && assertNotNull ( component , 'component' ) ;
const lElementNode = _getComponentHostLElementNode ( component ) ;
markViewDirty ( lElementNode . view ) ;
}
2018-02-14 11:22:14 -08:00
///////////////////////////////
//// Bindings & interpolations
///////////////////////////////
2017-12-01 14:23:03 -08:00
2017-12-20 10:47:22 -08:00
export interface NO_CHANGE {
2017-12-14 15:50:01 -08:00
// This is a brand that ensures that this type can never match anything else
2017-12-20 10:47:22 -08:00
brand : 'NO_CHANGE' ;
2017-12-14 15:50:01 -08:00
}
/** A special value which designates that a value has not changed. */
export const NO_CHANGE = { } as NO_CHANGE ;
2017-12-01 14:23:03 -08:00
/ * *
2018-02-14 11:22:14 -08:00
* Initializes the binding start index . Will get inlined .
*
* This function must be called before any binding related function is called
* ( ie ` bind() ` , ` interpolationX() ` , ` pureFunctionX() ` )
* /
function initBindings() {
// `bindingIndex` is initialized when the view is first entered when not in creation mode
ngDevMode &&
assertEqual (
creationMode , true , 'should only be called in creationMode for performance reasons' ) ;
if ( currentView . bindingStartIndex == null ) {
bindingIndex = currentView . bindingStartIndex = data . length ;
}
}
/ * *
* Creates a single value binding .
*
* @param value Value to diff
* /
export function bind < T > ( value : T | NO_CHANGE ) : T | NO_CHANGE {
if ( creationMode ) {
initBindings ( ) ;
return data [ bindingIndex ++ ] = value ;
}
const changed : boolean = value !== NO_CHANGE && isDifferent ( data [ bindingIndex ] , value ) ;
if ( changed ) {
2018-03-09 20:22:18 -08:00
throwErrorIfNoChangesMode ( data [ bindingIndex ] , value ) ;
2018-02-14 11:22:14 -08:00
data [ bindingIndex ] = value ;
}
bindingIndex ++ ;
return changed ? value : NO_CHANGE ;
}
/ * *
* Create interpolation bindings with a variable number of expressions .
2017-12-01 14:23:03 -08:00
*
2018-02-16 17:20:14 -08:00
* If there are 1 to 8 expressions ` interpolation1() ` to ` interpolation8() ` should be used instead .
2018-02-16 21:20:55 -08:00
* Those are faster because there is no need to create an array of expressions and iterate over it .
2017-12-01 14:23:03 -08:00
*
2018-01-30 16:33:28 -08:00
* ` values ` :
* - has static text at even indexes ,
2018-02-16 21:20:55 -08:00
* - has evaluated expressions at odd indexes .
2018-02-14 11:22:14 -08:00
*
* Returns the concatenated string when any of the arguments changes , ` NO_CHANGE ` otherwise .
2017-12-01 14:23:03 -08:00
* /
2018-02-14 11:22:14 -08:00
export function interpolationV ( values : any [ ] ) : string | NO_CHANGE {
2018-01-30 16:33:28 -08:00
ngDevMode && assertLessThan ( 2 , values . length , 'should have at least 3 values' ) ;
ngDevMode && assertEqual ( values . length % 2 , 1 , 'should have an odd number of values' ) ;
2018-02-16 21:20:55 -08:00
let different = false ;
for ( let i = 1 ; i < values . length ; i += 2 ) {
// Check if bindings (odd indexes) have changed
bindingUpdated ( values [ i ] ) && ( different = true ) ;
2017-12-01 14:23:03 -08:00
}
2018-01-29 17:41:07 -08:00
2018-02-16 21:20:55 -08:00
if ( ! different ) {
return NO_CHANGE ;
2017-12-01 14:23:03 -08:00
}
2018-01-29 17:41:07 -08:00
2018-02-16 21:20:55 -08:00
// Build the updated content
let content = values [ 0 ] ;
for ( let i = 1 ; i < values . length ; i += 2 ) {
content += stringify ( values [ i ] ) + values [ i + 1 ] ;
}
return content ;
2017-12-01 14:23:03 -08:00
}
/ * *
2018-02-14 11:22:14 -08:00
* Creates an interpolation binding with 1 expression .
2017-12-01 14:23:03 -08:00
*
* @param prefix static value used for concatenation only .
2018-02-16 17:20:14 -08:00
* @param v0 value checked for change .
2017-12-01 14:23:03 -08:00
* @param suffix static value used for concatenation only .
* /
2018-02-16 17:20:14 -08:00
export function interpolation1 ( prefix : string , v0 : any , suffix : string ) : string | NO_CHANGE {
2018-02-16 21:20:55 -08:00
const different = bindingUpdated ( v0 ) ;
2018-02-16 17:20:14 -08:00
return different ? prefix + stringify ( v0 ) + suffix : NO_CHANGE ;
2017-12-01 14:23:03 -08:00
}
2018-02-14 11:22:14 -08:00
/** Creates an interpolation binding with 2 expressions. */
export function interpolation2 (
prefix : string , v0 : any , i0 : string , v1 : any , suffix : string ) : string | NO_CHANGE {
2018-02-16 21:20:55 -08:00
const different = bindingUpdated2 ( v0 , v1 ) ;
2018-02-16 17:20:14 -08:00
2017-12-01 14:23:03 -08:00
return different ? prefix + stringify ( v0 ) + i0 + stringify ( v1 ) + suffix : NO_CHANGE ;
}
2018-02-14 11:22:14 -08:00
/** Creates an interpolation bindings with 3 expressions. */
export function interpolation3 (
2017-12-01 14:23:03 -08:00
prefix : string , v0 : any , i0 : string , v1 : any , i1 : string , v2 : any , suffix : string ) : string |
NO_CHANGE {
2018-02-16 21:20:55 -08:00
let different = bindingUpdated2 ( v0 , v1 ) ;
different = bindingUpdated ( v2 ) || different ;
2018-02-16 17:20:14 -08:00
2017-12-01 14:23:03 -08:00
return different ? prefix + stringify ( v0 ) + i0 + stringify ( v1 ) + i1 + stringify ( v2 ) + suffix :
NO_CHANGE ;
}
2018-02-14 11:22:14 -08:00
/** Create an interpolation binding with 4 expressions. */
export function interpolation4 (
2017-12-01 14:23:03 -08:00
prefix : string , v0 : any , i0 : string , v1 : any , i1 : string , v2 : any , i2 : string , v3 : any ,
suffix : string ) : string | NO_CHANGE {
2018-02-16 21:20:55 -08:00
const different = bindingUpdated4 ( v0 , v1 , v2 , v3 ) ;
2018-02-16 17:20:14 -08:00
2017-12-01 14:23:03 -08:00
return different ?
prefix + stringify ( v0 ) + i0 + stringify ( v1 ) + i1 + stringify ( v2 ) + i2 + stringify ( v3 ) +
suffix :
NO_CHANGE ;
}
2018-02-14 11:22:14 -08:00
/** Creates an interpolation binding with 5 expressions. */
export function interpolation5 (
2017-12-01 14:23:03 -08:00
prefix : string , v0 : any , i0 : string , v1 : any , i1 : string , v2 : any , i2 : string , v3 : any ,
i3 : string , v4 : any , suffix : string ) : string | NO_CHANGE {
2018-02-16 21:20:55 -08:00
let different = bindingUpdated4 ( v0 , v1 , v2 , v3 ) ;
different = bindingUpdated ( v4 ) || different ;
2017-12-01 14:23:03 -08:00
return different ?
prefix + stringify ( v0 ) + i0 + stringify ( v1 ) + i1 + stringify ( v2 ) + i2 + stringify ( v3 ) + i3 +
stringify ( v4 ) + suffix :
NO_CHANGE ;
}
2018-02-14 11:22:14 -08:00
/** Creates an interpolation binding with 6 expressions. */
export function interpolation6 (
2017-12-01 14:23:03 -08:00
prefix : string , v0 : any , i0 : string , v1 : any , i1 : string , v2 : any , i2 : string , v3 : any ,
i3 : string , v4 : any , i4 : string , v5 : any , suffix : string ) : string | NO_CHANGE {
2018-02-16 21:20:55 -08:00
let different = bindingUpdated4 ( v0 , v1 , v2 , v3 ) ;
different = bindingUpdated2 ( v4 , v5 ) || different ;
2017-12-01 14:23:03 -08:00
return different ?
prefix + stringify ( v0 ) + i0 + stringify ( v1 ) + i1 + stringify ( v2 ) + i2 + stringify ( v3 ) + i3 +
stringify ( v4 ) + i4 + stringify ( v5 ) + suffix :
NO_CHANGE ;
}
2018-02-14 11:22:14 -08:00
/** Creates an interpolation binding with 7 expressions. */
export function interpolation7 (
2017-12-01 14:23:03 -08:00
prefix : string , v0 : any , i0 : string , v1 : any , i1 : string , v2 : any , i2 : string , v3 : any ,
i3 : string , v4 : any , i4 : string , v5 : any , i5 : string , v6 : any , suffix : string ) : string |
NO_CHANGE {
2018-02-16 21:20:55 -08:00
let different = bindingUpdated4 ( v0 , v1 , v2 , v3 ) ;
different = bindingUpdated2 ( v4 , v5 ) || different ;
different = bindingUpdated ( v6 ) || different ;
2018-02-16 17:20:14 -08:00
2017-12-01 14:23:03 -08:00
return different ?
prefix + stringify ( v0 ) + i0 + stringify ( v1 ) + i1 + stringify ( v2 ) + i2 + stringify ( v3 ) + i3 +
stringify ( v4 ) + i4 + stringify ( v5 ) + i5 + stringify ( v6 ) + suffix :
NO_CHANGE ;
}
2018-02-14 11:22:14 -08:00
/** Creates an interpolation binding with 8 expressions. */
export function interpolation8 (
2017-12-01 14:23:03 -08:00
prefix : string , v0 : any , i0 : string , v1 : any , i1 : string , v2 : any , i2 : string , v3 : any ,
i3 : string , v4 : any , i4 : string , v5 : any , i5 : string , v6 : any , i6 : string , v7 : any ,
suffix : string ) : string | NO_CHANGE {
2018-02-16 21:20:55 -08:00
let different = bindingUpdated4 ( v0 , v1 , v2 , v3 ) ;
different = bindingUpdated4 ( v4 , v5 , v6 , v7 ) || different ;
2018-02-16 17:20:14 -08:00
2017-12-01 14:23:03 -08:00
return different ?
prefix + stringify ( v0 ) + i0 + stringify ( v1 ) + i1 + stringify ( v2 ) + i2 + stringify ( v3 ) + i3 +
stringify ( v4 ) + i4 + stringify ( v5 ) + i5 + stringify ( v6 ) + i6 + stringify ( v7 ) + suffix :
NO_CHANGE ;
}
2018-02-16 16:58:07 -08:00
/** Store a value in the `data` at a given `index`. */
export function store < T > ( index : number , value : T ) : void {
// We don't store any static data for local variables, so the first time
// we see the template, we should store as null to avoid a sparse array
if ( index >= tData . length ) {
tData [ index ] = null ;
}
data [ index ] = value ;
2017-12-01 14:23:03 -08:00
}
2018-02-16 16:58:07 -08:00
/** Retrieves a value from the `data`. */
export function load < T > ( index : number ) : T {
2018-03-21 15:10:34 -07:00
ngDevMode && assertDataInRange ( index ) ;
2018-02-16 16:58:07 -08:00
return data [ index ] ;
2017-12-01 14:23:03 -08:00
}
2018-03-21 15:10:34 -07:00
/** Retrieves a value from the `directives` array. */
export function loadDirective < T > ( index : number ) : T {
ngDevMode && assertNotNull ( directives , 'Directives array should be defined if reading a dir.' ) ;
ngDevMode && assertDataInRange ( index , directives ! ) ;
return directives ! [ index ] ;
}
2018-02-14 13:37:54 -08:00
/** Gets the current binding value and increments the binding index. */
export function consumeBinding ( ) : any {
ngDevMode && assertDataInRange ( bindingIndex ) ;
ngDevMode &&
assertNotEqual ( data [ bindingIndex ] , NO_CHANGE , 'Stored value should never be NO_CHANGE.' ) ;
return data [ bindingIndex ++ ] ;
}
/** Updates binding if changed, then returns whether it was updated. */
export function bindingUpdated ( value : any ) : boolean {
ngDevMode && assertNotEqual ( value , NO_CHANGE , 'Incoming value should never be NO_CHANGE.' ) ;
2018-03-09 20:22:18 -08:00
if ( creationMode ) {
initBindings ( ) ;
} else if ( isDifferent ( data [ bindingIndex ] , value ) ) {
throwErrorIfNoChangesMode ( data [ bindingIndex ] , value ) ;
2018-02-14 13:37:54 -08:00
} else {
bindingIndex ++ ;
return false ;
}
2018-03-09 20:22:18 -08:00
data [ bindingIndex ++ ] = value ;
return true ;
2018-02-14 13:37:54 -08:00
}
/** Updates binding if changed, then returns the latest value. */
export function checkAndUpdateBinding ( value : any ) : any {
bindingUpdated ( value ) ;
return value ;
}
/** Updates 2 bindings if changed, then returns whether either was updated. */
export function bindingUpdated2 ( exp1 : any , exp2 : any ) : boolean {
const different = bindingUpdated ( exp1 ) ;
return bindingUpdated ( exp2 ) || different ;
}
/** Updates 4 bindings if changed, then returns whether any was updated. */
export function bindingUpdated4 ( exp1 : any , exp2 : any , exp3 : any , exp4 : any ) : boolean {
const different = bindingUpdated2 ( exp1 , exp2 ) ;
return bindingUpdated2 ( exp3 , exp4 ) || different ;
}
2018-02-16 16:23:27 +01:00
export function getTView ( ) : TView {
return currentView . tView ;
}
2018-01-30 15:37:01 -08:00
export function getDirectiveInstance < T > ( instanceOrArray : T | [ T ] ) : T {
2018-03-21 15:10:34 -07:00
// Directives with content queries store an array in directives[directiveIndex]
2018-01-30 15:37:01 -08:00
// with the instance as the first index
return Array . isArray ( instanceOrArray ) ? instanceOrArray [ 0 ] : instanceOrArray ;
}
2018-01-17 09:45:40 -08:00
export function assertPreviousIsParent() {
2018-02-12 22:46:15 -08:00
assertEqual ( isParent , true , 'previousOrParentNode should be a parent' ) ;
2017-12-01 14:23:03 -08:00
}
function assertHasParent() {
2018-02-12 22:46:15 -08:00
assertNotNull ( previousOrParentNode . parent , 'previousOrParentNode should have a parent' ) ;
2017-12-01 14:23:03 -08:00
}
2017-12-08 11:48:54 -08:00
function assertDataInRange ( index : number , arr? : any [ ] ) {
if ( arr == null ) arr = data ;
2018-02-12 22:46:15 -08:00
assertLessThan ( index , arr ? arr.length : 0 , 'index expected to be a valid data index' ) ;
2017-12-01 14:23:03 -08:00
}
2018-01-18 13:27:01 -08:00
2018-03-21 15:10:34 -07:00
function assertDataNext ( index : number , arr? : any [ ] ) {
if ( arr == null ) arr = data ;
2018-03-27 11:01:52 -07:00
assertEqual (
arr . length , index , ` index ${ index } expected to be at the end of arr (length ${ arr . length } ) ` ) ;
2018-01-17 17:55:55 +01:00
}
2018-02-23 13:17:20 -08:00
export function _getComponentHostLElementNode < T > ( component : T ) : LElementNode {
ngDevMode && assertNotNull ( component , 'expecting component got null' ) ;
const lElementNode = ( component as any ) [ NG_HOST_SYMBOL ] as LElementNode ;
ngDevMode && assertNotNull ( component , 'object is not a component' ) ;
return lElementNode ;
}
export const CLEAN_PROMISE = _CLEAN_PROMISE ;
2018-03-25 21:32:39 -07:00
export const ROOT_DIRECTIVE_INDICES = _ROOT_DIRECTIVE_INDICES ;