Skip to content

Commit 445b9a5

Browse files
marclavalvicb
authored andcommitted
feat(ivy): support ViewContainerRef.createComponent() (angular#24997)
PR Close angular#24997
1 parent d523630 commit 445b9a5

File tree

9 files changed

+484
-133
lines changed

9 files changed

+484
-133
lines changed

packages/core/src/render3/STATUS.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,7 @@ The goal is for the `@Component` (and friends) to be the compiler of template. S
244244
| `clear()` || n/a | n/a | n/a | n/a | n/a |
245245
| `get()` || n/a | n/a | n/a | n/a | n/a |
246246
| `createEmbededView()` ||| n/a | n/a | n/a | n/a |
247-
| `createComponent()` | | n/a | n/a | n/a | n/a | n/a |
247+
| `createComponent()` | | n/a | n/a | n/a | n/a | n/a |
248248
| `insert()` || n/a | n/a | n/a | n/a | n/a |
249249
| `move()` || n/a | n/a | n/a | n/a | n/a |
250250
| `indexOf()` || n/a | n/a | n/a | n/a | n/a |

packages/core/src/render3/component_ref.ts

+57-16
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,12 @@ import {RendererFactory2} from '../render/api';
1717
import {Type} from '../type';
1818

1919
import {assertComponentType, assertDefined} from './assert';
20-
import {createRootContext} from './component';
21-
import {baseDirectiveCreate, createLViewData, createTView, enterView, hostElement, initChangeDetectorIfExisting, locateHostElement} from './instructions';
22-
import {ComponentDefInternal, ComponentType} from './interfaces/definition';
23-
import {LElementNode} from './interfaces/node';
24-
import {RElement} from './interfaces/renderer';
25-
import {INJECTOR, LViewData, LViewFlags, RootContext} from './interfaces/view';
20+
import {LifecycleHooksFeature, createRootContext} from './component';
21+
import {baseDirectiveCreate, createLNode, createLViewData, createTView, elementCreate, enterView, hostElement, initChangeDetectorIfExisting, locateHostElement, renderEmbeddedTemplate} from './instructions';
22+
import {ComponentDefInternal, ComponentType, RenderFlags} from './interfaces/definition';
23+
import {LElementNode, TNode, TNodeType} from './interfaces/node';
24+
import {RElement, domRendererFactory3} from './interfaces/renderer';
25+
import {FLAGS, INJECTOR, LViewData, LViewFlags, RootContext, TVIEW} from './interfaces/view';
2626
import {ViewRef} from './view_ref';
2727

2828
export class ComponentFactoryResolver extends viewEngine_ComponentFactoryResolver {
@@ -80,23 +80,28 @@ export class ComponentFactory<T> extends viewEngine_ComponentFactory<T> {
8080
}
8181

8282
create(
83-
parentComponentInjector: Injector, projectableNodes?: any[][]|undefined,
84-
rootSelectorOrNode?: any,
83+
injector: Injector, projectableNodes?: any[][]|undefined, rootSelectorOrNode?: any,
8584
ngModule?: viewEngine_NgModuleRef<any>|undefined): viewEngine_ComponentRef<T> {
86-
ngDevMode && assertDefined(ngModule, 'ngModule should always be defined');
85+
const isInternalRootView = rootSelectorOrNode === undefined;
8786

88-
const rendererFactory = ngModule ? ngModule.injector.get(RendererFactory2) : document;
89-
const hostNode = locateHostElement(rendererFactory, rootSelectorOrNode);
87+
const rendererFactory =
88+
ngModule ? ngModule.injector.get(RendererFactory2) : domRendererFactory3;
89+
const hostNode = isInternalRootView ?
90+
elementCreate(
91+
this.selector, rendererFactory.createRenderer(null, this.componentDef.rendererType)) :
92+
locateHostElement(rendererFactory, rootSelectorOrNode);
9093

9194
// The first index of the first selector is the tag name.
9295
const componentTag = this.componentDef.selectors ![0] ![0] as string;
9396

94-
const rootContext: RootContext = ngModule !.injector.get(ROOT_CONTEXT);
97+
const rootContext: RootContext = ngModule && !isInternalRootView ?
98+
ngModule.injector.get(ROOT_CONTEXT) :
99+
createRootContext(requestAnimationFrame.bind(window));
95100

96101
// Create the root view. Uses empty TView and ContentTemplate.
97102
const rootView: LViewData = createLViewData(
98103
rendererFactory.createRenderer(hostNode, this.componentDef.rendererType),
99-
createTView(-1, null, null, null, null), null,
104+
createTView(-1, null, null, null, null), rootContext,
100105
this.componentDef.onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways);
101106
rootView[INJECTOR] = ngModule && ngModule.injector || null;
102107

@@ -116,14 +121,49 @@ export class ComponentFactory<T> extends viewEngine_ComponentFactory<T> {
116121
component = baseDirectiveCreate(0, this.componentDef.factory(), this.componentDef) as T);
117122
initChangeDetectorIfExisting(elementNode.nodeInjector, component, elementNode.data !);
118123

124+
// TODO: should LifecycleHooksFeature and other host features be generated by the compiler and
125+
// executed here?
126+
// Angular 5 reference: https://stackblitz.com/edit/lifecycle-hooks-vcref
127+
LifecycleHooksFeature(component, this.componentDef);
128+
129+
// Transform the arrays of native nodes into a LNode structure that can be consumed by the
130+
// projection instruction. This is needed to support the reprojection of these nodes.
131+
if (projectableNodes) {
132+
let index = 0;
133+
const projection: TNode[] = elementNode.tNode.projection = [];
134+
for (let i = 0; i < projectableNodes.length; i++) {
135+
const nodeList = projectableNodes[i];
136+
let firstTNode: TNode|null = null;
137+
let previousTNode: TNode|null = null;
138+
for (let j = 0; j < nodeList.length; j++) {
139+
const lNode =
140+
createLNode(++index, TNodeType.Element, nodeList[j] as RElement, null, null);
141+
if (previousTNode) {
142+
previousTNode.next = lNode.tNode;
143+
} else {
144+
firstTNode = lNode.tNode;
145+
}
146+
previousTNode = lNode.tNode;
147+
}
148+
projection.push(firstTNode !);
149+
}
150+
}
151+
152+
// Execute the template in creation mode only, and then turn off the CreationMode flag
153+
renderEmbeddedTemplate(elementNode, elementNode.data ![TVIEW], component, RenderFlags.Create);
154+
elementNode.data ![FLAGS] &= ~LViewFlags.CreationMode;
119155
} finally {
120156
enterView(oldView, null);
121157
if (rendererFactory.end) rendererFactory.end();
122158
}
123159

124-
// TODO(misko): this is the wrong injector here.
125-
return new ComponentRef(
126-
this.componentType, component, rootView, ngModule !.injector, hostNode !);
160+
const componentRef =
161+
new ComponentRef(this.componentType, component, rootView, injector, hostNode !);
162+
if (isInternalRootView) {
163+
// The host element of the internal root view is attached to the component's host view node
164+
componentRef.hostView._lViewNode !.tNode.child = elementNode.tNode;
165+
}
166+
return componentRef;
127167
}
128168
}
129169

@@ -159,6 +199,7 @@ export class ComponentRef<T> extends viewEngine_ComponentRef<T> {
159199
* We might want to think about creating a fake component for the top level? Or overwrite
160200
* detectChanges with a function that calls tickRootContext? */
161201
this.hostView = this.changeDetectorRef = new ViewRef(rootView, instance);
202+
this.hostView._lViewNode = createLNode(-1, TNodeType.View, null, null, null, rootView);
162203
this.injector = injector;
163204
this.location = new ElementRef(hostNode);
164205
this.componentType = componentType;

packages/core/src/render3/di.ts

+34-10
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import {ChangeDetectorRef as viewEngine_ChangeDetectorRef} from '../change_detection/change_detector_ref';
1212
import {InjectFlags, Injector, inject, setCurrentInjector} from '../di/injector';
1313
import {ComponentFactory as viewEngine_ComponentFactory, ComponentRef as viewEngine_ComponentRef} from '../linker/component_factory';
14+
import {ComponentFactoryResolver as viewEngine_ComponentFactoryResolver} from '../linker/component_factory_resolver';
1415
import {ElementRef as viewEngine_ElementRef} from '../linker/element_ref';
1516
import {NgModuleRef as viewEngine_NgModuleRef} from '../linker/ng_module_factory';
1617
import {TemplateRef as viewEngine_TemplateRef} from '../linker/template_ref';
@@ -19,18 +20,19 @@ import {EmbeddedViewRef as viewEngine_EmbeddedViewRef, ViewRef as viewEngine_Vie
1920
import {Type} from '../type';
2021

2122
import {assertDefined, assertGreaterThan, assertLessThan} from './assert';
23+
import {ComponentFactoryResolver} from './component_ref';
2224
import {addToViewTree, assertPreviousIsParent, createEmbeddedViewNode, createLContainer, createLNodeObject, createTNode, getPreviousOrParentNode, getRenderer, isComponent, renderEmbeddedTemplate, resolveDirective} from './instructions';
2325
import {VIEWS} from './interfaces/container';
24-
import {ComponentTemplate, DirectiveDefInternal, RenderFlags} from './interfaces/definition';
26+
import {DirectiveDefInternal, RenderFlags} from './interfaces/definition';
2527
import {LInjector} from './interfaces/injector';
2628
import {AttributeMarker, LContainerNode, LElementNode, LNode, LViewNode, TContainerNode, TElementNode, TNodeFlags, TNodeType} from './interfaces/node';
2729
import {LQueries, QueryReadType} from './interfaces/query';
2830
import {Renderer3} from './interfaces/renderer';
2931
import {DIRECTIVES, HOST_NODE, INJECTOR, LViewData, QUERIES, RENDERER, TVIEW, TView} from './interfaces/view';
3032
import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert';
3133
import {addRemoveViewFromContainer, appendChild, detachView, getChildLNode, getParentLNode, insertView, removeView} from './node_manipulation';
32-
import {notImplemented, stringify} from './util';
33-
import {EmbeddedViewRef, ViewRef} from './view_ref';
34+
import {stringify} from './util';
35+
import {ViewRef} from './view_ref';
3436

3537

3638

@@ -130,7 +132,7 @@ export function getOrCreateNodeInjectorForNode(node: LElementNode | LContainerNo
130132
templateRef: null,
131133
viewContainerRef: null,
132134
elementRef: null,
133-
changeDetectorRef: null
135+
changeDetectorRef: null,
134136
};
135137
}
136138

@@ -221,6 +223,18 @@ export function injectChangeDetectorRef(): viewEngine_ChangeDetectorRef {
221223
return getOrCreateChangeDetectorRef(getOrCreateNodeInjector(), null);
222224
}
223225

226+
/**
227+
* Creates a ComponentFactoryResolver and stores it on the injector. Or, if the
228+
* ComponentFactoryResolver
229+
* already exists, retrieves the existing ComponentFactoryResolver.
230+
*
231+
* @returns The ComponentFactoryResolver instance to use
232+
*/
233+
export function injectComponentFactoryResolver(): viewEngine_ComponentFactoryResolver {
234+
return componentFactoryResolver;
235+
}
236+
const componentFactoryResolver: ComponentFactoryResolver = new ComponentFactoryResolver();
237+
224238
/**
225239
* Inject static attribute value into directive constructor.
226240
*
@@ -634,23 +648,31 @@ class ViewContainerRef implements viewEngine_ViewContainerRef {
634648
const adjustedIdx = this._adjustIndex(index);
635649
const viewRef = (templateRef as TemplateRef<C>)
636650
.createEmbeddedView(context || <any>{}, this._lContainerNode, adjustedIdx);
637-
(viewRef as EmbeddedViewRef<any>).attachToViewContainerRef(this);
651+
(viewRef as ViewRef<any>).attachToViewContainerRef(this);
638652
this._viewRefs.splice(adjustedIdx, 0, viewRef);
639653
return viewRef;
640654
}
641655

642656
createComponent<C>(
643657
componentFactory: viewEngine_ComponentFactory<C>, index?: number|undefined,
644658
injector?: Injector|undefined, projectableNodes?: any[][]|undefined,
645-
ngModule?: viewEngine_NgModuleRef<any>|undefined): viewEngine_ComponentRef<C> {
646-
throw notImplemented();
659+
ngModuleRef?: viewEngine_NgModuleRef<any>|undefined): viewEngine_ComponentRef<C> {
660+
const contextInjector = injector || this.parentInjector;
661+
if (!ngModuleRef && contextInjector) {
662+
ngModuleRef = contextInjector.get(viewEngine_NgModuleRef);
663+
}
664+
665+
const componentRef =
666+
componentFactory.create(contextInjector, projectableNodes, undefined, ngModuleRef);
667+
this.insert(componentRef.hostView, index);
668+
return componentRef;
647669
}
648670

649671
insert(viewRef: viewEngine_ViewRef, index?: number): viewEngine_ViewRef {
650672
if (viewRef.destroyed) {
651673
throw new Error('Cannot insert a destroyed View in a ViewContainer!');
652674
}
653-
const lViewNode = (viewRef as EmbeddedViewRef<any>)._lViewNode;
675+
const lViewNode = (viewRef as ViewRef<any>)._lViewNode !;
654676
const adjustedIdx = this._adjustIndex(index);
655677

656678
insertView(this._lContainerNode, lViewNode, adjustedIdx);
@@ -660,7 +682,7 @@ class ViewContainerRef implements viewEngine_ViewContainerRef {
660682
this._lContainerNode.native;
661683
addRemoveViewFromContainer(this._lContainerNode, lViewNode, true, beforeNode);
662684

663-
(viewRef as EmbeddedViewRef<any>).attachToViewContainerRef(this);
685+
(viewRef as ViewRef<any>).attachToViewContainerRef(this);
664686
this._viewRefs.splice(adjustedIdx, 0, viewRef);
665687

666688
return viewRef;
@@ -736,6 +758,8 @@ class TemplateRef<T> implements viewEngine_TemplateRef<T> {
736758
insertView(containerNode, viewNode, index !);
737759
}
738760
renderEmbeddedTemplate(viewNode, this._tView, context, RenderFlags.Create);
739-
return new EmbeddedViewRef(viewNode, this._tView.template !as ComponentTemplate<T>, context);
761+
const viewRef = new ViewRef(viewNode.data, context);
762+
viewRef._lViewNode = viewNode;
763+
return viewRef;
740764
}
741765
}

packages/core/src/render3/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import {I18nExpInstruction, I18nInstruction, i18nExpMapping, i18nInterpolation,
1515
import {ComponentDef, ComponentDefInternal, ComponentTemplate, ComponentType, DirectiveDef, DirectiveDefFlags, DirectiveDefInternal, DirectiveType, PipeDef} from './interfaces/definition';
1616

1717
export {ComponentFactory, ComponentFactoryResolver, ComponentRef} from './component_ref';
18-
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';
18+
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';
1919
export {RenderFlags} from './interfaces/definition';
2020
export {CssSelectorList} from './interfaces/projection';
2121

packages/core/src/render3/instructions.ts

+46-29
Original file line numberDiff line numberDiff line change
@@ -529,29 +529,35 @@ export function createEmbeddedViewNode<T>(
529529
* TView for dynamically created views on their host TNode, which only has one instance.
530530
*/
531531
export function renderEmbeddedTemplate<T>(
532-
viewNode: LViewNode, tView: TView, context: T, rf: RenderFlags): LViewNode {
532+
viewNode: LViewNode | LElementNode, tView: TView, context: T, rf: RenderFlags): LViewNode|
533+
LElementNode {
533534
const _isParent = isParent;
534535
const _previousOrParentNode = previousOrParentNode;
535536
let oldView: LViewData;
536-
try {
537-
isParent = true;
538-
previousOrParentNode = null !;
537+
if (viewNode.data ![PARENT] == null && viewNode.data ![CONTEXT] && !tView.template) {
538+
// This is a root view inside the view tree
539+
tickRootContext(viewNode.data ![CONTEXT] as RootContext);
540+
} else {
541+
try {
542+
isParent = true;
543+
previousOrParentNode = null !;
539544

540-
oldView = enterView(viewNode.data, viewNode);
541-
namespaceHTML();
542-
tView.template !(rf, context);
543-
if (rf & RenderFlags.Update) {
544-
refreshView();
545-
} else {
546-
viewNode.data[TVIEW].firstTemplatePass = firstTemplatePass = false;
545+
oldView = enterView(viewNode.data !, viewNode);
546+
namespaceHTML();
547+
tView.template !(rf, context);
548+
if (rf & RenderFlags.Update) {
549+
refreshView();
550+
} else {
551+
viewNode.data ![TVIEW].firstTemplatePass = firstTemplatePass = false;
552+
}
553+
} finally {
554+
// renderEmbeddedTemplate() is called twice in fact, once for creation only and then once for
555+
// update. When for creation only, leaveView() must not trigger view hooks, nor clean flags.
556+
const isCreationOnly = (rf & RenderFlags.Create) === RenderFlags.Create;
557+
leaveView(oldView !, isCreationOnly);
558+
isParent = _isParent;
559+
previousOrParentNode = _previousOrParentNode;
547560
}
548-
} finally {
549-
// renderEmbeddedTemplate() is called twice in fact, once for creation only and then once for
550-
// update. When for creation only, leaveView() must not trigger view hooks, nor clean flags.
551-
const isCreationOnly = (rf & RenderFlags.Create) === RenderFlags.Create;
552-
leaveView(oldView !, isCreationOnly);
553-
isParent = _isParent;
554-
previousOrParentNode = _previousOrParentNode;
555561
}
556562
return viewNode;
557563
}
@@ -654,17 +660,7 @@ export function elementStart(
654660

655661
ngDevMode && ngDevMode.rendererCreateElement++;
656662

657-
let native: RElement;
658-
659-
if (isProceduralRenderer(renderer)) {
660-
native = renderer.createElement(name, _currentNamespace);
661-
} else {
662-
if (_currentNamespace === null) {
663-
native = renderer.createElement(name);
664-
} else {
665-
native = renderer.createElementNS(_currentNamespace, name);
666-
}
667-
}
663+
const native = elementCreate(name);
668664

669665
ngDevMode && assertDataInRange(index - 1);
670666

@@ -679,6 +675,27 @@ export function elementStart(
679675
createDirectivesAndLocals(localRefs);
680676
return native;
681677
}
678+
/**
679+
* Creates a native element from a tag name, using a renderer.
680+
* @param name the tag name
681+
* @param overriddenRenderer Optional A renderer to override the default one
682+
* @returns the element created
683+
*/
684+
export function elementCreate(name: string, overriddenRenderer?: Renderer3): RElement {
685+
let native: RElement;
686+
const rendererToUse = overriddenRenderer || renderer;
687+
688+
if (isProceduralRenderer(rendererToUse)) {
689+
native = rendererToUse.createElement(name, _currentNamespace);
690+
} else {
691+
if (_currentNamespace === null) {
692+
native = rendererToUse.createElement(name);
693+
} else {
694+
native = rendererToUse.createElementNS(_currentNamespace, name);
695+
}
696+
}
697+
return native;
698+
}
682699

683700
/**
684701
* Creates directive instances and populates local refs.

0 commit comments

Comments
 (0)