feat(ivy): support ViewContainerRef.createComponent() (#24997)
PR Close #24997
This commit is contained in:
parent
d523630ea2
commit
445b9a5627
|
@ -244,7 +244,7 @@ The goal is for the `@Component` (and friends) to be the compiler of template. S
|
|||
| `clear()` | ✅ | n/a | n/a | n/a | n/a | n/a |
|
||||
| `get()` | ✅ | n/a | n/a | n/a | n/a | n/a |
|
||||
| `createEmbededView()` | ✅ | ✅ | n/a | n/a | n/a | n/a |
|
||||
| `createComponent()` | ❌ | n/a | n/a | n/a | n/a | n/a |
|
||||
| `createComponent()` | ✅ | n/a | n/a | n/a | n/a | n/a |
|
||||
| `insert()` | ✅ | n/a | n/a | n/a | n/a | n/a |
|
||||
| `move()` | ✅ | n/a | n/a | n/a | n/a | n/a |
|
||||
| `indexOf()` | ✅ | n/a | n/a | n/a | n/a | n/a |
|
||||
|
|
|
@ -17,12 +17,12 @@ import {RendererFactory2} from '../render/api';
|
|||
import {Type} from '../type';
|
||||
|
||||
import {assertComponentType, assertDefined} from './assert';
|
||||
import {createRootContext} from './component';
|
||||
import {baseDirectiveCreate, createLViewData, createTView, enterView, hostElement, initChangeDetectorIfExisting, locateHostElement} from './instructions';
|
||||
import {ComponentDefInternal, ComponentType} from './interfaces/definition';
|
||||
import {LElementNode} from './interfaces/node';
|
||||
import {RElement} from './interfaces/renderer';
|
||||
import {INJECTOR, LViewData, LViewFlags, RootContext} from './interfaces/view';
|
||||
import {LifecycleHooksFeature, createRootContext} from './component';
|
||||
import {baseDirectiveCreate, createLNode, createLViewData, createTView, elementCreate, enterView, hostElement, initChangeDetectorIfExisting, locateHostElement, renderEmbeddedTemplate} from './instructions';
|
||||
import {ComponentDefInternal, ComponentType, RenderFlags} from './interfaces/definition';
|
||||
import {LElementNode, TNode, TNodeType} from './interfaces/node';
|
||||
import {RElement, domRendererFactory3} from './interfaces/renderer';
|
||||
import {FLAGS, INJECTOR, LViewData, LViewFlags, RootContext, TVIEW} from './interfaces/view';
|
||||
import {ViewRef} from './view_ref';
|
||||
|
||||
export class ComponentFactoryResolver extends viewEngine_ComponentFactoryResolver {
|
||||
|
@ -80,23 +80,28 @@ export class ComponentFactory<T> extends viewEngine_ComponentFactory<T> {
|
|||
}
|
||||
|
||||
create(
|
||||
parentComponentInjector: Injector, projectableNodes?: any[][]|undefined,
|
||||
rootSelectorOrNode?: any,
|
||||
injector: Injector, projectableNodes?: any[][]|undefined, rootSelectorOrNode?: any,
|
||||
ngModule?: viewEngine_NgModuleRef<any>|undefined): viewEngine_ComponentRef<T> {
|
||||
ngDevMode && assertDefined(ngModule, 'ngModule should always be defined');
|
||||
const isInternalRootView = rootSelectorOrNode === undefined;
|
||||
|
||||
const rendererFactory = ngModule ? ngModule.injector.get(RendererFactory2) : document;
|
||||
const hostNode = locateHostElement(rendererFactory, rootSelectorOrNode);
|
||||
const rendererFactory =
|
||||
ngModule ? ngModule.injector.get(RendererFactory2) : domRendererFactory3;
|
||||
const hostNode = isInternalRootView ?
|
||||
elementCreate(
|
||||
this.selector, rendererFactory.createRenderer(null, this.componentDef.rendererType)) :
|
||||
locateHostElement(rendererFactory, rootSelectorOrNode);
|
||||
|
||||
// The first index of the first selector is the tag name.
|
||||
const componentTag = this.componentDef.selectors ![0] ![0] as string;
|
||||
|
||||
const rootContext: RootContext = ngModule !.injector.get(ROOT_CONTEXT);
|
||||
const rootContext: RootContext = ngModule && !isInternalRootView ?
|
||||
ngModule.injector.get(ROOT_CONTEXT) :
|
||||
createRootContext(requestAnimationFrame.bind(window));
|
||||
|
||||
// Create the root view. Uses empty TView and ContentTemplate.
|
||||
const rootView: LViewData = createLViewData(
|
||||
rendererFactory.createRenderer(hostNode, this.componentDef.rendererType),
|
||||
createTView(-1, null, null, null, null), null,
|
||||
createTView(-1, null, null, null, null), rootContext,
|
||||
this.componentDef.onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways);
|
||||
rootView[INJECTOR] = ngModule && ngModule.injector || null;
|
||||
|
||||
|
@ -116,14 +121,49 @@ export class ComponentFactory<T> extends viewEngine_ComponentFactory<T> {
|
|||
component = baseDirectiveCreate(0, this.componentDef.factory(), this.componentDef) as T);
|
||||
initChangeDetectorIfExisting(elementNode.nodeInjector, component, elementNode.data !);
|
||||
|
||||
// TODO: should LifecycleHooksFeature and other host features be generated by the compiler and
|
||||
// executed here?
|
||||
// Angular 5 reference: https://stackblitz.com/edit/lifecycle-hooks-vcref
|
||||
LifecycleHooksFeature(component, this.componentDef);
|
||||
|
||||
// Transform the arrays of native nodes into a LNode structure that can be consumed by the
|
||||
// projection instruction. This is needed to support the reprojection of these nodes.
|
||||
if (projectableNodes) {
|
||||
let index = 0;
|
||||
const projection: TNode[] = elementNode.tNode.projection = [];
|
||||
for (let i = 0; i < projectableNodes.length; i++) {
|
||||
const nodeList = projectableNodes[i];
|
||||
let firstTNode: TNode|null = null;
|
||||
let previousTNode: TNode|null = null;
|
||||
for (let j = 0; j < nodeList.length; j++) {
|
||||
const lNode =
|
||||
createLNode(++index, TNodeType.Element, nodeList[j] as RElement, null, null);
|
||||
if (previousTNode) {
|
||||
previousTNode.next = lNode.tNode;
|
||||
} else {
|
||||
firstTNode = lNode.tNode;
|
||||
}
|
||||
previousTNode = lNode.tNode;
|
||||
}
|
||||
projection.push(firstTNode !);
|
||||
}
|
||||
}
|
||||
|
||||
// Execute the template in creation mode only, and then turn off the CreationMode flag
|
||||
renderEmbeddedTemplate(elementNode, elementNode.data ![TVIEW], component, RenderFlags.Create);
|
||||
elementNode.data ![FLAGS] &= ~LViewFlags.CreationMode;
|
||||
} finally {
|
||||
enterView(oldView, null);
|
||||
if (rendererFactory.end) rendererFactory.end();
|
||||
}
|
||||
|
||||
// TODO(misko): this is the wrong injector here.
|
||||
return new ComponentRef(
|
||||
this.componentType, component, rootView, ngModule !.injector, hostNode !);
|
||||
const componentRef =
|
||||
new ComponentRef(this.componentType, component, rootView, injector, hostNode !);
|
||||
if (isInternalRootView) {
|
||||
// The host element of the internal root view is attached to the component's host view node
|
||||
componentRef.hostView._lViewNode !.tNode.child = elementNode.tNode;
|
||||
}
|
||||
return componentRef;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -159,6 +199,7 @@ export class ComponentRef<T> extends viewEngine_ComponentRef<T> {
|
|||
* We might want to think about creating a fake component for the top level? Or overwrite
|
||||
* detectChanges with a function that calls tickRootContext? */
|
||||
this.hostView = this.changeDetectorRef = new ViewRef(rootView, instance);
|
||||
this.hostView._lViewNode = createLNode(-1, TNodeType.View, null, null, null, rootView);
|
||||
this.injector = injector;
|
||||
this.location = new ElementRef(hostNode);
|
||||
this.componentType = componentType;
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
import {ChangeDetectorRef as viewEngine_ChangeDetectorRef} from '../change_detection/change_detector_ref';
|
||||
import {InjectFlags, Injector, inject, setCurrentInjector} from '../di/injector';
|
||||
import {ComponentFactory as viewEngine_ComponentFactory, ComponentRef as viewEngine_ComponentRef} from '../linker/component_factory';
|
||||
import {ComponentFactoryResolver as viewEngine_ComponentFactoryResolver} from '../linker/component_factory_resolver';
|
||||
import {ElementRef as viewEngine_ElementRef} from '../linker/element_ref';
|
||||
import {NgModuleRef as viewEngine_NgModuleRef} from '../linker/ng_module_factory';
|
||||
import {TemplateRef as viewEngine_TemplateRef} from '../linker/template_ref';
|
||||
|
@ -19,9 +20,10 @@ import {EmbeddedViewRef as viewEngine_EmbeddedViewRef, ViewRef as viewEngine_Vie
|
|||
import {Type} from '../type';
|
||||
|
||||
import {assertDefined, assertGreaterThan, assertLessThan} from './assert';
|
||||
import {ComponentFactoryResolver} from './component_ref';
|
||||
import {addToViewTree, assertPreviousIsParent, createEmbeddedViewNode, createLContainer, createLNodeObject, createTNode, getPreviousOrParentNode, getRenderer, isComponent, renderEmbeddedTemplate, resolveDirective} from './instructions';
|
||||
import {VIEWS} from './interfaces/container';
|
||||
import {ComponentTemplate, DirectiveDefInternal, RenderFlags} from './interfaces/definition';
|
||||
import {DirectiveDefInternal, RenderFlags} from './interfaces/definition';
|
||||
import {LInjector} from './interfaces/injector';
|
||||
import {AttributeMarker, LContainerNode, LElementNode, LNode, LViewNode, TContainerNode, TElementNode, TNodeFlags, TNodeType} from './interfaces/node';
|
||||
import {LQueries, QueryReadType} from './interfaces/query';
|
||||
|
@ -29,8 +31,8 @@ import {Renderer3} from './interfaces/renderer';
|
|||
import {DIRECTIVES, HOST_NODE, INJECTOR, LViewData, QUERIES, RENDERER, TVIEW, TView} from './interfaces/view';
|
||||
import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert';
|
||||
import {addRemoveViewFromContainer, appendChild, detachView, getChildLNode, getParentLNode, insertView, removeView} from './node_manipulation';
|
||||
import {notImplemented, stringify} from './util';
|
||||
import {EmbeddedViewRef, ViewRef} from './view_ref';
|
||||
import {stringify} from './util';
|
||||
import {ViewRef} from './view_ref';
|
||||
|
||||
|
||||
|
||||
|
@ -130,7 +132,7 @@ export function getOrCreateNodeInjectorForNode(node: LElementNode | LContainerNo
|
|||
templateRef: null,
|
||||
viewContainerRef: null,
|
||||
elementRef: null,
|
||||
changeDetectorRef: null
|
||||
changeDetectorRef: null,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -221,6 +223,18 @@ export function injectChangeDetectorRef(): viewEngine_ChangeDetectorRef {
|
|||
return getOrCreateChangeDetectorRef(getOrCreateNodeInjector(), null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a ComponentFactoryResolver and stores it on the injector. Or, if the
|
||||
* ComponentFactoryResolver
|
||||
* already exists, retrieves the existing ComponentFactoryResolver.
|
||||
*
|
||||
* @returns The ComponentFactoryResolver instance to use
|
||||
*/
|
||||
export function injectComponentFactoryResolver(): viewEngine_ComponentFactoryResolver {
|
||||
return componentFactoryResolver;
|
||||
}
|
||||
const componentFactoryResolver: ComponentFactoryResolver = new ComponentFactoryResolver();
|
||||
|
||||
/**
|
||||
* Inject static attribute value into directive constructor.
|
||||
*
|
||||
|
@ -634,7 +648,7 @@ class ViewContainerRef implements viewEngine_ViewContainerRef {
|
|||
const adjustedIdx = this._adjustIndex(index);
|
||||
const viewRef = (templateRef as TemplateRef<C>)
|
||||
.createEmbeddedView(context || <any>{}, this._lContainerNode, adjustedIdx);
|
||||
(viewRef as EmbeddedViewRef<any>).attachToViewContainerRef(this);
|
||||
(viewRef as ViewRef<any>).attachToViewContainerRef(this);
|
||||
this._viewRefs.splice(adjustedIdx, 0, viewRef);
|
||||
return viewRef;
|
||||
}
|
||||
|
@ -642,15 +656,23 @@ class ViewContainerRef implements viewEngine_ViewContainerRef {
|
|||
createComponent<C>(
|
||||
componentFactory: viewEngine_ComponentFactory<C>, index?: number|undefined,
|
||||
injector?: Injector|undefined, projectableNodes?: any[][]|undefined,
|
||||
ngModule?: viewEngine_NgModuleRef<any>|undefined): viewEngine_ComponentRef<C> {
|
||||
throw notImplemented();
|
||||
ngModuleRef?: viewEngine_NgModuleRef<any>|undefined): viewEngine_ComponentRef<C> {
|
||||
const contextInjector = injector || this.parentInjector;
|
||||
if (!ngModuleRef && contextInjector) {
|
||||
ngModuleRef = contextInjector.get(viewEngine_NgModuleRef);
|
||||
}
|
||||
|
||||
const componentRef =
|
||||
componentFactory.create(contextInjector, projectableNodes, undefined, ngModuleRef);
|
||||
this.insert(componentRef.hostView, index);
|
||||
return componentRef;
|
||||
}
|
||||
|
||||
insert(viewRef: viewEngine_ViewRef, index?: number): viewEngine_ViewRef {
|
||||
if (viewRef.destroyed) {
|
||||
throw new Error('Cannot insert a destroyed View in a ViewContainer!');
|
||||
}
|
||||
const lViewNode = (viewRef as EmbeddedViewRef<any>)._lViewNode;
|
||||
const lViewNode = (viewRef as ViewRef<any>)._lViewNode !;
|
||||
const adjustedIdx = this._adjustIndex(index);
|
||||
|
||||
insertView(this._lContainerNode, lViewNode, adjustedIdx);
|
||||
|
@ -660,7 +682,7 @@ class ViewContainerRef implements viewEngine_ViewContainerRef {
|
|||
this._lContainerNode.native;
|
||||
addRemoveViewFromContainer(this._lContainerNode, lViewNode, true, beforeNode);
|
||||
|
||||
(viewRef as EmbeddedViewRef<any>).attachToViewContainerRef(this);
|
||||
(viewRef as ViewRef<any>).attachToViewContainerRef(this);
|
||||
this._viewRefs.splice(adjustedIdx, 0, viewRef);
|
||||
|
||||
return viewRef;
|
||||
|
@ -736,6 +758,8 @@ class TemplateRef<T> implements viewEngine_TemplateRef<T> {
|
|||
insertView(containerNode, viewNode, index !);
|
||||
}
|
||||
renderEmbeddedTemplate(viewNode, this._tView, context, RenderFlags.Create);
|
||||
return new EmbeddedViewRef(viewNode, this._tView.template !as ComponentTemplate<T>, context);
|
||||
const viewRef = new ViewRef(viewNode.data, context);
|
||||
viewRef._lViewNode = viewNode;
|
||||
return viewRef;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ import {I18nExpInstruction, I18nInstruction, i18nExpMapping, i18nInterpolation,
|
|||
import {ComponentDef, ComponentDefInternal, ComponentTemplate, ComponentType, DirectiveDef, DirectiveDefFlags, DirectiveDefInternal, DirectiveType, PipeDef} from './interfaces/definition';
|
||||
|
||||
export {ComponentFactory, ComponentFactoryResolver, ComponentRef} from './component_ref';
|
||||
export {QUERY_READ_CONTAINER_REF, QUERY_READ_ELEMENT_REF, QUERY_READ_FROM_NODE, QUERY_READ_TEMPLATE_REF, directiveInject, injectAttribute, injectChangeDetectorRef, injectElementRef, injectTemplateRef, injectViewContainerRef} from './di';
|
||||
export {QUERY_READ_CONTAINER_REF, QUERY_READ_ELEMENT_REF, QUERY_READ_FROM_NODE, QUERY_READ_TEMPLATE_REF, directiveInject, injectAttribute, injectChangeDetectorRef, injectComponentFactoryResolver, injectElementRef, injectTemplateRef, injectViewContainerRef} from './di';
|
||||
export {RenderFlags} from './interfaces/definition';
|
||||
export {CssSelectorList} from './interfaces/projection';
|
||||
|
||||
|
|
|
@ -529,29 +529,35 @@ export function createEmbeddedViewNode<T>(
|
|||
* TView for dynamically created views on their host TNode, which only has one instance.
|
||||
*/
|
||||
export function renderEmbeddedTemplate<T>(
|
||||
viewNode: LViewNode, tView: TView, context: T, rf: RenderFlags): LViewNode {
|
||||
viewNode: LViewNode | LElementNode, tView: TView, context: T, rf: RenderFlags): LViewNode|
|
||||
LElementNode {
|
||||
const _isParent = isParent;
|
||||
const _previousOrParentNode = previousOrParentNode;
|
||||
let oldView: LViewData;
|
||||
try {
|
||||
isParent = true;
|
||||
previousOrParentNode = null !;
|
||||
if (viewNode.data ![PARENT] == null && viewNode.data ![CONTEXT] && !tView.template) {
|
||||
// This is a root view inside the view tree
|
||||
tickRootContext(viewNode.data ![CONTEXT] as RootContext);
|
||||
} else {
|
||||
try {
|
||||
isParent = true;
|
||||
previousOrParentNode = null !;
|
||||
|
||||
oldView = enterView(viewNode.data, viewNode);
|
||||
namespaceHTML();
|
||||
tView.template !(rf, context);
|
||||
if (rf & RenderFlags.Update) {
|
||||
refreshView();
|
||||
} else {
|
||||
viewNode.data[TVIEW].firstTemplatePass = firstTemplatePass = false;
|
||||
oldView = enterView(viewNode.data !, viewNode);
|
||||
namespaceHTML();
|
||||
tView.template !(rf, context);
|
||||
if (rf & RenderFlags.Update) {
|
||||
refreshView();
|
||||
} else {
|
||||
viewNode.data ![TVIEW].firstTemplatePass = firstTemplatePass = false;
|
||||
}
|
||||
} finally {
|
||||
// renderEmbeddedTemplate() is called twice in fact, once for creation only and then once for
|
||||
// update. When for creation only, leaveView() must not trigger view hooks, nor clean flags.
|
||||
const isCreationOnly = (rf & RenderFlags.Create) === RenderFlags.Create;
|
||||
leaveView(oldView !, isCreationOnly);
|
||||
isParent = _isParent;
|
||||
previousOrParentNode = _previousOrParentNode;
|
||||
}
|
||||
} finally {
|
||||
// renderEmbeddedTemplate() is called twice in fact, once for creation only and then once for
|
||||
// update. When for creation only, leaveView() must not trigger view hooks, nor clean flags.
|
||||
const isCreationOnly = (rf & RenderFlags.Create) === RenderFlags.Create;
|
||||
leaveView(oldView !, isCreationOnly);
|
||||
isParent = _isParent;
|
||||
previousOrParentNode = _previousOrParentNode;
|
||||
}
|
||||
return viewNode;
|
||||
}
|
||||
|
@ -654,17 +660,7 @@ export function elementStart(
|
|||
|
||||
ngDevMode && ngDevMode.rendererCreateElement++;
|
||||
|
||||
let native: RElement;
|
||||
|
||||
if (isProceduralRenderer(renderer)) {
|
||||
native = renderer.createElement(name, _currentNamespace);
|
||||
} else {
|
||||
if (_currentNamespace === null) {
|
||||
native = renderer.createElement(name);
|
||||
} else {
|
||||
native = renderer.createElementNS(_currentNamespace, name);
|
||||
}
|
||||
}
|
||||
const native = elementCreate(name);
|
||||
|
||||
ngDevMode && assertDataInRange(index - 1);
|
||||
|
||||
|
@ -679,6 +675,27 @@ export function elementStart(
|
|||
createDirectivesAndLocals(localRefs);
|
||||
return native;
|
||||
}
|
||||
/**
|
||||
* Creates a native element from a tag name, using a renderer.
|
||||
* @param name the tag name
|
||||
* @param overriddenRenderer Optional A renderer to override the default one
|
||||
* @returns the element created
|
||||
*/
|
||||
export function elementCreate(name: string, overriddenRenderer?: Renderer3): RElement {
|
||||
let native: RElement;
|
||||
const rendererToUse = overriddenRenderer || renderer;
|
||||
|
||||
if (isProceduralRenderer(rendererToUse)) {
|
||||
native = rendererToUse.createElement(name, _currentNamespace);
|
||||
} else {
|
||||
if (_currentNamespace === null) {
|
||||
native = rendererToUse.createElement(name);
|
||||
} else {
|
||||
native = rendererToUse.createElementNS(_currentNamespace, name);
|
||||
}
|
||||
}
|
||||
return native;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates directive instances and populates local refs.
|
||||
|
|
|
@ -12,7 +12,6 @@ import {ViewContainerRef as viewEngine_ViewContainerRef} from '../linker/view_co
|
|||
import {EmbeddedViewRef as viewEngine_EmbeddedViewRef, InternalViewRef as viewEngine_InternalViewRef} from '../linker/view_ref';
|
||||
|
||||
import {checkNoChanges, detectChanges, markViewDirty, storeCleanupFn, viewAttached} from './instructions';
|
||||
import {ComponentTemplate} from './interfaces/definition';
|
||||
import {LViewNode} from './interfaces/node';
|
||||
import {FLAGS, LViewData, LViewFlags} from './interfaces/view';
|
||||
import {destroyLView} from './node_manipulation';
|
||||
|
@ -24,8 +23,13 @@ export interface viewEngine_ChangeDetectorRef_interface extends viewEngine_Chang
|
|||
|
||||
export class ViewRef<T> implements viewEngine_EmbeddedViewRef<T>, viewEngine_InternalViewRef,
|
||||
viewEngine_ChangeDetectorRef_interface {
|
||||
// TODO(issue/24571): remove '!'.
|
||||
private _appRef !: ApplicationRef | null;
|
||||
private _appRef: ApplicationRef|null = null;
|
||||
private _viewContainerRef: viewEngine_ViewContainerRef|null = null;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
_lViewNode: LViewNode|null = null;
|
||||
|
||||
context: T;
|
||||
// TODO(issue/24571): remove '!'.
|
||||
|
@ -43,7 +47,13 @@ export class ViewRef<T> implements viewEngine_EmbeddedViewRef<T>, viewEngine_Int
|
|||
return (this._view[FLAGS] & LViewFlags.Destroyed) === LViewFlags.Destroyed;
|
||||
}
|
||||
|
||||
destroy(): void { destroyLView(this._view); }
|
||||
destroy(): void {
|
||||
if (this._viewContainerRef && viewAttached(this._view)) {
|
||||
this._viewContainerRef.detach(this._viewContainerRef.indexOf(this));
|
||||
this._viewContainerRef = null;
|
||||
}
|
||||
destroyLView(this._view);
|
||||
}
|
||||
|
||||
onDestroy(callback: Function) { storeCleanupFn(this._view, callback); }
|
||||
|
||||
|
@ -227,31 +237,9 @@ export class ViewRef<T> implements viewEngine_EmbeddedViewRef<T>, viewEngine_Int
|
|||
*/
|
||||
checkNoChanges(): void { checkNoChanges(this.context); }
|
||||
|
||||
attachToViewContainerRef(vcRef: viewEngine_ViewContainerRef) { this._viewContainerRef = vcRef; }
|
||||
|
||||
detachFromAppRef() { this._appRef = null; }
|
||||
|
||||
attachToAppRef(appRef: ApplicationRef) { this._appRef = appRef; }
|
||||
}
|
||||
|
||||
|
||||
export class EmbeddedViewRef<T> extends ViewRef<T> {
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
_lViewNode: LViewNode;
|
||||
private _viewContainerRef: viewEngine_ViewContainerRef|null = null;
|
||||
|
||||
constructor(viewNode: LViewNode, template: ComponentTemplate<T>, context: T) {
|
||||
super(viewNode.data, context);
|
||||
this._lViewNode = viewNode;
|
||||
}
|
||||
|
||||
destroy(): void {
|
||||
if (this._viewContainerRef && viewAttached(this._view)) {
|
||||
this._viewContainerRef.detach(this._viewContainerRef.indexOf(this));
|
||||
this._viewContainerRef = null;
|
||||
}
|
||||
super.destroy();
|
||||
}
|
||||
|
||||
attachToViewContainerRef(vcRef: viewEngine_ViewContainerRef) { this._viewContainerRef = vcRef; }
|
||||
}
|
||||
|
|
|
@ -50,6 +50,9 @@
|
|||
{
|
||||
"name": "NG_PROJECT_AS_ATTR_NAME"
|
||||
},
|
||||
{
|
||||
"name": "PARENT"
|
||||
},
|
||||
{
|
||||
"name": "QUERIES"
|
||||
},
|
||||
|
@ -80,6 +83,9 @@
|
|||
{
|
||||
"name": "_ROOT_DIRECTIVE_INDICES"
|
||||
},
|
||||
{
|
||||
"name": "_getComponentHostLElementNode"
|
||||
},
|
||||
{
|
||||
"name": "_renderCompCount"
|
||||
},
|
||||
|
@ -170,6 +176,9 @@
|
|||
{
|
||||
"name": "getRenderFlags"
|
||||
},
|
||||
{
|
||||
"name": "getRootView"
|
||||
},
|
||||
{
|
||||
"name": "hostElement"
|
||||
},
|
||||
|
@ -209,6 +218,9 @@
|
|||
{
|
||||
"name": "renderComponent"
|
||||
},
|
||||
{
|
||||
"name": "renderComponentOrTemplate"
|
||||
},
|
||||
{
|
||||
"name": "renderEmbeddedTemplate"
|
||||
},
|
||||
|
@ -230,6 +242,9 @@
|
|||
{
|
||||
"name": "text"
|
||||
},
|
||||
{
|
||||
"name": "tickRootContext"
|
||||
},
|
||||
{
|
||||
"name": "updateViewQuery"
|
||||
},
|
||||
|
|
|
@ -42,10 +42,7 @@
|
|||
"name": "EMPTY_RENDERER_TYPE_ID"
|
||||
},
|
||||
{
|
||||
"name": "ElementRef"
|
||||
},
|
||||
{
|
||||
"name": "EmbeddedViewRef"
|
||||
"name": "ElementRef$1"
|
||||
},
|
||||
{
|
||||
"name": "FLAGS"
|
||||
|
@ -95,6 +92,9 @@
|
|||
{
|
||||
"name": "NgIfContext"
|
||||
},
|
||||
{
|
||||
"name": "NgModuleRef"
|
||||
},
|
||||
{
|
||||
"name": "Optional"
|
||||
},
|
||||
|
@ -155,6 +155,9 @@
|
|||
{
|
||||
"name": "ViewEncapsulation$1"
|
||||
},
|
||||
{
|
||||
"name": "ViewRef"
|
||||
},
|
||||
{
|
||||
"name": "_CLEAN_PROMISE"
|
||||
},
|
||||
|
@ -167,9 +170,6 @@
|
|||
{
|
||||
"name": "_ROOT_DIRECTIVE_INDICES"
|
||||
},
|
||||
{
|
||||
"name": "__extends"
|
||||
},
|
||||
{
|
||||
"name": "__read"
|
||||
},
|
||||
|
@ -401,6 +401,9 @@
|
|||
{
|
||||
"name": "elementClassProp"
|
||||
},
|
||||
{
|
||||
"name": "elementCreate"
|
||||
},
|
||||
{
|
||||
"name": "elementEnd"
|
||||
},
|
||||
|
@ -437,9 +440,6 @@
|
|||
{
|
||||
"name": "executePipeOnDestroys"
|
||||
},
|
||||
{
|
||||
"name": "extendStatics"
|
||||
},
|
||||
{
|
||||
"name": "extractDirectiveDef"
|
||||
},
|
||||
|
@ -668,9 +668,6 @@
|
|||
{
|
||||
"name": "namespaceHTML"
|
||||
},
|
||||
{
|
||||
"name": "notImplemented"
|
||||
},
|
||||
{
|
||||
"name": "pointers"
|
||||
},
|
||||
|
|
|
@ -6,11 +6,12 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Component, Directive, EmbeddedViewRef, Pipe, PipeTransform, TemplateRef, ViewContainerRef} from '../../src/core';
|
||||
import {Component, ComponentFactoryResolver, Directive, EmbeddedViewRef, NgModuleRef, Pipe, PipeTransform, RendererFactory2, TemplateRef, ViewContainerRef, createInjector, defineInjector, ɵAPP_ROOT as APP_ROOT, ɵNgModuleDef as NgModuleDef} from '../../src/core';
|
||||
import {getOrCreateNodeInjectorForNode, getOrCreateTemplateRef} from '../../src/render3/di';
|
||||
import {NgOnChangesFeature, defineComponent, defineDirective, definePipe, injectTemplateRef, injectViewContainerRef} from '../../src/render3/index';
|
||||
import {bind, container, containerRefreshEnd, containerRefreshStart, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, interpolation1, load, loadDirective, projection, projectionDef, reserveSlots, text, textBinding} from '../../src/render3/instructions';
|
||||
import {NgOnChangesFeature, defineComponent, defineDirective, definePipe, injectComponentFactoryResolver, injectTemplateRef, injectViewContainerRef} from '../../src/render3/index';
|
||||
import {bind, container, containerRefreshEnd, containerRefreshStart, element, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, interpolation1, load, loadDirective, projection, projectionDef, reserveSlots, text, textBinding} from '../../src/render3/instructions';
|
||||
import {RenderFlags} from '../../src/render3/interfaces/definition';
|
||||
import {NgModuleFactory} from '../../src/render3/ng_module_ref';
|
||||
import {pipe, pipeBind1} from '../../src/render3/pipe';
|
||||
|
||||
import {getRendererFactory2} from './imported_renderer2';
|
||||
|
@ -25,7 +26,8 @@ describe('ViewContainerRef', () => {
|
|||
static ngDirectiveDef = defineDirective({
|
||||
type: DirectiveWithVCRef,
|
||||
selectors: [['', 'vcref', '']],
|
||||
factory: () => directiveInstance = new DirectiveWithVCRef(injectViewContainerRef()),
|
||||
factory: () => directiveInstance = new DirectiveWithVCRef(
|
||||
injectViewContainerRef(), injectComponentFactoryResolver()),
|
||||
inputs: {tplRef: 'tplRef'}
|
||||
});
|
||||
|
||||
|
@ -34,7 +36,7 @@ describe('ViewContainerRef', () => {
|
|||
|
||||
// injecting a ViewContainerRef to create a dynamic container in which embedded views will be
|
||||
// created
|
||||
constructor(public vcref: ViewContainerRef) {}
|
||||
constructor(public vcref: ViewContainerRef, public cfr: ComponentFactoryResolver) {}
|
||||
}
|
||||
|
||||
describe('API', () => {
|
||||
|
@ -650,6 +652,177 @@ describe('ViewContainerRef', () => {
|
|||
expect(() => { directiveInstance !.vcref.move(viewRef !, 42); }).toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('createComponent', () => {
|
||||
let templateExecutionCounter = 0;
|
||||
|
||||
class EmbeddedComponent {
|
||||
static ngComponentDef = defineComponent({
|
||||
type: EmbeddedComponent,
|
||||
selectors: [['embedded-cmp']],
|
||||
factory: () => new EmbeddedComponent(),
|
||||
template: (rf: RenderFlags, cmp: EmbeddedComponent) => {
|
||||
templateExecutionCounter++;
|
||||
if (rf & RenderFlags.Create) {
|
||||
text(0, 'foo');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
it('should work without Injector and NgModuleRef', () => {
|
||||
templateExecutionCounter = 0;
|
||||
const fixture = new TemplateFixture(createTemplate, updateTemplate, [DirectiveWithVCRef]);
|
||||
expect(fixture.html).toEqual('<p vcref=""></p>');
|
||||
expect(templateExecutionCounter).toEqual(0);
|
||||
|
||||
const componentRef = directiveInstance !.vcref.createComponent(
|
||||
directiveInstance !.cfr.resolveComponentFactory(EmbeddedComponent));
|
||||
fixture.update();
|
||||
expect(fixture.html).toEqual('<p vcref=""></p><embedded-cmp>foo</embedded-cmp>');
|
||||
expect(templateExecutionCounter).toEqual(2);
|
||||
|
||||
directiveInstance !.vcref.detach(0);
|
||||
fixture.update();
|
||||
expect(fixture.html).toEqual('<p vcref=""></p>');
|
||||
expect(templateExecutionCounter).toEqual(2);
|
||||
|
||||
directiveInstance !.vcref.insert(componentRef.hostView);
|
||||
fixture.update();
|
||||
expect(fixture.html).toEqual('<p vcref=""></p><embedded-cmp>foo</embedded-cmp>');
|
||||
expect(templateExecutionCounter).toEqual(3);
|
||||
});
|
||||
|
||||
it('should work with NgModuleRef and Injector', () => {
|
||||
class MyAppModule {
|
||||
static ngInjectorDef = defineInjector({
|
||||
factory: () => new MyAppModule(),
|
||||
imports: [],
|
||||
providers: [
|
||||
{provide: APP_ROOT, useValue: true},
|
||||
{provide: RendererFactory2, useValue: getRendererFactory2(document)}
|
||||
]
|
||||
});
|
||||
static ngModuleDef: NgModuleDef<any, any, any, any> = { bootstrap: [] } as any;
|
||||
}
|
||||
const myAppModuleFactory = new NgModuleFactory(MyAppModule);
|
||||
const ngModuleRef = myAppModuleFactory.create(null);
|
||||
|
||||
class SomeModule {
|
||||
static ngInjectorDef = defineInjector({
|
||||
factory: () => new SomeModule(),
|
||||
providers: [{provide: NgModuleRef, useValue: ngModuleRef}]
|
||||
});
|
||||
}
|
||||
const injector = createInjector(SomeModule);
|
||||
|
||||
templateExecutionCounter = 0;
|
||||
const fixture = new TemplateFixture(createTemplate, updateTemplate, [DirectiveWithVCRef]);
|
||||
expect(fixture.html).toEqual('<p vcref=""></p>');
|
||||
expect(templateExecutionCounter).toEqual(0);
|
||||
|
||||
directiveInstance !.vcref.createComponent(
|
||||
directiveInstance !.cfr.resolveComponentFactory(EmbeddedComponent), 0, injector);
|
||||
fixture.update();
|
||||
expect(fixture.html).toEqual('<p vcref=""></p><embedded-cmp>foo</embedded-cmp>');
|
||||
expect(templateExecutionCounter).toEqual(2);
|
||||
|
||||
directiveInstance !.vcref.createComponent(
|
||||
directiveInstance !.cfr.resolveComponentFactory(EmbeddedComponent), 0, undefined,
|
||||
undefined, ngModuleRef);
|
||||
fixture.update();
|
||||
expect(fixture.html)
|
||||
.toEqual(
|
||||
'<p vcref=""></p><embedded-cmp>foo</embedded-cmp><embedded-cmp>foo</embedded-cmp>');
|
||||
expect(templateExecutionCounter).toEqual(5);
|
||||
});
|
||||
|
||||
class EmbeddedComponentWithNgContent {
|
||||
static ngComponentDef = defineComponent({
|
||||
type: EmbeddedComponentWithNgContent,
|
||||
selectors: [['embedded-cmp-with-ngcontent']],
|
||||
factory: () => new EmbeddedComponentWithNgContent(),
|
||||
template: (rf: RenderFlags, cmp: EmbeddedComponentWithNgContent) => {
|
||||
if (rf & RenderFlags.Create) {
|
||||
projectionDef();
|
||||
projection(0, 0);
|
||||
element(1, 'hr');
|
||||
projection(2, 1);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
it('should support projectable nodes', () => {
|
||||
const fixture = new TemplateFixture(createTemplate, updateTemplate, [DirectiveWithVCRef]);
|
||||
expect(fixture.html).toEqual('<p vcref=""></p>');
|
||||
|
||||
const myNode = document.createElement('div');
|
||||
const myText = document.createTextNode('bar');
|
||||
const myText2 = document.createTextNode('baz');
|
||||
myNode.appendChild(myText);
|
||||
myNode.appendChild(myText2);
|
||||
|
||||
directiveInstance !.vcref.createComponent(
|
||||
directiveInstance !.cfr.resolveComponentFactory(EmbeddedComponentWithNgContent), 0,
|
||||
undefined, [[myNode]]);
|
||||
fixture.update();
|
||||
expect(fixture.html)
|
||||
.toEqual(
|
||||
'<p vcref=""></p><embedded-cmp-with-ngcontent><div>barbaz</div><hr></embedded-cmp-with-ngcontent>');
|
||||
});
|
||||
|
||||
it('should support reprojection of projectable nodes', () => {
|
||||
class Reprojector {
|
||||
static ngComponentDef = defineComponent({
|
||||
type: Reprojector,
|
||||
selectors: [['reprojector']],
|
||||
factory: () => new Reprojector(),
|
||||
template: (rf: RenderFlags, cmp: Reprojector) => {
|
||||
if (rf & RenderFlags.Create) {
|
||||
projectionDef();
|
||||
elementStart(0, 'embedded-cmp-with-ngcontent');
|
||||
{ projection(1, 0); }
|
||||
elementEnd();
|
||||
}
|
||||
},
|
||||
directives: [EmbeddedComponentWithNgContent]
|
||||
});
|
||||
}
|
||||
|
||||
const fixture = new TemplateFixture(createTemplate, updateTemplate, [DirectiveWithVCRef]);
|
||||
expect(fixture.html).toEqual('<p vcref=""></p>');
|
||||
|
||||
const myNode = document.createElement('div');
|
||||
const myText = document.createTextNode('bar');
|
||||
const myText2 = document.createTextNode('baz');
|
||||
myNode.appendChild(myText);
|
||||
myNode.appendChild(myText2);
|
||||
|
||||
directiveInstance !.vcref.createComponent(
|
||||
directiveInstance !.cfr.resolveComponentFactory(Reprojector), 0, undefined, [[myNode]]);
|
||||
fixture.update();
|
||||
expect(fixture.html)
|
||||
.toEqual(
|
||||
'<p vcref=""></p><reprojector><embedded-cmp-with-ngcontent><div>barbaz</div><hr></embedded-cmp-with-ngcontent></reprojector>');
|
||||
});
|
||||
|
||||
it('should support many projectable nodes with many slots', () => {
|
||||
const fixture = new TemplateFixture(createTemplate, updateTemplate, [DirectiveWithVCRef]);
|
||||
expect(fixture.html).toEqual('<p vcref=""></p>');
|
||||
|
||||
directiveInstance !.vcref.createComponent(
|
||||
directiveInstance !.cfr.resolveComponentFactory(EmbeddedComponentWithNgContent), 0,
|
||||
undefined, [
|
||||
[document.createTextNode('1'), document.createTextNode('2')],
|
||||
[document.createTextNode('3'), document.createTextNode('4')]
|
||||
]);
|
||||
fixture.update();
|
||||
expect(fixture.html)
|
||||
.toEqual(
|
||||
'<p vcref=""></p><embedded-cmp-with-ngcontent>12<hr>34</embedded-cmp-with-ngcontent>');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('projection', () => {
|
||||
|
@ -944,43 +1117,44 @@ describe('ViewContainerRef', () => {
|
|||
|
||||
// Angular 5 reference: https://stackblitz.com/edit/lifecycle-hooks-vcref
|
||||
const log: string[] = [];
|
||||
it('should call all hooks in correct order', () => {
|
||||
@Component({selector: 'hooks', template: `{{name}}`})
|
||||
class ComponentWithHooks {
|
||||
// TODO(issue/24571): remove '!'.
|
||||
name !: string;
|
||||
|
||||
private log(msg: string) { log.push(msg); }
|
||||
@Component({selector: 'hooks', template: `{{name}}`})
|
||||
class ComponentWithHooks {
|
||||
// TODO(issue/24571): remove '!'.
|
||||
name !: string;
|
||||
|
||||
ngOnChanges() { this.log('onChanges-' + this.name); }
|
||||
ngOnInit() { this.log('onInit-' + this.name); }
|
||||
ngDoCheck() { this.log('doCheck-' + this.name); }
|
||||
private log(msg: string) { log.push(msg); }
|
||||
|
||||
ngAfterContentInit() { this.log('afterContentInit-' + this.name); }
|
||||
ngAfterContentChecked() { this.log('afterContentChecked-' + this.name); }
|
||||
ngOnChanges() { this.log('onChanges-' + this.name); }
|
||||
ngOnInit() { this.log('onInit-' + this.name); }
|
||||
ngDoCheck() { this.log('doCheck-' + this.name); }
|
||||
|
||||
ngAfterViewInit() { this.log('afterViewInit-' + this.name); }
|
||||
ngAfterViewChecked() { this.log('afterViewChecked-' + this.name); }
|
||||
ngAfterContentInit() { this.log('afterContentInit-' + this.name); }
|
||||
ngAfterContentChecked() { this.log('afterContentChecked-' + this.name); }
|
||||
|
||||
ngOnDestroy() { this.log('onDestroy-' + this.name); }
|
||||
ngAfterViewInit() { this.log('afterViewInit-' + this.name); }
|
||||
ngAfterViewChecked() { this.log('afterViewChecked-' + this.name); }
|
||||
|
||||
static ngComponentDef = defineComponent({
|
||||
type: ComponentWithHooks,
|
||||
selectors: [['hooks']],
|
||||
factory: () => new ComponentWithHooks(),
|
||||
template: (rf: RenderFlags, cmp: ComponentWithHooks) => {
|
||||
if (rf & RenderFlags.Create) {
|
||||
text(0);
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
textBinding(0, interpolation1('', cmp.name, ''));
|
||||
}
|
||||
},
|
||||
features: [NgOnChangesFeature],
|
||||
inputs: {name: 'name'}
|
||||
});
|
||||
}
|
||||
ngOnDestroy() { this.log('onDestroy-' + this.name); }
|
||||
|
||||
static ngComponentDef = defineComponent({
|
||||
type: ComponentWithHooks,
|
||||
selectors: [['hooks']],
|
||||
factory: () => new ComponentWithHooks(),
|
||||
template: (rf: RenderFlags, cmp: ComponentWithHooks) => {
|
||||
if (rf & RenderFlags.Create) {
|
||||
text(0);
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
textBinding(0, interpolation1('', cmp.name, ''));
|
||||
}
|
||||
},
|
||||
features: [NgOnChangesFeature],
|
||||
inputs: {name: 'name'}
|
||||
});
|
||||
}
|
||||
|
||||
it('should call all hooks in correct order when creating with createEmbeddedView', () => {
|
||||
@Component({
|
||||
template: `
|
||||
<ng-template #foo>
|
||||
|
@ -1022,6 +1196,8 @@ describe('ViewContainerRef', () => {
|
|||
});
|
||||
}
|
||||
|
||||
log.length = 0;
|
||||
|
||||
const fixture = new ComponentFixture(SomeComponent);
|
||||
expect(log).toEqual([
|
||||
'onChanges-A', 'onInit-A', 'doCheck-A', 'onChanges-B', 'onInit-B', 'doCheck-B',
|
||||
|
@ -1082,5 +1258,98 @@ describe('ViewContainerRef', () => {
|
|||
'afterViewChecked-A', 'afterViewChecked-B'
|
||||
]);
|
||||
});
|
||||
|
||||
it('should call all hooks in correct order when creating with createComponent', () => {
|
||||
@Component({
|
||||
template: `
|
||||
<hooks vcref [name]="'A'"></hooks>
|
||||
<hooks [name]="'B'"></hooks>
|
||||
`
|
||||
})
|
||||
class SomeComponent {
|
||||
static ngComponentDef = defineComponent({
|
||||
type: SomeComponent,
|
||||
selectors: [['some-comp']],
|
||||
factory: () => new SomeComponent(),
|
||||
template: (rf: RenderFlags, cmp: SomeComponent) => {
|
||||
if (rf & RenderFlags.Create) {
|
||||
elementStart(0, 'hooks', ['vcref', '']);
|
||||
elementEnd();
|
||||
elementStart(1, 'hooks');
|
||||
elementEnd();
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
elementProperty(0, 'name', bind('A'));
|
||||
elementProperty(1, 'name', bind('B'));
|
||||
}
|
||||
},
|
||||
directives: [ComponentWithHooks, DirectiveWithVCRef]
|
||||
});
|
||||
}
|
||||
|
||||
log.length = 0;
|
||||
|
||||
const fixture = new ComponentFixture(SomeComponent);
|
||||
expect(log).toEqual([
|
||||
'onChanges-A', 'onInit-A', 'doCheck-A', 'onChanges-B', 'onInit-B', 'doCheck-B',
|
||||
'afterContentInit-A', 'afterContentChecked-A', 'afterContentInit-B',
|
||||
'afterContentChecked-B', 'afterViewInit-A', 'afterViewChecked-A', 'afterViewInit-B',
|
||||
'afterViewChecked-B'
|
||||
]);
|
||||
|
||||
log.length = 0;
|
||||
fixture.update();
|
||||
expect(log).toEqual([
|
||||
'doCheck-A', 'doCheck-B', 'afterContentChecked-A', 'afterContentChecked-B',
|
||||
'afterViewChecked-A', 'afterViewChecked-B'
|
||||
]);
|
||||
|
||||
log.length = 0;
|
||||
const componentRef = directiveInstance !.vcref.createComponent(
|
||||
directiveInstance !.cfr.resolveComponentFactory(ComponentWithHooks));
|
||||
expect(fixture.html).toEqual('<hooks vcref="">A</hooks><hooks></hooks><hooks>B</hooks>');
|
||||
expect(log).toEqual([]);
|
||||
|
||||
componentRef.instance.name = 'D';
|
||||
log.length = 0;
|
||||
fixture.update();
|
||||
expect(fixture.html).toEqual('<hooks vcref="">A</hooks><hooks>D</hooks><hooks>B</hooks>');
|
||||
expect(log).toEqual([
|
||||
'doCheck-A', 'doCheck-B', 'onChanges-D', 'onInit-D', 'doCheck-D', 'afterContentInit-D',
|
||||
'afterContentChecked-D', 'afterViewInit-D', 'afterViewChecked-D', 'afterContentChecked-A',
|
||||
'afterContentChecked-B', 'afterViewChecked-A', 'afterViewChecked-B'
|
||||
]);
|
||||
|
||||
log.length = 0;
|
||||
fixture.update();
|
||||
expect(log).toEqual([
|
||||
'doCheck-A', 'doCheck-B', 'doCheck-D', 'afterContentChecked-D', 'afterViewChecked-D',
|
||||
'afterContentChecked-A', 'afterContentChecked-B', 'afterViewChecked-A', 'afterViewChecked-B'
|
||||
]);
|
||||
|
||||
log.length = 0;
|
||||
const viewRef = directiveInstance !.vcref.detach(0);
|
||||
fixture.update();
|
||||
expect(log).toEqual([
|
||||
'doCheck-A', 'doCheck-B', 'afterContentChecked-A', 'afterContentChecked-B',
|
||||
'afterViewChecked-A', 'afterViewChecked-B'
|
||||
]);
|
||||
|
||||
log.length = 0;
|
||||
directiveInstance !.vcref.insert(viewRef !);
|
||||
fixture.update();
|
||||
expect(log).toEqual([
|
||||
'doCheck-A', 'doCheck-B', 'doCheck-D', 'afterContentChecked-D', 'afterViewChecked-D',
|
||||
'afterContentChecked-A', 'afterContentChecked-B', 'afterViewChecked-A', 'afterViewChecked-B'
|
||||
]);
|
||||
|
||||
log.length = 0;
|
||||
directiveInstance !.vcref.remove(0);
|
||||
fixture.update();
|
||||
expect(log).toEqual([
|
||||
'onDestroy-D', 'doCheck-A', 'doCheck-B', 'afterContentChecked-A', 'afterContentChecked-B',
|
||||
'afterViewChecked-A', 'afterViewChecked-B'
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue