feat(ivy): support injecting ChangeDetectorRef (#22469)

PR Close #22469
This commit is contained in:
Kara Erickson 2018-02-26 16:58:15 -08:00 committed by Alex Eagle
parent aabe16c08c
commit 9eaf1bbe67
9 changed files with 491 additions and 123 deletions

View File

@ -17,6 +17,7 @@ export {
inject as ɵinject, inject as ɵinject,
injectTemplateRef as ɵinjectTemplateRef, injectTemplateRef as ɵinjectTemplateRef,
injectViewContainerRef as ɵinjectViewContainerRef, injectViewContainerRef as ɵinjectViewContainerRef,
injectChangeDetectorRef as ɵinjectChangeDetectorRef,
InjectFlags as ɵInjectFlags, InjectFlags as ɵInjectFlags,
PublicFeature as ɵPublicFeature, PublicFeature as ɵPublicFeature,
NgOnChangesFeature as ɵNgOnChangesFeature, NgOnChangesFeature as ɵNgOnChangesFeature,

View File

@ -10,16 +10,14 @@
// correctly implementing its interfaces for backwards compatibility. // correctly implementing its interfaces for backwards compatibility.
import {Injector} from '../di/injector'; import {Injector} from '../di/injector';
import {ComponentRef as viewEngine_ComponentRef} from '../linker/component_factory'; import {ComponentRef as viewEngine_ComponentRef} from '../linker/component_factory';
import {EmbeddedViewRef as viewEngine_EmbeddedViewRef} from '../linker/view_ref';
import {assertNotNull} from './assert'; 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 {ComponentDef, ComponentType} from './interfaces/definition';
import {LElementNode} from './interfaces/node'; import {RElement, RendererFactory3, domRendererFactory3} from './interfaces/renderer';
import {RElement, Renderer3, RendererFactory3, domRendererFactory3} from './interfaces/renderer';
import {LViewFlags, RootContext} from './interfaces/view'; 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. */ /** Options that control how the component should be bootstrapped. */
@ -69,7 +67,7 @@ export interface CreateComponentOptions {
export function createComponentRef<T>( export function createComponentRef<T>(
componentType: ComponentType<T>, opts: CreateComponentOptions): viewEngine_ComponentRef<T> { componentType: ComponentType<T>, opts: CreateComponentOptions): viewEngine_ComponentRef<T> {
const component = renderComponent(componentType, opts); const component = renderComponent(componentType, opts);
const hostView = createViewRef(() => detectChanges(component), component); const hostView = createViewRef(component);
return { return {
location: {nativeElement: getHostElement(component)}, location: {nativeElement: getHostElement(component)},
injector: opts.injector || NULL_INJECTOR, injector: opts.injector || NULL_INJECTOR,
@ -83,84 +81,6 @@ export function createComponentRef<T>(
}; };
} }
/**
* Creates an EmbeddedViewRef.
*
* @param detectChanges The detectChanges function for this view
* @param context The context for this view
* @returns The EmbeddedViewRef
*/
function createViewRef<T>(detectChanges: () => void, context: T): EmbeddedViewRef<T> {
return addDestroyable(new EmbeddedViewRef(detectChanges), context);
}
class EmbeddedViewRef<T> implements viewEngine_EmbeddedViewRef<T> {
// 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<T> {
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<T, C>(obj: any, context: C): T&DestroyRef<C> {
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. // TODO: A hack to not pull in the NullInjector from @angular/core.
export const NULL_INJECTOR: Injector = { export const NULL_INJECTOR: Injector = {
@ -202,10 +122,12 @@ export function renderComponent<T>(
null !); null !);
try { try {
// Create element node at index 0 in data array // 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) // Create directive instance with n() and store at index 1 in data array (el is 0)
const instance = componentDef.n();
component = rootContext.component = component = rootContext.component =
getDirectiveInstance(directiveCreate(1, componentDef.n(), componentDef)); getDirectiveInstance(directiveCreate(1, instance, componentDef));
initChangeDetectorIfExisting(elementNode.nodeInjector, instance);
} finally { } finally {
leaveView(oldView); leaveView(oldView);
} }

View File

@ -8,6 +8,7 @@
// We are temporarily importing the existing viewEngine_from core so we can be sure we are // We are temporarily importing the existing viewEngine_from core so we can be sure we are
// correctly implementing its interfaces for backwards compatibility. // correctly implementing its interfaces for backwards compatibility.
import {ChangeDetectorRef as viewEngine_ChangeDetectorRef} from '../change_detection/change_detector_ref';
import {Injector} from '../di/injector'; import {Injector} from '../di/injector';
import {ComponentFactory as viewEngine_ComponentFactory, ComponentRef as viewEngine_ComponentRef} from '../linker/component_factory'; import {ComponentFactory as viewEngine_ComponentFactory, ComponentRef as viewEngine_ComponentRef} from '../linker/component_factory';
import {ElementRef as viewEngine_ElementRef} from '../linker/element_ref'; 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 {LContainerNode, LElementNode, LNode, LNodeFlags, LViewNode} from './interfaces/node';
import {QueryReadType} from './interfaces/query'; import {QueryReadType} from './interfaces/query';
import {Renderer3} from './interfaces/renderer'; import {Renderer3} from './interfaces/renderer';
import {LView} from './interfaces/view';
import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert'; import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert';
import {insertView} from './node_manipulation'; import {insertView} from './node_manipulation';
import {notImplemented, stringify} from './util'; 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, injector: null,
templateRef: null, templateRef: null,
viewContainerRef: null, viewContainerRef: null,
elementRef: null elementRef: null,
changeDetectorRef: null
}; };
} }
@ -227,6 +229,55 @@ export function injectViewContainerRef(): viewEngine_ViewContainerRef {
return getOrCreateContainerRef(getOrCreateNodeInjector()); 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 * Searches for an instance of the given directive type up the injector tree and returns
* that instance if found. * that instance if found.
@ -527,29 +578,6 @@ class TemplateRef<T> implements viewEngine_TemplateRef<T> {
createEmbeddedView(context: T): viewEngine_EmbeddedViewRef<T> { createEmbeddedView(context: T): viewEngine_EmbeddedViewRef<T> {
let viewNode: LViewNode = renderEmbeddedTemplate(null, this._template, context, this._renderer); 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<T> implements viewEngine_EmbeddedViewRef<T> {
context: T;
rootNodes: any[];
/**
* @internal
*/
_lViewNode: LViewNode;
constructor(viewNode: LViewNode, template: ComponentTemplate<T>, 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(); }
}

View File

@ -11,7 +11,7 @@ import {NgOnChangesFeature, PublicFeature, defineComponent, defineDirective, def
import {InjectFlags} from './di'; import {InjectFlags} from './di';
import {ComponentDef, ComponentTemplate, ComponentType, DirectiveDef, DirectiveDefFlags, DirectiveType} from './interfaces/definition'; 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'; export {CssSelector} from './interfaces/projection';

View File

@ -10,6 +10,7 @@ import './ng_dev_mode';
import {assertEqual, assertLessThan, assertNotEqual, assertNotNull, assertNull, assertSame} from './assert'; import {assertEqual, assertLessThan, assertNotEqual, assertNotNull, assertNull, assertSame} from './assert';
import {LContainer, TContainer} from './interfaces/container'; import {LContainer, TContainer} from './interfaces/container';
import {LInjector} from './interfaces/injector';
import {CssSelector, LProjection} from './interfaces/projection'; import {CssSelector, LProjection} from './interfaces/projection';
import {LQueries} from './interfaces/query'; import {LQueries} from './interfaces/query';
import {LView, LViewFlags, LifecycleStage, RootContext, TData, TView} from './interfaces/view'; 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 {RElement, RText, Renderer3, RendererFactory3, ProceduralRenderer3, RendererStyleFlags3, isProceduralRenderer} from './interfaces/renderer';
import {isDifferent, stringify} from './util'; import {isDifferent, stringify} from './util';
import {executeHooks, executeContentHooks, queueLifecycleHooks, queueInitHooks, executeInitHooks} from './hooks'; 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 * 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) { if (hostComponentDef) {
// TODO(mhevery): This assumes that the directives come in correct order, which // TODO(mhevery): This assumes that the directives come in correct order, which
// is not guaranteed. Must be refactored to take it into account. // is not guaranteed. Must be refactored to take it into account.
directiveCreate(++index, hostComponentDef.n(), hostComponentDef, queryName); const instance = hostComponentDef.n();
directiveCreate(++index, instance, hostComponentDef, queryName);
initChangeDetectorIfExisting(node.nodeInjector, instance);
} }
hack_declareDirectives(index, directiveTypes, localRefs); hack_declareDirectives(index, directiveTypes, localRefs);
} }
@ -473,6 +477,13 @@ export function elementStart(
return native; 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<any>)._setComponentContext(instance);
}
}
/** /**
* This function instantiates a directive with a correct queryName. It is a hack since we should * 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) * 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 rNode Render host element.
* @param def ComponentDef * @param def ComponentDef
*
* @returns LElementNode created
*/ */
export function hostElement(rNode: RElement | null, def: ComponentDef<any>) { export function hostElement(rNode: RElement | null, def: ComponentDef<any>): LElementNode {
resetApplicationState(); resetApplicationState();
createLNode( return createLNode(
0, LNodeFlags.Element, rNode, createLView( 0, LNodeFlags.Element, rNode, createLView(
-1, renderer, getOrCreateTView(def.template), null, null, -1, renderer, getOrCreateTView(def.template), null, null,
def.onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways)); def.onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways));

View File

@ -6,6 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {ChangeDetectorRef} from '../../change_detection/change_detector_ref';
import {Injector} from '../../di/injector'; import {Injector} from '../../di/injector';
import {ElementRef} from '../../linker/element_ref'; import {ElementRef} from '../../linker/element_ref';
import {TemplateRef} from '../../linker/template_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. */ /** Stores the ElementRef so subsequent injections of the ElementRef get the same instance. */
elementRef: ElementRef|null; 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 // Note: This hack is necessary so we don't erroneously get a circular dependency

View File

@ -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<T> implements viewEngine_EmbeddedViewRef<T> {
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<T> extends ViewRef<T> {
/**
* @internal
*/
_lViewNode: LViewNode;
constructor(viewNode: LViewNode, template: ComponentTemplate<T>, 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<T>(context: T): ViewRef<T> {
// TODO: add detectChanges back in when implementing ChangeDetectorRef.detectChanges
return addDestroyable(new ViewRef(context));
}
/** Interface for destroy logic. Implemented by addDestroyable. */
export interface DestroyRef<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
*/
export function addDestroyable<T, C>(obj: any): T&DestroyRef<C> {
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;
}

View File

@ -6,9 +6,8 @@
* found in the LICENSE file at https://angular.io/license * 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 * as $r3$ from '../../../src/core_render3_private_export';
import {renderComponent, toHtml} from '../render_util'; 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(); },
/** <my-comp></my-comp> */
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('<my-comp>ViewRef</my-comp>');
});
describe('template variables', () => { describe('template variables', () => {
interface ForOfContext { interface ForOfContext {

View File

@ -6,17 +6,18 @@
* found in the LICENSE file at https://angular.io/license * 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 {defineComponent} from '../../src/render3/definition';
import {InjectFlags, bloomAdd, bloomFindPossibleInjector, getOrCreateNodeInjector} from '../../src/render3/di'; import {InjectFlags, bloomAdd, bloomFindPossibleInjector, getOrCreateNodeInjector} from '../../src/render3/di';
import {PublicFeature, defineDirective, inject, injectElementRef, injectTemplateRef, injectViewContainerRef} from '../../src/render3/index'; import {NgOnChangesFeature, PublicFeature, defineDirective, inject, injectChangeDetectorRef, 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 {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 {LInjector} from '../../src/render3/interfaces/injector';
import {LNodeFlags} from '../../src/render3/interfaces/node'; import {LNodeFlags} from '../../src/render3/interfaces/node';
import {LViewFlags} from '../../src/render3/interfaces/view'; 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('di', () => {
describe('no dependencies', () => { 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(),
/** <my-comp dir dirSameInstance #dir="dir"></my-comp> {{ dir.value }} */
template: function(ctx: any, cm: boolean) {
if (cm) {
elementStart(0, MyComp, $e0_attrs$, [Directive, DirectiveSameInstance]);
elementEnd();
text(4);
}
textBinding(4, bind(load<Directive>(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('<my-comp dir="" dirsameinstance=""></my-comp>ViewRef');
expect((comp !.cdr as ViewRef<MyComp>).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()),
/** <div dir dirSameInstance #dir="dir"> {{ dir.value }} </div> */
template: function(ctx: any, cm: boolean) {
if (cm) {
elementStart(0, 'div', $e0_attrs$, [Directive, DirectiveSameInstance]);
{ text(3); }
elementEnd();
}
textBinding(3, bind(load<Directive>(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('<div dir="" dirsameinstance="">ViewRef</div>');
expect((app !.cdr as ViewRef<MyApp>).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()),
/**
* <my-comp>
* <div dir dirSameInstance #dir="dir"></div>
* </my-comp>
* {{ 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<Directive>(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('<my-comp><div dir="" dirsameinstance=""></div></my-comp>ViewRef');
expect((app !.cdr as ViewRef<MyApp>).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) {
* <div dir dirSameInstance #dir="dir"> {{ dir.value }} </div>
* % }
*/
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<Directive>(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('<div dir="" dirsameinstance="">ViewRef</div>');
expect((app !.cdr as ViewRef<MyApp>).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<any>, 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()),
/** <div *myIf="showing" dir dirSameInstance #dir="dir"> {{ dir.value }} </div> */
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<Directive>(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('<div dir="" dirsameinstance="">ViewRef</div>');
expect((app !.cdr as ViewRef<MyApp>).context).toBe(app);
expect(dir !.cdr).toBe(app.cdr);
expect(dir !.cdr).toBe(dirSameInstance !.cdr);
});
});
describe('inject', () => { describe('inject', () => {
describe('bloom filter', () => { describe('bloom filter', () => {
let di: LInjector; let di: LInjector;