2018-05-09 16:49:39 -07:00
|
|
|
/**
|
|
|
|
* @license
|
|
|
|
* Copyright Google Inc. All Rights Reserved.
|
|
|
|
*
|
|
|
|
* Use of this source code is governed by an MIT-style license that can be
|
|
|
|
* found in the LICENSE file at https://angular.io/license
|
|
|
|
*/
|
|
|
|
|
2018-08-06 14:09:38 -07:00
|
|
|
import {ChangeDetectorRef as ViewEngine_ChangeDetectorRef} from '../change_detection/change_detector_ref';
|
2018-05-09 16:49:39 -07:00
|
|
|
import {InjectionToken} from '../di/injection_token';
|
2018-10-23 14:28:15 -07:00
|
|
|
import {Injector} from '../di/injector';
|
2019-02-12 09:46:39 -08:00
|
|
|
import {InjectFlags} from '../di/interface/injector';
|
2019-01-09 13:49:16 -08:00
|
|
|
import {Type} from '../interface/type';
|
2018-05-09 16:49:39 -07:00
|
|
|
import {ComponentFactory as viewEngine_ComponentFactory, ComponentRef as viewEngine_ComponentRef} from '../linker/component_factory';
|
|
|
|
import {ComponentFactoryResolver as viewEngine_ComponentFactoryResolver} from '../linker/component_factory_resolver';
|
2018-08-06 14:09:38 -07:00
|
|
|
import {ElementRef as viewEngine_ElementRef} from '../linker/element_ref';
|
2018-05-09 16:49:39 -07:00
|
|
|
import {NgModuleRef as viewEngine_NgModuleRef} from '../linker/ng_module_factory';
|
|
|
|
import {RendererFactory2} from '../render/api';
|
2019-07-31 13:15:50 -07:00
|
|
|
import {Sanitizer} from '../sanitization/sanitizer';
|
2018-11-19 21:06:41 +01:00
|
|
|
import {VERSION} from '../version';
|
2019-01-23 13:26:48 +01:00
|
|
|
import {NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR} from '../view/provider';
|
2019-01-09 13:49:16 -08:00
|
|
|
import {assertComponentType} from './assert';
|
2018-10-12 15:02:54 -07:00
|
|
|
import {LifecycleHooksFeature, createRootComponent, createRootComponentView, createRootContext} from './component';
|
2018-08-29 16:34:44 -07:00
|
|
|
import {getComponentDef} from './definition';
|
2018-11-14 19:25:03 -08:00
|
|
|
import {NodeInjector} from './di';
|
2019-08-02 16:43:10 +02:00
|
|
|
import {assignTViewNodeToLView, createLView, createTView, elementCreate, locateHostElement, renderView} from './instructions/shared';
|
2019-01-21 14:55:37 +01:00
|
|
|
import {ComponentDef} from './interfaces/definition';
|
|
|
|
import {TContainerNode, TElementContainerNode, TElementNode} from './interfaces/node';
|
2019-12-17 15:40:37 -08:00
|
|
|
import {RNode, RendererFactory3, domRendererFactory3} from './interfaces/renderer';
|
2019-10-28 12:08:17 -07:00
|
|
|
import {LView, LViewFlags, TVIEW, TViewType} from './interfaces/view';
|
2019-12-18 14:35:22 +01:00
|
|
|
import {stringifyCSSSelectorList} from './node_selector_matcher';
|
2019-10-14 13:59:17 -07:00
|
|
|
import {enterView, leaveView} from './state';
|
2019-02-20 14:21:20 -08:00
|
|
|
import {defaultScheduler} from './util/misc_utils';
|
|
|
|
import {getTNode} from './util/view_utils';
|
2018-09-21 18:38:13 -07:00
|
|
|
import {createElementRef} from './view_engine_compatibility';
|
2018-07-25 11:07:54 +02:00
|
|
|
import {RootViewRef, ViewRef} from './view_ref';
|
2018-05-09 16:49:39 -07:00
|
|
|
|
|
|
|
export class ComponentFactoryResolver extends viewEngine_ComponentFactoryResolver {
|
2018-12-05 17:43:59 +01:00
|
|
|
/**
|
|
|
|
* @param ngModule The NgModuleRef to which all resolved factories are bound.
|
|
|
|
*/
|
|
|
|
constructor(private ngModule?: viewEngine_NgModuleRef<any>) { super(); }
|
|
|
|
|
2018-05-09 16:49:39 -07:00
|
|
|
resolveComponentFactory<T>(component: Type<T>): viewEngine_ComponentFactory<T> {
|
|
|
|
ngDevMode && assertComponentType(component);
|
2018-08-29 16:34:44 -07:00
|
|
|
const componentDef = getComponentDef(component) !;
|
2018-12-05 17:43:59 +01:00
|
|
|
return new ComponentFactory(componentDef, this.ngModule);
|
2018-05-09 16:49:39 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A change detection scheduler token for {@link RootContext}. This token is the default value used
|
|
|
|
* for the default `RootContext` found in the {@link ROOT_CONTEXT} token.
|
|
|
|
*/
|
2018-08-06 14:09:38 -07:00
|
|
|
export const SCHEDULER = new InjectionToken<((fn: () => void) => void)>('SCHEDULER_TOKEN', {
|
|
|
|
providedIn: 'root',
|
2018-10-26 12:27:40 +02:00
|
|
|
factory: () => defaultScheduler,
|
2018-08-06 14:09:38 -07:00
|
|
|
});
|
|
|
|
|
2018-11-14 17:04:22 +01:00
|
|
|
function createChainedInjector(rootViewInjector: Injector, moduleInjector: Injector): Injector {
|
|
|
|
return {
|
2019-02-12 09:46:39 -08:00
|
|
|
get: <T>(token: Type<T>| InjectionToken<T>, notFoundValue?: T, flags?: InjectFlags): T => {
|
|
|
|
const value = rootViewInjector.get(token, NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR as T, flags);
|
2018-11-14 17:04:22 +01:00
|
|
|
|
2018-12-05 17:43:59 +01:00
|
|
|
if (value !== NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR ||
|
|
|
|
notFoundValue === NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR) {
|
2018-11-14 17:04:22 +01:00
|
|
|
// Return the value from the root element injector when
|
|
|
|
// - it provides it
|
|
|
|
// (value !== NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR)
|
2018-12-05 17:43:59 +01:00
|
|
|
// - the module injector should not be checked
|
|
|
|
// (notFoundValue === NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR)
|
2018-11-14 17:04:22 +01:00
|
|
|
return value;
|
|
|
|
}
|
|
|
|
|
2019-02-12 09:46:39 -08:00
|
|
|
return moduleInjector.get(token, notFoundValue, flags);
|
2018-11-14 17:04:22 +01:00
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2018-05-09 16:49:39 -07:00
|
|
|
/**
|
|
|
|
* Render3 implementation of {@link viewEngine_ComponentFactory}.
|
|
|
|
*/
|
|
|
|
export class ComponentFactory<T> extends viewEngine_ComponentFactory<T> {
|
|
|
|
selector: string;
|
|
|
|
componentType: Type<any>;
|
|
|
|
ngContentSelectors: string[];
|
2019-01-16 15:42:13 +01:00
|
|
|
isBoundToModule: boolean;
|
2018-08-06 14:09:38 -07:00
|
|
|
|
2018-05-09 16:49:39 -07:00
|
|
|
get inputs(): {propName: string; templateName: string;}[] {
|
|
|
|
return toRefArray(this.componentDef.inputs);
|
|
|
|
}
|
2018-08-06 14:09:38 -07:00
|
|
|
|
2018-05-09 16:49:39 -07:00
|
|
|
get outputs(): {propName: string; templateName: string;}[] {
|
|
|
|
return toRefArray(this.componentDef.outputs);
|
|
|
|
}
|
|
|
|
|
2018-12-05 17:43:59 +01:00
|
|
|
/**
|
|
|
|
* @param componentDef The component definition.
|
|
|
|
* @param ngModule The NgModuleRef to which the factory is bound.
|
|
|
|
*/
|
|
|
|
constructor(
|
|
|
|
private componentDef: ComponentDef<any>, private ngModule?: viewEngine_NgModuleRef<any>) {
|
2018-05-09 16:49:39 -07:00
|
|
|
super();
|
|
|
|
this.componentType = componentDef.type;
|
2019-12-18 14:35:22 +01:00
|
|
|
this.selector = stringifyCSSSelectorList(componentDef.selectors);
|
2018-12-22 16:02:34 +00:00
|
|
|
this.ngContentSelectors =
|
2019-05-21 22:00:47 +02:00
|
|
|
componentDef.ngContentSelectors ? componentDef.ngContentSelectors : [];
|
2019-01-16 15:42:13 +01:00
|
|
|
this.isBoundToModule = !!ngModule;
|
2018-05-09 16:49:39 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
create(
|
2018-07-20 14:32:23 +02:00
|
|
|
injector: Injector, projectableNodes?: any[][]|undefined, rootSelectorOrNode?: any,
|
2018-05-09 16:49:39 -07:00
|
|
|
ngModule?: viewEngine_NgModuleRef<any>|undefined): viewEngine_ComponentRef<T> {
|
2018-12-05 17:43:59 +01:00
|
|
|
ngModule = ngModule || this.ngModule;
|
2018-05-09 16:49:39 -07:00
|
|
|
|
2018-11-28 20:42:40 -08:00
|
|
|
const rootViewInjector =
|
|
|
|
ngModule ? createChainedInjector(injector, ngModule.injector) : injector;
|
|
|
|
|
|
|
|
const rendererFactory =
|
|
|
|
rootViewInjector.get(RendererFactory2, domRendererFactory3) as RendererFactory3;
|
|
|
|
const sanitizer = rootViewInjector.get(Sanitizer, null);
|
2018-08-06 14:09:38 -07:00
|
|
|
|
2019-12-17 15:40:37 -08:00
|
|
|
const hostRenderer = rendererFactory.createRenderer(null, this.componentDef);
|
2019-07-19 18:45:21 +02:00
|
|
|
const hostRNode = rootSelectorOrNode ?
|
2019-12-17 15:40:37 -08:00
|
|
|
locateHostElement(hostRenderer, rootSelectorOrNode, this.componentDef.encapsulation) :
|
2019-12-18 14:35:22 +01:00
|
|
|
// 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.
|
|
|
|
elementCreate(
|
|
|
|
this.componentDef.selectors[0][0] as string || 'div',
|
|
|
|
rendererFactory.createRenderer(null, this.componentDef), null);
|
2018-05-09 16:49:39 -07:00
|
|
|
|
2018-09-13 16:07:23 -07:00
|
|
|
const rootFlags = this.componentDef.onPush ? LViewFlags.Dirty | LViewFlags.IsRoot :
|
|
|
|
LViewFlags.CheckAlways | LViewFlags.IsRoot;
|
2019-04-18 17:52:04 -07:00
|
|
|
|
|
|
|
// Check whether this Component needs to be isolated from other components, i.e. whether it
|
|
|
|
// should be placed into its own (empty) root context or existing root context should be used.
|
|
|
|
// Note: this is internal-only convention and might change in the future, so it should not be
|
|
|
|
// relied upon externally.
|
|
|
|
const isIsolated = typeof rootSelectorOrNode === 'string' &&
|
|
|
|
/^#root-ng-internal-isolated-\d+/.test(rootSelectorOrNode);
|
|
|
|
|
2019-07-19 18:45:21 +02:00
|
|
|
const rootContext = createRootContext();
|
2018-11-19 21:06:41 +01:00
|
|
|
|
2018-05-09 16:49:39 -07:00
|
|
|
// Create the root view. Uses empty TView and ContentTemplate.
|
2019-10-28 12:08:17 -07:00
|
|
|
const rootTView = createTView(TViewType.Root, -1, null, 1, 0, null, null, null, null, null);
|
2018-11-21 21:14:06 -08:00
|
|
|
const rootLView = createLView(
|
2019-12-17 15:40:37 -08:00
|
|
|
null, rootTView, rootContext, rootFlags, null, null, rendererFactory, hostRenderer,
|
|
|
|
sanitizer, rootViewInjector);
|
|
|
|
const addVersion = rootSelectorOrNode && hostRNode ? VERSION.full : null;
|
2018-05-09 16:49:39 -07:00
|
|
|
|
|
|
|
// rootView is the parent when bootstrapping
|
2019-10-14 13:59:17 -07:00
|
|
|
// 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, null);
|
2018-05-09 16:49:39 -07:00
|
|
|
|
|
|
|
let component: T;
|
2018-09-13 16:07:23 -07:00
|
|
|
let tElementNode: TElementNode;
|
fix(ivy): don't mask errors by calling lifecycle hooks after a crash (#31244)
The Angular runtime frequently calls into user code (for example, when
writing to a property binding). Since user code can throw errors, calls to
it are frequently wrapped in a try-finally block. In Ivy, the following
pattern is common:
```typescript
enterView();
try {
callUserCode();
} finally {
leaveView();
}
```
This has a significant problem, however: `leaveView` has a side effect: it
calls any pending lifecycle hooks that might've been scheduled during the
current round of change detection. Generally it's a bad idea to run
lifecycle hooks after the application has crashed. The application is in an
inconsistent state - directives may not be instantiated fully, queries may
not be resolved, bindings may not have been applied, etc. Invariants that
the app code relies upon may not hold. Further crashes or broken behavior
are likely.
Frequently, lifecycle hooks are used to make assertions about these
invariants. When these assertions fail, they will throw and "swallow" the
original error, making debugging of the problem much more difficult.
This commit modifies `leaveView` to understand whether the application is
currently crashing, via a parameter `safeToRunHooks`. This parameter is set
by modifying the above pattern:
```typescript
enterView();
let safeToRunHooks = false;
try {
callUserCode();
safeToRunHooks = true;
} finally {
leaveView(..., safeToRunHooks);
}
```
If `callUserCode` crashes, then `safeToRunHooks` will never be set to `true`
and `leaveView` won't call any further user code. The original error will
then propagate back up the stack and be reported correctly. A test is added
to verify this behavior.
PR Close #31244
2019-06-24 13:14:05 -07:00
|
|
|
|
2018-05-09 16:49:39 -07:00
|
|
|
try {
|
2018-11-21 21:14:06 -08:00
|
|
|
const componentView = createRootComponentView(
|
2019-12-17 15:40:37 -08:00
|
|
|
hostRNode, this.componentDef, rootLView, rendererFactory, hostRenderer, addVersion, null);
|
2018-12-14 15:11:14 +01:00
|
|
|
|
2020-01-30 14:57:44 -08:00
|
|
|
tElementNode = getTNode(rootLView[TVIEW], 0) as TElementNode;
|
2018-08-31 16:29:22 +02:00
|
|
|
|
2018-07-20 14:32:23 +02:00
|
|
|
if (projectableNodes) {
|
2019-01-21 14:55:37 +01:00
|
|
|
// 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.
|
|
|
|
tElementNode.projection =
|
|
|
|
projectableNodes.map((nodesforSlot: RNode[]) => { return Array.from(nodesforSlot); });
|
2018-07-20 14:32:23 +02:00
|
|
|
}
|
|
|
|
|
2018-10-08 16:04:46 -07:00
|
|
|
// 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(
|
2018-11-21 21:14:06 -08:00
|
|
|
componentView, this.componentDef, rootLView, rootContext, [LifecycleHooksFeature]);
|
2018-10-08 16:04:46 -07:00
|
|
|
|
2020-01-30 14:57:44 -08:00
|
|
|
renderView(rootTView, rootLView, null);
|
2018-05-09 16:49:39 -07:00
|
|
|
} finally {
|
2019-10-14 13:59:17 -07:00
|
|
|
leaveView();
|
2018-05-09 16:49:39 -07:00
|
|
|
}
|
|
|
|
|
2018-09-21 18:38:13 -07:00
|
|
|
const componentRef = new ComponentRef(
|
2018-11-14 19:25:03 -08:00
|
|
|
this.componentType, component,
|
2018-11-21 21:14:06 -08:00
|
|
|
createElementRef(viewEngine_ElementRef, tElementNode, rootLView), rootLView, tElementNode);
|
2018-09-21 18:38:13 -07:00
|
|
|
|
2019-07-19 18:45:21 +02:00
|
|
|
if (!rootSelectorOrNode || isIsolated) {
|
2019-06-27 10:56:40 -07:00
|
|
|
// The host element of the internal or isolated root view is attached to the component's host
|
|
|
|
// view node.
|
2018-09-13 16:07:23 -07:00
|
|
|
componentRef.hostView._tViewNode !.child = tElementNode;
|
2018-07-20 14:32:23 +02:00
|
|
|
}
|
|
|
|
return componentRef;
|
2018-05-09 16:49:39 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-09-21 18:38:13 -07:00
|
|
|
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(): viewEngine_ComponentFactoryResolver {
|
|
|
|
return componentFactoryResolver;
|
|
|
|
}
|
|
|
|
|
2018-05-09 16:49:39 -07:00
|
|
|
/**
|
|
|
|
* 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 viewEngine_ComponentRef<T> {
|
|
|
|
destroyCbs: (() => void)[]|null = [];
|
|
|
|
instance: T;
|
|
|
|
hostView: ViewRef<T>;
|
2018-08-06 14:09:38 -07:00
|
|
|
changeDetectorRef: ViewEngine_ChangeDetectorRef;
|
2018-05-09 16:49:39 -07:00
|
|
|
componentType: Type<T>;
|
|
|
|
|
|
|
|
constructor(
|
2018-11-14 19:25:03 -08:00
|
|
|
componentType: Type<T>, instance: T, public location: viewEngine_ElementRef,
|
2018-11-21 21:14:06 -08:00
|
|
|
private _rootLView: LView,
|
2018-11-14 19:25:03 -08:00
|
|
|
private _tNode: TElementNode|TContainerNode|TElementContainerNode) {
|
2018-05-09 16:49:39 -07:00
|
|
|
super();
|
|
|
|
this.instance = instance;
|
2018-11-21 21:14:06 -08:00
|
|
|
this.hostView = this.changeDetectorRef = new RootViewRef<T>(_rootLView);
|
2019-01-24 23:50:12 +00:00
|
|
|
this.hostView._tViewNode = assignTViewNodeToLView(_rootLView[TVIEW], null, -1, _rootLView);
|
2018-05-09 16:49:39 -07:00
|
|
|
this.componentType = componentType;
|
|
|
|
}
|
|
|
|
|
2018-11-21 21:14:06 -08:00
|
|
|
get injector(): Injector { return new NodeInjector(this._tNode, this._rootLView); }
|
2018-11-14 19:25:03 -08:00
|
|
|
|
2018-05-09 16:49:39 -07:00
|
|
|
destroy(): void {
|
2019-06-13 17:41:45 +02:00
|
|
|
if (this.destroyCbs) {
|
|
|
|
this.destroyCbs.forEach(fn => fn());
|
|
|
|
this.destroyCbs = null;
|
|
|
|
!this.hostView.destroyed && this.hostView.destroy();
|
|
|
|
}
|
2018-05-09 16:49:39 -07:00
|
|
|
}
|
2019-06-13 17:41:45 +02:00
|
|
|
|
2018-05-09 16:49:39 -07:00
|
|
|
onDestroy(callback: () => void): void {
|
2019-06-13 17:41:45 +02:00
|
|
|
if (this.destroyCbs) {
|
|
|
|
this.destroyCbs.push(callback);
|
|
|
|
}
|
2018-05-09 16:49:39 -07:00
|
|
|
}
|
2018-08-18 11:14:50 -07:00
|
|
|
}
|