feat(ivy): support ViewContainerRef.createComponent() (#24997)

PR Close #24997
This commit is contained in:
Marc Laval
2018-07-20 14:32:23 +02:00
committed by Victor Berchet
parent d523630ea2
commit 445b9a5627
9 changed files with 484 additions and 133 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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