fix(compiler): add hostVars and support pure functions in host bindings (#25626)
PR Close #25626
This commit is contained in:

committed by
Misko Hevery

parent
00f13110be
commit
b424b3187e
@ -590,6 +590,7 @@ describe('compiler compliance', () => {
|
|||||||
hostBindings: function HostBindingDir_HostBindings(dirIndex, elIndex) {
|
hostBindings: function HostBindingDir_HostBindings(dirIndex, elIndex) {
|
||||||
$r3$.ɵelementProperty(elIndex, "id", $r3$.ɵbind($r3$.ɵloadDirective(dirIndex).dirId));
|
$r3$.ɵelementProperty(elIndex, "id", $r3$.ɵbind($r3$.ɵloadDirective(dirIndex).dirId));
|
||||||
},
|
},
|
||||||
|
hostVars: 1,
|
||||||
features: [$r3$.ɵPublicFeature]
|
features: [$r3$.ɵPublicFeature]
|
||||||
});
|
});
|
||||||
`;
|
`;
|
||||||
@ -600,6 +601,53 @@ describe('compiler compliance', () => {
|
|||||||
expectEmit(source, HostBindingDirDeclaration, 'Invalid host binding code');
|
expectEmit(source, HostBindingDirDeclaration, 'Invalid host binding code');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should support host bindings with pure functions', () => {
|
||||||
|
const files = {
|
||||||
|
app: {
|
||||||
|
'spec.ts': `
|
||||||
|
import {Component, NgModule} from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'host-binding-comp',
|
||||||
|
host: {
|
||||||
|
'[id]': '["red", id]'
|
||||||
|
},
|
||||||
|
template: ''
|
||||||
|
})
|
||||||
|
export class HostBindingComp {
|
||||||
|
id = 'some id';
|
||||||
|
}
|
||||||
|
|
||||||
|
@NgModule({declarations: [HostBindingComp]})
|
||||||
|
export class MyModule {}
|
||||||
|
`
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const HostBindingCompDeclaration = `
|
||||||
|
const $ff$ = function ($v$) { return ["red", $v$]; };
|
||||||
|
…
|
||||||
|
HostBindingComp.ngComponentDef = $r3$.ɵdefineComponent({
|
||||||
|
type: HostBindingComp,
|
||||||
|
selectors: [["host-binding-comp"]],
|
||||||
|
factory: function HostBindingComp_Factory(t) { return new (t || HostBindingComp)(); },
|
||||||
|
hostBindings: function HostBindingComp_HostBindings(dirIndex, elIndex) {
|
||||||
|
$r3$.ɵelementProperty(elIndex, "id", $r3$.ɵbind($r3$.ɵpureFunction1(1, $ff$, $r3$.ɵloadDirective(dirIndex).id)));
|
||||||
|
},
|
||||||
|
hostVars: 3,
|
||||||
|
features: [$r3$.ɵPublicFeature],
|
||||||
|
consts: 0,
|
||||||
|
vars: 0,
|
||||||
|
template: function HostBindingComp_Template(rf, ctx) {}
|
||||||
|
});
|
||||||
|
`;
|
||||||
|
|
||||||
|
const result = compile(files, angularFiles);
|
||||||
|
const source = result.source;
|
||||||
|
|
||||||
|
expectEmit(source, HostBindingCompDeclaration, 'Invalid host binding code');
|
||||||
|
});
|
||||||
|
|
||||||
it('should support structural directives', () => {
|
it('should support structural directives', () => {
|
||||||
const files = {
|
const files = {
|
||||||
app: {
|
app: {
|
||||||
|
@ -26,7 +26,7 @@ import {Render3ParseResult} from '../r3_template_transform';
|
|||||||
import {typeWithParameters} from '../util';
|
import {typeWithParameters} from '../util';
|
||||||
|
|
||||||
import {R3ComponentDef, R3ComponentMetadata, R3DirectiveDef, R3DirectiveMetadata, R3QueryMetadata} from './api';
|
import {R3ComponentDef, R3ComponentMetadata, R3DirectiveDef, R3DirectiveMetadata, R3QueryMetadata} from './api';
|
||||||
import {BindingScope, TemplateDefinitionBuilder, renderFlagCheckIfStmt} from './template';
|
import {BindingScope, TemplateDefinitionBuilder, ValueConverter, renderFlagCheckIfStmt} from './template';
|
||||||
import {CONTEXT_NAME, DefinitionMap, RENDER_FLAGS, TEMPORARY_NAME, asLiteral, conditionallyCreateMapObjectLiteral, getQueryPredicate, temporaryAllocator} from './util';
|
import {CONTEXT_NAME, DefinitionMap, RENDER_FLAGS, TEMPORARY_NAME, asLiteral, conditionallyCreateMapObjectLiteral, getQueryPredicate, temporaryAllocator} from './util';
|
||||||
|
|
||||||
const EMPTY_ARRAY: any[] = [];
|
const EMPTY_ARRAY: any[] = [];
|
||||||
@ -56,8 +56,22 @@ function baseDirectiveFields(
|
|||||||
|
|
||||||
definitionMap.set('contentQueriesRefresh', createContentQueriesRefreshFunction(meta));
|
definitionMap.set('contentQueriesRefresh', createContentQueriesRefreshFunction(meta));
|
||||||
|
|
||||||
|
// Initialize hostVars to number of bound host properties (interpolations illegal)
|
||||||
|
let hostVars = Object.keys(meta.host.properties).length;
|
||||||
|
|
||||||
// e.g. `hostBindings: (dirIndex, elIndex) => { ... }
|
// e.g. `hostBindings: (dirIndex, elIndex) => { ... }
|
||||||
definitionMap.set('hostBindings', createHostBindingsFunction(meta, bindingParser));
|
definitionMap.set(
|
||||||
|
'hostBindings',
|
||||||
|
createHostBindingsFunction(meta, bindingParser, constantPool, (slots: number) => {
|
||||||
|
const originalSlots = hostVars;
|
||||||
|
hostVars += slots;
|
||||||
|
return originalSlots;
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (hostVars) {
|
||||||
|
// e.g. `hostVars: 2
|
||||||
|
definitionMap.set('hostVars', o.literal(hostVars));
|
||||||
|
}
|
||||||
|
|
||||||
// e.g. `attributes: ['role', 'listbox']`
|
// e.g. `attributes: ['role', 'listbox']`
|
||||||
definitionMap.set('attributes', createHostAttributesArray(meta));
|
definitionMap.set('attributes', createHostAttributesArray(meta));
|
||||||
@ -521,7 +535,8 @@ function createViewQueriesFunction(
|
|||||||
|
|
||||||
// Return a host binding function or null if one is not necessary.
|
// Return a host binding function or null if one is not necessary.
|
||||||
function createHostBindingsFunction(
|
function createHostBindingsFunction(
|
||||||
meta: R3DirectiveMetadata, bindingParser: BindingParser): o.Expression|null {
|
meta: R3DirectiveMetadata, bindingParser: BindingParser, constantPool: ConstantPool,
|
||||||
|
allocatePureFunctionSlots: (slots: number) => number): o.Expression|null {
|
||||||
const statements: o.Statement[] = [];
|
const statements: o.Statement[] = [];
|
||||||
|
|
||||||
const hostBindingSourceSpan = meta.typeSourceSpan;
|
const hostBindingSourceSpan = meta.typeSourceSpan;
|
||||||
@ -532,9 +547,16 @@ function createHostBindingsFunction(
|
|||||||
const bindings = bindingParser.createBoundHostProperties(directiveSummary, hostBindingSourceSpan);
|
const bindings = bindingParser.createBoundHostProperties(directiveSummary, hostBindingSourceSpan);
|
||||||
const bindingContext = o.importExpr(R3.loadDirective).callFn([o.variable('dirIndex')]);
|
const bindingContext = o.importExpr(R3.loadDirective).callFn([o.variable('dirIndex')]);
|
||||||
if (bindings) {
|
if (bindings) {
|
||||||
|
const valueConverter = new ValueConverter(
|
||||||
|
constantPool,
|
||||||
|
/* new nodes are illegal here */ () => error('Unexpected node'), allocatePureFunctionSlots,
|
||||||
|
/* pipes are illegal here */ () => error('Unexpected pipe'));
|
||||||
|
|
||||||
for (const binding of bindings) {
|
for (const binding of bindings) {
|
||||||
|
// resolve literal arrays and literal objects
|
||||||
|
const value = binding.expression.visit(valueConverter);
|
||||||
const bindingExpr = convertPropertyBinding(
|
const bindingExpr = convertPropertyBinding(
|
||||||
null, bindingContext, binding.expression, 'b', BindingForm.TrySimple,
|
null, bindingContext, value, 'b', BindingForm.TrySimple,
|
||||||
() => error('Unexpected interpolation'));
|
() => error('Unexpected interpolation'));
|
||||||
statements.push(...bindingExpr.stmts);
|
statements.push(...bindingExpr.stmts);
|
||||||
statements.push(o.importExpr(R3.elementProperty)
|
statements.push(o.importExpr(R3.elementProperty)
|
||||||
|
@ -908,7 +908,7 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ValueConverter extends AstMemoryEfficientTransformer {
|
export class ValueConverter extends AstMemoryEfficientTransformer {
|
||||||
private _pipeBindExprs: FunctionCall[] = [];
|
private _pipeBindExprs: FunctionCall[] = [];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
Reference in New Issue
Block a user