refactor(ivy): move init hooks into TView (#21650)

PR Close #21650
This commit is contained in:
Kara Erickson 2018-01-22 17:43:52 -08:00 committed by Misko Hevery
parent 98174758ad
commit 9c99e6a838
7 changed files with 259 additions and 168 deletions

View File

@ -13,8 +13,7 @@ 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 {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';
@ -173,7 +172,7 @@ export function renderComponent<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

@ -0,0 +1,153 @@
import {LifecycleHooksMap} from './interfaces/definition';
import {LNodeFlags} from './interfaces/node';
import {HookData, LView, TView} from './interfaces/view';
/**
* Enum used by the lifecycle (l) instruction to determine which lifecycle hook is requesting
* processing.
*/
export const enum LifecycleHook {
ON_INIT = 0b00,
ON_CHECK = 0b01,
ON_CHANGES = 0b10
}
/** Constants used by lifecycle hooks to determine when and how a hook should be called. */
export const enum LifecycleHookUtils {
/* 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
}
/**
* Loops through the directives on a node and queues their afterContentInit,
* afterContentChecked, and onDestroy hooks, if they exist.
*/
export function queueLifecycleHooks(flags: number, currentView: LView): void {
// It's necessary to loop through the directives at elementEnd() (rather than storing
// the hooks at creation time) so we can preserve the current hook order. All hooks
// for projected components and directives must be called *before* their hosts.
const size = (flags & LNodeFlags.SIZE_MASK) >> LNodeFlags.SIZE_SHIFT;
const start = flags >> LNodeFlags.INDX_SHIFT;
let contentHooks = currentView.contentHooks;
let cleanup = currentView.cleanup;
for (let i = start, end = start + size; i < end; i++) {
const instance = currentView.data[i];
if (instance.ngAfterContentInit != null) {
(contentHooks || (currentView.contentHooks = contentHooks = [
])).push(LifecycleHook.ON_INIT, instance.ngAfterContentInit, instance);
}
if (instance.ngAfterContentChecked != null) {
(contentHooks || (currentView.contentHooks = contentHooks = [
])).push(LifecycleHook.ON_CHECK, instance.ngAfterContentChecked, instance);
}
if (instance.ngOnDestroy != null) {
(cleanup || (currentView.cleanup = cleanup = [])).push(instance.ngOnDestroy, instance);
}
}
}
/**
* If this is the first template pass, any ngOnInit or ngDoCheck hooks on the current directive
* will be queued on TView.initHooks.
*
* 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, hooks: LifecycleHooksMap, tView: TView): void {
if (tView.firstTemplatePass && hooks.onInit != null) {
const hookFlags = index << LifecycleHookUtils.INDX_SHIFT;
(tView.initHooks || (tView.initHooks = [])).push(hookFlags, hooks.onInit);
}
if (tView.firstTemplatePass && hooks.doCheck != null) {
const hookFlags = (index << LifecycleHookUtils.INDX_SHIFT) | LifecycleHook.ON_CHECK;
(tView.initHooks || (tView.initHooks = [])).push(hookFlags, hooks.doCheck);
}
}
/**
* Calls onInit and doCheck calls if they haven't already been called.
*
* @param currentView The current view
* @param initHooks The init hooks for this view
*/
export function executeInitHooks(currentView: LView): void {
const initHooks = currentView.tView.initHooks;
if (currentView.initHooksCalled === false && initHooks != null) {
const data = currentView.data;
const creationMode = currentView.creationMode;
for (let i = 0; i < initHooks.length; i += 2) {
const flags = initHooks[i] as number;
const hook = initHooks[i | 1] as() => void;
const onInit = (flags & LifecycleHookUtils.TYPE_MASK) === LifecycleHook.ON_INIT;
const instance = data[flags >> LifecycleHookUtils.INDX_SHIFT];
if (onInit === false || creationMode) {
hook.call(instance);
}
}
currentView.initHooksCalled = true;
}
}
/** Iterates over view hook functions and calls them. */
export function executeViewHooks(data: any[], viewHookStartIndex: number | null): void {
if (viewHookStartIndex == null) return;
executeHooksAndRemoveInits(data, viewHookStartIndex);
}
/**
* Calls all afterContentInit and afterContentChecked hooks for the view, then splices
* out afterContentInit hooks to prep for the next run in update mode.
*/
export function executeContentHooks(currentView: LView): void {
if (currentView.contentHooks != null && currentView.contentHooksCalled === false) {
executeHooksAndRemoveInits(currentView.contentHooks, 0);
currentView.contentHooksCalled = true;
}
}
/**
* Calls lifecycle hooks with their contexts, then splices out any init-only hooks
* to prep for the next run in update mode.
*
* @param arr The array in which the hooks are found
* @param startIndex The index at which to start calling hooks
*/
function executeHooksAndRemoveInits(arr: any[], startIndex: number): void {
// 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 = startIndex;
let writeIndex = startIndex;
while (checkIndex < arr.length) {
// Call lifecycle hook with its context
arr[checkIndex + 1].call(arr[checkIndex + 2]);
if (arr[checkIndex] === LifecycleHook.ON_CHECK) {
// We know if the writeIndex falls behind that there is an init that needs to
// be overwritten.
if (writeIndex < checkIndex) {
arr[writeIndex] = arr[checkIndex];
arr[writeIndex + 1] = arr[checkIndex + 1];
arr[writeIndex + 2] = arr[checkIndex + 2];
}
writeIndex += 3;
}
checkIndex += 3;
}
// Truncate once at the writeIndex
arr.length = writeIndex;
}

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,
@ -72,6 +70,9 @@ export {
query as Q,
queryRefresh as qR,
} from './query';
export {LifecycleHook} from './hooks';
// clang-format on
export {

View File

@ -27,20 +27,9 @@ import {isNodeMatchingSelector} from './node_selector_matcher';
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 {LifecycleHook, 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_INIT = 8,
AFTER_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 +80,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;
@ -129,19 +118,6 @@ let bindingIndex: number;
*/
let cleanup: any[]|null;
/**
* Array of ngAfterContentInit and ngAfterContentChecked hooks.
*
* These need to be queued so they can be called all at once after init hooks
* and any embedded views are finished processing (to maintain backwards-compatible
* order).
*
* 1st index is: type of hook (afterContentInit or afterContentChecked)
* 2nd index is: method to call
* 3rd index is: context
*/
let contentHooks: any[]|null;
/** Index in the data array at which view hooks begin to be stored. */
let viewHookStartIndex: number|null;
@ -166,7 +142,6 @@ export function enterView(newView: LView, host: LElementNode | LViewNode | null)
viewHookStartIndex = newView.viewHookStartIndex;
cleanup = newView.cleanup;
contentHooks = newView.contentHooks;
renderer = newView.renderer;
if (host != null) {
@ -183,8 +158,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(data, viewHookStartIndex);
currentView.initHooksCalled = false;
currentView.contentHooksCalled = false;
if (currentView.tView.firstTemplatePass) currentView.tView.firstTemplatePass = false;
enterView(newView, null);
}
@ -209,7 +186,8 @@ export function createLView(
template: template,
context: context,
dynamicViewCount: 0,
contentHooksCalled: false
contentHooksCalled: false,
initHooksCalled: false
};
return newView;
@ -508,7 +486,11 @@ 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);
}
export function createTView(): TView {
return {data: [], firstTemplatePass: true, initHooks: null};
}
function setUpAttributes(native: RElement, attrs: string[]): void {
@ -627,35 +609,7 @@ export function elementEnd() {
ngDevMode && assertNodeType(previousOrParentNode, LNodeFlags.Element);
const query = previousOrParentNode.query;
query && query.addNode(previousOrParentNode);
queueLifecycleHooks();
}
/**
* Loops through the directives on a node and queues their afterContentInit,
* afterContentChecked, and onDestroy hooks, if they exist.
*/
function queueLifecycleHooks(): void {
// It's necessary to loop through the directives at elementEnd() (rather than storing
// the hooks at creation time) so we can preserve the current hook order. All hooks
// for projected components and directives must be called *before* their hosts.
const flags = previousOrParentNode.flags;
const size = (flags & LNodeFlags.SIZE_MASK) >> LNodeFlags.SIZE_SHIFT;
const start = flags >> LNodeFlags.INDX_SHIFT;
for (let i = start, end = start + size; i < end; i++) {
const instance = data[i];
if (instance.ngAfterContentInit != null) {
(contentHooks || (currentView.contentHooks = contentHooks = [
])).push(LifecycleHook.AFTER_INIT, instance.ngAfterContentInit, instance);
}
if (instance.ngAfterContentChecked != null) {
(contentHooks || (currentView.contentHooks = contentHooks = [
])).push(LifecycleHook.AFTER_CHECKED, instance.ngAfterContentChecked, instance);
}
if (instance.ngOnDestroy != null) {
(cleanup || (currentView.cleanup = cleanup = [])).push(instance.ngOnDestroy, instance);
}
}
queueLifecycleHooks(previousOrParentNode.flags, currentView);
}
/**
@ -949,6 +903,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.lifecycleHooks, currentView.tView);
return instance;
}
@ -1013,31 +972,13 @@ function generateInitialInputs(
* 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.AFTER_INIT, self: any, method: Function): void;
export function lifecycle(
lifecycle: LifecycleHook.AFTER_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 (
creationMode &&
(lifecycle === LifecycleHook.AFTER_INIT || lifecycle === LifecycleHook.AFTER_CHECKED)) {
export function lifecycle(lifecycle: LifecycleHook, self: any, method: Function): boolean {
if (creationMode &&
(lifecycle === LifecycleHook.ON_INIT || lifecycle === LifecycleHook.ON_CHECK)) {
if (viewHookStartIndex == null) {
currentView.viewHookStartIndex = viewHookStartIndex = data.length;
}
@ -1046,12 +987,6 @@ export function lifecycle(lifecycle: LifecycleHook, self?: any, method?: Functio
return false;
}
/** Iterates over view hook functions and calls them. */
export function executeViewHooks(): void {
if (viewHookStartIndex == null) return;
executeHooksAndRemoveInits(data, viewHookStartIndex);
}
//////////////////////////
//// ViewContainer & View
@ -1124,6 +1059,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);
}
/**
@ -1210,7 +1149,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];
}
@ -1261,7 +1200,8 @@ export const componentRefresh:
ngDevMode && assertDataInRange(directiveIndex);
const hostView = element.data !;
ngDevMode && assertNotEqual(hostView, null, 'hostView');
executeContentHooks();
executeInitHooks(currentView);
executeContentHooks(currentView);
const directive = data[directiveIndex];
const oldView = enterView(hostView, element);
try {
@ -1273,49 +1213,6 @@ export const componentRefresh:
}
};
/**
* Calls all afterContentInit and afterContentChecked hooks for the view, then splices
* out afterContentInit hooks to prep for the next run in update mode.
*/
function executeContentHooks(): void {
if (contentHooks == null || currentView.contentHooksCalled) return;
executeHooksAndRemoveInits(contentHooks, 0);
currentView.contentHooksCalled = true;
}
/**
* Calls lifecycle hooks with their contexts, then splices out any init-only hooks
* to prep for the next run in update mode.
*
* @param arr The array in which the hooks are found
* @param startIndex The index at which to start calling hooks
*/
function executeHooksAndRemoveInits(arr: any[], startIndex: number): void {
// 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 = startIndex;
let writeIndex = startIndex;
while (checkIndex < arr.length) {
// Call lifecycle hook with its context
arr[checkIndex + 1].call(arr[checkIndex + 2]);
if (arr[checkIndex] === LifecycleHook.AFTER_CHECKED) {
// We know if the writeIndex falls behind that there is an init that needs to
// be overwritten.
if (writeIndex < checkIndex) {
arr[writeIndex] = arr[checkIndex];
arr[writeIndex + 1] = arr[checkIndex + 1];
arr[writeIndex + 2] = arr[checkIndex + 2];
}
writeIndex += 3;
}
checkIndex += 3;
}
// Truncate once at the writeIndex
arr.length = writeIndex;
}
/**
* Instruction to distribute projectable nodes among <ng-content> occurrences in a given template.
* It takes all the selectors from the entire component's template and decides where

View File

@ -103,6 +103,16 @@ export interface LView {
*/
contentHooks: any[]|null;
/**
* Whether or not the ngOnInit and ngDoCheck hooks have been called in this change
* detection run.
*
* These two hooks are executed by the first Comp.r() instruction that runs OR the
* first cR instruction that runs (so inits are run for the top level view before
* any embedded views). For this reason, the call must be tracked.
*/
initHooksCalled: boolean;
/**
* Whether or not the content hooks have been called in this change detection run.
*
@ -194,7 +204,29 @@ 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 init 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 init hooks that should be executed for a view.
*
* Even indices: Flags (1st bit: hook type, remaining: directive index)
* Odd indices: Hook function
*/
export type HookData = (number | (() => void))[];
/**
* Static data that corresponds to the instance-specific data array on an LView.

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';
@ -312,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,8 @@
* found in the LICENSE file at https://angular.io/license
*/
import {C, ComponentDef, ComponentTemplate, E, L, LifecycleHook, T, V, b, cR, cr, defineComponent, e, l, m, p, r, v, pD, P} from '../../src/render3/index';
import {C, ComponentDef, ComponentTemplate, E, L, LifecycleHook, P, T, V, b, cR, cr, defineComponent, defineDirective, e, l, m, p, pD, r, v} from '../../src/render3/index';
import {containerEl, renderToHtml} from './render_util';
describe('lifecycles', () => {
@ -52,8 +53,6 @@ describe('lifecycles', () => {
type: Component,
tag: name,
factory: () => new Component(),
hostBindings: function(directiveIndex: number, elementIndex: number):
void { l(LifecycleHook.ON_INIT) && m<Component>(directiveIndex).ngOnInit(); },
inputs: {val: 'val'}, template
});
};
@ -220,6 +219,30 @@ describe('lifecycles', () => {
expect(events).toEqual(['comp1', 'projected1', 'comp2', 'projected2']);
});
it('should be called on directives after component', () => {
class Directive {
ngOnInit() { events.push('dir'); }
static ngDirectiveDef = defineDirective({type: Directive, factory: () => new Directive()});
}
function Template(ctx: any, cm: boolean) {
if (cm) {
E(0, Comp, null, [Directive]);
e();
}
Comp.ngComponentDef.h(1, 0);
Comp.ngComponentDef.r(1, 0);
}
renderToHtml(Template, {});
expect(events).toEqual(['comp', 'dir']);
renderToHtml(Template, {});
expect(events).toEqual(['comp', 'dir']);
});
it('should call onInit properly in for loop', () => {
/**
* <comp [val]="1"></comp>
@ -334,22 +357,13 @@ describe('lifecycles', () => {
return class Component {
ngDoCheck() {
events.push(name);
allEvents.push('ngDoCheck ' + name);
allEvents.push('check ' + name);
}
ngOnInit() { allEvents.push('ngOnInit ' + name); }
ngOnInit() { allEvents.push('init ' + name); }
static ngComponentDef = defineComponent({
type: Component,
tag: name,
factory: () => new Component(),
hostBindings: function(
this: ComponentDef<Component>, directiveIndex: number, elementIndex: number): void {
l(LifecycleHook.ON_INIT) && m<Component>(directiveIndex).ngOnInit();
m<Component>(directiveIndex).ngDoCheck();
},
template
});
static ngComponentDef =
defineComponent({type: Component, tag: name, factory: () => new Component(), template});
};
}
@ -402,10 +416,10 @@ describe('lifecycles', () => {
}
renderToHtml(Template, {});
expect(allEvents).toEqual(['ngOnInit comp', 'ngDoCheck comp']);
expect(allEvents).toEqual(['init comp', 'check comp']);
renderToHtml(Template, {});
expect(allEvents).toEqual(['ngOnInit comp', 'ngDoCheck comp', 'ngDoCheck comp']);
expect(allEvents).toEqual(['init comp', 'check comp', 'check comp']);
});
});
@ -829,8 +843,8 @@ describe('lifecycles', () => {
refresh: (directiveIndex: number, elementIndex: number) => {
r(directiveIndex, elementIndex, template);
const comp = m(directiveIndex) as Component;
l(LifecycleHook.AFTER_INIT, comp, comp.ngAfterViewInit);
l(LifecycleHook.AFTER_CHECKED, comp, comp.ngAfterViewChecked);
l(LifecycleHook.ON_INIT, comp, comp.ngAfterViewInit);
l(LifecycleHook.ON_CHECK, comp, comp.ngAfterViewChecked);
},
inputs: {val: 'val'},
template: template
@ -1693,16 +1707,11 @@ describe('lifecycles', () => {
type: Component,
tag: name,
factory: () => new Component(),
hostBindings: function(directiveIndex: number, elementIndex: number): void {
const comp = D(directiveIndex) as Component;
l(LifecycleHook.ON_INIT) && comp.ngOnInit();
comp.ngDoCheck();
},
refresh: function(directiveIndex: number, elementIndex: number): void {
r(directiveIndex, elementIndex, template);
const comp = D(directiveIndex) as Component;
l(LifecycleHook.AFTER_INIT, comp, comp.ngAfterViewInit);
l(LifecycleHook.AFTER_CHECKED, comp, comp.ngAfterViewChecked);
const comp = m(directiveIndex) as Component;
l(LifecycleHook.ON_INIT, comp, comp.ngAfterViewInit);
l(LifecycleHook.ON_CHECK, comp, comp.ngAfterViewChecked);
},
inputs: {val: 'val'}, template
});