2019-03-15 13:45:08 -07:00
|
|
|
|
/**
|
|
|
|
|
* @license
|
2020-05-19 12:08:49 -07:00
|
|
|
|
* Copyright Google LLC All Rights Reserved.
|
2019-03-15 13:45:08 -07:00
|
|
|
|
*
|
|
|
|
|
* 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
|
|
|
|
|
*/
|
2020-10-12 21:38:06 -07:00
|
|
|
|
import {Injector} from '../../di';
|
|
|
|
|
import {ErrorHandler} from '../../error_handler';
|
|
|
|
|
import {DoCheck, OnChanges, OnInit} from '../../interface/lifecycle_hooks';
|
|
|
|
|
import {CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA, SchemaMetadata} from '../../metadata/schema';
|
|
|
|
|
import {ViewEncapsulation} from '../../metadata/view';
|
|
|
|
|
import {validateAgainstEventAttributes, validateAgainstEventProperties} from '../../sanitization/sanitization';
|
|
|
|
|
import {Sanitizer} from '../../sanitization/sanitizer';
|
2020-10-16 12:48:07 -07:00
|
|
|
|
import {assertDefined, assertDomNode, assertEqual, assertGreaterThanOrEqual, assertIndexInRange, assertNotEqual, assertNotSame, assertSame, assertString} from '../../util/assert';
|
2020-10-12 21:38:06 -07:00
|
|
|
|
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, assertFirstUpdatePass, assertLContainer, assertLView, assertTNodeForLView, assertTNodeForTView} from '../assert';
|
|
|
|
|
import {attachPatchData} from '../context_discovery';
|
|
|
|
|
import {getFactoryDef} from '../definition';
|
|
|
|
|
import {diPublicInInjector, getNodeInjectable, getOrCreateNodeInjectorForNode} from '../di';
|
|
|
|
|
import {throwMultipleComponentError} from '../errors';
|
|
|
|
|
import {executeCheckHooks, executeInitAndCheckHooks, incrementInitPhaseFlags} from '../hooks';
|
|
|
|
|
import {CONTAINER_HEADER_OFFSET, HAS_TRANSPLANTED_VIEWS, LContainer, MOVED_VIEWS} from '../interfaces/container';
|
2020-10-15 17:04:41 -07:00
|
|
|
|
import {ComponentDef, ComponentTemplate, DirectiveDef, DirectiveDefListOrFactory, HostBindingsFunction, PipeDefListOrFactory, RenderFlags, ViewQueriesFunction} from '../interfaces/definition';
|
|
|
|
|
import {NodeInjectorFactory} from '../interfaces/injector';
|
|
|
|
|
import {AttributeMarker, InitialInputData, InitialInputs, LocalRefExtractor, PropertyAliases, PropertyAliasValue, TAttributes, TConstantsOrFactory, TContainerNode, TDirectiveHostNode, TElementContainerNode, TElementNode, TIcuContainerNode, TNode, TNodeFlags, TNodeType, TProjectionNode} from '../interfaces/node';
|
2020-10-12 21:38:06 -07:00
|
|
|
|
import {isProceduralRenderer, RComment, RElement, Renderer3, RendererFactory3, RNode, RText} from '../interfaces/renderer';
|
|
|
|
|
import {SanitizerFn} from '../interfaces/sanitization';
|
|
|
|
|
import {isComponentDef, isComponentHost, isContentQueryHost, isRootView} from '../interfaces/type_checks';
|
2020-10-15 17:04:41 -07:00
|
|
|
|
import {CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTEXT, DECLARATION_COMPONENT_VIEW, DECLARATION_VIEW, FLAGS, HEADER_OFFSET, HOST, HostBindingOpCodes, InitPhaseState, INJECTOR, LView, LViewFlags, NEXT, PARENT, RENDERER, RENDERER_FACTORY, RootContext, RootContextFlags, SANITIZER, T_HOST, TData, TRANSPLANTED_VIEWS_TO_REFRESH, TVIEW, TView, TViewType} from '../interfaces/view';
|
2020-10-12 21:38:06 -07:00
|
|
|
|
import {assertPureTNodeType, assertTNodeType} from '../node_assert';
|
|
|
|
|
import {updateTextNode} from '../node_manipulation';
|
|
|
|
|
import {isInlineTemplate, isNodeMatchingSelectorList} from '../node_selector_matcher';
|
|
|
|
|
import {enterView, getBindingsEnabled, getCurrentDirectiveIndex, getCurrentParentTNode, getCurrentTNode, getCurrentTNodePlaceholderOk, getSelectedIndex, isCurrentTNodeParent, isInCheckNoChangesMode, isInI18nBlock, leaveView, setBindingIndex, setBindingRootForHostBindings, setCurrentDirectiveIndex, setCurrentQueryIndex, setCurrentTNode, setIsInCheckNoChangesMode, setSelectedIndex} from '../state';
|
|
|
|
|
import {NO_CHANGE} from '../tokens';
|
|
|
|
|
import {isAnimationProp, mergeHostAttrs} from '../util/attrs_utils';
|
|
|
|
|
import {INTERPOLATION_DELIMITER, renderStringify, stringifyForError} from '../util/misc_utils';
|
|
|
|
|
import {getFirstLContainer, getLViewParent, getNextLContainer} from '../util/view_traversal_utils';
|
|
|
|
|
import {getComponentLViewByIndex, getNativeByIndex, getNativeByTNode, isCreationMode, readPatchedLView, resetPreOrderHookFlags, unwrapLView, updateTransplantedViewCount, viewAttachedToChangeDetector} from '../util/view_utils';
|
|
|
|
|
|
|
|
|
|
import {selectIndexInternal} from './advance';
|
|
|
|
|
import {attachLContainerDebug, attachLViewDebug, cloneToLViewFromTViewBlueprint, cloneToTViewData, LCleanup, LViewBlueprint, MatchesArray, TCleanup, TNodeDebug, TNodeInitialInputs, TNodeLocalNames, TViewComponents, TViewConstructor} from './lview_debug';
|
2019-04-02 16:16:00 -07:00
|
|
|
|
|
2019-04-04 11:41:52 -07:00
|
|
|
|
|
2019-07-09 18:39:10 +02:00
|
|
|
|
|
2019-04-01 15:36:43 -07:00
|
|
|
|
/**
|
|
|
|
|
* A permanent marker promise which signifies that the current CD tree is
|
|
|
|
|
* clean.
|
|
|
|
|
*/
|
2019-05-02 16:44:24 +01:00
|
|
|
|
const _CLEAN_PROMISE = (() => Promise.resolve(null))();
|
2019-04-01 15:36:43 -07:00
|
|
|
|
|
2020-01-09 12:05:40 -08:00
|
|
|
|
/**
|
2020-10-15 17:04:41 -07:00
|
|
|
|
* Invoke `HostBindingsFunction`s for view.
|
2020-01-09 12:05:40 -08:00
|
|
|
|
*
|
2020-10-15 17:04:41 -07:00
|
|
|
|
* This methods executes `TView.hostBindingOpCodes`. It is used to execute the
|
|
|
|
|
* `HostBindingsFunction`s associated with the current `LView`.
|
|
|
|
|
*
|
|
|
|
|
* @param tView Current `TView`.
|
|
|
|
|
* @param lView Current `LView`.
|
2020-01-09 12:05:40 -08:00
|
|
|
|
*/
|
2020-10-15 17:04:41 -07:00
|
|
|
|
export function processHostBindingOpCodes(tView: TView, lView: LView): void {
|
|
|
|
|
const hostBindingOpCodes = tView.hostBindingOpCodes;
|
|
|
|
|
if (hostBindingOpCodes === null) return;
|
2019-04-16 17:17:12 -07:00
|
|
|
|
try {
|
2020-10-15 17:04:41 -07:00
|
|
|
|
for (let i = 0; i < hostBindingOpCodes.length; i++) {
|
|
|
|
|
const opCode = hostBindingOpCodes[i] as number;
|
|
|
|
|
if (opCode < 0) {
|
|
|
|
|
// Negative numbers are element indexes.
|
|
|
|
|
setSelectedIndex(~opCode);
|
|
|
|
|
} else {
|
|
|
|
|
// Positive numbers are NumberTuple which store bindingRootIndex and directiveIndex.
|
|
|
|
|
const directiveIdx = opCode;
|
|
|
|
|
const bindingRootIndx = hostBindingOpCodes[++i] as number;
|
|
|
|
|
const hostBindingFn = hostBindingOpCodes[++i] as HostBindingsFunction<any>;
|
|
|
|
|
setBindingRootForHostBindings(bindingRootIndx, directiveIdx);
|
|
|
|
|
const context = lView[directiveIdx];
|
|
|
|
|
hostBindingFn(RenderFlags.Update, context);
|
2019-04-01 15:36:43 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
2019-04-16 17:17:12 -07:00
|
|
|
|
} finally {
|
2020-01-28 16:26:56 -08:00
|
|
|
|
setSelectedIndex(-1);
|
2019-04-01 15:36:43 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-15 17:04:41 -07:00
|
|
|
|
|
2019-06-07 10:55:48 +02:00
|
|
|
|
/** Refreshes all content queries declared by directives in a given view */
|
2019-04-01 15:36:43 -07:00
|
|
|
|
function refreshContentQueries(tView: TView, lView: LView): void {
|
2019-06-07 10:55:48 +02:00
|
|
|
|
const contentQueries = tView.contentQueries;
|
|
|
|
|
if (contentQueries !== null) {
|
|
|
|
|
for (let i = 0; i < contentQueries.length; i += 2) {
|
|
|
|
|
const queryStartIdx = contentQueries[i];
|
|
|
|
|
const directiveDefIdx = contentQueries[i + 1];
|
|
|
|
|
if (directiveDefIdx !== -1) {
|
|
|
|
|
const directiveDef = tView.data[directiveDefIdx] as DirectiveDef<any>;
|
2020-10-15 17:04:41 -07:00
|
|
|
|
ngDevMode && assertDefined(directiveDef, 'DirectiveDef not found.');
|
2019-06-07 10:55:48 +02:00
|
|
|
|
ngDevMode &&
|
|
|
|
|
assertDefined(directiveDef.contentQueries, 'contentQueries function should be defined');
|
|
|
|
|
setCurrentQueryIndex(queryStartIdx);
|
fix(core): ngOnDestroy on multi providers called with incorrect context (#35840)
Currently destroy hooks are stored in memory as `[1, hook, 5, hook]` where
the numbers represent the index at which to find the context and `hook` is
the function to be invoked. This breaks down for `multi` providers,
because the value at the index will be an array of providers, resulting in
the hook being invoked with an array of all the multi provider values,
rather than the provider that was destroyed. In ViewEngine `ngOnDestroy`
wasn't being called for `multi` providers at all.
These changes fix the issue by changing the structure of the destroy hooks to `[1, hook, 5, [0, hook, 3, hook]]` where the indexes inside the inner array point to the provider inside of the multi provider array. Note that this is slightly different from the original design which called for the structure to be `[1, hook, 5, [hook, hook]`, because in the process of implementing it, I realized that we wouldn't get passing the correct context if only some of the `multi` providers have `ngOnDestroy` and others don't.
I've run the newly-added `view_destroy_hooks` benchmark against these changes and compared it to master. The difference seems to be insignificant (between 1% and 2% slower).
Fixes #35231.
PR Close #35840
2020-03-03 21:05:26 +01:00
|
|
|
|
directiveDef.contentQueries!(RenderFlags.Update, lView[directiveDefIdx], directiveDefIdx);
|
2019-06-07 10:55:48 +02:00
|
|
|
|
}
|
2019-04-01 15:36:43 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-02 16:43:10 +02:00
|
|
|
|
/** Refreshes child components in the current view (update mode). */
|
2019-07-25 10:44:41 +02:00
|
|
|
|
function refreshChildComponents(hostLView: LView, components: number[]): void {
|
|
|
|
|
for (let i = 0; i < components.length; i++) {
|
2019-08-02 16:43:10 +02:00
|
|
|
|
refreshComponent(hostLView, components[i]);
|
2019-04-01 15:36:43 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-02 16:43:10 +02:00
|
|
|
|
/** Renders child components in the current view (creation mode). */
|
|
|
|
|
function renderChildComponents(hostLView: LView, components: number[]): void {
|
|
|
|
|
for (let i = 0; i < components.length; i++) {
|
|
|
|
|
renderComponent(hostLView, components[i]);
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-04-01 15:36:43 -07:00
|
|
|
|
|
|
|
|
|
export function createLView<T>(
|
fix(core): ngOnDestroy on multi providers called with incorrect context (#35840)
Currently destroy hooks are stored in memory as `[1, hook, 5, hook]` where
the numbers represent the index at which to find the context and `hook` is
the function to be invoked. This breaks down for `multi` providers,
because the value at the index will be an array of providers, resulting in
the hook being invoked with an array of all the multi provider values,
rather than the provider that was destroyed. In ViewEngine `ngOnDestroy`
wasn't being called for `multi` providers at all.
These changes fix the issue by changing the structure of the destroy hooks to `[1, hook, 5, [0, hook, 3, hook]]` where the indexes inside the inner array point to the provider inside of the multi provider array. Note that this is slightly different from the original design which called for the structure to be `[1, hook, 5, [hook, hook]`, because in the process of implementing it, I realized that we wouldn't get passing the correct context if only some of the `multi` providers have `ngOnDestroy` and others don't.
I've run the newly-added `view_destroy_hooks` benchmark against these changes and compared it to master. The difference seems to be insignificant (between 1% and 2% slower).
Fixes #35231.
PR Close #35840
2020-03-03 21:05:26 +01:00
|
|
|
|
parentLView: LView|null, tView: TView, context: T|null, flags: LViewFlags, host: RElement|null,
|
2020-09-14 11:21:15 -07:00
|
|
|
|
tHostNode: TNode|null, rendererFactory: RendererFactory3|null, renderer: Renderer3|null,
|
|
|
|
|
sanitizer: Sanitizer|null, injector: Injector|null): LView {
|
2019-10-28 12:08:17 -07:00
|
|
|
|
const lView =
|
|
|
|
|
ngDevMode ? cloneToLViewFromTViewBlueprint(tView) : tView.blueprint.slice() as LView;
|
2019-04-01 15:36:43 -07:00
|
|
|
|
lView[HOST] = host;
|
|
|
|
|
lView[FLAGS] = flags | LViewFlags.CreationMode | LViewFlags.Attached | LViewFlags.FirstLViewPass;
|
|
|
|
|
resetPreOrderHookFlags(lView);
|
2020-09-14 13:14:12 -07:00
|
|
|
|
ngDevMode && tView.declTNode && parentLView && assertTNodeForLView(tView.declTNode, parentLView);
|
2019-04-01 15:36:43 -07:00
|
|
|
|
lView[PARENT] = lView[DECLARATION_VIEW] = parentLView;
|
|
|
|
|
lView[CONTEXT] = context;
|
fix(core): ngOnDestroy on multi providers called with incorrect context (#35840)
Currently destroy hooks are stored in memory as `[1, hook, 5, hook]` where
the numbers represent the index at which to find the context and `hook` is
the function to be invoked. This breaks down for `multi` providers,
because the value at the index will be an array of providers, resulting in
the hook being invoked with an array of all the multi provider values,
rather than the provider that was destroyed. In ViewEngine `ngOnDestroy`
wasn't being called for `multi` providers at all.
These changes fix the issue by changing the structure of the destroy hooks to `[1, hook, 5, [0, hook, 3, hook]]` where the indexes inside the inner array point to the provider inside of the multi provider array. Note that this is slightly different from the original design which called for the structure to be `[1, hook, 5, [hook, hook]`, because in the process of implementing it, I realized that we wouldn't get passing the correct context if only some of the `multi` providers have `ngOnDestroy` and others don't.
I've run the newly-added `view_destroy_hooks` benchmark against these changes and compared it to master. The difference seems to be insignificant (between 1% and 2% slower).
Fixes #35231.
PR Close #35840
2020-03-03 21:05:26 +01:00
|
|
|
|
lView[RENDERER_FACTORY] = (rendererFactory || parentLView && parentLView[RENDERER_FACTORY])!;
|
2019-04-01 15:36:43 -07:00
|
|
|
|
ngDevMode && assertDefined(lView[RENDERER_FACTORY], 'RendererFactory is required');
|
fix(core): ngOnDestroy on multi providers called with incorrect context (#35840)
Currently destroy hooks are stored in memory as `[1, hook, 5, hook]` where
the numbers represent the index at which to find the context and `hook` is
the function to be invoked. This breaks down for `multi` providers,
because the value at the index will be an array of providers, resulting in
the hook being invoked with an array of all the multi provider values,
rather than the provider that was destroyed. In ViewEngine `ngOnDestroy`
wasn't being called for `multi` providers at all.
These changes fix the issue by changing the structure of the destroy hooks to `[1, hook, 5, [0, hook, 3, hook]]` where the indexes inside the inner array point to the provider inside of the multi provider array. Note that this is slightly different from the original design which called for the structure to be `[1, hook, 5, [hook, hook]`, because in the process of implementing it, I realized that we wouldn't get passing the correct context if only some of the `multi` providers have `ngOnDestroy` and others don't.
I've run the newly-added `view_destroy_hooks` benchmark against these changes and compared it to master. The difference seems to be insignificant (between 1% and 2% slower).
Fixes #35231.
PR Close #35840
2020-03-03 21:05:26 +01:00
|
|
|
|
lView[RENDERER] = (renderer || parentLView && parentLView[RENDERER])!;
|
2019-04-01 15:36:43 -07:00
|
|
|
|
ngDevMode && assertDefined(lView[RENDERER], 'Renderer is required');
|
fix(core): ngOnDestroy on multi providers called with incorrect context (#35840)
Currently destroy hooks are stored in memory as `[1, hook, 5, hook]` where
the numbers represent the index at which to find the context and `hook` is
the function to be invoked. This breaks down for `multi` providers,
because the value at the index will be an array of providers, resulting in
the hook being invoked with an array of all the multi provider values,
rather than the provider that was destroyed. In ViewEngine `ngOnDestroy`
wasn't being called for `multi` providers at all.
These changes fix the issue by changing the structure of the destroy hooks to `[1, hook, 5, [0, hook, 3, hook]]` where the indexes inside the inner array point to the provider inside of the multi provider array. Note that this is slightly different from the original design which called for the structure to be `[1, hook, 5, [hook, hook]`, because in the process of implementing it, I realized that we wouldn't get passing the correct context if only some of the `multi` providers have `ngOnDestroy` and others don't.
I've run the newly-added `view_destroy_hooks` benchmark against these changes and compared it to master. The difference seems to be insignificant (between 1% and 2% slower).
Fixes #35231.
PR Close #35840
2020-03-03 21:05:26 +01:00
|
|
|
|
lView[SANITIZER] = sanitizer || parentLView && parentLView[SANITIZER] || null!;
|
2019-04-01 15:36:43 -07:00
|
|
|
|
lView[INJECTOR as any] = injector || parentLView && parentLView[INJECTOR] || null;
|
|
|
|
|
lView[T_HOST] = tHostNode;
|
fix(core): ngOnDestroy on multi providers called with incorrect context (#35840)
Currently destroy hooks are stored in memory as `[1, hook, 5, hook]` where
the numbers represent the index at which to find the context and `hook` is
the function to be invoked. This breaks down for `multi` providers,
because the value at the index will be an array of providers, resulting in
the hook being invoked with an array of all the multi provider values,
rather than the provider that was destroyed. In ViewEngine `ngOnDestroy`
wasn't being called for `multi` providers at all.
These changes fix the issue by changing the structure of the destroy hooks to `[1, hook, 5, [0, hook, 3, hook]]` where the indexes inside the inner array point to the provider inside of the multi provider array. Note that this is slightly different from the original design which called for the structure to be `[1, hook, 5, [hook, hook]`, because in the process of implementing it, I realized that we wouldn't get passing the correct context if only some of the `multi` providers have `ngOnDestroy` and others don't.
I've run the newly-added `view_destroy_hooks` benchmark against these changes and compared it to master. The difference seems to be insignificant (between 1% and 2% slower).
Fixes #35231.
PR Close #35840
2020-03-03 21:05:26 +01:00
|
|
|
|
ngDevMode &&
|
|
|
|
|
assertEqual(
|
|
|
|
|
tView.type == TViewType.Embedded ? parentLView !== null : true, true,
|
|
|
|
|
'Embedded views must have parentLView');
|
2019-11-13 16:22:55 -08:00
|
|
|
|
lView[DECLARATION_COMPONENT_VIEW] =
|
fix(core): ngOnDestroy on multi providers called with incorrect context (#35840)
Currently destroy hooks are stored in memory as `[1, hook, 5, hook]` where
the numbers represent the index at which to find the context and `hook` is
the function to be invoked. This breaks down for `multi` providers,
because the value at the index will be an array of providers, resulting in
the hook being invoked with an array of all the multi provider values,
rather than the provider that was destroyed. In ViewEngine `ngOnDestroy`
wasn't being called for `multi` providers at all.
These changes fix the issue by changing the structure of the destroy hooks to `[1, hook, 5, [0, hook, 3, hook]]` where the indexes inside the inner array point to the provider inside of the multi provider array. Note that this is slightly different from the original design which called for the structure to be `[1, hook, 5, [hook, hook]`, because in the process of implementing it, I realized that we wouldn't get passing the correct context if only some of the `multi` providers have `ngOnDestroy` and others don't.
I've run the newly-added `view_destroy_hooks` benchmark against these changes and compared it to master. The difference seems to be insignificant (between 1% and 2% slower).
Fixes #35231.
PR Close #35840
2020-03-03 21:05:26 +01:00
|
|
|
|
tView.type == TViewType.Embedded ? parentLView![DECLARATION_COMPONENT_VIEW] : lView;
|
2019-04-01 15:36:43 -07:00
|
|
|
|
ngDevMode && attachLViewDebug(lView);
|
|
|
|
|
return lView;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Create and stores the TNode, and hooks it up to the tree.
|
|
|
|
|
*
|
2019-05-15 22:51:40 -07:00
|
|
|
|
* @param tView The current `TView`.
|
2019-04-01 15:36:43 -07:00
|
|
|
|
* @param index The index at which the TNode should be saved (null if view, since they are not
|
|
|
|
|
* saved).
|
|
|
|
|
* @param type The type of TNode to create
|
|
|
|
|
* @param native The native element for this node, if applicable
|
|
|
|
|
* @param name The tag name of the associated native element, if applicable
|
|
|
|
|
* @param attrs Any attrs for the native element, if applicable
|
|
|
|
|
*/
|
2019-05-15 22:51:40 -07:00
|
|
|
|
export function getOrCreateTNode(
|
2020-10-12 21:38:06 -07:00
|
|
|
|
tView: TView, index: number, type: TNodeType.Element|TNodeType.Text, name: string|null,
|
fix(core): ngOnDestroy on multi providers called with incorrect context (#35840)
Currently destroy hooks are stored in memory as `[1, hook, 5, hook]` where
the numbers represent the index at which to find the context and `hook` is
the function to be invoked. This breaks down for `multi` providers,
because the value at the index will be an array of providers, resulting in
the hook being invoked with an array of all the multi provider values,
rather than the provider that was destroyed. In ViewEngine `ngOnDestroy`
wasn't being called for `multi` providers at all.
These changes fix the issue by changing the structure of the destroy hooks to `[1, hook, 5, [0, hook, 3, hook]]` where the indexes inside the inner array point to the provider inside of the multi provider array. Note that this is slightly different from the original design which called for the structure to be `[1, hook, 5, [hook, hook]`, because in the process of implementing it, I realized that we wouldn't get passing the correct context if only some of the `multi` providers have `ngOnDestroy` and others don't.
I've run the newly-added `view_destroy_hooks` benchmark against these changes and compared it to master. The difference seems to be insignificant (between 1% and 2% slower).
Fixes #35231.
PR Close #35840
2020-03-03 21:05:26 +01:00
|
|
|
|
attrs: TAttributes|null): TElementNode;
|
2019-05-15 22:51:40 -07:00
|
|
|
|
export function getOrCreateTNode(
|
2020-09-04 13:03:46 -07:00
|
|
|
|
tView: TView, index: number, type: TNodeType.Container, name: string|null,
|
|
|
|
|
attrs: TAttributes|null): TContainerNode;
|
2019-05-15 22:51:40 -07:00
|
|
|
|
export function getOrCreateTNode(
|
2020-09-04 13:03:46 -07:00
|
|
|
|
tView: TView, index: number, type: TNodeType.Projection, name: null,
|
fix(core): ngOnDestroy on multi providers called with incorrect context (#35840)
Currently destroy hooks are stored in memory as `[1, hook, 5, hook]` where
the numbers represent the index at which to find the context and `hook` is
the function to be invoked. This breaks down for `multi` providers,
because the value at the index will be an array of providers, resulting in
the hook being invoked with an array of all the multi provider values,
rather than the provider that was destroyed. In ViewEngine `ngOnDestroy`
wasn't being called for `multi` providers at all.
These changes fix the issue by changing the structure of the destroy hooks to `[1, hook, 5, [0, hook, 3, hook]]` where the indexes inside the inner array point to the provider inside of the multi provider array. Note that this is slightly different from the original design which called for the structure to be `[1, hook, 5, [hook, hook]`, because in the process of implementing it, I realized that we wouldn't get passing the correct context if only some of the `multi` providers have `ngOnDestroy` and others don't.
I've run the newly-added `view_destroy_hooks` benchmark against these changes and compared it to master. The difference seems to be insignificant (between 1% and 2% slower).
Fixes #35231.
PR Close #35840
2020-03-03 21:05:26 +01:00
|
|
|
|
attrs: TAttributes|null): TProjectionNode;
|
2019-05-15 22:51:40 -07:00
|
|
|
|
export function getOrCreateTNode(
|
2020-09-04 13:03:46 -07:00
|
|
|
|
tView: TView, index: number, type: TNodeType.ElementContainer, name: string|null,
|
|
|
|
|
attrs: TAttributes|null): TElementContainerNode;
|
2019-05-15 22:51:40 -07:00
|
|
|
|
export function getOrCreateTNode(
|
2020-10-12 21:38:06 -07:00
|
|
|
|
tView: TView, index: number, type: TNodeType.Icu, name: null,
|
fix(core): ngOnDestroy on multi providers called with incorrect context (#35840)
Currently destroy hooks are stored in memory as `[1, hook, 5, hook]` where
the numbers represent the index at which to find the context and `hook` is
the function to be invoked. This breaks down for `multi` providers,
because the value at the index will be an array of providers, resulting in
the hook being invoked with an array of all the multi provider values,
rather than the provider that was destroyed. In ViewEngine `ngOnDestroy`
wasn't being called for `multi` providers at all.
These changes fix the issue by changing the structure of the destroy hooks to `[1, hook, 5, [0, hook, 3, hook]]` where the indexes inside the inner array point to the provider inside of the multi provider array. Note that this is slightly different from the original design which called for the structure to be `[1, hook, 5, [hook, hook]`, because in the process of implementing it, I realized that we wouldn't get passing the correct context if only some of the `multi` providers have `ngOnDestroy` and others don't.
I've run the newly-added `view_destroy_hooks` benchmark against these changes and compared it to master. The difference seems to be insignificant (between 1% and 2% slower).
Fixes #35231.
PR Close #35840
2020-03-03 21:05:26 +01:00
|
|
|
|
attrs: TAttributes|null): TElementContainerNode;
|
2019-05-15 22:51:40 -07:00
|
|
|
|
export function getOrCreateTNode(
|
2020-09-04 13:03:46 -07:00
|
|
|
|
tView: TView, index: number, type: TNodeType, name: string|null, attrs: TAttributes|null):
|
|
|
|
|
TElementNode&TContainerNode&TElementContainerNode&TProjectionNode&TIcuContainerNode {
|
2020-10-13 22:00:43 -07:00
|
|
|
|
ngDevMode && index !== 0 && // 0 are bogus nodes and they are OK. See `createContainerRef` in
|
|
|
|
|
// `view_engine_compatibility` for additional context.
|
|
|
|
|
assertGreaterThanOrEqual(index, HEADER_OFFSET, 'TNodes can\'t be in the LView header.');
|
2019-05-15 22:51:40 -07:00
|
|
|
|
// Keep this function short, so that the VM will inline it.
|
2020-10-12 21:38:06 -07:00
|
|
|
|
ngDevMode && assertPureTNodeType(type);
|
2020-10-13 22:00:43 -07:00
|
|
|
|
let tNode = tView.data[index] as TNode;
|
2020-09-25 15:01:56 -07:00
|
|
|
|
if (tNode === null) {
|
2020-10-13 22:00:43 -07:00
|
|
|
|
tNode = createTNodeAtIndex(tView, index, type, name, attrs);
|
2020-09-25 15:01:56 -07:00
|
|
|
|
if (isInI18nBlock()) {
|
|
|
|
|
// If we are in i18n block then all elements should be pre declared through `Placeholder`
|
|
|
|
|
// See `TNodeType.Placeholder` and `LFrame.inI18n` for more context.
|
|
|
|
|
// If the `TNode` was not pre-declared than it means it was not mentioned which means it was
|
|
|
|
|
// removed, so we mark it as detached.
|
|
|
|
|
tNode.flags |= TNodeFlags.isDetached;
|
|
|
|
|
}
|
2020-10-12 21:38:06 -07:00
|
|
|
|
} else if (tNode.type & TNodeType.Placeholder) {
|
2020-09-25 15:01:56 -07:00
|
|
|
|
tNode.type = type;
|
2020-10-12 16:57:07 -07:00
|
|
|
|
tNode.value = name;
|
2020-09-25 15:01:56 -07:00
|
|
|
|
tNode.attrs = attrs;
|
|
|
|
|
const parent = getCurrentParentTNode();
|
|
|
|
|
tNode.injectorIndex = parent === null ? -1 : parent.injectorIndex;
|
|
|
|
|
ngDevMode && assertTNodeForTView(tNode, tView);
|
2020-10-13 22:00:43 -07:00
|
|
|
|
ngDevMode && assertEqual(index, tNode.index, 'Expecting same index');
|
2020-09-25 15:01:56 -07:00
|
|
|
|
}
|
2020-09-14 13:43:44 -07:00
|
|
|
|
setCurrentTNode(tNode, true);
|
2020-09-14 13:06:05 -07:00
|
|
|
|
return tNode as TElementNode & TContainerNode & TElementContainerNode & TProjectionNode &
|
|
|
|
|
TIcuContainerNode;
|
2019-04-01 15:36:43 -07:00
|
|
|
|
}
|
|
|
|
|
|
2020-09-25 15:01:56 -07:00
|
|
|
|
export function createTNodeAtIndex(
|
2020-10-13 22:00:43 -07:00
|
|
|
|
tView: TView, index: number, type: TNodeType, name: string|null, attrs: TAttributes|null) {
|
2020-09-25 15:01:56 -07:00
|
|
|
|
const currentTNode = getCurrentTNodePlaceholderOk();
|
2020-09-14 13:43:44 -07:00
|
|
|
|
const isParent = isCurrentTNodeParent();
|
|
|
|
|
const parent = isParent ? currentTNode : currentTNode && currentTNode.parent;
|
2020-09-14 13:06:05 -07:00
|
|
|
|
// Parents cannot cross component boundaries because components will be used in multiple places.
|
2020-10-13 22:00:43 -07:00
|
|
|
|
const tNode = tView.data[index] =
|
|
|
|
|
createTNode(tView, parent as TElementNode | TContainerNode, type, index, name, attrs);
|
2019-08-28 15:17:26 +02:00
|
|
|
|
// 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.
|
|
|
|
|
if (tView.firstChild === null) {
|
2019-05-15 22:51:40 -07:00
|
|
|
|
tView.firstChild = tNode;
|
|
|
|
|
}
|
2020-09-14 13:43:44 -07:00
|
|
|
|
if (currentTNode !== null) {
|
2020-09-25 15:01:56 -07:00
|
|
|
|
if (isParent) {
|
|
|
|
|
// FIXME(misko): This logic looks unnecessarily complicated. Could we simplify?
|
|
|
|
|
if (currentTNode.child == null && tNode.parent !== null) {
|
|
|
|
|
// We are in the same view, which means we are adding content node to the parent view.
|
|
|
|
|
currentTNode.child = tNode;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if (currentTNode.next === null) {
|
|
|
|
|
// In the case of i18n the `currentTNode` may already be linked, in which case we don't want
|
|
|
|
|
// to break the links which i18n created.
|
|
|
|
|
currentTNode.next = tNode;
|
|
|
|
|
}
|
2019-05-15 22:51:40 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return tNode;
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-01 15:36:43 -07:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* When elements are created dynamically after a view blueprint is created (e.g. through
|
2020-09-25 15:01:56 -07:00
|
|
|
|
* i18nApply()), we need to adjust the blueprint for future
|
2019-04-01 15:36:43 -07:00
|
|
|
|
* template passes.
|
2019-07-08 14:59:10 +02:00
|
|
|
|
*
|
2020-01-30 14:57:44 -08:00
|
|
|
|
* @param tView `TView` associated with `LView`
|
2020-09-25 15:01:56 -07:00
|
|
|
|
* @param lView The `LView` containing the blueprint to adjust
|
2019-07-08 14:59:10 +02:00
|
|
|
|
* @param numSlotsToAlloc The number of slots to alloc in the LView, should be >0
|
2020-10-15 17:04:41 -07:00
|
|
|
|
* @param initialValue Initial value to store in blueprint
|
2019-04-01 15:36:43 -07:00
|
|
|
|
*/
|
2020-10-15 17:04:41 -07:00
|
|
|
|
export function allocExpando(
|
|
|
|
|
tView: TView, lView: LView, numSlotsToAlloc: number, initialValue: any): number {
|
|
|
|
|
if (numSlotsToAlloc === 0) return -1;
|
2020-09-25 15:01:56 -07:00
|
|
|
|
if (ngDevMode) {
|
2020-10-15 17:04:41 -07:00
|
|
|
|
assertFirstCreatePass(tView);
|
|
|
|
|
assertSame(tView, lView[TVIEW], '`LView` must be associated with `TView`!');
|
2020-09-25 15:01:56 -07:00
|
|
|
|
assertEqual(tView.data.length, lView.length, 'Expecting LView to be same size as TView');
|
|
|
|
|
assertEqual(
|
|
|
|
|
tView.data.length, tView.blueprint.length, 'Expecting Blueprint to be same size as TView');
|
|
|
|
|
assertFirstUpdatePass(tView);
|
|
|
|
|
}
|
|
|
|
|
const allocIdx = lView.length;
|
|
|
|
|
for (let i = 0; i < numSlotsToAlloc; i++) {
|
2020-10-15 17:04:41 -07:00
|
|
|
|
lView.push(initialValue);
|
|
|
|
|
tView.blueprint.push(initialValue);
|
2020-09-25 15:01:56 -07:00
|
|
|
|
tView.data.push(null);
|
2019-04-01 15:36:43 -07:00
|
|
|
|
}
|
2020-09-25 15:01:56 -07:00
|
|
|
|
return allocIdx;
|
2019-04-01 15:36:43 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//////////////////////////
|
|
|
|
|
//// Render
|
|
|
|
|
//////////////////////////
|
|
|
|
|
|
|
|
|
|
/**
|
2019-08-02 16:43:10 +02:00
|
|
|
|
* Processes a view in the creation mode. This includes a number of steps in a specific order:
|
|
|
|
|
* - creating view query functions (if any);
|
|
|
|
|
* - executing a template function in the creation mode;
|
|
|
|
|
* - updating static queries (if any);
|
|
|
|
|
* - creating child components defined in a given view.
|
2019-04-01 15:36:43 -07:00
|
|
|
|
*/
|
2020-01-30 14:57:44 -08:00
|
|
|
|
export function renderView<T>(tView: TView, lView: LView, context: T): void {
|
2019-08-02 16:43:10 +02:00
|
|
|
|
ngDevMode && assertEqual(isCreationMode(lView), true, 'Should be run in creation mode');
|
2020-09-03 22:46:11 -07:00
|
|
|
|
enterView(lView);
|
2019-08-02 11:50:48 +02:00
|
|
|
|
try {
|
2019-08-02 16:43:10 +02:00
|
|
|
|
const viewQuery = tView.viewQuery;
|
|
|
|
|
if (viewQuery !== null) {
|
|
|
|
|
executeViewQueryFn(RenderFlags.Create, viewQuery, context);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Execute a template associated with this view, if it exists. A template function might not be
|
|
|
|
|
// defined for the root component views.
|
2019-08-02 11:50:48 +02:00
|
|
|
|
const templateFn = tView.template;
|
|
|
|
|
if (templateFn !== null) {
|
2020-01-30 14:57:44 -08:00
|
|
|
|
executeTemplate(tView, lView, templateFn, RenderFlags.Create, context);
|
2019-08-02 16:43:10 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// This needs to be set before children are processed to support recursive components.
|
|
|
|
|
// This must be set to false immediately after the first creation run because in an
|
|
|
|
|
// ngFor loop, all the views will be created together before update mode runs and turns
|
2019-11-01 13:06:17 -07:00
|
|
|
|
// off firstCreatePass. If we don't set it here, instances will perform directive
|
2019-08-02 16:43:10 +02:00
|
|
|
|
// matching, etc again and again.
|
2019-11-01 13:06:17 -07:00
|
|
|
|
if (tView.firstCreatePass) {
|
|
|
|
|
tView.firstCreatePass = false;
|
2019-08-02 16:43:10 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// We resolve content queries specifically marked as `static` in creation mode. Dynamic
|
|
|
|
|
// content queries are resolved during change detection (i.e. update mode), after embedded
|
|
|
|
|
// views are refreshed (see block above).
|
|
|
|
|
if (tView.staticContentQueries) {
|
|
|
|
|
refreshContentQueries(tView, lView);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// We must materialize query results before child components are processed
|
|
|
|
|
// in case a child component has projected a container. The LContainer needs
|
|
|
|
|
// to exist so the embedded views are properly attached by the container.
|
|
|
|
|
if (tView.staticViewQueries) {
|
fix(core): ngOnDestroy on multi providers called with incorrect context (#35840)
Currently destroy hooks are stored in memory as `[1, hook, 5, hook]` where
the numbers represent the index at which to find the context and `hook` is
the function to be invoked. This breaks down for `multi` providers,
because the value at the index will be an array of providers, resulting in
the hook being invoked with an array of all the multi provider values,
rather than the provider that was destroyed. In ViewEngine `ngOnDestroy`
wasn't being called for `multi` providers at all.
These changes fix the issue by changing the structure of the destroy hooks to `[1, hook, 5, [0, hook, 3, hook]]` where the indexes inside the inner array point to the provider inside of the multi provider array. Note that this is slightly different from the original design which called for the structure to be `[1, hook, 5, [hook, hook]`, because in the process of implementing it, I realized that we wouldn't get passing the correct context if only some of the `multi` providers have `ngOnDestroy` and others don't.
I've run the newly-added `view_destroy_hooks` benchmark against these changes and compared it to master. The difference seems to be insignificant (between 1% and 2% slower).
Fixes #35231.
PR Close #35840
2020-03-03 21:05:26 +01:00
|
|
|
|
executeViewQueryFn(RenderFlags.Update, tView.viewQuery!, context);
|
2019-08-02 16:43:10 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Render child component views.
|
|
|
|
|
const components = tView.components;
|
|
|
|
|
if (components !== null) {
|
|
|
|
|
renderChildComponents(lView, components);
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-20 20:49:08 +02:00
|
|
|
|
} catch (error) {
|
|
|
|
|
// If we didn't manage to get past the first template pass due to
|
|
|
|
|
// an error, mark the view as corrupted so we can try to recover.
|
|
|
|
|
if (tView.firstCreatePass) {
|
|
|
|
|
tView.incompleteFirstPass = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
throw error;
|
2019-08-02 16:43:10 +02:00
|
|
|
|
} finally {
|
|
|
|
|
lView[FLAGS] &= ~LViewFlags.CreationMode;
|
2019-10-14 13:59:17 -07:00
|
|
|
|
leaveView();
|
2019-08-02 16:43:10 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Processes a view in update mode. This includes a number of steps in a specific order:
|
|
|
|
|
* - executing a template function in update mode;
|
|
|
|
|
* - executing hooks;
|
|
|
|
|
* - refreshing queries;
|
|
|
|
|
* - setting host bindings;
|
|
|
|
|
* - refreshing child (embedded and component) views.
|
|
|
|
|
*/
|
|
|
|
|
export function refreshView<T>(
|
fix(core): ngOnDestroy on multi providers called with incorrect context (#35840)
Currently destroy hooks are stored in memory as `[1, hook, 5, hook]` where
the numbers represent the index at which to find the context and `hook` is
the function to be invoked. This breaks down for `multi` providers,
because the value at the index will be an array of providers, resulting in
the hook being invoked with an array of all the multi provider values,
rather than the provider that was destroyed. In ViewEngine `ngOnDestroy`
wasn't being called for `multi` providers at all.
These changes fix the issue by changing the structure of the destroy hooks to `[1, hook, 5, [0, hook, 3, hook]]` where the indexes inside the inner array point to the provider inside of the multi provider array. Note that this is slightly different from the original design which called for the structure to be `[1, hook, 5, [hook, hook]`, because in the process of implementing it, I realized that we wouldn't get passing the correct context if only some of the `multi` providers have `ngOnDestroy` and others don't.
I've run the newly-added `view_destroy_hooks` benchmark against these changes and compared it to master. The difference seems to be insignificant (between 1% and 2% slower).
Fixes #35231.
PR Close #35840
2020-03-03 21:05:26 +01:00
|
|
|
|
tView: TView, lView: LView, templateFn: ComponentTemplate<{}>|null, context: T) {
|
2019-08-02 16:43:10 +02:00
|
|
|
|
ngDevMode && assertEqual(isCreationMode(lView), false, 'Should be run in update mode');
|
2019-08-14 14:35:36 +02:00
|
|
|
|
const flags = lView[FLAGS];
|
2019-12-04 14:10:50 -08:00
|
|
|
|
if ((flags & LViewFlags.Destroyed) === LViewFlags.Destroyed) return;
|
2020-09-03 22:46:11 -07:00
|
|
|
|
enterView(lView);
|
2020-10-14 15:11:01 -07:00
|
|
|
|
// Check no changes mode is a dev only mode used to verify that bindings have not changed
|
|
|
|
|
// since they were assigned. We do not want to execute lifecycle hooks in that mode.
|
|
|
|
|
const isInCheckNoChangesPass = isInCheckNoChangesMode();
|
2019-08-02 16:43:10 +02:00
|
|
|
|
try {
|
|
|
|
|
resetPreOrderHookFlags(lView);
|
|
|
|
|
|
2019-10-11 12:43:32 -07:00
|
|
|
|
setBindingIndex(tView.bindingStartIndex);
|
2019-08-19 17:42:52 -07:00
|
|
|
|
if (templateFn !== null) {
|
2020-01-30 14:57:44 -08:00
|
|
|
|
executeTemplate(tView, lView, templateFn, RenderFlags.Update, context);
|
2019-08-19 17:42:52 -07:00
|
|
|
|
}
|
|
|
|
|
|
2019-08-14 14:35:36 +02:00
|
|
|
|
const hooksInitPhaseCompleted =
|
|
|
|
|
(flags & LViewFlags.InitPhaseStateMask) === InitPhaseState.InitPhaseCompleted;
|
|
|
|
|
|
2020-01-11 16:23:11 +01:00
|
|
|
|
// execute pre-order hooks (OnInit, OnChanges, DoCheck)
|
2019-08-14 14:35:36 +02:00
|
|
|
|
// PERF WARNING: do NOT extract this to a separate function without running benchmarks
|
2020-10-14 15:11:01 -07:00
|
|
|
|
if (!isInCheckNoChangesPass) {
|
2019-08-14 14:35:36 +02:00
|
|
|
|
if (hooksInitPhaseCompleted) {
|
|
|
|
|
const preOrderCheckHooks = tView.preOrderCheckHooks;
|
|
|
|
|
if (preOrderCheckHooks !== null) {
|
|
|
|
|
executeCheckHooks(lView, preOrderCheckHooks, null);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
const preOrderHooks = tView.preOrderHooks;
|
|
|
|
|
if (preOrderHooks !== null) {
|
|
|
|
|
executeInitAndCheckHooks(lView, preOrderHooks, InitPhaseState.OnInitHooksToBeRun, null);
|
|
|
|
|
}
|
|
|
|
|
incrementInitPhaseFlags(lView, InitPhaseState.OnInitHooksToBeRun);
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-08-02 16:43:10 +02:00
|
|
|
|
|
2020-01-13 15:12:16 -08:00
|
|
|
|
// First mark transplanted views that are declared in this lView as needing a refresh at their
|
|
|
|
|
// insertion points. This is needed to avoid the situation where the template is defined in this
|
|
|
|
|
// `LView` but its declaration appears after the insertion component.
|
|
|
|
|
markTransplantedViewsForRefresh(lView);
|
2020-05-14 14:15:45 -07:00
|
|
|
|
refreshEmbeddedViews(lView);
|
2019-08-02 16:43:10 +02:00
|
|
|
|
|
|
|
|
|
// Content query results must be refreshed before content hooks are called.
|
|
|
|
|
if (tView.contentQueries !== null) {
|
|
|
|
|
refreshContentQueries(tView, lView);
|
2019-04-01 15:36:43 -07:00
|
|
|
|
}
|
2019-08-02 16:43:10 +02:00
|
|
|
|
|
2019-08-14 14:35:36 +02:00
|
|
|
|
// execute content hooks (AfterContentInit, AfterContentChecked)
|
|
|
|
|
// PERF WARNING: do NOT extract this to a separate function without running benchmarks
|
2020-10-14 15:11:01 -07:00
|
|
|
|
if (!isInCheckNoChangesPass) {
|
2019-08-14 14:35:36 +02:00
|
|
|
|
if (hooksInitPhaseCompleted) {
|
|
|
|
|
const contentCheckHooks = tView.contentCheckHooks;
|
|
|
|
|
if (contentCheckHooks !== null) {
|
|
|
|
|
executeCheckHooks(lView, contentCheckHooks);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
const contentHooks = tView.contentHooks;
|
|
|
|
|
if (contentHooks !== null) {
|
|
|
|
|
executeInitAndCheckHooks(
|
|
|
|
|
lView, contentHooks, InitPhaseState.AfterContentInitHooksToBeRun);
|
|
|
|
|
}
|
|
|
|
|
incrementInitPhaseFlags(lView, InitPhaseState.AfterContentInitHooksToBeRun);
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-08-02 16:43:10 +02:00
|
|
|
|
|
2020-10-15 17:04:41 -07:00
|
|
|
|
processHostBindingOpCodes(tView, lView);
|
2019-08-02 16:43:10 +02:00
|
|
|
|
|
|
|
|
|
// Refresh child component views.
|
|
|
|
|
const components = tView.components;
|
|
|
|
|
if (components !== null) {
|
|
|
|
|
refreshChildComponents(lView, components);
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-30 14:39:46 -07:00
|
|
|
|
// View queries must execute after refreshing child components because a template in this view
|
|
|
|
|
// could be inserted in a child component. If the view query executes before child component
|
|
|
|
|
// refresh, the template might not yet be inserted.
|
|
|
|
|
const viewQuery = tView.viewQuery;
|
|
|
|
|
if (viewQuery !== null) {
|
|
|
|
|
executeViewQueryFn(RenderFlags.Update, viewQuery, context);
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-14 14:35:36 +02:00
|
|
|
|
// execute view hooks (AfterViewInit, AfterViewChecked)
|
|
|
|
|
// PERF WARNING: do NOT extract this to a separate function without running benchmarks
|
2020-10-14 15:11:01 -07:00
|
|
|
|
if (!isInCheckNoChangesPass) {
|
2019-08-14 14:35:36 +02:00
|
|
|
|
if (hooksInitPhaseCompleted) {
|
|
|
|
|
const viewCheckHooks = tView.viewCheckHooks;
|
|
|
|
|
if (viewCheckHooks !== null) {
|
|
|
|
|
executeCheckHooks(lView, viewCheckHooks);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
const viewHooks = tView.viewHooks;
|
|
|
|
|
if (viewHooks !== null) {
|
|
|
|
|
executeInitAndCheckHooks(lView, viewHooks, InitPhaseState.AfterViewInitHooksToBeRun);
|
|
|
|
|
}
|
|
|
|
|
incrementInitPhaseFlags(lView, InitPhaseState.AfterViewInitHooksToBeRun);
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-10-29 15:15:09 -07:00
|
|
|
|
if (tView.firstUpdatePass === true) {
|
2019-12-17 15:40:37 -08:00
|
|
|
|
// We need to make sure that we only flip the flag on successful `refreshView` only
|
|
|
|
|
// Don't do this in `finally` block.
|
|
|
|
|
// If we did this in `finally` block then an exception could block the execution of styling
|
|
|
|
|
// instructions which in turn would be unable to insert themselves into the styling linked
|
|
|
|
|
// list. The result of this would be that if the exception would not be throw on subsequent CD
|
|
|
|
|
// the styling would be unable to process it data and reflect to the DOM.
|
2019-10-29 15:15:09 -07:00
|
|
|
|
tView.firstUpdatePass = false;
|
|
|
|
|
}
|
2020-01-11 16:23:11 +01:00
|
|
|
|
|
|
|
|
|
// Do not reset the dirty state when running in check no changes mode. We don't want components
|
|
|
|
|
// to behave differently depending on whether check no changes is enabled or not. For example:
|
|
|
|
|
// Marking an OnPush component as dirty from within the `ngAfterViewInit` hook in order to
|
|
|
|
|
// refresh a `NgClass` binding should work. If we would reset the dirty state in the check
|
|
|
|
|
// no changes cycle, the component would be not be dirty for the next update pass. This would
|
|
|
|
|
// be different in production mode where the component dirty state is not reset.
|
2020-10-14 15:11:01 -07:00
|
|
|
|
if (!isInCheckNoChangesPass) {
|
2020-01-11 16:23:11 +01:00
|
|
|
|
lView[FLAGS] &= ~(LViewFlags.Dirty | LViewFlags.FirstLViewPass);
|
|
|
|
|
}
|
2020-01-13 15:12:16 -08:00
|
|
|
|
if (lView[FLAGS] & LViewFlags.RefreshTransplantedView) {
|
|
|
|
|
lView[FLAGS] &= ~LViewFlags.RefreshTransplantedView;
|
|
|
|
|
updateTransplantedViewCount(lView[PARENT] as LContainer, -1);
|
|
|
|
|
}
|
2019-12-17 15:40:37 -08:00
|
|
|
|
} finally {
|
2020-01-15 16:52:54 -08:00
|
|
|
|
leaveView();
|
2019-04-01 15:36:43 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-05-17 15:37:39 -07:00
|
|
|
|
export function renderComponentOrTemplate<T>(
|
fix(core): ngOnDestroy on multi providers called with incorrect context (#35840)
Currently destroy hooks are stored in memory as `[1, hook, 5, hook]` where
the numbers represent the index at which to find the context and `hook` is
the function to be invoked. This breaks down for `multi` providers,
because the value at the index will be an array of providers, resulting in
the hook being invoked with an array of all the multi provider values,
rather than the provider that was destroyed. In ViewEngine `ngOnDestroy`
wasn't being called for `multi` providers at all.
These changes fix the issue by changing the structure of the destroy hooks to `[1, hook, 5, [0, hook, 3, hook]]` where the indexes inside the inner array point to the provider inside of the multi provider array. Note that this is slightly different from the original design which called for the structure to be `[1, hook, 5, [hook, hook]`, because in the process of implementing it, I realized that we wouldn't get passing the correct context if only some of the `multi` providers have `ngOnDestroy` and others don't.
I've run the newly-added `view_destroy_hooks` benchmark against these changes and compared it to master. The difference seems to be insignificant (between 1% and 2% slower).
Fixes #35231.
PR Close #35840
2020-03-03 21:05:26 +01:00
|
|
|
|
tView: TView, lView: LView, templateFn: ComponentTemplate<{}>|null, context: T) {
|
2020-01-30 14:57:44 -08:00
|
|
|
|
const rendererFactory = lView[RENDERER_FACTORY];
|
2020-10-14 15:11:01 -07:00
|
|
|
|
const normalExecutionPath = !isInCheckNoChangesMode();
|
2020-01-30 14:57:44 -08:00
|
|
|
|
const creationModeIsActive = isCreationMode(lView);
|
2019-04-01 15:36:43 -07:00
|
|
|
|
try {
|
|
|
|
|
if (normalExecutionPath && !creationModeIsActive && rendererFactory.begin) {
|
|
|
|
|
rendererFactory.begin();
|
|
|
|
|
}
|
|
|
|
|
if (creationModeIsActive) {
|
2020-01-30 14:57:44 -08:00
|
|
|
|
renderView(tView, lView, context);
|
2019-04-01 15:36:43 -07:00
|
|
|
|
}
|
2020-01-30 14:57:44 -08:00
|
|
|
|
refreshView(tView, lView, templateFn, context);
|
2019-04-01 15:36:43 -07:00
|
|
|
|
} finally {
|
|
|
|
|
if (normalExecutionPath && !creationModeIsActive && rendererFactory.end) {
|
|
|
|
|
rendererFactory.end();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-03 10:29:14 -07:00
|
|
|
|
function executeTemplate<T>(
|
2020-01-30 14:57:44 -08:00
|
|
|
|
tView: TView, lView: LView, templateFn: ComponentTemplate<T>, rf: RenderFlags, context: T) {
|
2019-04-16 17:17:12 -07:00
|
|
|
|
const prevSelectedIndex = getSelectedIndex();
|
|
|
|
|
try {
|
2020-01-28 16:26:56 -08:00
|
|
|
|
setSelectedIndex(-1);
|
2019-08-14 10:44:29 +02:00
|
|
|
|
if (rf & RenderFlags.Update && lView.length > HEADER_OFFSET) {
|
2019-09-06 23:43:16 +02:00
|
|
|
|
// When we're updating, inherently select 0 so we don't
|
|
|
|
|
// have to generate that instruction for most update blocks.
|
2020-10-13 22:00:43 -07:00
|
|
|
|
selectIndexInternal(tView, lView, HEADER_OFFSET, isInCheckNoChangesMode());
|
2019-06-03 10:29:14 -07:00
|
|
|
|
}
|
2019-04-16 17:17:12 -07:00
|
|
|
|
templateFn(rf, context);
|
|
|
|
|
} finally {
|
|
|
|
|
setSelectedIndex(prevSelectedIndex);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-01 15:36:43 -07:00
|
|
|
|
//////////////////////////
|
|
|
|
|
//// Element
|
|
|
|
|
//////////////////////////
|
|
|
|
|
|
|
|
|
|
export function executeContentQueries(tView: TView, tNode: TNode, lView: LView) {
|
|
|
|
|
if (isContentQueryHost(tNode)) {
|
|
|
|
|
const start = tNode.directiveStart;
|
|
|
|
|
const end = tNode.directiveEnd;
|
|
|
|
|
for (let directiveIndex = start; directiveIndex < end; directiveIndex++) {
|
|
|
|
|
const def = tView.data[directiveIndex] as DirectiveDef<any>;
|
|
|
|
|
if (def.contentQueries) {
|
|
|
|
|
def.contentQueries(RenderFlags.Create, lView[directiveIndex], directiveIndex);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2019-09-02 15:17:44 +02:00
|
|
|
|
* Creates directive instances.
|
2019-04-01 15:36:43 -07:00
|
|
|
|
*/
|
2019-10-21 10:38:21 +02:00
|
|
|
|
export function createDirectivesInstances(tView: TView, lView: LView, tNode: TDirectiveHostNode) {
|
2019-04-01 15:36:43 -07:00
|
|
|
|
if (!getBindingsEnabled()) return;
|
2019-10-21 10:38:21 +02:00
|
|
|
|
instantiateAllDirectives(tView, lView, tNode, getNativeByTNode(tNode, lView));
|
2019-10-11 16:38:07 +02:00
|
|
|
|
if ((tNode.flags & TNodeFlags.hasHostBindings) === TNodeFlags.hasHostBindings) {
|
|
|
|
|
invokeDirectivesHostBindings(tView, lView, tNode);
|
|
|
|
|
}
|
2019-04-01 15:36:43 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Takes a list of local names and indices and pushes the resolved local variable values
|
|
|
|
|
* to LView in the same order as they are loaded in the template with load().
|
|
|
|
|
*/
|
2019-09-02 15:17:44 +02:00
|
|
|
|
export function saveResolvedLocalsInData(
|
2019-10-21 10:38:21 +02:00
|
|
|
|
viewData: LView, tNode: TDirectiveHostNode,
|
|
|
|
|
localRefExtractor: LocalRefExtractor = getNativeByTNode): void {
|
2019-04-01 15:36:43 -07:00
|
|
|
|
const localNames = tNode.localNames;
|
2019-11-15 15:57:27 +01:00
|
|
|
|
if (localNames !== null) {
|
2019-04-01 15:36:43 -07:00
|
|
|
|
let localIndex = tNode.index + 1;
|
|
|
|
|
for (let i = 0; i < localNames.length; i += 2) {
|
|
|
|
|
const index = localNames[i + 1] as number;
|
|
|
|
|
const value = index === -1 ?
|
|
|
|
|
localRefExtractor(
|
|
|
|
|
tNode as TElementNode | TContainerNode | TElementContainerNode, viewData) :
|
|
|
|
|
viewData[index];
|
|
|
|
|
viewData[localIndex++] = value;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Gets TView from a template function or creates a new TView
|
|
|
|
|
* if it doesn't already exist.
|
|
|
|
|
*
|
2019-05-17 16:25:09 -07:00
|
|
|
|
* @param def ComponentDef
|
2019-04-01 15:36:43 -07:00
|
|
|
|
* @returns TView
|
|
|
|
|
*/
|
2019-10-28 12:08:17 -07:00
|
|
|
|
export function getOrCreateTComponentView(def: ComponentDef<any>): TView {
|
2020-04-20 20:49:08 +02:00
|
|
|
|
const tView = def.tView;
|
|
|
|
|
|
|
|
|
|
// Create a TView if there isn't one, or recreate it if the first create pass didn't
|
2020-09-03 22:46:11 -07:00
|
|
|
|
// complete successfully since we can't know for sure whether it's in a usable shape.
|
2020-04-20 20:49:08 +02:00
|
|
|
|
if (tView === null || tView.incompleteFirstPass) {
|
2020-09-14 11:21:15 -07:00
|
|
|
|
// 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;
|
2020-04-20 20:49:08 +02:00
|
|
|
|
return def.tView = createTView(
|
2020-09-14 11:21:15 -07:00
|
|
|
|
TViewType.Component, declTNode, def.template, def.decls, def.vars, def.directiveDefs,
|
2020-04-20 20:49:08 +02:00
|
|
|
|
def.pipeDefs, def.viewQuery, def.schemas, def.consts);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return tView;
|
2019-04-01 15:36:43 -07:00
|
|
|
|
}
|
|
|
|
|
|
2019-05-17 09:52:31 -07:00
|
|
|
|
|
2019-04-01 15:36:43 -07:00
|
|
|
|
/**
|
|
|
|
|
* Creates a TView instance
|
|
|
|
|
*
|
2020-09-14 11:21:15 -07:00
|
|
|
|
* @param type Type of `TView`.
|
|
|
|
|
* @param declTNode Declaration location of this `TView`.
|
2019-04-01 15:36:43 -07:00
|
|
|
|
* @param templateFn Template function
|
2019-09-23 20:08:51 +02:00
|
|
|
|
* @param decls The number of nodes, local refs, and pipes in this template
|
2019-04-01 15:36:43 -07:00
|
|
|
|
* @param directives Registry of directives for this view
|
|
|
|
|
* @param pipes Registry of pipes for this view
|
|
|
|
|
* @param viewQuery View queries for this view
|
|
|
|
|
* @param schemas Schemas for this view
|
2019-09-23 20:08:51 +02:00
|
|
|
|
* @param consts Constants for this view
|
2019-04-01 15:36:43 -07:00
|
|
|
|
*/
|
|
|
|
|
export function createTView(
|
2020-09-14 11:21:15 -07:00
|
|
|
|
type: TViewType, declTNode: TNode|null, templateFn: ComponentTemplate<any>|null, decls: number,
|
fix(core): ngOnDestroy on multi providers called with incorrect context (#35840)
Currently destroy hooks are stored in memory as `[1, hook, 5, hook]` where
the numbers represent the index at which to find the context and `hook` is
the function to be invoked. This breaks down for `multi` providers,
because the value at the index will be an array of providers, resulting in
the hook being invoked with an array of all the multi provider values,
rather than the provider that was destroyed. In ViewEngine `ngOnDestroy`
wasn't being called for `multi` providers at all.
These changes fix the issue by changing the structure of the destroy hooks to `[1, hook, 5, [0, hook, 3, hook]]` where the indexes inside the inner array point to the provider inside of the multi provider array. Note that this is slightly different from the original design which called for the structure to be `[1, hook, 5, [hook, hook]`, because in the process of implementing it, I realized that we wouldn't get passing the correct context if only some of the `multi` providers have `ngOnDestroy` and others don't.
I've run the newly-added `view_destroy_hooks` benchmark against these changes and compared it to master. The difference seems to be insignificant (between 1% and 2% slower).
Fixes #35231.
PR Close #35840
2020-03-03 21:05:26 +01:00
|
|
|
|
vars: number, directives: DirectiveDefListOrFactory|null, pipes: PipeDefListOrFactory|null,
|
|
|
|
|
viewQuery: ViewQueriesFunction<any>|null, schemas: SchemaMetadata[]|null,
|
2020-08-10 17:25:51 -07:00
|
|
|
|
constsOrFactory: TConstantsOrFactory|null): TView {
|
2019-04-01 15:36:43 -07:00
|
|
|
|
ngDevMode && ngDevMode.tView++;
|
2019-09-23 20:08:51 +02:00
|
|
|
|
const bindingStartIndex = HEADER_OFFSET + decls;
|
2019-04-01 15:36:43 -07:00
|
|
|
|
// This length does not yet contain host bindings from child directives because at this point,
|
|
|
|
|
// we don't know which directives are active on this template. As soon as a directive is matched
|
|
|
|
|
// that has a host binding, we will update the blueprint with that def's hostVars count.
|
|
|
|
|
const initialViewLength = bindingStartIndex + vars;
|
|
|
|
|
const blueprint = createViewBlueprint(bindingStartIndex, initialViewLength);
|
2020-08-10 17:25:51 -07:00
|
|
|
|
const consts = typeof constsOrFactory === 'function' ? constsOrFactory() : constsOrFactory;
|
2020-08-04 12:42:12 -07:00
|
|
|
|
const tView = blueprint[TVIEW as any] = ngDevMode ?
|
2019-05-17 09:52:31 -07:00
|
|
|
|
new TViewConstructor(
|
2020-10-15 17:04:41 -07:00
|
|
|
|
type, // type: TViewType,
|
2020-08-04 12:42:12 -07:00
|
|
|
|
blueprint, // blueprint: LView,
|
|
|
|
|
templateFn, // template: ComponentTemplate<{}>|null,
|
|
|
|
|
null, // queries: TQueries|null
|
|
|
|
|
viewQuery, // viewQuery: ViewQueriesFunction<{}>|null,
|
2020-09-14 11:21:15 -07:00
|
|
|
|
declTNode, // declTNode: TNode|null,
|
2020-08-04 12:42:12 -07:00
|
|
|
|
cloneToTViewData(blueprint).fill(null, bindingStartIndex), // data: TData,
|
|
|
|
|
bindingStartIndex, // bindingStartIndex: number,
|
|
|
|
|
initialViewLength, // expandoStartIndex: number,
|
2020-10-15 17:04:41 -07:00
|
|
|
|
null, // hostBindingOpCodes: HostBindingOpCodes,
|
|
|
|
|
true, // firstCreatePass: boolean,
|
|
|
|
|
true, // firstUpdatePass: boolean,
|
|
|
|
|
false, // staticViewQueries: boolean,
|
|
|
|
|
false, // staticContentQueries: boolean,
|
|
|
|
|
null, // preOrderHooks: HookData|null,
|
|
|
|
|
null, // preOrderCheckHooks: HookData|null,
|
|
|
|
|
null, // contentHooks: HookData|null,
|
|
|
|
|
null, // contentCheckHooks: HookData|null,
|
|
|
|
|
null, // viewHooks: HookData|null,
|
|
|
|
|
null, // viewCheckHooks: HookData|null,
|
|
|
|
|
null, // destroyHooks: DestroyHookData|null,
|
|
|
|
|
null, // cleanup: any[]|null,
|
|
|
|
|
null, // contentQueries: number[]|null,
|
|
|
|
|
null, // components: number[]|null,
|
|
|
|
|
typeof directives === 'function' ? //
|
|
|
|
|
directives() : //
|
|
|
|
|
directives, // directiveRegistry: DirectiveDefList|null,
|
2020-08-04 12:42:12 -07:00
|
|
|
|
typeof pipes === 'function' ? pipes() : pipes, // pipeRegistry: PipeDefList|null,
|
|
|
|
|
null, // firstChild: TNode|null,
|
|
|
|
|
schemas, // schemas: SchemaMetadata[]|null,
|
|
|
|
|
consts, // consts: TConstants|null
|
|
|
|
|
false, // incompleteFirstPass: boolean
|
|
|
|
|
decls, // ngDevMode only: decls
|
|
|
|
|
vars, // ngDevMode only: vars
|
|
|
|
|
) :
|
2019-05-17 09:52:31 -07:00
|
|
|
|
{
|
2019-11-08 15:13:22 -08:00
|
|
|
|
type: type,
|
2019-05-17 09:52:31 -07:00
|
|
|
|
blueprint: blueprint,
|
|
|
|
|
template: templateFn,
|
2019-06-07 10:55:48 +02:00
|
|
|
|
queries: null,
|
2019-05-17 09:52:31 -07:00
|
|
|
|
viewQuery: viewQuery,
|
2020-09-14 13:14:12 -07:00
|
|
|
|
declTNode: declTNode,
|
2019-05-17 09:52:31 -07:00
|
|
|
|
data: blueprint.slice().fill(null, bindingStartIndex),
|
|
|
|
|
bindingStartIndex: bindingStartIndex,
|
|
|
|
|
expandoStartIndex: initialViewLength,
|
2020-10-15 17:04:41 -07:00
|
|
|
|
hostBindingOpCodes: null,
|
2019-11-01 13:06:17 -07:00
|
|
|
|
firstCreatePass: true,
|
2019-10-29 15:15:09 -07:00
|
|
|
|
firstUpdatePass: true,
|
2019-05-17 09:52:31 -07:00
|
|
|
|
staticViewQueries: false,
|
|
|
|
|
staticContentQueries: false,
|
|
|
|
|
preOrderHooks: null,
|
|
|
|
|
preOrderCheckHooks: null,
|
|
|
|
|
contentHooks: null,
|
|
|
|
|
contentCheckHooks: null,
|
|
|
|
|
viewHooks: null,
|
|
|
|
|
viewCheckHooks: null,
|
|
|
|
|
destroyHooks: null,
|
|
|
|
|
cleanup: null,
|
|
|
|
|
contentQueries: null,
|
|
|
|
|
components: null,
|
|
|
|
|
directiveRegistry: typeof directives === 'function' ? directives() : directives,
|
|
|
|
|
pipeRegistry: typeof pipes === 'function' ? pipes() : pipes,
|
|
|
|
|
firstChild: null,
|
|
|
|
|
schemas: schemas,
|
2019-09-23 20:08:51 +02:00
|
|
|
|
consts: consts,
|
2020-04-20 20:49:08 +02:00
|
|
|
|
incompleteFirstPass: false
|
2019-05-17 09:52:31 -07:00
|
|
|
|
};
|
2020-08-04 12:42:12 -07:00
|
|
|
|
if (ngDevMode) {
|
|
|
|
|
// For performance reasons it is important that the tView retains the same shape during runtime.
|
|
|
|
|
// (To make sure that all of the code is monomorphic.) For this reason we seal the object to
|
|
|
|
|
// prevent class transitions.
|
|
|
|
|
Object.seal(tView);
|
|
|
|
|
}
|
|
|
|
|
return tView;
|
2019-04-01 15:36:43 -07:00
|
|
|
|
}
|
2019-05-17 09:52:31 -07:00
|
|
|
|
|
2019-04-01 15:36:43 -07:00
|
|
|
|
function createViewBlueprint(bindingStartIndex: number, initialViewLength: number): LView {
|
2019-08-15 14:14:25 +01:00
|
|
|
|
const blueprint = ngDevMode ? new LViewBlueprint() : [];
|
2019-07-25 14:27:10 +02:00
|
|
|
|
|
|
|
|
|
for (let i = 0; i < initialViewLength; i++) {
|
|
|
|
|
blueprint.push(i < bindingStartIndex ? null : NO_CHANGE);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return blueprint as LView;
|
2019-04-01 15:36:43 -07:00
|
|
|
|
}
|
|
|
|
|
|
2019-10-28 23:40:16 -07:00
|
|
|
|
function createError(text: string, token: any) {
|
2019-04-24 14:50:01 +02:00
|
|
|
|
return new Error(`Renderer: ${text} [${stringifyForError(token)}]`);
|
2019-04-01 15:36:43 -07:00
|
|
|
|
}
|
|
|
|
|
|
fix(core): ngOnDestroy on multi providers called with incorrect context (#35840)
Currently destroy hooks are stored in memory as `[1, hook, 5, hook]` where
the numbers represent the index at which to find the context and `hook` is
the function to be invoked. This breaks down for `multi` providers,
because the value at the index will be an array of providers, resulting in
the hook being invoked with an array of all the multi provider values,
rather than the provider that was destroyed. In ViewEngine `ngOnDestroy`
wasn't being called for `multi` providers at all.
These changes fix the issue by changing the structure of the destroy hooks to `[1, hook, 5, [0, hook, 3, hook]]` where the indexes inside the inner array point to the provider inside of the multi provider array. Note that this is slightly different from the original design which called for the structure to be `[1, hook, 5, [hook, hook]`, because in the process of implementing it, I realized that we wouldn't get passing the correct context if only some of the `multi` providers have `ngOnDestroy` and others don't.
I've run the newly-added `view_destroy_hooks` benchmark against these changes and compared it to master. The difference seems to be insignificant (between 1% and 2% slower).
Fixes #35231.
PR Close #35840
2020-03-03 21:05:26 +01:00
|
|
|
|
function assertHostNodeExists(rElement: RElement, elementOrSelector: RElement|string) {
|
2019-10-28 23:40:16 -07:00
|
|
|
|
if (!rElement) {
|
|
|
|
|
if (typeof elementOrSelector === 'string') {
|
|
|
|
|
throw createError('Host node with selector not found:', elementOrSelector);
|
|
|
|
|
} else {
|
|
|
|
|
throw createError('Host node is required:', elementOrSelector);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-04-01 15:36:43 -07:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Locates the host native element, used for bootstrapping existing nodes into rendering pipeline.
|
|
|
|
|
*
|
2019-10-28 23:40:16 -07:00
|
|
|
|
* @param rendererFactory Factory function to create renderer instance.
|
2019-04-01 15:36:43 -07:00
|
|
|
|
* @param elementOrSelector Render element or CSS selector to locate the element.
|
2019-10-28 23:40:16 -07:00
|
|
|
|
* @param encapsulation View Encapsulation defined for component that requests host element.
|
2019-04-01 15:36:43 -07:00
|
|
|
|
*/
|
|
|
|
|
export function locateHostElement(
|
fix(core): ngOnDestroy on multi providers called with incorrect context (#35840)
Currently destroy hooks are stored in memory as `[1, hook, 5, hook]` where
the numbers represent the index at which to find the context and `hook` is
the function to be invoked. This breaks down for `multi` providers,
because the value at the index will be an array of providers, resulting in
the hook being invoked with an array of all the multi provider values,
rather than the provider that was destroyed. In ViewEngine `ngOnDestroy`
wasn't being called for `multi` providers at all.
These changes fix the issue by changing the structure of the destroy hooks to `[1, hook, 5, [0, hook, 3, hook]]` where the indexes inside the inner array point to the provider inside of the multi provider array. Note that this is slightly different from the original design which called for the structure to be `[1, hook, 5, [hook, hook]`, because in the process of implementing it, I realized that we wouldn't get passing the correct context if only some of the `multi` providers have `ngOnDestroy` and others don't.
I've run the newly-added `view_destroy_hooks` benchmark against these changes and compared it to master. The difference seems to be insignificant (between 1% and 2% slower).
Fixes #35231.
PR Close #35840
2020-03-03 21:05:26 +01:00
|
|
|
|
renderer: Renderer3, elementOrSelector: RElement|string,
|
2019-10-28 23:40:16 -07:00
|
|
|
|
encapsulation: ViewEncapsulation): RElement {
|
|
|
|
|
if (isProceduralRenderer(renderer)) {
|
|
|
|
|
// When using native Shadow DOM, do not clear host element to allow native slot projection
|
|
|
|
|
const preserveContent = encapsulation === ViewEncapsulation.ShadowDom;
|
|
|
|
|
return renderer.selectRootElement(elementOrSelector, preserveContent);
|
2019-04-01 15:36:43 -07:00
|
|
|
|
}
|
2019-10-28 23:40:16 -07:00
|
|
|
|
|
|
|
|
|
let rElement = typeof elementOrSelector === 'string' ?
|
fix(core): ngOnDestroy on multi providers called with incorrect context (#35840)
Currently destroy hooks are stored in memory as `[1, hook, 5, hook]` where
the numbers represent the index at which to find the context and `hook` is
the function to be invoked. This breaks down for `multi` providers,
because the value at the index will be an array of providers, resulting in
the hook being invoked with an array of all the multi provider values,
rather than the provider that was destroyed. In ViewEngine `ngOnDestroy`
wasn't being called for `multi` providers at all.
These changes fix the issue by changing the structure of the destroy hooks to `[1, hook, 5, [0, hook, 3, hook]]` where the indexes inside the inner array point to the provider inside of the multi provider array. Note that this is slightly different from the original design which called for the structure to be `[1, hook, 5, [hook, hook]`, because in the process of implementing it, I realized that we wouldn't get passing the correct context if only some of the `multi` providers have `ngOnDestroy` and others don't.
I've run the newly-added `view_destroy_hooks` benchmark against these changes and compared it to master. The difference seems to be insignificant (between 1% and 2% slower).
Fixes #35231.
PR Close #35840
2020-03-03 21:05:26 +01:00
|
|
|
|
renderer.querySelector(elementOrSelector)! :
|
2019-10-28 23:40:16 -07:00
|
|
|
|
elementOrSelector;
|
|
|
|
|
ngDevMode && assertHostNodeExists(rElement, elementOrSelector);
|
|
|
|
|
|
|
|
|
|
// Always clear host element's content when Renderer3 is in use. For procedural renderer case we
|
|
|
|
|
// make it depend on whether ShadowDom encapsulation is used (in which case the content should be
|
|
|
|
|
// preserved to allow native slot projection). ShadowDom encapsulation requires procedural
|
|
|
|
|
// renderer, and procedural renderer case is handled above.
|
|
|
|
|
rElement.textContent = '';
|
|
|
|
|
|
|
|
|
|
return rElement;
|
2019-04-01 15:36:43 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Saves context for this cleanup function in LView.cleanupInstances.
|
|
|
|
|
*
|
|
|
|
|
* On the first template pass, saves in TView:
|
|
|
|
|
* - Cleanup function
|
|
|
|
|
* - Index of context we just saved in LView.cleanupInstances
|
|
|
|
|
*/
|
2020-01-30 14:57:44 -08:00
|
|
|
|
export function storeCleanupWithContext(
|
|
|
|
|
tView: TView, lView: LView, context: any, cleanupFn: Function): void {
|
|
|
|
|
const lCleanup = getLCleanup(lView);
|
2019-04-01 15:36:43 -07:00
|
|
|
|
lCleanup.push(context);
|
|
|
|
|
|
2020-01-30 14:57:44 -08:00
|
|
|
|
if (tView.firstCreatePass) {
|
|
|
|
|
getTViewCleanup(tView).push(cleanupFn, lCleanup.length - 1);
|
2019-04-01 15:36:43 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Constructs a TNode object from the arguments.
|
|
|
|
|
*
|
2019-06-14 07:55:17 +02:00
|
|
|
|
* @param tView `TView` to which this `TNode` belongs (used only in `ngDevMode`)
|
2020-09-14 11:21:15 -07:00
|
|
|
|
* @param tParent Parent `TNode`
|
2019-04-01 15:36:43 -07:00
|
|
|
|
* @param type The type of the node
|
2020-10-13 22:00:43 -07:00
|
|
|
|
* @param index The index of the TNode in TView.data, adjusted for HEADER_OFFSET
|
2019-04-01 15:36:43 -07:00
|
|
|
|
* @param tagName The tag name of the node
|
|
|
|
|
* @param attrs The attributes defined on this node
|
|
|
|
|
* @param tViews Any TViews attached to this node
|
|
|
|
|
* @returns the TNode object
|
|
|
|
|
*/
|
2020-09-14 13:06:05 -07:00
|
|
|
|
export function createTNode(
|
|
|
|
|
tView: TView, tParent: TElementNode|TContainerNode|null, type: TNodeType.Container,
|
2020-10-13 22:00:43 -07:00
|
|
|
|
index: number, tagName: string|null, attrs: TAttributes|null): TContainerNode;
|
2020-09-14 13:06:05 -07:00
|
|
|
|
export function createTNode(
|
2020-10-12 21:38:06 -07:00
|
|
|
|
tView: TView, tParent: TElementNode|TContainerNode|null, type: TNodeType.Element|TNodeType.Text,
|
2020-10-13 22:00:43 -07:00
|
|
|
|
index: number, tagName: string|null, attrs: TAttributes|null): TElementNode;
|
2020-09-14 13:06:05 -07:00
|
|
|
|
export function createTNode(
|
|
|
|
|
tView: TView, tParent: TElementNode|TContainerNode|null, type: TNodeType.ElementContainer,
|
2020-10-13 22:00:43 -07:00
|
|
|
|
index: number, tagName: string|null, attrs: TAttributes|null): TElementContainerNode;
|
2020-09-14 13:06:05 -07:00
|
|
|
|
export function createTNode(
|
2020-10-13 22:00:43 -07:00
|
|
|
|
tView: TView, tParent: TElementNode|TContainerNode|null, type: TNodeType.Icu, index: number,
|
|
|
|
|
tagName: string|null, attrs: TAttributes|null): TIcuContainerNode;
|
2020-09-14 13:06:05 -07:00
|
|
|
|
export function createTNode(
|
|
|
|
|
tView: TView, tParent: TElementNode|TContainerNode|null, type: TNodeType.Projection,
|
2020-10-13 22:00:43 -07:00
|
|
|
|
index: number, tagName: string|null, attrs: TAttributes|null): TProjectionNode;
|
2020-09-14 13:06:05 -07:00
|
|
|
|
export function createTNode(
|
2020-10-13 22:00:43 -07:00
|
|
|
|
tView: TView, tParent: TElementNode|TContainerNode|null, type: TNodeType, index: number,
|
2020-09-14 13:06:05 -07:00
|
|
|
|
tagName: string|null, attrs: TAttributes|null): TNode;
|
2019-04-01 15:36:43 -07:00
|
|
|
|
export function createTNode(
|
2020-10-13 22:00:43 -07:00
|
|
|
|
tView: TView, tParent: TElementNode|TContainerNode|null, type: TNodeType, index: number,
|
2020-10-12 16:57:07 -07:00
|
|
|
|
value: string|null, attrs: TAttributes|null): TNode {
|
2020-10-13 22:00:43 -07:00
|
|
|
|
ngDevMode && index !== 0 && // 0 are bogus nodes and they are OK. See `createContainerRef` in
|
|
|
|
|
// `view_engine_compatibility` for additional context.
|
|
|
|
|
assertGreaterThanOrEqual(index, HEADER_OFFSET, 'TNodes can\'t be in the LView header.');
|
2020-09-28 17:39:56 -07:00
|
|
|
|
ngDevMode && assertNotSame(attrs, undefined, '\'undefined\' is not valid value for \'attrs\'');
|
2019-04-01 15:36:43 -07:00
|
|
|
|
ngDevMode && ngDevMode.tNode++;
|
2020-09-25 15:01:56 -07:00
|
|
|
|
ngDevMode && tParent && assertTNodeForTView(tParent, tView);
|
2019-06-14 07:55:17 +02:00
|
|
|
|
let injectorIndex = tParent ? tParent.injectorIndex : -1;
|
2020-08-04 12:42:12 -07:00
|
|
|
|
const tNode = ngDevMode ?
|
|
|
|
|
new TNodeDebug(
|
|
|
|
|
tView, // tView_: TView
|
|
|
|
|
type, // type: TNodeType
|
2020-10-13 22:00:43 -07:00
|
|
|
|
index, // index: number
|
2020-09-25 15:01:56 -07:00
|
|
|
|
null, // insertBeforeIndex: null|-1|number|number[]
|
2020-08-04 12:42:12 -07:00
|
|
|
|
injectorIndex, // injectorIndex: number
|
|
|
|
|
-1, // directiveStart: number
|
|
|
|
|
-1, // directiveEnd: number
|
|
|
|
|
-1, // directiveStylingLast: number
|
|
|
|
|
null, // propertyBindings: number[]|null
|
|
|
|
|
0, // flags: TNodeFlags
|
|
|
|
|
0, // providerIndexes: TNodeProviderIndexes
|
2020-10-12 16:57:07 -07:00
|
|
|
|
value, // value: string|null
|
2020-08-04 12:42:12 -07:00
|
|
|
|
attrs, // attrs: (string|AttributeMarker|(string|SelectorFlags)[])[]|null
|
|
|
|
|
null, // mergedAttrs
|
|
|
|
|
null, // localNames: (string|number)[]|null
|
|
|
|
|
undefined, // initialInputs: (string[]|null)[]|null|undefined
|
|
|
|
|
null, // inputs: PropertyAliases|null
|
|
|
|
|
null, // outputs: PropertyAliases|null
|
|
|
|
|
null, // tViews: ITView|ITView[]|null
|
|
|
|
|
null, // next: ITNode|null
|
|
|
|
|
null, // projectionNext: ITNode|null
|
|
|
|
|
null, // child: ITNode|null
|
|
|
|
|
tParent, // parent: TElementNode|TContainerNode|null
|
|
|
|
|
null, // projection: number|(ITNode|RNode[])[]|null
|
|
|
|
|
null, // styles: string|null
|
|
|
|
|
null, // stylesWithoutHost: string|null
|
|
|
|
|
undefined, // residualStyles: string|null
|
|
|
|
|
null, // classes: string|null
|
|
|
|
|
null, // classesWithoutHost: string|null
|
|
|
|
|
undefined, // residualClasses: string|null
|
|
|
|
|
0 as any, // classBindings: TStylingRange;
|
|
|
|
|
0 as any, // styleBindings: TStylingRange;
|
|
|
|
|
) :
|
|
|
|
|
{
|
2020-10-13 22:00:43 -07:00
|
|
|
|
type,
|
|
|
|
|
index,
|
2020-09-25 15:01:56 -07:00
|
|
|
|
insertBeforeIndex: null,
|
2020-10-13 22:00:43 -07:00
|
|
|
|
injectorIndex,
|
2020-08-04 12:42:12 -07:00
|
|
|
|
directiveStart: -1,
|
|
|
|
|
directiveEnd: -1,
|
|
|
|
|
directiveStylingLast: -1,
|
|
|
|
|
propertyBindings: null,
|
|
|
|
|
flags: 0,
|
|
|
|
|
providerIndexes: 0,
|
2020-10-12 16:57:07 -07:00
|
|
|
|
value: value,
|
2020-08-04 12:42:12 -07:00
|
|
|
|
attrs: attrs,
|
|
|
|
|
mergedAttrs: null,
|
|
|
|
|
localNames: null,
|
|
|
|
|
initialInputs: undefined,
|
|
|
|
|
inputs: null,
|
|
|
|
|
outputs: null,
|
|
|
|
|
tViews: null,
|
|
|
|
|
next: null,
|
|
|
|
|
projectionNext: null,
|
|
|
|
|
child: null,
|
|
|
|
|
parent: tParent,
|
|
|
|
|
projection: null,
|
|
|
|
|
styles: null,
|
|
|
|
|
stylesWithoutHost: null,
|
|
|
|
|
residualStyles: undefined,
|
|
|
|
|
classes: null,
|
|
|
|
|
classesWithoutHost: null,
|
|
|
|
|
residualClasses: undefined,
|
|
|
|
|
classBindings: 0 as any,
|
|
|
|
|
styleBindings: 0 as any,
|
|
|
|
|
};
|
|
|
|
|
if (ngDevMode) {
|
|
|
|
|
// For performance reasons it is important that the tNode retains the same shape during runtime.
|
|
|
|
|
// (To make sure that all of the code is monomorphic.) For this reason we seal the object to
|
|
|
|
|
// prevent class transitions.
|
|
|
|
|
Object.seal(tNode);
|
|
|
|
|
}
|
|
|
|
|
return tNode;
|
2019-04-01 15:36:43 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2019-09-11 14:48:15 +02:00
|
|
|
|
function generatePropertyAliases(
|
|
|
|
|
inputAliasMap: {[publicName: string]: string}, directiveDefIdx: number,
|
fix(core): ngOnDestroy on multi providers called with incorrect context (#35840)
Currently destroy hooks are stored in memory as `[1, hook, 5, hook]` where
the numbers represent the index at which to find the context and `hook` is
the function to be invoked. This breaks down for `multi` providers,
because the value at the index will be an array of providers, resulting in
the hook being invoked with an array of all the multi provider values,
rather than the provider that was destroyed. In ViewEngine `ngOnDestroy`
wasn't being called for `multi` providers at all.
These changes fix the issue by changing the structure of the destroy hooks to `[1, hook, 5, [0, hook, 3, hook]]` where the indexes inside the inner array point to the provider inside of the multi provider array. Note that this is slightly different from the original design which called for the structure to be `[1, hook, 5, [hook, hook]`, because in the process of implementing it, I realized that we wouldn't get passing the correct context if only some of the `multi` providers have `ngOnDestroy` and others don't.
I've run the newly-added `view_destroy_hooks` benchmark against these changes and compared it to master. The difference seems to be insignificant (between 1% and 2% slower).
Fixes #35231.
PR Close #35840
2020-03-03 21:05:26 +01:00
|
|
|
|
propStore: PropertyAliases|null): PropertyAliases|null {
|
2019-09-11 14:48:15 +02:00
|
|
|
|
for (let publicName in inputAliasMap) {
|
|
|
|
|
if (inputAliasMap.hasOwnProperty(publicName)) {
|
|
|
|
|
propStore = propStore === null ? {} : propStore;
|
|
|
|
|
const internalName = inputAliasMap[publicName];
|
|
|
|
|
|
|
|
|
|
if (propStore.hasOwnProperty(publicName)) {
|
2019-11-13 17:06:54 +01:00
|
|
|
|
propStore[publicName].push(directiveDefIdx, internalName);
|
2019-09-11 14:48:15 +02:00
|
|
|
|
} else {
|
2019-11-13 17:06:54 +01:00
|
|
|
|
(propStore[publicName] = [directiveDefIdx, internalName]);
|
2019-09-11 14:48:15 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return propStore;
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-01 15:36:43 -07:00
|
|
|
|
/**
|
2020-10-26 20:18:06 -04:00
|
|
|
|
* Initializes data structures required to work with directive inputs and outputs.
|
2019-09-11 14:48:15 +02:00
|
|
|
|
* Initialization is done for all directives matched on a given TNode.
|
2019-04-01 15:36:43 -07:00
|
|
|
|
*/
|
2019-09-11 14:48:15 +02:00
|
|
|
|
function initializeInputAndOutputAliases(tView: TView, tNode: TNode): void {
|
2019-11-01 13:06:17 -07:00
|
|
|
|
ngDevMode && assertFirstCreatePass(tView);
|
2019-09-11 14:48:15 +02:00
|
|
|
|
|
2019-04-01 15:36:43 -07:00
|
|
|
|
const start = tNode.directiveStart;
|
|
|
|
|
const end = tNode.directiveEnd;
|
2020-10-15 17:04:41 -07:00
|
|
|
|
const tViewData = tView.data;
|
2019-04-01 15:36:43 -07:00
|
|
|
|
|
2019-10-16 11:44:08 +02:00
|
|
|
|
const tNodeAttrs = tNode.attrs;
|
|
|
|
|
const inputsFromAttrs: InitialInputData = ngDevMode ? new TNodeInitialInputs() : [];
|
2019-09-11 14:48:15 +02:00
|
|
|
|
let inputsStore: PropertyAliases|null = null;
|
|
|
|
|
let outputsStore: PropertyAliases|null = null;
|
|
|
|
|
for (let i = start; i < end; i++) {
|
2020-10-15 17:04:41 -07:00
|
|
|
|
const directiveDef = tViewData[i] as DirectiveDef<any>;
|
2019-10-16 11:44:08 +02:00
|
|
|
|
const directiveInputs = directiveDef.inputs;
|
2020-04-05 18:35:37 -07:00
|
|
|
|
// Do not use unbound attributes as inputs to structural directives, since structural
|
|
|
|
|
// directive inputs can only be set using microsyntax (e.g. `<div *dir="exp">`).
|
|
|
|
|
// TODO(FW-1930): microsyntax expressions may also contain unbound/static attributes, which
|
|
|
|
|
// should be set for inline templates.
|
|
|
|
|
const initialInputs = (tNodeAttrs !== null && !isInlineTemplate(tNode)) ?
|
|
|
|
|
generateInitialInputs(directiveInputs, tNodeAttrs) :
|
|
|
|
|
null;
|
|
|
|
|
inputsFromAttrs.push(initialInputs);
|
2019-10-16 11:44:08 +02:00
|
|
|
|
inputsStore = generatePropertyAliases(directiveInputs, i, inputsStore);
|
2019-09-11 14:48:15 +02:00
|
|
|
|
outputsStore = generatePropertyAliases(directiveDef.outputs, i, outputsStore);
|
2019-04-01 15:36:43 -07:00
|
|
|
|
}
|
2019-09-11 14:48:15 +02:00
|
|
|
|
|
2019-10-03 12:27:02 +02:00
|
|
|
|
if (inputsStore !== null) {
|
2020-02-25 10:31:01 -08:00
|
|
|
|
if (inputsStore.hasOwnProperty('class')) {
|
2019-10-03 12:27:02 +02:00
|
|
|
|
tNode.flags |= TNodeFlags.hasClassInput;
|
|
|
|
|
}
|
|
|
|
|
if (inputsStore.hasOwnProperty('style')) {
|
|
|
|
|
tNode.flags |= TNodeFlags.hasStyleInput;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-16 11:44:08 +02:00
|
|
|
|
tNode.initialInputs = inputsFromAttrs;
|
2019-09-11 14:48:15 +02:00
|
|
|
|
tNode.inputs = inputsStore;
|
|
|
|
|
tNode.outputs = outputsStore;
|
2019-04-01 15:36:43 -07:00
|
|
|
|
}
|
|
|
|
|
|
2019-03-28 14:54:22 -07:00
|
|
|
|
/**
|
2019-05-26 11:45:45 +02:00
|
|
|
|
* Mapping between attributes names that don't correspond to their element property names.
|
2019-09-10 12:10:44 +02:00
|
|
|
|
*
|
|
|
|
|
* Performance note: this function is written as a series of if checks (instead of, say, a property
|
|
|
|
|
* object lookup) for performance reasons - the series of `if` checks seems to be the fastest way of
|
|
|
|
|
* mapping property names. Do NOT change without benchmarking.
|
|
|
|
|
*
|
2019-05-26 11:45:45 +02:00
|
|
|
|
* Note: this mapping has to be kept in sync with the equally named mapping in the template
|
|
|
|
|
* type-checking machinery of ngtsc.
|
|
|
|
|
*/
|
2019-09-10 12:10:44 +02:00
|
|
|
|
function mapPropName(name: string): string {
|
|
|
|
|
if (name === 'class') return 'className';
|
|
|
|
|
if (name === 'for') return 'htmlFor';
|
|
|
|
|
if (name === 'formaction') return 'formAction';
|
|
|
|
|
if (name === 'innerHtml') return 'innerHTML';
|
|
|
|
|
if (name === 'readonly') return 'readOnly';
|
|
|
|
|
if (name === 'tabindex') return 'tabIndex';
|
|
|
|
|
return name;
|
|
|
|
|
}
|
2019-03-28 14:54:22 -07:00
|
|
|
|
|
|
|
|
|
export function elementPropertyInternal<T>(
|
2020-03-05 14:14:59 -08:00
|
|
|
|
tView: TView, tNode: TNode, lView: LView, propName: string, value: T, renderer: Renderer3,
|
fix(core): ngOnDestroy on multi providers called with incorrect context (#35840)
Currently destroy hooks are stored in memory as `[1, hook, 5, hook]` where
the numbers represent the index at which to find the context and `hook` is
the function to be invoked. This breaks down for `multi` providers,
because the value at the index will be an array of providers, resulting in
the hook being invoked with an array of all the multi provider values,
rather than the provider that was destroyed. In ViewEngine `ngOnDestroy`
wasn't being called for `multi` providers at all.
These changes fix the issue by changing the structure of the destroy hooks to `[1, hook, 5, [0, hook, 3, hook]]` where the indexes inside the inner array point to the provider inside of the multi provider array. Note that this is slightly different from the original design which called for the structure to be `[1, hook, 5, [hook, hook]`, because in the process of implementing it, I realized that we wouldn't get passing the correct context if only some of the `multi` providers have `ngOnDestroy` and others don't.
I've run the newly-added `view_destroy_hooks` benchmark against these changes and compared it to master. The difference seems to be insignificant (between 1% and 2% slower).
Fixes #35231.
PR Close #35840
2020-03-03 21:05:26 +01:00
|
|
|
|
sanitizer: SanitizerFn|null|undefined, nativeOnly: boolean): void {
|
2019-05-03 14:40:00 +02:00
|
|
|
|
ngDevMode && assertNotSame(value, NO_CHANGE as any, 'Incoming value should never be NO_CHANGE.');
|
2020-03-05 14:14:59 -08:00
|
|
|
|
const element = getNativeByTNode(tNode, lView) as RElement | RComment;
|
2019-09-11 14:48:15 +02:00
|
|
|
|
let inputData = tNode.inputs;
|
2019-03-28 14:54:22 -07:00
|
|
|
|
let dataValue: PropertyAliasValue|undefined;
|
2019-09-11 14:48:15 +02:00
|
|
|
|
if (!nativeOnly && inputData != null && (dataValue = inputData[propName])) {
|
2020-01-30 14:57:44 -08:00
|
|
|
|
setInputsForProperty(tView, lView, dataValue, propName, value);
|
2020-03-05 14:14:59 -08:00
|
|
|
|
if (isComponentHost(tNode)) markDirtyIfOnPush(lView, tNode.index);
|
2019-03-28 14:54:22 -07:00
|
|
|
|
if (ngDevMode) {
|
2019-10-03 16:47:40 -07:00
|
|
|
|
setNgReflectProperties(lView, element, tNode.type, dataValue, value);
|
2019-03-28 14:54:22 -07:00
|
|
|
|
}
|
2020-10-12 21:38:06 -07:00
|
|
|
|
} else if (tNode.type & TNodeType.AnyRNode) {
|
2019-09-10 12:10:44 +02:00
|
|
|
|
propName = mapPropName(propName);
|
2019-04-01 15:36:43 -07:00
|
|
|
|
|
2019-03-28 14:54:22 -07:00
|
|
|
|
if (ngDevMode) {
|
|
|
|
|
validateAgainstEventProperties(propName);
|
2020-07-15 07:03:04 +02:00
|
|
|
|
if (!validateProperty(tView, element, propName, tNode)) {
|
2019-09-04 21:48:04 +02:00
|
|
|
|
// Return here since we only log warnings for unknown properties.
|
2020-04-02 20:58:03 +02:00
|
|
|
|
logUnknownPropertyError(propName, tNode);
|
2019-09-04 21:48:04 +02:00
|
|
|
|
return;
|
|
|
|
|
}
|
2019-03-28 14:54:22 -07:00
|
|
|
|
ngDevMode.rendererSetProperty++;
|
|
|
|
|
}
|
|
|
|
|
|
2019-05-15 21:08:31 -07:00
|
|
|
|
// It is assumed that the sanitizer is only added when the compiler determines that the
|
2019-09-04 21:48:04 +02:00
|
|
|
|
// property is risky, so sanitization can be done without further checks.
|
2020-10-12 16:57:07 -07:00
|
|
|
|
value = sanitizer != null ? (sanitizer(value, tNode.value || '', propName) as any) : value;
|
2019-03-28 14:54:22 -07:00
|
|
|
|
if (isProceduralRenderer(renderer)) {
|
|
|
|
|
renderer.setProperty(element as RElement, propName, value);
|
|
|
|
|
} else if (!isAnimationProp(propName)) {
|
|
|
|
|
(element as RElement).setProperty ? (element as any).setProperty(propName, value) :
|
|
|
|
|
(element as any)[propName] = value;
|
|
|
|
|
}
|
2020-10-12 21:38:06 -07:00
|
|
|
|
} else if (tNode.type & TNodeType.AnyContainer) {
|
2019-03-28 14:54:22 -07:00
|
|
|
|
// If the node is a container and the property didn't
|
|
|
|
|
// match any of the inputs or schemas we should throw.
|
2020-10-12 16:57:07 -07:00
|
|
|
|
if (ngDevMode && !matchingSchemas(tView, tNode.value)) {
|
2020-04-02 20:58:03 +02:00
|
|
|
|
logUnknownPropertyError(propName, tNode);
|
2019-03-28 14:54:22 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-04-01 15:36:43 -07:00
|
|
|
|
|
2019-03-28 14:54:22 -07:00
|
|
|
|
/** If node is an OnPush component, marks its LView dirty. */
|
|
|
|
|
function markDirtyIfOnPush(lView: LView, viewIndex: number): void {
|
|
|
|
|
ngDevMode && assertLView(lView);
|
2019-10-09 16:10:14 -07:00
|
|
|
|
const childComponentLView = getComponentLViewByIndex(viewIndex, lView);
|
2019-03-28 14:54:22 -07:00
|
|
|
|
if (!(childComponentLView[FLAGS] & LViewFlags.CheckAlways)) {
|
|
|
|
|
childComponentLView[FLAGS] |= LViewFlags.Dirty;
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-04-01 15:36:43 -07:00
|
|
|
|
|
2019-10-03 16:47:40 -07:00
|
|
|
|
function setNgReflectProperty(
|
fix(core): ngOnDestroy on multi providers called with incorrect context (#35840)
Currently destroy hooks are stored in memory as `[1, hook, 5, hook]` where
the numbers represent the index at which to find the context and `hook` is
the function to be invoked. This breaks down for `multi` providers,
because the value at the index will be an array of providers, resulting in
the hook being invoked with an array of all the multi provider values,
rather than the provider that was destroyed. In ViewEngine `ngOnDestroy`
wasn't being called for `multi` providers at all.
These changes fix the issue by changing the structure of the destroy hooks to `[1, hook, 5, [0, hook, 3, hook]]` where the indexes inside the inner array point to the provider inside of the multi provider array. Note that this is slightly different from the original design which called for the structure to be `[1, hook, 5, [hook, hook]`, because in the process of implementing it, I realized that we wouldn't get passing the correct context if only some of the `multi` providers have `ngOnDestroy` and others don't.
I've run the newly-added `view_destroy_hooks` benchmark against these changes and compared it to master. The difference seems to be insignificant (between 1% and 2% slower).
Fixes #35231.
PR Close #35840
2020-03-03 21:05:26 +01:00
|
|
|
|
lView: LView, element: RElement|RComment, type: TNodeType, attrName: string, value: any) {
|
2019-04-18 14:22:32 +02:00
|
|
|
|
const renderer = lView[RENDERER];
|
|
|
|
|
attrName = normalizeDebugBindingName(attrName);
|
|
|
|
|
const debugValue = normalizeDebugBindingValue(value);
|
2020-10-12 21:38:06 -07:00
|
|
|
|
if (type & TNodeType.AnyRNode) {
|
2019-04-25 07:12:37 +02:00
|
|
|
|
if (value == null) {
|
|
|
|
|
isProceduralRenderer(renderer) ? renderer.removeAttribute((element as RElement), attrName) :
|
|
|
|
|
(element as RElement).removeAttribute(attrName);
|
|
|
|
|
} else {
|
|
|
|
|
isProceduralRenderer(renderer) ?
|
|
|
|
|
renderer.setAttribute((element as RElement), attrName, debugValue) :
|
|
|
|
|
(element as RElement).setAttribute(attrName, debugValue);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
const textContent = `bindings=${JSON.stringify({[attrName]: debugValue}, null, 2)}`;
|
2019-04-18 14:22:32 +02:00
|
|
|
|
if (isProceduralRenderer(renderer)) {
|
2019-04-25 07:12:37 +02:00
|
|
|
|
renderer.setValue((element as RComment), textContent);
|
2019-04-18 14:22:32 +02:00
|
|
|
|
} else {
|
2019-04-25 07:12:37 +02:00
|
|
|
|
(element as RComment).textContent = textContent;
|
2019-03-28 14:54:22 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-04-01 15:36:43 -07:00
|
|
|
|
|
2019-10-03 16:47:40 -07:00
|
|
|
|
export function setNgReflectProperties(
|
fix(core): ngOnDestroy on multi providers called with incorrect context (#35840)
Currently destroy hooks are stored in memory as `[1, hook, 5, hook]` where
the numbers represent the index at which to find the context and `hook` is
the function to be invoked. This breaks down for `multi` providers,
because the value at the index will be an array of providers, resulting in
the hook being invoked with an array of all the multi provider values,
rather than the provider that was destroyed. In ViewEngine `ngOnDestroy`
wasn't being called for `multi` providers at all.
These changes fix the issue by changing the structure of the destroy hooks to `[1, hook, 5, [0, hook, 3, hook]]` where the indexes inside the inner array point to the provider inside of the multi provider array. Note that this is slightly different from the original design which called for the structure to be `[1, hook, 5, [hook, hook]`, because in the process of implementing it, I realized that we wouldn't get passing the correct context if only some of the `multi` providers have `ngOnDestroy` and others don't.
I've run the newly-added `view_destroy_hooks` benchmark against these changes and compared it to master. The difference seems to be insignificant (between 1% and 2% slower).
Fixes #35231.
PR Close #35840
2020-03-03 21:05:26 +01:00
|
|
|
|
lView: LView, element: RElement|RComment, type: TNodeType, dataValue: PropertyAliasValue,
|
2019-10-03 16:47:40 -07:00
|
|
|
|
value: any) {
|
2020-10-12 21:38:06 -07:00
|
|
|
|
if (type & (TNodeType.AnyRNode | TNodeType.Container)) {
|
2019-10-03 16:47:40 -07:00
|
|
|
|
/**
|
|
|
|
|
* dataValue is an array containing runtime input or output names for the directives:
|
|
|
|
|
* i+0: directive instance index
|
2019-11-13 17:06:54 +01:00
|
|
|
|
* i+1: privateName
|
2019-10-03 16:47:40 -07:00
|
|
|
|
*
|
|
|
|
|
* e.g. [0, 'change', 'change-minified']
|
2019-11-13 17:06:54 +01:00
|
|
|
|
* we want to set the reflected property with the privateName: dataValue[i+1]
|
2019-10-03 16:47:40 -07:00
|
|
|
|
*/
|
2019-11-13 17:06:54 +01:00
|
|
|
|
for (let i = 0; i < dataValue.length; i += 2) {
|
|
|
|
|
setNgReflectProperty(lView, element, type, dataValue[i + 1] as string, value);
|
2019-10-03 16:47:40 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-04 21:48:04 +02:00
|
|
|
|
function validateProperty(
|
2020-07-15 07:03:04 +02:00
|
|
|
|
tView: TView, element: RElement|RComment, propName: string, tNode: TNode): boolean {
|
2020-03-15 09:08:57 -07:00
|
|
|
|
// If `schemas` is set to `null`, that's an indication that this Component was compiled in AOT
|
|
|
|
|
// mode where this check happens at compile time. In JIT mode, `schemas` is always present and
|
|
|
|
|
// defined as an array (as an empty array in case `schemas` field is not defined) and we should
|
|
|
|
|
// execute the check below.
|
|
|
|
|
if (tView.schemas === null) return true;
|
|
|
|
|
|
2019-09-04 21:48:04 +02:00
|
|
|
|
// The property is considered valid if the element matches the schema, it exists on the element
|
|
|
|
|
// or it is synthetic, and we are in a browser context (web worker nodes should be skipped).
|
2020-10-12 16:57:07 -07:00
|
|
|
|
if (matchingSchemas(tView, tNode.value) || propName in element || isAnimationProp(propName)) {
|
2019-12-11 22:03:00 +01:00
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Note: `typeof Node` returns 'function' in most browsers, but on IE it is 'object' so we
|
|
|
|
|
// need to account for both here, while being careful for `typeof null` also returning 'object'.
|
|
|
|
|
return typeof Node === 'undefined' || Node === null || !(element instanceof Node);
|
2019-03-28 14:54:22 -07:00
|
|
|
|
}
|
|
|
|
|
|
2020-07-15 07:03:04 +02:00
|
|
|
|
export function matchingSchemas(tView: TView, tagName: string|null): boolean {
|
2020-01-30 14:57:44 -08:00
|
|
|
|
const schemas = tView.schemas;
|
2019-03-28 14:54:22 -07:00
|
|
|
|
|
|
|
|
|
if (schemas !== null) {
|
|
|
|
|
for (let i = 0; i < schemas.length; i++) {
|
|
|
|
|
const schema = schemas[i];
|
|
|
|
|
if (schema === NO_ERRORS_SCHEMA ||
|
|
|
|
|
schema === CUSTOM_ELEMENTS_SCHEMA && tagName && tagName.indexOf('-') > -1) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2020-04-02 20:58:03 +02:00
|
|
|
|
* Logs an error that a property is not supported on an element.
|
2019-09-04 21:48:04 +02:00
|
|
|
|
* @param propName Name of the invalid property.
|
|
|
|
|
* @param tNode Node on which we encountered the property.
|
|
|
|
|
*/
|
2020-04-02 20:58:03 +02:00
|
|
|
|
function logUnknownPropertyError(propName: string, tNode: TNode): void {
|
2020-10-12 16:57:07 -07:00
|
|
|
|
console.error(`Can't bind to '${propName}' since it isn't a known property of '${tNode.value}'.`);
|
2019-03-28 14:54:22 -07:00
|
|
|
|
}
|
2019-04-01 15:36:43 -07:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Instantiate a root component.
|
|
|
|
|
*/
|
2019-10-21 10:38:21 +02:00
|
|
|
|
export function instantiateRootComponent<T>(tView: TView, lView: LView, def: ComponentDef<T>): T {
|
2020-09-14 13:43:44 -07:00
|
|
|
|
const rootTNode = getCurrentTNode()!;
|
2019-11-01 13:06:17 -07:00
|
|
|
|
if (tView.firstCreatePass) {
|
2019-04-01 15:36:43 -07:00
|
|
|
|
if (def.providersResolver) def.providersResolver(def);
|
2020-10-15 17:04:41 -07:00
|
|
|
|
const directiveIndex = allocExpando(tView, lView, 1, null);
|
|
|
|
|
ngDevMode &&
|
|
|
|
|
assertEqual(
|
|
|
|
|
directiveIndex, rootTNode.directiveStart,
|
|
|
|
|
'Because this is a root component the allocated expando should match the TNode component.');
|
|
|
|
|
configureViewWithDirective(tView, rootTNode, lView, directiveIndex, def);
|
2019-04-01 15:36:43 -07:00
|
|
|
|
}
|
2020-10-15 17:04:41 -07:00
|
|
|
|
const directive =
|
|
|
|
|
getNodeInjectable(lView, tView, rootTNode.directiveStart, rootTNode as TElementNode);
|
2019-10-21 10:38:21 +02:00
|
|
|
|
attachPatchData(directive, lView);
|
|
|
|
|
const native = getNativeByTNode(rootTNode, lView);
|
|
|
|
|
if (native) {
|
|
|
|
|
attachPatchData(native, lView);
|
|
|
|
|
}
|
2019-04-01 15:36:43 -07:00
|
|
|
|
return directive;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Resolve the matched directives on a node.
|
|
|
|
|
*/
|
2019-06-07 10:55:48 +02:00
|
|
|
|
export function resolveDirectives(
|
fix(core): ngOnDestroy on multi providers called with incorrect context (#35840)
Currently destroy hooks are stored in memory as `[1, hook, 5, hook]` where
the numbers represent the index at which to find the context and `hook` is
the function to be invoked. This breaks down for `multi` providers,
because the value at the index will be an array of providers, resulting in
the hook being invoked with an array of all the multi provider values,
rather than the provider that was destroyed. In ViewEngine `ngOnDestroy`
wasn't being called for `multi` providers at all.
These changes fix the issue by changing the structure of the destroy hooks to `[1, hook, 5, [0, hook, 3, hook]]` where the indexes inside the inner array point to the provider inside of the multi provider array. Note that this is slightly different from the original design which called for the structure to be `[1, hook, 5, [hook, hook]`, because in the process of implementing it, I realized that we wouldn't get passing the correct context if only some of the `multi` providers have `ngOnDestroy` and others don't.
I've run the newly-added `view_destroy_hooks` benchmark against these changes and compared it to master. The difference seems to be insignificant (between 1% and 2% slower).
Fixes #35231.
PR Close #35840
2020-03-03 21:05:26 +01:00
|
|
|
|
tView: TView, lView: LView, tNode: TElementNode|TContainerNode|TElementContainerNode,
|
|
|
|
|
localRefs: string[]|null): boolean {
|
2019-05-15 21:08:31 -07:00
|
|
|
|
// Please make sure to have explicit type for `exportsMap`. Inferred type triggers bug in
|
|
|
|
|
// tsickle.
|
2019-11-01 13:06:17 -07:00
|
|
|
|
ngDevMode && assertFirstCreatePass(tView);
|
2019-06-07 10:55:48 +02:00
|
|
|
|
|
2019-10-26 12:44:03 +02:00
|
|
|
|
let hasDirectives = false;
|
2020-01-09 21:48:16 -08:00
|
|
|
|
if (getBindingsEnabled()) {
|
|
|
|
|
const directiveDefs: DirectiveDef<any>[]|null = findDirectiveDefMatches(tView, lView, tNode);
|
fix(core): ngOnDestroy on multi providers called with incorrect context (#35840)
Currently destroy hooks are stored in memory as `[1, hook, 5, hook]` where
the numbers represent the index at which to find the context and `hook` is
the function to be invoked. This breaks down for `multi` providers,
because the value at the index will be an array of providers, resulting in
the hook being invoked with an array of all the multi provider values,
rather than the provider that was destroyed. In ViewEngine `ngOnDestroy`
wasn't being called for `multi` providers at all.
These changes fix the issue by changing the structure of the destroy hooks to `[1, hook, 5, [0, hook, 3, hook]]` where the indexes inside the inner array point to the provider inside of the multi provider array. Note that this is slightly different from the original design which called for the structure to be `[1, hook, 5, [hook, hook]`, because in the process of implementing it, I realized that we wouldn't get passing the correct context if only some of the `multi` providers have `ngOnDestroy` and others don't.
I've run the newly-added `view_destroy_hooks` benchmark against these changes and compared it to master. The difference seems to be insignificant (between 1% and 2% slower).
Fixes #35231.
PR Close #35840
2020-03-03 21:05:26 +01:00
|
|
|
|
const exportsMap: ({[key: string]: number}|null) = localRefs === null ? null : {'': -1};
|
2020-01-09 21:48:16 -08:00
|
|
|
|
|
|
|
|
|
if (directiveDefs !== null) {
|
|
|
|
|
hasDirectives = true;
|
|
|
|
|
initTNodeFlags(tNode, tView.data.length, directiveDefs.length);
|
|
|
|
|
// When the same token is provided by several directives on the same node, some rules apply in
|
|
|
|
|
// the viewEngine:
|
|
|
|
|
// - viewProviders have priority over providers
|
|
|
|
|
// - the last directive in NgModule.declarations has priority over the previous one
|
|
|
|
|
// So to match these rules, the order in which providers are added in the arrays is very
|
|
|
|
|
// important.
|
|
|
|
|
for (let i = 0; i < directiveDefs.length; i++) {
|
|
|
|
|
const def = directiveDefs[i];
|
|
|
|
|
if (def.providersResolver) def.providersResolver(def);
|
2019-11-24 20:56:18 -08:00
|
|
|
|
}
|
2020-01-09 21:48:16 -08:00
|
|
|
|
let preOrderHooksFound = false;
|
|
|
|
|
let preOrderCheckHooksFound = false;
|
2020-10-15 17:04:41 -07:00
|
|
|
|
let directiveIdx = allocExpando(tView, lView, directiveDefs.length, null);
|
|
|
|
|
ngDevMode &&
|
|
|
|
|
assertSame(
|
|
|
|
|
directiveIdx, tNode.directiveStart,
|
|
|
|
|
'TNode.directiveStart should point to just allocated space');
|
|
|
|
|
|
2020-01-09 21:48:16 -08:00
|
|
|
|
for (let i = 0; i < directiveDefs.length; i++) {
|
|
|
|
|
const def = directiveDefs[i];
|
|
|
|
|
// Merge the attrs in the order of matches. This assumes that the first directive is the
|
|
|
|
|
// component itself, so that the component has the least priority.
|
|
|
|
|
tNode.mergedAttrs = mergeHostAttrs(tNode.mergedAttrs, def.hostAttrs);
|
|
|
|
|
|
2020-10-15 17:04:41 -07:00
|
|
|
|
configureViewWithDirective(tView, tNode, lView, directiveIdx, def);
|
|
|
|
|
saveNameToExportMap(directiveIdx, def, exportsMap);
|
2020-01-09 21:48:16 -08:00
|
|
|
|
|
|
|
|
|
if (def.contentQueries !== null) tNode.flags |= TNodeFlags.hasContentQuery;
|
|
|
|
|
if (def.hostBindings !== null || def.hostAttrs !== null || def.hostVars !== 0)
|
|
|
|
|
tNode.flags |= TNodeFlags.hasHostBindings;
|
|
|
|
|
|
2020-02-14 18:02:11 -08:00
|
|
|
|
const lifeCycleHooks: OnChanges&OnInit&DoCheck = def.type.prototype;
|
2020-01-09 21:48:16 -08:00
|
|
|
|
// Only push a node index into the preOrderHooks array if this is the first
|
|
|
|
|
// pre-order hook found on this node.
|
2020-02-14 18:02:11 -08:00
|
|
|
|
if (!preOrderHooksFound &&
|
|
|
|
|
(lifeCycleHooks.ngOnChanges || lifeCycleHooks.ngOnInit || lifeCycleHooks.ngDoCheck)) {
|
2020-01-09 21:48:16 -08:00
|
|
|
|
// We will push the actual hook function into this array later during dir instantiation.
|
|
|
|
|
// We cannot do it now because we must ensure hooks are registered in the same
|
|
|
|
|
// order that directives are created (i.e. injection order).
|
2020-10-13 22:00:43 -07:00
|
|
|
|
(tView.preOrderHooks || (tView.preOrderHooks = [])).push(tNode.index);
|
2020-01-09 21:48:16 -08:00
|
|
|
|
preOrderHooksFound = true;
|
|
|
|
|
}
|
2019-11-24 20:56:18 -08:00
|
|
|
|
|
2020-02-14 18:02:11 -08:00
|
|
|
|
if (!preOrderCheckHooksFound && (lifeCycleHooks.ngOnChanges || lifeCycleHooks.ngDoCheck)) {
|
2020-10-13 22:00:43 -07:00
|
|
|
|
(tView.preOrderCheckHooks || (tView.preOrderCheckHooks = [])).push(tNode.index);
|
2020-01-09 21:48:16 -08:00
|
|
|
|
preOrderCheckHooksFound = true;
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-15 17:04:41 -07:00
|
|
|
|
directiveIdx++;
|
2019-11-24 20:56:18 -08:00
|
|
|
|
}
|
2020-01-09 12:05:40 -08:00
|
|
|
|
|
2020-01-09 21:48:16 -08:00
|
|
|
|
initializeInputAndOutputAliases(tView, tNode);
|
2019-04-01 15:36:43 -07:00
|
|
|
|
}
|
2020-01-09 21:48:16 -08:00
|
|
|
|
if (exportsMap) cacheMatchingLocalNames(tNode, localRefs, exportsMap);
|
2019-04-01 15:36:43 -07:00
|
|
|
|
}
|
2020-01-09 21:48:16 -08:00
|
|
|
|
// Merge the template attrs last so that they have the highest priority.
|
|
|
|
|
tNode.mergedAttrs = mergeHostAttrs(tNode.mergedAttrs, tNode.attrs);
|
2019-10-26 12:44:03 +02:00
|
|
|
|
return hasDirectives;
|
2019-04-01 15:36:43 -07:00
|
|
|
|
}
|
|
|
|
|
|
2020-01-09 12:05:40 -08:00
|
|
|
|
/**
|
2020-10-15 17:04:41 -07:00
|
|
|
|
* Add `hostBindings` to the `TView.hostBindingOpCodes`.
|
2020-01-09 12:05:40 -08:00
|
|
|
|
*
|
|
|
|
|
* @param tView `TView` to which the `hostBindings` should be added.
|
2020-10-15 17:04:41 -07:00
|
|
|
|
* @param tNode `TNode` the element which contains the directive
|
|
|
|
|
* @param lView `LView` current `LView`
|
|
|
|
|
* @param directiveIdx Directive index in view.
|
|
|
|
|
* @param directiveVarsIdx Where will the directive's vars be stored
|
2020-01-09 12:05:40 -08:00
|
|
|
|
* @param def `ComponentDef`/`DirectiveDef`, which contains the `hostVars`/`hostBindings` to add.
|
|
|
|
|
*/
|
2020-10-15 17:04:41 -07:00
|
|
|
|
export function registerHostBindingOpCodes(
|
|
|
|
|
tView: TView, tNode: TNode, lView: LView, directiveIdx: number, directiveVarsIdx: number,
|
|
|
|
|
def: ComponentDef<any>|DirectiveDef<any>): void {
|
2020-01-09 12:05:40 -08:00
|
|
|
|
ngDevMode && assertFirstCreatePass(tView);
|
2020-10-15 17:04:41 -07:00
|
|
|
|
|
|
|
|
|
const hostBindings = def.hostBindings;
|
|
|
|
|
if (hostBindings) {
|
|
|
|
|
let hostBindingOpCodes = tView.hostBindingOpCodes;
|
|
|
|
|
if (hostBindingOpCodes === null) {
|
|
|
|
|
hostBindingOpCodes = tView.hostBindingOpCodes = [] as any as HostBindingOpCodes;
|
|
|
|
|
}
|
|
|
|
|
const elementIndx = ~tNode.index;
|
|
|
|
|
if (lastSelectedElementIdx(hostBindingOpCodes) != elementIndx) {
|
|
|
|
|
// Conditionally add select element so that we are more efficient in execution.
|
|
|
|
|
// NOTE: this is strictly not necessary and it trades code size for runtime perf.
|
|
|
|
|
// (We could just always add it.)
|
|
|
|
|
hostBindingOpCodes.push(elementIndx);
|
|
|
|
|
}
|
|
|
|
|
hostBindingOpCodes.push(directiveIdx, directiveVarsIdx, hostBindings);
|
2020-01-09 12:05:40 -08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2020-10-15 17:04:41 -07:00
|
|
|
|
* Returns the last selected element index in the `HostBindingOpCodes`
|
2020-01-09 12:05:40 -08:00
|
|
|
|
*
|
2020-10-15 17:04:41 -07:00
|
|
|
|
* For perf reasons we don't need to update the selected element index in `HostBindingOpCodes` only
|
|
|
|
|
* if it changes. This method returns the last index (or '0' if not found.)
|
2020-01-09 12:05:40 -08:00
|
|
|
|
*
|
2020-10-15 17:04:41 -07:00
|
|
|
|
* Selected element index are only the ones which are negative.
|
2020-01-09 12:05:40 -08:00
|
|
|
|
*/
|
2020-10-15 17:04:41 -07:00
|
|
|
|
function lastSelectedElementIdx(hostBindingOpCodes: HostBindingOpCodes): number {
|
|
|
|
|
let i = hostBindingOpCodes.length;
|
|
|
|
|
while (i > 0) {
|
|
|
|
|
const value = hostBindingOpCodes[--i];
|
|
|
|
|
if (typeof value === 'number' && value < 0) {
|
|
|
|
|
return value;
|
|
|
|
|
}
|
2020-01-09 12:05:40 -08:00
|
|
|
|
}
|
2020-10-15 17:04:41 -07:00
|
|
|
|
return 0;
|
2020-01-09 12:05:40 -08:00
|
|
|
|
}
|
|
|
|
|
|
2020-10-15 17:04:41 -07:00
|
|
|
|
|
2019-04-01 15:36:43 -07:00
|
|
|
|
/**
|
|
|
|
|
* Instantiate all the directives that were previously resolved on the current node.
|
|
|
|
|
*/
|
2019-10-21 10:38:21 +02:00
|
|
|
|
function instantiateAllDirectives(
|
|
|
|
|
tView: TView, lView: LView, tNode: TDirectiveHostNode, native: RNode) {
|
2019-04-01 15:36:43 -07:00
|
|
|
|
const start = tNode.directiveStart;
|
|
|
|
|
const end = tNode.directiveEnd;
|
2019-11-01 13:06:17 -07:00
|
|
|
|
if (!tView.firstCreatePass) {
|
2019-10-18 16:22:44 +02:00
|
|
|
|
getOrCreateNodeInjectorForNode(tNode, lView);
|
2019-04-01 15:36:43 -07:00
|
|
|
|
}
|
2019-10-18 16:35:28 +02:00
|
|
|
|
|
2019-10-21 10:38:21 +02:00
|
|
|
|
attachPatchData(native, lView);
|
|
|
|
|
|
2019-10-22 16:42:23 +02:00
|
|
|
|
const initialInputs = tNode.initialInputs;
|
2019-04-01 15:36:43 -07:00
|
|
|
|
for (let i = start; i < end; i++) {
|
|
|
|
|
const def = tView.data[i] as DirectiveDef<any>;
|
2019-10-18 16:35:28 +02:00
|
|
|
|
const isComponent = isComponentDef(def);
|
|
|
|
|
|
|
|
|
|
if (isComponent) {
|
2020-10-12 21:38:06 -07:00
|
|
|
|
ngDevMode && assertTNodeType(tNode, TNodeType.AnyRNode);
|
2019-10-18 16:35:28 +02:00
|
|
|
|
addComponentLogic(lView, tNode as TElementNode, def as ComponentDef<any>);
|
2019-04-01 15:36:43 -07:00
|
|
|
|
}
|
2019-10-18 16:35:28 +02:00
|
|
|
|
|
2019-11-24 20:56:18 -08:00
|
|
|
|
const directive = getNodeInjectable(lView, tView, i, tNode);
|
2019-10-21 10:38:21 +02:00
|
|
|
|
attachPatchData(directive, lView);
|
2019-10-18 16:35:28 +02:00
|
|
|
|
|
2019-10-22 16:42:23 +02:00
|
|
|
|
if (initialInputs !== null) {
|
fix(core): ngOnDestroy on multi providers called with incorrect context (#35840)
Currently destroy hooks are stored in memory as `[1, hook, 5, hook]` where
the numbers represent the index at which to find the context and `hook` is
the function to be invoked. This breaks down for `multi` providers,
because the value at the index will be an array of providers, resulting in
the hook being invoked with an array of all the multi provider values,
rather than the provider that was destroyed. In ViewEngine `ngOnDestroy`
wasn't being called for `multi` providers at all.
These changes fix the issue by changing the structure of the destroy hooks to `[1, hook, 5, [0, hook, 3, hook]]` where the indexes inside the inner array point to the provider inside of the multi provider array. Note that this is slightly different from the original design which called for the structure to be `[1, hook, 5, [hook, hook]`, because in the process of implementing it, I realized that we wouldn't get passing the correct context if only some of the `multi` providers have `ngOnDestroy` and others don't.
I've run the newly-added `view_destroy_hooks` benchmark against these changes and compared it to master. The difference seems to be insignificant (between 1% and 2% slower).
Fixes #35231.
PR Close #35840
2020-03-03 21:05:26 +01:00
|
|
|
|
setInputsFromAttrs(lView, i - start, directive, def, tNode, initialInputs!);
|
2019-10-18 16:35:28 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (isComponent) {
|
|
|
|
|
const componentView = getComponentLViewByIndex(tNode.index, lView);
|
|
|
|
|
componentView[CONTEXT] = directive;
|
|
|
|
|
}
|
2019-04-01 15:36:43 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-17 15:40:37 -08:00
|
|
|
|
function invokeDirectivesHostBindings(tView: TView, lView: LView, tNode: TNode) {
|
2019-04-01 15:36:43 -07:00
|
|
|
|
const start = tNode.directiveStart;
|
|
|
|
|
const end = tNode.directiveEnd;
|
2019-11-01 13:06:17 -07:00
|
|
|
|
const firstCreatePass = tView.firstCreatePass;
|
2020-10-13 22:00:43 -07:00
|
|
|
|
const elementIndex = tNode.index;
|
2020-02-18 17:49:37 -08:00
|
|
|
|
const currentDirectiveIndex = getCurrentDirectiveIndex();
|
2019-04-16 17:17:12 -07:00
|
|
|
|
try {
|
2020-01-28 16:26:56 -08:00
|
|
|
|
setSelectedIndex(elementIndex);
|
2020-02-18 17:49:37 -08:00
|
|
|
|
for (let dirIndex = start; dirIndex < end; dirIndex++) {
|
|
|
|
|
const def = tView.data[dirIndex] as DirectiveDef<unknown>;
|
|
|
|
|
const directive = lView[dirIndex];
|
|
|
|
|
setCurrentDirectiveIndex(dirIndex);
|
2020-01-08 11:32:33 -08:00
|
|
|
|
if (def.hostBindings !== null || def.hostVars !== 0 || def.hostAttrs !== null) {
|
2020-01-25 12:38:42 +01:00
|
|
|
|
invokeHostBindingsInCreationMode(def, directive);
|
2019-04-16 17:17:12 -07:00
|
|
|
|
}
|
2019-04-01 15:36:43 -07:00
|
|
|
|
}
|
2019-04-16 17:17:12 -07:00
|
|
|
|
} finally {
|
2020-01-28 16:26:56 -08:00
|
|
|
|
setSelectedIndex(-1);
|
2020-02-18 17:49:37 -08:00
|
|
|
|
setCurrentDirectiveIndex(currentDirectiveIndex);
|
2019-04-01 15:36:43 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-17 15:40:37 -08:00
|
|
|
|
/**
|
|
|
|
|
* Invoke the host bindings in creation mode.
|
|
|
|
|
*
|
|
|
|
|
* @param def `DirectiveDef` which may contain the `hostBindings` function.
|
|
|
|
|
* @param directive Instance of directive.
|
|
|
|
|
*/
|
2020-01-25 12:38:42 +01:00
|
|
|
|
export function invokeHostBindingsInCreationMode(def: DirectiveDef<any>, directive: any) {
|
2020-01-08 11:32:33 -08:00
|
|
|
|
if (def.hostBindings !== null) {
|
fix(core): ngOnDestroy on multi providers called with incorrect context (#35840)
Currently destroy hooks are stored in memory as `[1, hook, 5, hook]` where
the numbers represent the index at which to find the context and `hook` is
the function to be invoked. This breaks down for `multi` providers,
because the value at the index will be an array of providers, resulting in
the hook being invoked with an array of all the multi provider values,
rather than the provider that was destroyed. In ViewEngine `ngOnDestroy`
wasn't being called for `multi` providers at all.
These changes fix the issue by changing the structure of the destroy hooks to `[1, hook, 5, [0, hook, 3, hook]]` where the indexes inside the inner array point to the provider inside of the multi provider array. Note that this is slightly different from the original design which called for the structure to be `[1, hook, 5, [hook, hook]`, because in the process of implementing it, I realized that we wouldn't get passing the correct context if only some of the `multi` providers have `ngOnDestroy` and others don't.
I've run the newly-added `view_destroy_hooks` benchmark against these changes and compared it to master. The difference seems to be insignificant (between 1% and 2% slower).
Fixes #35231.
PR Close #35840
2020-03-03 21:05:26 +01:00
|
|
|
|
def.hostBindings!(RenderFlags.Create, directive);
|
2020-01-08 11:32:33 -08:00
|
|
|
|
}
|
2019-04-01 15:36:43 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
fix(core): ngOnDestroy on multi providers called with incorrect context (#35840)
Currently destroy hooks are stored in memory as `[1, hook, 5, hook]` where
the numbers represent the index at which to find the context and `hook` is
the function to be invoked. This breaks down for `multi` providers,
because the value at the index will be an array of providers, resulting in
the hook being invoked with an array of all the multi provider values,
rather than the provider that was destroyed. In ViewEngine `ngOnDestroy`
wasn't being called for `multi` providers at all.
These changes fix the issue by changing the structure of the destroy hooks to `[1, hook, 5, [0, hook, 3, hook]]` where the indexes inside the inner array point to the provider inside of the multi provider array. Note that this is slightly different from the original design which called for the structure to be `[1, hook, 5, [hook, hook]`, because in the process of implementing it, I realized that we wouldn't get passing the correct context if only some of the `multi` providers have `ngOnDestroy` and others don't.
I've run the newly-added `view_destroy_hooks` benchmark against these changes and compared it to master. The difference seems to be insignificant (between 1% and 2% slower).
Fixes #35231.
PR Close #35840
2020-03-03 21:05:26 +01:00
|
|
|
|
* Matches the current node against all available selectors.
|
|
|
|
|
* If a component is matched (at most one), it is returned in first position in the array.
|
|
|
|
|
*/
|
2020-01-09 21:48:16 -08:00
|
|
|
|
function findDirectiveDefMatches(
|
2019-07-10 18:02:19 +02:00
|
|
|
|
tView: TView, viewData: LView,
|
fix(core): ngOnDestroy on multi providers called with incorrect context (#35840)
Currently destroy hooks are stored in memory as `[1, hook, 5, hook]` where
the numbers represent the index at which to find the context and `hook` is
the function to be invoked. This breaks down for `multi` providers,
because the value at the index will be an array of providers, resulting in
the hook being invoked with an array of all the multi provider values,
rather than the provider that was destroyed. In ViewEngine `ngOnDestroy`
wasn't being called for `multi` providers at all.
These changes fix the issue by changing the structure of the destroy hooks to `[1, hook, 5, [0, hook, 3, hook]]` where the indexes inside the inner array point to the provider inside of the multi provider array. Note that this is slightly different from the original design which called for the structure to be `[1, hook, 5, [hook, hook]`, because in the process of implementing it, I realized that we wouldn't get passing the correct context if only some of the `multi` providers have `ngOnDestroy` and others don't.
I've run the newly-added `view_destroy_hooks` benchmark against these changes and compared it to master. The difference seems to be insignificant (between 1% and 2% slower).
Fixes #35231.
PR Close #35840
2020-03-03 21:05:26 +01:00
|
|
|
|
tNode: TElementNode|TContainerNode|TElementContainerNode): DirectiveDef<any>[]|null {
|
2019-11-01 13:06:17 -07:00
|
|
|
|
ngDevMode && assertFirstCreatePass(tView);
|
2020-10-12 21:38:06 -07:00
|
|
|
|
ngDevMode && assertTNodeType(tNode, TNodeType.AnyRNode | TNodeType.AnyContainer);
|
2020-08-27 08:40:19 -04:00
|
|
|
|
|
2019-04-01 15:36:43 -07:00
|
|
|
|
const registry = tView.directiveRegistry;
|
|
|
|
|
let matches: any[]|null = null;
|
|
|
|
|
if (registry) {
|
|
|
|
|
for (let i = 0; i < registry.length; i++) {
|
|
|
|
|
const def = registry[i] as ComponentDef<any>| DirectiveDef<any>;
|
fix(core): ngOnDestroy on multi providers called with incorrect context (#35840)
Currently destroy hooks are stored in memory as `[1, hook, 5, hook]` where
the numbers represent the index at which to find the context and `hook` is
the function to be invoked. This breaks down for `multi` providers,
because the value at the index will be an array of providers, resulting in
the hook being invoked with an array of all the multi provider values,
rather than the provider that was destroyed. In ViewEngine `ngOnDestroy`
wasn't being called for `multi` providers at all.
These changes fix the issue by changing the structure of the destroy hooks to `[1, hook, 5, [0, hook, 3, hook]]` where the indexes inside the inner array point to the provider inside of the multi provider array. Note that this is slightly different from the original design which called for the structure to be `[1, hook, 5, [hook, hook]`, because in the process of implementing it, I realized that we wouldn't get passing the correct context if only some of the `multi` providers have `ngOnDestroy` and others don't.
I've run the newly-added `view_destroy_hooks` benchmark against these changes and compared it to master. The difference seems to be insignificant (between 1% and 2% slower).
Fixes #35231.
PR Close #35840
2020-03-03 21:05:26 +01:00
|
|
|
|
if (isNodeMatchingSelectorList(tNode, def.selectors!, /* isProjectionMode */ false)) {
|
2019-08-15 14:14:25 +01:00
|
|
|
|
matches || (matches = ngDevMode ? new MatchesArray() : []);
|
2019-07-10 18:02:19 +02:00
|
|
|
|
diPublicInInjector(getOrCreateNodeInjectorForNode(tNode, viewData), tView, def.type);
|
2019-04-01 15:36:43 -07:00
|
|
|
|
|
|
|
|
|
if (isComponentDef(def)) {
|
2020-08-27 08:40:19 -04:00
|
|
|
|
if (ngDevMode) {
|
2020-10-12 21:38:06 -07:00
|
|
|
|
assertTNodeType(
|
|
|
|
|
tNode, TNodeType.Element,
|
2020-10-12 16:57:07 -07:00
|
|
|
|
`"${tNode.value}" tags cannot be used as component hosts. ` +
|
2020-08-27 08:40:19 -04:00
|
|
|
|
`Please use a different tag to activate the ${stringify(def.type)} component.`);
|
|
|
|
|
|
|
|
|
|
if (tNode.flags & TNodeFlags.isComponentHost) throwMultipleComponentError(tNode);
|
|
|
|
|
}
|
2019-08-01 14:00:23 +02:00
|
|
|
|
markAsComponentHost(tView, tNode);
|
2019-04-01 15:36:43 -07:00
|
|
|
|
// The component is always stored first with directives after.
|
|
|
|
|
matches.unshift(def);
|
|
|
|
|
} else {
|
|
|
|
|
matches.push(def);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return matches;
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-01 14:00:23 +02:00
|
|
|
|
/**
|
|
|
|
|
* Marks a given TNode as a component's host. This consists of:
|
|
|
|
|
* - setting appropriate TNode flags;
|
|
|
|
|
* - storing index of component's host element so it will be queued for view refresh during CD.
|
fix(core): ngOnDestroy on multi providers called with incorrect context (#35840)
Currently destroy hooks are stored in memory as `[1, hook, 5, hook]` where
the numbers represent the index at which to find the context and `hook` is
the function to be invoked. This breaks down for `multi` providers,
because the value at the index will be an array of providers, resulting in
the hook being invoked with an array of all the multi provider values,
rather than the provider that was destroyed. In ViewEngine `ngOnDestroy`
wasn't being called for `multi` providers at all.
These changes fix the issue by changing the structure of the destroy hooks to `[1, hook, 5, [0, hook, 3, hook]]` where the indexes inside the inner array point to the provider inside of the multi provider array. Note that this is slightly different from the original design which called for the structure to be `[1, hook, 5, [hook, hook]`, because in the process of implementing it, I realized that we wouldn't get passing the correct context if only some of the `multi` providers have `ngOnDestroy` and others don't.
I've run the newly-added `view_destroy_hooks` benchmark against these changes and compared it to master. The difference seems to be insignificant (between 1% and 2% slower).
Fixes #35231.
PR Close #35840
2020-03-03 21:05:26 +01:00
|
|
|
|
*/
|
2019-08-01 14:00:23 +02:00
|
|
|
|
export function markAsComponentHost(tView: TView, hostTNode: TNode): void {
|
2019-11-01 13:06:17 -07:00
|
|
|
|
ngDevMode && assertFirstCreatePass(tView);
|
2019-10-03 15:02:10 +02:00
|
|
|
|
hostTNode.flags |= TNodeFlags.isComponentHost;
|
fix(core): ngOnDestroy on multi providers called with incorrect context (#35840)
Currently destroy hooks are stored in memory as `[1, hook, 5, hook]` where
the numbers represent the index at which to find the context and `hook` is
the function to be invoked. This breaks down for `multi` providers,
because the value at the index will be an array of providers, resulting in
the hook being invoked with an array of all the multi provider values,
rather than the provider that was destroyed. In ViewEngine `ngOnDestroy`
wasn't being called for `multi` providers at all.
These changes fix the issue by changing the structure of the destroy hooks to `[1, hook, 5, [0, hook, 3, hook]]` where the indexes inside the inner array point to the provider inside of the multi provider array. Note that this is slightly different from the original design which called for the structure to be `[1, hook, 5, [hook, hook]`, because in the process of implementing it, I realized that we wouldn't get passing the correct context if only some of the `multi` providers have `ngOnDestroy` and others don't.
I've run the newly-added `view_destroy_hooks` benchmark against these changes and compared it to master. The difference seems to be insignificant (between 1% and 2% slower).
Fixes #35231.
PR Close #35840
2020-03-03 21:05:26 +01:00
|
|
|
|
(tView.components || (tView.components = ngDevMode ? new TViewComponents() : []))
|
|
|
|
|
.push(hostTNode.index);
|
2019-04-01 15:36:43 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** Caches local names and their matching directive indices for query and template lookups. */
|
|
|
|
|
function cacheMatchingLocalNames(
|
fix(core): ngOnDestroy on multi providers called with incorrect context (#35840)
Currently destroy hooks are stored in memory as `[1, hook, 5, hook]` where
the numbers represent the index at which to find the context and `hook` is
the function to be invoked. This breaks down for `multi` providers,
because the value at the index will be an array of providers, resulting in
the hook being invoked with an array of all the multi provider values,
rather than the provider that was destroyed. In ViewEngine `ngOnDestroy`
wasn't being called for `multi` providers at all.
These changes fix the issue by changing the structure of the destroy hooks to `[1, hook, 5, [0, hook, 3, hook]]` where the indexes inside the inner array point to the provider inside of the multi provider array. Note that this is slightly different from the original design which called for the structure to be `[1, hook, 5, [hook, hook]`, because in the process of implementing it, I realized that we wouldn't get passing the correct context if only some of the `multi` providers have `ngOnDestroy` and others don't.
I've run the newly-added `view_destroy_hooks` benchmark against these changes and compared it to master. The difference seems to be insignificant (between 1% and 2% slower).
Fixes #35231.
PR Close #35840
2020-03-03 21:05:26 +01:00
|
|
|
|
tNode: TNode, localRefs: string[]|null, exportsMap: {[key: string]: number}): void {
|
2019-04-01 15:36:43 -07:00
|
|
|
|
if (localRefs) {
|
fix(core): ngOnDestroy on multi providers called with incorrect context (#35840)
Currently destroy hooks are stored in memory as `[1, hook, 5, hook]` where
the numbers represent the index at which to find the context and `hook` is
the function to be invoked. This breaks down for `multi` providers,
because the value at the index will be an array of providers, resulting in
the hook being invoked with an array of all the multi provider values,
rather than the provider that was destroyed. In ViewEngine `ngOnDestroy`
wasn't being called for `multi` providers at all.
These changes fix the issue by changing the structure of the destroy hooks to `[1, hook, 5, [0, hook, 3, hook]]` where the indexes inside the inner array point to the provider inside of the multi provider array. Note that this is slightly different from the original design which called for the structure to be `[1, hook, 5, [hook, hook]`, because in the process of implementing it, I realized that we wouldn't get passing the correct context if only some of the `multi` providers have `ngOnDestroy` and others don't.
I've run the newly-added `view_destroy_hooks` benchmark against these changes and compared it to master. The difference seems to be insignificant (between 1% and 2% slower).
Fixes #35231.
PR Close #35840
2020-03-03 21:05:26 +01:00
|
|
|
|
const localNames: (string|number)[] = tNode.localNames = ngDevMode ? new TNodeLocalNames() : [];
|
2019-04-01 15:36:43 -07:00
|
|
|
|
|
|
|
|
|
// Local names must be stored in tNode in the same order that localRefs are defined
|
|
|
|
|
// in the template to ensure the data is loaded in the same slots as their refs
|
|
|
|
|
// in the template (for template queries).
|
|
|
|
|
for (let i = 0; i < localRefs.length; i += 2) {
|
|
|
|
|
const index = exportsMap[localRefs[i + 1]];
|
|
|
|
|
if (index == null) throw new Error(`Export of name '${localRefs[i + 1]}' not found!`);
|
|
|
|
|
localNames.push(localRefs[i], index);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
fix(core): ngOnDestroy on multi providers called with incorrect context (#35840)
Currently destroy hooks are stored in memory as `[1, hook, 5, hook]` where
the numbers represent the index at which to find the context and `hook` is
the function to be invoked. This breaks down for `multi` providers,
because the value at the index will be an array of providers, resulting in
the hook being invoked with an array of all the multi provider values,
rather than the provider that was destroyed. In ViewEngine `ngOnDestroy`
wasn't being called for `multi` providers at all.
These changes fix the issue by changing the structure of the destroy hooks to `[1, hook, 5, [0, hook, 3, hook]]` where the indexes inside the inner array point to the provider inside of the multi provider array. Note that this is slightly different from the original design which called for the structure to be `[1, hook, 5, [hook, hook]`, because in the process of implementing it, I realized that we wouldn't get passing the correct context if only some of the `multi` providers have `ngOnDestroy` and others don't.
I've run the newly-added `view_destroy_hooks` benchmark against these changes and compared it to master. The difference seems to be insignificant (between 1% and 2% slower).
Fixes #35231.
PR Close #35840
2020-03-03 21:05:26 +01:00
|
|
|
|
* Builds up an export map as directives are created, so local refs can be quickly mapped
|
|
|
|
|
* to their directive instances.
|
|
|
|
|
*/
|
2019-04-01 15:36:43 -07:00
|
|
|
|
function saveNameToExportMap(
|
2020-10-15 17:04:41 -07:00
|
|
|
|
directiveIdx: number, def: DirectiveDef<any>|ComponentDef<any>,
|
fix(core): ngOnDestroy on multi providers called with incorrect context (#35840)
Currently destroy hooks are stored in memory as `[1, hook, 5, hook]` where
the numbers represent the index at which to find the context and `hook` is
the function to be invoked. This breaks down for `multi` providers,
because the value at the index will be an array of providers, resulting in
the hook being invoked with an array of all the multi provider values,
rather than the provider that was destroyed. In ViewEngine `ngOnDestroy`
wasn't being called for `multi` providers at all.
These changes fix the issue by changing the structure of the destroy hooks to `[1, hook, 5, [0, hook, 3, hook]]` where the indexes inside the inner array point to the provider inside of the multi provider array. Note that this is slightly different from the original design which called for the structure to be `[1, hook, 5, [hook, hook]`, because in the process of implementing it, I realized that we wouldn't get passing the correct context if only some of the `multi` providers have `ngOnDestroy` and others don't.
I've run the newly-added `view_destroy_hooks` benchmark against these changes and compared it to master. The difference seems to be insignificant (between 1% and 2% slower).
Fixes #35231.
PR Close #35840
2020-03-03 21:05:26 +01:00
|
|
|
|
exportsMap: {[key: string]: number}|null) {
|
2019-04-01 15:36:43 -07:00
|
|
|
|
if (exportsMap) {
|
|
|
|
|
if (def.exportAs) {
|
|
|
|
|
for (let i = 0; i < def.exportAs.length; i++) {
|
2020-10-15 17:04:41 -07:00
|
|
|
|
exportsMap[def.exportAs[i]] = directiveIdx;
|
2019-04-01 15:36:43 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
2020-10-15 17:04:41 -07:00
|
|
|
|
if (isComponentDef(def)) exportsMap[''] = directiveIdx;
|
2019-04-01 15:36:43 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Initializes the flags on the current node, setting all indices to the initial index,
|
|
|
|
|
* the directive count to 0, and adding the isComponent flag.
|
|
|
|
|
* @param index the initial index
|
|
|
|
|
*/
|
2020-01-09 21:48:16 -08:00
|
|
|
|
export function initTNodeFlags(tNode: TNode, index: number, numberOfDirectives: number) {
|
fix(core): ngOnDestroy on multi providers called with incorrect context (#35840)
Currently destroy hooks are stored in memory as `[1, hook, 5, hook]` where
the numbers represent the index at which to find the context and `hook` is
the function to be invoked. This breaks down for `multi` providers,
because the value at the index will be an array of providers, resulting in
the hook being invoked with an array of all the multi provider values,
rather than the provider that was destroyed. In ViewEngine `ngOnDestroy`
wasn't being called for `multi` providers at all.
These changes fix the issue by changing the structure of the destroy hooks to `[1, hook, 5, [0, hook, 3, hook]]` where the indexes inside the inner array point to the provider inside of the multi provider array. Note that this is slightly different from the original design which called for the structure to be `[1, hook, 5, [hook, hook]`, because in the process of implementing it, I realized that we wouldn't get passing the correct context if only some of the `multi` providers have `ngOnDestroy` and others don't.
I've run the newly-added `view_destroy_hooks` benchmark against these changes and compared it to master. The difference seems to be insignificant (between 1% and 2% slower).
Fixes #35231.
PR Close #35840
2020-03-03 21:05:26 +01:00
|
|
|
|
ngDevMode &&
|
|
|
|
|
assertNotEqual(
|
|
|
|
|
numberOfDirectives, tNode.directiveEnd - tNode.directiveStart,
|
|
|
|
|
'Reached the max number of directives');
|
2019-10-03 15:02:10 +02:00
|
|
|
|
tNode.flags |= TNodeFlags.isDirectiveHost;
|
2019-04-01 15:36:43 -07:00
|
|
|
|
// When the first directive is created on a node, save the index
|
|
|
|
|
tNode.directiveStart = index;
|
|
|
|
|
tNode.directiveEnd = index + numberOfDirectives;
|
|
|
|
|
tNode.providerIndexes = index;
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-15 17:04:41 -07:00
|
|
|
|
/**
|
|
|
|
|
* Setup directive for instantiation.
|
|
|
|
|
*
|
|
|
|
|
* We need to create a `NodeInjectorFactory` which is then inserted in both the `Blueprint` as well
|
|
|
|
|
* as `LView`. `TView` gets the `DirectiveDef`.
|
|
|
|
|
*
|
|
|
|
|
* @param tView `TView`
|
|
|
|
|
* @param tNode `TNode`
|
|
|
|
|
* @param lView `LView`
|
|
|
|
|
* @param directiveIndex Index where the directive will be stored in the Expando.
|
|
|
|
|
* @param def `DirectiveDef`
|
|
|
|
|
*/
|
|
|
|
|
function configureViewWithDirective<T>(
|
|
|
|
|
tView: TView, tNode: TNode, lView: LView, directiveIndex: number, def: DirectiveDef<T>): void {
|
|
|
|
|
ngDevMode &&
|
|
|
|
|
assertGreaterThanOrEqual(directiveIndex, HEADER_OFFSET, 'Must be in Expando section');
|
|
|
|
|
tView.data[directiveIndex] = def;
|
2020-01-08 11:32:33 -08:00
|
|
|
|
const directiveFactory =
|
fix(core): ngOnDestroy on multi providers called with incorrect context (#35840)
Currently destroy hooks are stored in memory as `[1, hook, 5, hook]` where
the numbers represent the index at which to find the context and `hook` is
the function to be invoked. This breaks down for `multi` providers,
because the value at the index will be an array of providers, resulting in
the hook being invoked with an array of all the multi provider values,
rather than the provider that was destroyed. In ViewEngine `ngOnDestroy`
wasn't being called for `multi` providers at all.
These changes fix the issue by changing the structure of the destroy hooks to `[1, hook, 5, [0, hook, 3, hook]]` where the indexes inside the inner array point to the provider inside of the multi provider array. Note that this is slightly different from the original design which called for the structure to be `[1, hook, 5, [hook, hook]`, because in the process of implementing it, I realized that we wouldn't get passing the correct context if only some of the `multi` providers have `ngOnDestroy` and others don't.
I've run the newly-added `view_destroy_hooks` benchmark against these changes and compared it to master. The difference seems to be insignificant (between 1% and 2% slower).
Fixes #35231.
PR Close #35840
2020-03-03 21:05:26 +01:00
|
|
|
|
def.factory || ((def as {factory: Function}).factory = getFactoryDef(def.type, true));
|
2019-04-01 15:36:43 -07:00
|
|
|
|
const nodeInjectorFactory = new NodeInjectorFactory(directiveFactory, isComponentDef(def), null);
|
2020-10-15 17:04:41 -07:00
|
|
|
|
tView.blueprint[directiveIndex] = nodeInjectorFactory;
|
|
|
|
|
lView[directiveIndex] = nodeInjectorFactory;
|
|
|
|
|
|
|
|
|
|
registerHostBindingOpCodes(
|
|
|
|
|
tView, tNode, lView, directiveIndex, allocExpando(tView, lView, def.hostVars, NO_CHANGE),
|
|
|
|
|
def);
|
2019-04-01 15:36:43 -07:00
|
|
|
|
}
|
|
|
|
|
|
2019-10-18 16:22:44 +02:00
|
|
|
|
function addComponentLogic<T>(lView: LView, hostTNode: TElementNode, def: ComponentDef<T>): void {
|
2019-08-27 12:02:30 +02:00
|
|
|
|
const native = getNativeByTNode(hostTNode, lView) as RElement;
|
2019-10-28 12:08:17 -07:00
|
|
|
|
const tView = getOrCreateTComponentView(def);
|
2019-04-01 15:36:43 -07:00
|
|
|
|
|
|
|
|
|
// Only component views should be added to the view tree directly. Embedded views are
|
|
|
|
|
// accessed through their containers because they may be removed / re-added later.
|
|
|
|
|
const rendererFactory = lView[RENDERER_FACTORY];
|
|
|
|
|
const componentView = addToViewTree(
|
2019-08-27 12:02:30 +02:00
|
|
|
|
lView,
|
|
|
|
|
createLView(
|
|
|
|
|
lView, tView, null, def.onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways, native,
|
2020-09-14 11:21:15 -07:00
|
|
|
|
hostTNode as TElementNode, rendererFactory, rendererFactory.createRenderer(native, def),
|
|
|
|
|
null, null));
|
2019-04-01 15:36:43 -07:00
|
|
|
|
|
|
|
|
|
// Component view will always be created before any injected LContainers,
|
|
|
|
|
// so this is a regular element, wrap it with the component view
|
2019-08-01 14:00:23 +02:00
|
|
|
|
lView[hostTNode.index] = componentView;
|
2019-05-31 10:39:14 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function elementAttributeInternal(
|
fix(core): ngOnDestroy on multi providers called with incorrect context (#35840)
Currently destroy hooks are stored in memory as `[1, hook, 5, hook]` where
the numbers represent the index at which to find the context and `hook` is
the function to be invoked. This breaks down for `multi` providers,
because the value at the index will be an array of providers, resulting in
the hook being invoked with an array of all the multi provider values,
rather than the provider that was destroyed. In ViewEngine `ngOnDestroy`
wasn't being called for `multi` providers at all.
These changes fix the issue by changing the structure of the destroy hooks to `[1, hook, 5, [0, hook, 3, hook]]` where the indexes inside the inner array point to the provider inside of the multi provider array. Note that this is slightly different from the original design which called for the structure to be `[1, hook, 5, [hook, hook]`, because in the process of implementing it, I realized that we wouldn't get passing the correct context if only some of the `multi` providers have `ngOnDestroy` and others don't.
I've run the newly-added `view_destroy_hooks` benchmark against these changes and compared it to master. The difference seems to be insignificant (between 1% and 2% slower).
Fixes #35231.
PR Close #35840
2020-03-03 21:05:26 +01:00
|
|
|
|
tNode: TNode, lView: LView, name: string, value: any, sanitizer: SanitizerFn|null|undefined,
|
|
|
|
|
namespace: string|null|undefined) {
|
2020-05-14 09:34:51 -07:00
|
|
|
|
if (ngDevMode) {
|
|
|
|
|
assertNotSame(value, NO_CHANGE as any, 'Incoming value should never be NO_CHANGE.');
|
|
|
|
|
validateAgainstEventAttributes(name);
|
2020-10-12 21:38:06 -07:00
|
|
|
|
assertTNodeType(
|
|
|
|
|
tNode, TNodeType.Element,
|
2020-05-14 09:34:51 -07:00
|
|
|
|
`Attempted to set attribute \`${name}\` on a container node. ` +
|
|
|
|
|
`Host bindings are not valid on ng-container or ng-template.`);
|
|
|
|
|
}
|
2020-03-05 14:14:59 -08:00
|
|
|
|
const element = getNativeByTNode(tNode, lView) as RElement;
|
2020-10-12 16:57:07 -07:00
|
|
|
|
setElementAttribute(lView[RENDERER], element, namespace, tNode.value, name, value, sanitizer);
|
2020-09-25 15:01:56 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function setElementAttribute(
|
|
|
|
|
renderer: Renderer3, element: RElement, namespace: string|null|undefined, tagName: string|null,
|
|
|
|
|
name: string, value: any, sanitizer: SanitizerFn|null|undefined) {
|
2019-05-31 10:39:14 -07:00
|
|
|
|
if (value == null) {
|
|
|
|
|
ngDevMode && ngDevMode.rendererRemoveAttribute++;
|
|
|
|
|
isProceduralRenderer(renderer) ? renderer.removeAttribute(element, name, namespace) :
|
|
|
|
|
element.removeAttribute(name);
|
|
|
|
|
} else {
|
|
|
|
|
ngDevMode && ngDevMode.rendererSetAttribute++;
|
|
|
|
|
const strValue =
|
2020-09-25 15:01:56 -07:00
|
|
|
|
sanitizer == null ? renderStringify(value) : sanitizer(value, tagName || '', name);
|
2019-05-31 10:39:14 -07:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (isProceduralRenderer(renderer)) {
|
|
|
|
|
renderer.setAttribute(element, name, strValue, namespace);
|
|
|
|
|
} else {
|
|
|
|
|
namespace ? element.setAttributeNS(namespace, name, strValue) :
|
|
|
|
|
element.setAttribute(name, strValue);
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-04-01 15:36:43 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Sets initial input properties on directive instances from attribute data
|
|
|
|
|
*
|
2019-09-14 13:18:37 +02:00
|
|
|
|
* @param lView Current LView that is being processed.
|
2019-04-01 15:36:43 -07:00
|
|
|
|
* @param directiveIndex Index of the directive in directives array
|
|
|
|
|
* @param instance Instance of the directive on which to set the initial inputs
|
2019-04-18 14:22:32 +02:00
|
|
|
|
* @param def The directive def that contains the list of inputs
|
2019-04-01 15:36:43 -07:00
|
|
|
|
* @param tNode The static data for this node
|
|
|
|
|
*/
|
|
|
|
|
function setInputsFromAttrs<T>(
|
2019-10-22 16:42:23 +02:00
|
|
|
|
lView: LView, directiveIndex: number, instance: T, def: DirectiveDef<T>, tNode: TNode,
|
|
|
|
|
initialInputData: InitialInputData): void {
|
fix(core): ngOnDestroy on multi providers called with incorrect context (#35840)
Currently destroy hooks are stored in memory as `[1, hook, 5, hook]` where
the numbers represent the index at which to find the context and `hook` is
the function to be invoked. This breaks down for `multi` providers,
because the value at the index will be an array of providers, resulting in
the hook being invoked with an array of all the multi provider values,
rather than the provider that was destroyed. In ViewEngine `ngOnDestroy`
wasn't being called for `multi` providers at all.
These changes fix the issue by changing the structure of the destroy hooks to `[1, hook, 5, [0, hook, 3, hook]]` where the indexes inside the inner array point to the provider inside of the multi provider array. Note that this is slightly different from the original design which called for the structure to be `[1, hook, 5, [hook, hook]`, because in the process of implementing it, I realized that we wouldn't get passing the correct context if only some of the `multi` providers have `ngOnDestroy` and others don't.
I've run the newly-added `view_destroy_hooks` benchmark against these changes and compared it to master. The difference seems to be insignificant (between 1% and 2% slower).
Fixes #35231.
PR Close #35840
2020-03-03 21:05:26 +01:00
|
|
|
|
const initialInputs: InitialInputs|null = initialInputData![directiveIndex];
|
2019-10-11 11:32:48 +02:00
|
|
|
|
if (initialInputs !== null) {
|
2019-04-01 15:36:43 -07:00
|
|
|
|
const setInput = def.setInput;
|
|
|
|
|
for (let i = 0; i < initialInputs.length;) {
|
|
|
|
|
const publicName = initialInputs[i++];
|
|
|
|
|
const privateName = initialInputs[i++];
|
|
|
|
|
const value = initialInputs[i++];
|
2019-10-11 11:32:48 +02:00
|
|
|
|
if (setInput !== null) {
|
fix(core): ngOnDestroy on multi providers called with incorrect context (#35840)
Currently destroy hooks are stored in memory as `[1, hook, 5, hook]` where
the numbers represent the index at which to find the context and `hook` is
the function to be invoked. This breaks down for `multi` providers,
because the value at the index will be an array of providers, resulting in
the hook being invoked with an array of all the multi provider values,
rather than the provider that was destroyed. In ViewEngine `ngOnDestroy`
wasn't being called for `multi` providers at all.
These changes fix the issue by changing the structure of the destroy hooks to `[1, hook, 5, [0, hook, 3, hook]]` where the indexes inside the inner array point to the provider inside of the multi provider array. Note that this is slightly different from the original design which called for the structure to be `[1, hook, 5, [hook, hook]`, because in the process of implementing it, I realized that we wouldn't get passing the correct context if only some of the `multi` providers have `ngOnDestroy` and others don't.
I've run the newly-added `view_destroy_hooks` benchmark against these changes and compared it to master. The difference seems to be insignificant (between 1% and 2% slower).
Fixes #35231.
PR Close #35840
2020-03-03 21:05:26 +01:00
|
|
|
|
def.setInput!(instance, value, publicName, privateName);
|
2019-04-01 15:36:43 -07:00
|
|
|
|
} else {
|
|
|
|
|
(instance as any)[privateName] = value;
|
|
|
|
|
}
|
2019-04-18 14:22:32 +02:00
|
|
|
|
if (ngDevMode) {
|
|
|
|
|
const nativeElement = getNativeByTNode(tNode, lView) as RElement;
|
|
|
|
|
setNgReflectProperty(lView, nativeElement, tNode.type, privateName, value);
|
|
|
|
|
}
|
2019-04-01 15:36:43 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Generates initialInputData for a node and stores it in the template's static storage
|
|
|
|
|
* so subsequent template invocations don't have to recalculate it.
|
|
|
|
|
*
|
|
|
|
|
* initialInputData is an array containing values that need to be set as input properties
|
|
|
|
|
* for directives on this node, but only once on creation. We need this array to support
|
|
|
|
|
* the case where you set an @Input property of a directive using attribute-like syntax.
|
|
|
|
|
* e.g. if you have a `name` @Input, you can set it once like this:
|
|
|
|
|
*
|
|
|
|
|
* <my-component name="Bess"></my-component>
|
|
|
|
|
*
|
|
|
|
|
* @param inputs The list of inputs from the directive def
|
2019-10-16 11:44:08 +02:00
|
|
|
|
* @param attrs The static attrs on this node
|
2019-04-01 15:36:43 -07:00
|
|
|
|
*/
|
2019-10-16 11:44:08 +02:00
|
|
|
|
function generateInitialInputs(inputs: {[key: string]: string}, attrs: TAttributes): InitialInputs|
|
|
|
|
|
null {
|
|
|
|
|
let inputsToStore: InitialInputs|null = null;
|
2019-04-01 15:36:43 -07:00
|
|
|
|
let i = 0;
|
|
|
|
|
while (i < attrs.length) {
|
|
|
|
|
const attrName = attrs[i];
|
|
|
|
|
if (attrName === AttributeMarker.NamespaceURI) {
|
|
|
|
|
// We do not allow inputs on namespaced attributes.
|
|
|
|
|
i += 4;
|
|
|
|
|
continue;
|
2019-04-08 22:47:23 +02:00
|
|
|
|
} else if (attrName === AttributeMarker.ProjectAs) {
|
|
|
|
|
// Skip over the `ngProjectAs` value.
|
|
|
|
|
i += 2;
|
|
|
|
|
continue;
|
2019-04-01 15:36:43 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If we hit any other attribute markers, we're done anyway. None of those are valid inputs.
|
|
|
|
|
if (typeof attrName === 'number') break;
|
|
|
|
|
|
2019-10-16 11:44:08 +02:00
|
|
|
|
if (inputs.hasOwnProperty(attrName as string)) {
|
|
|
|
|
if (inputsToStore === null) inputsToStore = [];
|
|
|
|
|
inputsToStore.push(attrName as string, inputs[attrName as string], attrs[i + 1] as string);
|
2019-04-01 15:36:43 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
i += 2;
|
|
|
|
|
}
|
2019-10-16 11:44:08 +02:00
|
|
|
|
return inputsToStore;
|
2019-04-01 15:36:43 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//////////////////////////
|
|
|
|
|
//// ViewContainer & View
|
|
|
|
|
//////////////////////////
|
|
|
|
|
|
2019-05-17 09:52:31 -07:00
|
|
|
|
// Not sure why I need to do `any` here but TS complains later.
|
2019-08-15 14:14:25 +01:00
|
|
|
|
const LContainerArray: any = ((typeof ngDevMode === 'undefined' || ngDevMode) && initNgDevMode()) &&
|
|
|
|
|
createNamedArrayType('LContainer');
|
2019-05-17 09:52:31 -07:00
|
|
|
|
|
2019-04-01 15:36:43 -07:00
|
|
|
|
/**
|
|
|
|
|
* Creates a LContainer, either from a container instruction, or for a ViewContainerRef.
|
|
|
|
|
*
|
|
|
|
|
* @param hostNative The host element for the LContainer
|
|
|
|
|
* @param hostTNode The host TNode for the LContainer
|
|
|
|
|
* @param currentView The parent view of the LContainer
|
|
|
|
|
* @param native The native comment element
|
|
|
|
|
* @param isForViewContainerRef Optional a flag indicating the ViewContainerRef case
|
|
|
|
|
* @returns LContainer
|
|
|
|
|
*/
|
|
|
|
|
export function createLContainer(
|
fix(core): ngOnDestroy on multi providers called with incorrect context (#35840)
Currently destroy hooks are stored in memory as `[1, hook, 5, hook]` where
the numbers represent the index at which to find the context and `hook` is
the function to be invoked. This breaks down for `multi` providers,
because the value at the index will be an array of providers, resulting in
the hook being invoked with an array of all the multi provider values,
rather than the provider that was destroyed. In ViewEngine `ngOnDestroy`
wasn't being called for `multi` providers at all.
These changes fix the issue by changing the structure of the destroy hooks to `[1, hook, 5, [0, hook, 3, hook]]` where the indexes inside the inner array point to the provider inside of the multi provider array. Note that this is slightly different from the original design which called for the structure to be `[1, hook, 5, [hook, hook]`, because in the process of implementing it, I realized that we wouldn't get passing the correct context if only some of the `multi` providers have `ngOnDestroy` and others don't.
I've run the newly-added `view_destroy_hooks` benchmark against these changes and compared it to master. The difference seems to be insignificant (between 1% and 2% slower).
Fixes #35231.
PR Close #35840
2020-03-03 21:05:26 +01:00
|
|
|
|
hostNative: RElement|RComment|LView, currentView: LView, native: RComment,
|
2019-11-07 06:32:59 +00:00
|
|
|
|
tNode: TNode): LContainer {
|
2019-04-01 15:36:43 -07:00
|
|
|
|
ngDevMode && assertLView(currentView);
|
2019-10-14 14:46:26 -07:00
|
|
|
|
ngDevMode && !isProceduralRenderer(currentView[RENDERER]) && assertDomNode(native);
|
2019-05-17 09:52:31 -07:00
|
|
|
|
// https://jsperf.com/array-literal-vs-new-array-really
|
|
|
|
|
const lContainer: LContainer = new (ngDevMode ? LContainerArray : Array)(
|
2020-05-12 10:35:52 -07:00
|
|
|
|
hostNative, // host native
|
|
|
|
|
true, // Boolean `true` in this position signifies that this is an `LContainer`
|
|
|
|
|
false, // has transplanted views
|
|
|
|
|
currentView, // parent
|
|
|
|
|
null, // next
|
|
|
|
|
0, // transplanted views to refresh count
|
|
|
|
|
tNode, // t_host
|
|
|
|
|
native, // native,
|
|
|
|
|
null, // view refs
|
|
|
|
|
null, // moved views
|
fix(core): ngOnDestroy on multi providers called with incorrect context (#35840)
Currently destroy hooks are stored in memory as `[1, hook, 5, hook]` where
the numbers represent the index at which to find the context and `hook` is
the function to be invoked. This breaks down for `multi` providers,
because the value at the index will be an array of providers, resulting in
the hook being invoked with an array of all the multi provider values,
rather than the provider that was destroyed. In ViewEngine `ngOnDestroy`
wasn't being called for `multi` providers at all.
These changes fix the issue by changing the structure of the destroy hooks to `[1, hook, 5, [0, hook, 3, hook]]` where the indexes inside the inner array point to the provider inside of the multi provider array. Note that this is slightly different from the original design which called for the structure to be `[1, hook, 5, [hook, hook]`, because in the process of implementing it, I realized that we wouldn't get passing the correct context if only some of the `multi` providers have `ngOnDestroy` and others don't.
I've run the newly-added `view_destroy_hooks` benchmark against these changes and compared it to master. The difference seems to be insignificant (between 1% and 2% slower).
Fixes #35231.
PR Close #35840
2020-03-03 21:05:26 +01:00
|
|
|
|
);
|
2020-01-13 15:12:16 -08:00
|
|
|
|
ngDevMode &&
|
|
|
|
|
assertEqual(
|
|
|
|
|
lContainer.length, CONTAINER_HEADER_OFFSET,
|
|
|
|
|
'Should allocate correct number of slots for LContainer header.');
|
2019-04-01 15:36:43 -07:00
|
|
|
|
ngDevMode && attachLContainerDebug(lContainer);
|
|
|
|
|
return lContainer;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2020-05-14 14:15:45 -07:00
|
|
|
|
* Goes over embedded views (ones created through ViewContainerRef APIs) and refreshes
|
2019-08-02 16:43:10 +02:00
|
|
|
|
* them by executing an associated template function.
|
2019-04-01 15:36:43 -07:00
|
|
|
|
*/
|
2020-05-14 14:15:45 -07:00
|
|
|
|
function refreshEmbeddedViews(lView: LView) {
|
2020-01-13 15:12:16 -08:00
|
|
|
|
for (let lContainer = getFirstLContainer(lView); lContainer !== null;
|
|
|
|
|
lContainer = getNextLContainer(lContainer)) {
|
|
|
|
|
for (let i = CONTAINER_HEADER_OFFSET; i < lContainer.length; i++) {
|
|
|
|
|
const embeddedLView = lContainer[i];
|
|
|
|
|
const embeddedTView = embeddedLView[TVIEW];
|
|
|
|
|
ngDevMode && assertDefined(embeddedTView, 'TView must be allocated');
|
|
|
|
|
if (viewAttachedToChangeDetector(embeddedLView)) {
|
|
|
|
|
refreshView(embeddedTView, embeddedLView, embeddedTView.template, embeddedLView[CONTEXT]!);
|
2019-11-07 06:32:59 +00:00
|
|
|
|
}
|
2019-04-01 15:36:43 -07:00
|
|
|
|
}
|
2020-01-13 15:12:16 -08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-08 15:13:22 -08:00
|
|
|
|
/**
|
2020-01-13 15:12:16 -08:00
|
|
|
|
* Mark transplanted views as needing to be refreshed at their insertion points.
|
2019-11-08 15:13:22 -08:00
|
|
|
|
*
|
2020-01-13 15:12:16 -08:00
|
|
|
|
* @param lView The `LView` that may have transplanted views.
|
2019-11-08 15:13:22 -08:00
|
|
|
|
*/
|
2020-01-13 15:12:16 -08:00
|
|
|
|
function markTransplantedViewsForRefresh(lView: LView) {
|
|
|
|
|
for (let lContainer = getFirstLContainer(lView); lContainer !== null;
|
|
|
|
|
lContainer = getNextLContainer(lContainer)) {
|
2020-05-12 10:35:52 -07:00
|
|
|
|
if (!lContainer[HAS_TRANSPLANTED_VIEWS]) continue;
|
|
|
|
|
|
|
|
|
|
const movedViews = lContainer[MOVED_VIEWS]!;
|
|
|
|
|
ngDevMode && assertDefined(movedViews, 'Transplanted View flags set but missing MOVED_VIEWS');
|
|
|
|
|
for (let i = 0; i < movedViews.length; i++) {
|
|
|
|
|
const movedLView = movedViews[i]!;
|
|
|
|
|
const insertionLContainer = movedLView[PARENT] as LContainer;
|
|
|
|
|
ngDevMode && assertLContainer(insertionLContainer);
|
|
|
|
|
// We don't want to increment the counter if the moved LView was already marked for
|
|
|
|
|
// refresh.
|
|
|
|
|
if ((movedLView[FLAGS] & LViewFlags.RefreshTransplantedView) === 0) {
|
|
|
|
|
updateTransplantedViewCount(insertionLContainer, 1);
|
2019-11-08 15:13:22 -08:00
|
|
|
|
}
|
2020-05-12 10:35:52 -07:00
|
|
|
|
// Note, it is possible that the `movedViews` is tracking views that are transplanted *and*
|
|
|
|
|
// those that aren't (declaration component === insertion component). In the latter case,
|
|
|
|
|
// it's fine to add the flag, as we will clear it immediately in
|
2020-05-14 14:15:45 -07:00
|
|
|
|
// `refreshEmbeddedViews` for the view currently being refreshed.
|
2020-05-12 10:35:52 -07:00
|
|
|
|
movedLView[FLAGS] |= LViewFlags.RefreshTransplantedView;
|
2019-11-08 15:13:22 -08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-04-01 15:36:43 -07:00
|
|
|
|
|
|
|
|
|
/////////////
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Refreshes components by entering the component view and processing its bindings, queries, etc.
|
|
|
|
|
*
|
2019-08-02 16:43:10 +02:00
|
|
|
|
* @param componentHostIdx Element index in LView[] (adjusted for HEADER_OFFSET)
|
2019-04-01 15:36:43 -07:00
|
|
|
|
*/
|
2019-08-02 16:43:10 +02:00
|
|
|
|
function refreshComponent(hostLView: LView, componentHostIdx: number): void {
|
|
|
|
|
ngDevMode && assertEqual(isCreationMode(hostLView), false, 'Should be run in update mode');
|
2019-10-09 16:10:14 -07:00
|
|
|
|
const componentView = getComponentLViewByIndex(componentHostIdx, hostLView);
|
2019-08-02 16:43:10 +02:00
|
|
|
|
// Only attached components that are CheckAlways or OnPush and dirty should be refreshed
|
2020-01-13 15:12:16 -08:00
|
|
|
|
if (viewAttachedToChangeDetector(componentView)) {
|
|
|
|
|
const tView = componentView[TVIEW];
|
|
|
|
|
if (componentView[FLAGS] & (LViewFlags.CheckAlways | LViewFlags.Dirty)) {
|
|
|
|
|
refreshView(tView, componentView, tView.template, componentView[CONTEXT]);
|
|
|
|
|
} else if (componentView[TRANSPLANTED_VIEWS_TO_REFRESH] > 0) {
|
|
|
|
|
// Only attached components that are CheckAlways or OnPush and dirty should be refreshed
|
|
|
|
|
refreshContainsDirtyView(componentView);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Refreshes all transplanted views marked with `LViewFlags.RefreshTransplantedView` that are
|
|
|
|
|
* children or descendants of the given lView.
|
|
|
|
|
*
|
|
|
|
|
* @param lView The lView which contains descendant transplanted views that need to be refreshed.
|
|
|
|
|
*/
|
|
|
|
|
function refreshContainsDirtyView(lView: LView) {
|
|
|
|
|
for (let lContainer = getFirstLContainer(lView); lContainer !== null;
|
|
|
|
|
lContainer = getNextLContainer(lContainer)) {
|
|
|
|
|
for (let i = CONTAINER_HEADER_OFFSET; i < lContainer.length; i++) {
|
|
|
|
|
const embeddedLView = lContainer[i];
|
|
|
|
|
if (embeddedLView[FLAGS] & LViewFlags.RefreshTransplantedView) {
|
|
|
|
|
const embeddedTView = embeddedLView[TVIEW];
|
|
|
|
|
ngDevMode && assertDefined(embeddedTView, 'TView must be allocated');
|
|
|
|
|
refreshView(embeddedTView, embeddedLView, embeddedTView.template, embeddedLView[CONTEXT]!);
|
|
|
|
|
} else if (embeddedLView[TRANSPLANTED_VIEWS_TO_REFRESH] > 0) {
|
|
|
|
|
refreshContainsDirtyView(embeddedLView);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const tView = lView[TVIEW];
|
|
|
|
|
// Refresh child component views.
|
|
|
|
|
const components = tView.components;
|
|
|
|
|
if (components !== null) {
|
|
|
|
|
for (let i = 0; i < components.length; i++) {
|
|
|
|
|
const componentView = getComponentLViewByIndex(components[i], lView);
|
|
|
|
|
// Only attached components that are CheckAlways or OnPush and dirty should be refreshed
|
|
|
|
|
if (viewAttachedToChangeDetector(componentView) &&
|
|
|
|
|
componentView[TRANSPLANTED_VIEWS_TO_REFRESH] > 0) {
|
|
|
|
|
refreshContainsDirtyView(componentView);
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-04-01 15:36:43 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-02 16:43:10 +02:00
|
|
|
|
function renderComponent(hostLView: LView, componentHostIdx: number) {
|
|
|
|
|
ngDevMode && assertEqual(isCreationMode(hostLView), true, 'Should be run in creation mode');
|
2019-10-09 16:10:14 -07:00
|
|
|
|
const componentView = getComponentLViewByIndex(componentHostIdx, hostLView);
|
2020-01-30 14:57:44 -08:00
|
|
|
|
const componentTView = componentView[TVIEW];
|
|
|
|
|
syncViewWithBlueprint(componentTView, componentView);
|
|
|
|
|
renderView(componentTView, componentView, componentView[CONTEXT]);
|
2019-08-02 16:43:10 +02:00
|
|
|
|
}
|
|
|
|
|
|
2019-04-01 15:36:43 -07:00
|
|
|
|
/**
|
|
|
|
|
* Syncs an LView instance with its blueprint if they have gotten out of sync.
|
|
|
|
|
*
|
|
|
|
|
* Typically, blueprints and their view instances should always be in sync, so the loop here
|
|
|
|
|
* will be skipped. However, consider this case of two components side-by-side:
|
|
|
|
|
*
|
|
|
|
|
* App template:
|
|
|
|
|
* ```
|
|
|
|
|
* <comp></comp>
|
|
|
|
|
* <comp></comp>
|
|
|
|
|
* ```
|
|
|
|
|
*
|
|
|
|
|
* The following will happen:
|
|
|
|
|
* 1. App template begins processing.
|
|
|
|
|
* 2. First <comp> is matched as a component and its LView is created.
|
|
|
|
|
* 3. Second <comp> is matched as a component and its LView is created.
|
|
|
|
|
* 4. App template completes processing, so it's time to check child templates.
|
|
|
|
|
* 5. First <comp> template is checked. It has a directive, so its def is pushed to blueprint.
|
|
|
|
|
* 6. Second <comp> template is checked. Its blueprint has been updated by the first
|
|
|
|
|
* <comp> template, but its LView was created before this update, so it is out of sync.
|
|
|
|
|
*
|
|
|
|
|
* Note that embedded views inside ngFor loops will never be out of sync because these views
|
|
|
|
|
* are processed as soon as they are created.
|
|
|
|
|
*
|
2020-01-30 14:57:44 -08:00
|
|
|
|
* @param tView The `TView` that contains the blueprint for syncing
|
|
|
|
|
* @param lView The view to sync
|
2019-04-01 15:36:43 -07:00
|
|
|
|
*/
|
2020-01-30 14:57:44 -08:00
|
|
|
|
function syncViewWithBlueprint(tView: TView, lView: LView) {
|
|
|
|
|
for (let i = lView.length; i < tView.blueprint.length; i++) {
|
|
|
|
|
lView.push(tView.blueprint[i]);
|
2019-04-01 15:36:43 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Adds LView or LContainer to the end of the current view tree.
|
|
|
|
|
*
|
|
|
|
|
* This structure will be used to traverse through nested views to remove listeners
|
|
|
|
|
* and call onDestroy callbacks.
|
|
|
|
|
*
|
|
|
|
|
* @param lView The view where LView or LContainer should be added
|
|
|
|
|
* @param adjustedHostIndex Index of the view's host node in LView[], adjusted for header
|
|
|
|
|
* @param lViewOrLContainer The LView or LContainer to add to the view tree
|
|
|
|
|
* @returns The state passed in
|
|
|
|
|
*/
|
|
|
|
|
export function addToViewTree<T extends LView|LContainer>(lView: LView, lViewOrLContainer: T): T {
|
2019-05-15 21:08:31 -07:00
|
|
|
|
// TODO(benlesh/misko): This implementation is incorrect, because it always adds the LContainer
|
2020-01-30 14:57:44 -08:00
|
|
|
|
// to the end of the queue, which means if the developer retrieves the LContainers from RNodes out
|
|
|
|
|
// of order, the change detection will run out of order, as the act of retrieving the the
|
|
|
|
|
// LContainer from the RNode is what adds it to the queue.
|
2019-04-01 15:36:43 -07:00
|
|
|
|
if (lView[CHILD_HEAD]) {
|
fix(core): ngOnDestroy on multi providers called with incorrect context (#35840)
Currently destroy hooks are stored in memory as `[1, hook, 5, hook]` where
the numbers represent the index at which to find the context and `hook` is
the function to be invoked. This breaks down for `multi` providers,
because the value at the index will be an array of providers, resulting in
the hook being invoked with an array of all the multi provider values,
rather than the provider that was destroyed. In ViewEngine `ngOnDestroy`
wasn't being called for `multi` providers at all.
These changes fix the issue by changing the structure of the destroy hooks to `[1, hook, 5, [0, hook, 3, hook]]` where the indexes inside the inner array point to the provider inside of the multi provider array. Note that this is slightly different from the original design which called for the structure to be `[1, hook, 5, [hook, hook]`, because in the process of implementing it, I realized that we wouldn't get passing the correct context if only some of the `multi` providers have `ngOnDestroy` and others don't.
I've run the newly-added `view_destroy_hooks` benchmark against these changes and compared it to master. The difference seems to be insignificant (between 1% and 2% slower).
Fixes #35231.
PR Close #35840
2020-03-03 21:05:26 +01:00
|
|
|
|
lView[CHILD_TAIL]![NEXT] = lViewOrLContainer;
|
2019-04-01 15:36:43 -07:00
|
|
|
|
} else {
|
|
|
|
|
lView[CHILD_HEAD] = lViewOrLContainer;
|
|
|
|
|
}
|
|
|
|
|
lView[CHILD_TAIL] = lViewOrLContainer;
|
|
|
|
|
return lViewOrLContainer;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
///////////////////////////////
|
|
|
|
|
//// Change detection
|
|
|
|
|
///////////////////////////////
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Marks current view and all ancestors dirty.
|
|
|
|
|
*
|
|
|
|
|
* Returns the root view because it is found as a byproduct of marking the view tree
|
|
|
|
|
* dirty, and can be used by methods that consume markViewDirty() to easily schedule
|
|
|
|
|
* change detection. Otherwise, such methods would need to traverse up the view tree
|
|
|
|
|
* an additional time to get the root view and schedule a tick on it.
|
|
|
|
|
*
|
|
|
|
|
* @param lView The starting LView to mark dirty
|
|
|
|
|
* @returns the root LView
|
|
|
|
|
*/
|
|
|
|
|
export function markViewDirty(lView: LView): LView|null {
|
|
|
|
|
while (lView) {
|
|
|
|
|
lView[FLAGS] |= LViewFlags.Dirty;
|
|
|
|
|
const parent = getLViewParent(lView);
|
|
|
|
|
// Stop traversing up as soon as you find a root view that wasn't attached to any container
|
|
|
|
|
if (isRootView(lView) && !parent) {
|
|
|
|
|
return lView;
|
|
|
|
|
}
|
|
|
|
|
// continue otherwise
|
fix(core): ngOnDestroy on multi providers called with incorrect context (#35840)
Currently destroy hooks are stored in memory as `[1, hook, 5, hook]` where
the numbers represent the index at which to find the context and `hook` is
the function to be invoked. This breaks down for `multi` providers,
because the value at the index will be an array of providers, resulting in
the hook being invoked with an array of all the multi provider values,
rather than the provider that was destroyed. In ViewEngine `ngOnDestroy`
wasn't being called for `multi` providers at all.
These changes fix the issue by changing the structure of the destroy hooks to `[1, hook, 5, [0, hook, 3, hook]]` where the indexes inside the inner array point to the provider inside of the multi provider array. Note that this is slightly different from the original design which called for the structure to be `[1, hook, 5, [hook, hook]`, because in the process of implementing it, I realized that we wouldn't get passing the correct context if only some of the `multi` providers have `ngOnDestroy` and others don't.
I've run the newly-added `view_destroy_hooks` benchmark against these changes and compared it to master. The difference seems to be insignificant (between 1% and 2% slower).
Fixes #35231.
PR Close #35840
2020-03-03 21:05:26 +01:00
|
|
|
|
lView = parent!;
|
2019-04-01 15:36:43 -07:00
|
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Used to schedule change detection on the whole application.
|
|
|
|
|
*
|
|
|
|
|
* Unlike `tick`, `scheduleTick` coalesces multiple calls into one change detection run.
|
|
|
|
|
* It is usually called indirectly by calling `markDirty` when the view needs to be
|
|
|
|
|
* re-rendered.
|
|
|
|
|
*
|
|
|
|
|
* Typically `scheduleTick` uses `requestAnimationFrame` to coalesce multiple
|
|
|
|
|
* `scheduleTick` requests. The scheduling function can be overridden in
|
|
|
|
|
* `renderComponent`'s `scheduler` option.
|
|
|
|
|
*/
|
|
|
|
|
export function scheduleTick(rootContext: RootContext, flags: RootContextFlags) {
|
|
|
|
|
const nothingScheduled = rootContext.flags === RootContextFlags.Empty;
|
|
|
|
|
rootContext.flags |= flags;
|
|
|
|
|
|
|
|
|
|
if (nothingScheduled && rootContext.clean == _CLEAN_PROMISE) {
|
|
|
|
|
let res: null|((val: null) => void);
|
|
|
|
|
rootContext.clean = new Promise<null>((r) => res = r);
|
|
|
|
|
rootContext.scheduler(() => {
|
|
|
|
|
if (rootContext.flags & RootContextFlags.DetectChanges) {
|
|
|
|
|
rootContext.flags &= ~RootContextFlags.DetectChanges;
|
|
|
|
|
tickRootContext(rootContext);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (rootContext.flags & RootContextFlags.FlushPlayers) {
|
|
|
|
|
rootContext.flags &= ~RootContextFlags.FlushPlayers;
|
|
|
|
|
const playerHandler = rootContext.playerHandler;
|
|
|
|
|
if (playerHandler) {
|
|
|
|
|
playerHandler.flushPlayers();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
rootContext.clean = _CLEAN_PROMISE;
|
fix(core): ngOnDestroy on multi providers called with incorrect context (#35840)
Currently destroy hooks are stored in memory as `[1, hook, 5, hook]` where
the numbers represent the index at which to find the context and `hook` is
the function to be invoked. This breaks down for `multi` providers,
because the value at the index will be an array of providers, resulting in
the hook being invoked with an array of all the multi provider values,
rather than the provider that was destroyed. In ViewEngine `ngOnDestroy`
wasn't being called for `multi` providers at all.
These changes fix the issue by changing the structure of the destroy hooks to `[1, hook, 5, [0, hook, 3, hook]]` where the indexes inside the inner array point to the provider inside of the multi provider array. Note that this is slightly different from the original design which called for the structure to be `[1, hook, 5, [hook, hook]`, because in the process of implementing it, I realized that we wouldn't get passing the correct context if only some of the `multi` providers have `ngOnDestroy` and others don't.
I've run the newly-added `view_destroy_hooks` benchmark against these changes and compared it to master. The difference seems to be insignificant (between 1% and 2% slower).
Fixes #35231.
PR Close #35840
2020-03-03 21:05:26 +01:00
|
|
|
|
res!(null);
|
2019-04-01 15:36:43 -07:00
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function tickRootContext(rootContext: RootContext) {
|
|
|
|
|
for (let i = 0; i < rootContext.components.length; i++) {
|
|
|
|
|
const rootComponent = rootContext.components[i];
|
fix(core): ngOnDestroy on multi providers called with incorrect context (#35840)
Currently destroy hooks are stored in memory as `[1, hook, 5, hook]` where
the numbers represent the index at which to find the context and `hook` is
the function to be invoked. This breaks down for `multi` providers,
because the value at the index will be an array of providers, resulting in
the hook being invoked with an array of all the multi provider values,
rather than the provider that was destroyed. In ViewEngine `ngOnDestroy`
wasn't being called for `multi` providers at all.
These changes fix the issue by changing the structure of the destroy hooks to `[1, hook, 5, [0, hook, 3, hook]]` where the indexes inside the inner array point to the provider inside of the multi provider array. Note that this is slightly different from the original design which called for the structure to be `[1, hook, 5, [hook, hook]`, because in the process of implementing it, I realized that we wouldn't get passing the correct context if only some of the `multi` providers have `ngOnDestroy` and others don't.
I've run the newly-added `view_destroy_hooks` benchmark against these changes and compared it to master. The difference seems to be insignificant (between 1% and 2% slower).
Fixes #35231.
PR Close #35840
2020-03-03 21:05:26 +01:00
|
|
|
|
const lView = readPatchedLView(rootComponent)!;
|
2019-08-02 16:43:10 +02:00
|
|
|
|
const tView = lView[TVIEW];
|
2020-01-30 14:57:44 -08:00
|
|
|
|
renderComponentOrTemplate(tView, lView, tView.template, rootComponent);
|
2019-04-01 15:36:43 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-30 14:57:44 -08:00
|
|
|
|
export function detectChangesInternal<T>(tView: TView, lView: LView, context: T) {
|
|
|
|
|
const rendererFactory = lView[RENDERER_FACTORY];
|
2019-04-01 15:36:43 -07:00
|
|
|
|
if (rendererFactory.begin) rendererFactory.begin();
|
|
|
|
|
try {
|
2020-01-30 14:57:44 -08:00
|
|
|
|
refreshView(tView, lView, tView.template, context);
|
2019-04-01 15:36:43 -07:00
|
|
|
|
} catch (error) {
|
2020-01-30 14:57:44 -08:00
|
|
|
|
handleError(lView, error);
|
2019-04-01 15:36:43 -07:00
|
|
|
|
throw error;
|
|
|
|
|
} finally {
|
|
|
|
|
if (rendererFactory.end) rendererFactory.end();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Synchronously perform change detection on a root view and its components.
|
|
|
|
|
*
|
|
|
|
|
* @param lView The view which the change detection should be performed on.
|
|
|
|
|
*/
|
|
|
|
|
export function detectChangesInRootView(lView: LView): void {
|
|
|
|
|
tickRootContext(lView[CONTEXT] as RootContext);
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-30 14:57:44 -08:00
|
|
|
|
export function checkNoChangesInternal<T>(tView: TView, view: LView, context: T) {
|
2020-10-14 15:11:01 -07:00
|
|
|
|
setIsInCheckNoChangesMode(true);
|
2019-04-01 15:36:43 -07:00
|
|
|
|
try {
|
2020-01-30 14:57:44 -08:00
|
|
|
|
detectChangesInternal(tView, view, context);
|
2019-04-01 15:36:43 -07:00
|
|
|
|
} finally {
|
2020-10-14 15:11:01 -07:00
|
|
|
|
setIsInCheckNoChangesMode(false);
|
2019-04-01 15:36:43 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Checks the change detector on a root view and its components, and throws if any changes are
|
|
|
|
|
* detected.
|
|
|
|
|
*
|
|
|
|
|
* This is used in development mode to verify that running change detection doesn't
|
|
|
|
|
* introduce other changes.
|
|
|
|
|
*
|
|
|
|
|
* @param lView The view which the change detection should be checked on.
|
|
|
|
|
*/
|
|
|
|
|
export function checkNoChangesInRootView(lView: LView): void {
|
2020-10-14 15:11:01 -07:00
|
|
|
|
setIsInCheckNoChangesMode(true);
|
2019-04-01 15:36:43 -07:00
|
|
|
|
try {
|
|
|
|
|
detectChangesInRootView(lView);
|
|
|
|
|
} finally {
|
2020-10-14 15:11:01 -07:00
|
|
|
|
setIsInCheckNoChangesMode(false);
|
2019-04-01 15:36:43 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-02 16:43:10 +02:00
|
|
|
|
function executeViewQueryFn<T>(
|
|
|
|
|
flags: RenderFlags, viewQueryFn: ViewQueriesFunction<{}>, component: T): void {
|
|
|
|
|
ngDevMode && assertDefined(viewQueryFn, 'View queries function to execute must be defined.');
|
|
|
|
|
setCurrentQueryIndex(0);
|
|
|
|
|
viewQueryFn(flags, component);
|
2019-04-01 15:36:43 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
///////////////////////////////
|
|
|
|
|
//// Bindings & interpolations
|
|
|
|
|
///////////////////////////////
|
|
|
|
|
|
|
|
|
|
/**
|
2019-09-03 17:21:57 +02:00
|
|
|
|
* Stores meta-data for a property binding to be used by TestBed's `DebugElement.properties`.
|
2019-04-01 15:36:43 -07:00
|
|
|
|
*
|
2019-09-03 17:21:57 +02:00
|
|
|
|
* In order to support TestBed's `DebugElement.properties` we need to save, for each binding:
|
|
|
|
|
* - a bound property name;
|
|
|
|
|
* - a static parts of interpolated strings;
|
2019-04-01 15:36:43 -07:00
|
|
|
|
*
|
2019-09-03 17:21:57 +02:00
|
|
|
|
* A given property metadata is saved at the binding's index in the `TView.data` (in other words, a
|
|
|
|
|
* property binding metadata will be stored in `TView.data` at the same index as a bound value in
|
|
|
|
|
* `LView`). Metadata are represented as `INTERPOLATION_DELIMITER`-delimited string with the
|
|
|
|
|
* following format:
|
|
|
|
|
* - `propertyName` for bound properties;
|
|
|
|
|
* - `propertyName<EFBFBD>prefix<EFBFBD>interpolation_static_part1<EFBFBD>..interpolation_static_partN<74>suffix` for
|
|
|
|
|
* interpolated properties.
|
2019-04-01 15:36:43 -07:00
|
|
|
|
*
|
2019-09-03 17:21:57 +02:00
|
|
|
|
* @param tData `TData` where meta-data will be saved;
|
2020-03-05 14:14:59 -08:00
|
|
|
|
* @param tNode `TNode` that is a target of the binding;
|
2019-09-03 17:21:57 +02:00
|
|
|
|
* @param propertyName bound property name;
|
|
|
|
|
* @param bindingIndex binding index in `LView`
|
|
|
|
|
* @param interpolationParts static interpolation parts (for property interpolations)
|
2019-04-01 15:36:43 -07:00
|
|
|
|
*/
|
2019-09-03 17:21:57 +02:00
|
|
|
|
export function storePropertyBindingMetadata(
|
2020-03-05 14:14:59 -08:00
|
|
|
|
tData: TData, tNode: TNode, propertyName: string, bindingIndex: number,
|
2019-09-03 17:21:57 +02:00
|
|
|
|
...interpolationParts: string[]) {
|
|
|
|
|
// Binding meta-data are stored only the first time a given property instruction is processed.
|
|
|
|
|
// Since we don't have a concept of the "first update pass" we need to check for presence of the
|
|
|
|
|
// binding meta-data to decide if one should be stored (or if was stored already).
|
|
|
|
|
if (tData[bindingIndex] === null) {
|
|
|
|
|
if (tNode.inputs == null || !tNode.inputs[propertyName]) {
|
|
|
|
|
const propBindingIdxs = tNode.propertyBindings || (tNode.propertyBindings = []);
|
|
|
|
|
propBindingIdxs.push(bindingIndex);
|
|
|
|
|
let bindingMetadata = propertyName;
|
|
|
|
|
if (interpolationParts.length > 0) {
|
|
|
|
|
bindingMetadata +=
|
|
|
|
|
INTERPOLATION_DELIMITER + interpolationParts.join(INTERPOLATION_DELIMITER);
|
|
|
|
|
}
|
|
|
|
|
tData[bindingIndex] = bindingMetadata;
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-04-01 15:36:43 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export const CLEAN_PROMISE = _CLEAN_PROMISE;
|
|
|
|
|
|
2020-01-30 14:57:44 -08:00
|
|
|
|
export function getLCleanup(view: LView): any[] {
|
2019-04-01 15:36:43 -07:00
|
|
|
|
// top level variables should not be exported for performance reasons (PERF_NOTES.md)
|
2019-08-15 14:14:25 +01:00
|
|
|
|
return view[CLEANUP] || (view[CLEANUP] = ngDevMode ? new LCleanup() : []);
|
2019-04-01 15:36:43 -07:00
|
|
|
|
}
|
|
|
|
|
|
2020-01-30 14:57:44 -08:00
|
|
|
|
function getTViewCleanup(tView: TView): any[] {
|
|
|
|
|
return tView.cleanup || (tView.cleanup = ngDevMode ? new TCleanup() : []);
|
2019-04-01 15:36:43 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* There are cases where the sub component's renderer needs to be included
|
|
|
|
|
* instead of the current renderer (see the componentSyntheticHost* instructions).
|
|
|
|
|
*/
|
2020-02-18 17:49:37 -08:00
|
|
|
|
export function loadComponentRenderer(
|
|
|
|
|
currentDef: DirectiveDef<any>|null, tNode: TNode, lView: LView): Renderer3 {
|
|
|
|
|
// TODO(FW-2043): the `currentDef` is null when host bindings are invoked while creating root
|
|
|
|
|
// component (see packages/core/src/render3/component.ts). This is not consistent with the process
|
|
|
|
|
// of creating inner components, when current directive index is available in the state. In order
|
|
|
|
|
// to avoid relying on current def being `null` (thus special-casing root component creation), the
|
|
|
|
|
// process of creating root component should be unified with the process of creating inner
|
|
|
|
|
// components.
|
|
|
|
|
if (currentDef === null || isComponentDef(currentDef)) {
|
|
|
|
|
lView = unwrapLView(lView[tNode.index])!;
|
|
|
|
|
}
|
|
|
|
|
return lView[RENDERER];
|
2019-04-01 15:36:43 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Handles an error thrown in an LView. */
|
|
|
|
|
export function handleError(lView: LView, error: any): void {
|
|
|
|
|
const injector = lView[INJECTOR];
|
|
|
|
|
const errorHandler = injector ? injector.get(ErrorHandler, null) : null;
|
|
|
|
|
errorHandler && errorHandler.handleError(error);
|
|
|
|
|
}
|
2019-03-15 13:45:08 -07:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Set the inputs of directives at the current node to corresponding value.
|
|
|
|
|
*
|
2020-01-30 14:57:44 -08:00
|
|
|
|
* @param tView The current TView
|
2019-03-15 13:45:08 -07:00
|
|
|
|
* @param lView the `LView` which contains the directives.
|
2019-04-18 14:22:32 +02:00
|
|
|
|
* @param inputs mapping between the public "input" name and privately-known,
|
2020-01-30 14:57:44 -08:00
|
|
|
|
* possibly minified, property names to write to.
|
2019-03-15 13:45:08 -07:00
|
|
|
|
* @param value Value to set.
|
|
|
|
|
*/
|
2019-11-13 17:06:54 +01:00
|
|
|
|
export function setInputsForProperty(
|
2020-01-30 14:57:44 -08:00
|
|
|
|
tView: TView, lView: LView, inputs: PropertyAliasValue, publicName: string, value: any): void {
|
2019-03-15 13:45:08 -07:00
|
|
|
|
for (let i = 0; i < inputs.length;) {
|
|
|
|
|
const index = inputs[i++] as number;
|
|
|
|
|
const privateName = inputs[i++] as string;
|
|
|
|
|
const instance = lView[index];
|
2020-08-04 12:42:12 -07:00
|
|
|
|
ngDevMode && assertIndexInRange(lView, index);
|
2019-03-15 13:45:08 -07:00
|
|
|
|
const def = tView.data[index] as DirectiveDef<any>;
|
2019-11-13 17:06:54 +01:00
|
|
|
|
if (def.setInput !== null) {
|
fix(core): ngOnDestroy on multi providers called with incorrect context (#35840)
Currently destroy hooks are stored in memory as `[1, hook, 5, hook]` where
the numbers represent the index at which to find the context and `hook` is
the function to be invoked. This breaks down for `multi` providers,
because the value at the index will be an array of providers, resulting in
the hook being invoked with an array of all the multi provider values,
rather than the provider that was destroyed. In ViewEngine `ngOnDestroy`
wasn't being called for `multi` providers at all.
These changes fix the issue by changing the structure of the destroy hooks to `[1, hook, 5, [0, hook, 3, hook]]` where the indexes inside the inner array point to the provider inside of the multi provider array. Note that this is slightly different from the original design which called for the structure to be `[1, hook, 5, [hook, hook]`, because in the process of implementing it, I realized that we wouldn't get passing the correct context if only some of the `multi` providers have `ngOnDestroy` and others don't.
I've run the newly-added `view_destroy_hooks` benchmark against these changes and compared it to master. The difference seems to be insignificant (between 1% and 2% slower).
Fixes #35231.
PR Close #35840
2020-03-03 21:05:26 +01:00
|
|
|
|
def.setInput!(instance, value, publicName, privateName);
|
2019-03-15 13:45:08 -07:00
|
|
|
|
} else {
|
|
|
|
|
instance[privateName] = value;
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-04-01 15:36:43 -07:00
|
|
|
|
}
|
2019-05-31 14:41:07 -07:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Updates a text binding at a given index in a given LView.
|
|
|
|
|
*/
|
|
|
|
|
export function textBindingInternal(lView: LView, index: number, value: string): void {
|
2020-09-25 15:01:56 -07:00
|
|
|
|
ngDevMode && assertString(value, 'Value should be a string');
|
2019-05-31 14:41:07 -07:00
|
|
|
|
ngDevMode && assertNotSame(value, NO_CHANGE as any, 'value should not be NO_CHANGE');
|
2020-10-13 22:00:43 -07:00
|
|
|
|
ngDevMode && assertIndexInRange(lView, index);
|
2019-05-31 14:41:07 -07:00
|
|
|
|
const element = getNativeByIndex(index, lView) as any as RText;
|
|
|
|
|
ngDevMode && assertDefined(element, 'native element should exist');
|
2020-09-25 15:01:56 -07:00
|
|
|
|
updateTextNode(lView[RENDERER], element, value);
|
2019-05-31 14:41:07 -07:00
|
|
|
|
}
|