diff --git a/packages/compiler-cli/test/compliance/r3_view_compiler_binding_spec.ts b/packages/compiler-cli/test/compliance/r3_view_compiler_binding_spec.ts index e93e8f9e34..ef78ee8caa 100644 --- a/packages/compiler-cli/test/compliance/r3_view_compiler_binding_spec.ts +++ b/packages/compiler-cli/test/compliance/r3_view_compiler_binding_spec.ts @@ -296,11 +296,17 @@ describe('compiler compliance: bindings', () => { }; const HostAttributeDirDeclaration = ` + const $c0$ = ["aria-label", "label"]; + … HostAttributeDir.ngDirectiveDef = $r3$.ɵdefineDirective({ type: HostAttributeDir, selectors: [["", "hostAttributeDir", ""]], factory: function HostAttributeDir_Factory(t) { return new (t || HostAttributeDir)(); }, - attributes: ["aria-label", "label"] + hostBindings: function HostAttributeDir_HostBindings(rf, ctx, elIndex) { + if (rf & 1) { + $r3$.ɵelementHostAttrs(ctx, $c0$); + } + } }); `; @@ -310,6 +316,75 @@ describe('compiler compliance: bindings', () => { expectEmit(source, HostAttributeDirDeclaration, 'Invalid host attribute code'); }); + it('should support host attributes together with host classes and styles', () => { + const files = { + app: { + 'spec.ts': ` + import {Component, Directive, NgModule} from '@angular/core'; + + @Component({ + selector: 'my-host-attribute-component', + template: "...", + host: { + 'title': 'hello there from component', + 'style': 'opacity:1' + } + }) + export class HostAttributeComp { + } + + @Directive({ + selector: '[hostAttributeDir]', + host: { + 'style': 'width: 200px; height: 500px', + '[style.opacity]': "true", + 'class': 'one two', + '[class.three]': "true", + 'title': 'hello there from directive', + } + }) + export class HostAttributeDir { + } + + @NgModule({declarations: [HostAttributeComp, HostAttributeDir]}) + export class MyModule {} + ` + } + }; + + const CompAndDirDeclaration = ` + const $c0$ = ["title", "hello there from component", ${AttributeMarker.Styles}, "opacity", "1"]; + const $c1$ = ["title", "hello there from directive", ${AttributeMarker.Classes}, "one", "two", ${AttributeMarker.Styles}, "width", "200px", "height", "500px"]; + … + HostAttributeComp.ngComponentDef = $r3$.ɵdefineComponent({ + type: HostAttributeComp, + selectors: [["my-host-attribute-component"]], + factory: function HostAttributeComp_Factory(t) { return new (t || HostAttributeComp)(); }, + hostBindings: function HostAttributeComp_HostBindings(rf, ctx, elIndex) { + if (rf & 1) { + $r3$.ɵelementHostAttrs(ctx, $c0$); + … + } + … + } + … + HostAttributeDir.ngDirectiveDef = $r3$.ɵdefineDirective({ + type: HostAttributeDir, + selectors: [["", "hostAttributeDir", ""]], + factory: function HostAttributeDir_Factory(t) { return new (t || HostAttributeDir)(); }, + hostBindings: function HostAttributeDir_HostBindings(rf, ctx, elIndex) { + if (rf & 1) { + $r3$.ɵelementHostAttrs(ctx, $c1$); + … + } + … + } + `; + + const result = compile(files, angularFiles); + const source = result.source; + expectEmit(source, CompAndDirDeclaration, 'Invalid host attribute code'); + }); }); describe('non bindable behavior', () => { diff --git a/packages/compiler-cli/test/compliance/r3_view_compiler_styling_spec.ts b/packages/compiler-cli/test/compliance/r3_view_compiler_styling_spec.ts index 80168a3511..f8d06cf28f 100644 --- a/packages/compiler-cli/test/compliance/r3_view_compiler_styling_spec.ts +++ b/packages/compiler-cli/test/compliance/r3_view_compiler_styling_spec.ts @@ -1100,7 +1100,7 @@ describe('compiler compliance: styling', () => { }; const template = ` - const $_c0$ = [${AttributeMarker.Classes}, "foo", "baz", ${AttributeMarker.Styles}, "width", "200px", "height", "500px"]; + const $_c0$ = ["title", "foo title", ${AttributeMarker.Classes}, "foo", "baz", ${AttributeMarker.Styles}, "width", "200px", "height", "500px"]; … hostBindings: function MyComponent_HostBindings(rf, ctx, elIndex) { if (rf & 1) { @@ -1114,9 +1114,7 @@ describe('compiler compliance: styling', () => { $r3$.ɵelementStylingMap(elIndex, ctx.myClass, ctx.myStyle, ctx); $r3$.ɵelementStylingApply(elIndex, ctx); } - }, - consts: 0, - vars: 0, + } `; const result = compile(files, angularFiles); diff --git a/packages/compiler/src/render3/view/compiler.ts b/packages/compiler/src/render3/view/compiler.ts index 5a0880e2a1..f8045eaaef 100644 --- a/packages/compiler/src/render3/view/compiler.ts +++ b/packages/compiler/src/render3/view/compiler.ts @@ -29,7 +29,7 @@ import {Render3ParseResult} from '../r3_template_transform'; import {prepareSyntheticListenerFunctionName, prepareSyntheticPropertyName, typeWithParameters} from '../util'; import {R3ComponentDef, R3ComponentMetadata, R3DirectiveDef, R3DirectiveMetadata, R3QueryMetadata} from './api'; -import {StylingBuilder, StylingInstruction} from './styling_builder'; +import {Instruction, StylingBuilder} from './styling_builder'; import {BindingScope, TemplateDefinitionBuilder, ValueConverter, prepareEventListenerParameters, renderFlagCheckIfStmt, resolveSanitizationFn} from './template'; import {CONTEXT_NAME, DefinitionMap, RENDER_FLAGS, TEMPORARY_NAME, asLiteral, conditionallyCreateMapObjectLiteral, getQueryPredicate, temporaryAllocator} from './util'; @@ -101,14 +101,11 @@ function baseDirectiveFields( } } - // e.g. `attributes: ['role', 'listbox']` - definitionMap.set('attributes', createHostAttributesArray(allOtherAttributes)); - // e.g. `hostBindings: (rf, ctx, elIndex) => { ... } definitionMap.set( - 'hostBindings', - createHostBindingsFunction( - meta, elVarExp, contextVarExp, styleBuilder, bindingParser, constantPool, hostVarsCount)); + 'hostBindings', createHostBindingsFunction( + meta, elVarExp, contextVarExp, allOtherAttributes, styleBuilder, + bindingParser, constantPool, hostVarsCount)); // e.g 'inputs: {a: 'a'}` definitionMap.set('inputs', conditionallyCreateMapObjectLiteral(meta.inputs, true)); @@ -504,16 +501,13 @@ function createDirectiveSelector(selector: string | null): o.Expression { return asLiteral(core.parseSelectorToR3Selector(selector)); } -function createHostAttributesArray(attributes: any): o.Expression|null { +function convertAttributesToExpressions(attributes: any): o.Expression[] { const values: o.Expression[] = []; for (let key of Object.getOwnPropertyNames(attributes)) { const value = attributes[key]; values.push(o.literal(key), o.literal(value)); } - if (values.length > 0) { - return o.literalArr(values); - } - return null; + return values; } // Return a contentQueries function or null if one is not necessary. @@ -649,8 +643,8 @@ function createViewQueriesFunction( // Return a host binding function or null if one is not necessary. function createHostBindingsFunction( meta: R3DirectiveMetadata, elVarExp: o.ReadVarExpr, bindingContext: o.ReadVarExpr, - styleBuilder: StylingBuilder, bindingParser: BindingParser, constantPool: ConstantPool, - hostVarsCount: number): o.Expression|null { + staticAttributesAndValues: any[], styleBuilder: StylingBuilder, bindingParser: BindingParser, + constantPool: ConstantPool, hostVarsCount: number): o.Expression|null { const createStatements: o.Statement[] = []; const updateStatements: o.Statement[] = []; @@ -740,18 +734,20 @@ function createHostBindingsFunction( } } - if (styleBuilder.hasBindingsOrInitialValues()) { - // since we're dealing with directives here and directives have a hostBinding - // function, we need to generate special instructions that deal with styling - // (both bindings and initial values). The instruction below will instruct - // all initial styling (styling that is inside of a host binding within a - // directive) to be attached to the host element of the directive. - const hostAttrsInstruction = - styleBuilder.buildDirectiveHostAttrsInstruction(null, constantPool); - if (hostAttrsInstruction) { - createStatements.push(createStylingStmt(hostAttrsInstruction, bindingContext, bindingFn)); - } + // since we're dealing with directives/components and both have hostBinding + // functions, we need to generate a special hostAttrs instruction that deals + // with both the assignment of styling as well as static attributes to the host + // element. The instruction below will instruct all initial styling (styling + // that is inside of a host binding within a directive/component) to be attached + // to the host element alongside any of the provided host attributes that were + // collected earlier. + const hostAttrs = convertAttributesToExpressions(staticAttributesAndValues); + const hostInstruction = styleBuilder.buildHostAttrsInstruction(null, hostAttrs, constantPool); + if (hostInstruction) { + createStatements.push(createStylingStmt(hostInstruction, bindingContext, bindingFn)); + } + if (styleBuilder.hasBindingsOrInitialValues()) { // singular style/class bindings (things like `[style.prop]` and `[class.name]`) // MUST be registered on a given element within the component/directive // templateFn/hostBindingsFn functions. The instruction below will figure out @@ -799,7 +795,7 @@ function createHostBindingsFunction( } function createStylingStmt( - instruction: StylingInstruction, bindingContext: any, bindingFn: Function): o.Statement { + instruction: Instruction, bindingContext: any, bindingFn: Function): o.Statement { const params = instruction.buildParams(value => bindingFn(bindingContext, value).currValExpr); return o.importExpr(instruction.reference, null, instruction.sourceSpan) .callFn(params, instruction.sourceSpan) diff --git a/packages/compiler/src/render3/view/styling_builder.ts b/packages/compiler/src/render3/view/styling_builder.ts index aebc989304..9eab564f8e 100644 --- a/packages/compiler/src/render3/view/styling_builder.ts +++ b/packages/compiler/src/render3/view/styling_builder.ts @@ -20,7 +20,7 @@ import {ValueConverter} from './template'; /** * A styling expression summary that is to be processed by the compiler */ -export interface StylingInstruction { +export interface Instruction { sourceSpan: ParseSourceSpan|null; reference: o.ExternalReference; buildParams(convertFn: (value: any) => o.Expression): o.Expression[]; @@ -225,17 +225,17 @@ export class StylingBuilder { * Builds an instruction with all the expressions and parameters for `elementHostAttrs`. * * The instruction generation code below is used for producing the AOT statement code which is - * responsible for registering initial styles (within a directive hostBindings' creation block) - * to the directive host element. + * responsible for registering initial styles (within a directive hostBindings' creation block), + * as well as any of the provided attribute values, to the directive host element. */ - buildDirectiveHostAttrsInstruction(sourceSpan: ParseSourceSpan|null, constantPool: ConstantPool): - StylingInstruction|null { - if (this._hasInitialValues && this._directiveExpr) { + buildHostAttrsInstruction( + sourceSpan: ParseSourceSpan|null, attrs: o.Expression[], + constantPool: ConstantPool): Instruction|null { + if (this._directiveExpr && (attrs.length || this._hasInitialValues)) { return { sourceSpan, reference: R3.elementHostAttrs, buildParams: () => { - const attrs: o.Expression[] = []; this.populateInitialStylingAttrs(attrs); return [this._directiveExpr !, getConstantLiteralFromArray(constantPool, attrs)]; } @@ -251,7 +251,7 @@ export class StylingBuilder { * responsible for registering style/class bindings to an element. */ buildElementStylingInstruction(sourceSpan: ParseSourceSpan|null, constantPool: ConstantPool): - StylingInstruction|null { + Instruction|null { if (this._hasBindings) { return { sourceSpan, @@ -312,7 +312,7 @@ export class StylingBuilder { * which include the `[style]` and `[class]` expression params (if they exist) as well as * the sanitizer and directive reference expression. */ - buildElementStylingMapInstruction(valueConverter: ValueConverter): StylingInstruction|null { + buildElementStylingMapInstruction(valueConverter: ValueConverter): Instruction|null { if (this._classMapInput || this._styleMapInput) { const stylingInput = this._classMapInput ! || this._styleMapInput !; @@ -355,7 +355,7 @@ export class StylingBuilder { private _buildSingleInputs( reference: o.ExternalReference, inputs: BoundStylingEntry[], mapIndex: Map, - allowUnits: boolean, valueConverter: ValueConverter): StylingInstruction[] { + allowUnits: boolean, valueConverter: ValueConverter): Instruction[] { return inputs.map(input => { const bindingIndex: number = mapIndex.get(input.name) !; const value = input.value.visit(valueConverter); @@ -381,7 +381,7 @@ export class StylingBuilder { }); } - private _buildClassInputs(valueConverter: ValueConverter): StylingInstruction[] { + private _buildClassInputs(valueConverter: ValueConverter): Instruction[] { if (this._singleClassInputs) { return this._buildSingleInputs( R3.elementClassProp, this._singleClassInputs, this._classesIndex, false, valueConverter); @@ -389,7 +389,7 @@ export class StylingBuilder { return []; } - private _buildStyleInputs(valueConverter: ValueConverter): StylingInstruction[] { + private _buildStyleInputs(valueConverter: ValueConverter): Instruction[] { if (this._singleStyleInputs) { return this._buildSingleInputs( R3.elementStyleProp, this._singleStyleInputs, this._stylesIndex, true, valueConverter); @@ -397,7 +397,7 @@ export class StylingBuilder { return []; } - private _buildApplyFn(): StylingInstruction { + private _buildApplyFn(): Instruction { return { sourceSpan: this._lastStylingInput ? this._lastStylingInput.sourceSpan : null, reference: R3.elementStylingApply, @@ -416,7 +416,7 @@ export class StylingBuilder { * into the update block of a template function or a directive hostBindings function. */ buildUpdateLevelInstructions(valueConverter: ValueConverter) { - const instructions: StylingInstruction[] = []; + const instructions: Instruction[] = []; if (this._hasBindings) { const mapInstruction = this.buildElementStylingMapInstruction(valueConverter); if (mapInstruction) { diff --git a/packages/compiler/src/render3/view/template.ts b/packages/compiler/src/render3/view/template.ts index ab1d3e94e0..7058630ada 100644 --- a/packages/compiler/src/render3/view/template.ts +++ b/packages/compiler/src/render3/view/template.ts @@ -36,7 +36,7 @@ import {I18nContext} from './i18n/context'; import {I18nMetaVisitor} from './i18n/meta'; import {getSerializedI18nContent} from './i18n/serializer'; import {I18N_ICU_MAPPING_PREFIX, assembleBoundTextPlaceholders, assembleI18nBoundString, formatI18nPlaceholderName, getTranslationConstPrefix, getTranslationDeclStmts, icuFromI18nMessage, isI18nRootNode, isSingleI18nIcu, metaFromI18nMessage, placeholdersToParams, wrapI18nPlaceholder} from './i18n/util'; -import {StylingBuilder, StylingInstruction} from './styling_builder'; +import {Instruction, StylingBuilder} from './styling_builder'; import {CONTEXT_NAME, IMPLICIT_REFERENCE, NON_BINDABLE_ATTR, REFERENCE_PREFIX, RENDER_FLAGS, asLiteral, getAttrsForDirectiveMatching, invalid, trimTrailingNulls, unsupported} from './util'; // Default selector used by `` if none specified @@ -689,7 +689,7 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver if (input.type === BindingType.Animation) { const value = input.value.visit(this._valueConverter); // animation bindings can be presented in the following formats: - // 1j [@binding]="fooExp" + // 1. [@binding]="fooExp" // 2. [@binding]="{value:fooExp, params:{...}}" // 3. [@binding] // 4. @binding @@ -936,7 +936,7 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver } private processStylingInstruction( - implicit: any, instruction: StylingInstruction|null, createMode: boolean) { + implicit: any, instruction: Instruction|null, createMode: boolean) { if (instruction) { const paramsFn = () => instruction.buildParams(value => this.convertPropertyBinding(implicit, value, true)); diff --git a/packages/core/src/render3/definition.ts b/packages/core/src/render3/definition.ts index 8b0d755a5d..11fef9fa21 100644 --- a/packages/core/src/render3/definition.ts +++ b/packages/core/src/render3/definition.ts @@ -68,14 +68,6 @@ export function defineComponent(componentDefinition: { */ vars: number; - /** - * Static attributes to set on host element. - * - * Even indices: attribute name - * Odd indices: attribute value - */ - attributes?: string[]; - /** * A map of input names. * @@ -260,7 +252,6 @@ export function defineComponent(componentDefinition: { hostBindings: componentDefinition.hostBindings || null, contentQueries: componentDefinition.contentQueries || null, contentQueriesRefresh: componentDefinition.contentQueriesRefresh || null, - attributes: componentDefinition.attributes || null, declaredInputs: declaredInputs, inputs: null !, // assigned in noSideEffects outputs: null !, // assigned in noSideEffects @@ -516,14 +507,6 @@ export const defineDirective = defineComponent as any as(directiveDefinition: */ factory: (t: Type| null) => T; - /** - * Static attributes to set on host element. - * - * Even indices: attribute name - * Odd indices: attribute value - */ - attributes?: string[]; - /** * A map of input names. * diff --git a/packages/core/src/render3/instructions.ts b/packages/core/src/render3/instructions.ts index fb895bfd3a..6e7363345f 100644 --- a/packages/core/src/render3/instructions.ts +++ b/packages/core/src/render3/instructions.ts @@ -752,31 +752,62 @@ function createViewBlueprint(bindingStartIndex: number, initialViewLength: numbe return blueprint; } -function setUpAttributes(native: RElement, attrs: TAttributes): void { +/** + * Assigns all attribute values to the provided element via the inferred renderer. + * + * This function accepts two forms of attribute entries: + * + * default: (key, value): + * attrs = [key1, value1, key2, value2] + * + * namespaced: (NAMESPACE_MARKER, uri, name, value) + * attrs = [NAMESPACE_MARKER, uri, name, value, NAMESPACE_MARKER, uri, name, value] + * + * The `attrs` array can contain a mix of both the default and namespaced entries. + * The "default" values are set without a marker, but if the function comes across + * a marker value then it will attempt to set a namespaced value. If the marker is + * not of a namespaced value then the function will quit and return the index value + * where it stopped during the iteration of the attrs array. + * + * See [AttributeMarker] to understand what the namespace marker value is. + * + * Note that this instruction does not support assigning style and class values to + * an element. See `elementStart` and `elementHostAttrs` to learn how styling values + * are applied to an element. + * + * @param native The element that the attributes will be assigned to + * @param attrs The attribute array of values that will be assigned to the element + * @returns the index value that was last accessed in the attributes array + */ +function setUpAttributes(native: RElement, attrs: TAttributes): number { const renderer = getLView()[RENDERER]; const isProc = isProceduralRenderer(renderer); - let i = 0; + let i = 0; while (i < attrs.length) { - const attrName = attrs[i++]; - if (typeof attrName == 'number') { - if (attrName === AttributeMarker.NamespaceURI) { - // Namespaced attributes - const namespaceURI = attrs[i++] as string; - const attrName = attrs[i++] as string; - const attrVal = attrs[i++] as string; - ngDevMode && ngDevMode.rendererSetAttribute++; - isProc ? - (renderer as ProceduralRenderer3) - .setAttribute(native, attrName, attrVal, namespaceURI) : - native.setAttributeNS(namespaceURI, attrName, attrVal); - } else { - // All other `AttributeMarker`s are ignored here. + const value = attrs[i]; + if (typeof value === 'number') { + // only namespaces are supported. Other value types (such as style/class + // entries) are not supported in this function. + if (value !== AttributeMarker.NamespaceURI) { break; } + + // we just landed on the marker value ... therefore + // we should skip to the next entry + i++; + + const namespaceURI = attrs[i++] as string; + const attrName = attrs[i++] as string; + const attrVal = attrs[i++] as string; + ngDevMode && ngDevMode.rendererSetAttribute++; + isProc ? + (renderer as ProceduralRenderer3).setAttribute(native, attrName, attrVal, namespaceURI) : + native.setAttributeNS(namespaceURI, attrName, attrVal); } else { /// attrName is string; - const attrVal = attrs[i++]; + const attrName = value as string; + const attrVal = attrs[++i]; if (attrName !== NG_PROJECT_AS_ATTR_NAME) { // Standard attributes ngDevMode && ngDevMode.rendererSetAttribute++; @@ -791,8 +822,15 @@ function setUpAttributes(native: RElement, attrs: TAttributes): void { native.setAttribute(attrName as string, attrVal as string); } } + i++; } } + + // another piece of code may iterate over the same attributes array. Therefore + // it may be helpful to return the exact spot where the attributes array exited + // whether by running into an unsupported marker or if all the static values were + // iterated over. + return i; } export function createError(text: string, token: any) { @@ -1257,19 +1295,41 @@ export function elementStyling( } /** - * Assign static styling values to a host element. + * Assign static attribute values to a host element. + * + * This instruction will assign static attribute values as well as class and style + * values to an element within the host bindings function. Since attribute values + * can consist of different types of values, the `attrs` array must include the values in + * the following format: + * + * attrs = [ + * // static attributes (like `title`, `name`, `id`...) + * attr1, value1, attr2, value, + * + * // a single namespace value (like `x:id`) + * NAMESPACE_MARKER, namespaceUri1, name1, value1, + * + * // another single namespace value (like `x:name`) + * NAMESPACE_MARKER, namespaceUri2, name2, value2, + * + * // a series of CSS classes that will be applied to the element (no spaces) + * CLASSES_MARKER, class1, class2, class3, + * + * // a series of CSS styles (property + value) that will be applied to the element + * STYLES_MARKER, prop1, value1, prop2, value2 + * ] + * + * All non-class and non-style attributes must be defined at the start of the list + * first before all class and style values are set. When there is a change in value + * type (like when classes and styles are introduced) a marker must be used to separate + * the entries. The marker values themselves are set via entries found in the + * [AttributeMarker] enum. * * NOTE: This instruction is meant to used from `hostBindings` function only. * * @param directive A directive instance the styling is associated with. - * @param attrs An array containing class and styling information. The values must be marked with - * `AttributeMarker`. - * - * ``` - * var attrs = [AttributeMarker.Classes, 'foo', 'bar', - * AttributeMarker.Styles, 'width', '100px', 'height, '200px'] - * elementHostAttrs(directive, attrs); - * ``` + * @param attrs An array of static values (attributes, classes and styles) with the correct marker + * values. * * @publicApi */ @@ -1278,7 +1338,10 @@ export function elementHostAttrs(directive: any, attrs: TAttributes) { if (!tNode.stylingTemplate) { tNode.stylingTemplate = initializeStaticStylingContext(attrs); } - patchContextWithStaticAttrs(tNode.stylingTemplate, attrs, directive); + const lView = getLView(); + const native = getNativeByTNode(tNode, lView) as RElement; + const i = setUpAttributes(native, attrs); + patchContextWithStaticAttrs(tNode.stylingTemplate, attrs, i, directive); } /** @@ -1658,11 +1721,6 @@ function postProcessBaseDirective( if (native) { attachPatchData(native, lView); } - - // TODO(misko): setUpAttributes should be a feature for better treeshakability. - if (def.attributes != null && previousOrParentTNode.type == TNodeType.Element) { - setUpAttributes(native as RElement, def.attributes as string[]); - } } diff --git a/packages/core/src/render3/interfaces/definition.ts b/packages/core/src/render3/interfaces/definition.ts index 3a7995a77b..e3ecf2c0fd 100644 --- a/packages/core/src/render3/interfaces/definition.ts +++ b/packages/core/src/render3/interfaces/definition.ts @@ -140,14 +140,6 @@ export interface DirectiveDef extends BaseDef { /** Refreshes host bindings on the associated directive. */ hostBindings: HostBindingsFunction|null; - /** - * Static attributes to set on host element. - * - * Even indices: attribute name - * Odd indices: attribute value - */ - readonly attributes: string[]|null; - /* The following are lifecycle hooks for this component */ onInit: (() => void)|null; doCheck: (() => void)|null; diff --git a/packages/core/src/render3/styling/class_and_style_bindings.ts b/packages/core/src/render3/styling/class_and_style_bindings.ts index 7f4fb1465a..0bcee71d94 100644 --- a/packages/core/src/render3/styling/class_and_style_bindings.ts +++ b/packages/core/src/render3/styling/class_and_style_bindings.ts @@ -75,7 +75,7 @@ export function initializeStaticContext(attrs: TAttributes) { * @param directive the directive instance with which static data is associated with. */ export function patchContextWithStaticAttrs( - context: StylingContext, attrs: TAttributes, directive: any): void { + context: StylingContext, attrs: TAttributes, startingIndex: number, directive: any): void { // If the styling context has already been patched with the given directive's bindings, // then there is no point in doing it again. The reason why this may happen (the directive // styling being patched twice) is because the `stylingBinding` function is called each time @@ -89,7 +89,7 @@ export function patchContextWithStaticAttrs( let initialStyles: InitialStylingValues|null = null; let mode = -1; - for (let i = 0; i < attrs.length; i++) { + for (let i = startingIndex; i < attrs.length; i++) { const attr = attrs[i]; if (typeof attr == 'number') { mode = attr; diff --git a/packages/core/test/bundling/hello_world/bundle.golden_symbols.json b/packages/core/test/bundling/hello_world/bundle.golden_symbols.json index 47d6132932..28212a2e4d 100644 --- a/packages/core/test/bundling/hello_world/bundle.golden_symbols.json +++ b/packages/core/test/bundling/hello_world/bundle.golden_symbols.json @@ -2,9 +2,6 @@ { "name": "ACTIVE_INDEX" }, - { - "name": "ANIMATION_PROP_PREFIX" - }, { "name": "BINDING_INDEX" }, @@ -77,9 +74,6 @@ { "name": "NG_PIPE_DEF" }, - { - "name": "NG_PROJECT_AS_ATTR_NAME" - }, { "name": "NO_CHANGE" }, @@ -320,9 +314,6 @@ { "name": "invertObject" }, - { - "name": "isAnimationProp" - }, { "name": "isComponentDef" }, @@ -434,9 +425,6 @@ { "name": "setTNodeAndViewData" }, - { - "name": "setUpAttributes" - }, { "name": "syncViewWithBlueprint" }, diff --git a/packages/core/test/render3/host_binding_spec.ts b/packages/core/test/render3/host_binding_spec.ts index f2c7f8fa3a..821d43dcc7 100644 --- a/packages/core/test/render3/host_binding_spec.ts +++ b/packages/core/test/render3/host_binding_spec.ts @@ -965,7 +965,11 @@ describe('host bindings', () => { selectors: [['', 'hostAttributeDir', '']], type: HostAttributeDir, factory: () => new HostAttributeDir(), - attributes: ['role', 'listbox'] + hostBindings: function(rf, ctx, elIndex) { + if (rf & RenderFlags.Create) { + elementHostAttrs(ctx, ['role', 'listbox']); + } + } }); } diff --git a/packages/core/test/render3/integration_spec.ts b/packages/core/test/render3/integration_spec.ts index 4acfc3ce65..df30a7b3ee 100644 --- a/packages/core/test/render3/integration_spec.ts +++ b/packages/core/test/render3/integration_spec.ts @@ -478,8 +478,12 @@ describe('render3 integration test', () => { factory: () => new HostAttributeComp(), consts: 0, vars: 0, + hostBindings: function(rf, ctx, elIndex) { + if (rf & RenderFlags.Create) { + elementHostAttrs(ctx, ['role', 'button']); + } + }, template: (rf: RenderFlags, ctx: HostAttributeComp) => {}, - attributes: ['role', 'button'] }); } @@ -1706,8 +1710,8 @@ describe('render3 integration test', () => { rf: RenderFlags, ctx: DirWithInitialStyling, elementIndex: number) { if (rf & RenderFlags.Create) { elementHostAttrs(ctx, [ - AttributeMarker.Classes, 'heavy', 'golden', AttributeMarker.Styles, 'color', - 'purple', 'font-weight', 'bold' + 'title', 'foo', AttributeMarker.Classes, 'heavy', 'golden', + AttributeMarker.Styles, 'color', 'purple', 'font-weight', 'bold' ]); } } @@ -1735,6 +1739,7 @@ describe('render3 integration test', () => { const classes = target.getAttribute('class') !.split(/\s+/).sort(); expect(classes).toEqual(['big', 'golden', 'heavy']); + expect(target.getAttribute('title')).toEqual('foo'); expect(target.style.getPropertyValue('color')).toEqual('black'); expect(target.style.getPropertyValue('font-size')).toEqual('200px'); expect(target.style.getPropertyValue('font-weight')).toEqual('bold'); diff --git a/packages/core/test/render3/styling/class_and_style_bindings_spec.ts b/packages/core/test/render3/styling/class_and_style_bindings_spec.ts index de3a76ee7c..05f4fc0a85 100644 --- a/packages/core/test/render3/styling/class_and_style_bindings_spec.ts +++ b/packages/core/test/render3/styling/class_and_style_bindings_spec.ts @@ -70,7 +70,7 @@ describe('style and class based bindings', () => { attrs.push(AttributeMarker.Styles); attrs.push(...styles); } - patchContextWithStaticAttrs(context, attrs, directiveRef || null); + patchContextWithStaticAttrs(context, attrs, 0, directiveRef || null); } function getRootContextInternal(lView: LView) { return lView[CONTEXT] as RootContext; } @@ -1388,7 +1388,7 @@ describe('style and class based bindings', () => { let styles: any = {fontSize: ''}; updateStyleProp(stylingContext, 0, ''); updateStylingMap(stylingContext, null, styles); - patchContextWithStaticAttrs(stylingContext, [], otherDirective); + patchContextWithStaticAttrs(stylingContext, [], 0, otherDirective); getStyles(stylingContext, otherDirective); expect(store.getValues()).toEqual({}); diff --git a/packages/core/test/render3/view_container_ref_spec.ts b/packages/core/test/render3/view_container_ref_spec.ts index 00df5eea08..f1c54e17a0 100644 --- a/packages/core/test/render3/view_container_ref_spec.ts +++ b/packages/core/test/render3/view_container_ref_spec.ts @@ -10,7 +10,7 @@ import {ChangeDetectorRef, Component as _Component, ComponentFactoryResolver, El import {ViewEncapsulation} from '../../src/metadata'; import {AttributeMarker, defineComponent, defineDirective, definePipe, injectComponentFactoryResolver, load, query, queryRefresh} from '../../src/render3/index'; -import {allocHostVars, bind, container, containerRefreshEnd, containerRefreshStart, directiveInject, element, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, interpolation1, interpolation3, nextContext, projection, projectionDef, reference, template, text, textBinding} from '../../src/render3/instructions'; +import {allocHostVars, bind, container, containerRefreshEnd, containerRefreshStart, directiveInject, element, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, interpolation1, interpolation3, nextContext, projection, projectionDef, reference, template, text, textBinding, elementHostAttrs} from '../../src/render3/instructions'; import {RenderFlags} from '../../src/render3/interfaces/definition'; import {RElement} from '../../src/render3/interfaces/renderer'; import {templateRefExtractor} from '../../src/render3/view_engine_compatibility_prebound'; @@ -1855,9 +1855,9 @@ describe('ViewContainerRef', () => { consts: 0, vars: 0, template: (rf: RenderFlags, cmp: HostBindingCmpt) => {}, - attributes: ['id', 'attribute'], hostBindings: function(rf: RenderFlags, ctx: HostBindingCmpt, elIndex: number) { if (rf & RenderFlags.Create) { + elementHostAttrs(ctx, ['id', 'attribute']); allocHostVars(1); } if (rf & RenderFlags.Update) {