From 9eaf1bbe677f8fd3ae5197e8be8c96c00edef546 Mon Sep 17 00:00:00 2001 From: Kara Erickson Date: Mon, 26 Feb 2018 16:58:15 -0800 Subject: [PATCH] feat(ivy): support injecting ChangeDetectorRef (#22469) PR Close #22469 --- .../core/src/core_render3_private_export.ts | 1 + packages/core/src/render3/component.ts | 96 +----- packages/core/src/render3/di.ts | 80 +++-- packages/core/src/render3/index.ts | 2 +- packages/core/src/render3/instructions.ts | 19 +- .../core/src/render3/interfaces/injector.ts | 7 + packages/core/src/render3/view_ref.ts | 84 ++++++ .../compiler_canonical_spec.ts | 49 +++- packages/core/test/render3/di_spec.ts | 276 +++++++++++++++++- 9 files changed, 491 insertions(+), 123 deletions(-) create mode 100644 packages/core/src/render3/view_ref.ts diff --git a/packages/core/src/core_render3_private_export.ts b/packages/core/src/core_render3_private_export.ts index ce3eb7b51c..fedee6c755 100644 --- a/packages/core/src/core_render3_private_export.ts +++ b/packages/core/src/core_render3_private_export.ts @@ -17,6 +17,7 @@ export { inject as ɵinject, injectTemplateRef as ɵinjectTemplateRef, injectViewContainerRef as ɵinjectViewContainerRef, + injectChangeDetectorRef as ɵinjectChangeDetectorRef, InjectFlags as ɵInjectFlags, PublicFeature as ɵPublicFeature, NgOnChangesFeature as ɵNgOnChangesFeature, diff --git a/packages/core/src/render3/component.ts b/packages/core/src/render3/component.ts index 3ba8945603..5c53ef6a34 100644 --- a/packages/core/src/render3/component.ts +++ b/packages/core/src/render3/component.ts @@ -10,16 +10,14 @@ // correctly implementing its interfaces for backwards compatibility. import {Injector} from '../di/injector'; import {ComponentRef as viewEngine_ComponentRef} from '../linker/component_factory'; -import {EmbeddedViewRef as viewEngine_EmbeddedViewRef} from '../linker/view_ref'; import {assertNotNull} from './assert'; -import {CLEAN_PROMISE, NG_HOST_SYMBOL, _getComponentHostLElementNode, createError, createLView, createTView, detectChanges, directiveCreate, enterView, getDirectiveInstance, hostElement, leaveView, locateHostElement, scheduleChangeDetection} from './instructions'; +import {CLEAN_PROMISE, _getComponentHostLElementNode, createLView, createTView, detectChanges, directiveCreate, enterView, getDirectiveInstance, hostElement, initChangeDetectorIfExisting, leaveView, locateHostElement, scheduleChangeDetection} from './instructions'; import {ComponentDef, ComponentType} from './interfaces/definition'; -import {LElementNode} from './interfaces/node'; -import {RElement, Renderer3, RendererFactory3, domRendererFactory3} from './interfaces/renderer'; +import {RElement, RendererFactory3, domRendererFactory3} from './interfaces/renderer'; import {LViewFlags, RootContext} from './interfaces/view'; -import {notImplemented, stringify} from './util'; - +import {stringify} from './util'; +import {createViewRef} from './view_ref'; /** Options that control how the component should be bootstrapped. */ @@ -69,7 +67,7 @@ export interface CreateComponentOptions { export function createComponentRef( componentType: ComponentType, opts: CreateComponentOptions): viewEngine_ComponentRef { const component = renderComponent(componentType, opts); - const hostView = createViewRef(() => detectChanges(component), component); + const hostView = createViewRef(component); return { location: {nativeElement: getHostElement(component)}, injector: opts.injector || NULL_INJECTOR, @@ -83,84 +81,6 @@ export function createComponentRef( }; } -/** - * Creates an EmbeddedViewRef. - * - * @param detectChanges The detectChanges function for this view - * @param context The context for this view - * @returns The EmbeddedViewRef - */ -function createViewRef(detectChanges: () => void, context: T): EmbeddedViewRef { - return addDestroyable(new EmbeddedViewRef(detectChanges), context); -} - -class EmbeddedViewRef implements viewEngine_EmbeddedViewRef { - // TODO: rootNodes should be replaced when properly implemented - rootNodes = null !; - context: T; - destroyed: boolean; - - constructor(public detectChanges: () => void) {} - - // inherited from core/ChangeDetectorRef - markForCheck() { - if (ngDevMode) { - throw notImplemented(); - } - } - detach() { - if (ngDevMode) { - throw notImplemented(); - } - } - - checkNoChanges() { - if (ngDevMode) { - throw notImplemented(); - } - } - - reattach() { - if (ngDevMode) { - throw notImplemented(); - } - } - - destroy(): void {} - - onDestroy(cb: Function): void {} -} - -/** Interface for destroy logic. Implemented by addDestroyable. */ -interface DestroyRef { - context: T; - /** Whether or not this object has been destroyed */ - destroyed: boolean; - /** Destroy the instance and call all onDestroy callbacks. */ - destroy(): void; - /** Register callbacks that should be called onDestroy */ - onDestroy(cb: Function): void; -} - -/** - * Decorates an object with destroy logic (implementing the DestroyRef interface) - * and returns the enhanced object. - * - * @param obj The object to decorate - * @returns The object with destroy logic - */ -function addDestroyable(obj: any, context: C): T&DestroyRef { - let destroyFn: Function[]|null = null; - obj.destroyed = false; - obj.destroy = function() { - destroyFn && destroyFn.forEach((fn) => fn()); - this.destroyed = true; - }; - obj.onDestroy = (fn: Function) => (destroyFn || (destroyFn = [])).push(fn); - obj.context = context; - return obj; -} - // TODO: A hack to not pull in the NullInjector from @angular/core. export const NULL_INJECTOR: Injector = { @@ -202,10 +122,12 @@ export function renderComponent( null !); try { // Create element node at index 0 in data array - hostElement(hostNode, componentDef); + const elementNode = hostElement(hostNode, componentDef); // Create directive instance with n() and store at index 1 in data array (el is 0) + const instance = componentDef.n(); component = rootContext.component = - getDirectiveInstance(directiveCreate(1, componentDef.n(), componentDef)); + getDirectiveInstance(directiveCreate(1, instance, componentDef)); + initChangeDetectorIfExisting(elementNode.nodeInjector, instance); } finally { leaveView(oldView); } diff --git a/packages/core/src/render3/di.ts b/packages/core/src/render3/di.ts index 8f33ee2013..27446d1503 100644 --- a/packages/core/src/render3/di.ts +++ b/packages/core/src/render3/di.ts @@ -8,6 +8,7 @@ // We are temporarily importing the existing viewEngine_from core so we can be sure we are // correctly implementing its interfaces for backwards compatibility. +import {ChangeDetectorRef as viewEngine_ChangeDetectorRef} from '../change_detection/change_detector_ref'; import {Injector} from '../di/injector'; import {ComponentFactory as viewEngine_ComponentFactory, ComponentRef as viewEngine_ComponentRef} from '../linker/component_factory'; import {ElementRef as viewEngine_ElementRef} from '../linker/element_ref'; @@ -24,10 +25,10 @@ import {LInjector} from './interfaces/injector'; import {LContainerNode, LElementNode, LNode, LNodeFlags, LViewNode} from './interfaces/node'; import {QueryReadType} from './interfaces/query'; import {Renderer3} from './interfaces/renderer'; -import {LView} from './interfaces/view'; import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert'; import {insertView} from './node_manipulation'; import {notImplemented, stringify} from './util'; +import {EmbeddedViewRef, ViewRef, addDestroyable, createViewRef} from './view_ref'; @@ -124,7 +125,8 @@ export function getOrCreateNodeInjectorForNode(node: LElementNode | LContainerNo injector: null, templateRef: null, viewContainerRef: null, - elementRef: null + elementRef: null, + changeDetectorRef: null }; } @@ -227,6 +229,55 @@ export function injectViewContainerRef(): viewEngine_ViewContainerRef { return getOrCreateContainerRef(getOrCreateNodeInjector()); } +/** Returns a ChangeDetectorRef (a.k.a. a ViewRef) */ +export function injectChangeDetectorRef(): viewEngine_ChangeDetectorRef { + return getOrCreateChangeDetectorRef(getOrCreateNodeInjector(), null); +} + +/** + * Creates a ViewRef and stores it on the injector as ChangeDetectorRef (public alias). + * Or, if it already exists, retrieves the existing instance. + * + * @returns The ChangeDetectorRef to use + */ +export function getOrCreateChangeDetectorRef( + di: LInjector, context: any): viewEngine_ChangeDetectorRef { + if (di.changeDetectorRef) return di.changeDetectorRef; + + const currentNode = di.node; + if (currentNode.data === null) { + // if data is null, this node is a regular element node (not a component) + return di.changeDetectorRef = getOrCreateHostChangeDetector(currentNode.view.node); + } else if ((currentNode.flags & LNodeFlags.TYPE_MASK) === LNodeFlags.Element) { + // if it's an element node with data, it's a component and context will be set later + return di.changeDetectorRef = createViewRef(context); + } + return null !; +} + +/** Gets or creates ChangeDetectorRef for the closest host component */ +function getOrCreateHostChangeDetector(currentNode: LViewNode | LElementNode): + viewEngine_ChangeDetectorRef { + const hostNode = getClosestComponentAncestor(currentNode); + const hostInjector = hostNode.nodeInjector; + const existingRef = hostInjector && hostInjector.changeDetectorRef; + + return existingRef ? existingRef : + createViewRef(hostNode.view.data[hostNode.flags >> LNodeFlags.INDX_SHIFT]); +} + +/** + * If the node is an embedded view, traverses up the view tree to return the closest + * ancestor view that is attached to a component. If it's already a component node, + * returns itself. + */ +function getClosestComponentAncestor(node: LViewNode | LElementNode): LElementNode { + while ((node.flags & LNodeFlags.TYPE_MASK) === LNodeFlags.View) { + node = node.view.node; + } + return node as LElementNode; +} + /** * Searches for an instance of the given directive type up the injector tree and returns * that instance if found. @@ -527,29 +578,6 @@ class TemplateRef implements viewEngine_TemplateRef { createEmbeddedView(context: T): viewEngine_EmbeddedViewRef { let viewNode: LViewNode = renderEmbeddedTemplate(null, this._template, context, this._renderer); - return new EmbeddedViewRef(viewNode, this._template, context); + return addDestroyable(new EmbeddedViewRef(viewNode, this._template, context)); } } - -class EmbeddedViewRef implements viewEngine_EmbeddedViewRef { - context: T; - rootNodes: any[]; - /** - * @internal - */ - _lViewNode: LViewNode; - - constructor(viewNode: LViewNode, template: ComponentTemplate, context: T) { - this._lViewNode = viewNode; - this.context = context; - } - - destroy(): void { notImplemented(); } - destroyed: boolean; - onDestroy(callback: Function) { notImplemented(); } - markForCheck(): void { notImplemented(); } - detach(): void { notImplemented(); } - detectChanges(): void { notImplemented(); } - checkNoChanges(): void { notImplemented(); } - reattach(): void { notImplemented(); } -} diff --git a/packages/core/src/render3/index.ts b/packages/core/src/render3/index.ts index 855d7f3d16..6391cb5a9b 100644 --- a/packages/core/src/render3/index.ts +++ b/packages/core/src/render3/index.ts @@ -11,7 +11,7 @@ import {NgOnChangesFeature, PublicFeature, defineComponent, defineDirective, def import {InjectFlags} from './di'; import {ComponentDef, ComponentTemplate, ComponentType, DirectiveDef, DirectiveDefFlags, DirectiveType} from './interfaces/definition'; -export {InjectFlags, QUERY_READ_CONTAINER_REF, QUERY_READ_ELEMENT_REF, QUERY_READ_FROM_NODE, QUERY_READ_TEMPLATE_REF, inject, injectElementRef, injectTemplateRef, injectViewContainerRef} from './di'; +export {InjectFlags, QUERY_READ_CONTAINER_REF, QUERY_READ_ELEMENT_REF, QUERY_READ_FROM_NODE, QUERY_READ_TEMPLATE_REF, inject, injectChangeDetectorRef, injectElementRef, injectTemplateRef, injectViewContainerRef} from './di'; export {CssSelector} from './interfaces/projection'; diff --git a/packages/core/src/render3/instructions.ts b/packages/core/src/render3/instructions.ts index c07c05108d..04f545ba80 100644 --- a/packages/core/src/render3/instructions.ts +++ b/packages/core/src/render3/instructions.ts @@ -10,6 +10,7 @@ import './ng_dev_mode'; import {assertEqual, assertLessThan, assertNotEqual, assertNotNull, assertNull, assertSame} from './assert'; import {LContainer, TContainer} from './interfaces/container'; +import {LInjector} from './interfaces/injector'; import {CssSelector, LProjection} from './interfaces/projection'; import {LQueries} from './interfaces/query'; import {LView, LViewFlags, LifecycleStage, RootContext, TData, TView} from './interfaces/view'; @@ -22,6 +23,7 @@ import {ComponentDef, ComponentTemplate, ComponentType, DirectiveDef, DirectiveT import {RElement, RText, Renderer3, RendererFactory3, ProceduralRenderer3, RendererStyleFlags3, isProceduralRenderer} from './interfaces/renderer'; import {isDifferent, stringify} from './util'; import {executeHooks, executeContentHooks, queueLifecycleHooks, queueInitHooks, executeInitHooks} from './hooks'; +import {ViewRef} from './view_ref'; /** * Directive (D) sets a property on all component instances using this constant as a key and the @@ -465,7 +467,9 @@ 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. - directiveCreate(++index, hostComponentDef.n(), hostComponentDef, queryName); + const instance = hostComponentDef.n(); + directiveCreate(++index, instance, hostComponentDef, queryName); + initChangeDetectorIfExisting(node.nodeInjector, instance); } hack_declareDirectives(index, directiveTypes, localRefs); } @@ -473,6 +477,13 @@ export function elementStart( return native; } +/** Sets the context for a ChangeDetectorRef to the given instance. */ +export function initChangeDetectorIfExisting(injector: LInjector | null, instance: any): void { + if (injector && injector.changeDetectorRef != null) { + (injector.changeDetectorRef as ViewRef)._setComponentContext(instance); + } +} + /** * This function instantiates a directive with a correct queryName. It is a hack since we should * compute the query value only once and store it with the template (rather than on each invocation) @@ -589,10 +600,12 @@ export function locateHostElement( * * @param rNode Render host element. * @param def ComponentDef + * + * @returns LElementNode created */ -export function hostElement(rNode: RElement | null, def: ComponentDef) { +export function hostElement(rNode: RElement | null, def: ComponentDef): LElementNode { resetApplicationState(); - createLNode( + return createLNode( 0, LNodeFlags.Element, rNode, createLView( -1, renderer, getOrCreateTView(def.template), null, null, def.onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways)); diff --git a/packages/core/src/render3/interfaces/injector.ts b/packages/core/src/render3/interfaces/injector.ts index ca533a339e..59d79c61f7 100644 --- a/packages/core/src/render3/interfaces/injector.ts +++ b/packages/core/src/render3/interfaces/injector.ts @@ -6,6 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ +import {ChangeDetectorRef} from '../../change_detection/change_detector_ref'; import {Injector} from '../../di/injector'; import {ElementRef} from '../../linker/element_ref'; import {TemplateRef} from '../../linker/template_ref'; @@ -66,6 +67,12 @@ export interface LInjector { /** Stores the ElementRef so subsequent injections of the ElementRef get the same instance. */ elementRef: ElementRef|null; + + /** + * Stores the ChangeDetectorRef so subsequent injections of the ChangeDetectorRef get the + * same instance. + */ + changeDetectorRef: ChangeDetectorRef|null; } // Note: This hack is necessary so we don't erroneously get a circular dependency diff --git a/packages/core/src/render3/view_ref.ts b/packages/core/src/render3/view_ref.ts new file mode 100644 index 0000000000..fe9965d8b2 --- /dev/null +++ b/packages/core/src/render3/view_ref.ts @@ -0,0 +1,84 @@ +/** + * @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 {EmbeddedViewRef as viewEngine_EmbeddedViewRef, ViewRef as viewEngine_ViewRef} from '../linker/view_ref'; + +import {ComponentTemplate} from './interfaces/definition'; +import {LViewNode} from './interfaces/node'; +import {notImplemented} from './util'; + +export class ViewRef implements viewEngine_EmbeddedViewRef { + context: T; + rootNodes: any[]; + + constructor(context: T|null) { this.context = context !; } + + /** @internal */ + _setComponentContext(context: T) { this.context = context; } + + destroy(): void { notImplemented(); } + destroyed: boolean; + onDestroy(callback: Function) { notImplemented(); } + markForCheck(): void { notImplemented(); } + detach(): void { notImplemented(); } + detectChanges(): void { notImplemented(); } + checkNoChanges(): void { notImplemented(); } + reattach(): void { notImplemented(); } +} + + +export class EmbeddedViewRef extends ViewRef { + /** + * @internal + */ + _lViewNode: LViewNode; + + constructor(viewNode: LViewNode, template: ComponentTemplate, context: T) { + super(context); + this._lViewNode = viewNode; + } +} + +/** + * Creates a ViewRef bundled with destroy functionality. + * + * @param context The context for this view + * @returns The ViewRef + */ +export function createViewRef(context: T): ViewRef { + // TODO: add detectChanges back in when implementing ChangeDetectorRef.detectChanges + return addDestroyable(new ViewRef(context)); +} + +/** Interface for destroy logic. Implemented by addDestroyable. */ +export interface DestroyRef { + /** Whether or not this object has been destroyed */ + destroyed: boolean; + /** Destroy the instance and call all onDestroy callbacks. */ + destroy(): void; + /** Register callbacks that should be called onDestroy */ + onDestroy(cb: Function): void; +} + +/** + * Decorates an object with destroy logic (implementing the DestroyRef interface) + * and returns the enhanced object. + * + * @param obj The object to decorate + * @returns The object with destroy logic + */ +export function addDestroyable(obj: any): T&DestroyRef { + let destroyFn: Function[]|null = null; + obj.destroyed = false; + obj.destroy = function() { + destroyFn && destroyFn.forEach((fn) => fn()); + this.destroyed = true; + }; + obj.onDestroy = (fn: Function) => (destroyFn || (destroyFn = [])).push(fn); + return obj; +} diff --git a/packages/core/test/render3/compiler_canonical/compiler_canonical_spec.ts b/packages/core/test/render3/compiler_canonical/compiler_canonical_spec.ts index e69069ff23..b44e41b93b 100644 --- a/packages/core/test/render3/compiler_canonical/compiler_canonical_spec.ts +++ b/packages/core/test/render3/compiler_canonical/compiler_canonical_spec.ts @@ -6,9 +6,8 @@ * found in the LICENSE file at https://angular.io/license */ -import {ChangeDetectionStrategy, Component, ContentChild, ContentChildren, Directive, HostBinding, HostListener, Injectable, Input, NgModule, OnDestroy, Optional, Pipe, PipeTransform, QueryList, SimpleChanges, TemplateRef, ViewChild, ViewChildren, ViewContainerRef} from '../../../src/core'; +import {ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChild, ContentChildren, Directive, HostBinding, HostListener, Injectable, Input, NgModule, OnDestroy, Optional, Pipe, PipeTransform, QueryList, SimpleChanges, TemplateRef, ViewChild, ViewChildren, ViewContainerRef} from '../../../src/core'; import * as $r3$ from '../../../src/core_render3_private_export'; - import {renderComponent, toHtml} from '../render_util'; /** @@ -1313,6 +1312,52 @@ describe('compiler specification', () => { }); + it('should inject ChangeDetectorRef', () => { + type $MyComp$ = MyComp; + type $MyApp$ = MyApp; + + @Component({selector: 'my-comp', template: `{{ value }}`}) + class MyComp { + value: string; + constructor(public cdr: ChangeDetectorRef) { this.value = (cdr.constructor as any).name; } + + // NORMATIVE + static ngComponentDef = $r3$.ɵdefineComponent({ + type: MyComp, + tag: 'my-comp', + factory: function MyComp_Factory() { return new MyComp($r3$.ɵinjectChangeDetectorRef()); }, + template: function MyComp_Template(ctx: $MyComp$, cm: $boolean$) { + if (cm) { + $r3$.ɵT(0); + } + $r3$.ɵt(0, $r3$.ɵb(ctx.value)); + } + }); + // /NORMATIVE + } + + class MyApp { + static ngComponentDef = $r3$.ɵdefineComponent({ + type: MyApp, + tag: 'my-app', + factory: function MyApp_Factory() { return new MyApp(); }, + /** */ + template: function MyApp_Template(ctx: $MyApp$, cm: $boolean$) { + if (cm) { + $r3$.ɵE(0, MyComp); + $r3$.ɵe(); + } + MyComp.ngComponentDef.h(1, 0); + $r3$.ɵr(1, 0); + } + }); + } + + const app = renderComponent(MyApp); + // ChangeDetectorRef is the token, ViewRef is historically the constructor + expect(toHtml(app)).toEqual('ViewRef'); + }); + describe('template variables', () => { interface ForOfContext { diff --git a/packages/core/test/render3/di_spec.ts b/packages/core/test/render3/di_spec.ts index 8184d55c31..97c79c2bb7 100644 --- a/packages/core/test/render3/di_spec.ts +++ b/packages/core/test/render3/di_spec.ts @@ -6,17 +6,18 @@ * found in the LICENSE file at https://angular.io/license */ -import {ElementRef, TemplateRef, ViewContainerRef} from '@angular/core'; +import {ChangeDetectorRef, ElementRef, TemplateRef, ViewContainerRef} from '@angular/core'; import {defineComponent} from '../../src/render3/definition'; import {InjectFlags, bloomAdd, bloomFindPossibleInjector, getOrCreateNodeInjector} from '../../src/render3/di'; -import {PublicFeature, defineDirective, inject, injectElementRef, injectTemplateRef, injectViewContainerRef} from '../../src/render3/index'; -import {bind, container, containerRefreshEnd, containerRefreshStart, createLNode, createLView, createTView, elementEnd, elementStart, embeddedViewEnd, embeddedViewStart, enterView, interpolation2, leaveView, load, text, textBinding} from '../../src/render3/instructions'; +import {NgOnChangesFeature, PublicFeature, defineDirective, inject, injectChangeDetectorRef, injectElementRef, injectTemplateRef, injectViewContainerRef} from '../../src/render3/index'; +import {bind, container, containerRefreshEnd, containerRefreshStart, createLNode, createLView, createTView, directiveRefresh, elementEnd, elementStart, embeddedViewEnd, embeddedViewStart, enterView, interpolation2, leaveView, load, projection, projectionDef, text, textBinding} from '../../src/render3/instructions'; import {LInjector} from '../../src/render3/interfaces/injector'; import {LNodeFlags} from '../../src/render3/interfaces/node'; import {LViewFlags} from '../../src/render3/interfaces/view'; +import {ViewRef} from '../../src/render3/view_ref'; -import {renderComponent, renderToHtml} from './render_util'; +import {renderComponent, renderToHtml, toHtml} from './render_util'; describe('di', () => { describe('no dependencies', () => { @@ -199,6 +200,273 @@ describe('di', () => { }); }); + describe('ChangeDetectorRef', () => { + let dir: Directive; + let dirSameInstance: DirectiveSameInstance; + let comp: MyComp; + + class MyComp { + constructor(public cdr: ChangeDetectorRef) {} + + static ngComponentDef = defineComponent({ + type: MyComp, + tag: 'my-comp', + factory: () => comp = new MyComp(injectChangeDetectorRef()), + template: function(ctx: MyComp, cm: boolean) { + if (cm) { + projectionDef(0); + projection(1, 0); + } + } + }); + } + + class Directive { + value: string; + constructor(public cdr: ChangeDetectorRef) { this.value = (cdr.constructor as any).name; } + static ngDirectiveDef = defineDirective({ + type: Directive, + factory: () => dir = new Directive(injectChangeDetectorRef()), + features: [PublicFeature], + exportAs: 'dir' + }); + } + + class DirectiveSameInstance { + constructor(public cdr: ChangeDetectorRef) {} + + static ngDirectiveDef = defineDirective({ + type: DirectiveSameInstance, + factory: () => dirSameInstance = new DirectiveSameInstance(injectChangeDetectorRef()) + }); + } + + const $e0_attrs$ = ['dir', '', 'dirSameInstance', '']; + + it('should inject current component ChangeDetectorRef into directives on components', () => { + class MyApp { + static ngComponentDef = defineComponent({ + type: MyApp, + tag: 'my-app', + factory: () => new MyApp(), + /** {{ dir.value }} */ + template: function(ctx: any, cm: boolean) { + if (cm) { + elementStart(0, MyComp, $e0_attrs$, [Directive, DirectiveSameInstance]); + elementEnd(); + text(4); + } + textBinding(4, bind(load(2).value)); + MyComp.ngComponentDef.h(1, 0); + Directive.ngDirectiveDef.h(2, 0); + DirectiveSameInstance.ngDirectiveDef.h(3, 0); + directiveRefresh(1, 0); + directiveRefresh(2, 0); + directiveRefresh(3, 0); + } + }); + } + + const app = renderComponent(MyApp); + // ChangeDetectorRef is the token, ViewRef has historically been the constructor + expect(toHtml(app)).toEqual('ViewRef'); + expect((comp !.cdr as ViewRef).context).toBe(comp); + + expect(dir !.cdr).toBe(comp !.cdr); + expect(dir !.cdr).toBe(dirSameInstance !.cdr); + }); + + it('should inject host component ChangeDetectorRef into directives on elements', () => { + + class MyApp { + constructor(public cdr: ChangeDetectorRef) {} + + static ngComponentDef = defineComponent({ + type: MyApp, + tag: 'my-app', + factory: () => new MyApp(injectChangeDetectorRef()), + /**
{{ dir.value }}
*/ + template: function(ctx: any, cm: boolean) { + if (cm) { + elementStart(0, 'div', $e0_attrs$, [Directive, DirectiveSameInstance]); + { text(3); } + elementEnd(); + } + textBinding(3, bind(load(1).value)); + Directive.ngDirectiveDef.h(1, 0); + DirectiveSameInstance.ngDirectiveDef.h(2, 0); + directiveRefresh(1, 0); + directiveRefresh(2, 0); + } + }); + } + + const app = renderComponent(MyApp); + expect(toHtml(app)).toEqual('
ViewRef
'); + expect((app !.cdr as ViewRef).context).toBe(app); + + expect(dir !.cdr).toBe(app.cdr); + expect(dir !.cdr).toBe(dirSameInstance !.cdr); + }); + + it('should inject host component ChangeDetectorRef into directives in ContentChildren', () => { + class MyApp { + constructor(public cdr: ChangeDetectorRef) {} + + static ngComponentDef = defineComponent({ + type: MyApp, + tag: 'my-app', + factory: () => new MyApp(injectChangeDetectorRef()), + /** + * + *
+ *
+ * {{ dir.value }} + */ + template: function(ctx: any, cm: boolean) { + if (cm) { + elementStart(0, MyComp); + { + elementStart(2, 'div', $e0_attrs$, [Directive, DirectiveSameInstance]); + elementEnd(); + } + elementEnd(); + text(5); + } + textBinding(5, bind(load(3).value)); + MyComp.ngComponentDef.h(1, 0); + Directive.ngDirectiveDef.h(3, 2); + DirectiveSameInstance.ngDirectiveDef.h(4, 2); + directiveRefresh(1, 0); + directiveRefresh(3, 2); + directiveRefresh(4, 2); + } + }); + } + + const app = renderComponent(MyApp); + expect(toHtml(app)) + .toEqual('
ViewRef'); + expect((app !.cdr as ViewRef).context).toBe(app); + + expect(dir !.cdr).toBe(app !.cdr); + expect(dir !.cdr).toBe(dirSameInstance !.cdr); + }); + + it('should inject host component ChangeDetectorRef into directives in embedded views', () => { + + class MyApp { + showing = true; + + constructor(public cdr: ChangeDetectorRef) {} + + static ngComponentDef = defineComponent({ + type: MyApp, + tag: 'my-app', + factory: () => new MyApp(injectChangeDetectorRef()), + /** + * % if (showing) { + *
{{ dir.value }}
+ * % } + */ + template: function(ctx: MyApp, cm: boolean) { + if (cm) { + container(0); + } + containerRefreshStart(0); + { + if (ctx.showing) { + if (embeddedViewStart(0)) { + elementStart(0, 'div', $e0_attrs$, [Directive, DirectiveSameInstance]); + { text(3); } + elementEnd(); + } + textBinding(3, bind(load(1).value)); + Directive.ngDirectiveDef.h(1, 0); + DirectiveSameInstance.ngDirectiveDef.h(2, 0); + directiveRefresh(1, 0); + directiveRefresh(2, 0); + } + embeddedViewEnd(); + } + containerRefreshEnd(); + } + }); + } + + const app = renderComponent(MyApp); + expect(toHtml(app)).toEqual('
ViewRef
'); + expect((app !.cdr as ViewRef).context).toBe(app); + + expect(dir !.cdr).toBe(app.cdr); + expect(dir !.cdr).toBe(dirSameInstance !.cdr); + }); + + it('should inject host component ChangeDetectorRef into directives on containers', () => { + class IfDirective { + /* @Input */ + myIf = true; + + constructor(public template: TemplateRef, public vcr: ViewContainerRef) {} + + ngOnChanges() { + if (this.myIf) { + this.vcr.createEmbeddedView(this.template); + } + } + + static ngDirectiveDef = defineDirective({ + type: IfDirective, + factory: () => new IfDirective(injectTemplateRef(), injectViewContainerRef()), + inputs: {myIf: 'myIf'}, + features: [PublicFeature, NgOnChangesFeature] + }); + } + + class MyApp { + showing = true; + + constructor(public cdr: ChangeDetectorRef) {} + + static ngComponentDef = defineComponent({ + type: MyApp, + tag: 'my-app', + factory: () => new MyApp(injectChangeDetectorRef()), + /**
{{ dir.value }}
*/ + template: function(ctx: MyApp, cm: boolean) { + if (cm) { + container(0, [IfDirective], C1); + } + containerRefreshStart(0); + { directiveRefresh(1, 0); } + containerRefreshEnd(); + + function C1(ctx1: any, cm1: boolean) { + if (cm1) { + elementStart(0, 'div', $e0_attrs$, [Directive, DirectiveSameInstance]); + { text(3); } + elementEnd(); + } + textBinding(3, bind(load(1).value)); + Directive.ngDirectiveDef.h(1, 0); + DirectiveSameInstance.ngDirectiveDef.h(2, 0); + directiveRefresh(1, 0); + directiveRefresh(2, 0); + } + } + }); + } + + const app = renderComponent(MyApp); + expect(toHtml(app)).toEqual('
ViewRef
'); + expect((app !.cdr as ViewRef).context).toBe(app); + + expect(dir !.cdr).toBe(app.cdr); + expect(dir !.cdr).toBe(dirSameInstance !.cdr); + }); + + }); + describe('inject', () => { describe('bloom filter', () => { let di: LInjector;