fix(ivy): temporary hack to set host styles and static classes (#27385)

PR Close #27385
This commit is contained in:
Kara Erickson 2018-11-30 18:25:41 -08:00 committed by Alex Rickabaugh
parent 7d89cff545
commit 2a39425e48
6 changed files with 147 additions and 13 deletions

View File

@ -38,13 +38,14 @@ import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert';
import {appendChild, appendProjectedNode, createTextNode, findComponentView, getLViewChild, getRenderParent, insertView, removeView} from './node_manipulation';
import {isNodeMatchingSelectorList, matchingSelectorIndex} from './node_selector_matcher';
import {decreaseElementDepthCount, enterView, getBindingsEnabled, getCheckNoChangesMode, getContextLView, getCreationMode, getCurrentDirectiveDef, getElementDepthCount, getFirstTemplatePass, getIsParent, getLView, getPreviousOrParentTNode, increaseElementDepthCount, leaveView, nextContextImpl, resetComponentState, setBindingRoot, setCheckNoChangesMode, setCurrentDirectiveDef, setFirstTemplatePass, setIsParent, setPreviousOrParentTNode} from './state';
import {createStylingContextTemplate, renderStyleAndClassBindings, updateClassProp as updateElementClassProp, updateStyleProp as updateElementStyleProp, updateStylingMap} from './styling/class_and_style_bindings';
import {createStylingContextTemplate, renderStyleAndClassBindings, setStyle, updateClassProp as updateElementClassProp, updateStyleProp as updateElementStyleProp, updateStylingMap} from './styling/class_and_style_bindings';
import {BoundPlayerFactory} from './styling/player_factory';
import {getStylingContext} from './styling/util';
import {NO_CHANGE} from './tokens';
import {getComponentViewByIndex, getNativeByIndex, getNativeByTNode, getRootContext, getRootView, getTNode, isComponent, isComponentDef, loadInternal, readElementValue, readPatchedLView, stringify} from './util';
/**
* A permanent marker promise which signifies that the current CD tree is
* clean.
@ -1216,9 +1217,6 @@ export function elementStylingApply(index: number, directive?: {}): void {
export function elementStyleProp(
index: number, styleIndex: number, value: string | number | String | PlayerFactory | null,
suffix?: string, directive?: {}): void {
if (directive != undefined)
return hackImplementationOfElementStyleProp(
index, styleIndex, value, suffix, directive); // supported in next PR
let valueToAdd: string|null = null;
if (value !== null) {
if (suffix) {
@ -1233,7 +1231,11 @@ export function elementStyleProp(
valueToAdd = value as any as string;
}
}
if (directive != undefined) {
hackImplementationOfElementStyleProp(index, styleIndex, valueToAdd, suffix, directive);
} else {
updateElementStyleProp(getStylingContext(index, getLView()), styleIndex, valueToAdd);
}
}
/**
@ -1293,14 +1295,40 @@ function hackImplementationOfElementStyling(
classDeclarations: (string | boolean | InitialStylingFlags)[] | null,
styleDeclarations: (string | boolean | InitialStylingFlags)[] | null,
styleSanitizer: StyleSanitizeFn | null, directive: {}): void {
const node = getNativeByTNode(getPreviousOrParentTNode(), getLView());
const node = getNativeByTNode(getPreviousOrParentTNode(), getLView()) as RElement;
ngDevMode && assertDefined(node, 'expecting parent DOM node');
const hostStylingHackMap: HostStylingHackMap =
((node as any).hostStylingHack || ((node as any).hostStylingHack = new Map()));
const squashedClassDeclarations = hackSquashDeclaration(classDeclarations);
hostStylingHackMap.set(directive, {
classDeclarations: hackSquashDeclaration(classDeclarations),
classDeclarations: squashedClassDeclarations,
styleDeclarations: hackSquashDeclaration(styleDeclarations), styleSanitizer
});
hackSetStaticClasses(node, squashedClassDeclarations);
}
function hackSetStaticClasses(node: RElement, classDeclarations: (string | boolean)[]) {
// Static classes need to be set here because static classes don't generate
// elementClassProp instructions.
const lView = getLView();
const staticClassStartIndex =
classDeclarations.indexOf(InitialStylingFlags.VALUES_MODE as any) + 1;
const renderer = lView[RENDERER];
for (let i = staticClassStartIndex; i < classDeclarations.length; i += 2) {
const className = classDeclarations[i] as string;
const value = classDeclarations[i + 1];
// if value is true, then this is a static class and we should set it now.
// class bindings are set separately in elementClassProp.
if (value === true) {
if (isProceduralRenderer(renderer)) {
renderer.addClass(node, className);
} else {
const classList = (node as HTMLElement).classList;
classList.add(className);
}
}
}
}
function hackSquashDeclaration(declarations: (string | boolean | InitialStylingFlags)[] | null):
@ -1330,9 +1358,15 @@ function hackImplementationOfElementStylingApply(index: number, directive?: {}):
}
function hackImplementationOfElementStyleProp(
index: number, styleIndex: number, value: string | number | String | PlayerFactory | null,
suffix?: string, directive?: {}): void {
throw new Error('unimplemented. Should not be needed by ViewEngine compatibility');
index: number, styleIndex: number, value: string | null, suffix?: string,
directive?: {}): void {
const lView = getLView();
const node = getNativeByIndex(index, lView);
ngDevMode && assertDefined(node, 'could not locate node');
const hostStylingHack: HostStylingHack = (node as any).hostStylingHack.get(directive);
const styleName = hostStylingHack.styleDeclarations[styleIndex];
const renderer = lView[RENDERER];
setStyle(node, styleName, value as string, renderer, null);
}
function hackImplementationOfElementStylingMap<T>(

View File

@ -594,7 +594,7 @@ export function renderStyleAndClassBindings(
* @param renderer
* @param store an optional key/value map that will be used as a context to render styles on
*/
function setStyle(
export function setStyle(
native: any, prop: string, value: string | null, renderer: Renderer3,
sanitizer: StyleSanitizeFn | null, store?: BindingStore | null,
playerBuilder?: ClassAndStylePlayerBuilder<any>| null) {

View File

@ -785,6 +785,9 @@
{
"name": "hackImplementationOfElementStylingMap"
},
{
"name": "hackSetStaticClasses"
},
{
"name": "hackSquashDeclaration"
},

View File

@ -818,6 +818,9 @@
{
"name": "hackImplementationOfElementStylingApply"
},
{
"name": "hackSetStaticClasses"
},
{
"name": "hackSquashDeclaration"
},

View File

@ -2000,6 +2000,9 @@
{
"name": "hackImplementationOfElementStylingApply"
},
{
"name": "hackSetStaticClasses"
},
{
"name": "hackSquashDeclaration"
},

View File

@ -9,9 +9,9 @@
import {ElementRef, EventEmitter} from '@angular/core';
import {AttributeMarker, defineComponent, template, defineDirective, InheritDefinitionFeature, ProvidersFeature, NgOnChangesFeature, QueryList} from '../../src/render3/index';
import {allocHostVars, bind, directiveInject, element, elementEnd, elementProperty, elementStart, listener, load, text, textBinding, loadQueryList, registerContentQuery} from '../../src/render3/instructions';
import {allocHostVars, bind, directiveInject, element, elementEnd, elementProperty, elementStyleProp, elementStyling, elementStylingApply, elementStart, listener, load, text, textBinding, loadQueryList, registerContentQuery} from '../../src/render3/instructions';
import {query, queryRefresh} from '../../src/render3/query';
import {RenderFlags} from '../../src/render3/interfaces/definition';
import {RenderFlags, InitialStylingFlags} from '../../src/render3/interfaces/definition';
import {pureFunction1, pureFunction2} from '../../src/render3/pure_function';
import {ComponentFixture, TemplateFixture, createComponent, createDirective} from './render_util';
@ -978,4 +978,95 @@ describe('host bindings', () => {
const hostBindingEl = fixture.hostElement.querySelector('host-binding-comp') as HTMLElement;
expect(hostBindingEl.id).toEqual('after-content');
});
describe('styles', () => {
it('should bind to host styles', () => {
let hostBindingDir !: HostBindingToStyles;
/**
* host: {
* '[style.width.px]': 'width'
* }
*/
class HostBindingToStyles {
width = 2;
static ngComponentDef = defineComponent({
type: HostBindingToStyles,
selectors: [['host-binding-to-styles']],
factory: () => hostBindingDir = new HostBindingToStyles(),
consts: 0,
vars: 0,
hostBindings: (rf: RenderFlags, ctx: HostBindingToStyles, elIndex: number) => {
if (rf & RenderFlags.Create) {
allocHostVars(0); // this is wrong, but necessary until FW-761 gets in
elementStyling(null, ['width'], null, ctx);
}
if (rf & RenderFlags.Update) {
elementStyleProp(0, 0, ctx.width, 'px', ctx);
elementStylingApply(0, ctx);
}
},
template: (rf: RenderFlags, cmp: HostBindingToStyles) => {}
});
}
/** <host-binding-to-styles></host-binding-to-styles> */
const App = createComponent('app', (rf: RenderFlags, ctx: any) => {
if (rf & RenderFlags.Create) {
element(0, 'host-binding-to-styles');
}
}, 1, 0, [HostBindingToStyles]);
const fixture = new ComponentFixture(App);
const hostBindingEl =
fixture.hostElement.querySelector('host-binding-to-styles') 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', () => {
/**
* host: {
* 'class': 'mat-toolbar'
* }
*/
class StaticHostClass {
static ngComponentDef = defineComponent({
type: StaticHostClass,
selectors: [['static-host-class']],
factory: () => new StaticHostClass(),
consts: 0,
vars: 0,
hostBindings: (rf: RenderFlags, ctx: StaticHostClass, elIndex: number) => {
if (rf & RenderFlags.Create) {
allocHostVars(0); // this is wrong, but necessary until FW-761 gets in
elementStyling(
['mat-toolbar', InitialStylingFlags.VALUES_MODE, 'mat-toolbar', true], null, null,
ctx);
}
if (rf & RenderFlags.Update) {
elementStylingApply(0, ctx);
}
},
template: (rf: RenderFlags, cmp: StaticHostClass) => {}
});
}
/** <static-host-class></static-host-class> */
const App = createComponent('app', (rf: RenderFlags, ctx: any) => {
if (rf & RenderFlags.Create) {
element(0, 'static-host-class');
}
}, 1, 0, [StaticHostClass]);
const fixture = new ComponentFixture(App);
const hostBindingEl = fixture.hostElement.querySelector('static-host-class') as HTMLElement;
expect(hostBindingEl.className).toEqual('mat-toolbar');
});
});
});