forked from angular/angular
/
component_ref.ts
441 lines (389 loc) · 18.2 KB
/
component_ref.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
/**
* @license
* Copyright Google LLC 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 {ChangeDetectorRef} from '../change_detection/change_detector_ref';
import {Injector} from '../di/injector';
import {convertToBitFlags} from '../di/injector_compatibility';
import {InjectFlags, InjectOptions} from '../di/interface/injector';
import {ProviderToken} from '../di/provider_token';
import {EnvironmentInjector} from '../di/r3_injector';
import {RuntimeError, RuntimeErrorCode} from '../errors';
import {Type} from '../interface/type';
import {ComponentFactory as AbstractComponentFactory, ComponentRef as AbstractComponentRef} from '../linker/component_factory';
import {ComponentFactoryResolver as AbstractComponentFactoryResolver} from '../linker/component_factory_resolver';
import {createElementRef, ElementRef} from '../linker/element_ref';
import {NgModuleRef} from '../linker/ng_module_factory';
import {RendererFactory2} from '../render/api';
import {Sanitizer} from '../sanitization/sanitizer';
import {assertDefined, assertIndexInRange} from '../util/assert';
import {VERSION} from '../version';
import {NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR} from '../view/provider_flags';
import {assertComponentType} from './assert';
import {getComponentDef} from './definition';
import {diPublicInInjector, getOrCreateNodeInjectorForNode, NodeInjector} from './di';
import {throwProviderNotFoundError} from './errors_di';
import {registerPostOrderHooks} from './hooks';
import {reportUnknownPropertyError} from './instructions/element_validation';
import {addToViewTree, createLView, createTView, getOrCreateComponentTView, getOrCreateTNode, initTNodeFlags, instantiateRootComponent, invokeHostBindingsInCreationMode, locateHostElement, markAsComponentHost, markDirtyIfOnPush, registerHostBindingOpCodes, renderView, setInputsForProperty} from './instructions/shared';
import {ComponentDef, RenderFlags} from './interfaces/definition';
import {PropertyAliasValue, TContainerNode, TElementContainerNode, TElementNode, TNode, TNodeType} from './interfaces/node';
import {Renderer, RendererFactory} from './interfaces/renderer';
import {RElement, RNode} from './interfaces/renderer_dom';
import {CONTEXT, HEADER_OFFSET, LView, LViewFlags, TVIEW, TViewType} from './interfaces/view';
import {MATH_ML_NAMESPACE, SVG_NAMESPACE} from './namespaces';
import {createElementNode, writeDirectClass, writeDirectStyle} from './node_manipulation';
import {extractAttrsAndClassesFromSelector, stringifyCSSSelectorList} from './node_selector_matcher';
import {enterView, getCurrentTNode, getLView, leaveView, setSelectedIndex} from './state';
import {computeStaticStyling} from './styling/static_styling';
import {setUpAttributes} from './util/attrs_utils';
import {stringifyForError} from './util/stringify_utils';
import {getTNode} from './util/view_utils';
import {RootViewRef, ViewRef} from './view_ref';
export class ComponentFactoryResolver extends AbstractComponentFactoryResolver {
/**
* @param ngModule The NgModuleRef to which all resolved factories are bound.
*/
constructor(private ngModule?: NgModuleRef<any>) {
super();
}
override resolveComponentFactory<T>(component: Type<T>): AbstractComponentFactory<T> {
ngDevMode && assertComponentType(component);
const componentDef = getComponentDef(component)!;
return new ComponentFactory(componentDef, this.ngModule);
}
}
function toRefArray(map: {[key: string]: string}): {propName: string; templateName: string;}[] {
const array: {propName: string; templateName: string;}[] = [];
for (let nonMinified in map) {
if (map.hasOwnProperty(nonMinified)) {
const minified = map[nonMinified];
array.push({propName: minified, templateName: nonMinified});
}
}
return array;
}
function getNamespace(elementName: string): string|null {
const name = elementName.toLowerCase();
return name === 'svg' ? SVG_NAMESPACE : (name === 'math' ? MATH_ML_NAMESPACE : null);
}
/**
* Injector that looks up a value using a specific injector, before falling back to the module
* injector. Used primarily when creating components or embedded views dynamically.
*/
class ChainedInjector implements Injector {
constructor(private injector: Injector, private parentInjector: Injector) {}
get<T>(token: ProviderToken<T>, notFoundValue?: T, flags?: InjectFlags|InjectOptions): T {
flags = convertToBitFlags(flags);
const value = this.injector.get<T|typeof NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR>(
token, NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR, flags);
if (value !== NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR ||
notFoundValue === (NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR as unknown as T)) {
// Return the value from the root element injector when
// - it provides it
// (value !== NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR)
// - the module injector should not be checked
// (notFoundValue === NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR)
return value as T;
}
return this.parentInjector.get(token, notFoundValue, flags);
}
}
/**
* ComponentFactory interface implementation.
*/
export class ComponentFactory<T> extends AbstractComponentFactory<T> {
override selector: string;
override componentType: Type<any>;
override ngContentSelectors: string[];
isBoundToModule: boolean;
override get inputs(): {propName: string; templateName: string;}[] {
return toRefArray(this.componentDef.inputs);
}
override get outputs(): {propName: string; templateName: string;}[] {
return toRefArray(this.componentDef.outputs);
}
/**
* @param componentDef The component definition.
* @param ngModule The NgModuleRef to which the factory is bound.
*/
constructor(private componentDef: ComponentDef<any>, private ngModule?: NgModuleRef<any>) {
super();
this.componentType = componentDef.type;
this.selector = stringifyCSSSelectorList(componentDef.selectors);
this.ngContentSelectors =
componentDef.ngContentSelectors ? componentDef.ngContentSelectors : [];
this.isBoundToModule = !!ngModule;
}
override create(
injector: Injector, projectableNodes?: any[][]|undefined, rootSelectorOrNode?: any,
environmentInjector?: NgModuleRef<any>|EnvironmentInjector|
undefined): AbstractComponentRef<T> {
environmentInjector = environmentInjector || this.ngModule;
let realEnvironmentInjector = environmentInjector instanceof EnvironmentInjector ?
environmentInjector :
environmentInjector?.injector;
if (realEnvironmentInjector && this.componentDef.getStandaloneInjector !== null) {
realEnvironmentInjector = this.componentDef.getStandaloneInjector(realEnvironmentInjector) ||
realEnvironmentInjector;
}
const rootViewInjector =
realEnvironmentInjector ? new ChainedInjector(injector, realEnvironmentInjector) : injector;
const rendererFactory = rootViewInjector.get(RendererFactory2, null);
if (rendererFactory === null) {
throw new RuntimeError(
RuntimeErrorCode.RENDERER_NOT_FOUND,
ngDevMode &&
'Angular was not able to inject a renderer (RendererFactory2). ' +
'Likely this is due to a broken DI hierarchy. ' +
'Make sure that any injector used to create this component has a correct parent.');
}
const sanitizer = rootViewInjector.get(Sanitizer, null);
const hostRenderer = rendererFactory.createRenderer(null, this.componentDef);
// Determine a tag name used for creating host elements when this component is created
// dynamically. Default to 'div' if this component did not specify any tag name in its selector.
const elementName = this.componentDef.selectors[0][0] as string || 'div';
const hostRNode = rootSelectorOrNode ?
locateHostElement(hostRenderer, rootSelectorOrNode, this.componentDef.encapsulation) :
createElementNode(
rendererFactory.createRenderer(null, this.componentDef), elementName,
getNamespace(elementName));
const rootFlags = this.componentDef.onPush ? LViewFlags.Dirty | LViewFlags.IsRoot :
LViewFlags.CheckAlways | LViewFlags.IsRoot;
// Create the root view. Uses empty TView and ContentTemplate.
const rootTView = createTView(TViewType.Root, null, null, 1, 0, null, null, null, null, null);
const rootLView = createLView(
null, rootTView, null, rootFlags, null, null, rendererFactory, hostRenderer, sanitizer,
rootViewInjector, null);
// rootView is the parent when bootstrapping
// TODO(misko): it looks like we are entering view here but we don't really need to as
// `renderView` does that. However as the code is written it is needed because
// `createRootComponentView` and `createRootComponent` both read global state. Fixing those
// issues would allow us to drop this.
enterView(rootLView);
let component: T;
let tElementNode: TElementNode;
try {
const componentView = createRootComponentView(
hostRNode, this.componentDef, rootLView, rendererFactory, hostRenderer);
if (hostRNode) {
if (rootSelectorOrNode) {
setUpAttributes(hostRenderer, hostRNode, ['ng-version', VERSION.full]);
} else {
// If host element is created as a part of this function call (i.e. `rootSelectorOrNode`
// is not defined), also apply attributes and classes extracted from component selector.
// Extract attributes and classes from the first selector only to match VE behavior.
const {attrs, classes} =
extractAttrsAndClassesFromSelector(this.componentDef.selectors[0]);
if (attrs) {
setUpAttributes(hostRenderer, hostRNode, attrs);
}
if (classes && classes.length > 0) {
writeDirectClass(hostRenderer, hostRNode, classes.join(' '));
}
}
}
tElementNode = getTNode(rootTView, HEADER_OFFSET) as TElementNode;
if (projectableNodes !== undefined) {
const projection: (TNode|RNode[]|null)[] = tElementNode.projection = [];
for (let i = 0; i < this.ngContentSelectors.length; i++) {
const nodesforSlot = projectableNodes[i];
// Projectable nodes can be passed as array of arrays or an array of iterables (ngUpgrade
// case). Here we do normalize passed data structure to be an array of arrays to avoid
// complex checks down the line.
// We also normalize the length of the passed in projectable nodes (to match the number of
// <ng-container> slots defined by a component).
projection.push(nodesforSlot != null ? Array.from(nodesforSlot) : null);
}
}
// 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
component =
createRootComponent(componentView, this.componentDef, rootLView, [LifecycleHooksFeature]);
renderView(rootTView, rootLView, null);
} finally {
leaveView();
}
return new ComponentRef(
this.componentType, component, createElementRef(tElementNode, rootLView), rootLView,
tElementNode);
}
}
const componentFactoryResolver: ComponentFactoryResolver = new ComponentFactoryResolver();
/**
* 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(): AbstractComponentFactoryResolver {
return componentFactoryResolver;
}
/**
* Represents an instance of a Component created via a {@link ComponentFactory}.
*
* `ComponentRef` provides access to the Component Instance as well other objects related to this
* Component Instance and allows you to destroy the Component Instance via the {@link #destroy}
* method.
*
*/
export class ComponentRef<T> extends AbstractComponentRef<T> {
override instance: T;
override hostView: ViewRef<T>;
override changeDetectorRef: ChangeDetectorRef;
override componentType: Type<T>;
constructor(
componentType: Type<T>, instance: T, public location: ElementRef, private _rootLView: LView,
private _tNode: TElementNode|TContainerNode|TElementContainerNode) {
super();
this.instance = instance;
this.hostView = this.changeDetectorRef = new RootViewRef<T>(_rootLView);
this.componentType = componentType;
}
override setInput(name: string, value: unknown): void {
const inputData = this._tNode.inputs;
let dataValue: PropertyAliasValue|undefined;
if (inputData !== null && (dataValue = inputData[name])) {
const lView = this._rootLView;
setInputsForProperty(lView[TVIEW], lView, dataValue, name, value);
markDirtyIfOnPush(lView, this._tNode.index);
} else {
if (ngDevMode) {
const cmpNameForError = stringifyForError(this.componentType);
let message =
`Can't set value of the '${name}' input on the '${cmpNameForError}' component. `;
message += `Make sure that the '${
name}' property is annotated with @Input() or a mapped @Input('${name}') exists.`;
reportUnknownPropertyError(message);
}
}
}
override get injector(): Injector {
return new NodeInjector(this._tNode, this._rootLView);
}
override destroy(): void {
this.hostView.destroy();
}
override onDestroy(callback: () => void): void {
this.hostView.onDestroy(callback);
}
}
/** Represents a HostFeature function. */
type HostFeature = (<T>(component: T, componentDef: ComponentDef<T>) => void);
// TODO: A hack to not pull in the NullInjector from @angular/core.
export const NULL_INJECTOR: Injector = {
get: (token: any, notFoundValue?: any) => {
throwProviderNotFoundError(token, 'NullInjector');
}
};
/**
* Creates the root component view and the root component node.
*
* @param rNode Render host element.
* @param def ComponentDef
* @param rootView The parent view where the host node is stored
* @param rendererFactory Factory to be used for creating child renderers.
* @param hostRenderer The current renderer
* @param sanitizer The sanitizer, if provided
*
* @returns Component view created
*/
export function createRootComponentView(
rNode: RElement|null, def: ComponentDef<any>, rootView: LView, rendererFactory: RendererFactory,
hostRenderer: Renderer, sanitizer?: Sanitizer|null): LView {
const tView = rootView[TVIEW];
const index = HEADER_OFFSET;
ngDevMode && assertIndexInRange(rootView, index);
rootView[index] = rNode;
// '#host' is added here as we don't know the real host DOM name (we don't want to read it) and at
// the same time we want to communicate the debug `TNode` that this is a special `TNode`
// representing a host element.
const tNode: TElementNode = getOrCreateTNode(tView, index, TNodeType.Element, '#host', null);
const mergedAttrs = tNode.mergedAttrs = def.hostAttrs;
if (mergedAttrs !== null) {
computeStaticStyling(tNode, mergedAttrs, true);
if (rNode !== null) {
setUpAttributes(hostRenderer, rNode, mergedAttrs);
if (tNode.classes !== null) {
writeDirectClass(hostRenderer, rNode, tNode.classes);
}
if (tNode.styles !== null) {
writeDirectStyle(hostRenderer, rNode, tNode.styles);
}
}
}
const viewRenderer = rendererFactory.createRenderer(rNode, def);
const componentView = createLView(
rootView, getOrCreateComponentTView(def), null,
def.onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways, rootView[index], tNode,
rendererFactory, viewRenderer, sanitizer || null, null, null);
if (tView.firstCreatePass) {
diPublicInInjector(getOrCreateNodeInjectorForNode(tNode, rootView), tView, def.type);
markAsComponentHost(tView, tNode, 0);
initTNodeFlags(tNode, rootView.length, 1);
}
addToViewTree(rootView, componentView);
// Store component view at node index, with node as the HOST
return rootView[index] = componentView;
}
/**
* Creates a root component and sets it up with features and host bindings.Shared by
* renderComponent() and ViewContainerRef.createComponent().
*/
export function createRootComponent<T>(
componentView: LView, componentDef: ComponentDef<T>, rootLView: LView,
hostFeatures: HostFeature[]|null): any {
const tView = rootLView[TVIEW];
// Create directive instance with factory() and store at next index in viewData
const component = instantiateRootComponent(tView, rootLView, componentDef);
// Root view only contains an instance of this component,
// so we use a reference to that component instance as a context.
componentView[CONTEXT] = rootLView[CONTEXT] = component;
if (hostFeatures !== null) {
for (const feature of hostFeatures) {
feature(component, componentDef);
}
}
// We want to generate an empty QueryList for root content queries for backwards
// compatibility with ViewEngine.
if (componentDef.contentQueries) {
const tNode = getCurrentTNode()!;
ngDevMode && assertDefined(tNode, 'TNode expected');
componentDef.contentQueries(RenderFlags.Create, component, tNode.directiveStart);
}
const rootTNode = getCurrentTNode()!;
ngDevMode && assertDefined(rootTNode, 'tNode should have been already created');
if (tView.firstCreatePass &&
(componentDef.hostBindings !== null || componentDef.hostAttrs !== null)) {
setSelectedIndex(rootTNode.index);
const rootTView = rootLView[TVIEW];
registerHostBindingOpCodes(
rootTView, rootTNode, rootLView, rootTNode.directiveStart, rootTNode.directiveEnd,
componentDef);
invokeHostBindingsInCreationMode(componentDef, component);
}
return component;
}
/**
* Used to enable lifecycle hooks on the root component.
*
* Include this feature when calling `renderComponent` if the root component
* you are rendering has lifecycle hooks defined. Otherwise, the hooks won't
* be called properly.
*
* Example:
*
* ```
* renderComponent(AppComponent, {hostFeatures: [LifecycleHooksFeature]});
* ```
*/
export function LifecycleHooksFeature(): void {
const tNode = getCurrentTNode()!;
ngDevMode && assertDefined(tNode, 'TNode is required');
registerPostOrderHooks(getLView()[TVIEW], tNode);
}