refactor(core): Remove reliance on `TNodeType.View`. (#38707)
`TNodeType.View` was created to support inline views. That feature did not materialize and we have since removed the instructions for it, leave an unneeded `TNodeType.View` which was still used in a very inconsistent way. This change no longer created `TNodeType.View` (and there will be a follow up chang to completely remove it.) Also simplified the mental model so that `LView[HOST]`/`LView[T_HOST]` always point to the insertion location of the `LView`. PR Close #38707
This commit is contained in:
parent
b2579d43cd
commit
eb32b6bd6b
|
@ -389,27 +389,6 @@
|
|||
"packages/core/src/render3/definition.ts",
|
||||
"packages/core/src/metadata/ng_module.ts"
|
||||
],
|
||||
[
|
||||
"packages/core/src/application_ref.ts",
|
||||
"packages/core/src/application_tokens.ts",
|
||||
"packages/core/src/linker/component_factory.ts",
|
||||
"packages/core/src/change_detection/change_detection.ts",
|
||||
"packages/core/src/change_detection/change_detector_ref.ts",
|
||||
"packages/core/src/render3/view_engine_compatibility.ts",
|
||||
"packages/core/src/render3/node_manipulation.ts",
|
||||
"packages/core/src/core.ts",
|
||||
"packages/core/src/metadata.ts",
|
||||
"packages/core/src/di.ts",
|
||||
"packages/core/src/di/index.ts",
|
||||
"packages/core/src/di/injectable.ts",
|
||||
"packages/core/src/di/jit/injectable.ts",
|
||||
"packages/core/src/di/jit/environment.ts",
|
||||
"packages/core/src/di/injector_compatibility.ts",
|
||||
"packages/core/src/di/injector.ts",
|
||||
"packages/core/src/di/r3_injector.ts",
|
||||
"packages/core/src/render3/definition.ts",
|
||||
"packages/core/src/metadata/ng_module.ts"
|
||||
],
|
||||
[
|
||||
"packages/core/src/application_ref.ts",
|
||||
"packages/core/src/application_tokens.ts",
|
||||
|
@ -904,6 +883,12 @@
|
|||
"packages/core/src/change_detection/differs/default_keyvalue_differ.ts",
|
||||
"packages/core/src/change_detection/differs/keyvalue_differs.ts"
|
||||
],
|
||||
[
|
||||
"packages/core/src/debug/debug_node.ts",
|
||||
"packages/core/src/view/index.ts",
|
||||
"packages/core/src/view/entrypoint.ts",
|
||||
"packages/core/src/view/services.ts"
|
||||
],
|
||||
[
|
||||
"packages/core/src/di/injectable.ts",
|
||||
"packages/core/src/di/jit/injectable.ts"
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
"master": {
|
||||
"uncompressed": {
|
||||
"runtime-es2015": 3037,
|
||||
"main-es2015": 448493,
|
||||
"main-es2015": 447742,
|
||||
"polyfills-es2015": 52415
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
"master": {
|
||||
"uncompressed": {
|
||||
"runtime-es2015": 1485,
|
||||
"main-es2015": 146989,
|
||||
"main-es2015": 146417,
|
||||
"polyfills-es2015": 36571
|
||||
}
|
||||
}
|
||||
|
@ -39,7 +39,7 @@
|
|||
"master": {
|
||||
"uncompressed": {
|
||||
"runtime-es2015": 2289,
|
||||
"main-es2015": 242351,
|
||||
"main-es2015": 241850,
|
||||
"polyfills-es2015": 36938,
|
||||
"5-es2015": 751
|
||||
}
|
||||
|
@ -49,7 +49,7 @@
|
|||
"master": {
|
||||
"uncompressed": {
|
||||
"runtime-es2015": 2289,
|
||||
"main-es2015": 218961,
|
||||
"main-es2015": 218329,
|
||||
"polyfills-es2015": 36723,
|
||||
"5-es2015": 781
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
*/
|
||||
|
||||
import {Injector} from '../di/injector';
|
||||
import {assertTNodeForLView} from '../render3/assert';
|
||||
import {CONTAINER_HEADER_OFFSET, LContainer, NATIVE} from '../render3/interfaces/container';
|
||||
import {TElementNode, TNode, TNodeFlags, TNodeType} from '../render3/interfaces/node';
|
||||
import {isComponentHost, isLContainer} from '../render3/interfaces/type_checks';
|
||||
|
@ -15,7 +16,7 @@ import {getComponent, getContext, getInjectionTokens, getInjector, getListeners,
|
|||
import {INTERPOLATION_DELIMITER, renderStringify} from '../render3/util/misc_utils';
|
||||
import {getComponentLViewByIndex, getNativeByTNodeOrNull} from '../render3/util/view_utils';
|
||||
import {assertDomNode} from '../util/assert';
|
||||
import {DebugContext} from '../view/types';
|
||||
import {DebugContext} from '../view/index';
|
||||
|
||||
|
||||
|
||||
|
@ -525,6 +526,7 @@ function _queryAllR3(
|
|||
function _queryNodeChildrenR3(
|
||||
tNode: TNode, lView: LView, predicate: Predicate<DebugElement>|Predicate<DebugNode>,
|
||||
matches: DebugElement[]|DebugNode[], elementsOnly: boolean, rootNativeNode: any) {
|
||||
ngDevMode && assertTNodeForLView(tNode, lView);
|
||||
const nativeNode = getNativeByTNodeOrNull(tNode, lView);
|
||||
// For each type of TNode, specific logic is executed.
|
||||
if (tNode.type === TNodeType.Element || tNode.type === TNodeType.ElementContainer) {
|
||||
|
@ -616,9 +618,11 @@ function _queryNodeChildrenInContainerR3(
|
|||
lContainer: LContainer, predicate: Predicate<DebugElement>|Predicate<DebugNode>,
|
||||
matches: DebugElement[]|DebugNode[], elementsOnly: boolean, rootNativeNode: any) {
|
||||
for (let i = CONTAINER_HEADER_OFFSET; i < lContainer.length; i++) {
|
||||
const childView = lContainer[i];
|
||||
_queryNodeChildrenR3(
|
||||
childView[TVIEW].node!, childView, predicate, matches, elementsOnly, rootNativeNode);
|
||||
const childView = lContainer[i] as LView;
|
||||
const firstChild = childView[TVIEW].firstChild;
|
||||
if (firstChild) {
|
||||
_queryNodeChildrenR3(firstChild, childView, predicate, matches, elementsOnly, rootNativeNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -658,7 +662,7 @@ function _addQueryMatchR3(
|
|||
*
|
||||
* @param nativeNode the current native node
|
||||
* @param predicate the predicate to match
|
||||
* @param matches the list of positive matches
|
||||
* @param matches the list where matches are stored
|
||||
* @param elementsOnly whether only elements should be searched
|
||||
*/
|
||||
function _queryNativeNodeDescendants(
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
import '../util/ng_dev_mode';
|
||||
|
||||
import {Type} from '../interface/type';
|
||||
import {assertNotEqual} from '../util/assert';
|
||||
import {getClosureSafeProperty} from '../util/property';
|
||||
import {stringify} from '../util/stringify';
|
||||
|
||||
|
@ -84,6 +85,19 @@ export function setInjectImplementation(
|
|||
return previous;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that `_injectImplementation` is not `fn`.
|
||||
*
|
||||
* This is useful, to prevent infinite recursion.
|
||||
*
|
||||
* @param fn Function which it should not equal to
|
||||
*/
|
||||
export function assertInjectImplementationNot(
|
||||
fn: (<T>(token: Type<T>|InjectionToken<T>, flags?: InjectFlags) => T | null)) {
|
||||
ngDevMode &&
|
||||
assertNotEqual(_injectImplementation, fn, 'Calling ɵɵinject would cause infinite recursion');
|
||||
}
|
||||
|
||||
export function injectInjectorOnly<T>(token: Type<T>|InjectionToken<T>): T;
|
||||
export function injectInjectorOnly<T>(token: Type<T>|InjectionToken<T>, flags?: InjectFlags): T|
|
||||
null;
|
||||
|
|
|
@ -6,14 +6,16 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {assertDefined, assertEqual, assertIndexInRange, assertNumber, throwError} from '../util/assert';
|
||||
import {getComponentDef, getNgModuleDef} from './definition';
|
||||
import {LContainer} from './interfaces/container';
|
||||
import {DirectiveDef} from './interfaces/definition';
|
||||
import { assertDefined, assertEqual, assertNumber, throwError } from '../util/assert';
|
||||
import { getComponentDef, getNgModuleDef } from './definition';
|
||||
import { LContainer } from './interfaces/container';
|
||||
import { DirectiveDef } from './interfaces/definition';
|
||||
import { PARENT_INJECTOR } from './interfaces/injector';
|
||||
import {TNode} from './interfaces/node';
|
||||
import {isLContainer, isLView} from './interfaces/type_checks';
|
||||
import {HEADER_OFFSET, LView, TVIEW, TView} from './interfaces/view';
|
||||
import { TNode } from './interfaces/node';
|
||||
import { isLContainer, isLView } from './interfaces/type_checks';
|
||||
import { HEADER_OFFSET, LView, TVIEW, TView } from './interfaces/view';
|
||||
|
||||
|
||||
|
||||
// [Assert functions do not constraint type when they are guarded by a truthy
|
||||
// expression.](https://github.com/microsoft/TypeScript/issues/37295)
|
||||
|
|
|
@ -129,9 +129,9 @@ export function renderComponent<T>(
|
|||
const rootContext = createRootContext(opts.scheduler, opts.playerHandler);
|
||||
|
||||
const renderer = rendererFactory.createRenderer(hostRNode, componentDef);
|
||||
const rootTView = createTView(TViewType.Root, -1, null, 1, 0, null, null, null, null, null);
|
||||
const rootTView = createTView(TViewType.Root, null, null, 1, 0, null, null, null, null, null);
|
||||
const rootView: LView = createLView(
|
||||
null, rootTView, rootContext, rootFlags, null, null, rendererFactory, renderer, undefined,
|
||||
null, rootTView, rootContext, rootFlags, null, null, rendererFactory, renderer, null,
|
||||
opts.injector || null);
|
||||
|
||||
enterView(rootView);
|
||||
|
@ -193,7 +193,7 @@ export function createRootComponentView(
|
|||
const componentView = createLView(
|
||||
rootView, getOrCreateTComponentView(def), null,
|
||||
def.onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways, rootView[HEADER_OFFSET], tNode,
|
||||
rendererFactory, viewRenderer, sanitizer);
|
||||
rendererFactory, viewRenderer, sanitizer || null, null);
|
||||
|
||||
if (tView.firstCreatePass) {
|
||||
diPublicInInjector(getOrCreateNodeInjectorForNode(tNode, rootView), tView, def.type);
|
||||
|
|
|
@ -19,18 +19,16 @@ import {RendererFactory2} from '../render/api';
|
|||
import {Sanitizer} from '../sanitization/sanitizer';
|
||||
import {VERSION} from '../version';
|
||||
import {NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR} from '../view/provider';
|
||||
|
||||
import {assertComponentType} from './assert';
|
||||
import {createRootComponent, createRootComponentView, createRootContext, LifecycleHooksFeature} from './component';
|
||||
import {getComponentDef} from './definition';
|
||||
import {NodeInjector} from './di';
|
||||
import {assignTViewNodeToLView, createLView, createTView, elementCreate, locateHostElement, renderView} from './instructions/shared';
|
||||
import {createLView, createTView, elementCreate, locateHostElement, renderView} from './instructions/shared';
|
||||
import {ComponentDef} from './interfaces/definition';
|
||||
import {TContainerNode, TElementContainerNode, TElementNode, TNode, TNodeType} from './interfaces/node';
|
||||
import {TContainerNode, TElementContainerNode, TElementNode, TNode} from './interfaces/node';
|
||||
import {domRendererFactory3, RendererFactory3, RNode} from './interfaces/renderer';
|
||||
import {LView, LViewFlags, TVIEW, TViewType} from './interfaces/view';
|
||||
import {LView, LViewFlags, TViewType} from './interfaces/view';
|
||||
import {MATH_ML_NAMESPACE, SVG_NAMESPACE} from './namespaces';
|
||||
import {assertNodeOfPossibleTypes} from './node_assert';
|
||||
import {writeDirectClass} from './node_manipulation';
|
||||
import {extractAttrsAndClassesFromSelector, stringifyCSSSelectorList} from './node_selector_matcher';
|
||||
import {enterView, leaveView} from './state';
|
||||
|
@ -158,7 +156,7 @@ export class ComponentFactory<T> extends viewEngine_ComponentFactory<T> {
|
|||
const rootContext = createRootContext();
|
||||
|
||||
// Create the root view. Uses empty TView and ContentTemplate.
|
||||
const rootTView = createTView(TViewType.Root, -1, null, 1, 0, null, null, null, null, null);
|
||||
const rootTView = createTView(TViewType.Root, null, null, 1, 0, null, null, null, null, null);
|
||||
const rootLView = createLView(
|
||||
null, rootTView, rootContext, rootFlags, null, null, rendererFactory, hostRenderer,
|
||||
sanitizer, rootViewInjector);
|
||||
|
@ -223,11 +221,6 @@ export class ComponentFactory<T> extends viewEngine_ComponentFactory<T> {
|
|||
const componentRef = new ComponentRef(
|
||||
this.componentType, component,
|
||||
createElementRef(viewEngine_ElementRef, tElementNode, rootLView), rootLView, tElementNode);
|
||||
|
||||
// The host element of the internal root view is attached to the component's host view node.
|
||||
ngDevMode && assertNodeOfPossibleTypes(rootTView.node, [TNodeType.View]);
|
||||
rootTView.node!.child = tElementNode;
|
||||
|
||||
return componentRef;
|
||||
}
|
||||
}
|
||||
|
@ -267,7 +260,6 @@ export class ComponentRef<T> extends viewEngine_ComponentRef<T> {
|
|||
super();
|
||||
this.instance = instance;
|
||||
this.hostView = this.changeDetectorRef = new RootViewRef<T>(_rootLView);
|
||||
assignTViewNodeToLView(_rootLView[TVIEW], null, -1, _rootLView);
|
||||
this.componentType = componentType;
|
||||
}
|
||||
|
||||
|
|
|
@ -190,13 +190,11 @@ export function isDirectiveInstance(instance: any): boolean {
|
|||
* Locates the element within the given LView and returns the matching index
|
||||
*/
|
||||
function findViaNativeElement(lView: LView, target: RElement): number {
|
||||
let tNode = lView[TVIEW].firstChild;
|
||||
while (tNode) {
|
||||
const native = getNativeByTNodeOrNull(tNode, lView)!;
|
||||
if (native === target) {
|
||||
return tNode.index;
|
||||
const tView = lView[TVIEW];
|
||||
for (let i = HEADER_OFFSET; i < tView.bindingStartIndex; i++) {
|
||||
if (unwrapRNode(lView[i]) === target) {
|
||||
return i;
|
||||
}
|
||||
tNode = traverseNextElement(tNode);
|
||||
}
|
||||
|
||||
return -1;
|
||||
|
@ -206,7 +204,11 @@ function findViaNativeElement(lView: LView, target: RElement): number {
|
|||
* Locates the next tNode (child, sibling or parent).
|
||||
*/
|
||||
function traverseNextElement(tNode: TNode): TNode|null {
|
||||
if (tNode.child) {
|
||||
if (tNode.child && tNode.child.parent === tNode) {
|
||||
// FIXME(misko): checking if `tNode.child.parent === tNode` should not be necessary
|
||||
// We have added it here because i18n creates TNode's which are not valid, so this is a work
|
||||
// around. The i18n code is being refactored in #??? and once it lands this extra check can be
|
||||
// deleted.
|
||||
return tNode.child;
|
||||
} else if (tNode.next) {
|
||||
return tNode.next;
|
||||
|
|
|
@ -13,10 +13,10 @@ import {injectRootLimpMode, setInjectImplementation} from '../di/injector_compat
|
|||
import {getInjectorDef} from '../di/interface/defs';
|
||||
import {InjectFlags} from '../di/interface/injector';
|
||||
import {Type} from '../interface/type';
|
||||
import {assertDefined, assertEqual} from '../util/assert';
|
||||
import {assertDefined, assertEqual, assertIndexInRange, throwError} from '../util/assert';
|
||||
import {noSideEffects} from '../util/closure';
|
||||
|
||||
import {assertDirectiveDef} from './assert';
|
||||
import {assertDirectiveDef, assertNodeInjector, assertTNodeForLView} from './assert';
|
||||
import {getFactoryDef} from './definition';
|
||||
import {NG_ELEMENT_ID, NG_FACTORY_DEF} from './fields';
|
||||
import {registerPreOrderHooks} from './hooks';
|
||||
|
@ -24,7 +24,7 @@ import {DirectiveDef, FactoryFn} from './interfaces/definition';
|
|||
import {isFactory, NO_PARENT_INJECTOR, NodeInjectorFactory, PARENT_INJECTOR, RelativeInjectorLocation, RelativeInjectorLocationFlags, TNODE} from './interfaces/injector';
|
||||
import {AttributeMarker, TContainerNode, TDirectiveHostNode, TElementContainerNode, TElementNode, TNode, TNodeProviderIndexes, TNodeType} from './interfaces/node';
|
||||
import {isComponentDef, isComponentHost} from './interfaces/type_checks';
|
||||
import {DECLARATION_COMPONENT_VIEW, DECLARATION_VIEW, INJECTOR, LView, T_HOST, TData, TVIEW, TView} from './interfaces/view';
|
||||
import {DECLARATION_COMPONENT_VIEW, DECLARATION_VIEW, INJECTOR, LView, T_HOST, TData, TVIEW, TView, TViewType} from './interfaces/view';
|
||||
import {assertNodeOfPossibleTypes} from './node_assert';
|
||||
import {enterDI, leaveDI} from './state';
|
||||
import {isNameOnlyAttributeMarker} from './util/attrs_utils';
|
||||
|
@ -184,16 +184,17 @@ function insertBloom(arr: any[], footer: TNode|null): void {
|
|||
}
|
||||
|
||||
|
||||
export function getInjectorIndex(tNode: TNode, hostView: LView): number {
|
||||
export function getInjectorIndex(tNode: TNode, lView: LView): number {
|
||||
if (tNode.injectorIndex === -1 ||
|
||||
// If the injector index is the same as its parent's injector index, then the index has been
|
||||
// copied down from the parent node. No injector has been created yet on this node.
|
||||
(tNode.parent && tNode.parent.injectorIndex === tNode.injectorIndex) ||
|
||||
// After the first template pass, the injector index might exist but the parent values
|
||||
// might not have been calculated yet for this instance
|
||||
hostView[tNode.injectorIndex + PARENT_INJECTOR] == null) {
|
||||
lView[tNode.injectorIndex + PARENT_INJECTOR] == null) {
|
||||
return -1;
|
||||
} else {
|
||||
ngDevMode && assertIndexInRange(lView, tNode.injectorIndex);
|
||||
return tNode.injectorIndex;
|
||||
}
|
||||
}
|
||||
|
@ -204,25 +205,55 @@ export function getInjectorIndex(tNode: TNode, hostView: LView): number {
|
|||
*
|
||||
* Returns a combination of number of `LView` we have to go up and index in that `LView`
|
||||
*/
|
||||
export function getParentInjectorLocation(tNode: TNode, view: LView): RelativeInjectorLocation {
|
||||
export function getParentInjectorLocation(tNode: TNode, lView: LView): RelativeInjectorLocation {
|
||||
if (tNode.parent && tNode.parent.injectorIndex !== -1) {
|
||||
// If we have a parent `TNode` and there is an injector associated with it we are done, because
|
||||
// the parent injector is within the current `LView`.
|
||||
return tNode.parent.injectorIndex as any; // ViewOffset is 0
|
||||
}
|
||||
|
||||
// For most cases, the parent injector index can be found on the host node (e.g. for component
|
||||
// or container), so this loop will be skipped, but we must keep the loop here to support
|
||||
// the rarer case of deeply nested <ng-template> tags or inline views.
|
||||
let hostTNode = view[T_HOST];
|
||||
let viewOffset = 1;
|
||||
while (hostTNode && hostTNode.injectorIndex === -1) {
|
||||
view = view[DECLARATION_VIEW]!;
|
||||
hostTNode = view ? view[T_HOST] : null;
|
||||
viewOffset++;
|
||||
}
|
||||
// When parent injector location is computed it may be outside of the current view. (ie it could
|
||||
// be pointing to a declared parent location). This variable stores number of declaration parents
|
||||
// we need to walk up in order to find the parent injector location.
|
||||
let declarationViewOffset = 0;
|
||||
let parentTNode: TNode|null = null;
|
||||
let lViewCursor: LView|null = lView;
|
||||
|
||||
return hostTNode ?
|
||||
hostTNode.injectorIndex | (viewOffset << RelativeInjectorLocationFlags.ViewOffsetShift) :
|
||||
-1 as any;
|
||||
// The parent injector is not in the current `LView`. We will have to walk the declared parent
|
||||
// `LView` hierarchy and look for it. If we walk of the top, that means that there is no parent
|
||||
// `NodeInjector`.
|
||||
while (lViewCursor !== null) {
|
||||
// First determine the `parentTNode` location. The parent pointer differs based on `TView.type`.
|
||||
const tView = lViewCursor[TVIEW];
|
||||
const tViewType = tView.type;
|
||||
if (tViewType === TViewType.Embedded) {
|
||||
ngDevMode && assertDefined(tView.node, 'Embedded TNodes should have declaration parents.');
|
||||
parentTNode = tView.node;
|
||||
} else if (tViewType === TViewType.Component) {
|
||||
// Components don't have `TView.declTNode` because each instance of component could be
|
||||
// inserted in different location, hence `TView.declTNode` is meaningless.
|
||||
parentTNode = lViewCursor[T_HOST];
|
||||
} else {
|
||||
ngDevMode && assertEqual(tView.type, TViewType.Root, 'Root type expected');
|
||||
parentTNode = null;
|
||||
}
|
||||
if (parentTNode === null) {
|
||||
// If we have no parent, than we are done.
|
||||
return NO_PARENT_INJECTOR;
|
||||
}
|
||||
|
||||
ngDevMode && parentTNode && assertTNodeForLView(parentTNode!, lViewCursor[DECLARATION_VIEW]!);
|
||||
// Every iteration of the loop requires that we go to the declared parent.
|
||||
declarationViewOffset++;
|
||||
lViewCursor = lViewCursor[DECLARATION_VIEW];
|
||||
|
||||
if (parentTNode.injectorIndex !== -1) {
|
||||
// We found a NodeInjector which points to something.
|
||||
return (parentTNode.injectorIndex |
|
||||
(declarationViewOffset << RelativeInjectorLocationFlags.ViewOffsetShift)) as any;
|
||||
}
|
||||
}
|
||||
return NO_PARENT_INJECTOR;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -367,13 +398,12 @@ export function getOrCreateInjectable<T>(
|
|||
flags & InjectFlags.Host ? lView[DECLARATION_COMPONENT_VIEW][T_HOST] : null;
|
||||
|
||||
// If we should skip this injector, or if there is no injector on this node, start by
|
||||
// searching
|
||||
// the parent injector.
|
||||
// searching the parent injector.
|
||||
if (injectorIndex === -1 || flags & InjectFlags.SkipSelf) {
|
||||
parentLocation = injectorIndex === -1 ? getParentInjectorLocation(tNode, lView) :
|
||||
lView[injectorIndex + PARENT_INJECTOR];
|
||||
|
||||
if (!shouldSearchParent(flags, false)) {
|
||||
if (parentLocation === NO_PARENT_INJECTOR || !shouldSearchParent(flags, false)) {
|
||||
injectorIndex = -1;
|
||||
} else {
|
||||
previousTView = lView[TVIEW];
|
||||
|
@ -385,10 +415,11 @@ export function getOrCreateInjectable<T>(
|
|||
// Traverse up the injector tree until we find a potential match or until we know there
|
||||
// *isn't* a match.
|
||||
while (injectorIndex !== -1) {
|
||||
parentLocation = lView[injectorIndex + PARENT_INJECTOR];
|
||||
ngDevMode && assertNodeInjector(lView, injectorIndex);
|
||||
|
||||
// Check the current injector. If it matches, see if it contains token.
|
||||
const tView = lView[TVIEW];
|
||||
ngDevMode && assertTNodeForLView(tView.data[injectorIndex + TNODE] as TNode, lView);
|
||||
if (bloomHasToken(bloomHash, injectorIndex, tView.data)) {
|
||||
// At this point, we have an injector which *may* contain the token, so we step through
|
||||
// the providers and directives associated with the injector's corresponding node to get
|
||||
|
@ -399,7 +430,9 @@ export function getOrCreateInjectable<T>(
|
|||
return instance;
|
||||
}
|
||||
}
|
||||
if (shouldSearchParent(
|
||||
parentLocation = lView[injectorIndex + PARENT_INJECTOR];
|
||||
if (parentLocation !== NO_PARENT_INJECTOR &&
|
||||
shouldSearchParent(
|
||||
flags, lView[TVIEW].data[injectorIndex + TNODE] === hostTElementNode) &&
|
||||
bloomHasToken(bloomHash, injectorIndex, lView)) {
|
||||
// The def wasn't found anywhere on this node, so it was a false positive.
|
||||
|
@ -453,7 +486,7 @@ function searchTokensOnInjector<T>(
|
|||
const currentTView = lView[TVIEW];
|
||||
const tNode = currentTView.data[injectorIndex + TNODE] as TNode;
|
||||
// First, we need to determine if view providers can be accessed by the starting element.
|
||||
// There are two possibities
|
||||
// There are two possibilities
|
||||
const canAccessViewProviders = previousTView == null ?
|
||||
// 1) This is the first invocation `previousTView == null` which means that we are at the
|
||||
// `TNode` of where injector is starting to look. In such a case the only time we are allowed
|
||||
|
@ -544,10 +577,8 @@ export function getNodeInjectable(
|
|||
}
|
||||
const previousIncludeViewProviders = setIncludeViewProviders(factory.canSeeViewProviders);
|
||||
factory.resolving = true;
|
||||
let previousInjectImplementation;
|
||||
if (factory.injectImpl) {
|
||||
previousInjectImplementation = setInjectImplementation(factory.injectImpl);
|
||||
}
|
||||
const previousInjectImplementation =
|
||||
factory.injectImpl ? setInjectImplementation(factory.injectImpl) : null;
|
||||
enterDI(lView, tNode);
|
||||
try {
|
||||
value = lView[index] = factory.factory(undefined, tData, lView, tNode);
|
||||
|
@ -562,7 +593,8 @@ export function getNodeInjectable(
|
|||
registerPreOrderHooks(index, tData[index] as DirectiveDef<any>, tView);
|
||||
}
|
||||
} finally {
|
||||
if (factory.injectImpl) setInjectImplementation(previousInjectImplementation);
|
||||
previousInjectImplementation !== null &&
|
||||
setInjectImplementation(previousInjectImplementation);
|
||||
setIncludeViewProviders(previousIncludeViewProviders);
|
||||
factory.resolving = false;
|
||||
leaveDI();
|
||||
|
|
|
@ -354,7 +354,13 @@ function appendI18nNode(
|
|||
// Re-organize node tree to put this node in the correct position.
|
||||
if (previousTNode === parentTNode && tNode !== parentTNode.child) {
|
||||
tNode.next = parentTNode.child;
|
||||
parentTNode.child = tNode;
|
||||
// FIXME(misko): Checking `tNode.parent` is a temporary workaround until we properly
|
||||
// refactor the i18n code in #38707 and this code will be deleted.
|
||||
if (tNode.parent === null) {
|
||||
tView.firstChild = tNode;
|
||||
} else {
|
||||
parentTNode.child = tNode;
|
||||
}
|
||||
} else if (previousTNode !== parentTNode && tNode !== previousTNode.next) {
|
||||
tNode.next = previousTNode.next;
|
||||
previousTNode.next = tNode;
|
||||
|
@ -446,7 +452,7 @@ function removeNode(tView: TView, lView: LView, index: number, markAsDetached: b
|
|||
}
|
||||
}
|
||||
|
||||
if (markAsDetached) {
|
||||
if (markAsDetached && removedPhTNode) {
|
||||
// Define this node as detached to avoid projecting it later
|
||||
removedPhTNode.flags |= TNodeFlags.isDetached;
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import {InjectFlags, InjectionToken, resolveForwardRef} from '../../di';
|
||||
import {ɵɵinject} from '../../di/injector_compatibility';
|
||||
import {assertInjectImplementationNot, ɵɵinject} from '../../di/injector_compatibility';
|
||||
import {Type} from '../../interface/type';
|
||||
import {getOrCreateInjectable, injectAttributeImpl} from '../di';
|
||||
import {TDirectiveHostNode} from '../interfaces/node';
|
||||
|
@ -43,7 +43,11 @@ export function ɵɵdirectiveInject<T>(
|
|||
const lView = getLView();
|
||||
// Fall back to inject() if view hasn't been created. This situation can happen in tests
|
||||
// if inject utilities are used before bootstrapping.
|
||||
if (lView == null) return ɵɵinject(token, flags);
|
||||
if (lView === null) {
|
||||
// Verify that we will not get into infinite loop.
|
||||
ngDevMode && assertInjectImplementationNot(ɵɵdirectiveInject);
|
||||
return ɵɵinject(token, flags);
|
||||
}
|
||||
const tNode = getPreviousOrParentTNode();
|
||||
return getOrCreateInjectable<T>(
|
||||
tNode as TDirectiveHostNode, lView, resolveForwardRef(token), flags);
|
||||
|
|
|
@ -17,7 +17,7 @@ import {getInjectorIndex} from '../di';
|
|||
import {CONTAINER_HEADER_OFFSET, HAS_TRANSPLANTED_VIEWS, LContainer, MOVED_VIEWS, NATIVE} from '../interfaces/container';
|
||||
import {ComponentTemplate, DirectiveDef, DirectiveDefList, PipeDefList, ViewQueriesFunction} from '../interfaces/definition';
|
||||
import {NO_PARENT_INJECTOR, PARENT_INJECTOR, TNODE} from '../interfaces/injector';
|
||||
import {AttributeMarker, PropertyAliases, TConstants, TContainerNode, TElementNode, TNode as ITNode, TNodeFlags, TNodeProviderIndexes, TNodeType, TNodeTypeAsString, TViewNode} from '../interfaces/node';
|
||||
import {AttributeMarker, PropertyAliases, TConstants, TContainerNode, TElementNode, TNode as ITNode, TNodeFlags, TNodeProviderIndexes, TNodeType, TNodeTypeAsString} from '../interfaces/node';
|
||||
import {SelectorFlags} from '../interfaces/projection';
|
||||
import {LQueries, TQueries} from '../interfaces/query';
|
||||
import {RComment, RElement, Renderer3, RendererFactory3, RNode} from '../interfaces/renderer';
|
||||
|
@ -116,12 +116,11 @@ function nameSuffix(text: string|null|undefined): string {
|
|||
export const TViewConstructor = class TView implements ITView {
|
||||
constructor(
|
||||
public type: TViewType, //
|
||||
public id: number, //
|
||||
public blueprint: LView, //
|
||||
public template: ComponentTemplate<{}>|null, //
|
||||
public queries: TQueries|null, //
|
||||
public viewQuery: ViewQueriesFunction<{}>|null, //
|
||||
public node: TViewNode|TElementNode|null, //
|
||||
public node: ITNode|null, //
|
||||
public data: TData, //
|
||||
public bindingStartIndex: number, //
|
||||
public expandoStartIndex: number, //
|
||||
|
@ -493,7 +492,7 @@ export class LViewDebug implements ILViewDebug {
|
|||
get queries(): LQueries|null {
|
||||
return this._raw_lView[QUERIES];
|
||||
}
|
||||
get tHost(): TViewNode|TElementNode|null {
|
||||
get tHost(): ITNode|null {
|
||||
return this._raw_lView[T_HOST];
|
||||
}
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ import {createNamedArrayType} from '../../util/named_array_type';
|
|||
import {initNgDevMode} from '../../util/ng_dev_mode';
|
||||
import {normalizeDebugBindingName, normalizeDebugBindingValue} from '../../util/ng_reflect';
|
||||
import {stringify} from '../../util/stringify';
|
||||
import {assertFirstCreatePass, assertLContainer, assertLView} from '../assert';
|
||||
import {assertFirstCreatePass, assertLContainer, assertLView, assertTNodeForLView} from '../assert';
|
||||
import {attachPatchData} from '../context_discovery';
|
||||
import {getFactoryDef} from '../definition';
|
||||
import {diPublicInInjector, getNodeInjectable, getOrCreateNodeInjectorForNode} from '../di';
|
||||
|
@ -172,13 +172,14 @@ export function elementCreate(name: string, renderer: Renderer3, namespace: stri
|
|||
|
||||
export function createLView<T>(
|
||||
parentLView: LView|null, tView: TView, context: T|null, flags: LViewFlags, host: RElement|null,
|
||||
tHostNode: TViewNode|TElementNode|null, rendererFactory?: RendererFactory3|null,
|
||||
renderer?: Renderer3|null, sanitizer?: Sanitizer|null, injector?: Injector|null): LView {
|
||||
tHostNode: TNode|null, rendererFactory: RendererFactory3|null, renderer: Renderer3|null,
|
||||
sanitizer: Sanitizer|null, injector: Injector|null): LView {
|
||||
const lView =
|
||||
ngDevMode ? cloneToLViewFromTViewBlueprint(tView) : tView.blueprint.slice() as LView;
|
||||
lView[HOST] = host;
|
||||
lView[FLAGS] = flags | LViewFlags.CreationMode | LViewFlags.Attached | LViewFlags.FirstLViewPass;
|
||||
resetPreOrderHookFlags(lView);
|
||||
ngDevMode && tView.node && parentLView && assertTNodeForLView(tView.node, parentLView);
|
||||
lView[PARENT] = lView[DECLARATION_VIEW] = parentLView;
|
||||
lView[CONTEXT] = context;
|
||||
lView[RENDERER_FACTORY] = (rendererFactory || parentLView && parentLView[RENDERER_FACTORY])!;
|
||||
|
@ -250,7 +251,7 @@ function createTNodeAtIndex(
|
|||
const parentInSameView = parent !== null && parent.type !== TNodeType.View;
|
||||
const tParentNode = parentInSameView ? parent as TElementNode | TContainerNode : null;
|
||||
const tNode = tView.data[adjustedIndex] =
|
||||
createTNode(tView, tParentNode, type, adjustedIndex, name, attrs);
|
||||
createTNode(tView, parent as TElementNode | TContainerNode, type, adjustedIndex, name, attrs);
|
||||
// Assign a pointer to the first child node of a given view. The first node is not always the one
|
||||
// at index 0, in case of i18n, index 0 can be the instruction `i18nStart` and the first node has
|
||||
// the index 1 or more, so we can't just check node index.
|
||||
|
@ -269,23 +270,6 @@ function createTNodeAtIndex(
|
|||
return tNode;
|
||||
}
|
||||
|
||||
export function assignTViewNodeToLView(
|
||||
tView: TView, tParentNode: TNode|null, index: number, lView: LView): void {
|
||||
// View nodes are not stored in data because they can be added / removed at runtime (which
|
||||
// would cause indices to change). Their TNodes are instead stored in tView.node.
|
||||
let tNode = tView.node;
|
||||
if (tNode == null) {
|
||||
ngDevMode && tParentNode &&
|
||||
assertNodeOfPossibleTypes(tParentNode, [TNodeType.Element, TNodeType.Container]);
|
||||
tView.node = tNode = createTNode(
|
||||
tView,
|
||||
tParentNode as TElementNode | TContainerNode | null, //
|
||||
TNodeType.View, index, null, null) as TViewNode;
|
||||
}
|
||||
|
||||
lView[T_HOST] = tNode as TViewNode;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* When elements are created dynamically after a view blueprint is created (e.g. through
|
||||
|
@ -623,8 +607,11 @@ export function getOrCreateTComponentView(def: ComponentDef<any>): TView {
|
|||
// Create a TView if there isn't one, or recreate it if the first create pass didn't
|
||||
// complete successfully since we can't know for sure whether it's in a usable shape.
|
||||
if (tView === null || tView.incompleteFirstPass) {
|
||||
// Declaration node here is null since this function is called when we dynamically create a
|
||||
// component and hence there is no declaration.
|
||||
const declTNode = null;
|
||||
return def.tView = createTView(
|
||||
TViewType.Component, -1, def.template, def.decls, def.vars, def.directiveDefs,
|
||||
TViewType.Component, declTNode, def.template, def.decls, def.vars, def.directiveDefs,
|
||||
def.pipeDefs, def.viewQuery, def.schemas, def.consts);
|
||||
}
|
||||
|
||||
|
@ -635,7 +622,8 @@ export function getOrCreateTComponentView(def: ComponentDef<any>): TView {
|
|||
/**
|
||||
* Creates a TView instance
|
||||
*
|
||||
* @param viewIndex The viewBlockId for inline views, or -1 if it's a component/dynamic
|
||||
* @param type Type of `TView`.
|
||||
* @param declTNode Declaration location of this `TView`.
|
||||
* @param templateFn Template function
|
||||
* @param decls The number of nodes, local refs, and pipes in this template
|
||||
* @param directives Registry of directives for this view
|
||||
|
@ -645,7 +633,7 @@ export function getOrCreateTComponentView(def: ComponentDef<any>): TView {
|
|||
* @param consts Constants for this view
|
||||
*/
|
||||
export function createTView(
|
||||
type: TViewType, viewIndex: number, templateFn: ComponentTemplate<any>|null, decls: number,
|
||||
type: TViewType, declTNode: TNode|null, templateFn: ComponentTemplate<any>|null, decls: number,
|
||||
vars: number, directives: DirectiveDefListOrFactory|null, pipes: PipeDefListOrFactory|null,
|
||||
viewQuery: ViewQueriesFunction<any>|null, schemas: SchemaMetadata[]|null,
|
||||
constsOrFactory: TConstantsOrFactory|null): TView {
|
||||
|
@ -660,12 +648,11 @@ export function createTView(
|
|||
const tView = blueprint[TVIEW as any] = ngDevMode ?
|
||||
new TViewConstructor(
|
||||
type,
|
||||
viewIndex, // id: number,
|
||||
blueprint, // blueprint: LView,
|
||||
templateFn, // template: ComponentTemplate<{}>|null,
|
||||
null, // queries: TQueries|null
|
||||
viewQuery, // viewQuery: ViewQueriesFunction<{}>|null,
|
||||
null!, // node: TViewNode|TElementNode|null,
|
||||
declTNode, // declTNode: TNode|null,
|
||||
cloneToTViewData(blueprint).fill(null, bindingStartIndex), // data: TData,
|
||||
bindingStartIndex, // bindingStartIndex: number,
|
||||
initialViewLength, // expandoStartIndex: number,
|
||||
|
@ -697,12 +684,11 @@ export function createTView(
|
|||
) :
|
||||
{
|
||||
type: type,
|
||||
id: viewIndex,
|
||||
blueprint: blueprint,
|
||||
template: templateFn,
|
||||
queries: null,
|
||||
viewQuery: viewQuery,
|
||||
node: null!,
|
||||
node: declTNode,
|
||||
data: blueprint.slice().fill(null, bindingStartIndex),
|
||||
bindingStartIndex: bindingStartIndex,
|
||||
expandoStartIndex: initialViewLength,
|
||||
|
@ -812,6 +798,7 @@ export function storeCleanupWithContext(
|
|||
* Constructs a TNode object from the arguments.
|
||||
*
|
||||
* @param tView `TView` to which this `TNode` belongs (used only in `ngDevMode`)
|
||||
* @param tParent Parent `TNode`
|
||||
* @param type The type of the node
|
||||
* @param adjustedIndex The index of the TNode in TView.data, adjusted for HEADER_OFFSET
|
||||
* @param tagName The tag name of the node
|
||||
|
@ -1488,7 +1475,8 @@ function addComponentLogic<T>(lView: LView, hostTNode: TElementNode, def: Compon
|
|||
lView,
|
||||
createLView(
|
||||
lView, tView, null, def.onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways, native,
|
||||
hostTNode as TElementNode, rendererFactory, rendererFactory.createRenderer(native, def)));
|
||||
hostTNode as TElementNode, rendererFactory, rendererFactory.createRenderer(native, def),
|
||||
null, null));
|
||||
|
||||
// Component view will always be created before any injected LContainers,
|
||||
// so this is a regular element, wrap it with the component view
|
||||
|
|
|
@ -35,13 +35,8 @@ function templateFirstCreatePass(
|
|||
registerPostOrderHooks(tView, tNode);
|
||||
|
||||
const embeddedTView = tNode.tViews = createTView(
|
||||
TViewType.Embedded, -1, templateFn, decls, vars, tView.directiveRegistry, tView.pipeRegistry,
|
||||
null, tView.schemas, tViewConsts);
|
||||
const embeddedTViewNode =
|
||||
createTNode(embeddedTView, null, TNodeType.View, -1, null, null) as TViewNode;
|
||||
embeddedTViewNode.injectorIndex = tNode.injectorIndex;
|
||||
// FIXME(misko): remove `embeddedTView.node'
|
||||
embeddedTView.node = embeddedTView.firstChild = embeddedTViewNode;
|
||||
TViewType.Embedded, tNode, templateFn, decls, vars, tView.directiveRegistry,
|
||||
tView.pipeRegistry, null, tView.schemas, tViewConsts);
|
||||
|
||||
if (tView.queries !== null) {
|
||||
tView.queries.template(tView, tNode);
|
||||
|
|
|
@ -14,6 +14,11 @@ import {assertDefined, assertEqual} from '../../util/assert';
|
|||
import {TDirectiveHostNode} from './node';
|
||||
import {LView, TData} from './view';
|
||||
|
||||
/**
|
||||
* Offset from 'injectorIndex' where:
|
||||
* - `TViewData[injectorIndex + TNODE]` => `TNode` associated with the current injector.
|
||||
* - `LView[injectorIndex + TNODE]` => index to the parent injector.
|
||||
*/
|
||||
export const TNODE = 8;
|
||||
export const PARENT_INJECTOR = 8;
|
||||
export const INJECTOR_BLOOM_PARENT_SIZE = 9;
|
||||
|
|
|
@ -41,6 +41,7 @@ export const RENDERER = 11;
|
|||
export const SANITIZER = 12;
|
||||
export const CHILD_HEAD = 13;
|
||||
export const CHILD_TAIL = 14;
|
||||
// FIXME(misko): Investigate if the three declarations aren't all same thing.
|
||||
export const DECLARATION_VIEW = 15;
|
||||
export const DECLARATION_COMPONENT_VIEW = 16;
|
||||
export const DECLARATION_LCONTAINER = 17;
|
||||
|
@ -79,8 +80,7 @@ export interface LView extends Array<any> {
|
|||
debug?: LViewDebug;
|
||||
|
||||
/**
|
||||
* The host node for this LView instance, if this is a component view.
|
||||
* If this is an embedded view, HOST will be null.
|
||||
* The node into which this `LView` is inserted.
|
||||
*/
|
||||
[HOST]: RElement|null;
|
||||
|
||||
|
@ -120,16 +120,35 @@ export interface LView extends Array<any> {
|
|||
[QUERIES]: LQueries|null;
|
||||
|
||||
/**
|
||||
* Pointer to the `TViewNode` or `TElementNode` which represents the root of the view.
|
||||
* Store the `TNode` of the location where the current `LView` is inserted into.
|
||||
*
|
||||
* If `TViewNode`, this is an embedded view of a container. We need this to be able to
|
||||
* efficiently find the `LViewNode` when inserting the view into an anchor.
|
||||
* Given:
|
||||
* ```
|
||||
* <div>
|
||||
* <ng-template><span></span></ng-template>
|
||||
* </div>
|
||||
* ```
|
||||
*
|
||||
* If `TElementNode`, this is the LView of a component.
|
||||
* We end up with two `TView`s.
|
||||
* - `parent` `TView` which contains `<div><!-- anchor --></div>`
|
||||
* - `child` `TView` which contains `<span></span>`
|
||||
*
|
||||
* If null, this is the root view of an application (root component is in this view).
|
||||
* Typically the `child` is inserted into the declaration location of the `parent`, but it can be
|
||||
* inserted anywhere. Because it can be inserted anywhere it is not possible to store the
|
||||
* insertion information in the `TView` and instead we must store it in the `LView[T_HOST]`.
|
||||
*
|
||||
* So to determine where is our insertion parent we would execute:
|
||||
* ```
|
||||
* const parentLView = lView[PARENT];
|
||||
* const parentTNode = lView[T_HOST];
|
||||
* const insertionParent = parentLView[parentTNode.index];
|
||||
* ```
|
||||
*
|
||||
*
|
||||
* If `null`, this is the root view of an application (root component is in this view) and it has
|
||||
* no parents.
|
||||
*/
|
||||
[T_HOST]: TViewNode|TElementNode|null;
|
||||
[T_HOST]: TNode|null;
|
||||
|
||||
/**
|
||||
* When a view is destroyed, listeners need to be released and outputs need to be
|
||||
|
@ -183,8 +202,6 @@ export interface LView extends Array<any> {
|
|||
/**
|
||||
* View where this view's template was declared.
|
||||
*
|
||||
* Only applicable for dynamically created views. Will be null for inline/component views.
|
||||
*
|
||||
* The template for a dynamically created view may be declared in a different view than
|
||||
* it is inserted. We already track the "insertion view" (view where the template was
|
||||
* inserted) in LView[PARENT], but we also need access to the "declaration view"
|
||||
|
@ -439,6 +456,17 @@ export const enum TViewType {
|
|||
Embedded = 2,
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts `TViewType` into human readable text.
|
||||
* Make sure this matches with `TViewType`
|
||||
*/
|
||||
export const TViewTypeAsString = [
|
||||
'Root', // 0
|
||||
'Component', // 1
|
||||
'Embedded', // 2
|
||||
] as const;
|
||||
|
||||
|
||||
/**
|
||||
* The static data for an LView (shared between all templates of a
|
||||
* given type).
|
||||
|
@ -451,15 +479,6 @@ export interface TView {
|
|||
*/
|
||||
type: TViewType;
|
||||
|
||||
/**
|
||||
* ID for inline views to determine whether a view is the same as the previous view
|
||||
* in a certain position. If it's not, we know the new view needs to be inserted
|
||||
* and the one that exists needs to be removed (e.g. if/else statements)
|
||||
*
|
||||
* If this is -1, then this is a component view or a dynamically created view.
|
||||
*/
|
||||
readonly id: number;
|
||||
|
||||
/**
|
||||
* This is a blueprint used to generate LView instances for this TView. Copying this
|
||||
* blueprint is faster than creating a new LView from scratch.
|
||||
|
@ -478,21 +497,12 @@ export interface TView {
|
|||
viewQuery: ViewQueriesFunction<{}>|null;
|
||||
|
||||
/**
|
||||
* Pointer to the host `TNode` (not part of this TView).
|
||||
*
|
||||
* If this is a `TViewNode` for an `LViewNode`, this is an embedded view of a container.
|
||||
* We need this pointer to be able to efficiently find this node when inserting the view
|
||||
* into an anchor.
|
||||
*
|
||||
* If this is a `TElementNode`, this is the view of a root component. It has exactly one
|
||||
* root TNode.
|
||||
*
|
||||
* If this is null, this is the view of a component that is not at root. We do not store
|
||||
* the host TNodes for child component views because they can potentially have several
|
||||
* different host TNodes, depending on where the component is being used. These host
|
||||
* TNodes cannot be shared (due to different indices, etc).
|
||||
* A `TNode` representing the declaration location of this `TView` (not part of this TView).
|
||||
*/
|
||||
node: TViewNode|TElementNode|null;
|
||||
// FIXME(misko): Rename `node` to `declTNode`
|
||||
node: TNode|null;
|
||||
|
||||
// FIXME(misko): Why does `TView` not have `declarationTView` property?
|
||||
|
||||
/** Whether or not this template has been processed in creation mode. */
|
||||
firstCreatePass: boolean;
|
||||
|
@ -1038,7 +1048,7 @@ export interface DebugNode {
|
|||
injector: NodeInjectorDebug;
|
||||
}
|
||||
|
||||
interface NodeInjectorDebug {
|
||||
export interface NodeInjectorDebug {
|
||||
/**
|
||||
* Instance bloom. Does the current injector have a provider with a given bloom mask.
|
||||
*/
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Renderer2} from '../core';
|
||||
import {ViewEncapsulation} from '../metadata/view';
|
||||
import {Renderer2} from '../render/api';
|
||||
import {addToArray, removeFromArray} from '../util/array_utils';
|
||||
import {assertDefined, assertDomNode, assertEqual, assertSame, assertString} from '../util/assert';
|
||||
|
||||
|
@ -20,7 +20,7 @@ import {TElementNode, TNode, TNodeFlags, TNodeType, TProjectionNode, TViewNode,
|
|||
import {unusedValueExportToPlacateAjd as unused3} from './interfaces/projection';
|
||||
import {isProceduralRenderer, ProceduralRenderer3, RElement, Renderer3, RNode, RText, unusedValueExportToPlacateAjd as unused4} from './interfaces/renderer';
|
||||
import {isLContainer, isLView} from './interfaces/type_checks';
|
||||
import {CHILD_HEAD, CLEANUP, DECLARATION_COMPONENT_VIEW, DECLARATION_LCONTAINER, DestroyHookData, FLAGS, HookData, HookFn, HOST, LView, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, T_HOST, TVIEW, TView, unusedValueExportToPlacateAjd as unused5} from './interfaces/view';
|
||||
import {CHILD_HEAD, CLEANUP, DECLARATION_COMPONENT_VIEW, DECLARATION_LCONTAINER, DestroyHookData, FLAGS, HookData, HookFn, HOST, LView, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, T_HOST, TVIEW, TView, TViewType, unusedValueExportToPlacateAjd as unused5} from './interfaces/view';
|
||||
import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert';
|
||||
import {getLViewParent} from './util/view_traversal_utils';
|
||||
import {getNativeByTNode, getNonViewFirstChild, unwrapRNode, updateTransplantedViewCount} from './util/view_utils';
|
||||
|
@ -125,7 +125,7 @@ export function createTextNode(value: string, renderer: Renderer3): RText {
|
|||
}
|
||||
|
||||
/**
|
||||
* Adds or removes all DOM elements associated with a view.
|
||||
* Removes all DOM elements associated with a view.
|
||||
*
|
||||
* Because some root nodes of the view may be containers, we sometimes need
|
||||
* to propagate deeply into the nested containers to remove all elements in the
|
||||
|
@ -133,24 +133,37 @@ export function createTextNode(value: string, renderer: Renderer3): RText {
|
|||
*
|
||||
* @param tView The `TView' of the `LView` from which elements should be added or removed
|
||||
* @param lView The view from which elements should be added or removed
|
||||
* @param insertMode Whether or not elements should be added (if false, removing)
|
||||
*/
|
||||
export function removeViewFromContainer(tView: TView, lView: LView): void {
|
||||
const renderer = lView[RENDERER];
|
||||
applyView(tView, lView, renderer, WalkTNodeTreeAction.Detach, null, null);
|
||||
lView[HOST] = null;
|
||||
lView[T_HOST] = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds all DOM elements associated with a view.
|
||||
*
|
||||
* Because some root nodes of the view may be containers, we sometimes need
|
||||
* to propagate deeply into the nested containers to add all elements in the
|
||||
* views beneath it.
|
||||
*
|
||||
* @param tView The `TView' of the `LView` from which elements should be added or removed
|
||||
* @param parentTNode The `TNode` where the `LView` should be attached to.
|
||||
* @param renderer Current renderer to use for DOM manipulations.
|
||||
* @param lView The view from which elements should be added or removed
|
||||
* @param parentNativeNode The parent `RElement` where it should be inserted into.
|
||||
* @param beforeNode The node before which elements should be added, if insert mode
|
||||
*/
|
||||
export function addRemoveViewFromContainer(
|
||||
tView: TView, lView: LView, insertMode: true, beforeNode: RNode|null): void;
|
||||
export function addRemoveViewFromContainer(
|
||||
tView: TView, lView: LView, insertMode: false, beforeNode: null): void;
|
||||
export function addRemoveViewFromContainer(
|
||||
tView: TView, lView: LView, insertMode: boolean, beforeNode: RNode|null): void {
|
||||
const renderParent = getContainerRenderParent(tView.node as TViewNode, lView);
|
||||
ngDevMode && assertNodeType(tView.node as TNode, TNodeType.View);
|
||||
if (renderParent) {
|
||||
const renderer = lView[RENDERER];
|
||||
const action = insertMode ? WalkTNodeTreeAction.Insert : WalkTNodeTreeAction.Detach;
|
||||
applyView(tView, lView, renderer, action, renderParent, beforeNode);
|
||||
}
|
||||
export function addViewToContainer(
|
||||
tView: TView, parentTNode: TNode, renderer: Renderer3, lView: LView, parentNativeNode: RElement,
|
||||
beforeNode: RNode|null): void {
|
||||
lView[HOST] = parentNativeNode;
|
||||
lView[T_HOST] = parentTNode;
|
||||
applyView(tView, lView, renderer, WalkTNodeTreeAction.Insert, parentNativeNode, beforeNode);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Detach a `LView` from the DOM by detaching its nodes.
|
||||
*
|
||||
|
@ -334,7 +347,7 @@ export function detachView(lContainer: LContainer, removeIndex: number): LView|u
|
|||
lContainer[indexInContainer - 1][NEXT] = viewToDetach[NEXT] as LView;
|
||||
}
|
||||
const removedLView = removeFromArray(lContainer, CONTAINER_HEADER_OFFSET + removeIndex);
|
||||
addRemoveViewFromContainer(viewToDetach[TVIEW], viewToDetach, false, null);
|
||||
removeViewFromContainer(viewToDetach[TVIEW], viewToDetach);
|
||||
|
||||
// notify query that a view has been removed
|
||||
const lQueries = removedLView[QUERIES];
|
||||
|
@ -417,10 +430,8 @@ function cleanUpView(tView: TView, lView: LView): void {
|
|||
|
||||
executeOnDestroys(tView, lView);
|
||||
removeListeners(tView, lView);
|
||||
const hostTNode = lView[T_HOST];
|
||||
// For component views only, the local renderer is destroyed as clean up time.
|
||||
if (hostTNode && hostTNode.type === TNodeType.Element &&
|
||||
isProceduralRenderer(lView[RENDERER])) {
|
||||
// For component views only, the local renderer is destroyed at clean up time.
|
||||
if (lView[TVIEW].type === TViewType.Component && isProceduralRenderer(lView[RENDERER])) {
|
||||
ngDevMode && ngDevMode.rendererDestroy++;
|
||||
(lView[RENDERER] as ProceduralRenderer3).destroy();
|
||||
}
|
||||
|
@ -530,7 +541,7 @@ function getRenderParent(tView: TView, tNode: TNode, currentView: LView): REleme
|
|||
// or a component view.
|
||||
if (parentTNode == null) {
|
||||
const hostTNode = currentView[T_HOST]!;
|
||||
if (hostTNode.type === TNodeType.View) {
|
||||
if (hostTNode && hostTNode.type === TNodeType.View) {
|
||||
// We are inserting a root element of an embedded view We might delay insertion of children
|
||||
// for a given view if it is disconnected. This might happen for 2 main reasons:
|
||||
// - view is not inserted into any container(view was created but not inserted yet)
|
||||
|
@ -542,7 +553,6 @@ function getRenderParent(tView: TView, tNode: TNode, currentView: LView): REleme
|
|||
} else {
|
||||
// We are inserting a root element of the component view into the component host element and
|
||||
// it should always be eager.
|
||||
ngDevMode && assertNodeOfPossibleTypes(hostTNode, [TNodeType.Element]);
|
||||
return currentView[HOST];
|
||||
}
|
||||
} else {
|
||||
|
@ -817,15 +827,19 @@ function applyNodes(
|
|||
* @param lView The LView which needs to be inserted, detached, destroyed.
|
||||
* @param renderer Renderer to use
|
||||
* @param action action to perform (insert, detach, destroy)
|
||||
* @param renderParent parent DOM element for insertion/removal.
|
||||
* @param renderParent parent DOM element for insertion (Removal does not need it).
|
||||
* @param beforeNode Before which node the insertions should happen.
|
||||
*/
|
||||
function applyView(
|
||||
tView: TView, lView: LView, renderer: Renderer3, action: WalkTNodeTreeAction.Destroy,
|
||||
renderParent: null, beforeNode: null): void;
|
||||
function applyView(
|
||||
tView: TView, lView: LView, renderer: Renderer3, action: WalkTNodeTreeAction,
|
||||
renderParent: RElement|null, beforeNode: RNode|null) {
|
||||
ngDevMode && assertNodeType(tView.node!, TNodeType.View);
|
||||
const viewRootTNode: TNode|null = getNonViewFirstChild(tView);
|
||||
applyNodes(renderer, action, viewRootTNode, lView, renderParent, beforeNode, false);
|
||||
renderParent: RElement|null, beforeNode: RNode|null): void;
|
||||
function applyView(
|
||||
tView: TView, lView: LView, renderer: Renderer3, action: WalkTNodeTreeAction,
|
||||
renderParent: RElement|null, beforeNode: RNode|null): void {
|
||||
applyNodes(renderer, action, tView.firstChild, lView, renderParent, beforeNode, false);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,51 +0,0 @@
|
|||
/**
|
||||
* @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 {RelativeInjectorLocation} from './interfaces/injector';
|
||||
import {TContainerNode, TElementNode, TNode} from './interfaces/node';
|
||||
import {DECLARATION_VIEW, LView, T_HOST} from './interfaces/view';
|
||||
import {getParentInjectorViewOffset} from './util/injector_utils';
|
||||
|
||||
/**
|
||||
* If `startTNode.parent` exists and has an injector, returns TNode for that injector.
|
||||
* Otherwise, unwraps a parent injector location number to find the view offset from the current
|
||||
* injector, then walks up the declaration view tree until the TNode of the parent injector is
|
||||
* found.
|
||||
*
|
||||
* @param location The location of the parent injector, which contains the view offset
|
||||
* @param startView The LView instance from which to start walking up the view tree
|
||||
* @param startTNode The TNode instance of the starting element
|
||||
* @returns The TNode of the parent injector
|
||||
*/
|
||||
export function getParentInjectorTNode(
|
||||
location: RelativeInjectorLocation, startView: LView, startTNode: TNode): TElementNode|
|
||||
TContainerNode|null {
|
||||
// If there is an injector on the parent TNode, retrieve the TNode for that injector.
|
||||
if (startTNode.parent && startTNode.parent.injectorIndex !== -1) {
|
||||
// view offset is 0
|
||||
const injectorIndex = startTNode.parent.injectorIndex;
|
||||
let tNode = startTNode.parent;
|
||||
// If tNode.injectorIndex === tNode.parent.injectorIndex, then the index belongs to a parent
|
||||
// injector.
|
||||
while (tNode.parent != null && injectorIndex == tNode.parent.injectorIndex) {
|
||||
tNode = tNode.parent;
|
||||
}
|
||||
return tNode;
|
||||
}
|
||||
let viewOffset = getParentInjectorViewOffset(location);
|
||||
// view offset is 1
|
||||
let parentView = startView;
|
||||
let parentTNode = startView[T_HOST] as TElementNode;
|
||||
// view offset is superior to 1
|
||||
while (viewOffset > 1) {
|
||||
parentView = parentView[DECLARATION_VIEW]!;
|
||||
parentTNode = parentView[T_HOST] as TElementNode;
|
||||
viewOffset--;
|
||||
}
|
||||
return parentTNode;
|
||||
}
|
|
@ -48,13 +48,17 @@ export function ɵɵpipe(index: number, pipeName: string): any {
|
|||
|
||||
const pipeFactory = pipeDef.factory || (pipeDef.factory = getFactoryDef(pipeDef.type, true));
|
||||
const previousInjectImplementation = setInjectImplementation(ɵɵdirectiveInject);
|
||||
let pipeInstance: any;
|
||||
|
||||
// DI for pipes is supposed to behave like directives when placed on a component
|
||||
// host node, which means that we have to disable access to `viewProviders`.
|
||||
const previousIncludeViewProviders = setIncludeViewProviders(false);
|
||||
const pipeInstance = pipeFactory();
|
||||
setIncludeViewProviders(previousIncludeViewProviders);
|
||||
setInjectImplementation(previousInjectImplementation);
|
||||
try {
|
||||
// DI for pipes is supposed to behave like directives when placed on a component
|
||||
// host node, which means that we have to disable access to `viewProviders`.
|
||||
const previousIncludeViewProviders = setIncludeViewProviders(false);
|
||||
pipeInstance = pipeFactory();
|
||||
setIncludeViewProviders(previousIncludeViewProviders);
|
||||
} finally {
|
||||
setInjectImplementation(previousInjectImplementation);
|
||||
}
|
||||
store(tView, getLView(), index, pipeInstance);
|
||||
return pipeInstance;
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
*/
|
||||
|
||||
import {Injector} from '../../di/injector';
|
||||
import {assertEqual} from '../../util/assert';
|
||||
import {assertLView} from '../assert';
|
||||
import {discoverLocalRefs, getComponentAtNodeIndex, getDirectivesAtNodeIndex, getLContext} from '../context_discovery';
|
||||
import {NodeInjector} from '../di';
|
||||
|
@ -15,7 +16,7 @@ import {LContext} from '../interfaces/context';
|
|||
import {DirectiveDef} from '../interfaces/definition';
|
||||
import {TElementNode, TNode, TNodeProviderIndexes} from '../interfaces/node';
|
||||
import {isLView} from '../interfaces/type_checks';
|
||||
import {CLEANUP, CONTEXT, DebugNode, FLAGS, HEADER_OFFSET, HOST, LView, LViewFlags, T_HOST, TVIEW} from '../interfaces/view';
|
||||
import {CLEANUP, CONTEXT, DebugNode, FLAGS, HEADER_OFFSET, HOST, LView, LViewFlags, T_HOST, TVIEW, TViewType} from '../interfaces/view';
|
||||
|
||||
import {stringifyForError} from './misc_utils';
|
||||
import {getLViewParent, getRootContext} from './view_traversal_utils';
|
||||
|
@ -101,8 +102,7 @@ export function getOwningComponent<T>(elementOrDir: Element|{}): T|null {
|
|||
let lView = context.lView;
|
||||
let parent: LView|null;
|
||||
ngDevMode && assertLView(lView);
|
||||
while (lView[HOST] === null && (parent = getLViewParent(lView)!)) {
|
||||
// As long as lView[HOST] is null we know we are part of sub-template such as `*ngIf`
|
||||
while (lView[TVIEW].type === TViewType.Embedded && (parent = getLViewParent(lView)!)) {
|
||||
lView = parent;
|
||||
}
|
||||
return lView[FLAGS] & LViewFlags.IsRoot ? null : lView[CONTEXT] as T;
|
||||
|
@ -389,6 +389,8 @@ export function getDebugNode(element: Element): DebugNode|null {
|
|||
// data. In this situation the TNode is not accessed at the same spot.
|
||||
const tNode = isLView(valueInLView) ? (valueInLView[T_HOST] as TNode) :
|
||||
getTNode(lView[TVIEW], nodeIndex - HEADER_OFFSET);
|
||||
ngDevMode &&
|
||||
assertEqual(tNode.index, nodeIndex, 'Expecting that TNode at index is same as index');
|
||||
debugNode = buildDebugNode(tNode, lView);
|
||||
}
|
||||
|
||||
|
|
|
@ -5,14 +5,26 @@
|
|||
* 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 {assertGreaterThan, assertNotEqual, assertNumber} from '../../util/assert';
|
||||
import {NO_PARENT_INJECTOR, RelativeInjectorLocation, RelativeInjectorLocationFlags} from '../interfaces/injector';
|
||||
import {DECLARATION_VIEW, LView} from '../interfaces/view';
|
||||
import {DECLARATION_VIEW, HEADER_OFFSET, LView} from '../interfaces/view';
|
||||
|
||||
|
||||
/// Parent Injector Utils ///////////////////////////////////////////////////////////////
|
||||
export function hasParentInjector(parentLocation: RelativeInjectorLocation): boolean {
|
||||
return parentLocation !== NO_PARENT_INJECTOR;
|
||||
}
|
||||
|
||||
export function getParentInjectorIndex(parentLocation: RelativeInjectorLocation): number {
|
||||
ngDevMode && assertNumber(parentLocation, 'Number expected');
|
||||
ngDevMode && assertNotEqual(parentLocation as any, -1, 'Not a valid state.');
|
||||
const parentInjectorIndex =
|
||||
(parentLocation as any as number) & RelativeInjectorLocationFlags.InjectorIndexMask;
|
||||
ngDevMode &&
|
||||
assertGreaterThan(
|
||||
parentInjectorIndex, HEADER_OFFSET,
|
||||
'Parent injector must be pointing past HEADER_OFFSET.');
|
||||
return (parentLocation as any as number) & RelativeInjectorLocationFlags.InjectorIndexMask;
|
||||
}
|
||||
|
||||
|
|
|
@ -105,10 +105,10 @@ export function getNativeByTNode(tNode: TNode, lView: LView): RNode {
|
|||
* @param tNode
|
||||
* @param lView
|
||||
*/
|
||||
export function getNativeByTNodeOrNull(tNode: TNode, lView: LView): RNode|null {
|
||||
const index = tNode.index;
|
||||
export function getNativeByTNodeOrNull(tNode: TNode|null, lView: LView): RNode|null {
|
||||
const index = tNode === null ? -1 : tNode.index;
|
||||
if (index !== -1) {
|
||||
ngDevMode && assertTNodeForLView(tNode, lView);
|
||||
ngDevMode && assertTNodeForLView(tNode!, lView);
|
||||
const node: RNode|null = unwrapRNode(lView[index]);
|
||||
ngDevMode && node !== null && !isProceduralRenderer(lView[RENDERER]) && assertDomNode(node);
|
||||
return node;
|
||||
|
@ -216,6 +216,7 @@ export function updateTransplantedViewCount(lContainer: LContainer, amount: 1|-
|
|||
* crashes on it, so we unwrap it.
|
||||
*/
|
||||
export function getNonViewFirstChild(tView: TView): TNode|null {
|
||||
// FIXME(misko): Delete me! (as TNodeType.View no longer exists)
|
||||
const firstChild = tView.firstChild;
|
||||
return firstChild === null ? null :
|
||||
(firstChild.type === TNodeType.View ? firstChild.child : firstChild);
|
||||
|
|
|
@ -6,33 +6,33 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ChangeDetectorRef as ViewEngine_ChangeDetectorRef} from '../change_detection/change_detector_ref';
|
||||
import {Injector} from '../di/injector';
|
||||
import {ComponentFactory as viewEngine_ComponentFactory, ComponentRef as viewEngine_ComponentRef} from '../linker/component_factory';
|
||||
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';
|
||||
import {ViewContainerRef as ViewEngine_ViewContainerRef} from '../linker/view_container_ref';
|
||||
import {EmbeddedViewRef as viewEngine_EmbeddedViewRef, ViewRef as viewEngine_ViewRef} from '../linker/view_ref';
|
||||
import {Renderer2} from '../render/api';
|
||||
import {addToArray, removeFromArray} from '../util/array_utils';
|
||||
import {assertDefined, assertEqual, assertGreaterThan, assertLessThan} from '../util/assert';
|
||||
import { ChangeDetectorRef as ViewEngine_ChangeDetectorRef } from '../change_detection/change_detector_ref';
|
||||
import { Injector } from '../di/injector';
|
||||
import { ComponentFactory as viewEngine_ComponentFactory, ComponentRef as viewEngine_ComponentRef } from '../linker/component_factory';
|
||||
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';
|
||||
import { ViewContainerRef as ViewEngine_ViewContainerRef } from '../linker/view_container_ref';
|
||||
import { EmbeddedViewRef as viewEngine_EmbeddedViewRef, ViewRef as viewEngine_ViewRef } from '../linker/view_ref';
|
||||
import { Renderer2 } from '../render/api';
|
||||
import { addToArray, removeFromArray } from '../util/array_utils';
|
||||
import { assertDefined, assertEqual, assertGreaterThan, assertLessThan } from '../util/assert';
|
||||
import { assertLContainer, assertNodeInjector } from './assert';
|
||||
import { getParentInjectorLocation, NodeInjector } from './di';
|
||||
import { addToViewTree, createLContainer, createLView, renderView } from './instructions/shared';
|
||||
import { CONTAINER_HEADER_OFFSET, LContainer, NATIVE, VIEW_REFS } from './interfaces/container';
|
||||
import { TNODE } from './interfaces/injector';
|
||||
import { TContainerNode, TDirectiveHostNode, TElementContainerNode, TElementNode, TNode, TNodeType } from './interfaces/node';
|
||||
import { isProceduralRenderer, RComment, RElement } from './interfaces/renderer';
|
||||
import { isComponentHost, isLContainer, isLView, isRootView } from './interfaces/type_checks';
|
||||
import { DECLARATION_COMPONENT_VIEW, DECLARATION_LCONTAINER, LView, LViewFlags, PARENT, QUERIES, RENDERER, TVIEW, TView, T_HOST } from './interfaces/view';
|
||||
import { assertNodeOfPossibleTypes } from './node_assert';
|
||||
import { addViewToContainer, appendChild, destroyLView, detachView, getBeforeNodeForView, insertView, nativeInsertBefore, nativeNextSibling, nativeParentNode } from './node_manipulation';
|
||||
import { getLView, getPreviousOrParentTNode } from './state';
|
||||
import { getParentInjectorIndex, getParentInjectorView, hasParentInjector } from './util/injector_utils';
|
||||
import { getComponentLViewByIndex, getNativeByTNode, unwrapRNode, viewAttachedToContainer } from './util/view_utils';
|
||||
import { ViewRef } from './view_ref';
|
||||
|
||||
import {assertLContainer} from './assert';
|
||||
import {getParentInjectorLocation, NodeInjector} from './di';
|
||||
import {addToViewTree, createLContainer, createLView, renderView} from './instructions/shared';
|
||||
import {CONTAINER_HEADER_OFFSET, LContainer, VIEW_REFS} from './interfaces/container';
|
||||
import {TContainerNode, TDirectiveHostNode, TElementContainerNode, TElementNode, TNode, TNodeType} from './interfaces/node';
|
||||
import {isProceduralRenderer, RComment, RElement} from './interfaces/renderer';
|
||||
import {isComponentHost, isLContainer, isLView, isRootView} from './interfaces/type_checks';
|
||||
import {DECLARATION_COMPONENT_VIEW, DECLARATION_LCONTAINER, LView, LViewFlags, PARENT, QUERIES, RENDERER, T_HOST, TVIEW, TView} from './interfaces/view';
|
||||
import {assertNodeOfPossibleTypes} from './node_assert';
|
||||
import {addRemoveViewFromContainer, appendChild, destroyLView, detachView, getBeforeNodeForView, insertView, nativeInsertBefore, nativeNextSibling, nativeParentNode} from './node_manipulation';
|
||||
import {getParentInjectorTNode} from './node_util';
|
||||
import {getLView, getPreviousOrParentTNode} from './state';
|
||||
import {getParentInjectorView, hasParentInjector} from './util/injector_utils';
|
||||
import {getComponentLViewByIndex, getNativeByTNode, unwrapRNode, viewAttachedToContainer} from './util/view_utils';
|
||||
import {ViewRef} from './view_ref';
|
||||
|
||||
|
||||
|
||||
|
@ -106,7 +106,7 @@ export function createTemplateRef<T>(
|
|||
const embeddedTView = this._declarationTContainer.tViews as TView;
|
||||
const embeddedLView = createLView(
|
||||
this._declarationView, embeddedTView, context, LViewFlags.CheckAlways, null,
|
||||
embeddedTView.node);
|
||||
embeddedTView.node, null, null, null, null);
|
||||
|
||||
const declarationLContainer = this._declarationView[this._declarationTContainer.index];
|
||||
ngDevMode && assertLContainer(declarationLContainer);
|
||||
|
@ -188,12 +188,15 @@ export function createContainerRef(
|
|||
/** @deprecated No replacement */
|
||||
get parentInjector(): Injector {
|
||||
const parentLocation = getParentInjectorLocation(this._hostTNode, this._hostView);
|
||||
const parentView = getParentInjectorView(parentLocation, this._hostView);
|
||||
const parentTNode = getParentInjectorTNode(parentLocation, this._hostView, this._hostTNode);
|
||||
|
||||
return !hasParentInjector(parentLocation) || parentTNode == null ?
|
||||
new NodeInjector(null, this._hostView) :
|
||||
new NodeInjector(parentTNode, parentView);
|
||||
if (hasParentInjector(parentLocation)) {
|
||||
const parentView = getParentInjectorView(parentLocation, this._hostView);
|
||||
const injectorIndex = getParentInjectorIndex(parentLocation);
|
||||
ngDevMode && assertNodeInjector(parentView, injectorIndex);
|
||||
const parentTNode = parentView[TVIEW].data[injectorIndex + TNODE] as TElementNode;
|
||||
return new NodeInjector(parentTNode, parentView);
|
||||
} else {
|
||||
return new NodeInjector(null, this._hostView);
|
||||
}
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
|
@ -277,14 +280,21 @@ export function createContainerRef(
|
|||
}
|
||||
}
|
||||
|
||||
// Logical operation of adding `LView` to `LContainer`
|
||||
const adjustedIdx = this._adjustIndex(index);
|
||||
insertView(tView, lView, this._lContainer, adjustedIdx);
|
||||
const lContainer = this._lContainer;
|
||||
insertView(tView, lView, lContainer, adjustedIdx);
|
||||
|
||||
const beforeNode = getBeforeNodeForView(adjustedIdx, this._lContainer);
|
||||
addRemoveViewFromContainer(tView, lView, true, beforeNode);
|
||||
// Physical operation of adding the DOM nodes.
|
||||
const beforeNode = getBeforeNodeForView(adjustedIdx, lContainer);
|
||||
const renderer = lView[RENDERER];
|
||||
const renderParent = nativeParentNode(renderer, lContainer[NATIVE] as RElement | RComment);
|
||||
if (renderParent !== null) {
|
||||
addViewToContainer(tView, lContainer[T_HOST], renderer, lView, renderParent, beforeNode);
|
||||
}
|
||||
|
||||
(viewRef as ViewRef<any>).attachToViewContainerRef(this);
|
||||
addToArray(this._lContainer[VIEW_REFS]!, adjustedIdx, viewRef);
|
||||
addToArray(lContainer[VIEW_REFS]!, adjustedIdx, viewRef);
|
||||
|
||||
return viewRef;
|
||||
}
|
||||
|
|
|
@ -35,11 +35,8 @@ export class ViewRef<T> implements viewEngine_EmbeddedViewRef<T>, viewEngine_Int
|
|||
|
||||
get rootNodes(): any[] {
|
||||
const lView = this._lView;
|
||||
if (lView[HOST] == null) {
|
||||
const hostTView = lView[T_HOST] as TViewNode;
|
||||
return collectNativeNodes(lView[TVIEW], lView, hostTView.child, []);
|
||||
}
|
||||
return [];
|
||||
const tView = lView[TVIEW];
|
||||
return collectNativeNodes(tView, lView, tView.firstChild, []);
|
||||
}
|
||||
|
||||
constructor(
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
*/
|
||||
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {Attribute, ChangeDetectorRef, Component, ComponentFactoryResolver, ComponentRef, Directive, ElementRef, EventEmitter, forwardRef, Host, HostBinding, Inject, Injectable, InjectionToken, INJECTOR, Injector, Input, LOCALE_ID, ModuleWithProviders, NgModule, NgZone, Optional, Output, Pipe, PipeTransform, Self, SkipSelf, TemplateRef, ViewChild, ViewContainerRef, ViewRef, ɵDEFAULT_LOCALE_ID as DEFAULT_LOCALE_ID} from '@angular/core';
|
||||
import {Attribute, ChangeDetectorRef, Component, ComponentFactoryResolver, ComponentRef, Directive, ElementRef, EventEmitter, forwardRef, Host, HostBinding, Inject, Injectable, InjectionToken, INJECTOR, Injector, Input, LOCALE_ID, NgModule, NgZone, Optional, Output, Pipe, PipeTransform, Self, SkipSelf, TemplateRef, ViewChild, ViewContainerRef, ViewRef, ɵDEFAULT_LOCALE_ID as DEFAULT_LOCALE_ID} from '@angular/core';
|
||||
import {ɵINJECTOR_SCOPE} from '@angular/core/src/core';
|
||||
import {ViewRef as ViewRefInternal} from '@angular/core/src/render3/view_ref';
|
||||
import {TestBed} from '@angular/core/testing';
|
||||
|
|
|
@ -53,7 +53,7 @@ describe('acceptance integration tests', () => {
|
|||
expect(fixture.nativeElement.innerHTML).toEqual('<h1>Hello, World!</h1>');
|
||||
onlyInIvy('perf counters').expectPerfCounters({
|
||||
tView: 2, // Host view + App
|
||||
tNode: 4, // Host Node + App Node + <span> + #text
|
||||
tNode: 3, // Host Node + <h1> + #text
|
||||
});
|
||||
|
||||
fixture.componentInstance.name = 'New World';
|
||||
|
@ -63,7 +63,7 @@ describe('acceptance integration tests', () => {
|
|||
// Assert that the tView/tNode count does not increase (they are correctly cached)
|
||||
onlyInIvy('perf counters').expectPerfCounters({
|
||||
tView: 2,
|
||||
tNode: 4,
|
||||
tNode: 3,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -797,7 +797,7 @@ describe('styling', () => {
|
|||
|
||||
onlyInIvy('perf counters').expectPerfCounters({
|
||||
rendererSetStyle: 1,
|
||||
tNode: 3,
|
||||
tNode: 2,
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -704,9 +704,6 @@
|
|||
{
|
||||
"name": "addHostBindingsToExpandoInstructions"
|
||||
},
|
||||
{
|
||||
"name": "addRemoveViewFromContainer"
|
||||
},
|
||||
{
|
||||
"name": "addToArray"
|
||||
},
|
||||
|
@ -821,9 +818,6 @@
|
|||
{
|
||||
"name": "createPlatformFactory"
|
||||
},
|
||||
{
|
||||
"name": "createTNode"
|
||||
},
|
||||
{
|
||||
"name": "createTView"
|
||||
},
|
||||
|
@ -974,9 +968,6 @@
|
|||
{
|
||||
"name": "getConstant"
|
||||
},
|
||||
{
|
||||
"name": "getContainerRenderParent"
|
||||
},
|
||||
{
|
||||
"name": "getDOM"
|
||||
},
|
||||
|
@ -1064,9 +1055,6 @@
|
|||
{
|
||||
"name": "getParentInjectorView"
|
||||
},
|
||||
{
|
||||
"name": "getParentInjectorViewOffset"
|
||||
},
|
||||
{
|
||||
"name": "getParentState"
|
||||
},
|
||||
|
|
|
@ -59,9 +59,6 @@
|
|||
{
|
||||
"name": "allocLFrame"
|
||||
},
|
||||
{
|
||||
"name": "appendChild"
|
||||
},
|
||||
{
|
||||
"name": "attachPatchData"
|
||||
},
|
||||
|
@ -110,6 +107,9 @@
|
|||
{
|
||||
"name": "extractPipeDef"
|
||||
},
|
||||
{
|
||||
"name": "getBeforeNodeForView"
|
||||
},
|
||||
{
|
||||
"name": "getCheckNoChangesMode"
|
||||
},
|
||||
|
@ -235,5 +235,8 @@
|
|||
},
|
||||
{
|
||||
"name": "viewAttachedToChangeDetector"
|
||||
},
|
||||
{
|
||||
"name": "ɵɵtext"
|
||||
}
|
||||
]
|
|
@ -926,9 +926,6 @@
|
|||
{
|
||||
"name": "addHostBindingsToExpandoInstructions"
|
||||
},
|
||||
{
|
||||
"name": "addRemoveViewFromContainer"
|
||||
},
|
||||
{
|
||||
"name": "addToArray"
|
||||
},
|
||||
|
@ -1070,9 +1067,6 @@
|
|||
{
|
||||
"name": "createRouterScroller"
|
||||
},
|
||||
{
|
||||
"name": "createTNode"
|
||||
},
|
||||
{
|
||||
"name": "createTView"
|
||||
},
|
||||
|
@ -1286,9 +1280,6 @@
|
|||
{
|
||||
"name": "getConstant"
|
||||
},
|
||||
{
|
||||
"name": "getContainerRenderParent"
|
||||
},
|
||||
{
|
||||
"name": "getCurrentQueryIndex"
|
||||
},
|
||||
|
@ -1394,9 +1385,6 @@
|
|||
{
|
||||
"name": "getParentInjectorView"
|
||||
},
|
||||
{
|
||||
"name": "getParentInjectorViewOffset"
|
||||
},
|
||||
{
|
||||
"name": "getParentState"
|
||||
},
|
||||
|
|
|
@ -179,9 +179,6 @@
|
|||
{
|
||||
"name": "addHostBindingsToExpandoInstructions"
|
||||
},
|
||||
{
|
||||
"name": "addRemoveViewFromContainer"
|
||||
},
|
||||
{
|
||||
"name": "addToArray"
|
||||
},
|
||||
|
@ -260,9 +257,6 @@
|
|||
{
|
||||
"name": "createLView"
|
||||
},
|
||||
{
|
||||
"name": "createTNode"
|
||||
},
|
||||
{
|
||||
"name": "createTView"
|
||||
},
|
||||
|
@ -353,9 +347,6 @@
|
|||
{
|
||||
"name": "getConstant"
|
||||
},
|
||||
{
|
||||
"name": "getContainerRenderParent"
|
||||
},
|
||||
{
|
||||
"name": "getDebugContext"
|
||||
},
|
||||
|
@ -425,9 +416,6 @@
|
|||
{
|
||||
"name": "getParentInjectorView"
|
||||
},
|
||||
{
|
||||
"name": "getParentInjectorViewOffset"
|
||||
},
|
||||
{
|
||||
"name": "getParentState"
|
||||
},
|
||||
|
|
|
@ -226,8 +226,8 @@ describe('di', () => {
|
|||
describe('getOrCreateNodeInjector', () => {
|
||||
it('should handle initial undefined state', () => {
|
||||
const contentView = createLView(
|
||||
null, createTView(TViewType.Component, -1, null, 1, 0, null, null, null, null, null), {},
|
||||
LViewFlags.CheckAlways, null, null, {} as any, {} as any);
|
||||
null, createTView(TViewType.Component, null, null, 1, 0, null, null, null, null, null),
|
||||
{}, LViewFlags.CheckAlways, null, null, {} as any, {} as any, null, null);
|
||||
enterView(contentView);
|
||||
try {
|
||||
const parentTNode = getOrCreateTNode(contentView[TVIEW], 0, TNodeType.Element, null, null);
|
||||
|
|
|
@ -26,7 +26,7 @@ describe('lView_debug', () => {
|
|||
let tNode!: TNodeDebug;
|
||||
let tView!: TView;
|
||||
beforeEach(() => {
|
||||
tView = createTView(TViewType.Component, 0, null, 0, 0, null, null, null, null, null);
|
||||
tView = createTView(TViewType.Component, null, null, 0, 0, null, null, null, null, null);
|
||||
tNode = createTNode(tView, null!, TNodeType.Element, 0, '', null) as TNodeDebug;
|
||||
});
|
||||
afterEach(() => tNode = tView = null!);
|
||||
|
|
|
@ -36,7 +36,7 @@ export function enterViewWithOneDiv() {
|
|||
const vars = 60; // Space for directive expando, template, component + 3 directives if we assume
|
||||
// that each consume 10 slots.
|
||||
const tView = createTView(
|
||||
TViewType.Component, -1, emptyTemplate, consts, vars, null, null, null, null, null);
|
||||
TViewType.Component, null, emptyTemplate, consts, vars, null, null, null, null, null);
|
||||
// Just assume that the expando starts after 10 initial bindings.
|
||||
tView.expandoStartIndex = HEADER_OFFSET + 10;
|
||||
const tNode = tView.firstChild = createTNode(tView, null!, TNodeType.Element, 0, 'div', null);
|
||||
|
|
|
@ -91,7 +91,6 @@ export function isTView(obj: any): obj is TView {
|
|||
}
|
||||
const ShapeOfTView: ShapeOf<TView> = {
|
||||
type: true,
|
||||
id: true,
|
||||
blueprint: true,
|
||||
template: true,
|
||||
viewQuery: true,
|
||||
|
|
|
@ -52,7 +52,7 @@ describe('render3 matchers', () => {
|
|||
});
|
||||
|
||||
describe('matchTView', () => {
|
||||
const tView = createTView(TViewType.Root, 1, null, 2, 3, null, null, null, null, null);
|
||||
const tView = createTView(TViewType.Root, null, null, 2, 3, null, null, null, null, null);
|
||||
it('should match', () => {
|
||||
expect(tView).toEqual(matchTView());
|
||||
expect(tView).toEqual(matchTView({type: TViewType.Root}));
|
||||
|
@ -60,7 +60,7 @@ describe('render3 matchers', () => {
|
|||
});
|
||||
});
|
||||
describe('matchTNode', () => {
|
||||
const tView = createTView(TViewType.Root, 1, null, 2, 3, null, null, null, null, null);
|
||||
const tView = createTView(TViewType.Root, null, null, 2, 3, null, null, null, null, null);
|
||||
const tNode = createTNode(tView, null, TNodeType.Element, 1, 'tagName', []);
|
||||
|
||||
it('should match', () => {
|
||||
|
|
|
@ -75,12 +75,12 @@ function testTemplate(rf: RenderFlags, ctx: any) {
|
|||
}
|
||||
|
||||
const rootLView = createLView(
|
||||
null, createTView(TViewType.Root, -1, null, 0, 0, null, null, null, null, null), {},
|
||||
LViewFlags.IsRoot, null, null);
|
||||
null, createTView(TViewType.Root, null, null, 0, 0, null, null, null, null, null), {},
|
||||
LViewFlags.IsRoot, null, null, null, null, null, null);
|
||||
|
||||
const viewTNode = createTNode(null!, null, TNodeType.View, -1, null, null) as TViewNode;
|
||||
const embeddedTView = createTView(
|
||||
TViewType.Embedded, -1, testTemplate, 21, 10, [Tooltip.ɵdir], null, null, null,
|
||||
TViewType.Embedded, null, testTemplate, 21, 10, [Tooltip.ɵdir], null, null, null,
|
||||
[['position', 'top', 3, 'tooltip']]);
|
||||
|
||||
// create view once so we don't profile the first create pass
|
||||
|
|
|
@ -64,12 +64,12 @@ function testTemplate(rf: RenderFlags, ctx: any) {
|
|||
}
|
||||
|
||||
const rootLView = createLView(
|
||||
null, createTView(TViewType.Root, -1, null, 0, 0, null, null, null, null, null), {},
|
||||
LViewFlags.IsRoot, null, null);
|
||||
null, createTView(TViewType.Root, null, null, 0, 0, null, null, null, null, null), {},
|
||||
LViewFlags.IsRoot, null, null, null, null, null, null);
|
||||
|
||||
const viewTNode = createTNode(null!, null, TNodeType.View, -1, null, null) as TViewNode;
|
||||
const embeddedTView = createTView(
|
||||
TViewType.Embedded, -1, testTemplate, 21, 0, null, null, null, null, [[
|
||||
TViewType.Embedded, null, testTemplate, 21, 0, null, null, null, null, [[
|
||||
'name1', 'value1', 'name2', 'value2', 'name3', 'value3', 'name4', 'value4', 'name5', 'value5'
|
||||
]]);
|
||||
|
||||
|
|
|
@ -66,12 +66,12 @@ function testTemplate(rf: RenderFlags, ctx: any) {
|
|||
}
|
||||
|
||||
const rootLView = createLView(
|
||||
null, createTView(TViewType.Root, -1, null, 0, 0, null, null, null, null, null), {},
|
||||
LViewFlags.IsRoot, null, null);
|
||||
null, createTView(TViewType.Root, null, null, 0, 0, null, null, null, null, null), {},
|
||||
LViewFlags.IsRoot, null, null, null, null, null, null);
|
||||
|
||||
const viewTNode = createTNode(null!, null, TNodeType.View, -1, null, null) as TViewNode;
|
||||
const embeddedTView = createTView(
|
||||
TViewType.Embedded, -1, testTemplate, 11, 0, null, null, null, null, [[3, 'click', 'input']]);
|
||||
TViewType.Embedded, null, testTemplate, 11, 0, null, null, null, null, [[3, 'click', 'input']]);
|
||||
|
||||
// create view once so we don't profile the first create pass
|
||||
createAndRenderLView(rootLView, embeddedTView, viewTNode);
|
||||
|
|
|
@ -59,12 +59,12 @@ function testTemplate(rf: RenderFlags, ctx: any) {
|
|||
}
|
||||
|
||||
const rootLView = createLView(
|
||||
null, createTView(TViewType.Root, -1, null, 0, 0, null, null, null, null, null), {},
|
||||
LViewFlags.IsRoot, null, null);
|
||||
null, createTView(TViewType.Root, null, null, 0, 0, null, null, null, null, null), {},
|
||||
LViewFlags.IsRoot, null, null, null, null, null, null);
|
||||
|
||||
const viewTNode = createTNode(null!, null, TNodeType.View, -1, null, null) as TViewNode;
|
||||
const embeddedTView = createTView(
|
||||
TViewType.Root, -1, testTemplate, 2, 0, [NgIfLike.ɵdir], null, null, null,
|
||||
TViewType.Root, null, testTemplate, 2, 0, [NgIfLike.ɵdir], null, null, null,
|
||||
[['viewManipulation', '']]);
|
||||
|
||||
// create view once so we don't profile first template pass
|
||||
|
|
|
@ -22,7 +22,8 @@ const renderer = rendererFactory.createRenderer(null, null);
|
|||
export function createAndRenderLView(
|
||||
parentLView: LView, tView: TView, hostTNode: TViewNode): LView {
|
||||
const embeddedLView = createLView(
|
||||
parentLView, tView, {}, LViewFlags.CheckAlways, null, hostTNode, rendererFactory, renderer);
|
||||
parentLView, tView, {}, LViewFlags.CheckAlways, null, hostTNode, rendererFactory, renderer,
|
||||
null, null);
|
||||
renderView(tView, embeddedLView, null);
|
||||
return embeddedLView;
|
||||
}
|
||||
|
@ -49,12 +50,12 @@ export function setupTestHarness(
|
|||
embeddedViewContext: any = {}, consts: TAttributes[]|null = null,
|
||||
directiveRegistry: DirectiveDefList|null = null): TestHarness {
|
||||
// Create a root view with a container
|
||||
const hostTView = createTView(TViewType.Root, -1, null, 1, 0, null, null, null, null, consts);
|
||||
const hostTView = createTView(TViewType.Root, null, null, 1, 0, null, null, null, null, consts);
|
||||
const tContainerNode = getOrCreateTNode(hostTView, 0, TNodeType.Container, null, null);
|
||||
const hostNode = renderer.createElement('div');
|
||||
const hostLView = createLView(
|
||||
null, hostTView, {}, LViewFlags.CheckAlways | LViewFlags.IsRoot, hostNode, null,
|
||||
rendererFactory, renderer);
|
||||
rendererFactory, renderer, null, null);
|
||||
const mockRCommentNode = renderer.createComment('');
|
||||
const lContainer =
|
||||
createLContainer(mockRCommentNode, hostLView, mockRCommentNode, tContainerNode);
|
||||
|
@ -63,13 +64,14 @@ export function setupTestHarness(
|
|||
|
||||
// create test embedded views
|
||||
const embeddedTView = createTView(
|
||||
TViewType.Embedded, -1, templateFn, decls, vars, directiveRegistry, null, null, null, consts);
|
||||
TViewType.Embedded, null, templateFn, decls, vars, directiveRegistry, null, null, null,
|
||||
consts);
|
||||
const viewTNode = createTNode(hostTView, null, TNodeType.View, -1, null, null) as TViewNode;
|
||||
|
||||
function createEmbeddedLView(): LView {
|
||||
const embeddedLView = createLView(
|
||||
hostLView, embeddedTView, embeddedViewContext, LViewFlags.CheckAlways, null, viewTNode,
|
||||
rendererFactory, renderer);
|
||||
rendererFactory, renderer, null, null);
|
||||
renderView(embeddedTView, embeddedLView, embeddedViewContext);
|
||||
return embeddedLView;
|
||||
}
|
||||
|
|
|
@ -52,12 +52,12 @@ function testTemplate(rf: RenderFlags, ctx: any) {
|
|||
}
|
||||
|
||||
const rootLView = createLView(
|
||||
null, createTView(TViewType.Root, -1, null, 0, 0, null, null, null, null, null), {},
|
||||
LViewFlags.IsRoot, null, null);
|
||||
null, createTView(TViewType.Root, null, null, 0, 0, null, null, null, null, null), {},
|
||||
LViewFlags.IsRoot, null, null, null, null, null, null);
|
||||
|
||||
const viewTNode = createTNode(null!, null, TNodeType.View, -1, null, null) as TViewNode;
|
||||
const embeddedTView = createTView(
|
||||
TViewType.Embedded, -1, testTemplate, 21, 10, [ToDestroy.ɵdir], null, null, null,
|
||||
TViewType.Embedded, null, testTemplate, 21, 10, [ToDestroy.ɵdir], null, null, null,
|
||||
[['to-destroy']]);
|
||||
|
||||
// create view once so we don't profile the first create pass
|
||||
|
|
|
@ -261,10 +261,10 @@ export function renderTemplate<T>(
|
|||
const renderer = providedRendererFactory.createRenderer(null, null);
|
||||
|
||||
// We need to create a root view so it's possible to look up the host element through its index
|
||||
const tView = createTView(TViewType.Root, -1, null, 1, 0, null, null, null, null, null);
|
||||
const tView = createTView(TViewType.Root, null, null, 1, 0, null, null, null, null, null);
|
||||
const hostLView = createLView(
|
||||
null, tView, {}, LViewFlags.CheckAlways | LViewFlags.IsRoot, null, null,
|
||||
providedRendererFactory, renderer);
|
||||
providedRendererFactory, renderer, null, null);
|
||||
enterView(hostLView);
|
||||
|
||||
const def: ComponentDef<any> = ɵɵdefineComponent({
|
||||
|
@ -282,7 +282,7 @@ export function renderTemplate<T>(
|
|||
hostLView[hostTNode.index] = hostNode;
|
||||
componentView = createLView(
|
||||
hostLView, componentTView, context, LViewFlags.CheckAlways, hostNode, hostTNode,
|
||||
providedRendererFactory, renderer, sanitizer);
|
||||
providedRendererFactory, renderer, sanitizer || null, null);
|
||||
}
|
||||
renderComponentOrTemplate(componentView[TVIEW], componentView, templateFn, context);
|
||||
return componentView;
|
||||
|
|
|
@ -13,7 +13,7 @@ import {TViewType} from '@angular/core/src/render3/interfaces/view';
|
|||
describe('view_utils', () => {
|
||||
it('should verify unwrap methods', () => {
|
||||
const div = document.createElement('div');
|
||||
const tView = createTView(TViewType.Root, 0, null, 0, 0, null, null, null, null, null);
|
||||
const tView = createTView(TViewType.Root, null, null, 0, 0, null, null, null, null, null);
|
||||
const lView = createLView(null, tView, {}, 0, div, null, {} as any, {} as any, null, null);
|
||||
const tNode = createTNode(null!, null, 3, 0, 'div', []);
|
||||
const lContainer = createLContainer(lView, lView, div, tNode);
|
||||
|
|
Loading…
Reference in New Issue