Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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$);
}
}
});
`;

Expand All @@ -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', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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);
Expand Down
48 changes: 22 additions & 26 deletions packages/compiler/src/render3/view/compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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[] = [];

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
28 changes: 14 additions & 14 deletions packages/compiler/src/render3/view/styling_builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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[];
Expand Down Expand Up @@ -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)];
}
Expand All @@ -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,
Expand Down Expand Up @@ -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 !;

Expand Down Expand Up @@ -355,7 +355,7 @@ export class StylingBuilder {

private _buildSingleInputs(
reference: o.ExternalReference, inputs: BoundStylingEntry[], mapIndex: Map<string, number>,
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);
Expand All @@ -381,23 +381,23 @@ 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);
}
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);
}
return [];
}

private _buildApplyFn(): StylingInstruction {
private _buildApplyFn(): Instruction {
return {
sourceSpan: this._lastStylingInput ? this._lastStylingInput.sourceSpan : null,
reference: R3.elementStylingApply,
Expand All @@ -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) {
Expand Down
6 changes: 3 additions & 3 deletions packages/compiler/src/render3/view/template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<ng-content>` if none specified
Expand Down Expand Up @@ -689,7 +689,7 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, 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
Expand Down Expand Up @@ -936,7 +936,7 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, 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));
Expand Down
17 changes: 0 additions & 17 deletions packages/core/src/render3/definition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,14 +68,6 @@ export function defineComponent<T>(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.
*
Expand Down Expand Up @@ -260,7 +252,6 @@ export function defineComponent<T>(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
Expand Down Expand Up @@ -516,14 +507,6 @@ export const defineDirective = defineComponent as any as<T>(directiveDefinition:
*/
factory: (t: Type<T>| null) => T;

/**
* Static attributes to set on host element.
*
* Even indices: attribute name
* Odd indices: attribute value
*/
attributes?: string[];

/**
* A map of input names.
*
Expand Down
Loading