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,
injectTemplateRef as ɵinjectTemplateRef,
injectViewContainerRef as ɵinjectViewContainerRef,
injectChangeDetectorRef as ɵinjectChangeDetectorRef,
InjectFlags as ɵInjectFlags,
PublicFeature as ɵPublicFeature,
NgOnChangesFeature as ɵNgOnChangesFeature,

View File

@ -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<T>(
componentType: ComponentType<T>, opts: CreateComponentOptions): viewEngine_ComponentRef<T> {
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<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.
export const NULL_INJECTOR: Injector = {
@ -202,10 +122,12 @@ export function renderComponent<T>(
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);
}

View File

@ -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<T> implements viewEngine_TemplateRef<T> {
createEmbeddedView(context: T): viewEngine_EmbeddedViewRef<T> {
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 {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';

View File

@ -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<any>)._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<any>) {
export function hostElement(rNode: RElement | null, def: ComponentDef<any>): LElementNode {
resetApplicationState();
createLNode(
return createLNode(
0, LNodeFlags.Element, rNode, createLView(
-1, renderer, getOrCreateTView(def.template), null, null,
def.onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways));

View File

@ -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

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
*/
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(); },
/** <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', () => {
interface ForOfContext {

View File

@ -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(),
/** <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('bloom filter', () => {
let di: LInjector;