Compare commits

...

10 Commits

39 changed files with 2012 additions and 503 deletions

View File

@ -18,14 +18,14 @@
"hello_world__render3__closure": { "hello_world__render3__closure": {
"master": { "master": {
"uncompressed": { "uncompressed": {
"bundle": 7065 "bundle": 7674
} }
} }
}, },
"hello_world__render3__rollup": { "hello_world__render3__rollup": {
"master": { "master": {
"uncompressed": { "uncompressed": {
"bundle": 56550 "bundle": 58662
} }
} }
} }

View File

@ -14,6 +14,7 @@ export class HelloWorld {
/** @nocollapse */ /** @nocollapse */
static ngComponentDef: ComponentDef<HelloWorld> = defineComponent({ static ngComponentDef: ComponentDef<HelloWorld> = defineComponent({
type: HelloWorld,
tag: 'hello-world', tag: 'hello-world',
template: function (ctx: HelloWorld, cm: boolean) { template: function (ctx: HelloWorld, cm: boolean) {
if (cm) { if (cm) {

View File

@ -14,6 +14,7 @@ export class HelloWorld {
/** @nocollapse */ /** @nocollapse */
static ngComponentDef: ComponentDef<HelloWorld> = defineComponent({ static ngComponentDef: ComponentDef<HelloWorld> = defineComponent({
type: HelloWorld,
tag: 'hello-world', tag: 'hello-world',
template: function (ctx: HelloWorld, cm: boolean) { template: function (ctx: HelloWorld, cm: boolean) {
if (cm) { if (cm) {

View File

@ -16,6 +16,7 @@ export class LargeTableComponent {
/** @nocollapse */ /** @nocollapse */
static ngComponentDef: ComponentDef<LargeTableComponent> = defineComponent({ static ngComponentDef: ComponentDef<LargeTableComponent> = defineComponent({
type: LargeTableComponent,
tag: 'largetable', tag: 'largetable',
template: function(ctx: LargeTableComponent, cm: boolean) { template: function(ctx: LargeTableComponent, cm: boolean) {
if (cm) { if (cm) {

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {ɵC as C, ɵE as E, ɵT as T, ɵV as V, ɵb as b, ɵb1 as b1, ɵcR as cR, ɵcr as cr, ɵdefineComponent as defineComponent, ɵdetectChanges as _detectChanges, ɵe as e, ɵp as p, ɵs as s, ɵt as t, ɵv as v} from '@angular/core'; import {ɵC as C, ɵE as E, ɵT as T, ɵV as V, ɵb as b, ɵb1 as b1, ɵcR as cR, ɵcr as cr, ɵdefineComponent as defineComponent, ɵdetectChanges as _detectChanges, ɵe as e, ɵp as p, ɵr as r, ɵs as s, ɵt as t, ɵv as v} from '@angular/core';
import {ComponentDef} from '@angular/core/src/render3/interfaces/definition'; import {ComponentDef} from '@angular/core/src/render3/interfaces/definition';
import {TreeNode, buildTree, emptyTree} from '../util'; import {TreeNode, buildTree, emptyTree} from '../util';
@ -36,6 +36,7 @@ export class TreeComponent {
/** @nocollapse */ /** @nocollapse */
static ngComponentDef: ComponentDef<TreeComponent> = defineComponent({ static ngComponentDef: ComponentDef<TreeComponent> = defineComponent({
type: TreeComponent,
tag: 'tree', tag: 'tree',
template: function(ctx: TreeComponent, cm: boolean) { template: function(ctx: TreeComponent, cm: boolean) {
if (cm) { if (cm) {
@ -58,7 +59,7 @@ export class TreeComponent {
} }
p(0, 'data', b(ctx.data.left)); p(0, 'data', b(ctx.data.left));
TreeComponent.ngComponentDef.h(1, 0); TreeComponent.ngComponentDef.h(1, 0);
TreeComponent.ngComponentDef.r(1, 0); r(1, 0);
} }
v(); v();
} }
@ -75,7 +76,7 @@ export class TreeComponent {
} }
p(0, 'data', b(ctx.data.right)); p(0, 'data', b(ctx.data.right));
TreeComponent.ngComponentDef.h(1, 0); TreeComponent.ngComponentDef.h(1, 0);
TreeComponent.ngComponentDef.r(1, 0); r(1, 0);
} }
v(); v();
} }
@ -92,6 +93,7 @@ export class TreeFunction {
/** @nocollapse */ /** @nocollapse */
static ngComponentDef: ComponentDef<TreeFunction> = defineComponent({ static ngComponentDef: ComponentDef<TreeFunction> = defineComponent({
type: TreeFunction,
tag: 'tree', tag: 'tree',
template: function(ctx: TreeFunction, cm: boolean) { template: function(ctx: TreeFunction, cm: boolean) {
// bit of a hack // bit of a hack

View File

@ -34,5 +34,6 @@ export {
s as ɵs, s as ɵs,
t as ɵt, t as ɵt,
v as ɵv, v as ɵv,
r as ɵr,
} from './render3/index'; } from './render3/index';
// clang-format on // clang-format on

View File

@ -13,8 +13,8 @@ import {ComponentRef as viewEngine_ComponentRef} from '../linker/component_facto
import {EmbeddedViewRef as viewEngine_EmbeddedViewRef} from '../linker/view_ref'; import {EmbeddedViewRef as viewEngine_EmbeddedViewRef} from '../linker/view_ref';
import {assertNotNull} from './assert'; import {assertNotNull} from './assert';
import {NG_HOST_SYMBOL, createError, createLView, directiveCreate, enterView, hostElement, leaveView, locateHostElement, renderComponentOrTemplate} from './instructions'; import {NG_HOST_SYMBOL, createError, createLView, createTView, directiveCreate, enterView, hostElement, leaveView, locateHostElement, renderComponentOrTemplate} from './instructions';
import {ComponentDef, ComponentType, TypedComponentDef} from './interfaces/definition'; import {ComponentDef, ComponentType} from './interfaces/definition';
import {LElementNode} from './interfaces/node'; import {LElementNode} from './interfaces/node';
import {RElement, Renderer3, RendererFactory3, domRendererFactory3} from './interfaces/renderer'; import {RElement, Renderer3, RendererFactory3, domRendererFactory3} from './interfaces/renderer';
import {notImplemented, stringify} from './util'; import {notImplemented, stringify} from './util';
@ -166,13 +166,13 @@ export const NULL_INJECTOR: Injector = {
export function renderComponent<T>( export function renderComponent<T>(
componentType: ComponentType<T>, opts: CreateComponentOptions = {}): T { componentType: ComponentType<T>, opts: CreateComponentOptions = {}): T {
const rendererFactory = opts.rendererFactory || domRendererFactory3; const rendererFactory = opts.rendererFactory || domRendererFactory3;
const componentDef = componentType.ngComponentDef as TypedComponentDef<T>; const componentDef = componentType.ngComponentDef as ComponentDef<T>;
if (componentDef.type != componentType) componentDef.type = componentType; if (componentDef.type != componentType) componentDef.type = componentType;
let component: T; let component: T;
const hostNode = locateHostElement(rendererFactory, opts.host || componentDef.tag); const hostNode = locateHostElement(rendererFactory, opts.host || componentDef.tag);
const oldView = enterView( const oldView = enterView(
createLView( createLView(
-1, rendererFactory.createRenderer(hostNode, componentDef.rendererType), {data: []}), -1, rendererFactory.createRenderer(hostNode, componentDef.rendererType), createTView()),
null !); null !);
try { try {
// Create element node at index 0 in data array // Create element node at index 0 in data array

View File

@ -13,8 +13,7 @@ import {Type} from '../type';
import {resolveRendererType2} from '../view/util'; import {resolveRendererType2} from '../view/util';
import {diPublic} from './di'; import {diPublic} from './di';
import {componentRefresh} from './instructions'; import {ComponentDef, ComponentDefArgs, DirectiveDef, DirectiveDefArgs} from './interfaces/definition';
import {ComponentDef, ComponentDefArgs, DirectiveDef, DirectiveDefArgs, TypedDirectiveDef} from './interfaces/definition';
@ -34,22 +33,26 @@ import {ComponentDef, ComponentDefArgs, DirectiveDef, DirectiveDefArgs, TypedDir
* ``` * ```
*/ */
export function defineComponent<T>(componentDefinition: ComponentDefArgs<T>): ComponentDef<T> { export function defineComponent<T>(componentDefinition: ComponentDefArgs<T>): ComponentDef<T> {
const type = componentDefinition.type;
const def = <ComponentDef<any>>{ const def = <ComponentDef<any>>{
type: type,
diPublic: null, diPublic: null,
n: componentDefinition.factory, n: componentDefinition.factory,
tag: (componentDefinition as ComponentDefArgs<T>).tag || null !, tag: (componentDefinition as ComponentDefArgs<T>).tag || null !,
template: (componentDefinition as ComponentDefArgs<T>).template || null !, template: (componentDefinition as ComponentDefArgs<T>).template || null !,
r: componentDefinition.refresh || (componentDefinition.template ?
function(d: number, e: number) {
componentRefresh(d, e, componentDefinition.template);
} :
noop),
h: componentDefinition.hostBindings || noop, h: componentDefinition.hostBindings || noop,
inputs: invertObject(componentDefinition.inputs), inputs: invertObject(componentDefinition.inputs),
outputs: invertObject(componentDefinition.outputs), outputs: invertObject(componentDefinition.outputs),
methods: invertObject(componentDefinition.methods), methods: invertObject(componentDefinition.methods),
rendererType: resolveRendererType2(componentDefinition.rendererType) || null, rendererType: resolveRendererType2(componentDefinition.rendererType) || null,
exportAs: componentDefinition.exportAs, exportAs: componentDefinition.exportAs,
onInit: type.prototype.ngOnInit || null,
doCheck: type.prototype.ngDoCheck || null,
afterContentInit: type.prototype.ngAfterContentInit || null,
afterContentChecked: type.prototype.ngAfterContentChecked || null,
afterViewInit: type.prototype.ngAfterViewInit || null,
afterViewChecked: type.prototype.ngAfterViewChecked || null,
onDestroy: type.prototype.ngOnDestroy || null
}; };
const feature = componentDefinition.features; const feature = componentDefinition.features;
feature && feature.forEach((fn) => fn(def)); feature && feature.forEach((fn) => fn(def));
@ -96,7 +99,7 @@ export function NgOnChangesFeature<T>(type: Type<T>): (definition: DirectiveDef<
} }
}); });
} }
proto.ngDoCheck = (function(delegateDoCheck) { definition.doCheck = (function(delegateDoCheck) {
return function(this: OnChangesExpando) { return function(this: OnChangesExpando) {
let simpleChanges = this[PRIVATE_PREFIX]; let simpleChanges = this[PRIVATE_PREFIX];
if (simpleChanges != null) { if (simpleChanges != null) {
@ -109,6 +112,7 @@ export function NgOnChangesFeature<T>(type: Type<T>): (definition: DirectiveDef<
}; };
} }
export function PublicFeature<T>(definition: DirectiveDef<T>) { export function PublicFeature<T>(definition: DirectiveDef<T>) {
definition.diPublic = diPublic; definition.diPublic = diPublic;
} }

View File

@ -19,7 +19,7 @@ import {Type} from '../type';
import {assertLessThan} from './assert'; import {assertLessThan} from './assert';
import {assertPreviousIsParent, getPreviousOrParentNode, getRenderer, renderEmbeddedTemplate} from './instructions'; import {assertPreviousIsParent, getPreviousOrParentNode, getRenderer, renderEmbeddedTemplate} from './instructions';
import {ComponentTemplate, DirectiveDef, TypedDirectiveDef} from './interfaces/definition'; import {ComponentTemplate, DirectiveDef} from './interfaces/definition';
import {LInjector} from './interfaces/injector'; import {LInjector} from './interfaces/injector';
import {LContainerNode, LElementNode, LNode, LNodeFlags, LViewNode} from './interfaces/node'; import {LContainerNode, LElementNode, LNode, LNodeFlags, LViewNode} from './interfaces/node';
import {QueryReadType} from './interfaces/query'; import {QueryReadType} from './interfaces/query';
@ -158,7 +158,7 @@ function createInjectionError(text: string, token: any) {
* @param di The node injector in which a directive will be added * @param di The node injector in which a directive will be added
* @param def The definition of the directive to be made public * @param def The definition of the directive to be made public
*/ */
export function diPublicInInjector(di: LInjector, def: TypedDirectiveDef<any>): void { export function diPublicInInjector(di: LInjector, def: DirectiveDef<any>): void {
bloomAdd(di, def.type); bloomAdd(di, def.type);
} }
@ -167,7 +167,7 @@ export function diPublicInInjector(di: LInjector, def: TypedDirectiveDef<any>):
* *
* @param def The definition of the directive to be made public * @param def The definition of the directive to be made public
*/ */
export function diPublic(def: TypedDirectiveDef<any>): void { export function diPublic(def: DirectiveDef<any>): void {
diPublicInInjector(getOrCreateNodeInjector(), def); diPublicInInjector(getOrCreateNodeInjector(), def);
} }
@ -291,7 +291,7 @@ export function getOrCreateInjectable<T>(
for (let i = start, ii = start + size; i < ii; i++) { for (let i = start, ii = start + size; i < ii; i++) {
// Get the definition for the directive at this index and, if it is injectable (diPublic), // Get the definition for the directive at this index and, if it is injectable (diPublic),
// and matches the given token, return the directive instance. // and matches the given token, return the directive instance.
const directiveDef = tData[i] as TypedDirectiveDef<any>; const directiveDef = tData[i] as DirectiveDef<any>;
if (directiveDef.diPublic && directiveDef.type == token) { if (directiveDef.diPublic && directiveDef.type == token) {
return node.view.data[i]; return node.view.data[i];
} }

View File

@ -0,0 +1,174 @@
/**
* @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 {DirectiveDef} from './interfaces/definition';
import {LNodeFlags} from './interfaces/node';
import {HookData, LView, LifecycleStage, TView} from './interfaces/view';
/** Constants used by lifecycle hooks to determine when and how a hook should be called. */
export const enum LifecycleHook {
ON_INIT = 0b00,
ON_CHECK = 0b01,
/* Mask used to get the type of the lifecycle hook from flags in hook queue */
TYPE_MASK = 0b00000000000000000000000000000001,
/* Shift needed to get directive index from flags in hook queue */
INDX_SHIFT = 1
}
/**
* If this is the first template pass, any ngOnInit or ngDoCheck hooks will be queued into
* TView.initHooks during directiveCreate.
*
* The directive index and hook type are encoded into one number (1st bit: type, remaining bits:
* directive index), then saved in the even indices of the initHooks array. The odd indices
* hold the hook functions themselves.
*
* @param index The index of the directive in LView.data
* @param hooks The static hooks map on the directive def
* @param tView The current TView
*/
export function queueInitHooks(
index: number, onInit: (() => void) | null, doCheck: (() => void) | null, tView: TView): void {
if (tView.firstTemplatePass === true) {
if (onInit != null) {
(tView.initHooks || (tView.initHooks = [])).push(getInitFlags(index), onInit);
}
if (doCheck != null) {
(tView.initHooks || (tView.initHooks = [])).push(getCheckFlags(index), doCheck);
}
}
}
/**
* Loops through the directives on a node and queues all their hooks except ngOnInit
* and ngDoCheck, which are queued separately in directiveCreate.
*/
export function queueLifecycleHooks(flags: number, currentView: LView): void {
const tView = currentView.tView;
if (tView.firstTemplatePass === true) {
const size = (flags & LNodeFlags.SIZE_MASK) >> LNodeFlags.SIZE_SHIFT;
const start = flags >> LNodeFlags.INDX_SHIFT;
// It's necessary to loop through the directives at elementEnd() (rather than processing in
// directiveCreate) so we can preserve the current hook order. Content, view, and destroy
// hooks for projected components and directives must be called *before* their hosts.
for (let i = start, end = start + size; i < end; i++) {
const def = (tView.data[i] as DirectiveDef<any>);
queueContentHooks(def, tView, i);
queueViewHooks(def, tView, i);
queueDestroyHooks(def, tView, i);
}
}
}
/** Queues afterContentInit and afterContentChecked hooks on TView */
function queueContentHooks(def: DirectiveDef<any>, tView: TView, i: number): void {
if (def.afterContentInit != null) {
(tView.contentHooks || (tView.contentHooks = [])).push(getInitFlags(i), def.afterContentInit);
}
if (def.afterContentChecked != null) {
(tView.contentHooks || (tView.contentHooks = [
])).push(getCheckFlags(i), def.afterContentChecked);
}
}
/** Queues afterViewInit and afterViewChecked hooks on TView */
function queueViewHooks(def: DirectiveDef<any>, tView: TView, i: number): void {
if (def.afterViewInit != null) {
(tView.viewHooks || (tView.viewHooks = [])).push(getInitFlags(i), def.afterViewInit);
}
if (def.afterViewChecked != null) {
(tView.viewHooks || (tView.viewHooks = [])).push(getCheckFlags(i), def.afterViewChecked);
}
}
/** Queues onDestroy hooks on TView */
function queueDestroyHooks(def: DirectiveDef<any>, tView: TView, i: number): void {
if (def.onDestroy != null) {
(tView.destroyHooks || (tView.destroyHooks = [])).push(i, def.onDestroy);
}
}
/** Generates flags for init-only hooks */
function getInitFlags(index: number): number {
return index << LifecycleHook.INDX_SHIFT;
}
/** Generates flags for hooks called every change detection run */
function getCheckFlags(index: number): number {
return (index << LifecycleHook.INDX_SHIFT) | LifecycleHook.ON_CHECK;
}
/**
* Calls onInit and doCheck calls if they haven't already been called.
*
* @param currentView The current view
*/
export function executeInitHooks(currentView: LView): void {
const initHooks = currentView.tView.initHooks;
if (currentView.lifecycleStage === LifecycleStage.INIT && initHooks != null) {
executeLifecycleHooks(currentView, initHooks);
currentView.lifecycleStage = LifecycleStage.CONTENT_INIT;
}
}
/**
* Calls all afterContentInit and afterContentChecked hooks for the view, then splices
* out afterContentInit hooks to prep for the next run in update mode.
*
* @param currentView The current view
*/
export function executeContentHooks(currentView: LView): void {
const contentHooks = currentView.tView.contentHooks;
if (currentView.lifecycleStage < LifecycleStage.VIEW_INIT && contentHooks != null) {
executeLifecycleHooks(currentView, contentHooks);
currentView.lifecycleStage = LifecycleStage.VIEW_INIT;
}
}
/**
* Iterates over afterViewInit and afterViewChecked functions and calls them.
*
* @param currentView The current view
*/
export function executeViewHooks(currentView: LView): void {
const viewHooks = currentView.tView.viewHooks;
if (viewHooks != null) {
executeLifecycleHooks(currentView, viewHooks);
}
}
/**
* Calls lifecycle hooks with their contexts, skipping init hooks if it's not
* creation mode.
*
* @param currentView The current view
* @param arr The array in which the hooks are found
*/
function executeLifecycleHooks(currentView: LView, arr: HookData): void {
const data = currentView.data;
const creationMode = currentView.creationMode;
for (let i = 0; i < arr.length; i += 2) {
const flags = arr[i] as number;
const initOnly = (flags & LifecycleHook.TYPE_MASK) === LifecycleHook.ON_INIT;
if (initOnly === false || creationMode) {
(arr[i | 1] as() => void).call(data[flags >> LifecycleHook.INDX_SHIFT]);
}
}
}

View File

@ -24,8 +24,6 @@ export {InjectFlags, QUERY_READ_CONTAINER_REF, QUERY_READ_ELEMENT_REF, QUERY_REA
// clang-format off // clang-format off
export { export {
LifecycleHook,
NO_CHANGE as NC, NO_CHANGE as NC,
bind as b, bind as b,
@ -52,7 +50,6 @@ export {
elementStart as E, elementStart as E,
elementStyle as s, elementStyle as s,
lifecycle as l,
listener as L, listener as L,
memory as m, memory as m,
@ -72,6 +69,9 @@ export {
query as Q, query as Q,
queryRefresh as qR, queryRefresh as qR,
} from './query'; } from './query';
export {LifecycleHook} from './hooks';
// clang-format on // clang-format on
export { export {

View File

@ -8,39 +8,22 @@
import './ng_dev_mode'; import './ng_dev_mode';
import {ElementRef} from '../linker/element_ref';
import {TemplateRef} from '../linker/template_ref';
import {ViewContainerRef} from '../linker/view_container_ref';
import {Type} from '../type';
import {assertEqual, assertLessThan, assertNotEqual, assertNotNull} from './assert'; import {assertEqual, assertLessThan, assertNotEqual, assertNotNull} from './assert';
import {LContainer, TContainer} from './interfaces/container'; import {LContainer, TContainer} from './interfaces/container';
import {LInjector} from './interfaces/injector';
import {CssSelector, LProjection} from './interfaces/projection'; import {CssSelector, LProjection} from './interfaces/projection';
import {LQuery, QueryReadType} from './interfaces/query'; import {LQuery, QueryReadType} from './interfaces/query';
import {LView, TData, TView} from './interfaces/view'; import {LView, LifecycleStage, TData, TView} from './interfaces/view';
import {LContainerNode, LElementNode, LNode, LNodeFlags, LProjectionNode, LTextNode, LViewNode, TNode, TContainerNode, InitialInputData, InitialInputs, PropertyAliases, PropertyAliasValue,} from './interfaces/node'; import {LContainerNode, LElementNode, LNode, LNodeFlags, LProjectionNode, LTextNode, LViewNode, TNode, TContainerNode, InitialInputData, InitialInputs, PropertyAliases, PropertyAliasValue,} from './interfaces/node';
import {assertNodeType, assertNodeOfPossibleTypes} from './node_assert'; import {assertNodeType, assertNodeOfPossibleTypes} from './node_assert';
import {appendChild, insertChild, insertView, processProjectedNode, removeView} from './node_manipulation'; import {appendChild, insertChild, insertView, processProjectedNode, removeView} from './node_manipulation';
import {isNodeMatchingSelector} from './node_selector_matcher'; import {isNodeMatchingSelector} from './node_selector_matcher';
import {ComponentDef, ComponentTemplate, ComponentType, DirectiveDef, DirectiveType, TypedDirectiveDef, TypedComponentDef} from './interfaces/definition'; import {ComponentDef, ComponentTemplate, ComponentType, DirectiveDef, DirectiveType} from './interfaces/definition';
import {RComment, RElement, RText, Renderer3, RendererFactory3, ProceduralRenderer3, ObjectOrientedRenderer3, RendererStyleFlags3} from './interfaces/renderer'; import {RComment, RElement, RText, Renderer3, RendererFactory3, ProceduralRenderer3, ObjectOrientedRenderer3, RendererStyleFlags3} from './interfaces/renderer';
import {isDifferent, stringify} from './util'; import {isDifferent, stringify} from './util';
import {executeViewHooks, executeContentHooks, queueLifecycleHooks, queueInitHooks, executeInitHooks} from './hooks';
/**
* Enum used by the lifecycle (l) instruction to determine which lifecycle hook is requesting
* processing.
*/
export const enum LifecycleHook {
ON_INIT = 1,
ON_DESTROY = 2,
ON_CHANGES = 4,
AFTER_VIEW_INIT = 8,
AFTER_VIEW_CHECKED = 16
}
/** /**
* Directive (D) sets a property on all component instances using this constant as a key and the * Directive (D) sets a property on all component instances using this constant as a key and the
* component's host node (LElement) as the value. This is used in methods like detectChanges to * component's host node (LElement) as the value. This is used in methods like detectChanges to
@ -91,7 +74,7 @@ let tData: TData;
/** State of the current view being processed. */ /** State of the current view being processed. */
let currentView: LView; let currentView: LView;
// The initialization has to be after the `let`, otherwise `createLView` can't see `let`. // The initialization has to be after the `let`, otherwise `createLView` can't see `let`.
currentView = createLView(null !, null !, {data: []}); currentView = createLView(null !, null !, createTView());
let currentQuery: LQuery|null; let currentQuery: LQuery|null;
@ -112,10 +95,11 @@ let data: any[];
let bindingIndex: number; let bindingIndex: number;
/** /**
* When a view is destroyed, listeners need to be released * When a view is destroyed, listeners need to be released and outputs need to be
* and onDestroy callbacks need to be called. This cleanup array * unsubscribed. This cleanup array stores both listener data (in chunks of 4)
* stores both listener data (in chunks of 4) and onDestroy data * and output data (in chunks of 2) for a particular view. Combining the arrays
* (in chunks of 2), as they'll be processed at the same time. * saves on memory (70 bytes per array) and on a few bytes of code size (for two
* separate for loops).
* *
* If it's a listener being stored: * If it's a listener being stored:
* 1st index is: event name to remove * 1st index is: event name to remove
@ -123,15 +107,12 @@ let bindingIndex: number;
* 3rd index is: listener function * 3rd index is: listener function
* 4th index is: useCapture boolean * 4th index is: useCapture boolean
* *
* If it's an onDestroy function: * If it's an output subscription:
* 1st index is: onDestroy function * 1st index is: unsubscribe function
* 2nd index is: context for function * 2nd index is: context for function
*/ */
let cleanup: any[]|null; let cleanup: any[]|null;
/** Index in the data array at which view hooks begin to be stored. */
let viewHookStartIndex: number|null;
/** /**
* Swap the current state with a new state. * Swap the current state with a new state.
* *
@ -151,7 +132,6 @@ export function enterView(newView: LView, host: LElementNode | LViewNode | null)
tData = newView.tView.data; tData = newView.tView.data;
creationMode = newView.creationMode; creationMode = newView.creationMode;
viewHookStartIndex = newView.viewHookStartIndex;
cleanup = newView.cleanup; cleanup = newView.cleanup;
renderer = newView.renderer; renderer = newView.renderer;
@ -161,6 +141,8 @@ export function enterView(newView: LView, host: LElementNode | LViewNode | null)
} }
currentView = newView; currentView = newView;
currentQuery = newView.query;
return oldView !; return oldView !;
} }
@ -169,7 +151,10 @@ export function enterView(newView: LView, host: LElementNode | LViewNode | null)
* the direction of traversal (up or down the view tree) a bit clearer. * the direction of traversal (up or down the view tree) a bit clearer.
*/ */
export function leaveView(newView: LView): void { export function leaveView(newView: LView): void {
executeViewHooks(); executeViewHooks(currentView);
currentView.creationMode = false;
currentView.lifecycleStage = LifecycleStage.INIT;
currentView.tView.firstTemplatePass = false;
enterView(newView, null); enterView(newView, null);
} }
@ -189,10 +174,11 @@ export function createLView(
next: null, next: null,
bindingStartIndex: null, bindingStartIndex: null,
creationMode: true, creationMode: true,
viewHookStartIndex: null,
template: template, template: template,
context: context, context: context,
dynamicViewCount: 0, dynamicViewCount: 0,
lifecycleStage: LifecycleStage.INIT,
query: null,
}; };
return newView; return newView;
@ -320,7 +306,7 @@ export function renderEmbeddedTemplate<T>(
previousOrParentNode = null !; previousOrParentNode = null !;
let cm: boolean = false; let cm: boolean = false;
if (viewNode == null) { if (viewNode == null) {
const view = createLView(-1, renderer, {data: []}, template, context); const view = createLView(-1, renderer, createTView(), template, context);
viewNode = createLNode(null, LNodeFlags.View, null, view); viewNode = createLNode(null, LNodeFlags.View, null, view);
cm = true; cm = true;
} }
@ -347,14 +333,13 @@ export function renderComponentOrTemplate<T>(
template(componentOrContext !, creationMode); template(componentOrContext !, creationMode);
} else { } else {
// Element was stored at 0 and directive was stored at 1 in renderComponent // Element was stored at 0 and directive was stored at 1 in renderComponent
// so to refresh the component, r() needs to be called with (1, 0) // so to refresh the component, refresh() needs to be called with (1, 0)
(componentOrContext.constructor as ComponentType<T>).ngComponentDef.r(1, 0); componentRefresh(1, 0);
} }
} finally { } finally {
if (rendererFactory.end) { if (rendererFactory.end) {
rendererFactory.end(); rendererFactory.end();
} }
hostView.creationMode = false;
leaveView(oldView); leaveView(oldView);
} }
} }
@ -430,8 +415,6 @@ export function elementStart(
if (hostComponentDef) { if (hostComponentDef) {
// TODO(mhevery): This assumes that the directives come in correct order, which // TODO(mhevery): This assumes that the directives come in correct order, which
// is not guaranteed. Must be refactored to take it into account. // is not guaranteed. Must be refactored to take it into account.
(hostComponentDef as TypedComponentDef<any>).type =
nameOrComponentType as ComponentType<any>;
directiveCreate(++index, hostComponentDef.n(), hostComponentDef, queryName); directiveCreate(++index, hostComponentDef.n(), hostComponentDef, queryName);
} }
hack_declareDirectives(index, directiveTypes, localRefs); hack_declareDirectives(index, directiveTypes, localRefs);
@ -459,7 +442,6 @@ function hack_declareDirectives(
// TODO(misko): refactor this to store the `DirectiveDef` in `TView.data`. // TODO(misko): refactor this to store the `DirectiveDef` in `TView.data`.
const directiveType = directiveTypes[i]; const directiveType = directiveTypes[i];
const directiveDef = directiveType.ngDirectiveDef; const directiveDef = directiveType.ngDirectiveDef;
(directiveDef as TypedDirectiveDef<any>).type = directiveType;
directiveCreate( directiveCreate(
++index, directiveDef.n(), directiveDef, hack_findQueryName(directiveDef, localRefs)); ++index, directiveDef.n(), directiveDef, hack_findQueryName(directiveDef, localRefs));
} }
@ -494,7 +476,19 @@ function hack_findQueryName(
* @returns TView * @returns TView
*/ */
function getOrCreateTView(template: ComponentTemplate<any>): TView { function getOrCreateTView(template: ComponentTemplate<any>): TView {
return template.ngPrivateData || (template.ngPrivateData = { data: [] } as never); return template.ngPrivateData || (template.ngPrivateData = createTView() as never);
}
/** Creates a TView instance */
export function createTView(): TView {
return {
data: [],
firstTemplatePass: true,
initHooks: null,
contentHooks: null,
viewHooks: null,
destroyHooks: null
};
} }
function setUpAttributes(native: RElement, attrs: string[]): void { function setUpAttributes(native: RElement, attrs: string[]): void {
@ -613,6 +607,7 @@ export function elementEnd() {
ngDevMode && assertNodeType(previousOrParentNode, LNodeFlags.Element); ngDevMode && assertNodeType(previousOrParentNode, LNodeFlags.Element);
const query = previousOrParentNode.query; const query = previousOrParentNode.query;
query && query.addNode(previousOrParentNode); query && query.addNode(previousOrParentNode);
queueLifecycleHooks(previousOrParentNode.flags, currentView);
} }
/** /**
@ -831,23 +826,22 @@ export function text(index: number, value?: any): void {
* @param value Stringified value to write. * @param value Stringified value to write.
*/ */
export function textBinding<T>(index: number, value: T | NO_CHANGE): void { export function textBinding<T>(index: number, value: T | NO_CHANGE): void {
// TODO(misko): I don't think index < nodes.length check is needed here. ngDevMode && assertDataInRange(index);
let existingNode = index < data.length && data[index] as LTextNode; let existingNode = data[index] as LTextNode;
if (existingNode && existingNode.native) { ngDevMode && assertNotNull(existingNode, 'existing node');
if (existingNode.native) {
// If DOM node exists and value changed, update textContent // If DOM node exists and value changed, update textContent
value !== NO_CHANGE && value !== NO_CHANGE &&
((renderer as ProceduralRenderer3).setValue ? ((renderer as ProceduralRenderer3).setValue ?
(renderer as ProceduralRenderer3).setValue(existingNode.native, stringify(value)) : (renderer as ProceduralRenderer3).setValue(existingNode.native, stringify(value)) :
existingNode.native.textContent = stringify(value)); existingNode.native.textContent = stringify(value));
} else if (existingNode) { } else {
// Node was created but DOM node creation was delayed. Create and append now. // Node was created but DOM node creation was delayed. Create and append now.
existingNode.native = existingNode.native =
((renderer as ProceduralRenderer3).createText ? ((renderer as ProceduralRenderer3).createText ?
(renderer as ProceduralRenderer3).createText(stringify(value)) : (renderer as ProceduralRenderer3).createText(stringify(value)) :
(renderer as ObjectOrientedRenderer3).createTextNode !(stringify(value))); (renderer as ObjectOrientedRenderer3).createTextNode !(stringify(value)));
insertChild(existingNode, currentView); insertChild(existingNode, currentView);
} else {
text(index, value);
} }
} }
@ -906,6 +900,11 @@ export function directiveCreate<T>(
if (tNode && tNode.attrs) { if (tNode && tNode.attrs) {
setInputsFromAttrs<T>(instance, directiveDef !.inputs, tNode); setInputsFromAttrs<T>(instance, directiveDef !.inputs, tNode);
} }
// Init hooks are queued now so ngOnInit is called in host components before
// any projected components.
queueInitHooks(index, directiveDef.onInit, directiveDef.doCheck, currentView.tView);
return instance; return instance;
} }
@ -966,76 +965,6 @@ function generateInitialInputs(
return initialInputData; return initialInputData;
} }
/**
* Accepts a lifecycle hook type and determines when and how the related lifecycle hook
* callback should run.
*
* For the onInit lifecycle hook, it will return whether or not the ngOnInit() function
* should run. If so, ngOnInit() will be called outside of this function.
*
* e.g. l(LifecycleHook.ON_INIT) && ctx.ngOnInit();
*
* For the onDestroy lifecycle hook, this instruction also accepts an onDestroy
* method that should be stored and called internally when the parent view is being
* cleaned up.
*
* e.g. l(LifecycleHook.ON_DESTROY, ctx, ctx.onDestroy);
*
* @param lifecycle
* @param self
* @param method
*/
export function lifecycle(lifecycle: LifecycleHook.ON_DESTROY, self: any, method: Function): void;
export function lifecycle(
lifecycle: LifecycleHook.AFTER_VIEW_INIT, self: any, method: Function): void;
export function lifecycle(
lifecycle: LifecycleHook.AFTER_VIEW_CHECKED, self: any, method: Function): void;
export function lifecycle(lifecycle: LifecycleHook): boolean;
export function lifecycle(lifecycle: LifecycleHook, self?: any, method?: Function): boolean {
if (lifecycle === LifecycleHook.ON_INIT) {
return creationMode;
} else if (lifecycle === LifecycleHook.ON_DESTROY) {
(cleanup || (currentView.cleanup = cleanup = [])).push(method, self);
} else if (
creationMode && (lifecycle === LifecycleHook.AFTER_VIEW_INIT ||
lifecycle === LifecycleHook.AFTER_VIEW_CHECKED)) {
if (viewHookStartIndex == null) {
currentView.viewHookStartIndex = viewHookStartIndex = data.length;
}
data.push(lifecycle, method, self);
}
return false;
}
/** Iterates over view hook functions and calls them. */
export function executeViewHooks(): void {
if (viewHookStartIndex == null) return;
// Instead of using splice to remove init hooks after their first run (expensive), we
// shift over the AFTER_CHECKED hooks as we call them and truncate once at the end.
let checkIndex = viewHookStartIndex as number;
let writeIndex = checkIndex;
while (checkIndex < data.length) {
// Call lifecycle hook with its context
data[checkIndex + 1].call(data[checkIndex + 2]);
if (data[checkIndex] === LifecycleHook.AFTER_VIEW_CHECKED) {
// We know if the writeIndex falls behind that there is an init that needs to
// be overwritten.
if (writeIndex < checkIndex) {
data[writeIndex] = data[checkIndex];
data[writeIndex + 1] = data[checkIndex + 1];
data[writeIndex + 2] = data[checkIndex + 2];
}
writeIndex += 3;
}
checkIndex += 3;
}
// Truncate once at the writeIndex
data.length = writeIndex;
}
////////////////////////// //////////////////////////
//// ViewContainer & View //// ViewContainer & View
@ -1071,14 +1000,17 @@ export function container(
renderParent = currentParent as LElementNode; renderParent = currentParent as LElementNode;
} }
const node = createLNode(index, LNodeFlags.Container, comment, <LContainer>{ const lContainer = <LContainer>{
views: [], views: [],
nextIndex: 0, renderParent, nextIndex: 0, renderParent,
template: template == null ? null : template, template: template == null ? null : template,
next: null, next: null,
parent: currentView, parent: currentView,
dynamicViewCount: 0, dynamicViewCount: 0,
}); query: null
};
const node = createLNode(index, LNodeFlags.Container, comment, lContainer);
if (node.tNode == null) { if (node.tNode == null) {
// TODO(misko): implement queryName caching // TODO(misko): implement queryName caching
@ -1093,8 +1025,13 @@ export function container(
isParent = false; isParent = false;
ngDevMode && assertNodeType(previousOrParentNode, LNodeFlags.Container); ngDevMode && assertNodeType(previousOrParentNode, LNodeFlags.Container);
const query = previousOrParentNode.query; const query = node.query;
query && query.addNode(previousOrParentNode); if (query) {
// check if a given container node matches
query.addNode(node);
// prepare place for matching nodes from views inserted into a given container
lContainer.query = query.container();
}
} }
/** /**
@ -1108,6 +1045,10 @@ export function containerRefreshStart(index: number): void {
ngDevMode && assertNodeType(previousOrParentNode, LNodeFlags.Container); ngDevMode && assertNodeType(previousOrParentNode, LNodeFlags.Container);
isParent = true; isParent = true;
(previousOrParentNode as LContainerNode).data.nextIndex = 0; (previousOrParentNode as LContainerNode).data.nextIndex = 0;
// 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);
} }
/** /**
@ -1171,6 +1112,10 @@ export function viewStart(viewBlockId: number): boolean {
// When we create a new LView, we always reset the state of the instructions. // When we create a new LView, we always reset the state of the instructions.
const newView = const newView =
createLView(viewBlockId, renderer, getOrCreateEmbeddedTView(viewBlockId, container)); createLView(viewBlockId, renderer, getOrCreateEmbeddedTView(viewBlockId, container));
if (lContainer.query) {
newView.query = lContainer.query.enterView(lContainer.nextIndex);
}
enterView(newView, createLNode(null, LNodeFlags.View, null, newView)); enterView(newView, createLNode(null, LNodeFlags.View, null, newView));
lContainer.nextIndex++; lContainer.nextIndex++;
} }
@ -1194,7 +1139,7 @@ function getOrCreateEmbeddedTView(viewIndex: number, parent: LContainerNode): TV
ngDevMode && assertNodeType(parent, LNodeFlags.Container); ngDevMode && assertNodeType(parent, LNodeFlags.Container);
const tContainer = (parent !.tNode as TContainerNode).data; const tContainer = (parent !.tNode as TContainerNode).data;
if (viewIndex >= tContainer.length || tContainer[viewIndex] == null) { if (viewIndex >= tContainer.length || tContainer[viewIndex] == null) {
tContainer[viewIndex] = { data: [] } as TView; tContainer[viewIndex] = createTView();
} }
return tContainer[viewIndex]; return tContainer[viewIndex];
} }
@ -1215,7 +1160,6 @@ export function viewEnd(): void {
if (viewIdChanged) { if (viewIdChanged) {
insertView(container, viewNode, containerState.nextIndex - 1); insertView(container, viewNode, containerState.nextIndex - 1);
currentView.creationMode = false;
} }
} }
leaveView(currentView !.parent !); leaveView(currentView !.parent !);
@ -1232,29 +1176,29 @@ export function viewEnd(): void {
* *
* @param directiveIndex * @param directiveIndex
* @param elementIndex * @param elementIndex
* @param template
*/ */
export const componentRefresh: export function componentRefresh<T>(directiveIndex: number, elementIndex: number): void {
<T>(directiveIndex: number, elementIndex: number, template: ComponentTemplate<T>) => executeInitHooks(currentView);
void = function<T>( executeContentHooks(currentView);
directiveIndex: number, elementIndex: number, template: ComponentTemplate<T>) { const template = (tData[directiveIndex] as ComponentDef<T>).template;
if (template != null) {
ngDevMode && assertDataInRange(elementIndex); ngDevMode && assertDataInRange(elementIndex);
const element = data ![elementIndex] as LElementNode; const element = data ![elementIndex] as LElementNode;
ngDevMode && assertNodeOfPossibleTypes(element, LNodeFlags.Element, LNodeFlags.Container); ngDevMode && assertNodeType(element, LNodeFlags.Element);
ngDevMode && assertNotEqual(element.data, null, 'isComponent'); ngDevMode && assertNotEqual(element.data, null, 'isComponent');
ngDevMode && assertDataInRange(directiveIndex); ngDevMode && assertDataInRange(directiveIndex);
const directive = data[directiveIndex];
const hostView = element.data !; const hostView = element.data !;
ngDevMode && assertNotEqual(hostView, null, 'hostView'); ngDevMode && assertNotEqual(hostView, null, 'hostView');
const directive = data[directiveIndex];
const oldView = enterView(hostView, element); const oldView = enterView(hostView, element);
try { try {
template(directive, creationMode); template(directive, creationMode);
} finally { } finally {
hostView.creationMode = false;
refreshDynamicChildren(); refreshDynamicChildren();
leaveView(oldView); leaveView(oldView);
} }
}; }
}
/** /**
* Instruction to distribute projectable nodes among <ng-content> occurrences in a given template. * Instruction to distribute projectable nodes among <ng-content> occurrences in a given template.

View File

@ -8,9 +8,11 @@
import {ComponentTemplate} from './definition'; import {ComponentTemplate} from './definition';
import {LElementNode, LViewNode} from './node'; import {LElementNode, LViewNode} from './node';
import {LQuery} from './query';
import {LView, TView} from './view'; import {LView, TView} from './view';
/** The state associated with an LContainer */ /** The state associated with an LContainer */
export interface LContainer { export interface LContainer {
/** /**
@ -67,12 +69,17 @@ export interface LContainer {
*/ */
readonly template: ComponentTemplate<any>|null; readonly template: ComponentTemplate<any>|null;
/** /**
* A count of dynamic views rendered into this container. If this is non-zero, the `views` array * A count of dynamic views rendered into this container. If this is non-zero, the `views` array
* will be traversed when refreshing dynamic views on this container. * will be traversed when refreshing dynamic views on this container.
*/ */
dynamicViewCount: number; dynamicViewCount: number;
/**
* Queries active for this container - all the views inserted to / removed from
* this container are reported to queries referenced here.
*/
query: LQuery|null;
} }
/** /**

View File

@ -27,6 +27,9 @@ export const enum DirectiveDefFlags {ContentQuery = 0b10}
* `DirectiveDef` is a compiled version of the Directive used by the renderer instructions. * `DirectiveDef` is a compiled version of the Directive used by the renderer instructions.
*/ */
export interface DirectiveDef<T> { export interface DirectiveDef<T> {
/** Token representing the directive. Used by DI. */
type: Type<T>;
/** Function that makes a directive public to the DI system. */ /** Function that makes a directive public to the DI system. */
diPublic: ((def: DirectiveDef<any>) => void)|null; diPublic: ((def: DirectiveDef<any>) => void)|null;
@ -64,42 +67,23 @@ export interface DirectiveDef<T> {
*/ */
n(): T; n(): T;
/**
* Refreshes the view of the component. Also calls lifecycle hooks like
* ngAfterViewInit, if they are defined on the component.
*
* NOTE: this property is short (1 char) because it is used in component
* templates which is sensitive to size.
*
* @param directiveIndex index of the directive in the containing template
* @param elementIndex index of an host element for a given directive.
*/
r(directiveIndex: number, elementIndex: number): void;
/** /**
* Refreshes host bindings on the associated directive. Also calls lifecycle hooks * Refreshes host bindings on the associated directive. Also calls lifecycle hooks
* like ngOnInit and ngDoCheck, if they are defined on the directive. * like ngOnInit and ngDoCheck, if they are defined on the directive.
*/ */
// Note: This call must be separate from r() because hooks like ngOnInit need to
// be called breadth-first across a view before processing onInits in children
// (for backwards compatibility). Child template processing thus needs to be
// delayed until all inputs and host bindings in a view have been checked.
h(directiveIndex: number, elementIndex: number): void; h(directiveIndex: number, elementIndex: number): void;
/* The following are lifecycle hooks for this component */
onInit: (() => void)|null;
doCheck: (() => void)|null;
afterContentInit: (() => void)|null;
afterContentChecked: (() => void)|null;
afterViewInit: (() => void)|null;
afterViewChecked: (() => void)|null;
onDestroy: (() => void)|null;
} }
export interface ComponentDef<T> extends DirectiveDef<T> { export interface ComponentDef<T> extends DirectiveDef<T> {
/**
* Refreshes the view of the component. Also calls lifecycle hooks like
* ngAfterViewInit, if they are defined on the component.
*
* NOTE: this property is short (1 char) because it is used in
* component templates which is sensitive to size.
*
* @param directiveIndex index of the directive in the containing template
* @param elementIndex index of an host element for a given component.
*/
r(directiveIndex: number, elementIndex: number): void;
/** /**
* The tag name which should be used by the component. * The tag name which should be used by the component.
* *
@ -122,31 +106,20 @@ export interface ComponentDef<T> extends DirectiveDef<T> {
readonly rendererType: RendererType2|null; readonly rendererType: RendererType2|null;
} }
/**
* Private: do not export
*/
export interface TypedDirectiveDef<T> extends DirectiveDef<T> { type: DirectiveType<T>; }
/**
* Private: do not export
*/
export interface TypedComponentDef<T> extends ComponentDef<T> { type: ComponentType<T>; }
export interface DirectiveDefArgs<T> { export interface DirectiveDefArgs<T> {
type: Type<T>;
factory: () => T; factory: () => T;
refresh?: (directiveIndex: number, elementIndex: number) => void;
inputs?: {[P in keyof T]?: string}; inputs?: {[P in keyof T]?: string};
outputs?: {[P in keyof T]?: string}; outputs?: {[P in keyof T]?: string};
methods?: {[P in keyof T]?: string}; methods?: {[P in keyof T]?: string};
features?: DirectiveDefFeature[]; features?: DirectiveDefFeature[];
hostBindings?: (directiveIndex: number, elementIndex: number) => void;
exportAs?: string; exportAs?: string;
} }
export interface ComponentDefArgs<T> extends DirectiveDefArgs<T> { export interface ComponentDefArgs<T> extends DirectiveDefArgs<T> {
tag: string; tag: string;
template: ComponentTemplate<T>; template: ComponentTemplate<T>;
refresh?: (directiveIndex: number, elementIndex: number) => void;
hostBindings?: (directiveIndex: number, elementIndex: number) => void;
features?: ComponentDefFeature[]; features?: ComponentDefFeature[];
rendererType?: RendererType2; rendererType?: RendererType2;
} }

View File

@ -8,9 +8,7 @@
import {QueryList} from '../../linker'; import {QueryList} from '../../linker';
import {Type} from '../../type'; import {Type} from '../../type';
import {LNode} from './node';
import {LInjector} from './injector';
import {LContainerNode, LNode, LViewNode} from './node';
/** Used for tracking queries (e.g. ViewChild, ContentChild). */ /** Used for tracking queries (e.g. ViewChild, ContentChild). */
@ -25,19 +23,28 @@ export interface LQuery {
child(): LQuery|null; child(): LQuery|null;
/** /**
* Notify `LQuery` that a `LNode` has been created. * Notify `LQuery` that a new `LNode` has been created and needs to be added to query results
* if matching query predicate.
*/ */
addNode(node: LNode): void; addNode(node: LNode): void;
/** /**
* Notify `LQuery` that an `LViewNode` has been added to `LContainerNode`. * Notify `LQuery` that a `LNode` has been created and needs to be added to query results
* if matching query predicate.
*/ */
insertView(container: LContainerNode, view: LViewNode, insertIndex: number): void; container(): LQuery|null;
/** /**
* Notify `LQuery` that an `LViewNode` has been removed from `LContainerNode`. * Notify `LQuery` that a new view was created and is being entered in the creation mode.
* This allow queries to prepare space for matching nodes from views.
*/ */
removeView(container: LContainerNode, view: LViewNode, removeIndex: number): void; enterView(newViewIndex: number): LQuery|null;
/**
* Notify `LQuery` that an `LViewNode` has been removed from `LContainerNode`. As a result all
* the matching nodes from this view should be removed from container's queries.
*/
removeView(removeIndex: number): void;
/** /**
* Add additional `QueryList` to track. * Add additional `QueryList` to track.

View File

@ -9,6 +9,7 @@
import {LContainer} from './container'; import {LContainer} from './container';
import {ComponentTemplate, DirectiveDef} from './definition'; import {ComponentTemplate, DirectiveDef} from './definition';
import {LElementNode, LViewNode, TNode} from './node'; import {LElementNode, LViewNode, TNode} from './node';
import {LQuery} from './query';
import {Renderer3} from './renderer'; import {Renderer3} from './renderer';
@ -33,9 +34,6 @@ export interface LView {
*/ */
creationMode: boolean; creationMode: boolean;
/** The index in the data array at which view hooks begin to be stored. */
viewHookStartIndex: number|null;
/** /**
* The parent view is needed when we exit the view and must restore the previous * The parent view is needed when we exit the view and must restore the previous
* `LView`. Without this, the render method would have to keep a stack of * `LView`. Without this, the render method would have to keep a stack of
@ -72,9 +70,9 @@ export interface LView {
bindingStartIndex: number|null; bindingStartIndex: number|null;
/** /**
* When a view is destroyed, listeners need to be released and onDestroy callbacks * When a view is destroyed, listeners need to be released and outputs need to be
* need to be called. This cleanup array stores both listener data (in chunks of 4) * unsubscribed. This cleanup array stores both listener data (in chunks of 4)
* and onDestroy data (in chunks of 2) for a particular view. Combining the arrays * 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 * saves on memory (70 bytes per array) and on a few bytes of code size (for two
* separate for loops). * separate for loops).
* *
@ -84,12 +82,29 @@ export interface LView {
* 3rd index is: listener function * 3rd index is: listener function
* 4th index is: useCapture boolean * 4th index is: useCapture boolean
* *
* If it's an onDestroy function: * If it's an output subscription:
* 1st index is: onDestroy function * 1st index is: unsubscribe function
* 2nd index is; context for function * 2nd index is: context for function
*/ */
cleanup: any[]|null; cleanup: any[]|null;
/**
* This number tracks the next lifecycle hook that needs to be run.
*
* If lifecycleStage === LifecycleStage.ON_INIT, the init hooks haven't yet been run
* and should be executed by the first r() instruction that runs OR the first
* cR() instruction that runs (so inits are run for the top level view before any
* embedded views).
*
* If lifecycleStage === LifecycleStage.CONTENT_INIT, the init hooks have been run, but
* the content hooks have not yet been run. They should be executed on the first
* r() instruction that runs.
*
* If lifecycleStage === LifecycleStage.VIEW_INIT, both the init hooks and content hooks
* have already been run.
*/
lifecycleStage: LifecycleStage;
/** /**
* The first LView or LContainer beneath this LView in the hierarchy. * The first LView or LContainer beneath this LView in the hierarchy.
* *
@ -156,6 +171,11 @@ export interface LView {
* after refreshing the view itself. * after refreshing the view itself.
*/ */
dynamicViewCount: number; dynamicViewCount: number;
/**
* Queries active for this view - nodes from a view are reported to those queries
*/
query: LQuery|null;
} }
/** Interface necessary to work with view tree traversal */ /** Interface necessary to work with view tree traversal */
@ -172,7 +192,67 @@ export interface LViewOrLContainer {
* *
* Stored on the template function as ngPrivateData. * Stored on the template function as ngPrivateData.
*/ */
export interface TView { data: TData; } export interface TView {
/** Static data equivalent of LView.data[]. Contains TNodes and directive defs. */
data: TData;
/** Whether or not this template has been processed. */
firstTemplatePass: boolean;
/**
* Array of ngOnInit and ngDoCheck hooks that should be executed for this view.
*
* Even indices: Flags (1st bit: hook type, remaining: directive index)
* Odd indices: Hook function
*/
initHooks: HookData|null;
/**
* Array of ngAfterContentInit and ngAfterContentChecked hooks that should be executed for
* this view.
*
* Even indices: Flags (1st bit: hook type, remaining: directive index)
* Odd indices: Hook function
*/
contentHooks: HookData|null;
/**
* Array of ngAfterViewInit and ngAfterViewChecked hooks that should be executed for
* this view.
*
* Even indices: Flags (1st bit: hook type, remaining: directive index)
* Odd indices: Hook function
*/
viewHooks: HookData|null;
/**
* Array of ngOnDestroy hooks that should be executed when this view is destroyed.
*
* Even indices: Directive index
* Odd indices: Hook function
*/
destroyHooks: HookData|null;
}
/**
* Array of hooks that should be executed for a view and their directive indices.
*
* Even indices: Flags (1st bit: hook type, remaining: directive index)
* Odd indices: Hook function
*/
export type HookData = (number | (() => void))[];
/** Possible values of LView.lifecycleStage, used to determine which hooks to run. */
export const enum LifecycleStage {
/* Init hooks need to be run, if any. */
INIT = 1,
/* Content hooks need to be run, if any. Init hooks have already run. */
CONTENT_INIT = 2,
/* View hooks need to be run, if any. Any init hooks/content hooks have ran. */
VIEW_INIT = 3
}
/** /**
* Static data that corresponds to the instance-specific data array on an LView. * Static data that corresponds to the instance-specific data array on an LView.

View File

@ -11,7 +11,7 @@ import {LContainer, unusedValueExportToPlacateAjd as unused1} from './interfaces
import {LContainerNode, LElementNode, LNode, LNodeFlags, LProjectionNode, LTextNode, LViewNode, unusedValueExportToPlacateAjd as unused2} from './interfaces/node'; import {LContainerNode, LElementNode, LNode, LNodeFlags, LProjectionNode, LTextNode, LViewNode, unusedValueExportToPlacateAjd as unused2} from './interfaces/node';
import {LProjection, unusedValueExportToPlacateAjd as unused3} from './interfaces/projection'; import {LProjection, unusedValueExportToPlacateAjd as unused3} from './interfaces/projection';
import {ProceduralRenderer3, RComment, RElement, RNode, RText, unusedValueExportToPlacateAjd as unused4} from './interfaces/renderer'; import {ProceduralRenderer3, RComment, RElement, RNode, RText, unusedValueExportToPlacateAjd as unused4} from './interfaces/renderer';
import {LView, LViewOrLContainer, unusedValueExportToPlacateAjd as unused5} from './interfaces/view'; import {HookData, LView, LViewOrLContainer, TView, unusedValueExportToPlacateAjd as unused5} from './interfaces/view';
import {assertNodeType} from './node_assert'; import {assertNodeType} from './node_assert';
const unusedValueToPlacateAjd = unused1 + unused2 + unused3 + unused4 + unused5; const unusedValueToPlacateAjd = unused1 + unused2 + unused3 + unused4 + unused5;
@ -222,8 +222,6 @@ export function insertView(
container, newView, true, findBeforeNode(index, state, container.native)); container, newView, true, findBeforeNode(index, state, container.native));
} }
// Notify query that view has been inserted
container.query && container.query.insertView(container, newView, index);
return newView; return newView;
} }
@ -248,7 +246,7 @@ export function removeView(container: LContainerNode, removeIndex: number): LVie
destroyViewTree(viewNode.data); destroyViewTree(viewNode.data);
addRemoveViewFromContainer(container, viewNode, false); addRemoveViewFromContainer(container, viewNode, false);
// Notify query that view has been removed // Notify query that view has been removed
container.query && container.query.removeView(container, viewNode, removeIndex); container.data.query && container.data.query.removeView(removeIndex);
return viewNode; return viewNode;
} }
@ -295,8 +293,14 @@ export function getParentState(state: LViewOrLContainer, rootView: LView): LView
* @param view The LView to clean up * @param view The LView to clean up
*/ */
function cleanUpView(view: LView): void { function cleanUpView(view: LView): void {
if (!view.cleanup) return; removeListeners(view);
executeOnDestroys(view);
}
/** Removes listeners and unsubscribes from output subscriptions */
function removeListeners(view: LView): void {
const cleanup = view.cleanup !; const cleanup = view.cleanup !;
if (cleanup != null) {
for (let i = 0; i < cleanup.length - 1; i += 2) { for (let i = 0; i < cleanup.length - 1; i += 2) {
if (typeof cleanup[i] === 'string') { if (typeof cleanup[i] === 'string') {
cleanup ![i + 1].removeEventListener(cleanup[i], cleanup[i + 2], cleanup[i + 3]); cleanup ![i + 1].removeEventListener(cleanup[i], cleanup[i + 2], cleanup[i + 3]);
@ -306,6 +310,19 @@ function cleanUpView(view: LView): void {
} }
} }
view.cleanup = null; view.cleanup = null;
}
}
/** Calls onDestroy hooks for this view */
function executeOnDestroys(view: LView): void {
const tView = view.tView;
let destroyHooks: HookData|null;
if (tView != null && (destroyHooks = tView.destroyHooks) != null) {
for (let i = 0; i < destroyHooks.length; i += 2) {
const instance = view.data[destroyHooks[i] as number];
(destroyHooks[i | 1] as() => void).call(instance);
}
}
} }
/** /**

View File

@ -10,37 +10,21 @@
// correctly implementing its interfaces for backwards compatibility. // correctly implementing its interfaces for backwards compatibility.
import {Observable} from 'rxjs/Observable'; import {Observable} from 'rxjs/Observable';
import {ElementRef as viewEngine_ElementRef} from '../linker/element_ref';
import {QueryList as viewEngine_QueryList} from '../linker/query_list'; import {QueryList as viewEngine_QueryList} from '../linker/query_list';
import {TemplateRef as viewEngine_TemplateRef} from '../linker/template_ref';
import {Type} from '../type'; import {Type} from '../type';
import {assertNotNull} from './assert'; import {assertEqual, assertNotNull} from './assert';
import {ReadFromInjectorFn, getOrCreateNodeInjectorForNode} from './di'; import {ReadFromInjectorFn, getOrCreateNodeInjectorForNode} from './di';
import {assertPreviousIsParent, getCurrentQuery} from './instructions'; import {assertPreviousIsParent, getCurrentQuery} from './instructions';
import {DirectiveDef, TypedDirectiveDef, unusedValueExportToPlacateAjd as unused1} from './interfaces/definition'; import {DirectiveDef, unusedValueExportToPlacateAjd as unused1} from './interfaces/definition';
import {LInjector, unusedValueExportToPlacateAjd as unused2} from './interfaces/injector'; import {LInjector, unusedValueExportToPlacateAjd as unused2} from './interfaces/injector';
import {LContainerNode, LElementNode, LNode, LNodeFlags, LViewNode, TNode, unusedValueExportToPlacateAjd as unused3} from './interfaces/node'; import {LContainerNode, LElementNode, LNode, LNodeFlags, TNode, unusedValueExportToPlacateAjd as unused3} from './interfaces/node';
import {LQuery, QueryReadType, unusedValueExportToPlacateAjd as unused4} from './interfaces/query'; import {LQuery, QueryReadType, unusedValueExportToPlacateAjd as unused4} from './interfaces/query';
import {assertNodeOfPossibleTypes} from './node_assert'; import {flatten} from './util';
const unusedValueToPlacateAjd = unused1 + unused2 + unused3 + unused4; const unusedValueToPlacateAjd = unused1 + unused2 + unused3 + unused4;
export function query<T>(
predicate: Type<any>| string[], descend?: boolean,
read?: QueryReadType<T>| Type<T>): QueryList<T> {
ngDevMode && assertPreviousIsParent();
const queryList = new QueryList<T>();
const query = getCurrentQuery(LQuery_);
query.track(queryList, predicate, descend, read);
return queryList;
}
export function queryRefresh(query: QueryList<any>): boolean {
return (query as any)._refresh();
}
/** /**
* A predicate which determines if a given element/directive should be included in the query * A predicate which determines if a given element/directive should be included in the query
*/ */
@ -112,17 +96,90 @@ export class LQuery_ implements LQuery {
} }
} }
container(): LQuery|null {
let result: QueryPredicate<any>|null = null;
let predicate = this.deep;
while (predicate) {
const containerValues: any[] = []; // prepare room for views
predicate.values.push(containerValues);
const clonedPredicate: QueryPredicate<any> = {
next: null,
list: predicate.list,
type: predicate.type,
selector: predicate.selector,
read: predicate.read,
values: containerValues
};
clonedPredicate.next = result;
result = clonedPredicate;
predicate = predicate.next;
}
return result ? new LQuery_(result) : null;
}
enterView(index: number): LQuery|null {
let result: QueryPredicate<any>|null = null;
let predicate = this.deep;
while (predicate) {
const viewValues: any[] = []; // prepare room for view nodes
predicate.values.splice(index, 0, viewValues);
const clonedPredicate: QueryPredicate<any> = {
next: null,
list: predicate.list,
type: predicate.type,
selector: predicate.selector,
read: predicate.read,
values: viewValues
};
clonedPredicate.next = result;
result = clonedPredicate;
predicate = predicate.next;
}
return result ? new LQuery_(result) : null;
}
addNode(node: LNode): void { addNode(node: LNode): void {
add(this.shallow, node); add(this.shallow, node);
add(this.deep, node); add(this.deep, node);
} }
insertView(container: LContainerNode, view: LViewNode, index: number): void { removeView(index: number): void {
throw new Error('Method not implemented.'); let predicate = this.deep;
while (predicate) {
const removed = predicate.values.splice(index, 1);
// mark a query as dirty only when removed view had matching modes
ngDevMode && assertEqual(removed.length, 1, 'removed.length');
if (removed[0].length) {
predicate.list.setDirty();
} }
removeView(container: LContainerNode, view: LViewNode, index: number): void { predicate = predicate.next;
throw new Error('Method not implemented.'); }
}
/**
* Clone LQuery by taking all the deep query predicates and cloning those using a provided clone
* function.
* Shallow predicates are ignored.
*/
private _clonePredicates(
predicateCloneFn: (predicate: QueryPredicate<any>) => QueryPredicate<any>): LQuery|null {
let result: QueryPredicate<any>|null = null;
let predicate = this.deep;
while (predicate) {
const clonedPredicate = predicateCloneFn(predicate);
clonedPredicate.next = result;
result = clonedPredicate;
predicate = predicate.next;
}
return result ? new LQuery_(result) : null;
} }
} }
@ -159,7 +216,7 @@ function geIdxOfMatchingDirective(node: LNode, type: Type<any>): number|null {
for (let i = flags >> LNodeFlags.INDX_SHIFT, for (let i = flags >> LNodeFlags.INDX_SHIFT,
ii = i + ((flags & LNodeFlags.SIZE_MASK) >> LNodeFlags.SIZE_SHIFT); ii = i + ((flags & LNodeFlags.SIZE_MASK) >> LNodeFlags.SIZE_SHIFT);
i < ii; i++) { i < ii; i++) {
const def = tData[i] as TypedDirectiveDef<any>; const def = tData[i] as DirectiveDef<any>;
if (def.diPublic && def.type === type) { if (def.diPublic && def.type === type) {
return i; return i;
} }
@ -191,10 +248,10 @@ function add(predicate: QueryPredicate<any>| null, node: LNode) {
if (predicate.read !== null) { if (predicate.read !== null) {
const requestedRead = readFromNodeInjector(nodeInjector, node, predicate.read); const requestedRead = readFromNodeInjector(nodeInjector, node, predicate.read);
if (requestedRead !== null) { if (requestedRead !== null) {
predicate.values.push(requestedRead); addMatch(predicate, requestedRead);
} }
} else { } else {
predicate.values.push(node.view.data[directiveIdx]); addMatch(predicate, node.view.data[directiveIdx]);
} }
} }
} else { } else {
@ -207,10 +264,10 @@ function add(predicate: QueryPredicate<any>| null, node: LNode) {
if (predicate.read !== null) { if (predicate.read !== null) {
const result = readFromNodeInjector(nodeInjector, node, predicate.read !, directiveIdx); const result = readFromNodeInjector(nodeInjector, node, predicate.read !, directiveIdx);
if (result !== null) { if (result !== null) {
predicate.values.push(result); addMatch(predicate, result);
} }
} else { } else {
predicate.values.push(node.view.data[directiveIdx]); addMatch(predicate, node.view.data[directiveIdx]);
} }
} }
} }
@ -219,27 +276,31 @@ function add(predicate: QueryPredicate<any>| null, node: LNode) {
} }
} }
function addMatch(predicate: QueryPredicate<any>, matchingValue: any): void {
predicate.values.push(matchingValue);
predicate.list.setDirty();
}
function createPredicate<T>( function createPredicate<T>(
previous: QueryPredicate<any>| null, queryList: QueryList<T>, predicate: Type<T>| string[], previous: QueryPredicate<any>| null, queryList: QueryList<T>, predicate: Type<T>| string[],
read: QueryReadType<T>| Type<T>| null): QueryPredicate<T> { read: QueryReadType<T>| Type<T>| null): QueryPredicate<T> {
const isArray = Array.isArray(predicate); const isArray = Array.isArray(predicate);
const values = <any>[];
if ((queryList as any as QueryList_<T>)._valuesTree === null) {
(queryList as any as QueryList_<T>)._valuesTree = values;
}
return { return {
next: previous, next: previous,
list: queryList, list: queryList,
type: isArray ? null : predicate as Type<T>, type: isArray ? null : predicate as Type<T>,
selector: isArray ? predicate as string[] : null, selector: isArray ? predicate as string[] : null,
read: read, read: read,
values: values values: (queryList as any as QueryList_<T>)._valuesTree
}; };
} }
class QueryList_<T>/* implements viewEngine_QueryList<T> */ { class QueryList_<T>/* implements viewEngine_QueryList<T> */ {
dirty: boolean = false; readonly dirty = true;
changes: Observable<T>; readonly changes: Observable<T>;
private _values: T[]|null = null;
/** @internal */
_valuesTree: any[] = [];
get length(): number { get length(): number {
ngDevMode && assertNotNull(this._values, 'refreshed'); ngDevMode && assertNotNull(this._values, 'refreshed');
@ -258,21 +319,6 @@ class QueryList_<T>/* implements viewEngine_QueryList<T> */ {
return values.length ? values[values.length - 1] : null; return values.length ? values[values.length - 1] : null;
} }
/** @internal */
_valuesTree: any[]|null = null;
/** @internal */
_values: T[]|null = null;
/** @internal */
_refresh(): boolean {
// TODO(misko): needs more logic to flatten tree.
if (this._values === null) {
this._values = this._valuesTree;
return true;
}
return false;
}
map<U>(fn: (item: T, index: number, array: T[]) => U): U[] { map<U>(fn: (item: T, index: number, array: T[]) => U): U[] {
throw new Error('Method not implemented.'); throw new Error('Method not implemented.');
} }
@ -295,10 +341,16 @@ class QueryList_<T>/* implements viewEngine_QueryList<T> */ {
ngDevMode && assertNotNull(this._values, 'refreshed'); ngDevMode && assertNotNull(this._values, 'refreshed');
return this._values !; return this._values !;
} }
toString(): string { throw new Error('Method not implemented.'); } toString(): string {
reset(res: (any[]|T)[]): void { throw new Error('Method not implemented.'); } ngDevMode && assertNotNull(this._values, 'refreshed');
return this._values !.toString();
}
reset(res: (any[]|T)[]): void {
this._values = flatten(res);
(this as{dirty: boolean}).dirty = false;
}
notifyOnChanges(): void { throw new Error('Method not implemented.'); } notifyOnChanges(): void { throw new Error('Method not implemented.'); }
setDirty(): void { throw new Error('Method not implemented.'); } setDirty(): void { (this as{dirty: boolean}).dirty = true; }
destroy(): void { throw new Error('Method not implemented.'); } destroy(): void { throw new Error('Method not implemented.'); }
} }
@ -306,3 +358,27 @@ class QueryList_<T>/* implements viewEngine_QueryList<T> */ {
// it can't be implemented only extended. // it can't be implemented only extended.
export type QueryList<T> = viewEngine_QueryList<T>; export type QueryList<T> = viewEngine_QueryList<T>;
export const QueryList: typeof viewEngine_QueryList = QueryList_ as any; export const QueryList: typeof viewEngine_QueryList = QueryList_ as any;
export function query<T>(
predicate: Type<any>| string[], descend?: boolean,
read?: QueryReadType<T>| Type<T>): QueryList<T> {
ngDevMode && assertPreviousIsParent();
const queryList = new QueryList<T>();
const query = getCurrentQuery(LQuery_);
query.track(queryList, predicate, descend, read);
return queryList;
}
/**
* Refreshes a query by combining matches from all active views and removing matches from deleted
* views.
* Returns true if a query got dirty during change detection, false otherwise.
*/
export function queryRefresh(query: QueryList<any>): boolean {
const queryImpl = (query as any as QueryList_<any>);
if (query.dirty) {
query.reset(queryImpl._valuesTree);
return true;
}
return false;
}

View File

@ -31,3 +31,28 @@ export function stringify(value: any): string {
export function notImplemented(): Error { export function notImplemented(): Error {
return new Error('NotImplemented'); return new Error('NotImplemented');
} }
/**
* Flattens an array in non-recursive way. Input arrays are not modified.
*/
export function flatten(list: any[]): any[] {
const result: any[] = [];
let i = 0;
while (i < list.length) {
const item = list[i];
if (Array.isArray(item)) {
if (item.length > 0) {
list = item.concat(list.slice(i + 1));
i = 0;
} else {
i++;
}
} else {
result.push(item);
i++;
}
}
return result;
}

View File

@ -32,6 +32,7 @@ describe('iv perf test', () => {
it(`${iteration}. create ${count} divs in Render3`, () => { it(`${iteration}. create ${count} divs in Render3`, () => {
class Component { class Component {
static ngComponentDef = defineComponent({ static ngComponentDef = defineComponent({
type: Component,
tag: 'div', tag: 'div',
template: function Template(ctx: any, cm: any) { template: function Template(ctx: any, cm: any) {
if (cm) { if (cm) {

View File

@ -8,7 +8,7 @@
import {NgForOfContext} from '@angular/common'; import {NgForOfContext} from '@angular/common';
import {C, E, T, b, cR, cr, defineComponent, e, p, t} from '../../src/render3/index'; import {C, E, T, b, cR, cr, defineComponent, e, p, r, t} from '../../src/render3/index';
import {NgForOf} from './common_with_def'; import {NgForOf} from './common_with_def';
import {renderComponent, toHtml} from './render_util'; import {renderComponent, toHtml} from './render_util';
@ -20,6 +20,7 @@ describe('@angular/common integration', () => {
items: string[] = ['first', 'second']; items: string[] = ['first', 'second'];
static ngComponentDef = defineComponent({ static ngComponentDef = defineComponent({
type: MyApp,
factory: () => new MyApp(), factory: () => new MyApp(),
tag: 'my-app', tag: 'my-app',
// <ul> // <ul>
@ -33,7 +34,7 @@ describe('@angular/common integration', () => {
} }
p(1, 'ngForOf', b(myApp.items)); p(1, 'ngForOf', b(myApp.items));
cR(1); cR(1);
NgForOf.ngDirectiveDef.r(2, 0); r(2, 0);
cr(); cr();
function liTemplate(row: NgForOfContext<string>, cm: boolean) { function liTemplate(row: NgForOfContext<string>, cm: boolean) {

View File

@ -15,13 +15,11 @@ import {DirectiveType, InjectFlags, NgOnChangesFeature, defineDirective, inject,
export const NgForOf: DirectiveType<NgForOfDef<any>> = NgForOfDef as any; export const NgForOf: DirectiveType<NgForOfDef<any>> = NgForOfDef as any;
NgForOf.ngDirectiveDef = defineDirective({ NgForOf.ngDirectiveDef = defineDirective({
type: NgForOfDef,
factory: () => new NgForOfDef( factory: () => new NgForOfDef(
injectViewContainerRef(), injectTemplateRef(), injectViewContainerRef(), injectTemplateRef(),
inject(IterableDiffers, InjectFlags.Default, defaultIterableDiffers)), inject(IterableDiffers, InjectFlags.Default, defaultIterableDiffers)),
features: [NgOnChangesFeature(NgForOf)], features: [NgOnChangesFeature(NgForOf)],
refresh: (directiveIndex: number, elementIndex: number) => {
m<NgForOfDef<any>>(directiveIndex).ngDoCheck();
},
inputs: { inputs: {
ngForOf: 'ngForOf', ngForOf: 'ngForOf',
ngForTrackBy: 'ngForTrackBy', ngForTrackBy: 'ngForTrackBy',

View File

@ -27,6 +27,7 @@ describe('compiler specification', () => {
class MyComponent { class MyComponent {
// NORMATIVE // NORMATIVE
static ngComponentDef = r3.defineComponent({ static ngComponentDef = r3.defineComponent({
type: MyComponent,
tag: 'my-component', tag: 'my-component',
factory: () => new MyComponent(), factory: () => new MyComponent(),
template: function(ctx: MyComponent, cm: boolean) { template: function(ctx: MyComponent, cm: boolean) {
@ -59,6 +60,7 @@ describe('compiler specification', () => {
constructor() { log.push('ChildComponent'); } constructor() { log.push('ChildComponent'); }
// NORMATIVE // NORMATIVE
static ngComponentDef = r3.defineComponent({ static ngComponentDef = r3.defineComponent({
type: ChildComponent,
tag: `child`, tag: `child`,
factory: () => new ChildComponent(), factory: () => new ChildComponent(),
template: function(ctx: ChildComponent, cm: boolean) { template: function(ctx: ChildComponent, cm: boolean) {
@ -77,6 +79,7 @@ describe('compiler specification', () => {
constructor() { log.push('SomeDirective'); } constructor() { log.push('SomeDirective'); }
// NORMATIVE // NORMATIVE
static ngDirectiveDef = r3.defineDirective({ static ngDirectiveDef = r3.defineDirective({
type: SomeDirective,
factory: () => new SomeDirective(), factory: () => new SomeDirective(),
}); });
// /NORMATIVE // /NORMATIVE
@ -86,6 +89,7 @@ describe('compiler specification', () => {
class MyComponent { class MyComponent {
// NORMATIVE // NORMATIVE
static ngComponentDef = r3.defineComponent({ static ngComponentDef = r3.defineComponent({
type: MyComponent,
tag: 'my-component', tag: 'my-component',
factory: () => new MyComponent(), factory: () => new MyComponent(),
template: function(ctx: MyComponent, cm: boolean) { template: function(ctx: MyComponent, cm: boolean) {
@ -94,8 +98,8 @@ describe('compiler specification', () => {
r3.e(); r3.e();
r3.T(3, '!'); r3.T(3, '!');
} }
ChildComponent.ngComponentDef.r(1, 0); r3.r(1, 0);
SomeDirective.ngDirectiveDef.r(2, 0); r3.r(2, 0);
} }
}); });
// /NORMATIVE // /NORMATIVE
@ -119,6 +123,7 @@ describe('compiler specification', () => {
constructor(template: TemplateRef<any>) { log.push('ifDirective'); } constructor(template: TemplateRef<any>) { log.push('ifDirective'); }
// NORMATIVE // NORMATIVE
static ngDirectiveDef = r3.defineDirective({ static ngDirectiveDef = r3.defineDirective({
type: IfDirective,
factory: () => new IfDirective(r3.injectTemplateRef()), factory: () => new IfDirective(r3.injectTemplateRef()),
}); });
// /NORMATIVE // /NORMATIVE
@ -130,6 +135,7 @@ describe('compiler specification', () => {
salutation = 'Hello'; salutation = 'Hello';
// NORMATIVE // NORMATIVE
static ngComponentDef = r3.defineComponent({ static ngComponentDef = r3.defineComponent({
type: MyComponent,
tag: 'my-component', tag: 'my-component',
factory: () => new MyComponent(), factory: () => new MyComponent(),
template: function(ctx: MyComponent, cm: boolean) { template: function(ctx: MyComponent, cm: boolean) {
@ -140,7 +146,7 @@ describe('compiler specification', () => {
} }
let foo = r3.m<any>(1); let foo = r3.m<any>(1);
r3.cR(2); r3.cR(2);
IfDirective.ngDirectiveDef.r(3, 2); r3.r(3, 2);
r3.cr(); r3.cr();
function C1(ctx1: any, cm: boolean) { function C1(ctx1: any, cm: boolean) {
@ -169,6 +175,7 @@ describe('compiler specification', () => {
@Component({selector: 'simple', template: `<div><ng-content></ng-content></div>`}) @Component({selector: 'simple', template: `<div><ng-content></ng-content></div>`})
class SimpleComponent { class SimpleComponent {
static ngComponentDef = r3.defineComponent({ static ngComponentDef = r3.defineComponent({
type: SimpleComponent,
tag: 'simple', tag: 'simple',
factory: () => new SimpleComponent(), factory: () => new SimpleComponent(),
template: function(ctx: SimpleComponent, cm: boolean) { template: function(ctx: SimpleComponent, cm: boolean) {
@ -190,6 +197,7 @@ describe('compiler specification', () => {
}) })
class ComplexComponent { class ComplexComponent {
static ngComponentDef = r3.defineComponent({ static ngComponentDef = r3.defineComponent({
type: ComplexComponent,
tag: 'complex', tag: 'complex',
factory: () => new ComplexComponent(), factory: () => new ComplexComponent(),
template: function(ctx: ComplexComponent, cm: boolean) { template: function(ctx: ComplexComponent, cm: boolean) {
@ -215,6 +223,7 @@ describe('compiler specification', () => {
}) })
class MyApp { class MyApp {
static ngComponentDef = r3.defineComponent({ static ngComponentDef = r3.defineComponent({
type: MyApp,
tag: 'my-app', tag: 'my-app',
factory: () => new MyApp(), factory: () => new MyApp(),
template: function(ctx: MyApp, cm: boolean) { template: function(ctx: MyApp, cm: boolean) {
@ -236,6 +245,7 @@ describe('compiler specification', () => {
class MyComponent { class MyComponent {
// NORMATIVE // NORMATIVE
static ngComponentDef = r3.defineComponent({ static ngComponentDef = r3.defineComponent({
type: MyComponent,
tag: 'my-component', tag: 'my-component',
factory: () => new MyComponent, factory: () => new MyComponent,
template: function(ctx: MyComponent, cm: boolean) { template: function(ctx: MyComponent, cm: boolean) {
@ -304,14 +314,12 @@ describe('compiler specification', () => {
// NORMATIVE // NORMATIVE
static ngDirectiveDef = r3.defineDirective({ static ngDirectiveDef = r3.defineDirective({
type: ForOfDirective,
factory: function ForOfDirective_Factory() { factory: function ForOfDirective_Factory() {
return new ForOfDirective(r3.injectViewContainerRef(), r3.injectTemplateRef()); return new ForOfDirective(r3.injectViewContainerRef(), r3.injectTemplateRef());
}, },
// TODO(chuckj): Enable when ngForOf enabling lands. // TODO(chuckj): Enable when ngForOf enabling lands.
// features: [NgOnChangesFeature(NgForOf)], // features: [NgOnChangesFeature(NgForOf)],
refresh: function ForOfDirective_Refresh(directiveIndex: number, elementIndex: number) {
r3.m<ForOfDirective>(directiveIndex).ngDoCheck();
},
inputs: {forOf: 'forOf'} inputs: {forOf: 'forOf'}
}); });
// /NORMATIVE // /NORMATIVE
@ -333,6 +341,7 @@ describe('compiler specification', () => {
// NORMATIVE // NORMATIVE
static ngComponentDef = r3.defineComponent({ static ngComponentDef = r3.defineComponent({
type: MyComponent,
tag: 'my-component', tag: 'my-component',
factory: function MyComponent_Factory() { return new MyComponent(); }, factory: function MyComponent_Factory() { return new MyComponent(); },
template: function MyComponentTemplate(ctx: MyComponent, cm: boolean) { template: function MyComponentTemplate(ctx: MyComponent, cm: boolean) {
@ -343,7 +352,7 @@ describe('compiler specification', () => {
} }
r3.p(1, 'forOf', r3.b(ctx.items)); r3.p(1, 'forOf', r3.b(ctx.items));
r3.cR(1); r3.cR(1);
ForOfDirective.ngDirectiveDef.r(2, 1); r3.r(2, 1);
r3.cr(); r3.cr();
function MyComponent_ForOfDirective_Template_1(ctx1: any, cm: boolean) { function MyComponent_ForOfDirective_Template_1(ctx1: any, cm: boolean) {
@ -397,6 +406,7 @@ describe('compiler specification', () => {
// NORMATIVE // NORMATIVE
static ngComponentDef = r3.defineComponent({ static ngComponentDef = r3.defineComponent({
type: MyComponent,
tag: 'my-component', tag: 'my-component',
factory: function MyComponent_Factory() { return new MyComponent(); }, factory: function MyComponent_Factory() { return new MyComponent(); },
template: function MyComponent_Template(ctx: MyComponent, cm: boolean) { template: function MyComponent_Template(ctx: MyComponent, cm: boolean) {
@ -407,7 +417,7 @@ describe('compiler specification', () => {
} }
r3.p(1, 'forOf', r3.b(ctx.items)); r3.p(1, 'forOf', r3.b(ctx.items));
r3.cR(1); r3.cR(1);
ForOfDirective.ngDirectiveDef.r(2, 1); r3.r(2, 1);
r3.cr(); r3.cr();
function MyComponent_ForOfDirective_Template_1(ctx1: any, cm: boolean) { function MyComponent_ForOfDirective_Template_1(ctx1: any, cm: boolean) {
@ -423,7 +433,7 @@ describe('compiler specification', () => {
r3.t(1, r3.b1('', l0_item.name, '')); r3.t(1, r3.b1('', l0_item.name, ''));
r3.p(4, 'forOf', r3.b(ctx.items)); r3.p(4, 'forOf', r3.b(ctx.items));
r3.cR(3); r3.cR(3);
ForOfDirective.ngDirectiveDef.r(4, 3); r3.r(4, 3);
r3.cr(); r3.cr();
function MyComponent_ForOfDirective_ForOfDirective_Template_3( function MyComponent_ForOfDirective_ForOfDirective_Template_3(

View File

@ -7,7 +7,7 @@
*/ */
import {ViewEncapsulation} from '../../src/core'; import {ViewEncapsulation} from '../../src/core';
import {E, T, b, defineComponent, e, markDirty, t} from '../../src/render3/index'; import {E, T, b, defineComponent, e, markDirty, r, t} from '../../src/render3/index';
import {createRendererType2} from '../../src/view/index'; import {createRendererType2} from '../../src/view/index';
import {getRendererFactory2} from './imported_renderer2'; import {getRendererFactory2} from './imported_renderer2';
@ -20,6 +20,7 @@ describe('component', () => {
increment() { this.count++; } increment() { this.count++; }
static ngComponentDef = defineComponent({ static ngComponentDef = defineComponent({
type: CounterComponent,
tag: 'counter', tag: 'counter',
template: function(ctx: CounterComponent, cm: boolean) { template: function(ctx: CounterComponent, cm: boolean) {
if (cm) { if (cm) {
@ -63,6 +64,7 @@ describe('component', () => {
describe('encapsulation', () => { describe('encapsulation', () => {
class WrapperComponent { class WrapperComponent {
static ngComponentDef = defineComponent({ static ngComponentDef = defineComponent({
type: WrapperComponent,
tag: 'wrapper', tag: 'wrapper',
template: function(ctx: WrapperComponent, cm: boolean) { template: function(ctx: WrapperComponent, cm: boolean) {
if (cm) { if (cm) {
@ -70,7 +72,7 @@ describe('encapsulation', () => {
e(); e();
} }
EncapsulatedComponent.ngComponentDef.h(1, 0); EncapsulatedComponent.ngComponentDef.h(1, 0);
EncapsulatedComponent.ngComponentDef.r(1, 0); r(1, 0);
}, },
factory: () => new WrapperComponent, factory: () => new WrapperComponent,
}); });
@ -78,6 +80,7 @@ describe('encapsulation', () => {
class EncapsulatedComponent { class EncapsulatedComponent {
static ngComponentDef = defineComponent({ static ngComponentDef = defineComponent({
type: EncapsulatedComponent,
tag: 'encapsulated', tag: 'encapsulated',
template: function(ctx: EncapsulatedComponent, cm: boolean) { template: function(ctx: EncapsulatedComponent, cm: boolean) {
if (cm) { if (cm) {
@ -86,7 +89,7 @@ describe('encapsulation', () => {
e(); e();
} }
LeafComponent.ngComponentDef.h(2, 1); LeafComponent.ngComponentDef.h(2, 1);
LeafComponent.ngComponentDef.r(2, 1); r(2, 1);
}, },
factory: () => new EncapsulatedComponent, factory: () => new EncapsulatedComponent,
rendererType: rendererType:
@ -96,6 +99,7 @@ describe('encapsulation', () => {
class LeafComponent { class LeafComponent {
static ngComponentDef = defineComponent({ static ngComponentDef = defineComponent({
type: LeafComponent,
tag: 'leaf', tag: 'leaf',
template: function(ctx: LeafComponent, cm: boolean) { template: function(ctx: LeafComponent, cm: boolean) {
if (cm) { if (cm) {
@ -125,6 +129,7 @@ describe('encapsulation', () => {
it('should encapsulate host and children with different attributes', () => { it('should encapsulate host and children with different attributes', () => {
class WrapperComponentWith { class WrapperComponentWith {
static ngComponentDef = defineComponent({ static ngComponentDef = defineComponent({
type: WrapperComponentWith,
tag: 'wrapper', tag: 'wrapper',
template: function(ctx: WrapperComponentWith, cm: boolean) { template: function(ctx: WrapperComponentWith, cm: boolean) {
if (cm) { if (cm) {
@ -132,7 +137,7 @@ describe('encapsulation', () => {
e(); e();
} }
LeafComponentwith.ngComponentDef.h(1, 0); LeafComponentwith.ngComponentDef.h(1, 0);
LeafComponentwith.ngComponentDef.r(1, 0); r(1, 0);
}, },
factory: () => new WrapperComponentWith, factory: () => new WrapperComponentWith,
rendererType: rendererType:
@ -142,6 +147,7 @@ describe('encapsulation', () => {
class LeafComponentwith { class LeafComponentwith {
static ngComponentDef = defineComponent({ static ngComponentDef = defineComponent({
type: LeafComponentwith,
tag: 'leaf', tag: 'leaf',
template: function(ctx: LeafComponentwith, cm: boolean) { template: function(ctx: LeafComponentwith, cm: boolean) {
if (cm) { if (cm) {

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {C, E, P, T, V, cR, cr, detectChanges, e, m, pD, v} from '../../src/render3/index'; import {C, E, P, T, V, cR, cr, detectChanges, e, m, pD, r, v} from '../../src/render3/index';
import {createComponent, renderComponent, toHtml} from './render_util'; import {createComponent, renderComponent, toHtml} from './render_util';
@ -35,7 +35,7 @@ describe('content projection', () => {
e(); e();
} }
Child.ngComponentDef.h(1, 0); Child.ngComponentDef.h(1, 0);
Child.ngComponentDef.r(1, 0); r(1, 0);
}); });
const parent = renderComponent(Parent); const parent = renderComponent(Parent);
expect(toHtml(parent)).toEqual('<child><div>content</div></child>'); expect(toHtml(parent)).toEqual('<child><div>content</div></child>');
@ -55,7 +55,7 @@ describe('content projection', () => {
e(); e();
} }
Child.ngComponentDef.h(1, 0); Child.ngComponentDef.h(1, 0);
Child.ngComponentDef.r(1, 0); r(1, 0);
}); });
const parent = renderComponent(Parent); const parent = renderComponent(Parent);
expect(toHtml(parent)).toEqual('<child>content</child>'); expect(toHtml(parent)).toEqual('<child>content</child>');
@ -77,7 +77,7 @@ describe('content projection', () => {
{ P(3, 0); } { P(3, 0); }
e(); e();
GrandChild.ngComponentDef.h(2, 1); GrandChild.ngComponentDef.h(2, 1);
GrandChild.ngComponentDef.r(2, 1); r(2, 1);
} }
}); });
const Parent = createComponent('parent', function(ctx: any, cm: boolean) { const Parent = createComponent('parent', function(ctx: any, cm: boolean) {
@ -92,13 +92,55 @@ describe('content projection', () => {
e(); e();
} }
Child.ngComponentDef.h(1, 0); Child.ngComponentDef.h(1, 0);
Child.ngComponentDef.r(1, 0); r(1, 0);
}); });
const parent = renderComponent(Parent); const parent = renderComponent(Parent);
expect(toHtml(parent)) expect(toHtml(parent))
.toEqual('<child><grand-child><div><b>Hello</b>World!</div></grand-child></child>'); .toEqual('<child><grand-child><div><b>Hello</b>World!</div></grand-child></child>');
}); });
it('should project components', () => {
/** <div><ng-content></ng-content></div> */
const Child = createComponent('child', (ctx: any, cm: boolean) => {
if (cm) {
pD(0);
E(1, 'div');
{ P(2, 0); }
e();
}
});
const ProjectedComp = createComponent('projected-comp', (ctx: any, cm: boolean) => {
if (cm) {
T(0, 'content');
}
});
/**
* <child>
* <projected-comp></projected-comp>
* </child>
*/
const Parent = createComponent('parent', (ctx: any, cm: boolean) => {
if (cm) {
E(0, Child);
{
E(2, ProjectedComp);
e();
}
e();
}
Child.ngComponentDef.h(1, 0);
ProjectedComp.ngComponentDef.h(3, 2);
r(3, 2);
r(1, 0);
});
const parent = renderComponent(Parent);
expect(toHtml(parent))
.toEqual('<child><div><projected-comp>content</projected-comp></div></child>');
});
it('should project content with container.', () => { it('should project content with container.', () => {
const Child = createComponent('child', function(ctx: any, cm: boolean) { const Child = createComponent('child', function(ctx: any, cm: boolean) {
if (cm) { if (cm) {
@ -129,7 +171,7 @@ describe('content projection', () => {
} }
cr(); cr();
Child.ngComponentDef.h(1, 0); Child.ngComponentDef.h(1, 0);
Child.ngComponentDef.r(1, 0); r(1, 0);
}); });
const parent = renderComponent(Parent); const parent = renderComponent(Parent);
expect(toHtml(parent)).toEqual('<child><div>()</div></child>'); expect(toHtml(parent)).toEqual('<child><div>()</div></child>');
@ -165,7 +207,7 @@ describe('content projection', () => {
} }
cr(); cr();
Child.ngComponentDef.h(1, 0); Child.ngComponentDef.h(1, 0);
Child.ngComponentDef.r(1, 0); r(1, 0);
}); });
const parent = renderComponent(Parent); const parent = renderComponent(Parent);
expect(toHtml(parent)).toEqual('<child></child>'); expect(toHtml(parent)).toEqual('<child></child>');
@ -214,7 +256,7 @@ describe('content projection', () => {
} }
cr(); cr();
Child.ngComponentDef.h(1, 0); Child.ngComponentDef.h(1, 0);
Child.ngComponentDef.r(1, 0); r(1, 0);
}); });
const parent = renderComponent(Parent); const parent = renderComponent(Parent);
expect(toHtml(parent)).toEqual('<child><div>(else)</div></child>'); expect(toHtml(parent)).toEqual('<child><div>(else)</div></child>');
@ -272,7 +314,7 @@ describe('content projection', () => {
e(); e();
} }
Child.ngComponentDef.h(1, 0); Child.ngComponentDef.h(1, 0);
Child.ngComponentDef.r(1, 0); r(1, 0);
}); });
const parent = renderComponent(Parent); const parent = renderComponent(Parent);
expect(toHtml(parent)).toEqual('<child><div><span>content</span></div></child>'); expect(toHtml(parent)).toEqual('<child><div><span>content</span></div></child>');
@ -325,7 +367,7 @@ describe('content projection', () => {
e(); e();
} }
Child.ngComponentDef.h(1, 0); Child.ngComponentDef.h(1, 0);
Child.ngComponentDef.r(1, 0); r(1, 0);
}); });
const parent = renderComponent(Parent); const parent = renderComponent(Parent);
expect(toHtml(parent)).toEqual('<child><div>content</div></child>'); expect(toHtml(parent)).toEqual('<child><div>content</div></child>');
@ -362,7 +404,7 @@ describe('content projection', () => {
e(); e();
} }
Child.ngComponentDef.h(1, 0); Child.ngComponentDef.h(1, 0);
Child.ngComponentDef.r(1, 0); r(1, 0);
}); });
const parent = renderComponent(Parent); const parent = renderComponent(Parent);
expect(toHtml(parent)).toEqual('<child><div></div><span>content</span></child>'); expect(toHtml(parent)).toEqual('<child><div></div><span>content</span></child>');
@ -420,7 +462,7 @@ describe('content projection', () => {
e(); e();
} }
Child.ngComponentDef.h(1, 0); Child.ngComponentDef.h(1, 0);
Child.ngComponentDef.r(1, 0); r(1, 0);
}); });
const parent = renderComponent(Parent); const parent = renderComponent(Parent);
expect(toHtml(parent)).toEqual('<child>content<div></div></child>'); expect(toHtml(parent)).toEqual('<child>content<div></div></child>');
@ -469,7 +511,7 @@ describe('content projection', () => {
e(); e();
} }
Child.ngComponentDef.h(1, 0); Child.ngComponentDef.h(1, 0);
Child.ngComponentDef.r(1, 0); r(1, 0);
}); });
const parent = renderComponent(Parent); const parent = renderComponent(Parent);
@ -515,7 +557,7 @@ describe('content projection', () => {
e(); e();
} }
Child.ngComponentDef.h(1, 0); Child.ngComponentDef.h(1, 0);
Child.ngComponentDef.r(1, 0); r(1, 0);
}); });
const parent = renderComponent(Parent); const parent = renderComponent(Parent);
@ -561,7 +603,7 @@ describe('content projection', () => {
e(); e();
} }
Child.ngComponentDef.h(1, 0); Child.ngComponentDef.h(1, 0);
Child.ngComponentDef.r(1, 0); r(1, 0);
}); });
const parent = renderComponent(Parent); const parent = renderComponent(Parent);
@ -607,7 +649,7 @@ describe('content projection', () => {
e(); e();
} }
Child.ngComponentDef.h(1, 0); Child.ngComponentDef.h(1, 0);
Child.ngComponentDef.r(1, 0); r(1, 0);
}); });
const parent = renderComponent(Parent); const parent = renderComponent(Parent);
@ -654,7 +696,7 @@ describe('content projection', () => {
e(); e();
} }
Child.ngComponentDef.h(1, 0); Child.ngComponentDef.h(1, 0);
Child.ngComponentDef.r(1, 0); r(1, 0);
}); });
const parent = renderComponent(Parent); const parent = renderComponent(Parent);
@ -701,8 +743,8 @@ describe('content projection', () => {
} }
e(); e();
} }
Child.ngComponentDef.h(0, 0); Child.ngComponentDef.h(1, 0);
Child.ngComponentDef.r(0, 0); r(1, 0);
}); });
const parent = renderComponent(Parent); const parent = renderComponent(Parent);
@ -750,7 +792,7 @@ describe('content projection', () => {
} }
e(); e();
GrandChild.ngComponentDef.h(2, 1); GrandChild.ngComponentDef.h(2, 1);
GrandChild.ngComponentDef.r(2, 1); r(2, 1);
} }
}); });
@ -772,7 +814,7 @@ describe('content projection', () => {
e(); e();
} }
Child.ngComponentDef.h(1, 0); Child.ngComponentDef.h(1, 0);
Child.ngComponentDef.r(1, 0); r(1, 0);
}); });
const parent = renderComponent(Parent); const parent = renderComponent(Parent);
@ -821,7 +863,7 @@ describe('content projection', () => {
} }
cr(); cr();
Child.ngComponentDef.h(1, 0); Child.ngComponentDef.h(1, 0);
Child.ngComponentDef.r(1, 0); r(1, 0);
}); });
const parent = renderComponent(Parent); const parent = renderComponent(Parent);
expect(toHtml(parent)).toEqual('<child><span><div>content</div></span></child>'); expect(toHtml(parent)).toEqual('<child><span><div>content</div></span></child>');

View File

@ -28,6 +28,7 @@ describe('define', () => {
} }
static ngDirectiveDef = defineDirective({ static ngDirectiveDef = defineDirective({
type: MyDirective,
factory: () => new MyDirective(), factory: () => new MyDirective(),
features: [NgOnChangesFeature(MyDirective)], features: [NgOnChangesFeature(MyDirective)],
inputs: {valA: 'valA', valB: 'valB'} inputs: {valA: 'valA', valB: 'valB'}
@ -41,7 +42,7 @@ describe('define', () => {
expect(myDir.log).toEqual(['second']); expect(myDir.log).toEqual(['second']);
expect(myDir.valB).toEqual('works'); expect(myDir.valB).toEqual('works');
myDir.log.length = 0; myDir.log.length = 0;
myDir.ngDoCheck(); MyDirective.ngDirectiveDef.doCheck !.call(myDir);
expect(myDir.log).toEqual([ expect(myDir.log).toEqual([
'ngOnChanges', 'valA', 'initValue', 'first', 'valB', undefined, 'second', 'ngDoCheck' 'ngOnChanges', 'valA', 'initValue', 'first', 'valB', undefined, 'second', 'ngDoCheck'
]); ]);

View File

@ -11,7 +11,7 @@ import {ElementRef, TemplateRef, ViewContainerRef} from '@angular/core';
import {defineComponent} from '../../src/render3/definition'; import {defineComponent} from '../../src/render3/definition';
import {InjectFlags, bloomAdd, bloomFindPossibleInjector, getOrCreateNodeInjector} from '../../src/render3/di'; import {InjectFlags, bloomAdd, bloomFindPossibleInjector, getOrCreateNodeInjector} from '../../src/render3/di';
import {C, E, PublicFeature, T, V, b, b2, cR, cr, defineDirective, e, inject, injectElementRef, injectTemplateRef, injectViewContainerRef, m, t, v} from '../../src/render3/index'; import {C, E, PublicFeature, T, V, b, b2, cR, cr, defineDirective, e, inject, injectElementRef, injectTemplateRef, injectViewContainerRef, m, t, v} from '../../src/render3/index';
import {createLNode, createLView, enterView, leaveView} from '../../src/render3/instructions'; import {createLNode, createLView, createTView, enterView, leaveView} from '../../src/render3/instructions';
import {LInjector} from '../../src/render3/interfaces/injector'; import {LInjector} from '../../src/render3/interfaces/injector';
import {LNodeFlags} from '../../src/render3/interfaces/node'; import {LNodeFlags} from '../../src/render3/interfaces/node';
@ -22,7 +22,7 @@ describe('di', () => {
it('should create directive with no deps', () => { it('should create directive with no deps', () => {
class Directive { class Directive {
value: string = 'Created'; value: string = 'Created';
static ngDirectiveDef = defineDirective({factory: () => new Directive}); static ngDirectiveDef = defineDirective({type: Directive, factory: () => new Directive});
} }
function Template(ctx: any, cm: boolean) { function Template(ctx: any, cm: boolean) {
@ -42,21 +42,23 @@ describe('di', () => {
it('should create directive with inter view dependencies', () => { it('should create directive with inter view dependencies', () => {
class DirectiveA { class DirectiveA {
value: string = 'A'; value: string = 'A';
static ngDirectiveDef = static ngDirectiveDef = defineDirective(
defineDirective({factory: () => new DirectiveA, features: [PublicFeature]}); {type: DirectiveA, factory: () => new DirectiveA, features: [PublicFeature]});
} }
class DirectiveB { class DirectiveB {
value: string = 'B'; value: string = 'B';
static ngDirectiveDef = static ngDirectiveDef = defineDirective(
defineDirective({factory: () => new DirectiveB, features: [PublicFeature]}); {type: DirectiveB, factory: () => new DirectiveB, features: [PublicFeature]});
} }
class DirectiveC { class DirectiveC {
value: string; value: string;
constructor(a: DirectiveA, b: DirectiveB) { this.value = a.value + b.value; } constructor(a: DirectiveA, b: DirectiveB) { this.value = a.value + b.value; }
static ngDirectiveDef = defineDirective( static ngDirectiveDef = defineDirective({
{factory: () => new DirectiveC(inject(DirectiveA), inject(DirectiveB))}); type: DirectiveC,
factory: () => new DirectiveC(inject(DirectiveA), inject(DirectiveB))
});
} }
function Template(ctx: any, cm: boolean) { function Template(ctx: any, cm: boolean) {
@ -83,8 +85,11 @@ describe('di', () => {
constructor(public elementRef: ElementRef) { constructor(public elementRef: ElementRef) {
this.value = (elementRef.constructor as any).name; this.value = (elementRef.constructor as any).name;
} }
static ngDirectiveDef = defineDirective( static ngDirectiveDef = defineDirective({
{factory: () => new Directive(injectElementRef()), features: [PublicFeature]}); type: Directive,
factory: () => new Directive(injectElementRef()),
features: [PublicFeature]
});
} }
class DirectiveSameInstance { class DirectiveSameInstance {
@ -92,8 +97,10 @@ describe('di', () => {
constructor(elementRef: ElementRef, directive: Directive) { constructor(elementRef: ElementRef, directive: Directive) {
this.value = elementRef === directive.elementRef; this.value = elementRef === directive.elementRef;
} }
static ngDirectiveDef = defineDirective( static ngDirectiveDef = defineDirective({
{factory: () => new DirectiveSameInstance(injectElementRef(), inject(Directive))}); type: DirectiveSameInstance,
factory: () => new DirectiveSameInstance(injectElementRef(), inject(Directive))
});
} }
function Template(ctx: any, cm: boolean) { function Template(ctx: any, cm: boolean) {
@ -116,8 +123,11 @@ describe('di', () => {
constructor(public templateRef: TemplateRef<any>) { constructor(public templateRef: TemplateRef<any>) {
this.value = (templateRef.constructor as any).name; this.value = (templateRef.constructor as any).name;
} }
static ngDirectiveDef = defineDirective( static ngDirectiveDef = defineDirective({
{factory: () => new Directive(injectTemplateRef()), features: [PublicFeature]}); type: Directive,
factory: () => new Directive(injectTemplateRef()),
features: [PublicFeature]
});
} }
class DirectiveSameInstance { class DirectiveSameInstance {
@ -125,8 +135,10 @@ describe('di', () => {
constructor(templateRef: TemplateRef<any>, directive: Directive) { constructor(templateRef: TemplateRef<any>, directive: Directive) {
this.value = templateRef === directive.templateRef; this.value = templateRef === directive.templateRef;
} }
static ngDirectiveDef = defineDirective( static ngDirectiveDef = defineDirective({
{factory: () => new DirectiveSameInstance(injectTemplateRef(), inject(Directive))}); type: DirectiveSameInstance,
factory: () => new DirectiveSameInstance(injectTemplateRef(), inject(Directive))
});
} }
@ -149,8 +161,11 @@ describe('di', () => {
constructor(public viewContainerRef: ViewContainerRef) { constructor(public viewContainerRef: ViewContainerRef) {
this.value = (viewContainerRef.constructor as any).name; this.value = (viewContainerRef.constructor as any).name;
} }
static ngDirectiveDef = defineDirective( static ngDirectiveDef = defineDirective({
{factory: () => new Directive(injectViewContainerRef()), features: [PublicFeature]}); type: Directive,
factory: () => new Directive(injectViewContainerRef()),
features: [PublicFeature]
});
} }
class DirectiveSameInstance { class DirectiveSameInstance {
@ -159,6 +174,7 @@ describe('di', () => {
this.value = viewContainerRef === directive.viewContainerRef; this.value = viewContainerRef === directive.viewContainerRef;
} }
static ngDirectiveDef = defineDirective({ static ngDirectiveDef = defineDirective({
type: DirectiveSameInstance,
factory: () => new DirectiveSameInstance(injectViewContainerRef(), inject(Directive)) factory: () => new DirectiveSameInstance(injectViewContainerRef(), inject(Directive))
}); });
} }
@ -224,7 +240,7 @@ describe('di', () => {
constructor(public value: string) {} constructor(public value: string) {}
static ngComponentDef = defineComponent({ static ngComponentDef = defineComponent({
// type: MyApp, type: MyApp,
tag: 'my-app', tag: 'my-app',
factory: () => new MyApp(inject(String as any, InjectFlags.Default, 'DefaultValue')), factory: () => new MyApp(inject(String as any, InjectFlags.Default, 'DefaultValue')),
template: () => null template: () => null
@ -237,8 +253,11 @@ describe('di', () => {
it('should inject from parent view', () => { it('should inject from parent view', () => {
class ParentDirective { class ParentDirective {
static ngDirectiveDef = static ngDirectiveDef = defineDirective({
defineDirective({factory: () => new ParentDirective(), features: [PublicFeature]}); type: ParentDirective,
factory: () => new ParentDirective(),
features: [PublicFeature]
});
} }
class ChildDirective { class ChildDirective {
@ -247,6 +266,7 @@ describe('di', () => {
this.value = (parent.constructor as any).name; this.value = (parent.constructor as any).name;
} }
static ngDirectiveDef = defineDirective({ static ngDirectiveDef = defineDirective({
type: ChildDirective,
factory: () => new ChildDirective(inject(ParentDirective)), factory: () => new ChildDirective(inject(ParentDirective)),
features: [PublicFeature] features: [PublicFeature]
}); });
@ -257,8 +277,10 @@ describe('di', () => {
constructor(parent: ParentDirective, child: ChildDirective) { constructor(parent: ParentDirective, child: ChildDirective) {
this.value = parent === child.parent; this.value = parent === child.parent;
} }
static ngDirectiveDef = defineDirective( static ngDirectiveDef = defineDirective({
{factory: () => new Child2Directive(inject(ParentDirective), inject(ChildDirective))}); type: Child2Directive,
factory: () => new Child2Directive(inject(ParentDirective), inject(ChildDirective))
});
} }
function Template(ctx: any, cm: boolean) { function Template(ctx: any, cm: boolean) {
@ -290,7 +312,7 @@ describe('di', () => {
describe('getOrCreateNodeInjector', () => { describe('getOrCreateNodeInjector', () => {
it('should handle initial undefined state', () => { it('should handle initial undefined state', () => {
const contentView = createLView(-1, null !, {data: []}); const contentView = createLView(-1, null !, createTView());
const oldView = enterView(contentView, null !); const oldView = enterView(contentView, null !);
try { try {
const parent = createLNode(0, LNodeFlags.Element, null, null); const parent = createLNode(0, LNodeFlags.Element, null, null);

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {E, b, defineDirective, e, m, p} from '../../src/render3/index'; import {E, b, defineDirective, e, m, p, r} from '../../src/render3/index';
import {renderToHtml} from './render_util'; import {renderToHtml} from './render_util';
@ -20,8 +20,9 @@ describe('directive', () => {
class Directive { class Directive {
klass = 'foo'; klass = 'foo';
static ngDirectiveDef = defineDirective({ static ngDirectiveDef = defineDirective({
type: Directive,
factory: () => directiveInstance = new Directive, factory: () => directiveInstance = new Directive,
refresh: (directiveIndex: number, elementIndex: number) => { hostBindings: (directiveIndex: number, elementIndex: number) => {
p(elementIndex, 'className', b(m<Directive>(directiveIndex).klass)); p(elementIndex, 'className', b(m<Directive>(directiveIndex).klass));
} }
}); });
@ -32,7 +33,8 @@ describe('directive', () => {
E(0, 'span', null, [Directive]); E(0, 'span', null, [Directive]);
e(); e();
} }
Directive.ngDirectiveDef.r(1, 0); Directive.ngDirectiveDef.h(1, 0);
r(1, 0);
} }
expect(renderToHtml(Template, {})).toEqual('<span class="foo"></span>'); expect(renderToHtml(Template, {})).toEqual('<span class="foo"></span>');

View File

@ -42,8 +42,12 @@ describe('exports', () => {
class MyComponent { class MyComponent {
name = 'Nancy'; name = 'Nancy';
static ngComponentDef = static ngComponentDef = defineComponent({
defineComponent({tag: 'comp', template: function() {}, factory: () => new MyComponent}); type: MyComponent,
tag: 'comp',
template: function() {},
factory: () => new MyComponent
});
} }
expect(renderToHtml(Template, {})).toEqual('<comp></comp>Nancy'); expect(renderToHtml(Template, {})).toEqual('<comp></comp>Nancy');
@ -55,14 +59,19 @@ describe('exports', () => {
let myDir: MyDir; let myDir: MyDir;
class MyComponent { class MyComponent {
constructor() { myComponent = this; } constructor() { myComponent = this; }
static ngComponentDef = static ngComponentDef = defineComponent({
defineComponent({tag: 'comp', template: function() {}, factory: () => new MyComponent}); type: MyComponent,
tag: 'comp',
template: function() {},
factory: () => new MyComponent
});
} }
class MyDir { class MyDir {
myDir: MyComponent; myDir: MyComponent;
constructor() { myDir = this; } constructor() { myDir = this; }
static ngDirectiveDef = defineDirective({factory: () => new MyDir, inputs: {myDir: 'myDir'}}); static ngDirectiveDef =
defineDirective({type: MyDir, factory: () => new MyDir, inputs: {myDir: 'myDir'}});
} }
/** <comp #myComp></comp> <div [myDir]="myComp"></div> */ /** <comp #myComp></comp> <div [myDir]="myComp"></div> */
@ -94,7 +103,7 @@ describe('exports', () => {
class SomeDir { class SomeDir {
name = 'Drew'; name = 'Drew';
static ngDirectiveDef = defineDirective({factory: () => new SomeDir}); static ngDirectiveDef = defineDirective({type: SomeDir, factory: () => new SomeDir});
} }
expect(renderToHtml(Template, {})).toEqual('<div></div>Drew'); expect(renderToHtml(Template, {})).toEqual('<div></div>Drew');
@ -175,6 +184,7 @@ describe('exports', () => {
constructor() { myComponent = this; } constructor() { myComponent = this; }
static ngComponentDef = defineComponent({ static ngComponentDef = defineComponent({
type: MyComponent,
tag: 'comp', tag: 'comp',
template: function(ctx: MyComponent, cm: boolean) {}, template: function(ctx: MyComponent, cm: boolean) {},
factory: () => new MyComponent factory: () => new MyComponent
@ -187,7 +197,7 @@ describe('exports', () => {
constructor() { myDir = this; } constructor() { myDir = this; }
static ngDirectiveDef = static ngDirectiveDef =
defineDirective({factory: () => new MyDir, inputs: {myDir: 'myDir'}}); defineDirective({type: MyDir, factory: () => new MyDir, inputs: {myDir: 'myDir'}});
} }
/** <div [myDir]="myComp"></div><comp #myComp></comp> */ /** <div [myDir]="myComp"></div><comp #myComp></comp> */
@ -230,8 +240,12 @@ describe('exports', () => {
constructor() { myComponent = this; } constructor() { myComponent = this; }
static ngComponentDef = static ngComponentDef = defineComponent({
defineComponent({tag: 'comp', template: function() {}, factory: () => new MyComponent}); type: MyComponent,
tag: 'comp',
template: function() {},
factory: () => new MyComponent
});
} }
expect(renderToHtml(Template, {})).toEqual('oneNancy<comp></comp><input value="one">'); expect(renderToHtml(Template, {})).toEqual('oneNancy<comp></comp><input value="one">');
}); });

View File

@ -210,6 +210,7 @@ describe('render3 integration test', () => {
value = ' one'; value = ' one';
static ngComponentDef = defineComponent({ static ngComponentDef = defineComponent({
type: TodoComponent,
tag: 'todo', tag: 'todo',
template: function TodoTemplate(ctx: any, cm: boolean) { template: function TodoTemplate(ctx: any, cm: boolean) {
if (cm) { if (cm) {
@ -233,7 +234,7 @@ describe('render3 integration test', () => {
e(); e();
} }
TodoComponent.ngComponentDef.h(1, 0); TodoComponent.ngComponentDef.h(1, 0);
TodoComponent.ngComponentDef.r(1, 0); r(1, 0);
} }
expect(renderToHtml(Template, null)).toEqual('<todo><p>Todo one</p></todo>'); expect(renderToHtml(Template, null)).toEqual('<todo><p>Todo one</p></todo>');
@ -247,7 +248,7 @@ describe('render3 integration test', () => {
T(2, 'two'); T(2, 'two');
} }
TodoComponent.ngComponentDef.h(1, 0); TodoComponent.ngComponentDef.h(1, 0);
TodoComponent.ngComponentDef.r(1, 0); r(1, 0);
} }
expect(renderToHtml(Template, null)).toEqual('<todo><p>Todo one</p></todo>two'); expect(renderToHtml(Template, null)).toEqual('<todo><p>Todo one</p></todo>two');
}); });
@ -266,8 +267,8 @@ describe('render3 integration test', () => {
} }
TodoComponent.ngComponentDef.h(1, 0); TodoComponent.ngComponentDef.h(1, 0);
TodoComponent.ngComponentDef.h(3, 2); TodoComponent.ngComponentDef.h(3, 2);
TodoComponent.ngComponentDef.r(1, 0); r(1, 0);
TodoComponent.ngComponentDef.r(3, 2); r(3, 2);
} }
expect(renderToHtml(Template, null)) expect(renderToHtml(Template, null))
.toEqual('<todo><p>Todo one</p></todo><todo><p>Todo one</p></todo>'); .toEqual('<todo><p>Todo one</p></todo><todo><p>Todo one</p></todo>');
@ -279,6 +280,7 @@ describe('render3 integration test', () => {
class TodoComponentHostBinding { class TodoComponentHostBinding {
title = 'one'; title = 'one';
static ngComponentDef = defineComponent({ static ngComponentDef = defineComponent({
type: TodoComponentHostBinding,
tag: 'todo', tag: 'todo',
template: function TodoComponentHostBindingTemplate( template: function TodoComponentHostBindingTemplate(
ctx: TodoComponentHostBinding, cm: boolean) { ctx: TodoComponentHostBinding, cm: boolean) {
@ -301,7 +303,7 @@ describe('render3 integration test', () => {
e(); e();
} }
TodoComponentHostBinding.ngComponentDef.h(1, 0); TodoComponentHostBinding.ngComponentDef.h(1, 0);
TodoComponentHostBinding.ngComponentDef.r(1, 0); r(1, 0);
} }
expect(renderToHtml(Template, {})).toEqual('<todo title="one">one</todo>'); expect(renderToHtml(Template, {})).toEqual('<todo title="one">one</todo>');
@ -314,6 +316,7 @@ describe('render3 integration test', () => {
class MyComp { class MyComp {
name = 'Bess'; name = 'Bess';
static ngComponentDef = defineComponent({ static ngComponentDef = defineComponent({
type: MyComp,
tag: 'comp', tag: 'comp',
template: function MyCompTemplate(ctx: any, cm: boolean) { template: function MyCompTemplate(ctx: any, cm: boolean) {
if (cm) { if (cm) {
@ -333,7 +336,7 @@ describe('render3 integration test', () => {
e(); e();
} }
MyComp.ngComponentDef.h(1, 0); MyComp.ngComponentDef.h(1, 0);
MyComp.ngComponentDef.r(1, 0); r(1, 0);
} }
expect(renderToHtml(Template, null)).toEqual('<comp><p>Bess</p></comp>'); expect(renderToHtml(Template, null)).toEqual('<comp><p>Bess</p></comp>');
@ -348,6 +351,7 @@ describe('render3 integration test', () => {
class MyComp { class MyComp {
condition: boolean; condition: boolean;
static ngComponentDef = defineComponent({ static ngComponentDef = defineComponent({
type: MyComp,
tag: 'comp', tag: 'comp',
template: function MyCompTemplate(ctx: any, cm: boolean) { template: function MyCompTemplate(ctx: any, cm: boolean) {
if (cm) { if (cm) {
@ -379,7 +383,7 @@ describe('render3 integration test', () => {
} }
p(0, 'condition', b(ctx.condition)); p(0, 'condition', b(ctx.condition));
MyComp.ngComponentDef.h(1, 0); MyComp.ngComponentDef.h(1, 0);
MyComp.ngComponentDef.r(1, 0); r(1, 0);
} }
expect(renderToHtml(Template, {condition: true})).toEqual('<comp><div>text</div></comp>'); expect(renderToHtml(Template, {condition: true})).toEqual('<comp><div>text</div></comp>');

File diff suppressed because it is too large Load Diff

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {C, E, L, T, V, cR, cr, defineComponent, e, v} from '../../src/render3/index'; import {C, E, L, T, V, cR, cr, defineComponent, e, r, v} from '../../src/render3/index';
import {containerEl, renderComponent, renderToHtml} from './render_util'; import {containerEl, renderComponent, renderToHtml} from './render_util';
@ -21,6 +21,7 @@ describe('event listeners', () => {
onClick() { this.counter++; } onClick() { this.counter++; }
static ngComponentDef = defineComponent({ static ngComponentDef = defineComponent({
type: MyComp,
tag: 'comp', tag: 'comp',
/** <button (click)="onClick()"> Click me </button> */ /** <button (click)="onClick()"> Click me </button> */
template: function CompTemplate(ctx: any, cm: boolean) { template: function CompTemplate(ctx: any, cm: boolean) {
@ -207,8 +208,8 @@ describe('event listeners', () => {
} }
MyComp.ngComponentDef.h(2, 1); MyComp.ngComponentDef.h(2, 1);
MyComp.ngComponentDef.h(4, 3); MyComp.ngComponentDef.h(4, 3);
MyComp.ngComponentDef.r(2, 1); r(2, 1);
MyComp.ngComponentDef.r(4, 3); r(4, 3);
v(); v();
} }
} }

View File

@ -8,7 +8,7 @@
import {EventEmitter} from '@angular/core'; import {EventEmitter} from '@angular/core';
import {C, E, L, LifecycleHook, T, V, b, cR, cr, defineComponent, defineDirective, e, l, p, v} from '../../src/render3/index'; import {C, E, L, T, V, b, cR, cr, defineComponent, defineDirective, e, p, r, v} from '../../src/render3/index';
import {containerEl, renderToHtml} from './render_util'; import {containerEl, renderToHtml} from './render_util';
@ -20,6 +20,7 @@ describe('outputs', () => {
resetStream = new EventEmitter(); resetStream = new EventEmitter();
static ngComponentDef = defineComponent({ static ngComponentDef = defineComponent({
type: ButtonToggle,
tag: 'button-toggle', tag: 'button-toggle',
template: function(ctx: any, cm: boolean) {}, template: function(ctx: any, cm: boolean) {},
factory: () => buttonToggle = new ButtonToggle(), factory: () => buttonToggle = new ButtonToggle(),
@ -32,8 +33,11 @@ describe('outputs', () => {
class OtherDir { class OtherDir {
changeStream = new EventEmitter(); changeStream = new EventEmitter();
static ngDirectiveDef = defineDirective( static ngDirectiveDef = defineDirective({
{factory: () => otherDir = new OtherDir, outputs: {changeStream: 'change'}}); type: OtherDir,
factory: () => otherDir = new OtherDir,
outputs: {changeStream: 'change'}
});
} }
it('should call component output function when event is emitted', () => { it('should call component output function when event is emitted', () => {
@ -45,7 +49,7 @@ describe('outputs', () => {
e(); e();
} }
ButtonToggle.ngComponentDef.h(1, 0); ButtonToggle.ngComponentDef.h(1, 0);
ButtonToggle.ngComponentDef.r(1, 0); r(1, 0);
} }
let counter = 0; let counter = 0;
@ -71,7 +75,7 @@ describe('outputs', () => {
e(); e();
} }
ButtonToggle.ngComponentDef.h(1, 0); ButtonToggle.ngComponentDef.h(1, 0);
ButtonToggle.ngComponentDef.r(1, 0); r(1, 0);
} }
let counter = 0; let counter = 0;
@ -95,7 +99,7 @@ describe('outputs', () => {
e(); e();
} }
ButtonToggle.ngComponentDef.h(1, 0); ButtonToggle.ngComponentDef.h(1, 0);
ButtonToggle.ngComponentDef.r(1, 0); r(1, 0);
} }
const ctx = {counter: 0}; const ctx = {counter: 0};
@ -129,7 +133,7 @@ describe('outputs', () => {
e(); e();
} }
ButtonToggle.ngComponentDef.h(1, 0); ButtonToggle.ngComponentDef.h(1, 0);
ButtonToggle.ngComponentDef.r(1, 0); r(1, 0);
v(); v();
} }
} }
@ -179,7 +183,7 @@ describe('outputs', () => {
e(); e();
} }
ButtonToggle.ngComponentDef.h(1, 0); ButtonToggle.ngComponentDef.h(1, 0);
ButtonToggle.ngComponentDef.r(1, 0); r(1, 0);
v(); v();
} }
} }
@ -212,13 +216,10 @@ describe('outputs', () => {
ngOnDestroy() { this.events.push('destroy'); } ngOnDestroy() { this.events.push('destroy'); }
static ngComponentDef = defineComponent({ static ngComponentDef = defineComponent({
type: DestroyComp,
tag: 'destroy-comp', tag: 'destroy-comp',
template: function(ctx: any, cm: boolean) {}, template: function(ctx: any, cm: boolean) {},
factory: () => { factory: () => destroyComp = new DestroyComp()
destroyComp = new DestroyComp();
l(LifecycleHook.ON_DESTROY, destroyComp, destroyComp.ngOnDestroy);
return destroyComp;
}
}); });
} }
@ -251,8 +252,8 @@ describe('outputs', () => {
} }
ButtonToggle.ngComponentDef.h(3, 2); ButtonToggle.ngComponentDef.h(3, 2);
DestroyComp.ngComponentDef.h(5, 4); DestroyComp.ngComponentDef.h(5, 4);
ButtonToggle.ngComponentDef.r(3, 2); r(3, 2);
DestroyComp.ngComponentDef.r(5, 4); r(5, 4);
v(); v();
} }
} }
@ -291,8 +292,8 @@ describe('outputs', () => {
class MyButton { class MyButton {
click = new EventEmitter(); click = new EventEmitter();
static ngDirectiveDef = static ngDirectiveDef = defineDirective(
defineDirective({factory: () => buttonDir = new MyButton, outputs: {click: 'click'}}); {type: MyButton, factory: () => buttonDir = new MyButton, outputs: {click: 'click'}});
} }
function Template(ctx: any, cm: boolean) { function Template(ctx: any, cm: boolean) {
@ -325,7 +326,7 @@ describe('outputs', () => {
e(); e();
} }
ButtonToggle.ngComponentDef.h(1, 0); ButtonToggle.ngComponentDef.h(1, 0);
ButtonToggle.ngComponentDef.r(1, 0); r(1, 0);
} }
let counter = 0; let counter = 0;
@ -344,8 +345,8 @@ describe('outputs', () => {
class OtherDir { class OtherDir {
change: boolean; change: boolean;
static ngDirectiveDef = static ngDirectiveDef = defineDirective(
defineDirective({factory: () => otherDir = new OtherDir, inputs: {change: 'change'}}); {type: OtherDir, factory: () => otherDir = new OtherDir, inputs: {change: 'change'}});
} }
/** <button-toggle (change)="onChange()" otherDir [change]="change"></button-toggle> */ /** <button-toggle (change)="onChange()" otherDir [change]="change"></button-toggle> */
@ -357,7 +358,7 @@ describe('outputs', () => {
} }
p(0, 'change', b(ctx.change)); p(0, 'change', b(ctx.change));
ButtonToggle.ngComponentDef.h(1, 0); ButtonToggle.ngComponentDef.h(1, 0);
ButtonToggle.ngComponentDef.r(1, 0); r(1, 0);
} }
let counter = 0; let counter = 0;
@ -400,7 +401,7 @@ describe('outputs', () => {
e(); e();
} }
ButtonToggle.ngComponentDef.h(1, 0); ButtonToggle.ngComponentDef.h(1, 0);
ButtonToggle.ngComponentDef.r(1, 0); r(1, 0);
v(); v();
} else { } else {
if (V(1)) { if (V(1)) {

View File

@ -8,7 +8,7 @@
import {EventEmitter} from '@angular/core'; import {EventEmitter} from '@angular/core';
import {C, E, L, T, V, b, b1, cR, cr, defineComponent, defineDirective, e, m, p, t, v} from '../../src/render3/index'; import {C, E, L, T, V, b, b1, cR, cr, defineComponent, defineDirective, e, m, p, r, t, v} from '../../src/render3/index';
import {NO_CHANGE} from '../../src/render3/instructions'; import {NO_CHANGE} from '../../src/render3/instructions';
import {renderToHtml} from './render_util'; import {renderToHtml} from './render_util';
@ -69,8 +69,8 @@ describe('elementProperty', () => {
class MyButton { class MyButton {
disabled: boolean; disabled: boolean;
static ngDirectiveDef = static ngDirectiveDef = defineDirective(
defineDirective({factory: () => button = new MyButton(), inputs: {disabled: 'disabled'}}); {type: MyButton, factory: () => button = new MyButton(), inputs: {disabled: 'disabled'}});
} }
class OtherDir { class OtherDir {
@ -78,6 +78,7 @@ describe('elementProperty', () => {
clickStream = new EventEmitter(); clickStream = new EventEmitter();
static ngDirectiveDef = defineDirective({ static ngDirectiveDef = defineDirective({
type: OtherDir,
factory: () => otherDir = new OtherDir(), factory: () => otherDir = new OtherDir(),
inputs: {id: 'id'}, inputs: {id: 'id'},
outputs: {clickStream: 'click'} outputs: {clickStream: 'click'}
@ -140,6 +141,7 @@ describe('elementProperty', () => {
id: number; id: number;
static ngComponentDef = defineComponent({ static ngComponentDef = defineComponent({
type: Comp,
tag: 'comp', tag: 'comp',
template: function(ctx: any, cm: boolean) {}, template: function(ctx: any, cm: boolean) {},
factory: () => comp = new Comp(), factory: () => comp = new Comp(),
@ -155,7 +157,7 @@ describe('elementProperty', () => {
} }
p(0, 'id', b(ctx.id)); p(0, 'id', b(ctx.id));
Comp.ngComponentDef.h(1, 0); Comp.ngComponentDef.h(1, 0);
Comp.ngComponentDef.r(1, 0); r(1, 0);
} }
expect(renderToHtml(Template, {id: 1})).toEqual(`<comp></comp>`); expect(renderToHtml(Template, {id: 1})).toEqual(`<comp></comp>`);
@ -172,6 +174,7 @@ describe('elementProperty', () => {
disabled: boolean; disabled: boolean;
static ngDirectiveDef = defineDirective({ static ngDirectiveDef = defineDirective({
type: OtherDisabledDir,
factory: () => otherDisabledDir = new OtherDisabledDir(), factory: () => otherDisabledDir = new OtherDisabledDir(),
inputs: {disabled: 'disabled'} inputs: {disabled: 'disabled'}
}); });
@ -231,8 +234,8 @@ describe('elementProperty', () => {
class IdDir { class IdDir {
idNumber: number; idNumber: number;
static ngDirectiveDef = static ngDirectiveDef = defineDirective(
defineDirective({factory: () => idDir = new IdDir(), inputs: {idNumber: 'id'}}); {type: IdDir, factory: () => idDir = new IdDir(), inputs: {idNumber: 'id'}});
} }
/** /**
@ -294,6 +297,7 @@ describe('elementProperty', () => {
changeStream = new EventEmitter(); changeStream = new EventEmitter();
static ngDirectiveDef = defineDirective({ static ngDirectiveDef = defineDirective({
type: MyDir,
factory: () => myDir = new MyDir(), factory: () => myDir = new MyDir(),
inputs: {role: 'role', direction: 'dir'}, inputs: {role: 'role', direction: 'dir'},
outputs: {changeStream: 'change'} outputs: {changeStream: 'change'}
@ -304,8 +308,8 @@ describe('elementProperty', () => {
class MyDirB { class MyDirB {
roleB: string; roleB: string;
static ngDirectiveDef = static ngDirectiveDef = defineDirective(
defineDirective({factory: () => dirB = new MyDirB(), inputs: {roleB: 'role'}}); {type: MyDirB, factory: () => dirB = new MyDirB(), inputs: {roleB: 'role'}});
} }
it('should set input property based on attribute if existing', () => { it('should set input property based on attribute if existing', () => {
@ -467,6 +471,7 @@ describe('elementProperty', () => {
class Comp { class Comp {
static ngComponentDef = defineComponent({ static ngComponentDef = defineComponent({
type: Comp,
tag: 'comp', tag: 'comp',
template: function(ctx: any, cm: boolean) { template: function(ctx: any, cm: boolean) {
if (cm) { if (cm) {
@ -497,7 +502,7 @@ describe('elementProperty', () => {
e(); e();
} }
Comp.ngComponentDef.h(1, 0); Comp.ngComponentDef.h(1, 0);
Comp.ngComponentDef.r(1, 0); r(1, 0);
v(); v();
} }
} }

View File

@ -6,8 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {QUERY_READ_CONTAINER_REF, QUERY_READ_ELEMENT_REF, QUERY_READ_FROM_NODE, QUERY_READ_TEMPLATE_REF} from '../../src/render3/di'; import {QUERY_READ_CONTAINER_REF, QUERY_READ_ELEMENT_REF, QUERY_READ_FROM_NODE, QUERY_READ_TEMPLATE_REF} from '../../src/render3/di';
import {C, E, Q, QueryList, e, m, qR} from '../../src/render3/index'; import {C, E, Q, QueryList, V, cR, cr, detectChanges, e, m, qR, v} from '../../src/render3/index';
import {QueryReadType} from '../../src/render3/interfaces/query';
import {createComponent, createDirective, renderComponent} from './render_util'; import {createComponent, createDirective, renderComponent} from './render_util';
@ -511,7 +510,6 @@ describe('query', () => {
it('should not add results to query if a requested token cant be read', () => { it('should not add results to query if a requested token cant be read', () => {
const Child = createDirective(); const Child = createDirective();
let childInstance, div;
/** /**
* <div #foo></div> * <div #foo></div>
* class Cmpt { * class Cmpt {
@ -522,7 +520,7 @@ describe('query', () => {
let tmp: any; let tmp: any;
if (cm) { if (cm) {
m(0, Q(['foo'], false, Child)); m(0, Q(['foo'], false, Child));
div = E(1, 'div', null, null, ['foo', '']); E(1, 'div', null, null, ['foo', '']);
e(); e();
} }
qR(tmp = m<QueryList<any>>(0)) && (ctx.query = tmp as QueryList<any>); qR(tmp = m<QueryList<any>>(0)) && (ctx.query = tmp as QueryList<any>);
@ -534,4 +532,299 @@ describe('query', () => {
}); });
}); });
describe('view boundaries', () => {
it('should report results in embedded views', () => {
let firstEl;
/**
* <ng-template [ngIf]="exp">
* <div #foo></div>
* </ng-template>
* class Cmpt {
* @ViewChildren('foo') query;
* }
*/
const Cmpt = createComponent('cmpt', function(ctx: any, cm: boolean) {
let tmp: any;
if (cm) {
m(0, Q(['foo'], true, QUERY_READ_FROM_NODE));
C(1);
}
cR(1);
{
if (ctx.exp) {
let cm1 = V(1);
{
if (cm1) {
firstEl = E(0, 'div', null, null, ['foo', '']);
e();
}
}
v();
}
}
cr();
qR(tmp = m<QueryList<any>>(0)) && (ctx.query = tmp as QueryList<any>);
});
const cmptInstance = renderComponent(Cmpt);
const query = (cmptInstance.query as any);
expect(query.length).toBe(0);
cmptInstance.exp = true;
detectChanges(cmptInstance);
expect(query.length).toBe(1);
expect(query.first.nativeElement).toBe(firstEl);
cmptInstance.exp = false;
detectChanges(cmptInstance);
expect(query.length).toBe(0);
});
it('should add results from embedded views in the correct order - views and elements mix',
() => {
let firstEl, lastEl, viewEl;
/**
* <span #foo></span>
* <ng-template [ngIf]="exp">
* <div #foo></div>
* </ng-template>
* <span #foo></span>
* class Cmpt {
* @ViewChildren('foo') query;
* }
*/
const Cmpt = createComponent('cmpt', function(ctx: any, cm: boolean) {
let tmp: any;
if (cm) {
m(0, Q(['foo'], true, QUERY_READ_FROM_NODE));
firstEl = E(1, 'b', null, null, ['foo', '']);
e();
C(2);
lastEl = E(3, 'i', null, null, ['foo', '']);
e();
}
cR(2);
{
if (ctx.exp) {
let cm1 = V(1);
{
if (cm1) {
viewEl = E(0, 'div', null, null, ['foo', '']);
e();
}
}
v();
}
}
cr();
qR(tmp = m<QueryList<any>>(0)) && (ctx.query = tmp as QueryList<any>);
});
const cmptInstance = renderComponent(Cmpt);
const query = (cmptInstance.query as any);
expect(query.length).toBe(2);
expect(query.first.nativeElement).toBe(firstEl);
expect(query.last.nativeElement).toBe(lastEl);
cmptInstance.exp = true;
detectChanges(cmptInstance);
expect(query.length).toBe(3);
expect(query.toArray()[0].nativeElement).toBe(firstEl);
expect(query.toArray()[1].nativeElement).toBe(viewEl);
expect(query.toArray()[2].nativeElement).toBe(lastEl);
cmptInstance.exp = false;
detectChanges(cmptInstance);
expect(query.length).toBe(2);
expect(query.first.nativeElement).toBe(firstEl);
expect(query.last.nativeElement).toBe(lastEl);
});
it('should add results from embedded views in the correct order - views side by side', () => {
let firstEl, lastEl;
/**
* <ng-template [ngIf]="exp1">
* <div #foo></div>
* </ng-template>
* <ng-template [ngIf]="exp2">
* <span #foo></span>
* </ng-template>
* class Cmpt {
* @ViewChildren('foo') query;
* }
*/
const Cmpt = createComponent('cmpt', function(ctx: any, cm: boolean) {
let tmp: any;
if (cm) {
m(0, Q(['foo'], true, QUERY_READ_FROM_NODE));
C(1);
}
cR(1);
{
if (ctx.exp1) {
let cm1 = V(0);
{
if (cm1) {
firstEl = E(0, 'div', null, null, ['foo', '']);
e();
}
}
v();
}
if (ctx.exp2) {
let cm1 = V(1);
{
if (cm1) {
lastEl = E(0, 'span', null, null, ['foo', '']);
e();
}
}
v();
}
}
cr();
qR(tmp = m<QueryList<any>>(0)) && (ctx.query = tmp as QueryList<any>);
});
const cmptInstance = renderComponent(Cmpt);
const query = (cmptInstance.query as any);
expect(query.length).toBe(0);
cmptInstance.exp2 = true;
detectChanges(cmptInstance);
expect(query.length).toBe(1);
expect(query.last.nativeElement).toBe(lastEl);
cmptInstance.exp1 = true;
detectChanges(cmptInstance);
expect(query.length).toBe(2);
expect(query.first.nativeElement).toBe(firstEl);
expect(query.last.nativeElement).toBe(lastEl);
});
it('should add results from embedded views in the correct order - nested views', () => {
let firstEl, lastEl;
/**
* <ng-template [ngIf]="exp1">
* <div #foo></div>
* <ng-template [ngIf]="exp2">
* <span #foo></span>
* </ng-template>
* </ng-template>
* class Cmpt {
* @ViewChildren('foo') query;
* }
*/
const Cmpt = createComponent('cmpt', function(ctx: any, cm: boolean) {
let tmp: any;
if (cm) {
m(0, Q(['foo'], true, QUERY_READ_FROM_NODE));
C(1);
}
cR(1);
{
if (ctx.exp1) {
let cm1 = V(0);
{
if (cm1) {
firstEl = E(0, 'div', null, null, ['foo', '']);
e();
C(1);
}
cR(1);
{
if (ctx.exp2) {
let cm2 = V(0);
{
if (cm2) {
lastEl = E(0, 'span', null, null, ['foo', '']);
e();
}
}
v();
}
}
cr();
}
v();
}
}
cr();
qR(tmp = m<QueryList<any>>(0)) && (ctx.query = tmp as QueryList<any>);
});
const cmptInstance = renderComponent(Cmpt);
const query = (cmptInstance.query as any);
expect(query.length).toBe(0);
cmptInstance.exp1 = true;
detectChanges(cmptInstance);
expect(query.length).toBe(1);
expect(query.first.nativeElement).toBe(firstEl);
cmptInstance.exp2 = true;
detectChanges(cmptInstance);
expect(query.length).toBe(2);
expect(query.first.nativeElement).toBe(firstEl);
expect(query.last.nativeElement).toBe(lastEl);
});
it('should support combination of deep and shallow queries', () => {
/**
* <ng-template [ngIf]="exp">
* <div #foo></div>
* </ng-template>
* <span #foo></span>
* class Cmpt {
* @ViewChildren('foo') query;
* }
*/
const Cmpt = createComponent('cmpt', function(ctx: any, cm: boolean) {
let tmp: any;
if (cm) {
m(0, Q(['foo'], true));
m(1, Q(['foo'], false));
C(2);
E(3, 'span', null, null, ['foo', '']);
e();
}
cR(2);
{
if (ctx.exp) {
let cm1 = V(0);
{
if (cm1) {
E(0, 'div', null, null, ['foo', '']);
e();
}
}
v();
}
}
cr();
qR(tmp = m<QueryList<any>>(0)) && (ctx.deep = tmp as QueryList<any>);
qR(tmp = m<QueryList<any>>(1)) && (ctx.shallow = tmp as QueryList<any>);
});
const cmptInstance = renderComponent(Cmpt);
const deep = (cmptInstance.deep as any);
const shallow = (cmptInstance.shallow as any);
expect(deep.length).toBe(1);
expect(shallow.length).toBe(1);
cmptInstance.exp = true;
detectChanges(cmptInstance);
expect(deep.length).toBe(2);
expect(shallow.length).toBe(1);
cmptInstance.exp = false;
detectChanges(cmptInstance);
expect(deep.length).toBe(1);
expect(shallow.length).toBe(1);
});
});
}); });

View File

@ -82,14 +82,20 @@ export function createComponent(
name: string, template: ComponentTemplate<any>): ComponentType<any> { name: string, template: ComponentTemplate<any>): ComponentType<any> {
return class Component { return class Component {
value: any; value: any;
static ngComponentDef = defineComponent( static ngComponentDef = defineComponent({
{tag: name, factory: () => new Component, template: template, features: [PublicFeature]}); type: Component,
tag: name,
factory: () => new Component,
template: template,
features: [PublicFeature]
});
}; };
} }
export function createDirective({exportAs}: {exportAs?: string} = {}): DirectiveType<any> { export function createDirective({exportAs}: {exportAs?: string} = {}): DirectiveType<any> {
return class Directive { return class Directive {
static ngDirectiveDef = defineDirective({ static ngDirectiveDef = defineDirective({
type: Directive,
factory: () => new Directive(), factory: () => new Directive(),
features: [PublicFeature], features: [PublicFeature],
exportAs: exportAs, exportAs: exportAs,

View File

@ -10,7 +10,7 @@ import {AnimationEvent} from '@angular/animations';
import {MockAnimationDriver, MockAnimationPlayer} from '@angular/animations/browser/testing'; import {MockAnimationDriver, MockAnimationPlayer} from '@angular/animations/browser/testing';
import {RendererType2, ViewEncapsulation} from '../../src/core'; import {RendererType2, ViewEncapsulation} from '../../src/core';
import {E, L, T, b, defineComponent, detectChanges, e, p} from '../../src/render3/index'; import {E, L, T, b, defineComponent, detectChanges, e, p, r} from '../../src/render3/index';
import {createRendererType2} from '../../src/view/index'; import {createRendererType2} from '../../src/view/index';
import {getAnimationRendererFactory2, getRendererFactory2} from './imported_renderer2'; import {getAnimationRendererFactory2, getRendererFactory2} from './imported_renderer2';
@ -29,6 +29,7 @@ describe('renderer factory lifecycle', () => {
class SomeComponent { class SomeComponent {
static ngComponentDef = defineComponent({ static ngComponentDef = defineComponent({
type: SomeComponent,
tag: 'some-component', tag: 'some-component',
template: function(ctx: SomeComponent, cm: boolean) { template: function(ctx: SomeComponent, cm: boolean) {
logs.push('component'); logs.push('component');
@ -42,6 +43,7 @@ describe('renderer factory lifecycle', () => {
class SomeComponentWhichThrows { class SomeComponentWhichThrows {
static ngComponentDef = defineComponent({ static ngComponentDef = defineComponent({
type: SomeComponentWhichThrows,
tag: 'some-component-with-Error', tag: 'some-component-with-Error',
template: function(ctx: SomeComponentWhichThrows, cm: boolean) { template: function(ctx: SomeComponentWhichThrows, cm: boolean) {
throw(new Error('SomeComponentWhichThrows threw')); throw(new Error('SomeComponentWhichThrows threw'));
@ -65,7 +67,7 @@ describe('renderer factory lifecycle', () => {
e(); e();
} }
SomeComponent.ngComponentDef.h(2, 1); SomeComponent.ngComponentDef.h(2, 1);
SomeComponent.ngComponentDef.r(2, 1); r(2, 1);
} }
beforeEach(() => { logs = []; }); beforeEach(() => { logs = []; });
@ -120,6 +122,7 @@ describe('animation renderer factory', () => {
class SomeComponent { class SomeComponent {
static ngComponentDef = defineComponent({ static ngComponentDef = defineComponent({
type: SomeComponent,
tag: 'some-component', tag: 'some-component',
template: function(ctx: SomeComponent, cm: boolean) { template: function(ctx: SomeComponent, cm: boolean) {
if (cm) { if (cm) {
@ -136,6 +139,7 @@ describe('animation renderer factory', () => {
eventLogs.push(`${event.fromState ? event.fromState : event.toState} - ${event.phaseName}`); eventLogs.push(`${event.fromState ? event.fromState : event.toState} - ${event.phaseName}`);
} }
static ngComponentDef = defineComponent({ static ngComponentDef = defineComponent({
type: SomeComponentWithAnimation,
tag: 'some-component', tag: 'some-component',
template: function(ctx: SomeComponentWithAnimation, cm: boolean) { template: function(ctx: SomeComponentWithAnimation, cm: boolean) {
if (cm) { if (cm) {

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {isDifferent} from '../../src/render3/util'; import {flatten, isDifferent} from '../../src/render3/util';
describe('util', () => { describe('util', () => {
@ -32,4 +32,20 @@ describe('util', () => {
expect(isDifferent(5, NaN)).toBeTruthy(); expect(isDifferent(5, NaN)).toBeTruthy();
}); });
}); });
describe('flatten', () => {
it('should flatten an empty array', () => { expect(flatten([])).toEqual([]); });
it('should flatten a flat array', () => { expect(flatten([1, 2, 3])).toEqual([1, 2, 3]); });
it('should flatten a nested array', () => {
expect(flatten([1, [2], 3])).toEqual([1, 2, 3]);
expect(flatten([[1], 2, [3]])).toEqual([1, 2, 3]);
expect(flatten([1, [2, [3]], 4])).toEqual([1, 2, 3, 4]);
expect(flatten([1, [2, [3]], [4]])).toEqual([1, 2, 3, 4]);
expect(flatten([1, [2, [3]], [[[4]]]])).toEqual([1, 2, 3, 4]);
expect(flatten([1, [], 2])).toEqual([1, 2]);
});
});
}); });

View File

@ -7,7 +7,7 @@
*/ */
import {TemplateRef, ViewContainerRef} from '../../src/core'; import {TemplateRef, ViewContainerRef} from '../../src/core';
import {C, T, b, cR, cr, defineComponent, defineDirective, injectTemplateRef, injectViewContainerRef, m, t} from '../../src/render3/index'; import {C, T, b, cR, cr, defineComponent, defineDirective, injectTemplateRef, injectViewContainerRef, m, r, t} from '../../src/render3/index';
import {renderComponent, toHtml} from './render_util'; import {renderComponent, toHtml} from './render_util';
@ -16,6 +16,7 @@ describe('ViewContainerRef', () => {
constructor(public viewContainer: ViewContainerRef, public template: TemplateRef<any>, ) {} constructor(public viewContainer: ViewContainerRef, public template: TemplateRef<any>, ) {}
static ngDirectiveDef = defineDirective({ static ngDirectiveDef = defineDirective({
type: TestDirective,
factory: () => new TestDirective(injectViewContainerRef(), injectTemplateRef(), ), factory: () => new TestDirective(injectViewContainerRef(), injectTemplateRef(), ),
}); });
} }
@ -24,6 +25,7 @@ describe('ViewContainerRef', () => {
testDir: TestDirective; testDir: TestDirective;
static ngComponentDef = defineComponent({ static ngComponentDef = defineComponent({
type: TestComponent,
tag: 'test-cmp', tag: 'test-cmp',
factory: () => new TestComponent(), factory: () => new TestComponent(),
template: (cmp: TestComponent, cm: boolean) => { template: (cmp: TestComponent, cm: boolean) => {
@ -38,7 +40,8 @@ describe('ViewContainerRef', () => {
} }
cR(0); cR(0);
cmp.testDir = m(1) as TestDirective; cmp.testDir = m(1) as TestDirective;
TestDirective.ngDirectiveDef.r(1, 0); TestDirective.ngDirectiveDef.h(1, 0);
r(1, 0);
cr(); cr();
}, },
}); });