fix(ivy): fix styling context resolution for host bindings on containers (#28221)

Previous to this change, the isStylingContext() function was improperly
returning true for LContainers because it used the presence of an array
at index 2 to determine whether it was a styling context. Unfortunately,
LContainers also contain arrays at index 2, so this would return a false
positive. This led to other errors down the line because we would treat
nodes with containers as if they already had styling contexts (even if
they did not), so the proper initialization logic for styling contexts
was not run.

This commit fixes the isStylingContext() function to use LCONTAINER_LENGTH
as a marker rather than the presence of an array, which in turn fixes
host bindings to styles on nodes with containers.

PR Close #28221
This commit is contained in:
Kara Erickson 2019-01-17 18:21:43 -08:00 committed by Alex Rickabaugh
parent 7980f1d2ea
commit 058aafcc0c
2 changed files with 55 additions and 3 deletions

View File

@ -9,7 +9,7 @@ import '../../util/ng_dev_mode';
import {StyleSanitizeFn} from '../../sanitization/style_sanitizer'; import {StyleSanitizeFn} from '../../sanitization/style_sanitizer';
import {getLContext} from '../context_discovery'; import {getLContext} from '../context_discovery';
import {LContainer} from '../interfaces/container'; import {LCONTAINER_LENGTH, LContainer} from '../interfaces/container';
import {LContext} from '../interfaces/context'; import {LContext} from '../interfaces/context';
import {AttributeMarker, TAttributes, TNode, TNodeFlags} from '../interfaces/node'; import {AttributeMarker, TAttributes, TNode, TNodeFlags} from '../interfaces/node';
import {PlayState, Player, PlayerContext, PlayerIndex} from '../interfaces/player'; import {PlayState, Player, PlayerContext, PlayerIndex} from '../interfaces/player';
@ -96,7 +96,7 @@ export function getStylingContext(index: number, viewData: LView): StylingContex
export function isStylingContext(value: any): value is StylingContext { export function isStylingContext(value: any): value is StylingContext {
// Not an LView or an LContainer // Not an LView or an LContainer
return Array.isArray(value) && typeof value[StylingIndex.MasterFlagPosition] === 'number' && return Array.isArray(value) && typeof value[StylingIndex.MasterFlagPosition] === 'number' &&
Array.isArray(value[StylingIndex.InitialStyleValuesPosition]); value.length !== LCONTAINER_LENGTH;
} }
export function isAnimationProp(name: string): boolean { export function isAnimationProp(name: string): boolean {

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {ElementRef, QueryList} from '@angular/core'; import {ElementRef, QueryList, ViewContainerRef} from '@angular/core';
import {AttributeMarker, defineComponent, template, defineDirective, InheritDefinitionFeature, ProvidersFeature} from '../../src/render3/index'; import {AttributeMarker, defineComponent, template, defineDirective, InheritDefinitionFeature, ProvidersFeature} from '../../src/render3/index';
import {allocHostVars, bind, directiveInject, element, elementAttribute, elementEnd, elementProperty, elementStyleProp, elementStyling, elementStylingApply, elementStart, listener, load, text, textBinding, loadQueryList, registerContentQuery, elementHostAttrs} from '../../src/render3/instructions'; import {allocHostVars, bind, directiveInject, element, elementAttribute, elementEnd, elementProperty, elementStyleProp, elementStyling, elementStylingApply, elementStart, listener, load, text, textBinding, loadQueryList, registerContentQuery, elementHostAttrs} from '../../src/render3/instructions';
@ -1131,6 +1131,58 @@ describe('host bindings', () => {
expect(hostBindingEl.style.width).toEqual('5px'); expect(hostBindingEl.style.width).toEqual('5px');
}); });
it('should bind to host styles on containers', () => {
let hostBindingDir !: HostBindingToStyles;
/**
* host: {
* '[style.width.px]': 'width'
* }
*/
class HostBindingToStyles {
width = 2;
static ngDirectiveDef = defineDirective({
type: HostBindingToStyles,
selectors: [['', 'hostStyles', '']],
factory: () => hostBindingDir = new HostBindingToStyles(),
hostBindings: (rf: RenderFlags, ctx: HostBindingToStyles, elIndex: number) => {
if (rf & RenderFlags.Create) {
elementStyling(null, ['width'], null, ctx);
}
if (rf & RenderFlags.Update) {
elementStyleProp(0, 0, ctx.width, 'px', ctx);
elementStylingApply(0, ctx);
}
}
});
}
class ContainerDir {
constructor(public vcr: ViewContainerRef) {}
static ngDirectiveDef = defineDirective({
type: ContainerDir,
selectors: [['', 'containerDir', '']],
factory: () => new ContainerDir(directiveInject(ViewContainerRef as any)),
});
}
/** <div hostStyles containerDir></div> */
const App = createComponent('app', (rf: RenderFlags, ctx: any) => {
if (rf & RenderFlags.Create) {
element(0, 'div', ['containerDir', '', 'hostStyles', '']);
}
}, 1, 0, [ContainerDir, HostBindingToStyles]);
const fixture = new ComponentFixture(App);
const hostBindingEl = fixture.hostElement.querySelector('div') as HTMLElement;
expect(hostBindingEl.style.width).toEqual('2px');
hostBindingDir.width = 5;
fixture.update();
expect(hostBindingEl.style.width).toEqual('5px');
});
it('should apply static host classes', () => { it('should apply static host classes', () => {
/** /**
* host: { * host: {