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,
|
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,
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(); }
|
|
||||||
}
|
|
||||||
|
|
|
@ -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';
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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));
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
* 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 {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in New Issue