Compare commits

...

10 Commits

39 changed files with 2012 additions and 503 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -6,7 +6,7 @@
* 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 {TreeNode, buildTree, emptyTree} from '../util';
@ -36,6 +36,7 @@ export class TreeComponent {
/** @nocollapse */
static ngComponentDef: ComponentDef<TreeComponent> = defineComponent({
type: TreeComponent,
tag: 'tree',
template: function(ctx: TreeComponent, cm: boolean) {
if (cm) {
@ -58,7 +59,7 @@ export class TreeComponent {
}
p(0, 'data', b(ctx.data.left));
TreeComponent.ngComponentDef.h(1, 0);
TreeComponent.ngComponentDef.r(1, 0);
r(1, 0);
}
v();
}
@ -75,7 +76,7 @@ export class TreeComponent {
}
p(0, 'data', b(ctx.data.right));
TreeComponent.ngComponentDef.h(1, 0);
TreeComponent.ngComponentDef.r(1, 0);
r(1, 0);
}
v();
}
@ -92,6 +93,7 @@ export class TreeFunction {
/** @nocollapse */
static ngComponentDef: ComponentDef<TreeFunction> = defineComponent({
type: TreeFunction,
tag: 'tree',
template: function(ctx: TreeFunction, cm: boolean) {
// bit of a hack

View File

@ -34,5 +34,6 @@ export {
s as ɵs,
t as ɵt,
v as ɵv,
r as ɵr,
} from './render3/index';
// 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 {assertNotNull} from './assert';
import {NG_HOST_SYMBOL, createError, createLView, directiveCreate, enterView, hostElement, leaveView, locateHostElement, renderComponentOrTemplate} from './instructions';
import {ComponentDef, ComponentType, TypedComponentDef} from './interfaces/definition';
import {NG_HOST_SYMBOL, createError, createLView, createTView, directiveCreate, enterView, hostElement, leaveView, locateHostElement, renderComponentOrTemplate} from './instructions';
import {ComponentDef, ComponentType} from './interfaces/definition';
import {LElementNode} from './interfaces/node';
import {RElement, Renderer3, RendererFactory3, domRendererFactory3} from './interfaces/renderer';
import {notImplemented, stringify} from './util';
@ -166,13 +166,13 @@ export const NULL_INJECTOR: Injector = {
export function renderComponent<T>(
componentType: ComponentType<T>, opts: CreateComponentOptions = {}): T {
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;
let component: T;
const hostNode = locateHostElement(rendererFactory, opts.host || componentDef.tag);
const oldView = enterView(
createLView(
-1, rendererFactory.createRenderer(hostNode, componentDef.rendererType), {data: []}),
-1, rendererFactory.createRenderer(hostNode, componentDef.rendererType), createTView()),
null !);
try {
// 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 {diPublic} from './di';
import {componentRefresh} from './instructions';
import {ComponentDef, ComponentDefArgs, DirectiveDef, DirectiveDefArgs, TypedDirectiveDef} from './interfaces/definition';
import {ComponentDef, ComponentDefArgs, DirectiveDef, DirectiveDefArgs} from './interfaces/definition';
@ -34,22 +33,26 @@ import {ComponentDef, ComponentDefArgs, DirectiveDef, DirectiveDefArgs, TypedDir
* ```
*/
export function defineComponent<T>(componentDefinition: ComponentDefArgs<T>): ComponentDef<T> {
const type = componentDefinition.type;
const def = <ComponentDef<any>>{
type: type,
diPublic: null,
n: componentDefinition.factory,
tag: (componentDefinition as ComponentDefArgs<T>).tag || 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,
inputs: invertObject(componentDefinition.inputs),
outputs: invertObject(componentDefinition.outputs),
methods: invertObject(componentDefinition.methods),
rendererType: resolveRendererType2(componentDefinition.rendererType) || null,
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;
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) {
let simpleChanges = this[PRIVATE_PREFIX];
if (simpleChanges != null) {
@ -109,6 +112,7 @@ export function NgOnChangesFeature<T>(type: Type<T>): (definition: DirectiveDef<
};
}
export function PublicFeature<T>(definition: DirectiveDef<T>) {
definition.diPublic = diPublic;
}

View File

@ -19,7 +19,7 @@ import {Type} from '../type';
import {assertLessThan} from './assert';
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 {LContainerNode, LElementNode, LNode, LNodeFlags, LViewNode} from './interfaces/node';
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 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);
}
@ -167,7 +167,7 @@ export function diPublicInInjector(di: LInjector, def: TypedDirectiveDef<any>):
*
* @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);
}
@ -291,7 +291,7 @@ export function getOrCreateInjectable<T>(
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),
// 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) {
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
export {
LifecycleHook,
NO_CHANGE as NC,
bind as b,
@ -52,7 +50,6 @@ export {
elementStart as E,
elementStyle as s,
lifecycle as l,
listener as L,
memory as m,
@ -72,6 +69,9 @@ export {
query as Q,
queryRefresh as qR,
} from './query';
export {LifecycleHook} from './hooks';
// clang-format on
export {

View File

@ -8,39 +8,22 @@
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 {LContainer, TContainer} from './interfaces/container';
import {LInjector} from './interfaces/injector';
import {CssSelector, LProjection} from './interfaces/projection';
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 {assertNodeType, assertNodeOfPossibleTypes} from './node_assert';
import {appendChild, insertChild, insertView, processProjectedNode, removeView} from './node_manipulation';
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 {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
* 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. */
let currentView: LView;
// 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;
@ -112,10 +95,11 @@ let data: any[];
let bindingIndex: number;
/**
* When a view is destroyed, listeners need to be released
* and onDestroy callbacks need to be called. This cleanup array
* stores both listener data (in chunks of 4) and onDestroy data
* (in chunks of 2), as they'll be processed at the same time.
* When a view is destroyed, listeners need to be released and outputs need to be
* unsubscribed. This cleanup array stores both listener data (in chunks of 4)
* and output data (in chunks of 2) for a particular view. Combining the arrays
* saves on memory (70 bytes per array) and on a few bytes of code size (for two
* separate for loops).
*
* If it's a listener being stored:
* 1st index is: event name to remove
@ -123,15 +107,12 @@ let bindingIndex: number;
* 3rd index is: listener function
* 4th index is: useCapture boolean
*
* If it's an onDestroy function:
* 1st index is: onDestroy function
* If it's an output subscription:
* 1st index is: unsubscribe function
* 2nd index is: context for function
*/
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.
*
@ -151,7 +132,6 @@ export function enterView(newView: LView, host: LElementNode | LViewNode | null)
tData = newView.tView.data;
creationMode = newView.creationMode;
viewHookStartIndex = newView.viewHookStartIndex;
cleanup = newView.cleanup;
renderer = newView.renderer;
@ -161,6 +141,8 @@ export function enterView(newView: LView, host: LElementNode | LViewNode | null)
}
currentView = newView;
currentQuery = newView.query;
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.
*/
export function leaveView(newView: LView): void {
executeViewHooks();
executeViewHooks(currentView);
currentView.creationMode = false;
currentView.lifecycleStage = LifecycleStage.INIT;
currentView.tView.firstTemplatePass = false;
enterView(newView, null);
}
@ -189,10 +174,11 @@ export function createLView(
next: null,
bindingStartIndex: null,
creationMode: true,
viewHookStartIndex: null,
template: template,
context: context,
dynamicViewCount: 0,
lifecycleStage: LifecycleStage.INIT,
query: null,
};
return newView;
@ -320,7 +306,7 @@ export function renderEmbeddedTemplate<T>(
previousOrParentNode = null !;
let cm: boolean = false;
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);
cm = true;
}
@ -347,14 +333,13 @@ export function renderComponentOrTemplate<T>(
template(componentOrContext !, creationMode);
} else {
// 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)
(componentOrContext.constructor as ComponentType<T>).ngComponentDef.r(1, 0);
// so to refresh the component, refresh() needs to be called with (1, 0)
componentRefresh(1, 0);
}
} finally {
if (rendererFactory.end) {
rendererFactory.end();
}
hostView.creationMode = false;
leaveView(oldView);
}
}
@ -430,8 +415,6 @@ export function elementStart(
if (hostComponentDef) {
// TODO(mhevery): This assumes that the directives come in correct order, which
// is not guaranteed. Must be refactored to take it into account.
(hostComponentDef as TypedComponentDef<any>).type =
nameOrComponentType as ComponentType<any>;
directiveCreate(++index, hostComponentDef.n(), hostComponentDef, queryName);
}
hack_declareDirectives(index, directiveTypes, localRefs);
@ -459,7 +442,6 @@ function hack_declareDirectives(
// TODO(misko): refactor this to store the `DirectiveDef` in `TView.data`.
const directiveType = directiveTypes[i];
const directiveDef = directiveType.ngDirectiveDef;
(directiveDef as TypedDirectiveDef<any>).type = directiveType;
directiveCreate(
++index, directiveDef.n(), directiveDef, hack_findQueryName(directiveDef, localRefs));
}
@ -494,7 +476,19 @@ function hack_findQueryName(
* @returns 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 {
@ -613,6 +607,7 @@ export function elementEnd() {
ngDevMode && assertNodeType(previousOrParentNode, LNodeFlags.Element);
const query = previousOrParentNode.query;
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.
*/
export function textBinding<T>(index: number, value: T | NO_CHANGE): void {
// TODO(misko): I don't think index < nodes.length check is needed here.
let existingNode = index < data.length && data[index] as LTextNode;
if (existingNode && existingNode.native) {
ngDevMode && assertDataInRange(index);
let existingNode = data[index] as LTextNode;
ngDevMode && assertNotNull(existingNode, 'existing node');
if (existingNode.native) {
// If DOM node exists and value changed, update textContent
value !== NO_CHANGE &&
((renderer as ProceduralRenderer3).setValue ?
(renderer as ProceduralRenderer3).setValue(existingNode.native, stringify(value)) :
existingNode.native.textContent = stringify(value));
} else if (existingNode) {
} else {
// Node was created but DOM node creation was delayed. Create and append now.
existingNode.native =
((renderer as ProceduralRenderer3).createText ?
(renderer as ProceduralRenderer3).createText(stringify(value)) :
(renderer as ObjectOrientedRenderer3).createTextNode !(stringify(value)));
insertChild(existingNode, currentView);
} else {
text(index, value);
}
}
@ -906,6 +900,11 @@ export function directiveCreate<T>(
if (tNode && tNode.attrs) {
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;
}
@ -966,76 +965,6 @@ function generateInitialInputs(
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
@ -1071,14 +1000,17 @@ export function container(
renderParent = currentParent as LElementNode;
}
const node = createLNode(index, LNodeFlags.Container, comment, <LContainer>{
const lContainer = <LContainer>{
views: [],
nextIndex: 0, renderParent,
template: template == null ? null : template,
next: null,
parent: currentView,
dynamicViewCount: 0,
});
query: null
};
const node = createLNode(index, LNodeFlags.Container, comment, lContainer);
if (node.tNode == null) {
// TODO(misko): implement queryName caching
@ -1093,8 +1025,13 @@ export function container(
isParent = false;
ngDevMode && assertNodeType(previousOrParentNode, LNodeFlags.Container);
const query = previousOrParentNode.query;
query && query.addNode(previousOrParentNode);
const query = node.query;
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);
isParent = true;
(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.
const newView =
createLView(viewBlockId, renderer, getOrCreateEmbeddedTView(viewBlockId, container));
if (lContainer.query) {
newView.query = lContainer.query.enterView(lContainer.nextIndex);
}
enterView(newView, createLNode(null, LNodeFlags.View, null, newView));
lContainer.nextIndex++;
}
@ -1194,7 +1139,7 @@ function getOrCreateEmbeddedTView(viewIndex: number, parent: LContainerNode): TV
ngDevMode && assertNodeType(parent, LNodeFlags.Container);
const tContainer = (parent !.tNode as TContainerNode).data;
if (viewIndex >= tContainer.length || tContainer[viewIndex] == null) {
tContainer[viewIndex] = { data: [] } as TView;
tContainer[viewIndex] = createTView();
}
return tContainer[viewIndex];
}
@ -1215,7 +1160,6 @@ export function viewEnd(): void {
if (viewIdChanged) {
insertView(container, viewNode, containerState.nextIndex - 1);
currentView.creationMode = false;
}
}
leaveView(currentView !.parent !);
@ -1232,29 +1176,29 @@ export function viewEnd(): void {
*
* @param directiveIndex
* @param elementIndex
* @param template
*/
export const componentRefresh:
<T>(directiveIndex: number, elementIndex: number, template: ComponentTemplate<T>) =>
void = function<T>(
directiveIndex: number, elementIndex: number, template: ComponentTemplate<T>) {
ngDevMode && assertDataInRange(elementIndex);
const element = data ![elementIndex] as LElementNode;
ngDevMode && assertNodeOfPossibleTypes(element, LNodeFlags.Element, LNodeFlags.Container);
ngDevMode && assertNotEqual(element.data, null, 'isComponent');
ngDevMode && assertDataInRange(directiveIndex);
const hostView = element.data !;
ngDevMode && assertNotEqual(hostView, null, 'hostView');
const directive = data[directiveIndex];
const oldView = enterView(hostView, element);
try {
template(directive, creationMode);
} finally {
hostView.creationMode = false;
refreshDynamicChildren();
leaveView(oldView);
export function componentRefresh<T>(directiveIndex: number, elementIndex: number): void {
executeInitHooks(currentView);
executeContentHooks(currentView);
const template = (tData[directiveIndex] as ComponentDef<T>).template;
if (template != null) {
ngDevMode && assertDataInRange(elementIndex);
const element = data ![elementIndex] as LElementNode;
ngDevMode && assertNodeType(element, LNodeFlags.Element);
ngDevMode && assertNotEqual(element.data, null, 'isComponent');
ngDevMode && assertDataInRange(directiveIndex);
const directive = data[directiveIndex];
const hostView = element.data !;
ngDevMode && assertNotEqual(hostView, null, 'hostView');
const oldView = enterView(hostView, element);
try {
template(directive, creationMode);
} finally {
refreshDynamicChildren();
leaveView(oldView);
}
}
};
}
/**
* Instruction to distribute projectable nodes among <ng-content> occurrences in a given template.

View File

@ -8,9 +8,11 @@
import {ComponentTemplate} from './definition';
import {LElementNode, LViewNode} from './node';
import {LQuery} from './query';
import {LView, TView} from './view';
/** The state associated with an LContainer */
export interface LContainer {
/**
@ -67,12 +69,17 @@ export interface LContainer {
*/
readonly template: ComponentTemplate<any>|null;
/**
* 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.
*/
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.
*/
export interface DirectiveDef<T> {
/** Token representing the directive. Used by DI. */
type: Type<T>;
/** Function that makes a directive public to the DI system. */
diPublic: ((def: DirectiveDef<any>) => void)|null;
@ -64,42 +67,23 @@ export interface DirectiveDef<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
* 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;
/* 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> {
/**
* 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.
*
@ -122,31 +106,20 @@ export interface ComponentDef<T> extends DirectiveDef<T> {
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> {
type: Type<T>;
factory: () => T;
refresh?: (directiveIndex: number, elementIndex: number) => void;
inputs?: {[P in keyof T]?: string};
outputs?: {[P in keyof T]?: string};
methods?: {[P in keyof T]?: string};
features?: DirectiveDefFeature[];
hostBindings?: (directiveIndex: number, elementIndex: number) => void;
exportAs?: string;
}
export interface ComponentDefArgs<T> extends DirectiveDefArgs<T> {
tag: string;
template: ComponentTemplate<T>;
refresh?: (directiveIndex: number, elementIndex: number) => void;
hostBindings?: (directiveIndex: number, elementIndex: number) => void;
features?: ComponentDefFeature[];
rendererType?: RendererType2;
}

View File

@ -8,9 +8,7 @@
import {QueryList} from '../../linker';
import {Type} from '../../type';
import {LInjector} from './injector';
import {LContainerNode, LNode, LViewNode} from './node';
import {LNode} from './node';
/** Used for tracking queries (e.g. ViewChild, ContentChild). */
@ -25,19 +23,28 @@ export interface LQuery {
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;
/**
* 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.

View File

@ -9,6 +9,7 @@
import {LContainer} from './container';
import {ComponentTemplate, DirectiveDef} from './definition';
import {LElementNode, LViewNode, TNode} from './node';
import {LQuery} from './query';
import {Renderer3} from './renderer';
@ -33,9 +34,6 @@ export interface LView {
*/
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
* `LView`. Without this, the render method would have to keep a stack of
@ -72,9 +70,9 @@ export interface LView {
bindingStartIndex: number|null;
/**
* When a view is destroyed, listeners need to be released and onDestroy callbacks
* need to be called. 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
* When a view is destroyed, listeners need to be released and outputs need to be
* unsubscribed. This cleanup array stores both listener data (in chunks of 4)
* and output data (in chunks of 2) for a particular view. Combining the arrays
* saves on memory (70 bytes per array) and on a few bytes of code size (for two
* separate for loops).
*
@ -84,12 +82,29 @@ export interface LView {
* 3rd index is: listener function
* 4th index is: useCapture boolean
*
* If it's an onDestroy function:
* 1st index is: onDestroy function
* 2nd index is; context for function
* If it's an output subscription:
* 1st index is: unsubscribe function
* 2nd index is: context for function
*/
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.
*
@ -156,6 +171,11 @@ export interface LView {
* after refreshing the view itself.
*/
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 */
@ -172,7 +192,67 @@ export interface LViewOrLContainer {
*
* 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.

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 {LProjection, unusedValueExportToPlacateAjd as unused3} from './interfaces/projection';
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';
const unusedValueToPlacateAjd = unused1 + unused2 + unused3 + unused4 + unused5;
@ -222,8 +222,6 @@ export function insertView(
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;
}
@ -248,7 +246,7 @@ export function removeView(container: LContainerNode, removeIndex: number): LVie
destroyViewTree(viewNode.data);
addRemoveViewFromContainer(container, viewNode, false);
// 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;
}
@ -295,17 +293,36 @@ export function getParentState(state: LViewOrLContainer, rootView: LView): LView
* @param view The LView to clean up
*/
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 !;
for (let i = 0; i < cleanup.length - 1; i += 2) {
if (typeof cleanup[i] === 'string') {
cleanup ![i + 1].removeEventListener(cleanup[i], cleanup[i + 2], cleanup[i + 3]);
i += 2;
} else {
cleanup[i].call(cleanup[i + 1]);
if (cleanup != null) {
for (let i = 0; i < cleanup.length - 1; i += 2) {
if (typeof cleanup[i] === 'string') {
cleanup ![i + 1].removeEventListener(cleanup[i], cleanup[i + 2], cleanup[i + 3]);
i += 2;
} else {
cleanup[i].call(cleanup[i + 1]);
}
}
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.cleanup = null;
}
/**

View File

@ -10,37 +10,21 @@
// correctly implementing its interfaces for backwards compatibility.
import {Observable} from 'rxjs/Observable';
import {ElementRef as viewEngine_ElementRef} from '../linker/element_ref';
import {QueryList as viewEngine_QueryList} from '../linker/query_list';
import {TemplateRef as viewEngine_TemplateRef} from '../linker/template_ref';
import {Type} from '../type';
import {assertNotNull} from './assert';
import {assertEqual, assertNotNull} from './assert';
import {ReadFromInjectorFn, getOrCreateNodeInjectorForNode} from './di';
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 {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 {assertNodeOfPossibleTypes} from './node_assert';
import {flatten} from './util';
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
*/
@ -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 {
add(this.shallow, node);
add(this.deep, node);
}
insertView(container: LContainerNode, view: LViewNode, index: number): void {
throw new Error('Method not implemented.');
removeView(index: number): void {
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();
}
predicate = predicate.next;
}
}
removeView(container: LContainerNode, view: LViewNode, index: number): void {
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,
ii = i + ((flags & LNodeFlags.SIZE_MASK) >> LNodeFlags.SIZE_SHIFT);
i < ii; i++) {
const def = tData[i] as TypedDirectiveDef<any>;
const def = tData[i] as DirectiveDef<any>;
if (def.diPublic && def.type === type) {
return i;
}
@ -191,10 +248,10 @@ function add(predicate: QueryPredicate<any>| null, node: LNode) {
if (predicate.read !== null) {
const requestedRead = readFromNodeInjector(nodeInjector, node, predicate.read);
if (requestedRead !== null) {
predicate.values.push(requestedRead);
addMatch(predicate, requestedRead);
}
} else {
predicate.values.push(node.view.data[directiveIdx]);
addMatch(predicate, node.view.data[directiveIdx]);
}
}
} else {
@ -207,10 +264,10 @@ function add(predicate: QueryPredicate<any>| null, node: LNode) {
if (predicate.read !== null) {
const result = readFromNodeInjector(nodeInjector, node, predicate.read !, directiveIdx);
if (result !== null) {
predicate.values.push(result);
addMatch(predicate, result);
}
} 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>(
previous: QueryPredicate<any>| null, queryList: QueryList<T>, predicate: Type<T>| string[],
read: QueryReadType<T>| Type<T>| null): QueryPredicate<T> {
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 {
next: previous,
list: queryList,
type: isArray ? null : predicate as Type<T>,
selector: isArray ? predicate as string[] : null,
read: read,
values: values
values: (queryList as any as QueryList_<T>)._valuesTree
};
}
class QueryList_<T>/* implements viewEngine_QueryList<T> */ {
dirty: boolean = false;
changes: Observable<T>;
readonly dirty = true;
readonly changes: Observable<T>;
private _values: T[]|null = null;
/** @internal */
_valuesTree: any[] = [];
get length(): number {
ngDevMode && assertNotNull(this._values, 'refreshed');
@ -258,21 +319,6 @@ class QueryList_<T>/* implements viewEngine_QueryList<T> */ {
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[] {
throw new Error('Method not implemented.');
}
@ -295,10 +341,16 @@ class QueryList_<T>/* implements viewEngine_QueryList<T> */ {
ngDevMode && assertNotNull(this._values, 'refreshed');
return this._values !;
}
toString(): string { throw new Error('Method not implemented.'); }
reset(res: (any[]|T)[]): void { throw new Error('Method not implemented.'); }
toString(): string {
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.'); }
setDirty(): void { throw new Error('Method not implemented.'); }
setDirty(): void { (this as{dirty: boolean}).dirty = true; }
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.
export type QueryList<T> = viewEngine_QueryList<T>;
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 {
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`, () => {
class Component {
static ngComponentDef = defineComponent({
type: Component,
tag: 'div',
template: function Template(ctx: any, cm: any) {
if (cm) {

View File

@ -8,7 +8,7 @@
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 {renderComponent, toHtml} from './render_util';
@ -20,6 +20,7 @@ describe('@angular/common integration', () => {
items: string[] = ['first', 'second'];
static ngComponentDef = defineComponent({
type: MyApp,
factory: () => new MyApp(),
tag: 'my-app',
// <ul>
@ -33,7 +34,7 @@ describe('@angular/common integration', () => {
}
p(1, 'ngForOf', b(myApp.items));
cR(1);
NgForOf.ngDirectiveDef.r(2, 0);
r(2, 0);
cr();
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;
NgForOf.ngDirectiveDef = defineDirective({
type: NgForOfDef,
factory: () => new NgForOfDef(
injectViewContainerRef(), injectTemplateRef(),
inject(IterableDiffers, InjectFlags.Default, defaultIterableDiffers)),
features: [NgOnChangesFeature(NgForOf)],
refresh: (directiveIndex: number, elementIndex: number) => {
m<NgForOfDef<any>>(directiveIndex).ngDoCheck();
},
inputs: {
ngForOf: 'ngForOf',
ngForTrackBy: 'ngForTrackBy',

View File

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

View File

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

View File

@ -6,7 +6,7 @@
* 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';
@ -35,7 +35,7 @@ describe('content projection', () => {
e();
}
Child.ngComponentDef.h(1, 0);
Child.ngComponentDef.r(1, 0);
r(1, 0);
});
const parent = renderComponent(Parent);
expect(toHtml(parent)).toEqual('<child><div>content</div></child>');
@ -55,7 +55,7 @@ describe('content projection', () => {
e();
}
Child.ngComponentDef.h(1, 0);
Child.ngComponentDef.r(1, 0);
r(1, 0);
});
const parent = renderComponent(Parent);
expect(toHtml(parent)).toEqual('<child>content</child>');
@ -77,7 +77,7 @@ describe('content projection', () => {
{ P(3, 0); }
e();
GrandChild.ngComponentDef.h(2, 1);
GrandChild.ngComponentDef.r(2, 1);
r(2, 1);
}
});
const Parent = createComponent('parent', function(ctx: any, cm: boolean) {
@ -92,13 +92,55 @@ describe('content projection', () => {
e();
}
Child.ngComponentDef.h(1, 0);
Child.ngComponentDef.r(1, 0);
r(1, 0);
});
const parent = renderComponent(Parent);
expect(toHtml(parent))
.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.', () => {
const Child = createComponent('child', function(ctx: any, cm: boolean) {
if (cm) {
@ -129,7 +171,7 @@ describe('content projection', () => {
}
cr();
Child.ngComponentDef.h(1, 0);
Child.ngComponentDef.r(1, 0);
r(1, 0);
});
const parent = renderComponent(Parent);
expect(toHtml(parent)).toEqual('<child><div>()</div></child>');
@ -165,7 +207,7 @@ describe('content projection', () => {
}
cr();
Child.ngComponentDef.h(1, 0);
Child.ngComponentDef.r(1, 0);
r(1, 0);
});
const parent = renderComponent(Parent);
expect(toHtml(parent)).toEqual('<child></child>');
@ -214,7 +256,7 @@ describe('content projection', () => {
}
cr();
Child.ngComponentDef.h(1, 0);
Child.ngComponentDef.r(1, 0);
r(1, 0);
});
const parent = renderComponent(Parent);
expect(toHtml(parent)).toEqual('<child><div>(else)</div></child>');
@ -272,7 +314,7 @@ describe('content projection', () => {
e();
}
Child.ngComponentDef.h(1, 0);
Child.ngComponentDef.r(1, 0);
r(1, 0);
});
const parent = renderComponent(Parent);
expect(toHtml(parent)).toEqual('<child><div><span>content</span></div></child>');
@ -325,7 +367,7 @@ describe('content projection', () => {
e();
}
Child.ngComponentDef.h(1, 0);
Child.ngComponentDef.r(1, 0);
r(1, 0);
});
const parent = renderComponent(Parent);
expect(toHtml(parent)).toEqual('<child><div>content</div></child>');
@ -362,7 +404,7 @@ describe('content projection', () => {
e();
}
Child.ngComponentDef.h(1, 0);
Child.ngComponentDef.r(1, 0);
r(1, 0);
});
const parent = renderComponent(Parent);
expect(toHtml(parent)).toEqual('<child><div></div><span>content</span></child>');
@ -420,7 +462,7 @@ describe('content projection', () => {
e();
}
Child.ngComponentDef.h(1, 0);
Child.ngComponentDef.r(1, 0);
r(1, 0);
});
const parent = renderComponent(Parent);
expect(toHtml(parent)).toEqual('<child>content<div></div></child>');
@ -469,7 +511,7 @@ describe('content projection', () => {
e();
}
Child.ngComponentDef.h(1, 0);
Child.ngComponentDef.r(1, 0);
r(1, 0);
});
const parent = renderComponent(Parent);
@ -515,7 +557,7 @@ describe('content projection', () => {
e();
}
Child.ngComponentDef.h(1, 0);
Child.ngComponentDef.r(1, 0);
r(1, 0);
});
const parent = renderComponent(Parent);
@ -561,7 +603,7 @@ describe('content projection', () => {
e();
}
Child.ngComponentDef.h(1, 0);
Child.ngComponentDef.r(1, 0);
r(1, 0);
});
const parent = renderComponent(Parent);
@ -607,7 +649,7 @@ describe('content projection', () => {
e();
}
Child.ngComponentDef.h(1, 0);
Child.ngComponentDef.r(1, 0);
r(1, 0);
});
const parent = renderComponent(Parent);
@ -654,7 +696,7 @@ describe('content projection', () => {
e();
}
Child.ngComponentDef.h(1, 0);
Child.ngComponentDef.r(1, 0);
r(1, 0);
});
const parent = renderComponent(Parent);
@ -701,8 +743,8 @@ describe('content projection', () => {
}
e();
}
Child.ngComponentDef.h(0, 0);
Child.ngComponentDef.r(0, 0);
Child.ngComponentDef.h(1, 0);
r(1, 0);
});
const parent = renderComponent(Parent);
@ -750,7 +792,7 @@ describe('content projection', () => {
}
e();
GrandChild.ngComponentDef.h(2, 1);
GrandChild.ngComponentDef.r(2, 1);
r(2, 1);
}
});
@ -772,7 +814,7 @@ describe('content projection', () => {
e();
}
Child.ngComponentDef.h(1, 0);
Child.ngComponentDef.r(1, 0);
r(1, 0);
});
const parent = renderComponent(Parent);
@ -821,7 +863,7 @@ describe('content projection', () => {
}
cr();
Child.ngComponentDef.h(1, 0);
Child.ngComponentDef.r(1, 0);
r(1, 0);
});
const parent = renderComponent(Parent);
expect(toHtml(parent)).toEqual('<child><span><div>content</div></span></child>');

View File

@ -28,6 +28,7 @@ describe('define', () => {
}
static ngDirectiveDef = defineDirective({
type: MyDirective,
factory: () => new MyDirective(),
features: [NgOnChangesFeature(MyDirective)],
inputs: {valA: 'valA', valB: 'valB'}
@ -41,7 +42,7 @@ describe('define', () => {
expect(myDir.log).toEqual(['second']);
expect(myDir.valB).toEqual('works');
myDir.log.length = 0;
myDir.ngDoCheck();
MyDirective.ngDirectiveDef.doCheck !.call(myDir);
expect(myDir.log).toEqual([
'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 {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 {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 {LNodeFlags} from '../../src/render3/interfaces/node';
@ -22,7 +22,7 @@ describe('di', () => {
it('should create directive with no deps', () => {
class Directive {
value: string = 'Created';
static ngDirectiveDef = defineDirective({factory: () => new Directive});
static ngDirectiveDef = defineDirective({type: Directive, factory: () => new Directive});
}
function Template(ctx: any, cm: boolean) {
@ -42,21 +42,23 @@ describe('di', () => {
it('should create directive with inter view dependencies', () => {
class DirectiveA {
value: string = 'A';
static ngDirectiveDef =
defineDirective({factory: () => new DirectiveA, features: [PublicFeature]});
static ngDirectiveDef = defineDirective(
{type: DirectiveA, factory: () => new DirectiveA, features: [PublicFeature]});
}
class DirectiveB {
value: string = 'B';
static ngDirectiveDef =
defineDirective({factory: () => new DirectiveB, features: [PublicFeature]});
static ngDirectiveDef = defineDirective(
{type: DirectiveB, factory: () => new DirectiveB, features: [PublicFeature]});
}
class DirectiveC {
value: string;
constructor(a: DirectiveA, b: DirectiveB) { this.value = a.value + b.value; }
static ngDirectiveDef = defineDirective(
{factory: () => new DirectiveC(inject(DirectiveA), inject(DirectiveB))});
static ngDirectiveDef = defineDirective({
type: DirectiveC,
factory: () => new DirectiveC(inject(DirectiveA), inject(DirectiveB))
});
}
function Template(ctx: any, cm: boolean) {
@ -83,8 +85,11 @@ describe('di', () => {
constructor(public elementRef: ElementRef) {
this.value = (elementRef.constructor as any).name;
}
static ngDirectiveDef = defineDirective(
{factory: () => new Directive(injectElementRef()), features: [PublicFeature]});
static ngDirectiveDef = defineDirective({
type: Directive,
factory: () => new Directive(injectElementRef()),
features: [PublicFeature]
});
}
class DirectiveSameInstance {
@ -92,8 +97,10 @@ describe('di', () => {
constructor(elementRef: ElementRef, directive: Directive) {
this.value = elementRef === directive.elementRef;
}
static ngDirectiveDef = defineDirective(
{factory: () => new DirectiveSameInstance(injectElementRef(), inject(Directive))});
static ngDirectiveDef = defineDirective({
type: DirectiveSameInstance,
factory: () => new DirectiveSameInstance(injectElementRef(), inject(Directive))
});
}
function Template(ctx: any, cm: boolean) {
@ -116,8 +123,11 @@ describe('di', () => {
constructor(public templateRef: TemplateRef<any>) {
this.value = (templateRef.constructor as any).name;
}
static ngDirectiveDef = defineDirective(
{factory: () => new Directive(injectTemplateRef()), features: [PublicFeature]});
static ngDirectiveDef = defineDirective({
type: Directive,
factory: () => new Directive(injectTemplateRef()),
features: [PublicFeature]
});
}
class DirectiveSameInstance {
@ -125,8 +135,10 @@ describe('di', () => {
constructor(templateRef: TemplateRef<any>, directive: Directive) {
this.value = templateRef === directive.templateRef;
}
static ngDirectiveDef = defineDirective(
{factory: () => new DirectiveSameInstance(injectTemplateRef(), inject(Directive))});
static ngDirectiveDef = defineDirective({
type: DirectiveSameInstance,
factory: () => new DirectiveSameInstance(injectTemplateRef(), inject(Directive))
});
}
@ -149,8 +161,11 @@ describe('di', () => {
constructor(public viewContainerRef: ViewContainerRef) {
this.value = (viewContainerRef.constructor as any).name;
}
static ngDirectiveDef = defineDirective(
{factory: () => new Directive(injectViewContainerRef()), features: [PublicFeature]});
static ngDirectiveDef = defineDirective({
type: Directive,
factory: () => new Directive(injectViewContainerRef()),
features: [PublicFeature]
});
}
class DirectiveSameInstance {
@ -159,6 +174,7 @@ describe('di', () => {
this.value = viewContainerRef === directive.viewContainerRef;
}
static ngDirectiveDef = defineDirective({
type: DirectiveSameInstance,
factory: () => new DirectiveSameInstance(injectViewContainerRef(), inject(Directive))
});
}
@ -224,7 +240,7 @@ describe('di', () => {
constructor(public value: string) {}
static ngComponentDef = defineComponent({
// type: MyApp,
type: MyApp,
tag: 'my-app',
factory: () => new MyApp(inject(String as any, InjectFlags.Default, 'DefaultValue')),
template: () => null
@ -237,8 +253,11 @@ describe('di', () => {
it('should inject from parent view', () => {
class ParentDirective {
static ngDirectiveDef =
defineDirective({factory: () => new ParentDirective(), features: [PublicFeature]});
static ngDirectiveDef = defineDirective({
type: ParentDirective,
factory: () => new ParentDirective(),
features: [PublicFeature]
});
}
class ChildDirective {
@ -247,6 +266,7 @@ describe('di', () => {
this.value = (parent.constructor as any).name;
}
static ngDirectiveDef = defineDirective({
type: ChildDirective,
factory: () => new ChildDirective(inject(ParentDirective)),
features: [PublicFeature]
});
@ -257,8 +277,10 @@ describe('di', () => {
constructor(parent: ParentDirective, child: ChildDirective) {
this.value = parent === child.parent;
}
static ngDirectiveDef = defineDirective(
{factory: () => new Child2Directive(inject(ParentDirective), inject(ChildDirective))});
static ngDirectiveDef = defineDirective({
type: Child2Directive,
factory: () => new Child2Directive(inject(ParentDirective), inject(ChildDirective))
});
}
function Template(ctx: any, cm: boolean) {
@ -290,7 +312,7 @@ describe('di', () => {
describe('getOrCreateNodeInjector', () => {
it('should handle initial undefined state', () => {
const contentView = createLView(-1, null !, {data: []});
const contentView = createLView(-1, null !, createTView());
const oldView = enterView(contentView, null !);
try {
const parent = createLNode(0, LNodeFlags.Element, null, null);

View File

@ -6,7 +6,7 @@
* 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';
@ -20,8 +20,9 @@ describe('directive', () => {
class Directive {
klass = 'foo';
static ngDirectiveDef = defineDirective({
type: Directive,
factory: () => directiveInstance = new Directive,
refresh: (directiveIndex: number, elementIndex: number) => {
hostBindings: (directiveIndex: number, elementIndex: number) => {
p(elementIndex, 'className', b(m<Directive>(directiveIndex).klass));
}
});
@ -32,7 +33,8 @@ describe('directive', () => {
E(0, 'span', null, [Directive]);
e();
}
Directive.ngDirectiveDef.r(1, 0);
Directive.ngDirectiveDef.h(1, 0);
r(1, 0);
}
expect(renderToHtml(Template, {})).toEqual('<span class="foo"></span>');

View File

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

View File

@ -210,6 +210,7 @@ describe('render3 integration test', () => {
value = ' one';
static ngComponentDef = defineComponent({
type: TodoComponent,
tag: 'todo',
template: function TodoTemplate(ctx: any, cm: boolean) {
if (cm) {
@ -233,7 +234,7 @@ describe('render3 integration test', () => {
e();
}
TodoComponent.ngComponentDef.h(1, 0);
TodoComponent.ngComponentDef.r(1, 0);
r(1, 0);
}
expect(renderToHtml(Template, null)).toEqual('<todo><p>Todo one</p></todo>');
@ -247,7 +248,7 @@ describe('render3 integration test', () => {
T(2, 'two');
}
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');
});
@ -266,8 +267,8 @@ describe('render3 integration test', () => {
}
TodoComponent.ngComponentDef.h(1, 0);
TodoComponent.ngComponentDef.h(3, 2);
TodoComponent.ngComponentDef.r(1, 0);
TodoComponent.ngComponentDef.r(3, 2);
r(1, 0);
r(3, 2);
}
expect(renderToHtml(Template, null))
.toEqual('<todo><p>Todo one</p></todo><todo><p>Todo one</p></todo>');
@ -279,6 +280,7 @@ describe('render3 integration test', () => {
class TodoComponentHostBinding {
title = 'one';
static ngComponentDef = defineComponent({
type: TodoComponentHostBinding,
tag: 'todo',
template: function TodoComponentHostBindingTemplate(
ctx: TodoComponentHostBinding, cm: boolean) {
@ -301,7 +303,7 @@ describe('render3 integration test', () => {
e();
}
TodoComponentHostBinding.ngComponentDef.h(1, 0);
TodoComponentHostBinding.ngComponentDef.r(1, 0);
r(1, 0);
}
expect(renderToHtml(Template, {})).toEqual('<todo title="one">one</todo>');
@ -314,6 +316,7 @@ describe('render3 integration test', () => {
class MyComp {
name = 'Bess';
static ngComponentDef = defineComponent({
type: MyComp,
tag: 'comp',
template: function MyCompTemplate(ctx: any, cm: boolean) {
if (cm) {
@ -333,7 +336,7 @@ describe('render3 integration test', () => {
e();
}
MyComp.ngComponentDef.h(1, 0);
MyComp.ngComponentDef.r(1, 0);
r(1, 0);
}
expect(renderToHtml(Template, null)).toEqual('<comp><p>Bess</p></comp>');
@ -348,6 +351,7 @@ describe('render3 integration test', () => {
class MyComp {
condition: boolean;
static ngComponentDef = defineComponent({
type: MyComp,
tag: 'comp',
template: function MyCompTemplate(ctx: any, cm: boolean) {
if (cm) {
@ -379,7 +383,7 @@ describe('render3 integration test', () => {
}
p(0, 'condition', b(ctx.condition));
MyComp.ngComponentDef.h(1, 0);
MyComp.ngComponentDef.r(1, 0);
r(1, 0);
}
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
*/
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';
@ -21,6 +21,7 @@ describe('event listeners', () => {
onClick() { this.counter++; }
static ngComponentDef = defineComponent({
type: MyComp,
tag: 'comp',
/** <button (click)="onClick()"> Click me </button> */
template: function CompTemplate(ctx: any, cm: boolean) {
@ -207,8 +208,8 @@ describe('event listeners', () => {
}
MyComp.ngComponentDef.h(2, 1);
MyComp.ngComponentDef.h(4, 3);
MyComp.ngComponentDef.r(2, 1);
MyComp.ngComponentDef.r(4, 3);
r(2, 1);
r(4, 3);
v();
}
}

View File

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

View File

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

View File

@ -6,8 +6,7 @@
* 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 {C, E, Q, QueryList, e, m, qR} from '../../src/render3/index';
import {QueryReadType} from '../../src/render3/interfaces/query';
import {C, E, Q, QueryList, V, cR, cr, detectChanges, e, m, qR, v} from '../../src/render3/index';
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', () => {
const Child = createDirective();
let childInstance, div;
/**
* <div #foo></div>
* class Cmpt {
@ -522,7 +520,7 @@ describe('query', () => {
let tmp: any;
if (cm) {
m(0, Q(['foo'], false, Child));
div = E(1, 'div', null, null, ['foo', '']);
E(1, 'div', null, null, ['foo', '']);
e();
}
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> {
return class Component {
value: any;
static ngComponentDef = defineComponent(
{tag: name, factory: () => new Component, template: template, features: [PublicFeature]});
static ngComponentDef = defineComponent({
type: Component,
tag: name,
factory: () => new Component,
template: template,
features: [PublicFeature]
});
};
}
export function createDirective({exportAs}: {exportAs?: string} = {}): DirectiveType<any> {
return class Directive {
static ngDirectiveDef = defineDirective({
type: Directive,
factory: () => new Directive(),
features: [PublicFeature],
exportAs: exportAs,

View File

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

View File

@ -6,7 +6,7 @@
* 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', () => {
@ -32,4 +32,20 @@ describe('util', () => {
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 {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';
@ -16,6 +16,7 @@ describe('ViewContainerRef', () => {
constructor(public viewContainer: ViewContainerRef, public template: TemplateRef<any>, ) {}
static ngDirectiveDef = defineDirective({
type: TestDirective,
factory: () => new TestDirective(injectViewContainerRef(), injectTemplateRef(), ),
});
}
@ -24,6 +25,7 @@ describe('ViewContainerRef', () => {
testDir: TestDirective;
static ngComponentDef = defineComponent({
type: TestComponent,
tag: 'test-cmp',
factory: () => new TestComponent(),
template: (cmp: TestComponent, cm: boolean) => {
@ -38,7 +40,8 @@ describe('ViewContainerRef', () => {
}
cR(0);
cmp.testDir = m(1) as TestDirective;
TestDirective.ngDirectiveDef.r(1, 0);
TestDirective.ngDirectiveDef.h(1, 0);
r(1, 0);
cr();
},
});