fix(ivy): ensure host bindings and host styling works on a root component (#28664)
Prior to this fix if a root component was instantiated it create host bindings, but never render them once update mode ran unless one or more slot-allocated bindings were issued. Since styling in Ivy does not make use of LView slots, the host bindings function never ran on the root component. This fix ensures that the `hostBindings` function does run for a root component and also renders the schedlued styling instructions when executed. Jira Issue: FW-1062 PR Close #28664
This commit is contained in:

committed by
Miško Hevery

parent
b41da03f00
commit
627cecdfe2
@ -18,14 +18,14 @@ import {getComponentDef} from './definition';
|
||||
import {diPublicInInjector, getOrCreateNodeInjectorForNode} from './di';
|
||||
import {publishDefaultGlobalUtils} from './global_utils';
|
||||
import {registerPostOrderHooks, registerPreOrderHooks} from './hooks';
|
||||
import {addToViewTree, CLEAN_PROMISE, createLView, createNodeAtIndex, createTNode, createTView, getOrCreateTView, initNodeFlags, instantiateRootComponent, locateHostElement, queueComponentIndexForCheck, refreshDescendantViews,} from './instructions';
|
||||
import {CLEAN_PROMISE, addToViewTree, createLView, createNodeAtIndex, createTNode, createTView, getOrCreateTView, initNodeFlags, instantiateRootComponent, invokeHostBindingsInCreationMode, locateHostElement, queueComponentIndexForCheck, refreshDescendantViews} from './instructions';
|
||||
import {ComponentDef, ComponentType, RenderFlags} from './interfaces/definition';
|
||||
import {TElementNode, TNode, TNodeFlags, TNodeType} from './interfaces/node';
|
||||
import {PlayerHandler} from './interfaces/player';
|
||||
import {RElement, Renderer3, RendererFactory3, domRendererFactory3} from './interfaces/renderer';
|
||||
import {CONTEXT, FLAGS, HEADER_OFFSET, HOST, LView, LViewFlags, RootContext, RootContextFlags, TVIEW, T_HOST} from './interfaces/view';
|
||||
import {enterView, getPreviousOrParentTNode, leaveView, resetComponentState, setCurrentDirectiveDef} from './state';
|
||||
import {defaultScheduler, getRootView, readPatchedLView, renderStringify} from './util';
|
||||
import {applyOnCreateInstructions, defaultScheduler, getRootView, readPatchedLView, renderStringify} from './util';
|
||||
|
||||
|
||||
|
||||
@ -200,9 +200,10 @@ export function createRootComponent<T>(
|
||||
|
||||
if (tView.firstTemplatePass && componentDef.hostBindings) {
|
||||
const rootTNode = getPreviousOrParentTNode();
|
||||
setCurrentDirectiveDef(componentDef);
|
||||
componentDef.hostBindings(RenderFlags.Create, component, rootTNode.index - HEADER_OFFSET);
|
||||
setCurrentDirectiveDef(null);
|
||||
const expando = tView.expandoInstructions !;
|
||||
invokeHostBindingsInCreationMode(
|
||||
componentDef, expando, component, rootTNode, tView.firstTemplatePass);
|
||||
rootTNode.onElementCreationFns && applyOnCreateInstructions(rootTNode);
|
||||
}
|
||||
|
||||
return component;
|
||||
|
@ -32,7 +32,7 @@ import {CssSelectorList, NG_PROJECT_AS_ATTR_NAME} from './interfaces/projection'
|
||||
import {LQueries} from './interfaces/query';
|
||||
import {GlobalTargetResolver, ProceduralRenderer3, RComment, RElement, RText, Renderer3, RendererFactory3, isProceduralRenderer} from './interfaces/renderer';
|
||||
import {SanitizerFn} from './interfaces/sanitization';
|
||||
import {BINDING_INDEX, CLEANUP, CONTAINER_INDEX, CONTEXT, DECLARATION_VIEW, FLAGS, HEADER_OFFSET, HOST, INJECTOR, InitPhaseState, LView, LViewFlags, NEXT, OpaqueViewState, PARENT, QUERIES, RENDERER, RENDERER_FACTORY, RootContext, RootContextFlags, SANITIZER, TAIL, TData, TVIEW, TView, T_HOST} from './interfaces/view';
|
||||
import {BINDING_INDEX, CLEANUP, CONTAINER_INDEX, CONTEXT, DECLARATION_VIEW, ExpandoInstructions, FLAGS, HEADER_OFFSET, HOST, INJECTOR, InitPhaseState, LView, LViewFlags, NEXT, OpaqueViewState, PARENT, QUERIES, RENDERER, RENDERER_FACTORY, RootContext, RootContextFlags, SANITIZER, TAIL, TData, TVIEW, TView, T_HOST} from './interfaces/view';
|
||||
import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert';
|
||||
import {appendChild, appendProjectedNode, createTextNode, getLViewChild, insertView, removeView} from './node_manipulation';
|
||||
import {isNodeMatchingSelectorList, matchingSelectorIndex} from './node_selector_matcher';
|
||||
@ -41,7 +41,7 @@ import {getInitialClassNameValue, getInitialStyleStringValue, initializeStaticCo
|
||||
import {BoundPlayerFactory} from './styling/player_factory';
|
||||
import {ANIMATION_PROP_PREFIX, allocateDirectiveIntoContext, createEmptyStylingContext, forceClassesAsString, forceStylesAsString, getStylingContext, hasClassInput, hasStyleInput, hasStyling, isAnimationProp} from './styling/util';
|
||||
import {NO_CHANGE} from './tokens';
|
||||
import {INTERPOLATION_DELIMITER, findComponentView, getComponentViewByIndex, getNativeByIndex, getNativeByTNode, getRootContext, getRootView, getTNode, isComponent, isComponentDef, isContentQueryHost, isRootView, loadInternal, readElementValue, readPatchedLView, renderStringify} from './util';
|
||||
import {INTERPOLATION_DELIMITER, applyOnCreateInstructions, findComponentView, getComponentViewByIndex, getNativeByIndex, getNativeByTNode, getRootContext, getRootView, getTNode, isComponent, isComponentDef, isContentQueryHost, isRootView, loadInternal, readElementValue, readPatchedLView, renderStringify} from './util';
|
||||
|
||||
|
||||
|
||||
@ -1079,18 +1079,9 @@ export function elementEnd(): void {
|
||||
setPreviousOrParentTNode(previousOrParentTNode);
|
||||
}
|
||||
|
||||
// there may be some instructions that need to run in a specific
|
||||
// order because the CREATE block in a directive runs before the
|
||||
// CREATE block in a template. To work around this instructions
|
||||
// can get access to the function array below and defer any code
|
||||
// to run after the element is created.
|
||||
let fns: Function[]|null;
|
||||
if (fns = previousOrParentTNode.onElementCreationFns) {
|
||||
for (let i = 0; i < fns.length; i++) {
|
||||
fns[i]();
|
||||
}
|
||||
previousOrParentTNode.onElementCreationFns = null;
|
||||
}
|
||||
// this is required for all host-level styling-related instructions to run
|
||||
// in the correct order
|
||||
previousOrParentTNode.onElementCreationFns && applyOnCreateInstructions(previousOrParentTNode);
|
||||
|
||||
ngDevMode && assertNodeType(previousOrParentTNode, TNodeType.Element);
|
||||
const lView = getLView();
|
||||
@ -1815,23 +1806,29 @@ function invokeDirectivesHostBindings(tView: TView, viewData: LView, tNode: TNod
|
||||
const def = tView.data[i] as DirectiveDef<any>;
|
||||
const directive = viewData[i];
|
||||
if (def.hostBindings) {
|
||||
const previousExpandoLength = expando.length;
|
||||
setCurrentDirectiveDef(def);
|
||||
def.hostBindings !(RenderFlags.Create, directive, tNode.index - HEADER_OFFSET);
|
||||
setCurrentDirectiveDef(null);
|
||||
// `hostBindings` function may or may not contain `allocHostVars` call
|
||||
// (e.g. it may not if it only contains host listeners), so we need to check whether
|
||||
// `expandoInstructions` has changed and if not - we still push `hostBindings` to
|
||||
// expando block, to make sure we execute it for DI cycle
|
||||
if (previousExpandoLength === expando.length && firstTemplatePass) {
|
||||
expando.push(def.hostBindings);
|
||||
}
|
||||
invokeHostBindingsInCreationMode(def, expando, directive, tNode, firstTemplatePass);
|
||||
} else if (firstTemplatePass) {
|
||||
expando.push(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function invokeHostBindingsInCreationMode(
|
||||
def: DirectiveDef<any>, expando: ExpandoInstructions, directive: any, tNode: TNode,
|
||||
firstTemplatePass: boolean) {
|
||||
const previousExpandoLength = expando.length;
|
||||
setCurrentDirectiveDef(def);
|
||||
def.hostBindings !(RenderFlags.Create, directive, tNode.index - HEADER_OFFSET);
|
||||
setCurrentDirectiveDef(null);
|
||||
// `hostBindings` function may or may not contain `allocHostVars` call
|
||||
// (e.g. it may not if it only contains host listeners), so we need to check whether
|
||||
// `expandoInstructions` has changed and if not - we still push `hostBindings` to
|
||||
// expando block, to make sure we execute it for DI cycle
|
||||
if (previousExpandoLength === expando.length && firstTemplatePass) {
|
||||
expando.push(def.hostBindings);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a new block in TView.expandoInstructions for this node.
|
||||
*
|
||||
|
@ -292,6 +292,13 @@ export const enum InitPhaseState {
|
||||
InitPhaseCompleted = 0b11,
|
||||
}
|
||||
|
||||
/**
|
||||
* Set of instructions used to process host bindings efficiently.
|
||||
*
|
||||
* See VIEW_DATA.md for more information.
|
||||
*/
|
||||
export interface ExpandoInstructions extends Array<number|HostBindingsFunction<any>|null> {}
|
||||
|
||||
/**
|
||||
* The static data for an LView (shared between all templates of a
|
||||
* given type).
|
||||
@ -401,7 +408,7 @@ export interface TView {
|
||||
*
|
||||
* See VIEW_DATA.md for more information.
|
||||
*/
|
||||
expandoInstructions: (number|HostBindingsFunction<any>|null)[]|null;
|
||||
expandoInstructions: ExpandoInstructions|null;
|
||||
|
||||
/**
|
||||
* Full registry of directives and components that may be found in this view.
|
||||
|
@ -14,7 +14,7 @@ import {LContext} from '../interfaces/context';
|
||||
import {AttributeMarker, TAttributes, TNode, TNodeFlags} from '../interfaces/node';
|
||||
import {PlayState, Player, PlayerContext, PlayerIndex} from '../interfaces/player';
|
||||
import {RElement} from '../interfaces/renderer';
|
||||
import {InitialStylingValues, StylingContext, StylingFlags, StylingIndex} from '../interfaces/styling';
|
||||
import {InitialStylingValues, InitialStylingValuesIndex, StylingContext, StylingFlags, StylingIndex} from '../interfaces/styling';
|
||||
import {HEADER_OFFSET, HOST, LView, RootContext} from '../interfaces/view';
|
||||
import {getTNode} from '../util';
|
||||
|
||||
@ -100,10 +100,14 @@ export function getStylingContext(index: number, viewData: LView): StylingContex
|
||||
}
|
||||
}
|
||||
|
||||
export function isStylingContext(value: any): value is StylingContext {
|
||||
export function isStylingContext(value: any): boolean {
|
||||
// Not an LView or an LContainer
|
||||
return Array.isArray(value) && typeof value[StylingIndex.MasterFlagPosition] === 'number' &&
|
||||
value.length !== LCONTAINER_LENGTH;
|
||||
if (Array.isArray(value) && value.length >= StylingIndex.SingleStylesStartPosition) {
|
||||
return typeof value[StylingIndex.MasterFlagPosition] === 'number' &&
|
||||
value[StylingIndex.InitialClassValuesPosition]
|
||||
[InitialStylingValuesIndex.DefaultNullValuePosition] === null;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function isAnimationProp(name: string): boolean {
|
||||
|
@ -317,3 +317,18 @@ export const INTERPOLATION_DELIMITER = `<60>`;
|
||||
export function isPropMetadataString(str: string): boolean {
|
||||
return str.indexOf(INTERPOLATION_DELIMITER) >= 0;
|
||||
}
|
||||
|
||||
export function applyOnCreateInstructions(tNode: TNode) {
|
||||
// there may be some instructions that need to run in a specific
|
||||
// order because the CREATE block in a directive runs before the
|
||||
// CREATE block in a template. To work around this instructions
|
||||
// can get access to the function array below and defer any code
|
||||
// to run after the element is created.
|
||||
let fns: Function[]|null;
|
||||
if (fns = tNode.onElementCreationFns) {
|
||||
for (let i = 0; i < fns.length; i++) {
|
||||
fns[i]();
|
||||
}
|
||||
tNode.onElementCreationFns = null;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user