fix(core): Host classes should not be fed back into @Input
(#35889)
Previously all static styling information (including the ones from component/directive host bindings) would get merged into a single value before it would be written into the `@Input('class'/'style')`. The new behavior specifically excludes host values from the `@Input` bindings. Fix #35383 PR Close #35889
This commit is contained in:

committed by
Kara Erickson

parent
aaa89bb715
commit
cda2530df5
@ -177,7 +177,7 @@ export function createRootComponentView(
|
||||
const tNode: TElementNode = getOrCreateTNode(tView, null, 0, TNodeType.Element, null, null);
|
||||
const mergedAttrs = tNode.mergedAttrs = def.hostAttrs;
|
||||
if (mergedAttrs !== null) {
|
||||
computeStaticStyling(tNode, mergedAttrs);
|
||||
computeStaticStyling(tNode, mergedAttrs, true);
|
||||
if (rNode !== null) {
|
||||
setUpAttributes(hostRenderer, rNode, mergedAttrs);
|
||||
if (tNode.classes !== null) {
|
||||
|
@ -39,8 +39,12 @@ function elementStartFirstCreatePass(
|
||||
resolveDirectives(tView, lView, tNode, getConstant<string[]>(tViewConsts, localRefsIndex));
|
||||
ngDevMode && logUnknownElementError(tView, lView, native, tNode, hasDirectives);
|
||||
|
||||
if (tNode.attrs !== null) {
|
||||
computeStaticStyling(tNode, tNode.attrs, false);
|
||||
}
|
||||
|
||||
if (tNode.mergedAttrs !== null) {
|
||||
computeStaticStyling(tNode, tNode.mergedAttrs);
|
||||
computeStaticStyling(tNode, tNode.mergedAttrs, true);
|
||||
}
|
||||
|
||||
if (tView.queries !== null) {
|
||||
@ -148,12 +152,12 @@ export function ɵɵelementEnd(): void {
|
||||
}
|
||||
}
|
||||
|
||||
if (tNode.classes !== null && hasClassInput(tNode)) {
|
||||
setDirectiveInputsWhichShadowsStyling(tView, tNode, getLView(), tNode.classes, true);
|
||||
if (tNode.classesWithoutHost != null && hasClassInput(tNode)) {
|
||||
setDirectiveInputsWhichShadowsStyling(tView, tNode, getLView(), tNode.classesWithoutHost, true);
|
||||
}
|
||||
|
||||
if (tNode.styles !== null && hasStyleInput(tNode)) {
|
||||
setDirectiveInputsWhichShadowsStyling(tView, tNode, getLView(), tNode.styles, false);
|
||||
if (tNode.stylesWithoutHost != null && hasStyleInput(tNode)) {
|
||||
setDirectiveInputsWhichShadowsStyling(tView, tNode, getLView(), tNode.stylesWithoutHost, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -33,7 +33,7 @@ function elementContainerStartFirstCreatePass(
|
||||
// While ng-container doesn't necessarily support styling, we use the style context to identify
|
||||
// and execute directives on the ng-container.
|
||||
if (attrs !== null) {
|
||||
computeStaticStyling(tNode, attrs);
|
||||
computeStaticStyling(tNode, attrs, true);
|
||||
}
|
||||
|
||||
const localRefs = getConstant<string[]>(tViewConsts, localRefsIndex);
|
||||
|
@ -179,8 +179,10 @@ class TNode implements ITNode {
|
||||
public parent: TElementNode|TContainerNode|null, //
|
||||
public projection: number|(ITNode|RNode[])[]|null, //
|
||||
public styles: string|null, //
|
||||
public stylesWithoutHost: string|null, //
|
||||
public residualStyles: KeyValueArray<any>|undefined|null, //
|
||||
public classes: string|null, //
|
||||
public classesWithoutHost: string|null, //
|
||||
public residualClasses: KeyValueArray<any>|undefined|null, //
|
||||
public classBindings: TStylingRange, //
|
||||
public styleBindings: TStylingRange, //
|
||||
|
@ -851,8 +851,10 @@ export function createTNode(
|
||||
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;
|
||||
@ -881,8 +883,10 @@ export function createTNode(
|
||||
parent: tParent,
|
||||
projection: null,
|
||||
styles: null,
|
||||
stylesWithoutHost: null,
|
||||
residualStyles: undefined,
|
||||
classes: null,
|
||||
classesWithoutHost: null,
|
||||
residualClasses: undefined,
|
||||
classBindings: 0 as any,
|
||||
styleBindings: 0 as any,
|
||||
|
@ -221,7 +221,7 @@ export function checkStylingMap(
|
||||
// the binding has removed it. This would confuse `[ngStyle]`/`[ngClass]` to do the wrong
|
||||
// thing as it would think that the static portion was removed. For this reason we
|
||||
// concatenate it so that `[ngStyle]`/`[ngClass]` can continue to work on changed.
|
||||
let staticPrefix = isClassBased ? tNode.classes : tNode.styles;
|
||||
let staticPrefix = isClassBased ? tNode.classesWithoutHost : tNode.stylesWithoutHost;
|
||||
ngDevMode && isClassBased === false && staticPrefix !== null &&
|
||||
assertEqual(
|
||||
staticPrefix.endsWith(';'), true, 'Expecting static portion to end with \';\'');
|
||||
|
@ -502,14 +502,30 @@ export interface TNode {
|
||||
projection: (TNode|RNode[])[]|number|null;
|
||||
|
||||
/**
|
||||
* A collection of all style static values for an element.
|
||||
* A collection of all `style` static values for an element (including from host).
|
||||
*
|
||||
* This field will be populated if and when:
|
||||
*
|
||||
* - There are one or more initial styles on an element (e.g. `<div style="width:200px">`)
|
||||
* - There are one or more initial `style`s on an element (e.g. `<div style="width:200px;">`)
|
||||
* - There are one or more initial `style`s on a directive/component host
|
||||
* (e.g. `@Directive({host: {style: "width:200px;" } }`)
|
||||
*/
|
||||
styles: string|null;
|
||||
|
||||
|
||||
/**
|
||||
* A collection of all `style` static values for an element excluding host sources.
|
||||
*
|
||||
* Populated when there are one or more initial `style`s on an element
|
||||
* (e.g. `<div style="width:200px;">`)
|
||||
* Must be stored separately from `tNode.styles` to facilitate setting directive
|
||||
* inputs that shadow the `style` property. If we used `tNode.styles` as is for shadowed inputs,
|
||||
* we would feed host styles back into directives as "inputs". If we used `tNode.attrs`, we would
|
||||
* have to concatenate the attributes on every template pass. Instead, we process once on first
|
||||
* create pass and store here.
|
||||
*/
|
||||
stylesWithoutHost: string|null;
|
||||
|
||||
/**
|
||||
* A `KeyValueArray` version of residual `styles`.
|
||||
*
|
||||
@ -540,14 +556,29 @@ export interface TNode {
|
||||
residualStyles: KeyValueArray<any>|undefined|null;
|
||||
|
||||
/**
|
||||
* A collection of all class static values for an element.
|
||||
* A collection of all class static values for an element (including from host).
|
||||
*
|
||||
* This field will be populated if and when:
|
||||
*
|
||||
* - There are one or more initial classes on an element (e.g. `<div class="one two three">`)
|
||||
* - There are one or more initial classes on an directive/component host
|
||||
* (e.g. `@Directive({host: {class: "SOME_CLASS" } }`)
|
||||
*/
|
||||
classes: string|null;
|
||||
|
||||
/**
|
||||
* A collection of all class static values for an element excluding host sources.
|
||||
*
|
||||
* Populated when there are one or more initial classes on an element
|
||||
* (e.g. `<div class="SOME_CLASS">`)
|
||||
* Must be stored separately from `tNode.classes` to facilitate setting directive
|
||||
* inputs that shadow the `class` property. If we used `tNode.classes` as is for shadowed inputs,
|
||||
* we would feed host classes back into directives as "inputs". If we used `tNode.attrs`, we would
|
||||
* have to concatenate the attributes on every template pass. Instead, we process once on first
|
||||
* create pass and store here.
|
||||
*/
|
||||
classesWithoutHost: string|null;
|
||||
|
||||
/**
|
||||
* A `KeyValueArray` version of residual `classes`.
|
||||
*
|
||||
|
@ -18,25 +18,31 @@ import {getTView} from '../state';
|
||||
*
|
||||
* @param tNode The `TNode` into which the styling information should be loaded.
|
||||
* @param attrs `TAttributes` containing the styling information.
|
||||
* @param writeToHost Where should the resulting static styles be written?
|
||||
* - `false` Write to `TNode.stylesWithoutHost` / `TNode.classesWithoutHost`
|
||||
* - `true` Write to `TNode.styles` / `TNode.classes`
|
||||
*/
|
||||
export function computeStaticStyling(tNode: TNode, attrs: TAttributes): void {
|
||||
export function computeStaticStyling(
|
||||
tNode: TNode, attrs: TAttributes|null, writeToHost: boolean): void {
|
||||
ngDevMode &&
|
||||
assertFirstCreatePass(getTView(), 'Expecting to be called in first template pass only');
|
||||
let styles: string|null = tNode.styles;
|
||||
let classes: string|null = tNode.classes;
|
||||
let styles: string|null = writeToHost ? tNode.styles : null;
|
||||
let classes: string|null = writeToHost ? tNode.classes : null;
|
||||
let mode: AttributeMarker|0 = 0;
|
||||
for (let i = 0; i < attrs.length; i++) {
|
||||
const value = attrs[i];
|
||||
if (typeof value === 'number') {
|
||||
mode = value;
|
||||
} else if (mode == AttributeMarker.Classes) {
|
||||
classes = concatStringsWithSpace(classes, value as string);
|
||||
} else if (mode == AttributeMarker.Styles) {
|
||||
const style = value as string;
|
||||
const styleValue = attrs[++i] as string;
|
||||
styles = concatStringsWithSpace(styles, style + ': ' + styleValue + ';');
|
||||
if (attrs !== null) {
|
||||
for (let i = 0; i < attrs.length; i++) {
|
||||
const value = attrs[i];
|
||||
if (typeof value === 'number') {
|
||||
mode = value;
|
||||
} else if (mode == AttributeMarker.Classes) {
|
||||
classes = concatStringsWithSpace(classes, value as string);
|
||||
} else if (mode == AttributeMarker.Styles) {
|
||||
const style = value as string;
|
||||
const styleValue = attrs[++i] as string;
|
||||
styles = concatStringsWithSpace(styles, style + ': ' + styleValue + ';');
|
||||
}
|
||||
}
|
||||
}
|
||||
styles !== null && (tNode.styles = styles);
|
||||
classes !== null && (tNode.classes = classes);
|
||||
writeToHost ? tNode.styles = styles : tNode.stylesWithoutHost = styles;
|
||||
writeToHost ? tNode.classes = classes : tNode.classesWithoutHost = classes;
|
||||
}
|
Reference in New Issue
Block a user