mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
fix(compiler-cli): Handle ng-template with structural directive in indexer (#44788)
An `ng-template` with an inline template (i.e. has a structural directive) would previously not get an `undefined` `tagName` because the logic assumed the element would be `t.Element` or `t.Content` and read the tag name from the `name` property. For a `t.Template`, this exists instead on the `t.tagName`. The final result would be an `tagName` of `undefined` for the parent `t.Template`, causing failures in the indexer downstream. This `undefined` value is actually expected in the renderer code, even though the type does not specify this possibility. This change updates the type of `tagName` to be `string|null` and explicitly handles the case where there is a structural directive on an `ng-template`. You can see how the two are differentiated in the compliance code that was modified in this commit. PR Close #44788
This commit is contained in:
committed by
Andrew Kushnir
parent
6cb7c3ebe3
commit
fe3e4d6865
@@ -229,7 +229,7 @@ class TemplateVisitor extends TmplAstRecursiveVisitor {
|
||||
let name: string;
|
||||
let kind: IdentifierKind.Element|IdentifierKind.Template;
|
||||
if (node instanceof TmplAstTemplate) {
|
||||
name = node.tagName;
|
||||
name = node.tagName ?? 'ng-template';
|
||||
kind = IdentifierKind.Template;
|
||||
} else {
|
||||
name = node.name;
|
||||
|
||||
@@ -75,6 +75,20 @@ runInEachFileSystem(() => {
|
||||
});
|
||||
});
|
||||
|
||||
it('works when structural directives are on templates', () => {
|
||||
const template = '<ng-template *ngIf="true">';
|
||||
const refs = getTemplateIdentifiers(bind(template));
|
||||
|
||||
const [ref] = Array.from(refs);
|
||||
expect(ref).toEqual({
|
||||
kind: IdentifierKind.Template,
|
||||
name: 'ng-template',
|
||||
span: new AbsoluteSourceSpan(1, 12),
|
||||
usedDirectives: new Set(),
|
||||
attributes: new Set(),
|
||||
});
|
||||
});
|
||||
|
||||
it('should generate nothing in empty template', () => {
|
||||
const template = '';
|
||||
const refs = getTemplateIdentifiers(bind(template));
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@ consts: function() {
|
||||
},
|
||||
template: function MyComponent_Template(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵɵtemplate(0, MyComponent_0_Template, 2, 1, undefined, 0);
|
||||
$r3$.ɵɵtemplate(0, MyComponent_0_Template, 2, 1, null, 0);
|
||||
}
|
||||
if (rf & 2) {
|
||||
$r3$.ɵɵproperty("ngIf", true);
|
||||
|
||||
+1
-1
@@ -19,7 +19,7 @@ consts: function() {
|
||||
},
|
||||
template: function MyComponent_Template(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵɵtemplate(0, MyComponent_0_Template, 1, 0, undefined, 0);
|
||||
$r3$.ɵɵtemplate(0, MyComponent_0_Template, 1, 0, null, 0);
|
||||
}
|
||||
if (rf & 2) {
|
||||
$r3$.ɵɵproperty("ngIf", ctx.visible);
|
||||
|
||||
+1
-1
@@ -31,7 +31,7 @@ consts: function() {
|
||||
},
|
||||
template: function MyComponent_Template(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵɵtemplate(0, MyComponent_0_Template, 1, 0, undefined, 0);
|
||||
$r3$.ɵɵtemplate(0, MyComponent_0_Template, 1, 0, null, 0);
|
||||
$r3$.ɵɵtemplate(1, MyComponent_ng_container_1_Template, 2, 0, "ng-container", 0);
|
||||
}
|
||||
if (rf & 2) {
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
consts: [[4, "ngIf"], [3, "dir"]],
|
||||
template: function TestComp_Template(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
$i0$.ɵɵtemplate(0, $TestComp_0_Template$, 1, 1, undefined, 0);
|
||||
$i0$.ɵɵtemplate(0, $TestComp_0_Template$, 1, 1, null, 0);
|
||||
}
|
||||
if (rf & 2) {
|
||||
$i0$.ɵɵproperty("ngIf", true);
|
||||
|
||||
@@ -119,11 +119,23 @@ export class Element implements Node {
|
||||
|
||||
export class Template implements Node {
|
||||
constructor(
|
||||
public tagName: string, public attributes: TextAttribute[], public inputs: BoundAttribute[],
|
||||
public outputs: BoundEvent[], public templateAttrs: (BoundAttribute|TextAttribute)[],
|
||||
public children: Node[], public references: Reference[], public variables: Variable[],
|
||||
public sourceSpan: ParseSourceSpan, public startSourceSpan: ParseSourceSpan,
|
||||
public endSourceSpan: ParseSourceSpan|null, public i18n?: I18nMeta) {}
|
||||
// tagName is the name of the container element, if applicable.
|
||||
// `null` is a special case for when there is a structural directive on an `ng-template` so
|
||||
// the renderer can differentiate between the synthetic template and the one written in the
|
||||
// file.
|
||||
public tagName: string|null,
|
||||
public attributes: TextAttribute[],
|
||||
public inputs: BoundAttribute[],
|
||||
public outputs: BoundEvent[],
|
||||
public templateAttrs: (BoundAttribute|TextAttribute)[],
|
||||
public children: Node[],
|
||||
public references: Reference[],
|
||||
public variables: Variable[],
|
||||
public sourceSpan: ParseSourceSpan,
|
||||
public startSourceSpan: ParseSourceSpan,
|
||||
public endSourceSpan: ParseSourceSpan|null,
|
||||
public i18n?: I18nMeta,
|
||||
) {}
|
||||
visit<Result>(visitor: Visitor<Result>): Result {
|
||||
return visitor.visitTemplate(this);
|
||||
}
|
||||
|
||||
@@ -186,7 +186,7 @@ class HtmlAstToIvyAst implements html.Visitor {
|
||||
const children: t.Node[] =
|
||||
html.visitAll(preparsedElement.nonBindable ? NON_BINDABLE_VISITOR : this, element.children);
|
||||
|
||||
let parsedElement: t.Node|undefined;
|
||||
let parsedElement: t.Content|t.Template|t.Element|undefined;
|
||||
if (preparsedElement.type === PreparsedElementType.NG_CONTENT) {
|
||||
// `<ng-content>`
|
||||
if (element.children &&
|
||||
@@ -235,13 +235,12 @@ class HtmlAstToIvyAst implements html.Visitor {
|
||||
// the wrapping template to prevent unnecessary i18n instructions from being generated. The
|
||||
// necessary i18n meta information will be extracted from child elements.
|
||||
const i18n = isTemplateElement && isI18nRootElement ? undefined : element.i18n;
|
||||
const name = parsedElement instanceof t.Template ? null : parsedElement.name;
|
||||
|
||||
// TODO(pk): test for this case
|
||||
parsedElement = new t.Template(
|
||||
(parsedElement as t.Element | t.Content).name, hoistedAttrs.attributes,
|
||||
hoistedAttrs.inputs, hoistedAttrs.outputs, templateAttrs, [parsedElement],
|
||||
[/* no references */], templateVariables, element.sourceSpan, element.startSourceSpan,
|
||||
element.endSourceSpan, i18n);
|
||||
name, hoistedAttrs.attributes, hoistedAttrs.inputs, hoistedAttrs.outputs, templateAttrs,
|
||||
[parsedElement], [/* no references */], templateVariables, element.sourceSpan,
|
||||
element.startSourceSpan, element.endSourceSpan, i18n);
|
||||
}
|
||||
if (isI18nRootElement) {
|
||||
this.inI18nBlock = false;
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
import {BindingType} from '../../src/expression_parser/ast';
|
||||
import * as t from '../../src/render3/r3_ast';
|
||||
import {unparse} from '../expression_parser/utils/unparser';
|
||||
|
||||
import {parseR3 as parse} from './view/util';
|
||||
|
||||
|
||||
@@ -272,6 +273,18 @@ describe('R3 template transform', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it('should support <ng-template> with structural directive', () => {
|
||||
expectFromHtml('<ng-template *ngIf="true"></ng-template>').toEqual([
|
||||
['Template'],
|
||||
['BoundAttribute', 0, 'ngIf', 'true'],
|
||||
['Template'],
|
||||
]);
|
||||
const res = parse('<ng-template *ngIf="true"></ng-template>', {ignoreError: false});
|
||||
expect((res.nodes[0] as t.Template).tagName).toEqual(null);
|
||||
expect(((res.nodes[0] as t.Template).children[0] as t.Template).tagName)
|
||||
.toEqual('ng-template');
|
||||
});
|
||||
|
||||
it('should support reference via #...', () => {
|
||||
expectFromHtml('<ng-template #a></ng-template>').toEqual([
|
||||
['Template'],
|
||||
|
||||
@@ -183,7 +183,7 @@ function toAttributeCssSelector(attribute: t.TextAttribute|t.BoundAttribute|t.Bo
|
||||
}
|
||||
|
||||
function getNodeName(node: t.Template|t.Element): string {
|
||||
return node instanceof t.Template ? node.tagName : node.name;
|
||||
return node instanceof t.Template ? (node.tagName ?? 'ng-template') : node.name;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user