diff --git a/modules/@angular/core/src/view/anchor.ts b/modules/@angular/core/src/view/anchor.ts index 757feb8ccd..722dc8b1b8 100644 --- a/modules/@angular/core/src/view/anchor.ts +++ b/modules/@angular/core/src/view/anchor.ts @@ -18,11 +18,13 @@ export function anchorDef( parent: undefined, childFlags: undefined, bindingIndex: undefined, + disposableIndex: undefined, providerIndices: undefined, // regular values flags, childCount, bindings: [], + disposableCount: 0, element: undefined, provider: undefined, text: undefined, diff --git a/modules/@angular/core/src/view/element.ts b/modules/@angular/core/src/view/element.ts index b26af2a336..bf3eb85541 100644 --- a/modules/@angular/core/src/view/element.ts +++ b/modules/@angular/core/src/view/element.ts @@ -8,14 +8,16 @@ import {SecurityContext} from '../security'; -import {BindingDef, BindingType, NodeData, NodeDef, NodeFlags, NodeType, ViewData, ViewFlags} from './types'; +import {BindingDef, BindingType, DisposableFn, ElementOutputDef, NodeData, NodeDef, NodeFlags, NodeType, ViewData, ViewFlags} from './types'; import {checkAndUpdateBinding, setBindingDebugInfo} from './util'; export function elementDef( flags: NodeFlags, childCount: number, name: string, fixedAttrs: {[name: string]: string} = {}, - bindings: ([BindingType.ElementClass, string] | [BindingType.ElementStyle, string, string] | [ - BindingType.ElementAttribute | BindingType.ElementProperty, string, SecurityContext - ])[] = []): NodeDef { + bindings?: + ([BindingType.ElementClass, string] | [BindingType.ElementStyle, string, string] | + [BindingType.ElementAttribute | BindingType.ElementProperty, string, SecurityContext])[], + outputs?: (string | [string, string])[]): NodeDef { + bindings = bindings || []; const bindingDefs = new Array(bindings.length); for (let i = 0; i < bindings.length; i++) { const entry = bindings[i]; @@ -35,6 +37,19 @@ export function elementDef( } bindingDefs[i] = {type: bindingType, name, nonMinfiedName: name, securityContext, suffix}; } + outputs = outputs || []; + const outputDefs: ElementOutputDef[] = new Array(outputs.length); + for (let i = 0; i < outputs.length; i++) { + const output = outputs[i]; + let target: string; + let eventName: string; + if (Array.isArray(output)) { + [target, eventName] = output; + } else { + eventName = output; + } + outputDefs[i] = {eventName: eventName, target: target}; + } return { type: NodeType.Element, // will bet set by the view definition @@ -43,12 +58,14 @@ export function elementDef( parent: undefined, childFlags: undefined, bindingIndex: undefined, + disposableIndex: undefined, providerIndices: undefined, // regular values flags, childCount, bindings: bindingDefs, - element: {name, attrs: fixedAttrs}, + disposableCount: outputDefs.length, + element: {name, attrs: fixedAttrs, outputs: outputDefs}, provider: undefined, text: undefined, component: undefined, @@ -62,22 +79,52 @@ export function createElement(view: ViewData, renderHost: any, def: NodeDef): No let el: any; if (view.renderer) { el = view.renderer.createElement(parentNode, elDef.name); - if (elDef.attrs) { - for (let attrName in elDef.attrs) { - view.renderer.setElementAttribute(el, attrName, elDef.attrs[attrName]); - } - } } else { el = document.createElement(elDef.name); if (parentNode) { parentNode.appendChild(el); } - if (elDef.attrs) { - for (let attrName in elDef.attrs) { + } + if (elDef.attrs) { + for (let attrName in elDef.attrs) { + if (view.renderer) { + view.renderer.setElementAttribute(el, attrName, elDef.attrs[attrName]); + } else { el.setAttribute(attrName, elDef.attrs[attrName]); } } } + if (elDef.outputs.length) { + for (let i = 0; i < elDef.outputs.length; i++) { + const output = elDef.outputs[i]; + let disposable: DisposableFn; + if (view.renderer) { + const handleEventClosure = renderEventHandlerClosure(view, def.index, output.eventName); + if (output.target) { + disposable = + view.renderer.listenGlobal(output.target, output.eventName, handleEventClosure); + } else { + disposable = view.renderer.listen(el, output.eventName, handleEventClosure); + } + } else { + let target: any; + switch (output.target) { + case 'window': + target = window; + break; + case 'document': + target = document; + break; + default: + target = el; + } + const handleEventClosure = directDomEventHandlerClosure(view, def.index, output.eventName); + target.addEventListener(output.eventName, handleEventClosure); + disposable = target.removeEventListener.bind(target, output.eventName, handleEventClosure); + } + view.disposables[def.disposableIndex + i] = disposable; + } + } return { renderNode: el, provider: undefined, @@ -86,6 +133,21 @@ export function createElement(view: ViewData, renderHost: any, def: NodeDef): No }; } +function renderEventHandlerClosure(view: ViewData, index: number, eventName: string) { + return (event: any) => { return view.def.handleEvent(view, index, eventName, event); }; +} + + +function directDomEventHandlerClosure(view: ViewData, index: number, eventName: string) { + return (event: any) => { + const result = view.def.handleEvent(view, index, eventName, event); + if (result === false) { + event.preventDefault(); + } + return result; + }; +} + export function checkAndUpdateElementInline( view: ViewData, def: NodeDef, v0: any, v1: any, v2: any, v3: any, v4: any, v5: any, v6: any, v7: any, v8: any, v9: any) { diff --git a/modules/@angular/core/src/view/provider.ts b/modules/@angular/core/src/view/provider.ts index 88fc8afda2..8762da500e 100644 --- a/modules/@angular/core/src/view/provider.ts +++ b/modules/@angular/core/src/view/provider.ts @@ -14,7 +14,7 @@ import {TemplateRef} from '../linker/template_ref'; import {ViewContainerRef} from '../linker/view_container_ref'; import {Renderer} from '../render/api'; -import {BindingDef, BindingType, DepDef, DepFlags, NodeData, NodeDef, NodeFlags, NodeType, Services, ViewData, ViewDefinition, ViewFlags} from './types'; +import {BindingDef, BindingType, DepDef, DepFlags, DisposableFn, NodeData, NodeDef, NodeFlags, NodeType, ProviderOutputDef, Services, ViewData, ViewDefinition, ViewFlags} from './types'; import {checkAndUpdateBinding, checkAndUpdateBindingWithChange, setBindingDebugInfo} from './util'; const _tokenKeyCache = new Map(); @@ -26,7 +26,8 @@ const TemplateRefTokenKey = tokenKey(TemplateRef); export function providerDef( flags: NodeFlags, ctor: any, deps: ([DepFlags, any] | any)[], - props?: {[name: string]: [number, string]}, component?: () => ViewDefinition): NodeDef { + props?: {[name: string]: [number, string]}, outputs?: {[name: string]: string}, + component?: () => ViewDefinition): NodeDef { const bindings: BindingDef[] = []; if (props) { for (let prop in props) { @@ -39,6 +40,12 @@ export function providerDef( }; } } + const outputDefs: ProviderOutputDef[] = []; + if (outputs) { + for (let propName in outputs) { + outputDefs.push({propName, eventName: outputs[propName]}); + } + } const depDefs: DepDef[] = deps.map(value => { let token: any; let flags: DepFlags; @@ -61,16 +68,14 @@ export function providerDef( parent: undefined, childFlags: undefined, bindingIndex: undefined, + disposableIndex: undefined, providerIndices: undefined, // regular values flags, childCount: 0, bindings, + disposableCount: outputDefs.length, element: undefined, - provider: { - tokenKey: tokenKey(ctor), - ctor, - deps: depDefs, - }, + provider: {tokenKey: tokenKey(ctor), ctor, deps: depDefs, outputs: outputDefs}, text: undefined, component, template: undefined }; @@ -87,10 +92,19 @@ export function tokenKey(token: any): string { export function createProvider(view: ViewData, def: NodeDef, componentView: ViewData): NodeData { const providerDef = def.provider; + const provider = createInstance(view, def.parent, providerDef.ctor, providerDef.deps); + if (providerDef.outputs.length) { + for (let i = 0; i < providerDef.outputs.length; i++) { + const output = providerDef.outputs[i]; + const subscription = provider[output.propName].subscribe( + view.def.handleEvent.bind(null, view, def.parent, output.eventName)); + view.disposables[def.disposableIndex + i] = subscription.unsubscribe.bind(subscription); + } + } return { renderNode: undefined, - provider: createInstance(view, def.parent, providerDef.ctor, providerDef.deps), - embeddedViews: undefined, componentView + provider, + embeddedViews: undefined, componentView, }; } diff --git a/modules/@angular/core/src/view/text.ts b/modules/@angular/core/src/view/text.ts index 0ef3c7b925..af2b84cc9b 100644 --- a/modules/@angular/core/src/view/text.ts +++ b/modules/@angular/core/src/view/text.ts @@ -30,10 +30,12 @@ export function textDef(constants: string[]): NodeDef { parent: undefined, childFlags: undefined, bindingIndex: undefined, + disposableIndex: undefined, providerIndices: undefined, // regular values flags: 0, childCount: 0, bindings, + disposableCount: 0, element: undefined, provider: undefined, text: {prefix: constants[0]}, diff --git a/modules/@angular/core/src/view/types.ts b/modules/@angular/core/src/view/types.ts index 8bf3cc4f52..3f37c5c4e4 100644 --- a/modules/@angular/core/src/view/types.ts +++ b/modules/@angular/core/src/view/types.ts @@ -19,6 +19,7 @@ export interface ViewDefinition { flags: ViewFlags; componentType: RenderComponentType; update: ViewUpdateFn; + handleEvent: ViewHandleEventFn; /** * Order: Depth first. * Especially providers are before elements / anchros. @@ -33,10 +34,10 @@ export interface ViewDefinition { reverseChildNodes: NodeDef[]; lastRootNode: number; bindingCount: number; + disposableCount: number; } -export type ViewUpdateFn = (updater: NodeUpdater, view: ViewData, component: any, context: any) => - void; +export type ViewUpdateFn = (updater: NodeUpdater, view: ViewData) => void; export interface NodeUpdater { checkInline( @@ -45,6 +46,9 @@ export interface NodeUpdater { checkDynamic(view: ViewData, nodeIndex: number, values: any[]): void; } +export type ViewHandleEventFn = + (view: ViewData, nodeIndex: number, eventName: string, event: any) => boolean; + /** * Bitmask for ViewDefintion.flags. */ @@ -66,6 +70,8 @@ export interface NodeDef { childFlags: NodeFlags; bindingIndex: number; bindings: BindingDef[]; + disposableIndex: number; + disposableCount: number; element: ElementDef; providerIndices: {[tokenKey: string]: number}; provider: ProviderDef; @@ -102,6 +108,12 @@ export enum NodeFlags { export interface ElementDef { name: string; attrs: {[name: string]: string}; + outputs: ElementOutputDef[]; +} + +export interface ElementOutputDef { + target: string; + eventName: string; } /** @@ -118,10 +130,16 @@ export interface DepDef { tokenKey: string; } +export interface ProviderOutputDef { + propName: string; + eventName: string; +} + export interface ProviderDef { tokenKey: string; ctor: any; deps: DepDef[]; + outputs: ProviderOutputDef[]; } export interface TextDef { prefix: string; } @@ -164,8 +182,11 @@ export interface ViewData { nodes: NodeData[]; firstChange: boolean; oldValues: any[]; + disposables: DisposableFn[]; } +export type DisposableFn = () => void; + /** * Node instance data. * Attention: Adding fields to this is performance sensitive! diff --git a/modules/@angular/core/src/view/view.ts b/modules/@angular/core/src/view/view.ts index b62c5b882c..c7c4aae708 100644 --- a/modules/@angular/core/src/view/view.ts +++ b/modules/@angular/core/src/view/view.ts @@ -13,14 +13,14 @@ import {createAnchor} from './anchor'; import {checkAndUpdateElementDynamic, checkAndUpdateElementInline, createElement} from './element'; import {callLifecycleHooksChildrenFirst, checkAndUpdateProviderDynamic, checkAndUpdateProviderInline, createProvider} from './provider'; import {checkAndUpdateTextDynamic, checkAndUpdateTextInline, createText} from './text'; -import {ElementDef, NodeData, NodeDef, NodeFlags, NodeType, NodeUpdater, ProviderDef, Services, TextDef, ViewData, ViewDefinition, ViewFlags, ViewUpdateFn} from './types'; +import {ElementDef, NodeData, NodeDef, NodeFlags, NodeType, NodeUpdater, ProviderDef, Services, TextDef, ViewData, ViewDefinition, ViewFlags, ViewHandleEventFn, ViewUpdateFn} from './types'; import {checkBindingNoChanges} from './util'; -const NOOP_UPDATE = (): any => undefined; +const NOOP = (): any => undefined; export function viewDef( flags: ViewFlags, nodesWithoutIndices: NodeDef[], update?: ViewUpdateFn, - componentType?: RenderComponentType): ViewDefinition { + handleEvent?: ViewHandleEventFn, componentType?: RenderComponentType): ViewDefinition { // clone nodes and set auto calculated values if (nodesWithoutIndices.length === 0) { throw new Error(`Illegal State: Views without nodes are not allowed!`); @@ -28,6 +28,7 @@ export function viewDef( const nodes: NodeDef[] = new Array(nodesWithoutIndices.length); const reverseChildNodes: NodeDef[] = new Array(nodesWithoutIndices.length); let viewBindingCount = 0; + let viewDisposableCount = 0; let viewFlags = 0; let currentParent: NodeDef = null; let lastRootNode: NodeDef = null; @@ -44,7 +45,8 @@ export function viewDef( const node = cloneAndModifyNode(nodesWithoutIndices[i], { index: i, parent: currentParent ? currentParent.index : undefined, - bindingIndex: viewBindingCount, reverseChildIndex, + bindingIndex: viewBindingCount, + disposableIndex: viewDisposableCount, reverseChildIndex, providerIndices: Object.create(currentParent ? currentParent.providerIndices : null) }); nodes[i] = node; @@ -53,6 +55,7 @@ export function viewDef( viewFlags |= node.flags; viewBindingCount += node.bindings.length; + viewDisposableCount += node.disposableCount; if (currentParent) { currentParent.childFlags |= node.flags; } @@ -72,8 +75,10 @@ export function viewDef( nodeFlags: viewFlags, flags, nodes: nodes, reverseChildNodes, - update: update || NOOP_UPDATE, componentType, + update: update || NOOP, + handleEvent: handleEvent || NOOP, componentType, bindingCount: viewBindingCount, + disposableCount: viewDisposableCount, lastRootNode: lastRootNode.index }; } @@ -148,6 +153,7 @@ function cloneAndModifyNode(nodeDef: NodeDef, values: { reverseChildIndex: number, parent: number, bindingIndex: number, + disposableIndex: number, providerIndices: {[tokenKey: string]: number} }): NodeDef { const clonedNode: NodeDef = {}; @@ -157,6 +163,7 @@ function cloneAndModifyNode(nodeDef: NodeDef, values: { clonedNode.index = values.index; clonedNode.bindingIndex = values.bindingIndex; + clonedNode.disposableIndex = values.disposableIndex; clonedNode.parent = values.parent; clonedNode.reverseChildIndex = values.reverseChildIndex; clonedNode.providerIndices = values.providerIndices; @@ -188,6 +195,7 @@ function createView( } else { renderer = def.componentType ? services.renderComponent(def.componentType) : parent.renderer; } + const disposables = def.disposableCount ? new Array(def.disposableCount) : undefined; const view: ViewData = { def, parent, @@ -195,7 +203,7 @@ function createView( context: undefined, component: undefined, nodes, firstChange: true, renderer, services, - oldValues: new Array(def.bindingCount) + oldValues: new Array(def.bindingCount), disposables }; return view; } @@ -232,7 +240,7 @@ function initView(view: ViewData, renderHost: any, component: any, context: any) } export function checkNoChangesView(view: ViewData) { - view.def.update(CheckNoChanges, view, view.component, view.context); + view.def.update(CheckNoChanges, view); execEmbeddedViewsAction(view, ViewAction.CheckNoChanges); execComponentViewsAction(view, ViewAction.CheckNoChanges); } @@ -274,7 +282,7 @@ const CheckNoChanges: NodeUpdater = { }; export function checkAndUpdateView(view: ViewData) { - view.def.update(CheckAndUpdate, view, view.component, view.context); + view.def.update(CheckAndUpdate, view); execEmbeddedViewsAction(view, ViewAction.CheckAndUpdate); callLifecycleHooksChildrenFirst( @@ -320,6 +328,11 @@ const CheckAndUpdate: NodeUpdater = { export function destroyView(view: ViewData) { callLifecycleHooksChildrenFirst(view, NodeFlags.OnDestroy); + if (view.disposables) { + for (let i = 0; i < view.disposables.length; i++) { + view.disposables[i](); + } + } execComponentViewsAction(view, ViewAction.Destroy); execEmbeddedViewsAction(view, ViewAction.Destroy); } diff --git a/modules/@angular/core/test/view/anchor_spec.ts b/modules/@angular/core/test/view/anchor_spec.ts index 8c90b787e0..76db0e02f0 100644 --- a/modules/@angular/core/test/view/anchor_spec.ts +++ b/modules/@angular/core/test/view/anchor_spec.ts @@ -7,7 +7,7 @@ */ import {RenderComponentType, RootRenderer, Sanitizer, SecurityContext, ViewEncapsulation} from '@angular/core'; -import {DefaultServices, NodeDef, NodeFlags, NodeUpdater, Services, ViewData, ViewDefinition, ViewFlags, ViewUpdateFn, anchorDef, checkAndUpdateView, checkNoChangesView, createRootView, elementDef, rootRenderNodes, textDef, viewDef} from '@angular/core/src/view/index'; +import {DefaultServices, NodeDef, NodeFlags, NodeUpdater, Services, ViewData, ViewDefinition, ViewFlags, ViewHandleEventFn, ViewUpdateFn, anchorDef, checkAndUpdateView, checkNoChangesView, createRootView, elementDef, rootRenderNodes, textDef, viewDef} from '@angular/core/src/view/index'; import {inject} from '@angular/core/testing'; import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; @@ -34,8 +34,9 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) { new RenderComponentType('1', 'someUrl', 0, ViewEncapsulation.None, [], {}); })); - function compViewDef(nodes: NodeDef[], updater?: ViewUpdateFn): ViewDefinition { - return viewDef(config.viewFlags, nodes, updater, renderComponentType); + function compViewDef( + nodes: NodeDef[], update?: ViewUpdateFn, handleEvent?: ViewHandleEventFn): ViewDefinition { + return viewDef(config.viewFlags, nodes, update, handleEvent, renderComponentType); } function createAndGetRootNodes(viewDef: ViewDefinition): {rootNodes: any[], view: ViewData} { diff --git a/modules/@angular/core/test/view/component_view_spec.ts b/modules/@angular/core/test/view/component_view_spec.ts index 34105bfae5..77cb396885 100644 --- a/modules/@angular/core/test/view/component_view_spec.ts +++ b/modules/@angular/core/test/view/component_view_spec.ts @@ -7,7 +7,7 @@ */ import {RenderComponentType, RootRenderer, Sanitizer, SecurityContext, ViewEncapsulation} from '@angular/core'; -import {BindingType, DefaultServices, NodeDef, NodeFlags, NodeUpdater, Services, ViewData, ViewDefinition, ViewFlags, ViewUpdateFn, anchorDef, checkAndUpdateView, checkNoChangesView, createRootView, destroyView, elementDef, providerDef, rootRenderNodes, textDef, viewDef} from '@angular/core/src/view/index'; +import {BindingType, DefaultServices, NodeDef, NodeFlags, NodeUpdater, Services, ViewData, ViewDefinition, ViewFlags, ViewHandleEventFn, ViewUpdateFn, anchorDef, checkAndUpdateView, checkNoChangesView, createRootView, destroyView, elementDef, providerDef, rootRenderNodes, textDef, viewDef} from '@angular/core/src/view/index'; import {inject} from '@angular/core/testing'; import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; @@ -34,8 +34,9 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) { new RenderComponentType('1', 'someUrl', 0, ViewEncapsulation.None, [], {}); })); - function compViewDef(nodes: NodeDef[], updater?: ViewUpdateFn): ViewDefinition { - return viewDef(config.viewFlags, nodes, updater, renderComponentType); + function compViewDef( + nodes: NodeDef[], update?: ViewUpdateFn, handleEvent?: ViewHandleEventFn): ViewDefinition { + return viewDef(config.viewFlags, nodes, update, handleEvent, renderComponentType); } function createAndGetRootNodes(viewDef: ViewDefinition): {rootNodes: any[], view: ViewData} { @@ -45,56 +46,57 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) { } it('should create and attach component views', () => { - class AComp {} + let instance: AComp; + class AComp { + constructor() { instance = this; } + } const {view, rootNodes} = createAndGetRootNodes(compViewDef([ elementDef(NodeFlags.None, 1, 'div'), - providerDef(NodeFlags.None, AComp, [], null, () => compViewDef([ - elementDef(NodeFlags.None, 0, 'span'), - ])), + providerDef(NodeFlags.None, AComp, [], null, null, () => compViewDef([ + elementDef(NodeFlags.None, 0, 'span'), + ])), ])); + const compView = view.nodes[1].componentView; + + expect(compView.context).toBe(instance); + expect(compView.component).toBe(instance); + const compRootEl = getDOM().childNodes(rootNodes[0])[0]; expect(getDOM().nodeName(compRootEl).toLowerCase()).toBe('span'); }); it('should dirty check component views', () => { let value = 'v1'; - let instance: AComp; class AComp { a: any; - constructor() { instance = this; } } - const updater = jasmine.createSpy('updater').and.callFake( + const update = jasmine.createSpy('updater').and.callFake( (updater: NodeUpdater, view: ViewData) => updater.checkInline(view, 0, value)); const {view, rootNodes} = createAndGetRootNodes( compViewDef([ elementDef(NodeFlags.None, 1, 'div'), - providerDef(NodeFlags.None, AComp, [], null, () => compViewDef( + providerDef(NodeFlags.None, AComp, [], null, null, () => compViewDef( [ elementDef(NodeFlags.None, 0, 'span', null, [[BindingType.ElementAttribute, 'a', SecurityContext.NONE]]), - ], updater + ], update )), ], jasmine.createSpy('parentUpdater'))); + const compView = view.nodes[1].componentView; checkAndUpdateView(view); - expect(updater).toHaveBeenCalled(); - // component - expect(updater.calls.mostRecent().args[2]).toBe(instance); - // view context - expect(updater.calls.mostRecent().args[3]).toBe(instance); + expect(update).toHaveBeenCalled(); + expect(update.calls.mostRecent().args[1]).toBe(compView); - updater.calls.reset(); + update.calls.reset(); checkNoChangesView(view); - expect(updater).toHaveBeenCalled(); - // component - expect(updater.calls.mostRecent().args[2]).toBe(instance); - // view context - expect(updater.calls.mostRecent().args[3]).toBe(instance); + expect(update).toHaveBeenCalled(); + expect(update.calls.mostRecent().args[1]).toBe(compView); value = 'v2'; expect(() => checkNoChangesView(view)) @@ -114,10 +116,11 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) { const {view, rootNodes} = createAndGetRootNodes(compViewDef([ elementDef(NodeFlags.None, 1, 'div'), providerDef( - NodeFlags.None, AComp, [], null, () => compViewDef([ - elementDef(NodeFlags.None, 1, 'span'), - providerDef(NodeFlags.OnDestroy, ChildProvider, []) - ])), + NodeFlags.None, AComp, [], null, null, + () => compViewDef([ + elementDef(NodeFlags.None, 1, 'span'), + providerDef(NodeFlags.OnDestroy, ChildProvider, []) + ])), ])); destroyView(view); diff --git a/modules/@angular/core/test/view/element_spec.ts b/modules/@angular/core/test/view/element_spec.ts index 7176f76471..ddf8a1070c 100644 --- a/modules/@angular/core/test/view/element_spec.ts +++ b/modules/@angular/core/test/view/element_spec.ts @@ -7,7 +7,7 @@ */ import {RenderComponentType, RootRenderer, Sanitizer, SecurityContext, ViewEncapsulation} from '@angular/core'; -import {BindingType, DefaultServices, NodeDef, NodeFlags, NodeUpdater, Services, ViewData, ViewDefinition, ViewFlags, ViewUpdateFn, anchorDef, checkAndUpdateView, checkNoChangesView, createRootView, elementDef, rootRenderNodes, textDef, viewDef} from '@angular/core/src/view/index'; +import {BindingType, DefaultServices, NodeDef, NodeFlags, NodeUpdater, Services, ViewData, ViewDefinition, ViewFlags, ViewHandleEventFn, ViewUpdateFn, anchorDef, checkAndUpdateView, checkNoChangesView, createRootView, destroyView, elementDef, rootRenderNodes, textDef, viewDef} from '@angular/core/src/view/index'; import {inject} from '@angular/core/testing'; import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; @@ -34,8 +34,9 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) { new RenderComponentType('1', 'someUrl', 0, ViewEncapsulation.None, [], {}); })); - function compViewDef(nodes: NodeDef[], updater?: ViewUpdateFn): ViewDefinition { - return viewDef(config.viewFlags, nodes, updater, renderComponentType); + function compViewDef( + nodes: NodeDef[], update?: ViewUpdateFn, handleEvent?: ViewHandleEventFn): ViewDefinition { + return viewDef(config.viewFlags, nodes, update, handleEvent, renderComponentType); } function createAndGetRootNodes(viewDef: ViewDefinition): {rootNodes: any[], view: ViewData} { @@ -101,12 +102,12 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) { describe('change properties', () => { [{ name: 'inline', - updater: (updater: NodeUpdater, view: ViewData) => updater.checkInline(view, 0, 'v1', 'v2') + update: (updater: NodeUpdater, view: ViewData) => updater.checkInline(view, 0, 'v1', 'v2') }, { name: 'dynamic', - updater: (updater: NodeUpdater, view: ViewData) => - updater.checkDynamic(view, 0, ['v1', 'v2']) + update: (updater: NodeUpdater, view: ViewData) => + updater.checkDynamic(view, 0, ['v1', 'v2']) }].forEach((config) => { it(`should update ${config.name}`, () => { @@ -119,7 +120,7 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) { [BindingType.ElementProperty, 'value', SecurityContext.NONE] ]), ], - config.updater)); + config.update)); checkAndUpdateView(view); @@ -133,12 +134,12 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) { describe('change attributes', () => { [{ name: 'inline', - updater: (updater: NodeUpdater, view: ViewData) => updater.checkInline(view, 0, 'v1', 'v2') + update: (updater: NodeUpdater, view: ViewData) => updater.checkInline(view, 0, 'v1', 'v2') }, { name: 'dynamic', - updater: (updater: NodeUpdater, view: ViewData) => - updater.checkDynamic(view, 0, ['v1', 'v2']) + update: (updater: NodeUpdater, view: ViewData) => + updater.checkDynamic(view, 0, ['v1', 'v2']) }].forEach((config) => { it(`should update ${config.name}`, () => { const {view, rootNodes} = createAndGetRootNodes(compViewDef( @@ -150,7 +151,7 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) { [BindingType.ElementAttribute, 'a2', SecurityContext.NONE] ]), ], - config.updater)); + config.update)); checkAndUpdateView(view); @@ -192,12 +193,12 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) { describe('change styles', () => { [{ name: 'inline', - updater: (updater: NodeUpdater, view: ViewData) => updater.checkInline(view, 0, 10, 'red') + update: (updater: NodeUpdater, view: ViewData) => updater.checkInline(view, 0, 10, 'red') }, { name: 'dynamic', - updater: (updater: NodeUpdater, view: ViewData) => - updater.checkDynamic(view, 0, [10, 'red']) + update: (updater: NodeUpdater, view: ViewData) => + updater.checkDynamic(view, 0, [10, 'red']) }].forEach((config) => { it(`should update ${config.name}`, () => { const {view, rootNodes} = createAndGetRootNodes(compViewDef( @@ -209,7 +210,7 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) { [BindingType.ElementStyle, 'color', null] ]), ], - config.updater)); + config.update)); checkAndUpdateView(view); @@ -219,5 +220,131 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) { }); }); }); + + if (getDOM().supportsDOMEvents()) { + describe('listen to DOM events', () => { + let removeNodes: Node[]; + beforeEach(() => { removeNodes = []; }); + afterEach(() => { + removeNodes.forEach((node) => { + if (node.parentNode) { + node.parentNode.removeChild(node); + } + }); + }); + + function createAndAttachAndGetRootNodes(viewDef: ViewDefinition): + {rootNodes: any[], view: ViewData} { + const result = createAndGetRootNodes(viewDef); + // Note: We need to append the node to the document.body, otherwise `click` events + // won't work in IE. + result.rootNodes.forEach((node) => { + document.body.appendChild(node); + removeNodes.push(node); + }); + return result; + } + + it('should listen to DOM events', () => { + const handleEventSpy = jasmine.createSpy('handleEvent'); + const removeListenerSpy = + spyOn(HTMLElement.prototype, 'removeEventListener').and.callThrough(); + const {view, rootNodes} = createAndAttachAndGetRootNodes(compViewDef( + [elementDef(NodeFlags.None, 0, 'button', null, null, ['click'])], null, + handleEventSpy)); + + rootNodes[0].click(); + + expect(handleEventSpy).toHaveBeenCalled(); + let handleEventArgs = handleEventSpy.calls.mostRecent().args; + expect(handleEventArgs[0]).toBe(view); + expect(handleEventArgs[1]).toBe(0); + expect(handleEventArgs[2]).toBe('click'); + expect(handleEventArgs[3]).toBeTruthy(); + + destroyView(view); + + expect(removeListenerSpy).toHaveBeenCalled(); + }); + + it('should listen to window events', () => { + const handleEventSpy = jasmine.createSpy('handleEvent'); + const addListenerSpy = spyOn(window, 'addEventListener'); + const removeListenerSpy = spyOn(window, 'removeEventListener'); + const {view, rootNodes} = createAndAttachAndGetRootNodes(compViewDef( + [elementDef(NodeFlags.None, 0, 'button', null, null, [['window', 'windowClick']])], + null, handleEventSpy)); + + expect(addListenerSpy).toHaveBeenCalled(); + expect(addListenerSpy.calls.mostRecent().args[0]).toBe('windowClick'); + addListenerSpy.calls.mostRecent().args[1]({name: 'windowClick'}); + + expect(handleEventSpy).toHaveBeenCalled(); + const handleEventArgs = handleEventSpy.calls.mostRecent().args; + expect(handleEventArgs[0]).toBe(view); + expect(handleEventArgs[1]).toBe(0); + expect(handleEventArgs[2]).toBe('windowClick'); + expect(handleEventArgs[3]).toBeTruthy(); + + destroyView(view); + + expect(removeListenerSpy).toHaveBeenCalled(); + }); + + it('should listen to document events', () => { + const handleEventSpy = jasmine.createSpy('handleEvent'); + const addListenerSpy = spyOn(document, 'addEventListener'); + const removeListenerSpy = spyOn(document, 'removeEventListener'); + const {view, rootNodes} = createAndAttachAndGetRootNodes(compViewDef( + [elementDef( + NodeFlags.None, 0, 'button', null, null, [['document', 'documentClick']])], + null, handleEventSpy)); + + expect(addListenerSpy).toHaveBeenCalled(); + expect(addListenerSpy.calls.mostRecent().args[0]).toBe('documentClick'); + addListenerSpy.calls.mostRecent().args[1]({name: 'documentClick'}); + + expect(handleEventSpy).toHaveBeenCalled(); + const handleEventArgs = handleEventSpy.calls.mostRecent().args; + expect(handleEventArgs[0]).toBe(view); + expect(handleEventArgs[1]).toBe(0); + expect(handleEventArgs[2]).toBe('documentClick'); + expect(handleEventArgs[3]).toBeTruthy(); + + destroyView(view); + + expect(removeListenerSpy).toHaveBeenCalled(); + }); + + it('should preventDefault only if the handler returns false', () => { + let eventHandlerResult: any; + let preventDefaultSpy: jasmine.Spy; + + const {view, rootNodes} = createAndAttachAndGetRootNodes(compViewDef( + [elementDef(NodeFlags.None, 0, 'button', null, null, ['click'])], null, + (view, index, eventName, event) => { + preventDefaultSpy = spyOn(event, 'preventDefault').and.callThrough(); + return eventHandlerResult; + })); + + eventHandlerResult = undefined; + rootNodes[0].click(); + expect(preventDefaultSpy).not.toHaveBeenCalled(); + + eventHandlerResult = true; + rootNodes[0].click(); + expect(preventDefaultSpy).not.toHaveBeenCalled(); + + eventHandlerResult = 'someString'; + rootNodes[0].click(); + expect(preventDefaultSpy).not.toHaveBeenCalled(); + + eventHandlerResult = false; + rootNodes[0].click(); + expect(preventDefaultSpy).toHaveBeenCalled(); + }); + + }); + } }); } diff --git a/modules/@angular/core/test/view/embedded_view_spec.ts b/modules/@angular/core/test/view/embedded_view_spec.ts index 56b0176ac6..b7b014c752 100644 --- a/modules/@angular/core/test/view/embedded_view_spec.ts +++ b/modules/@angular/core/test/view/embedded_view_spec.ts @@ -7,7 +7,7 @@ */ import {RenderComponentType, RootRenderer, Sanitizer, SecurityContext, ViewEncapsulation} from '@angular/core'; -import {BindingType, DefaultServices, NodeDef, NodeFlags, NodeUpdater, Services, ViewData, ViewDefinition, ViewFlags, ViewUpdateFn, anchorDef, attachEmbeddedView, checkAndUpdateView, checkNoChangesView, createEmbeddedView, createRootView, destroyView, detachEmbeddedView, elementDef, providerDef, rootRenderNodes, textDef, viewDef} from '@angular/core/src/view/index'; +import {BindingType, DefaultServices, NodeDef, NodeFlags, NodeUpdater, Services, ViewData, ViewDefinition, ViewFlags, ViewHandleEventFn, ViewUpdateFn, anchorDef, attachEmbeddedView, checkAndUpdateView, checkNoChangesView, createEmbeddedView, createRootView, destroyView, detachEmbeddedView, elementDef, providerDef, rootRenderNodes, textDef, viewDef} from '@angular/core/src/view/index'; import {inject} from '@angular/core/testing'; import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; @@ -34,12 +34,13 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) { new RenderComponentType('1', 'someUrl', 0, ViewEncapsulation.None, [], {}); })); - function compViewDef(nodes: NodeDef[], updater?: ViewUpdateFn): ViewDefinition { - return viewDef(config.viewFlags, nodes, updater, renderComponentType); + function compViewDef( + nodes: NodeDef[], update?: ViewUpdateFn, handleEvent?: ViewHandleEventFn): ViewDefinition { + return viewDef(config.viewFlags, nodes, update, handleEvent, renderComponentType); } - function embeddedViewDef(nodes: NodeDef[], updater?: ViewUpdateFn): ViewDefinition { - return viewDef(config.viewFlags, nodes, updater); + function embeddedViewDef(nodes: NodeDef[], update?: ViewUpdateFn): ViewDefinition { + return viewDef(config.viewFlags, nodes, update); } function createAndGetRootNodes( @@ -49,6 +50,23 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) { return {rootNodes, view}; } + it('should create embedded views with the right context', () => { + const parentContext = new Object(); + const childContext = new Object(); + + const {view: parentView, rootNodes} = createAndGetRootNodes( + compViewDef([ + elementDef(NodeFlags.None, 2, 'div'), + anchorDef(NodeFlags.HasEmbeddedViews, 0, embeddedViewDef([elementDef( + NodeFlags.None, 0, 'span')])), + ]), + parentContext); + + const childView = createEmbeddedView(parentView, parentView.def.nodes[1], childContext); + expect(childView.component).toBe(parentContext); + expect(childView.context).toBe(childContext); + }); + it('should attach and detach embedded views', () => { const {view: parentView, rootNodes} = createAndGetRootNodes(compViewDef([ elementDef(NodeFlags.None, 2, 'div'), @@ -97,45 +115,35 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) { it('should dirty check embedded views', () => { let childValue = 'v1'; - const parentContext = new Object(); - const childContext = new Object(); - const updater = jasmine.createSpy('updater').and.callFake( + const update = jasmine.createSpy('updater').and.callFake( (updater: NodeUpdater, view: ViewData) => updater.checkInline(view, 0, childValue)); - const {view: parentView, rootNodes} = createAndGetRootNodes( - compViewDef([ - elementDef(NodeFlags.None, 1, 'div'), - anchorDef( - NodeFlags.HasEmbeddedViews, 0, - embeddedViewDef( - [elementDef( - NodeFlags.None, 0, 'span', null, - [[BindingType.ElementAttribute, 'name', SecurityContext.NONE]])], - updater)) - ]), - parentContext); + const {view: parentView, rootNodes} = createAndGetRootNodes(compViewDef([ + elementDef(NodeFlags.None, 1, 'div'), + anchorDef( + NodeFlags.HasEmbeddedViews, 0, + embeddedViewDef( + [elementDef( + NodeFlags.None, 0, 'span', null, + [[BindingType.ElementAttribute, 'name', SecurityContext.NONE]])], + update)) + ])); - const childView0 = createEmbeddedView(parentView, parentView.def.nodes[1], childContext); + const childView0 = createEmbeddedView(parentView, parentView.def.nodes[1]); const rootEl = rootNodes[0]; attachEmbeddedView(parentView.nodes[1], 0, childView0); checkAndUpdateView(parentView); - expect(updater).toHaveBeenCalled(); - // component - expect(updater.calls.mostRecent().args[2]).toBe(parentContext); - // view context - expect(updater.calls.mostRecent().args[3]).toBe(childContext); + expect(update).toHaveBeenCalled(); + expect(update.calls.mostRecent().args[1]).toBe(childView0); - updater.calls.reset(); + update.calls.reset(); checkNoChangesView(parentView); - expect(updater).toHaveBeenCalled(); - // component - expect(updater.calls.mostRecent().args[2]).toBe(parentContext); - // view context - expect(updater.calls.mostRecent().args[3]).toBe(childContext); + expect(update).toHaveBeenCalled(); + expect(update.calls.mostRecent().args[1]).toBe(childView0); childValue = 'v2'; expect(() => checkNoChangesView(parentView)) diff --git a/modules/@angular/core/test/view/provider_spec.ts b/modules/@angular/core/test/view/provider_spec.ts index c8fea5611a..3c42070710 100644 --- a/modules/@angular/core/test/view/provider_spec.ts +++ b/modules/@angular/core/test/view/provider_spec.ts @@ -6,8 +6,8 @@ * found in the LICENSE file at https://angular.io/license */ -import {AfterContentChecked, AfterContentInit, AfterViewChecked, AfterViewInit, DoCheck, ElementRef, OnChanges, OnDestroy, OnInit, RenderComponentType, Renderer, RootRenderer, Sanitizer, SecurityContext, SimpleChange, TemplateRef, ViewContainerRef, ViewEncapsulation} from '@angular/core'; -import {BindingType, DefaultServices, NodeDef, NodeFlags, NodeUpdater, Services, ViewData, ViewDefinition, ViewFlags, ViewUpdateFn, anchorDef, checkAndUpdateView, checkNoChangesView, createRootView, destroyView, elementDef, providerDef, rootRenderNodes, textDef, viewDef} from '@angular/core/src/view/index'; +import {AfterContentChecked, AfterContentInit, AfterViewChecked, AfterViewInit, DoCheck, ElementRef, EventEmitter, OnChanges, OnDestroy, OnInit, RenderComponentType, Renderer, RootRenderer, Sanitizer, SecurityContext, SimpleChange, TemplateRef, ViewContainerRef, ViewEncapsulation} from '@angular/core'; +import {BindingType, DefaultServices, NodeDef, NodeFlags, NodeUpdater, Services, ViewData, ViewDefinition, ViewFlags, ViewHandleEventFn, ViewUpdateFn, anchorDef, checkAndUpdateView, checkNoChangesView, createRootView, destroyView, elementDef, providerDef, rootRenderNodes, textDef, viewDef} from '@angular/core/src/view/index'; import {inject} from '@angular/core/testing'; import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; @@ -34,12 +34,13 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) { new RenderComponentType('1', 'someUrl', 0, ViewEncapsulation.None, [], {}); })); - function compViewDef(nodes: NodeDef[], updater?: ViewUpdateFn): ViewDefinition { - return viewDef(config.viewFlags, nodes, updater, renderComponentType); + function compViewDef( + nodes: NodeDef[], update?: ViewUpdateFn, handleEvent?: ViewHandleEventFn): ViewDefinition { + return viewDef(config.viewFlags, nodes, update, handleEvent, renderComponentType); } - function embeddedViewDef(nodes: NodeDef[], updater?: ViewUpdateFn): ViewDefinition { - return viewDef(config.viewFlags, nodes, updater); + function embeddedViewDef(nodes: NodeDef[], update?: ViewUpdateFn): ViewDefinition { + return viewDef(config.viewFlags, nodes, update); } function createAndGetRootNodes(viewDef: ViewDefinition): {rootNodes: any[], view: ViewData} { @@ -110,10 +111,11 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) { createAndGetRootNodes(compViewDef([ elementDef(NodeFlags.None, 1, 'div'), providerDef( - NodeFlags.None, Dep, [], null, () => compViewDef([ - elementDef(NodeFlags.None, 1, 'span'), - providerDef(NodeFlags.None, SomeService, [Dep]) - ])), + NodeFlags.None, Dep, [], null, null, + () => compViewDef([ + elementDef(NodeFlags.None, 1, 'span'), + providerDef(NodeFlags.None, SomeService, [Dep]) + ])), ])); expect(instance.dep instanceof Dep).toBeTruthy(); @@ -173,12 +175,12 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) { describe('data binding', () => { [{ name: 'inline', - updater: (updater: NodeUpdater, view: ViewData) => updater.checkInline(view, 1, 'v1', 'v2') + update: (updater: NodeUpdater, view: ViewData) => updater.checkInline(view, 1, 'v1', 'v2') }, { name: 'dynamic', - updater: (updater: NodeUpdater, view: ViewData) => - updater.checkDynamic(view, 1, ['v1', 'v2']) + update: (updater: NodeUpdater, view: ViewData) => + updater.checkDynamic(view, 1, ['v1', 'v2']) }].forEach((config) => { it(`should update ${config.name}`, () => { let instance: SomeService; @@ -194,7 +196,7 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) { elementDef(NodeFlags.None, 1, 'span'), providerDef(NodeFlags.None, SomeService, [], {a: [0, 'a'], b: [1, 'b']}) ], - config.updater)); + config.update)); checkAndUpdateView(view); @@ -226,6 +228,39 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) { }); }); + describe('outputs', () => { + it('should listen to provider events', () => { + let emitter = new EventEmitter(); + let unsubscribeSpy: any; + + class SomeService { + emitter = { + subscribe: (callback: any) => { + const subscription = emitter.subscribe(callback); + unsubscribeSpy = spyOn(subscription, 'unsubscribe').and.callThrough(); + return subscription; + } + }; + } + + const handleEvent = jasmine.createSpy('handleEvent'); + const subscribe = spyOn(emitter, 'subscribe').and.callThrough(); + + const {view, rootNodes} = createAndGetRootNodes(compViewDef( + [ + elementDef(NodeFlags.None, 1, 'span'), + providerDef(NodeFlags.None, SomeService, [], null, {emitter: 'someEventName'}) + ], + null, handleEvent)); + + emitter.emit('someEventInstance'); + expect(handleEvent).toHaveBeenCalledWith(view, 0, 'someEventName', 'someEventInstance'); + + destroyView(view); + expect(unsubscribeSpy).toHaveBeenCalled(); + }); + }); + describe('lifecycle hooks', () => { it('should call the lifecycle hooks in the right order', () => { let instanceCount = 0; diff --git a/modules/@angular/core/test/view/text_spec.ts b/modules/@angular/core/test/view/text_spec.ts index b67ee7ea56..0dac5c5dc3 100644 --- a/modules/@angular/core/test/view/text_spec.ts +++ b/modules/@angular/core/test/view/text_spec.ts @@ -7,7 +7,7 @@ */ import {RenderComponentType, RootRenderer, Sanitizer, SecurityContext, ViewEncapsulation} from '@angular/core'; -import {DefaultServices, NodeDef, NodeFlags, NodeUpdater, Services, ViewData, ViewDefinition, ViewFlags, ViewUpdateFn, anchorDef, checkAndUpdateView, checkNoChangesView, createRootView, elementDef, rootRenderNodes, textDef, viewDef} from '@angular/core/src/view/index'; +import {DefaultServices, NodeDef, NodeFlags, NodeUpdater, Services, ViewData, ViewDefinition, ViewFlags, ViewHandleEventFn, ViewUpdateFn, anchorDef, checkAndUpdateView, checkNoChangesView, createRootView, elementDef, rootRenderNodes, textDef, viewDef} from '@angular/core/src/view/index'; import {inject} from '@angular/core/testing'; import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; @@ -34,8 +34,9 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) { new RenderComponentType('1', 'someUrl', 0, ViewEncapsulation.None, [], {}); })); - function compViewDef(nodes: NodeDef[], updater?: ViewUpdateFn): ViewDefinition { - return viewDef(config.viewFlags, nodes, updater, renderComponentType); + function compViewDef( + nodes: NodeDef[], update?: ViewUpdateFn, handleEvent?: ViewHandleEventFn): ViewDefinition { + return viewDef(config.viewFlags, nodes, update, handleEvent, renderComponentType); } function createAndGetRootNodes(viewDef: ViewDefinition): {rootNodes: any[], view: ViewData} { @@ -88,19 +89,19 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) { describe('change text', () => { [{ name: 'inline', - updater: (updater: NodeUpdater, view: ViewData) => updater.checkInline(view, 0, 'a', 'b') + update: (updater: NodeUpdater, view: ViewData) => updater.checkInline(view, 0, 'a', 'b') }, { name: 'dynamic', - updater: (updater: NodeUpdater, view: ViewData) => - updater.checkDynamic(view, 0, ['a', 'b']) + update: (updater: NodeUpdater, view: ViewData) => + updater.checkDynamic(view, 0, ['a', 'b']) }].forEach((config) => { it(`should update ${config.name}`, () => { const {view, rootNodes} = createAndGetRootNodes(compViewDef( [ textDef(['0', '1', '2']), ], - config.updater)); + config.update)); checkAndUpdateView(view); diff --git a/modules/benchmarks/src/tree/ng2_next/tree.ts b/modules/benchmarks/src/tree/ng2_next/tree.ts index ad5e35f67f..327b92bda9 100644 --- a/modules/benchmarks/src/tree/ng2_next/tree.ts +++ b/modules/benchmarks/src/tree/ng2_next/tree.ts @@ -25,16 +25,18 @@ let viewFlags = ViewFlags.DirectDom; const TreeComponent_Host: ViewDefinition = viewDef(viewFlags, [ elementDef(NodeFlags.None, 1, 'tree'), - providerDef(NodeFlags.None, TreeComponent, [], null, () => TreeComponent_0), + providerDef(NodeFlags.None, TreeComponent, [], null, null, () => TreeComponent_0), ]); const TreeComponent_1: ViewDefinition = viewDef( viewFlags, [ elementDef(NodeFlags.None, 1, 'tree'), - providerDef(NodeFlags.None, TreeComponent, [], {data: [0, 'data']}, () => TreeComponent_0), + providerDef( + NodeFlags.None, TreeComponent, [], {data: [0, 'data']}, null, () => TreeComponent_0), ], - (updater: NodeUpdater, view: ViewData, cmp: TreeComponent) => { + (updater: NodeUpdater, view: ViewData) => { + const cmp = view.component; updater.checkInline(view, 1, cmp.data.left); }); @@ -42,9 +44,11 @@ const TreeComponent_2: ViewDefinition = viewDef( viewFlags, [ elementDef(NodeFlags.None, 1, 'tree'), - providerDef(NodeFlags.None, TreeComponent, [], {data: [0, 'data']}, () => TreeComponent_0), + providerDef( + NodeFlags.None, TreeComponent, [], {data: [0, 'data']}, null, () => TreeComponent_0), ], - (updater: NodeUpdater, view: ViewData, cmp: TreeComponent) => { + (updater: NodeUpdater, view: ViewData) => { + const cmp = view.component; updater.checkInline(view, 1, cmp.data.right); }); @@ -59,7 +63,8 @@ const TreeComponent_0: ViewDefinition = viewDef( anchorDef(NodeFlags.HasEmbeddedViews, 1, TreeComponent_2), providerDef(NodeFlags.None, NgIf, [ViewContainerRef, TemplateRef], {ngIf: [0, 'ngIf']}), ], - (updater: NodeUpdater, view: ViewData, cmp: TreeComponent) => { + (updater: NodeUpdater, view: ViewData) => { + const cmp = view.component; updater.checkInline(view, 0, cmp.bgColor); updater.checkInline(view, 1, cmp.data.value); updater.checkInline(view, 3, cmp.data.left != null);