feat(ivy): properly apply style="", [style], [style.foo] and [attr.style] bindings (#24602)

PR Close #24602
This commit is contained in:
Matias Niemelä
2018-07-06 13:51:00 -07:00
committed by Miško Hevery
parent 52d43a99ef
commit 3980640d53
22 changed files with 1904 additions and 143 deletions
+5 -2
View File
@@ -56,8 +56,11 @@ export {
elementEnd as e,
elementProperty as p,
elementStart as E,
elementStyle as s,
elementStyleNamed as sn,
elementStyling as s,
elementStyle as sm,
elementStyleProp as sp,
elementStylingApply as sa,
listener as L,
store as st,
+119 -55
View File
@@ -27,6 +27,7 @@ import {ComponentDefInternal, ComponentTemplate, ComponentQuery, DirectiveDefInt
import {RComment, RElement, RText, Renderer3, RendererFactory3, ProceduralRenderer3, RendererStyleFlags3, isProceduralRenderer} from './interfaces/renderer';
import {isDifferent, stringify} from './util';
import {ViewRef} from './view_ref';
import {StylingContext, allocStylingContext, createStylingContextTemplate, updateStyleMap as updateElementStyleMap, updateStyleProp as updateElementStyleProp, renderStyles as renderElementStyles} from './styling';
/**
* Directive (D) sets a property on all component instances using this constant as a key and the
@@ -652,7 +653,9 @@ export function elementStart(
const node: LElementNode =
createLNode(index, TNodeType.Element, native !, name, attrs || null, null);
if (attrs) setUpAttributes(native, attrs);
if (attrs) {
setUpAttributes(native, attrs);
}
appendChild(getParentLNode(node), native, viewData);
createDirectivesAndLocals(localRefs);
return native;
@@ -1185,7 +1188,8 @@ export function createTNode(
child: null,
parent: parent,
dynamicContainerNode: null,
detached: null
detached: null,
stylingTemplate: null
};
}
@@ -1287,9 +1291,94 @@ export function elementClass<T>(index: number, value: T | NO_CHANGE): void {
}
/**
* Update a given style on an Element.
* Assign any inline style values to the element during creation mode.
*
* @param index Index of the element to change in the data array
* This instruction is meant to be called during creation mode to apply all styling
* (e.g. `style="..."`) values to the element. This is also where the provided index
* value is allocated for the styling details for its corresponding element (the element
* index is the previous index value from this one).
*
* (Note this function calls `elementStylingApply` immediately when called.)
*
*
* @param index Index value which will be allocated to store styling data for the element.
* (Note that this is not the element index, but rather an index value allocated
* specifically for element styling--the index must be the next index after the element
* index.)
* @param styles A key/value map of CSS styles that will be registered on the element.
* Each individual style will be used on the element as long as it is not overridden
* by any styles placed on the element by multiple (`[style]`) or singular (`[style.prop]`)
* bindings. If a style binding changes its value to null then the initial styling
* values that are passed in here will be applied to the element (if matched).
*/
export function elementStyling<T>(index: number, styles?: (string | number)[] | null): void {
const tNode = load<LElementNode>(index - 1).tNode;
if (!tNode.stylingTemplate) {
// initialize the styling template.
tNode.stylingTemplate = createStylingContextTemplate(styles);
}
// Allocate space but leave null for lazy creation.
viewData[index + HEADER_OFFSET] = null;
if (styles && styles.length) {
elementStylingApply(index);
}
}
/**
* Retrieve the `StylingContext` at a given index.
*
* This method lazily creates the `StylingContext`. This is because in most cases
* we have styling without any bindings. Creating `StylingContext` eagerly would mean that
* every style declaration such as `<div style="color: 'red' ">` would result `StyleContext`
* which would create unnecessary memory pressure.
*
* @param index Index of the style allocation. See: `elementStyling`.
*/
function getStylingContext(index: number): StylingContext {
let stylingContext = load<StylingContext>(index);
if (!stylingContext) {
const lElement: LElementNode = load(index - 1);
const tNode = lElement.tNode;
ngDevMode &&
assertDefined(tNode.stylingTemplate, 'getStylingContext() called before elementStyling()');
stylingContext = viewData[index + HEADER_OFFSET] = allocStylingContext(tNode.stylingTemplate !);
}
return stylingContext;
}
/**
* Apply all styling values to the element which have been queued by any styling instructions.
*
* This instruction is meant to be run once one or more `elementStyle` and/or `elementStyleProp`
* have been issued against the element. This function will also determine if any styles have
* changed and will then skip the operation if there is nothing new to render.
*
* Once called then all queued styles will be flushed.
*
* @param index Index of the element's styling storage that will be rendered.
* (Note that this is not the element index, but rather an index value allocated
* specifically for element styling--the index must be the next index after the element
* index.)
*/
export function elementStylingApply<T>(index: number): void {
renderElementStyles(load<LElementNode>(index - 1), getStylingContext(index), renderer);
}
/**
* Queue a given style to be rendered on an Element.
*
* If the style value is `null` then it will be removed from the element
* (or assigned a different value depending if there are any styles placed
* on the element with `elementStyle` or any styles that are present
* from when the element was created (with `elementStyling`).
*
* (Note that the styling instruction will not be applied until `elementStylingApply` is called.)
*
* @param index Index of the element's styling storage to change in the data array.
* (Note that this is not the element index, but rather an index value allocated
* specifically for element styling--the index must be the next index after the element
* index.)
* @param styleIndex Index of the style property on this element. (Monotonically increasing.)
* @param styleName Name of property. Because it is going to DOM this is not subject to
* renaming as part of minification.
* @param value New value to write (null to remove).
@@ -1297,69 +1386,44 @@ export function elementClass<T>(index: number, value: T | NO_CHANGE): void {
* @param sanitizer An optional function used to transform the value typically used for
* sanitization.
*/
export function elementStyleNamed<T>(
index: number, styleName: string, value: T | NO_CHANGE, suffix?: string): void;
export function elementStyleNamed<T>(
index: number, styleName: string, value: T | NO_CHANGE, sanitizer?: SanitizerFn): void;
export function elementStyleNamed<T>(
index: number, styleName: string, value: T | NO_CHANGE,
export function elementStyleProp<T>(
index: number, styleIndex: number, value: T | null, suffix?: string): void;
export function elementStyleProp<T>(
index: number, styleIndex: number, value: T | null, sanitizer?: SanitizerFn): void;
export function elementStyleProp<T>(
index: number, styleIndex: number, value: T | null,
suffixOrSanitizer?: string | SanitizerFn): void {
if (value !== NO_CHANGE) {
const lElement: LElementNode = load(index);
if (value == null) {
ngDevMode && ngDevMode.rendererRemoveStyle++;
isProceduralRenderer(renderer) ?
renderer.removeStyle(lElement.native, styleName, RendererStyleFlags3.DashCase) :
lElement.native['style'].removeProperty(styleName);
} else {
let strValue =
typeof suffixOrSanitizer == 'function' ? suffixOrSanitizer(value) : stringify(value);
if (typeof suffixOrSanitizer == 'string') strValue = strValue + suffixOrSanitizer;
ngDevMode && ngDevMode.rendererSetStyle++;
isProceduralRenderer(renderer) ?
renderer.setStyle(lElement.native, styleName, strValue, RendererStyleFlags3.DashCase) :
lElement.native['style'].setProperty(styleName, strValue);
let valueToAdd: string|null = null;
if (value) {
valueToAdd =
typeof suffixOrSanitizer == 'function' ? suffixOrSanitizer(value) : stringify(value);
if (typeof suffixOrSanitizer == 'string') {
valueToAdd = valueToAdd + suffixOrSanitizer;
}
}
updateElementStyleProp(getStylingContext(index), styleIndex, valueToAdd);
}
/**
* Set the `style` property on a DOM element.
* Queue a key/value map of styles to be rendered on an Element.
*
* This instruction is meant to handle the `[style]="exp"` usage.
* This instruction is meant to handle the `[style]="exp"` usage. When styles are applied to
* the Element they will then be placed with respect to any styles set with `elementStyleProp`.
* If any styles are set to `null` then they will be removed from the element (unless the same
* style properties have been assigned to the element during creation using `elementStyling`).
*
* (Note that the styling instruction will not be applied until `elementStylingApply` is called.)
*
* @param index The index of the element to update in the LViewData array
* @param index Index of the element's styling storage to change in the data array.
* (Note that this is not the element index, but rather an index value allocated
* specifically for element styling--the index must be the next index after the element
* index.)
* @param value A value indicating if a given style should be added or removed.
* The expected shape of `value` is an object where keys are style names and the values
* are their corresponding values to set. If value is falsy, then the style is removed. An absence
* of style does not cause that style to be removed. `NO_CHANGE` implies that no update should be
* performed.
* are their corresponding values to set. If value is null, then the style is removed.
*/
export function elementStyle<T>(
index: number, value: {[styleName: string]: any} | NO_CHANGE): void {
if (value !== NO_CHANGE) {
// TODO: This is a naive implementation which simply writes value to the `style`. In the future
// we will add logic here which would work with the animation code.
const lElement = load(index) as LElementNode;
if (isProceduralRenderer(renderer)) {
ngDevMode && ngDevMode.rendererSetStyle++;
renderer.setProperty(lElement.native, 'style', value);
} else {
const style = lElement.native['style'];
for (let i = 0, keys = Object.keys(value); i < keys.length; i++) {
const styleName: string = keys[i];
const styleValue: any = (value as any)[styleName];
if (styleValue == null) {
ngDevMode && ngDevMode.rendererRemoveStyle++;
style.removeProperty(styleName);
} else {
ngDevMode && ngDevMode.rendererSetStyle++;
style.setProperty(styleName, styleValue);
}
}
}
}
export function elementStyle<T>(index: number, value: {[styleName: string]: any} | null): void {
updateElementStyleMap(getStylingContext(index), value);
}
//////////////////////////
@@ -290,3 +290,9 @@ export type PipeTypeList =
// Note: This hack is necessary so we don't erroneously get a circular dependency
// failure based on types.
export const unusedValueExportToPlacateAjd = 1;
// Note this will expand once `class` is introduced to styling
export const enum InitialStylingFlags {
/** Mode for matching initial style values */
INITIAL_STYLES = 0b00,
}
@@ -6,6 +6,8 @@
* found in the LICENSE file at https://angular.io/license
*/
import {StylingContext} from '../styling';
import {LContainer} from './container';
import {LInjector} from './injector';
import {LProjection} from './projection';
@@ -341,6 +343,8 @@ export interface TNode {
* If this node is not part of an i18n block, this field is null.
*/
detached: boolean|null;
stylingTemplate: StylingContext|null;
}
/** Static data for an LElementNode */
+3 -2
View File
@@ -80,8 +80,9 @@ export const angularCoreEnv: {[name: string]: Function} = {
'ɵqR': r3.qR,
'ɵrS': r3.rS,
'ɵs': r3.s,
'ɵsn': r3.sn,
'ɵst': r3.st,
'ɵsm': r3.sm,
'ɵsp': r3.sp,
'ɵsa': r3.sa,
'ɵT': r3.T,
'ɵt': r3.t,
'ɵV': r3.V,
File diff suppressed because it is too large Load Diff