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-02-28 15:00:58 +01:00
import { CssSelector , 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-01-10 18:19:16 -08:00
import { LContainerNode , LElementNode , LNode , LNodeFlags , 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-01-31 15:50:24 +01:00
import { matchingSelectorIndex } from './node_selector_matcher' ;
2018-01-22 15:27:21 -08:00
import { ComponentDef , ComponentTemplate , ComponentType , DirectiveDef , DirectiveType } 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-01-25 20:41:57 -08:00
import { executeHooks , executeContentHooks , 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-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
* in the data array . Each directive ' s definition is stored here at the same index
* as its directive instance 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
/ * *
* 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-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 ;
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 ;
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 (
currentView . data , currentView . tView . viewHooks , currentView . tView . viewCheckHooks ,
creationMode ) ;
}
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-23 10:57:48 -08:00
currentView . tView . firstTemplatePass = false ;
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-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-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 (
2017-12-01 14:23:03 -08:00
index : number | null , type : LNodeFlags . 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-01-08 20:17:13 -08:00
index : null , type : LNodeFlags . View , native : null , lView : LView ) : LViewNode ;
2017-12-08 11:48:54 -08:00
export function createLNode (
2018-01-26 11:36:31 +01:00
index : number , type : LNodeFlags . 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 (
2017-12-01 14:23:03 -08:00
index : number , type : LNodeFlags . 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-01-26 11:36:31 +01:00
index : number | null , type : LNodeFlags , native : RText | RElement | null | undefined ,
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 = {
2017-12-01 14:23:03 -08:00
flags : type ,
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
} ;
if ( ( type & LNodeFlags . ViewOrElement ) === LNodeFlags . ViewOrElement && isState ) {
// 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 ||
( previousOrParentNode . flags & LNodeFlags . TYPE_MASK ) === LNodeFlags . 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
/ * *
*
* @param host Existing node to render into .
* @param template Template function with the instructions .
* @param context to pass into the template .
* /
2017-12-11 16:30:46 +01:00
export function renderTemplate < T > (
hostNode : RElement , template : ComponentTemplate < T > , context : T ,
2018-01-08 20:17:13 -08:00
providedRendererFactory : RendererFactory3 , host : LElementNode | 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 ;
host = createLNode (
null , LNodeFlags . Element , hostNode ,
2018-01-10 18:19:16 -08:00
createLView (
2018-02-03 20:34:30 -08:00
- 1 , providedRendererFactory . createRenderer ( null , null ) , getOrCreateTView ( template ) ,
2018-02-23 13:17:20 -08:00
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-02-23 13:17:20 -08:00
const view =
createLView ( - 1 , renderer , createTView ( ) , template , context , LViewFlags . CheckAlways ) ;
2018-01-17 10:09:05 -08:00
viewNode = createLNode ( null , LNodeFlags . View , null , view ) ;
cm = true ;
}
enterView ( viewNode . data , viewNode ) ;
template ( context , cm ) ;
} finally {
refreshDynamicChildren ( ) ;
leaveView ( currentView ! . parent ! ) ;
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 ) ;
} else {
// Element was stored at 0 and directive was stored at 1 in renderComponent
2018-01-22 19:52:06 -08:00
// so to refresh the component, refresh() needs to be called with (1, 0)
2018-02-23 11:02:38 +01:00
directiveRefresh ( 1 , 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-01-08 21:57:50 -08:00
* @param nameOrComponentType Name of the DOM Node or ` ComponentType ` to create .
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 directiveTypes A set of directives declared on this element .
* @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-01-08 21:57:50 -08:00
index : number , nameOrComponentType? : string | ComponentType < any > , attrs? : string [ ] | null ,
directiveTypes? : DirectiveType < any > [ ] | 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-01-08 21:57:50 -08:00
if ( nameOrComponentType == null ) {
2017-12-01 14:23:03 -08:00
// native node retrieval - used for exporting elements as tpl local variables (<div #foo>)
2017-12-08 11:48:54 -08:00
const node = data [ index ] ! ;
2018-01-08 20:17:13 -08:00
native = node && ( node as LElementNode ) . native ;
2017-12-01 14:23:03 -08:00
} else {
2018-02-12 22:46:15 -08:00
ngDevMode &&
assertNull ( currentView . bindingStartIndex , 'elements should be created before any bindings' ) ;
2018-01-08 21:57:50 -08:00
const isHostElement = typeof nameOrComponentType !== 'string' ;
// MEGAMORPHIC: `ngComponentDef` is a megamorphic property access here.
// This is OK, since we will refactor this code and store the result in `TView.data`
// which means that we will be reading this value only once. We are trading clean/simple
// template
// code for slight startup(first run) performance. (No impact on subsequent runs)
// TODO(misko): refactor this to store the `ComponentDef` in `TView.data`.
const hostComponentDef =
isHostElement ? ( nameOrComponentType as ComponentType < any > ) . ngComponentDef : null ;
const name = isHostElement ? hostComponentDef ! . tag : nameOrComponentType as string ;
2017-12-01 14:23:03 -08:00
if ( name === null ) {
// TODO: future support for nameless components.
throw 'for now name is required' ;
} else {
native = renderer . createElement ( name ) ;
2017-12-11 14:08:52 -08:00
2018-01-08 20:17:13 -08:00
let componentView : LView | null = null ;
2017-12-11 14:08:52 -08:00
if ( isHostElement ) {
2018-01-10 18:19:16 -08:00
const tView = getOrCreateTView ( hostComponentDef ! . template ) ;
2018-02-23 13:17:20 -08:00
const hostView = createLView (
2018-02-03 20:34:30 -08:00
- 1 , rendererFactory . createRenderer ( native , hostComponentDef ! . rendererType ) , tView ,
2018-02-23 13:17:20 -08:00
null , null , hostComponentDef ! . onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways ) ;
componentView = addToViewTree ( hostView ) ;
2017-12-11 14:08:52 -08:00
}
2017-12-01 14:23:03 -08: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.
2017-12-11 14:08:52 -08:00
node = createLNode ( index , LNodeFlags . Element , native , componentView ) ;
2017-12-01 14:23:03 -08:00
2018-01-08 21:57:50 -08:00
// TODO(misko): implement code which caches the local reference resolution
const queryName : string | null = hack_findQueryName ( hostComponentDef , localRefs , '' ) ;
2018-01-08 20:17:13 -08:00
if ( node . tNode == null ) {
2017-12-11 14:08:52 -08:00
ngDevMode && assertDataInRange ( index - 1 ) ;
2018-01-10 18:19:16 -08:00
node . tNode = tData [ index ] =
2018-01-08 21:57:50 -08:00
createTNode ( name , attrs || null , null , hostComponentDef ? null : queryName ) ;
2017-12-01 14:23:03 -08:00
}
if ( attrs ) setUpAttributes ( native , attrs ) ;
appendChild ( node . parent ! , native , currentView ) ;
2018-01-08 21:57:50 -08:00
if ( hostComponentDef ) {
// TODO(mhevery): This assumes that the directives come in correct order, which
// is not guaranteed. Must be refactored to take it into account.
2018-02-26 16:58:15 -08:00
const instance = hostComponentDef . n ( ) ;
directiveCreate ( ++ index , instance , hostComponentDef , queryName ) ;
initChangeDetectorIfExisting ( node . nodeInjector , instance ) ;
2018-01-08 21:57:50 -08:00
}
hack_declareDirectives ( index , directiveTypes , localRefs ) ;
2017-12-01 14:23:03 -08:00
}
}
return native ;
}
2018-02-26 16:58:15 -08:00
/** Sets the context for a ChangeDetectorRef to the given instance. */
export function initChangeDetectorIfExisting ( injector : LInjector | null , instance : any ) : void {
if ( injector && injector . changeDetectorRef != null ) {
( injector . changeDetectorRef as ViewRef < any > ) . _setComponentContext ( instance ) ;
}
}
2018-01-08 21:57:50 -08:00
/ * *
* This function instantiates a directive with a correct queryName . It is a hack since we should
* compute the query value only once and store it with the template ( rather than on each invocation )
* /
function hack_declareDirectives (
index : number , directiveTypes : DirectiveType < any > [ ] | null | undefined ,
localRefs : string [ ] | null | undefined , ) {
if ( directiveTypes ) {
// TODO(mhevery): This assumes that the directives come in correct order, which
// is not guaranteed. Must be refactored to take it into account.
for ( let i = 0 ; i < directiveTypes . length ; i ++ ) {
// MEGAMORPHIC: `ngDirectiveDef` is a megamorphic property access here.
// This is OK, since we will refactor this code and store the result in `TView.data`
// which means that we will be reading this value only once. We are trading clean/simple
// template
// code for slight startup(first run) performance. (No impact on subsequent runs)
// TODO(misko): refactor this to store the `DirectiveDef` in `TView.data`.
2018-01-09 16:43:12 -08:00
const directiveType = directiveTypes [ i ] ;
const directiveDef = directiveType . ngDirectiveDef ;
2018-01-08 21:57:50 -08:00
directiveCreate (
++ index , directiveDef . n ( ) , directiveDef , hack_findQueryName ( directiveDef , localRefs ) ) ;
}
}
}
/ * *
* This function returns the queryName for a directive . It is a hack since we should
* compute the query value only once and store it with the template ( rather than on each invocation )
* /
function hack_findQueryName (
directiveDef : DirectiveDef < any > | null , localRefs : string [ ] | null | undefined ,
defaultExport? : string , ) : string | null {
const exportAs = directiveDef && directiveDef . exportAs || defaultExport ;
if ( exportAs != null && localRefs ) {
for ( let i = 0 ; i < localRefs . length ; i = i + 2 ) {
const local = localRefs [ i ] ;
const toExportAs = localRefs [ i | 1 ] ;
if ( toExportAs === exportAs || toExportAs === defaultExport ) {
return local ;
}
}
}
return null ;
}
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-01-10 18:19:16 -08:00
* @returns TView
2017-12-11 14:08:52 -08:00
* /
2018-01-10 18:19:16 -08:00
function getOrCreateTView ( template : ComponentTemplate < any > ) : TView {
2018-01-22 17:43:52 -08:00
return template . ngPrivateData || ( template . ngPrivateData = createTView ( ) as never ) ;
}
2018-01-23 10:57:48 -08:00
/** Creates a TView instance */
2018-01-22 17:43:52 -08:00
export function createTView ( ) : TView {
2018-01-23 10:57:48 -08:00
return {
data : [ ] ,
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-02-13 10:24:41 -08:00
destroyHooks : null
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-02-26 16:58:15 -08:00
export function hostElement ( rNode : RElement | null , def : ComponentDef < any > ) : LElementNode {
2018-01-03 10:45:09 +01:00
resetApplicationState ( ) ;
2018-02-26 16:58:15 -08:00
return createLNode (
2018-02-23 13:17:20 -08:00
0 , LNodeFlags . Element , rNode , createLView (
- 1 , renderer , getOrCreateTView ( def . template ) , null , null ,
def . onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways ) ) ;
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-02-07 22:19:24 -08:00
tNode . outputs = generatePropertyAliases ( node . 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 ) {
2017-12-11 14:08:52 -08:00
ngDevMode && assertDataInRange ( outputs [ i ] as number ) ;
2017-12-14 15:03:46 -08:00
const subscription = data [ 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 ! ;
}
ngDevMode && assertNodeType ( previousOrParentNode , LNodeFlags . Element ) ;
2018-01-29 14:51:37 +01:00
const queries = previousOrParentNode . queries ;
queries && queries . addNode ( previousOrParentNode ) ;
2018-01-22 17:43:52 -08:00
queueLifecycleHooks ( previousOrParentNode . 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-01-08 20:17:13 -08:00
if ( tNode . inputs === undefined ) {
2017-12-01 14:23:03 -08:00
// mark inputs as checked
2018-02-07 22:19:24 -08:00
tNode . inputs = generatePropertyAliases ( node . flags , BindingDirection . Input ) ;
2017-12-01 14:23:03 -08:00
}
2018-01-08 20:17:13 -08:00
const inputData = 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-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-01-10 18:19:16 -08:00
tagName : string | null , attrs : string [ ] | null , data : TContainer | null ,
2018-01-08 20:17:13 -08:00
localName : string | null ) : TNode {
2017-12-08 11:48:54 -08:00
return {
2017-12-12 14:42:28 +01:00
tagName : tagName ,
attrs : attrs ,
2017-12-19 16:51:42 +01:00
localNames : localName ? [ localName , - 1 ] : 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 ) {
2017-12-11 14:08:52 -08:00
ngDevMode && assertDataInRange ( inputs [ i ] as number ) ;
data [ 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-02-07 22:19:24 -08:00
function generatePropertyAliases ( lNodeFlags : number , direction : BindingDirection ) : PropertyAliases |
null {
const size = ( lNodeFlags & LNodeFlags . SIZE_MASK ) >> LNodeFlags . SIZE_SHIFT ;
let propStore : PropertyAliases | null = null ;
if ( size > 0 ) {
const start = lNodeFlags >> LNodeFlags . INDX_SHIFT ;
const isInput = direction === BindingDirection . Input ;
for ( let i = start , ii = start + size ; i < ii ; i ++ ) {
const directiveDef = tData ! [ i ] as DirectiveDef < any > ;
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
}
/ * *
* Add or remove a class in a classList .
*
* 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 .
* /
export function elementClass < T > ( index : number , className : string , value : T | NO_CHANGE ) : void {
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
}
}
}
/ * *
* 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
* /
export function elementStyle < T > (
2018-03-01 17:14:01 -08:00
index : number , styleName : string , value : T | NO_CHANGE , suffix? : string ) : void ;
export function elementStyle < T > (
index : number , styleName : string , value : T | NO_CHANGE , sanitizer? : Sanitizer ) : void ;
export function elementStyle < T > (
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-01-08 20:17:13 -08:00
const lElement = data [ index ] as LElementNode ;
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 ) :
2017-12-01 14:23:03 -08:00
lElement . native . style . removeProperty ( styleName ) ;
} 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 ) :
lElement . native . style . setProperty ( styleName , strValue ) ;
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 ;
2017-12-08 11:48:54 -08:00
const node = createLNode ( index , LNodeFlags . 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 .
*
* @param index Each directive in a ` View ` will have a unique index . Directives can
* be created or retrieved out of order .
* @param directive The directive instance .
* @param directiveDef DirectiveDef object which contains information about the template .
2018-01-08 21:57:50 -08:00
* @param queryName Name under which the 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 > (
index : number , directive : T , directiveDef : DirectiveDef < T > , queryName? : string | null ) : T {
2017-12-01 14:23:03 -08:00
let instance ;
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 ( ) ;
let flags = previousOrParentNode ! . flags ;
let size = flags & LNodeFlags . SIZE_MASK ;
if ( size === 0 ) {
flags = ( index << LNodeFlags . INDX_SHIFT ) | LNodeFlags . SIZE_SKIP | flags & LNodeFlags . TYPE_MASK ;
2017-12-01 14:23:03 -08:00
} else {
2018-01-08 21:57:50 -08:00
flags += LNodeFlags . SIZE_SKIP ;
}
previousOrParentNode ! . flags = flags ;
2017-12-01 14:23:03 -08:00
2018-01-08 21:57:50 -08:00
ngDevMode && assertDataInRange ( index - 1 ) ;
Object . defineProperty (
directive , NG_HOST_SYMBOL , { enumerable : false , value : previousOrParentNode } ) ;
2017-12-19 16:51:42 +01:00
2018-01-08 21:57:50 -08:00
data [ index ] = instance = directive ;
2017-12-11 14:08:52 -08:00
2018-01-10 18:19:16 -08:00
if ( index >= tData . length ) {
tData [ index ] = directiveDef ! ;
2018-01-08 21:57:50 -08:00
if ( queryName ) {
2018-01-10 18:19:16 -08:00
ngDevMode && assertNotNull ( previousOrParentNode . tNode , 'previousOrParentNode.tNode' ) ;
const tNode = previousOrParentNode ! . tNode ! ;
( tNode . localNames || ( tNode . localNames = [ ] ) ) . push ( queryName , index ) ;
2017-12-01 14:23:03 -08:00
}
2018-01-08 21:57:50 -08:00
}
2017-12-11 14:08:52 -08:00
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-02-16 12:09:47 -08:00
if ( directiveDef ! . attributes != null &&
( previousOrParentNode . flags & LNodeFlags . TYPE_MASK ) == LNodeFlags . Element ) {
setUpAttributes (
( previousOrParentNode as LElementNode ) . native , directiveDef ! . attributes as string [ ] ) ;
}
2018-01-10 18:19:16 -08:00
const tNode : TNode | null = previousOrParentNode . tNode ! ;
if ( tNode && tNode . attrs ) {
setInputsFromAttrs < T > ( instance , directiveDef ! . inputs , tNode ) ;
2018-01-08 21:57:50 -08:00
}
2018-01-22 17:43:52 -08:00
// Init hooks are queued now so ngOnInit is called in host components before
// any projected components.
2018-01-23 18:39:09 -08:00
queueInitHooks ( index , directiveDef . onInit , directiveDef . doCheck , currentView . tView ) ;
2018-01-22 17:43:52 -08:00
2017-12-01 14:23:03 -08:00
return instance ;
}
/ * *
* Sets initial input properties on directive instances from attribute data
*
* @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-01-08 20:17:13 -08:00
function setInputsFromAttrs < T > ( instance : T , inputs : { [ key : string ] : string } , tNode : TNode ) : void {
2017-12-01 14:23:03 -08:00
const directiveIndex =
( ( previousOrParentNode . flags & LNodeFlags . SIZE_MASK ) >> LNodeFlags . SIZE_SHIFT ) - 1 ;
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-01-08 21:57:50 -08:00
index : number , directiveTypes? : DirectiveType < any > [ ] , 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-01-26 11:36:31 +01:00
const node = createLNode ( index , LNodeFlags . Container , undefined , lContainer ) ;
2017-12-01 14:23:03 -08:00
2018-01-08 20:17:13 -08:00
if ( node . tNode == null ) {
2018-01-08 21:57:50 -08:00
// TODO(misko): implement queryName caching
const queryName : string | null = hack_findQueryName ( null , localRefs , '' ) ;
2018-01-10 18:19:16 -08:00
node . tNode = tData [ index ] = createTNode ( tagName || null , attrs || null , [ ] , queryName || 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-01-08 21:57:50 -08:00
hack_declareDirectives ( index , directiveTypes , localRefs ) ;
2017-12-01 14:23:03 -08:00
2018-01-09 13:32:24 -08:00
isParent = false ;
2017-12-01 14:23:03 -08:00
ngDevMode && assertNodeType ( previousOrParentNode , LNodeFlags . 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 ;
2017-12-01 14:23:03 -08:00
ngDevMode && assertNodeType ( previousOrParentNode , LNodeFlags . Container ) ;
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 {
ngDevMode && assertNodeType ( previousOrParentNode , LNodeFlags . View ) ;
ngDevMode && assertHasParent ( ) ;
previousOrParentNode = previousOrParentNode . parent ! ;
}
ngDevMode && assertNodeType ( previousOrParentNode , LNodeFlags . 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 ;
2017-12-01 14:23:03 -08:00
ngDevMode && assertNodeType ( container , LNodeFlags . Container ) ;
const nextIndex = container . data . nextIndex ;
2017-12-13 19:34:46 -08:00
while ( nextIndex < container . data . views . length ) {
2017-12-01 14:23:03 -08:00
// remove extra view.
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 ) ;
}
}
}
}
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 ;
2017-12-01 14:23:03 -08:00
ngDevMode && assertNodeType ( container , LNodeFlags . Container ) ;
2018-01-08 20:17:13 -08:00
const lContainer = container . data ;
const views = lContainer . views ;
2017-12-01 14:23:03 -08:00
2018-01-08 20:17:13 -08:00
const existingView : LViewNode | false =
! creationMode && lContainer . nextIndex < views . length && views [ lContainer . nextIndex ] ;
let viewUpdateMode = existingView && viewBlockId === ( existingView as LViewNode ) . data . id ;
2017-12-01 14:23:03 -08:00
if ( viewUpdateMode ) {
2018-01-08 20:17:13 -08:00
previousOrParentNode = views [ lContainer . nextIndex ++ ] ;
2017-12-01 14:23:03 -08:00
ngDevMode && assertNodeType ( previousOrParentNode , LNodeFlags . View ) ;
isParent = true ;
2018-01-08 20:17:13 -08:00
enterView ( ( existingView as LViewNode ) . data , previousOrParentNode 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-01-08 20:17:13 -08:00
enterView ( newView , createLNode ( null , LNodeFlags . View , null , newView ) ) ;
lContainer . nextIndex ++ ;
2017-12-01 14:23:03 -08:00
}
2017-12-08 11:48:54 -08:00
2017-12-01 14:23:03 -08:00
return ! viewUpdateMode ;
}
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 {
2017-12-11 14:08:52 -08:00
ngDevMode && assertNodeType ( parent , LNodeFlags . Container ) ;
2018-01-10 18:19:16 -08:00
const tContainer = ( parent ! . tNode as TContainerNode ) . data ;
if ( viewIndex >= tContainer . length || tContainer [ viewIndex ] == null ) {
2018-01-22 17:43:52 -08:00
tContainer [ viewIndex ] = createTView ( ) ;
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 {
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 ;
const container = previousOrParentNode . parent as LContainerNode ;
2018-01-17 10:09:05 -08:00
if ( container ) {
ngDevMode && assertNodeType ( viewNode , LNodeFlags . View ) ;
ngDevMode && assertNodeType ( container , LNodeFlags . Container ) ;
const containerState = container . data ;
const previousView = containerState . nextIndex <= containerState . views . length ?
containerState . views [ containerState . nextIndex - 1 ] as LViewNode :
null ;
const viewIdChanged = previousView == null ? true : previousView . data . id !== viewNode . data . id ;
if ( viewIdChanged ) {
insertView ( container , viewNode , containerState . nextIndex - 1 ) ;
}
2017-12-01 14:23:03 -08:00
}
leaveView ( currentView ! . parent ! ) ;
ngDevMode && assertEqual ( isParent , false , 'isParent' ) ;
ngDevMode && assertNodeType ( previousOrParentNode , LNodeFlags . View ) ;
}
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-02-23 11:02:38 +01:00
* Refreshes the directive , triggering init and content hooks .
2017-12-14 16:26:28 -08:00
*
2018-02-23 11:02:38 +01:00
* When it is a component , it also enters the component ' s view and processes it to update bindings ,
* queries , etc .
2017-12-14 16:26:28 -08:00
*
* @param directiveIndex
* @param elementIndex
* /
2018-02-23 11:02:38 +01:00
export function directiveRefresh < T > ( directiveIndex : number , elementIndex : number ) : void {
2018-03-09 20:22:18 -08:00
if ( ! checkNoChangesMode ) {
executeInitHooks ( currentView , currentView . tView , creationMode ) ;
executeContentHooks ( currentView , currentView . tView , creationMode ) ;
}
2018-01-22 19:52:06 -08:00
const template = ( tData [ directiveIndex ] as ComponentDef < T > ) . template ;
if ( template != null ) {
ngDevMode && assertDataInRange ( elementIndex ) ;
const element = data ! [ elementIndex ] as LElementNode ;
ngDevMode && assertNodeType ( element , LNodeFlags . Element ) ;
2018-02-12 22:46:15 -08:00
ngDevMode &&
assertNotNull ( element . data , ` Component's host node should have an LView attached. ` ) ;
2018-01-22 19:52:06 -08:00
const hostView = element . data ! ;
2018-02-23 13:17:20 -08:00
2018-03-08 16:55:47 -08:00
// Only attached CheckAlways components or attached, dirty OnPush components should be checked
if ( viewAttached ( hostView ) && hostView . flags & ( LViewFlags . CheckAlways | LViewFlags . Dirty ) ) {
2018-02-23 13:17:20 -08:00
ngDevMode && assertDataInRange ( directiveIndex ) ;
2018-03-06 11:58:08 -08:00
detectChangesInternal ( hostView , element , getDirectiveInstance < T > ( data [ directiveIndex ] ) ) ;
2018-01-22 19:52:06 -08:00
}
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 (
index : number , selectors? : CssSelector [ ] , 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-01-26 11:36:31 +01:00
const node = createLNode ( nodeIndex , LNodeFlags . Projection , null , { head : null , tail : null } ) ;
2018-01-31 15:50:24 +01:00
if ( node . tNode == null ) {
node . tNode = createTNode ( null , attrs || null , null , null ) ;
}
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 ] ;
if ( ( nodeToProject . flags & LNodeFlags . TYPE_MASK ) === LNodeFlags . 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 ;
2017-12-01 14:23:03 -08:00
while ( ( viewRootLNode . flags & LNodeFlags . TYPE_MASK ) === LNodeFlags . 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
}
ngDevMode && assertNodeType ( viewRootLNode , LNodeFlags . Element ) ;
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-06 11:58:08 -08:00
detectChangesInternal ( hostNode . data as LView , hostNode , component ) ;
}
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. */
function detectChangesInternal < T > ( hostView : LView , hostNode : LElementNode , component : T ) {
const componentIndex = hostNode . flags >> LNodeFlags . INDX_SHIFT ;
const template = ( hostNode . view . tView . data [ componentIndex ] as ComponentDef < T > ) . template ;
const oldView = enterView ( hostView , hostNode ) ;
if ( template != null ) {
try {
template ( component , creationMode ) ;
} finally {
refreshDynamicChildren ( ) ;
leaveView ( oldView ) ;
}
}
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 {
ngDevMode && assertDataInRange ( index , data ) ;
return data [ index ] ;
2017-12-01 14:23:03 -08:00
}
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 {
// Directives with content queries store an array in data[directiveIndex]
// 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
function assertDataNext ( index : number ) {
2018-02-12 22:46:15 -08:00
assertEqual ( data . length , index , 'index expected to be at the end of data' ) ;
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 ;