feat(ivy): support injecting ChangeDetectorRef (#22469)
PR Close #22469
This commit is contained in:
parent
aabe16c08c
commit
9eaf1bbe67
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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(); }
|
||||
}
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue