feat(ivy): support host attribute and property bindings (#22334)
PR Close #22334
This commit is contained in:
parent
c499c8f4db
commit
73c203fda9
@ -15,7 +15,7 @@ import {Identifiers, createTokenForExternalReference} from '../identifiers';
|
|||||||
import {InjectableCompiler} from '../injectable_compiler';
|
import {InjectableCompiler} from '../injectable_compiler';
|
||||||
import {CompileMetadataResolver} from '../metadata_resolver';
|
import {CompileMetadataResolver} from '../metadata_resolver';
|
||||||
import {HtmlParser} from '../ml_parser/html_parser';
|
import {HtmlParser} from '../ml_parser/html_parser';
|
||||||
import {InterpolationConfig} from '../ml_parser/interpolation_config';
|
import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from '../ml_parser/interpolation_config';
|
||||||
import {NgModuleCompiler} from '../ng_module_compiler';
|
import {NgModuleCompiler} from '../ng_module_compiler';
|
||||||
import {OutputEmitter} from '../output/abstract_emitter';
|
import {OutputEmitter} from '../output/abstract_emitter';
|
||||||
import * as o from '../output/output_ast';
|
import * as o from '../output/output_ast';
|
||||||
@ -24,6 +24,7 @@ import {compilePipe as compileIvyPipe} from '../render3/r3_pipe_compiler';
|
|||||||
import {compileComponent as compileIvyComponent, compileDirective as compileIvyDirective} from '../render3/r3_view_compiler';
|
import {compileComponent as compileIvyComponent, compileDirective as compileIvyDirective} from '../render3/r3_view_compiler';
|
||||||
import {CompiledStylesheet, StyleCompiler} from '../style_compiler';
|
import {CompiledStylesheet, StyleCompiler} from '../style_compiler';
|
||||||
import {SummaryResolver} from '../summary_resolver';
|
import {SummaryResolver} from '../summary_resolver';
|
||||||
|
import {BindingParser} from '../template_parser/binding_parser';
|
||||||
import {TemplateAst} from '../template_parser/template_ast';
|
import {TemplateAst} from '../template_parser/template_ast';
|
||||||
import {TemplateParser} from '../template_parser/template_parser';
|
import {TemplateParser} from '../template_parser/template_parser';
|
||||||
import {OutputContext, ValueVisitor, error, syntaxError, visitValue} from '../util';
|
import {OutputContext, ValueVisitor, error, syntaxError, visitValue} from '../util';
|
||||||
@ -345,8 +346,12 @@ export class AotCompiler {
|
|||||||
directives: StaticSymbol[], pipes: StaticSymbol[], ngModules: CompileNgModuleMetadata[],
|
directives: StaticSymbol[], pipes: StaticSymbol[], ngModules: CompileNgModuleMetadata[],
|
||||||
injectables: CompileInjectableMetadata[]): PartialModule[] {
|
injectables: CompileInjectableMetadata[]): PartialModule[] {
|
||||||
const classes: o.ClassStmt[] = [];
|
const classes: o.ClassStmt[] = [];
|
||||||
|
const errors: ParseError[] = [];
|
||||||
|
|
||||||
const context = this._createOutputContext(fileName);
|
const context = this._createOutputContext(fileName);
|
||||||
|
const hostBindingParser = new BindingParser(
|
||||||
|
this._templateParser.expressionParser, DEFAULT_INTERPOLATION_CONFIG, null !, [], errors);
|
||||||
|
|
||||||
|
|
||||||
// Process all components and directives
|
// Process all components and directives
|
||||||
directives.forEach(directiveType => {
|
directives.forEach(directiveType => {
|
||||||
@ -360,9 +365,10 @@ export class AotCompiler {
|
|||||||
const {template: parsedTemplate, pipes: parsedPipes} =
|
const {template: parsedTemplate, pipes: parsedPipes} =
|
||||||
this._parseTemplate(directiveMetadata, module, module.transitiveModule.directives);
|
this._parseTemplate(directiveMetadata, module, module.transitiveModule.directives);
|
||||||
compileIvyComponent(
|
compileIvyComponent(
|
||||||
context, directiveMetadata, parsedPipes, parsedTemplate, this._reflector);
|
context, directiveMetadata, parsedPipes, parsedTemplate, this._reflector,
|
||||||
|
hostBindingParser);
|
||||||
} else {
|
} else {
|
||||||
compileIvyDirective(context, directiveMetadata, this._reflector);
|
compileIvyDirective(context, directiveMetadata, this._reflector, hostBindingParser);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@ import {splitAtColon, stringify} from './util';
|
|||||||
// group 3: "@trigger" from "@trigger"
|
// group 3: "@trigger" from "@trigger"
|
||||||
const HOST_REG_EXP = /^(?:(?:\[([^\]]+)\])|(?:\(([^\)]+)\)))|(\@[-\w]+)$/;
|
const HOST_REG_EXP = /^(?:(?:\[([^\]]+)\])|(?:\(([^\)]+)\)))|(\@[-\w]+)$/;
|
||||||
|
|
||||||
function _sanitizeIdentifier(name: string): string {
|
export function sanitizeIdentifier(name: string): string {
|
||||||
return name.replace(/\W/g, '_');
|
return name.replace(/\W/g, '_');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -42,7 +42,7 @@ export function identifierName(compileIdentifier: CompileIdentifierMetadata | nu
|
|||||||
identifier = `anonymous_${_anonymousTypeIndex++}`;
|
identifier = `anonymous_${_anonymousTypeIndex++}`;
|
||||||
ref['__anonymousType'] = identifier;
|
ref['__anonymousType'] = identifier;
|
||||||
} else {
|
} else {
|
||||||
identifier = _sanitizeIdentifier(identifier);
|
identifier = sanitizeIdentifier(identifier);
|
||||||
}
|
}
|
||||||
return identifier;
|
return identifier;
|
||||||
}
|
}
|
||||||
@ -120,7 +120,7 @@ export interface CompileFactoryMetadata extends CompileIdentifierMetadata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function tokenName(token: CompileTokenMetadata) {
|
export function tokenName(token: CompileTokenMetadata) {
|
||||||
return token.value != null ? _sanitizeIdentifier(token.value) : identifierName(token.identifier);
|
return token.value != null ? sanitizeIdentifier(token.value) : identifierName(token.identifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function tokenReference(token: CompileTokenMetadata) {
|
export function tokenReference(token: CompileTokenMetadata) {
|
||||||
|
@ -114,4 +114,6 @@ export class Identifiers {
|
|||||||
static queryRefresh: o.ExternalReference = {name: 'ɵqR', moduleName: CORE};
|
static queryRefresh: o.ExternalReference = {name: 'ɵqR', moduleName: CORE};
|
||||||
|
|
||||||
static NgOnChangesFeature: o.ExternalReference = {name: 'ɵNgOnChangesFeature', moduleName: CORE};
|
static NgOnChangesFeature: o.ExternalReference = {name: 'ɵNgOnChangesFeature', moduleName: CORE};
|
||||||
|
|
||||||
|
static listener: o.ExternalReference = {name: 'ɵL', moduleName: CORE};
|
||||||
}
|
}
|
@ -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 {CompileDirectiveMetadata, CompilePipeSummary, CompileQueryMetadata, CompileTokenMetadata, CompileTypeMetadata, flatten, identifierName, rendererTypeName, tokenReference, viewClassName} from '../compile_metadata';
|
import {CompileDirectiveMetadata, CompileDirectiveSummary, CompilePipeSummary, CompileQueryMetadata, CompileTokenMetadata, CompileTypeMetadata, flatten, identifierName, rendererTypeName, sanitizeIdentifier, tokenReference, viewClassName} from '../compile_metadata';
|
||||||
import {CompileReflector} from '../compile_reflector';
|
import {CompileReflector} from '../compile_reflector';
|
||||||
import {BindingForm, BuiltinConverter, BuiltinFunctionCall, ConvertPropertyBindingResult, EventHandlerVars, LocalResolver, convertActionBinding, convertPropertyBinding, convertPropertyBindingBuiltins} from '../compiler_util/expression_converter';
|
import {BindingForm, BuiltinConverter, BuiltinFunctionCall, ConvertPropertyBindingResult, EventHandlerVars, LocalResolver, convertActionBinding, convertPropertyBinding, convertPropertyBindingBuiltins} from '../compiler_util/expression_converter';
|
||||||
import {ConstantPool, DefinitionKind} from '../constant_pool';
|
import {ConstantPool, DefinitionKind} from '../constant_pool';
|
||||||
@ -14,8 +14,9 @@ import {AST, AstMemoryEfficientTransformer, AstTransformer, BindingPipe, Functio
|
|||||||
import {Identifiers} from '../identifiers';
|
import {Identifiers} from '../identifiers';
|
||||||
import {LifecycleHooks} from '../lifecycle_reflector';
|
import {LifecycleHooks} from '../lifecycle_reflector';
|
||||||
import * as o from '../output/output_ast';
|
import * as o from '../output/output_ast';
|
||||||
import {ParseSourceSpan} from '../parse_util';
|
import {ParseSourceSpan, typeSourceSpan} from '../parse_util';
|
||||||
import {CssSelector} from '../selector';
|
import {CssSelector} from '../selector';
|
||||||
|
import {BindingParser} from '../template_parser/binding_parser';
|
||||||
import {AttrAst, BoundDirectivePropertyAst, BoundElementPropertyAst, BoundEventAst, BoundTextAst, DirectiveAst, ElementAst, EmbeddedTemplateAst, NgContentAst, PropertyBindingType, ProviderAst, QueryMatch, RecursiveTemplateAstVisitor, ReferenceAst, TemplateAst, TemplateAstVisitor, TextAst, VariableAst, templateVisitAll} from '../template_parser/template_ast';
|
import {AttrAst, BoundDirectivePropertyAst, BoundElementPropertyAst, BoundEventAst, BoundTextAst, DirectiveAst, ElementAst, EmbeddedTemplateAst, NgContentAst, PropertyBindingType, ProviderAst, QueryMatch, RecursiveTemplateAstVisitor, ReferenceAst, TemplateAst, TemplateAstVisitor, TextAst, VariableAst, templateVisitAll} from '../template_parser/template_ast';
|
||||||
import {OutputContext, error} from '../util';
|
import {OutputContext, error} from '../util';
|
||||||
|
|
||||||
@ -39,22 +40,30 @@ const REFERENCE_PREFIX = '_r';
|
|||||||
const IMPLICIT_REFERENCE = '$implicit';
|
const IMPLICIT_REFERENCE = '$implicit';
|
||||||
|
|
||||||
export function compileDirective(
|
export function compileDirective(
|
||||||
outputCtx: OutputContext, directive: CompileDirectiveMetadata, reflector: CompileReflector) {
|
outputCtx: OutputContext, directive: CompileDirectiveMetadata, reflector: CompileReflector,
|
||||||
|
bindingParser: BindingParser) {
|
||||||
const definitionMapValues: {key: string, quoted: boolean, value: o.Expression}[] = [];
|
const definitionMapValues: {key: string, quoted: boolean, value: o.Expression}[] = [];
|
||||||
|
|
||||||
|
const field = (key: string, value: o.Expression | null) => {
|
||||||
|
if (value) {
|
||||||
|
definitionMapValues.push({key, value, quoted: false});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// e.g. 'type: MyDirective`
|
// e.g. 'type: MyDirective`
|
||||||
definitionMapValues.push(
|
field('type', outputCtx.importExpr(directive.type.reference));
|
||||||
{key: 'type', value: outputCtx.importExpr(directive.type.reference), quoted: false});
|
|
||||||
|
|
||||||
// e.g. `factory: () => new MyApp(injectElementRef())`
|
// e.g. `factory: () => new MyApp(injectElementRef())`
|
||||||
const templateFactory = createFactory(directive.type, outputCtx, reflector, directive.queries);
|
field('factory', createFactory(directive.type, outputCtx, reflector, directive.queries));
|
||||||
definitionMapValues.push({key: 'factory', value: templateFactory, quoted: false});
|
|
||||||
|
// e.g. `hostBindings: (dirIndex, elIndex) => { ... }
|
||||||
|
field('hostBindings', createHostBindingsFunction(directive, outputCtx, bindingParser));
|
||||||
|
|
||||||
|
// e.g. `attributes: ['role', 'listbox']`
|
||||||
|
field('attributes', createHostAttributesArray(directive, outputCtx));
|
||||||
|
|
||||||
// e.g 'inputs: {a: 'a'}`
|
// e.g 'inputs: {a: 'a'}`
|
||||||
if (Object.getOwnPropertyNames(directive.inputs).length > 0) {
|
field('inputs', createInputsObject(directive, outputCtx));
|
||||||
definitionMapValues.push(
|
|
||||||
{key: 'inputs', quoted: false, value: mapToExpression(directive.inputs)});
|
|
||||||
}
|
|
||||||
|
|
||||||
const className = identifierName(directive.type) !;
|
const className = identifierName(directive.type) !;
|
||||||
className || error(`Cannot resolver the name of ${directive.type}`);
|
className || error(`Cannot resolver the name of ${directive.type}`);
|
||||||
@ -76,19 +85,24 @@ export function compileDirective(
|
|||||||
|
|
||||||
export function compileComponent(
|
export function compileComponent(
|
||||||
outputCtx: OutputContext, component: CompileDirectiveMetadata, pipes: CompilePipeSummary[],
|
outputCtx: OutputContext, component: CompileDirectiveMetadata, pipes: CompilePipeSummary[],
|
||||||
template: TemplateAst[], reflector: CompileReflector) {
|
template: TemplateAst[], reflector: CompileReflector, bindingParser: BindingParser) {
|
||||||
const definitionMapValues: {key: string, quoted: boolean, value: o.Expression}[] = [];
|
const definitionMapValues: {key: string, quoted: boolean, value: o.Expression}[] = [];
|
||||||
|
|
||||||
|
const field = (key: string, value: o.Expression | null) => {
|
||||||
|
if (value) {
|
||||||
|
definitionMapValues.push({key, value, quoted: false});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// e.g. `type: MyApp`
|
// e.g. `type: MyApp`
|
||||||
definitionMapValues.push(
|
field('type', outputCtx.importExpr(component.type.reference));
|
||||||
{key: 'type', value: outputCtx.importExpr(component.type.reference), quoted: false});
|
|
||||||
|
|
||||||
// e.g. `tag: 'my-app'`
|
// e.g. `tag: 'my-app'`
|
||||||
// This is optional and only included if the first selector of a component has element.
|
// This is optional and only included if the first selector of a component has element.
|
||||||
const selector = component.selector && CssSelector.parse(component.selector);
|
const selector = component.selector && CssSelector.parse(component.selector);
|
||||||
const firstSelector = selector && selector[0];
|
const firstSelector = selector && selector[0];
|
||||||
if (firstSelector && firstSelector.hasElementSelector()) {
|
if (firstSelector && firstSelector.hasElementSelector()) {
|
||||||
definitionMapValues.push({key: 'tag', value: o.literal(firstSelector.element), quoted: false});
|
field('tag', o.literal(firstSelector.element));
|
||||||
}
|
}
|
||||||
|
|
||||||
// e.g. `attr: ["class", ".my.app"]
|
// e.g. `attr: ["class", ".my.app"]
|
||||||
@ -96,26 +110,19 @@ export function compileComponent(
|
|||||||
if (firstSelector) {
|
if (firstSelector) {
|
||||||
const selectorAttributes = firstSelector.getAttrs();
|
const selectorAttributes = firstSelector.getAttrs();
|
||||||
if (selectorAttributes.length) {
|
if (selectorAttributes.length) {
|
||||||
definitionMapValues.push({
|
field(
|
||||||
key: 'attrs',
|
'attrs', outputCtx.constantPool.getConstLiteral(
|
||||||
value: outputCtx.constantPool.getConstLiteral(
|
o.literalArr(selectorAttributes.map(
|
||||||
o.literalArr(selectorAttributes.map(
|
value => value != null ? o.literal(value) : o.literal(undefined))),
|
||||||
value => value != null ? o.literal(value) : o.literal(undefined))),
|
/* forceShared */ true));
|
||||||
/* forceShared */ true),
|
|
||||||
quoted: false
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// e.g. `factory: function MyApp_Factory() { return new MyApp(injectElementRef()); }`
|
// e.g. `factory: function MyApp_Factory() { return new MyApp(injectElementRef()); }`
|
||||||
const templateFactory = createFactory(component.type, outputCtx, reflector, component.queries);
|
field('factory', createFactory(component.type, outputCtx, reflector, component.queries));
|
||||||
definitionMapValues.push({key: 'factory', value: templateFactory, quoted: false});
|
|
||||||
|
|
||||||
// e.g `hostBindings: function MyApp_HostBindings { ... }
|
// e.g `hostBindings: function MyApp_HostBindings { ... }
|
||||||
const hostBindings = createHostBindingsFunction(component.type, outputCtx, component.queries);
|
field('hostBindings', createHostBindingsFunction(component, outputCtx, bindingParser));
|
||||||
if (hostBindings) {
|
|
||||||
definitionMapValues.push({key: 'hostBindings', value: hostBindings, quoted: false});
|
|
||||||
}
|
|
||||||
|
|
||||||
// e.g. `template: function MyComponent_Template(_ctx, _cm) {...}`
|
// e.g. `template: function MyComponent_Template(_ctx, _cm) {...}`
|
||||||
const templateTypeName = component.type.reference.name;
|
const templateTypeName = component.type.reference.name;
|
||||||
@ -127,13 +134,11 @@ export function compileComponent(
|
|||||||
component.template !.ngContentSelectors, templateTypeName, templateName, pipeMap,
|
component.template !.ngContentSelectors, templateTypeName, templateName, pipeMap,
|
||||||
component.viewQueries)
|
component.viewQueries)
|
||||||
.buildTemplateFunction(template, []);
|
.buildTemplateFunction(template, []);
|
||||||
definitionMapValues.push({key: 'template', value: templateFunctionExpression, quoted: false});
|
|
||||||
|
field('template', templateFunctionExpression);
|
||||||
|
|
||||||
// e.g `inputs: {a: 'a'}`
|
// e.g `inputs: {a: 'a'}`
|
||||||
if (Object.getOwnPropertyNames(component.inputs).length > 0) {
|
field('inputs', createInputsObject(component, outputCtx));
|
||||||
definitionMapValues.push(
|
|
||||||
{key: 'inputs', quoted: false, value: mapToExpression(component.inputs)});
|
|
||||||
}
|
|
||||||
|
|
||||||
// e.g. `features: [NgOnChangesFeature(MyComponent)]`
|
// e.g. `features: [NgOnChangesFeature(MyComponent)]`
|
||||||
const features: o.Expression[] = [];
|
const features: o.Expression[] = [];
|
||||||
@ -142,7 +147,7 @@ export function compileComponent(
|
|||||||
component.type.reference)]));
|
component.type.reference)]));
|
||||||
}
|
}
|
||||||
if (features.length) {
|
if (features.length) {
|
||||||
definitionMapValues.push({key: 'features', quoted: false, value: o.literalArr(features)});
|
field('features', o.literalArr(features));
|
||||||
}
|
}
|
||||||
|
|
||||||
const className = identifierName(component.type) !;
|
const className = identifierName(component.type) !;
|
||||||
@ -163,7 +168,6 @@ export function compileComponent(
|
|||||||
/* methods */[]));
|
/* methods */[]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// TODO: Remove these when the things are fully supported
|
// TODO: Remove these when the things are fully supported
|
||||||
function unknown<T>(arg: o.Expression | o.Statement | TemplateAst): never {
|
function unknown<T>(arg: o.Expression | o.Statement | TemplateAst): never {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
@ -780,10 +784,28 @@ export function createFactory(
|
|||||||
type.reference.name ? `${type.reference.name}_Factory` : null);
|
type.reference.name ? `${type.reference.name}_Factory` : null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type HostBindings = {
|
||||||
|
[key: string]: string
|
||||||
|
};
|
||||||
|
|
||||||
|
function createHostAttributesArray(
|
||||||
|
directiveMetadata: CompileDirectiveMetadata, outputCtx: OutputContext): o.Expression|null {
|
||||||
|
const values: o.Expression[] = [];
|
||||||
|
const attributes = directiveMetadata.hostAttributes;
|
||||||
|
for (let key of Object.getOwnPropertyNames(attributes)) {
|
||||||
|
const value = attributes[key];
|
||||||
|
values.push(o.literal(key), o.literal(value));
|
||||||
|
}
|
||||||
|
if (values.length > 0) {
|
||||||
|
return outputCtx.constantPool.getConstLiteral(o.literalArr(values));
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
// Return a host binding function or null if one is not necessary.
|
// Return a host binding function or null if one is not necessary.
|
||||||
export function createHostBindingsFunction(
|
function createHostBindingsFunction(
|
||||||
type: CompileTypeMetadata, outputCtx: OutputContext,
|
directiveMetadata: CompileDirectiveMetadata, outputCtx: OutputContext,
|
||||||
queries: CompileQueryMetadata[]): o.Expression|null {
|
bindingParser: BindingParser): o.Expression|null {
|
||||||
const statements: o.Statement[] = [];
|
const statements: o.Statement[] = [];
|
||||||
|
|
||||||
const temporary = function() {
|
const temporary = function() {
|
||||||
@ -797,8 +819,12 @@ export function createHostBindingsFunction(
|
|||||||
};
|
};
|
||||||
}();
|
}();
|
||||||
|
|
||||||
for (let index = 0; index < queries.length; index++) {
|
const hostBindingSourceSpan = typeSourceSpan(
|
||||||
const query = queries[index];
|
directiveMetadata.isComponent ? 'Component' : 'Directive', directiveMetadata.type);
|
||||||
|
|
||||||
|
// Calculate the queries
|
||||||
|
for (let index = 0; index < directiveMetadata.queries.length; index++) {
|
||||||
|
const query = directiveMetadata.queries[index];
|
||||||
|
|
||||||
// e.g. r3.qR(tmp = r3.ld(dirIndex)[1]) && (r3.ld(dirIndex)[0].someDir = tmp);
|
// e.g. r3.qR(tmp = r3.ld(dirIndex)[1]) && (r3.ld(dirIndex)[0].someDir = tmp);
|
||||||
const getDirectiveMemory = o.importExpr(R3.load).callFn([o.variable('dirIndex')]);
|
const getDirectiveMemory = o.importExpr(R3.load).callFn([o.variable('dirIndex')]);
|
||||||
@ -808,18 +834,67 @@ export function createHostBindingsFunction(
|
|||||||
const callQueryRefresh = o.importExpr(R3.queryRefresh).callFn([assignToTemporary]);
|
const callQueryRefresh = o.importExpr(R3.queryRefresh).callFn([assignToTemporary]);
|
||||||
const updateDirective = getDirectiveMemory.key(o.literal(0, o.INFERRED_TYPE))
|
const updateDirective = getDirectiveMemory.key(o.literal(0, o.INFERRED_TYPE))
|
||||||
.prop(query.propertyName)
|
.prop(query.propertyName)
|
||||||
.set(query.first ? temporary().key(o.literal(0)) : temporary());
|
.set(query.first ? temporary().prop('first') : temporary());
|
||||||
const andExpression = callQueryRefresh.and(updateDirective);
|
const andExpression = callQueryRefresh.and(updateDirective);
|
||||||
statements.push(andExpression.toStmt());
|
statements.push(andExpression.toStmt());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (statements.length > 0) {
|
const directiveSummary = directiveMetadata.toSummary();
|
||||||
return o.fn(
|
|
||||||
[new o.FnParam('dirIndex', o.NUMBER_TYPE), new o.FnParam('elIndex', o.NUMBER_TYPE)],
|
// Calculate the host property bindings
|
||||||
statements, o.INFERRED_TYPE, null,
|
const bindings = bindingParser.createBoundHostProperties(directiveSummary, hostBindingSourceSpan);
|
||||||
type.reference.name ? `${type.reference.name}_HostBindings` : null);
|
const bindingContext = o.importExpr(R3.load).callFn([o.variable('dirIndex')]);
|
||||||
|
if (bindings) {
|
||||||
|
for (const binding of bindings) {
|
||||||
|
const bindingExpr = convertPropertyBinding(
|
||||||
|
null, bindingContext, binding.expression, 'b', BindingForm.TrySimple,
|
||||||
|
() => error('Unexpected interpolation'));
|
||||||
|
statements.push(...bindingExpr.stmts);
|
||||||
|
statements.push(o.importExpr(R3.elementProperty)
|
||||||
|
.callFn([
|
||||||
|
o.variable('elIndex'), o.literal(binding.name),
|
||||||
|
o.importExpr(R3.bind).callFn([bindingExpr.currValExpr])
|
||||||
|
])
|
||||||
|
.toStmt());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Calculate host event bindings
|
||||||
|
const eventBindings =
|
||||||
|
bindingParser.createDirectiveHostEventAsts(directiveSummary, hostBindingSourceSpan);
|
||||||
|
if (eventBindings) {
|
||||||
|
for (const binding of eventBindings) {
|
||||||
|
const bindingExpr = convertActionBinding(
|
||||||
|
null, bindingContext, binding.handler, 'b', () => error('Unexpected interpolation'));
|
||||||
|
const bindingName = binding.name && sanitizeIdentifier(binding.name);
|
||||||
|
const typeName = identifierName(directiveMetadata.type);
|
||||||
|
const functionName =
|
||||||
|
typeName && bindingName ? `${typeName}_${bindingName}_HostBindingHandler` : null;
|
||||||
|
const handler = o.fn(
|
||||||
|
[new o.FnParam('event', o.DYNAMIC_TYPE)],
|
||||||
|
[...bindingExpr.stmts, new o.ReturnStatement(bindingExpr.allowDefault)], o.INFERRED_TYPE,
|
||||||
|
null, functionName);
|
||||||
|
statements.push(
|
||||||
|
o.importExpr(R3.listener).callFn([o.literal(binding.name), handler]).toStmt());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (statements.length > 0) {
|
||||||
|
const typeName = directiveMetadata.type.reference.name;
|
||||||
|
return o.fn(
|
||||||
|
[new o.FnParam('dirIndex', o.NUMBER_TYPE), new o.FnParam('elIndex', o.NUMBER_TYPE)],
|
||||||
|
statements, o.INFERRED_TYPE, null, typeName ? `${typeName}_HostBindings` : null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createInputsObject(
|
||||||
|
directive: CompileDirectiveMetadata, outputCtx: OutputContext): o.Expression|null {
|
||||||
|
if (Object.getOwnPropertyNames(directive.inputs).length > 0) {
|
||||||
|
return outputCtx.constantPool.getConstLiteral(mapToExpression(directive.inputs));
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,9 +63,8 @@ export class BindingParser {
|
|||||||
|
|
||||||
getUsedPipes(): CompilePipeSummary[] { return Array.from(this._usedPipes.values()); }
|
getUsedPipes(): CompilePipeSummary[] { return Array.from(this._usedPipes.values()); }
|
||||||
|
|
||||||
createDirectiveHostPropertyAsts(
|
createBoundHostProperties(dirMeta: CompileDirectiveSummary, sourceSpan: ParseSourceSpan):
|
||||||
dirMeta: CompileDirectiveSummary, elementSelector: string,
|
BoundProperty[]|null {
|
||||||
sourceSpan: ParseSourceSpan): BoundElementPropertyAst[]|null {
|
|
||||||
if (dirMeta.hostProperties) {
|
if (dirMeta.hostProperties) {
|
||||||
const boundProps: BoundProperty[] = [];
|
const boundProps: BoundProperty[] = [];
|
||||||
Object.keys(dirMeta.hostProperties).forEach(propName => {
|
Object.keys(dirMeta.hostProperties).forEach(propName => {
|
||||||
@ -78,11 +77,19 @@ export class BindingParser {
|
|||||||
sourceSpan);
|
sourceSpan);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return boundProps.map((prop) => this.createElementPropertyAst(elementSelector, prop));
|
return boundProps;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
createDirectiveHostPropertyAsts(
|
||||||
|
dirMeta: CompileDirectiveSummary, elementSelector: string,
|
||||||
|
sourceSpan: ParseSourceSpan): BoundElementPropertyAst[]|null {
|
||||||
|
const boundProps = this.createBoundHostProperties(dirMeta, sourceSpan);
|
||||||
|
return boundProps &&
|
||||||
|
boundProps.map((prop) => this.createElementPropertyAst(elementSelector, prop));
|
||||||
|
}
|
||||||
|
|
||||||
createDirectiveHostEventAsts(dirMeta: CompileDirectiveSummary, sourceSpan: ParseSourceSpan):
|
createDirectiveHostEventAsts(dirMeta: CompileDirectiveSummary, sourceSpan: ParseSourceSpan):
|
||||||
BoundEventAst[]|null {
|
BoundEventAst[]|null {
|
||||||
if (dirMeta.hostListeners) {
|
if (dirMeta.hostListeners) {
|
||||||
|
@ -100,6 +100,8 @@ export class TemplateParser {
|
|||||||
private _htmlParser: I18NHtmlParser, private _console: Console,
|
private _htmlParser: I18NHtmlParser, private _console: Console,
|
||||||
public transforms: TemplateAstVisitor[]) {}
|
public transforms: TemplateAstVisitor[]) {}
|
||||||
|
|
||||||
|
public get expressionParser() { return this._exprParser; }
|
||||||
|
|
||||||
parse(
|
parse(
|
||||||
component: CompileDirectiveMetadata, template: string|ParseTreeResult,
|
component: CompileDirectiveMetadata, template: string|ParseTreeResult,
|
||||||
directives: CompileDirectiveSummary[], pipes: CompilePipeSummary[], schemas: SchemaMetadata[],
|
directives: CompileDirectiveSummary[], pipes: CompilePipeSummary[], schemas: SchemaMetadata[],
|
||||||
@ -434,6 +436,7 @@ class TemplateParseVisitor implements html.Visitor {
|
|||||||
|
|
||||||
const bindParts = name.match(BIND_NAME_REGEXP);
|
const bindParts = name.match(BIND_NAME_REGEXP);
|
||||||
let hasBinding = false;
|
let hasBinding = false;
|
||||||
|
const boundEvents: BoundEventAst[] = [];
|
||||||
|
|
||||||
if (bindParts !== null) {
|
if (bindParts !== null) {
|
||||||
hasBinding = true;
|
hasBinding = true;
|
||||||
@ -814,7 +817,7 @@ class ElementOrDirectiveRef {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Splits a raw, potentially comma-delimted `exportAs` value into an array of names. */
|
/** Splits a raw, potentially comma-delimited `exportAs` value into an array of names. */
|
||||||
function splitExportAs(exportAs: string | null): string[] {
|
function splitExportAs(exportAs: string | null): string[] {
|
||||||
return exportAs ? exportAs.split(',').map(e => e.trim()) : [];
|
return exportAs ? exportAs.split(',').map(e => e.trim()) : [];
|
||||||
}
|
}
|
||||||
|
@ -6,14 +6,16 @@
|
|||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {AotCompilerHost, AotCompilerOptions, AotSummaryResolver, CompileDirectiveMetadata, CompileMetadataResolver, CompilePipeSummary, CompilerConfig, DirectiveNormalizer, DirectiveResolver, DomElementSchemaRegistry, HtmlParser, I18NHtmlParser, Lexer, NgModuleResolver, Parser, PipeResolver, StaticReflector, StaticSymbol, StaticSymbolCache, StaticSymbolResolver, TemplateParser, TypeScriptEmitter, analyzeNgModules, createAotUrlResolver} from '@angular/compiler';
|
import {AotCompilerHost, AotCompilerOptions, AotSummaryResolver, CompileDirectiveMetadata, CompileMetadataResolver, CompilePipeSummary, CompilerConfig, DEFAULT_INTERPOLATION_CONFIG, DirectiveNormalizer, DirectiveResolver, DomElementSchemaRegistry, HtmlParser, I18NHtmlParser, Lexer, NgModuleResolver, ParseError, Parser, PipeResolver, StaticReflector, StaticSymbol, StaticSymbolCache, StaticSymbolResolver, TemplateParser, TypeScriptEmitter, analyzeNgModules, createAotUrlResolver} from '@angular/compiler';
|
||||||
import {ViewEncapsulation} from '@angular/core';
|
import {ViewEncapsulation} from '@angular/core';
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
import {ConstantPool} from '../../src/constant_pool';
|
import {ConstantPool} from '../../src/constant_pool';
|
||||||
|
import {ParserError} from '../../src/expression_parser/ast';
|
||||||
import * as o from '../../src/output/output_ast';
|
import * as o from '../../src/output/output_ast';
|
||||||
import {compilePipe} from '../../src/render3/r3_pipe_compiler';
|
import {compilePipe} from '../../src/render3/r3_pipe_compiler';
|
||||||
import {compileComponent, compileDirective} from '../../src/render3/r3_view_compiler';
|
import {compileComponent, compileDirective} from '../../src/render3/r3_view_compiler';
|
||||||
|
import {BindingParser} from '../../src/template_parser/binding_parser';
|
||||||
import {OutputContext} from '../../src/util';
|
import {OutputContext} from '../../src/util';
|
||||||
import {MockAotCompilerHost, MockCompilerHost, MockData, MockDirectory, arrayToMockDir, expectNoDiagnostics, settings, setup, toMockFileArray} from '../aot/test_util';
|
import {MockAotCompilerHost, MockCompilerHost, MockData, MockDirectory, arrayToMockDir, expectNoDiagnostics, settings, setup, toMockFileArray} from '../aot/test_util';
|
||||||
|
|
||||||
@ -194,6 +196,11 @@ export function compile(
|
|||||||
constantPool: new ConstantPool()
|
constantPool: new ConstantPool()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const errors: ParseError[] = [];
|
||||||
|
|
||||||
|
const hostBindingParser = new BindingParser(
|
||||||
|
expressionParser, DEFAULT_INTERPOLATION_CONFIG, elementSchemaRegistry, [], errors);
|
||||||
|
|
||||||
// Load all directives and pipes
|
// Load all directives and pipes
|
||||||
for (const pipeOrDirective of pipesOrDirectives) {
|
for (const pipeOrDirective of pipesOrDirectives) {
|
||||||
const module = analyzedModules.ngModuleByPipeOrDirective.get(pipeOrDirective) !;
|
const module = analyzedModules.ngModuleByPipeOrDirective.get(pipeOrDirective) !;
|
||||||
@ -219,9 +226,10 @@ export function compile(
|
|||||||
const parsedTemplate = templateParser.parse(
|
const parsedTemplate = templateParser.parse(
|
||||||
metadata, htmlAst, directives, pipes, module.schemas, fakeUrl, false);
|
metadata, htmlAst, directives, pipes, module.schemas, fakeUrl, false);
|
||||||
compileComponent(
|
compileComponent(
|
||||||
fakeOutputContext, metadata, pipes, parsedTemplate.template, staticReflector);
|
fakeOutputContext, metadata, pipes, parsedTemplate.template, staticReflector,
|
||||||
|
hostBindingParser);
|
||||||
} else {
|
} else {
|
||||||
compileDirective(fakeOutputContext, metadata, staticReflector);
|
compileDirective(fakeOutputContext, metadata, staticReflector, hostBindingParser);
|
||||||
}
|
}
|
||||||
} else if (resolver.isPipe(pipeOrDirective)) {
|
} else if (resolver.isPipe(pipeOrDirective)) {
|
||||||
const metadata = resolver.getPipeMetadata(pipeOrDirective);
|
const metadata = resolver.getPipeMetadata(pipeOrDirective);
|
||||||
@ -243,5 +251,9 @@ export function compile(
|
|||||||
/* referenceFilter */ undefined,
|
/* referenceFilter */ undefined,
|
||||||
/* importFilter */ e => e.moduleName != null && e.moduleName.startsWith('/app'));
|
/* importFilter */ e => e.moduleName != null && e.moduleName.startsWith('/app'));
|
||||||
|
|
||||||
|
if (errors.length) {
|
||||||
|
throw new Error('Unexpected errors:' + errors.map(e => e.toString()).join(', '));
|
||||||
|
}
|
||||||
|
|
||||||
return {source: result.sourceText, outputContext: fakeOutputContext};
|
return {source: result.sourceText, outputContext: fakeOutputContext};
|
||||||
}
|
}
|
||||||
|
@ -141,6 +141,40 @@ describe('compiler compliance', () => {
|
|||||||
expectEmit(source, MyComponentDefinition, 'Incorrect MyComponentDefinition.ngComponentDef');
|
expectEmit(source, MyComponentDefinition, 'Incorrect MyComponentDefinition.ngComponentDef');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should support host bindings', () => {
|
||||||
|
const files = {
|
||||||
|
app: {
|
||||||
|
'spec.ts': `
|
||||||
|
import {Directive, HostBinding, NgModule} from '@angular/core';
|
||||||
|
|
||||||
|
@Directive({selector: '[hostBindingDir]'})
|
||||||
|
export class HostBindingDir {
|
||||||
|
@HostBinding('id') dirId = 'some id';
|
||||||
|
}
|
||||||
|
|
||||||
|
@NgModule({declarations: [HostBindingDir]})
|
||||||
|
export class MyModule {}
|
||||||
|
`
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const HostBindingDirDeclaration = `
|
||||||
|
static ngDirectiveDef = $r3$.ɵdefineDirective({
|
||||||
|
type: HostBindingDir,
|
||||||
|
factory: function HostBindingDir_Factory() { return new HostBindingDir(); },
|
||||||
|
hostBindings: function HostBindingDir_HostBindings(
|
||||||
|
dirIndex: $number$, elIndex: $number$) {
|
||||||
|
$r3$.ɵp(elIndex, 'id', $r3$.ɵb($r3$.ɵld(dirIndex).dirId));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
`;
|
||||||
|
|
||||||
|
const result = compile(files, angularFiles);
|
||||||
|
const source = result.source;
|
||||||
|
|
||||||
|
expectEmit(source, HostBindingDirDeclaration, 'Invalid host binding code');
|
||||||
|
});
|
||||||
|
|
||||||
it('should support structural directives', () => {
|
it('should support structural directives', () => {
|
||||||
const files = {
|
const files = {
|
||||||
app: {
|
app: {
|
||||||
@ -658,7 +692,7 @@ describe('compiler compliance', () => {
|
|||||||
hostBindings: function ContentQueryComponent_HostBindings(
|
hostBindings: function ContentQueryComponent_HostBindings(
|
||||||
dirIndex: $number$, elIndex: $number$) {
|
dirIndex: $number$, elIndex: $number$) {
|
||||||
var $tmp$: $any$;
|
var $tmp$: $any$;
|
||||||
($r3$.ɵqR(($tmp$ = $r3$.ɵld(dirIndex)[1])) && ($r3$.ɵld(dirIndex)[0].someDir = $tmp$[0]));
|
($r3$.ɵqR(($tmp$ = $r3$.ɵld(dirIndex)[1])) && ($r3$.ɵld(dirIndex)[0].someDir = $tmp$.first));
|
||||||
},
|
},
|
||||||
template: function ContentQueryComponent_Template(
|
template: function ContentQueryComponent_Template(
|
||||||
ctx: $ContentQueryComponent$, cm: $boolean$) {
|
ctx: $ContentQueryComponent$, cm: $boolean$) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user