Skip to content

Commit 952cf06

Browse files
authored
fix(material/schematics): not migrating elements with template directives (#25956)
The MDC migration wasn't traversing into template elements which meant that it wouldn't migrate elements with an `*ngIf` or an `*ngFor` on them. Fixes #25824.
1 parent 4573263 commit 952cf06

File tree

4 files changed

+120
-5
lines changed

4 files changed

+120
-5
lines changed

integration/mdc-migration/golden/src/app/components/chips/chips.component.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,12 @@ <h2>Chips example</h2>
1515
<mat-form-field class="example-chip-list" appearance="fill">
1616
<mat-label>Favorite Fruits</mat-label>
1717
<mat-chip-grid #fruitChipList aria-label="Fruit selection">
18-
<mat-chip *ngFor="let fruit of fruits" (removed)="remove(fruit)">
18+
<mat-chip-row *ngFor="let fruit of fruits" (removed)="remove(fruit)">
1919
{{fruit.name}}
2020
<button matChipRemove>
2121
<mat-icon>cancel</mat-icon>
2222
</button>
23-
</mat-chip>
23+
</mat-chip-row>
2424
<input placeholder="New fruit..."
2525
[matChipInputFor]="fruitChipList"
2626
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"

src/material/schematics/ng-generate/mdc-migration/rules/components/chips/chips-template.spec.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,4 +93,66 @@ describe('chips template migrator', () => {
9393
it('should update standalone chips', async () => {
9494
await runMigrationTest('<mat-chip></mat-chip>', '<mat-chip-option></mat-chip-option>');
9595
});
96+
97+
it('should update mat-chip with an *ngFor', async () => {
98+
await runMigrationTest(
99+
`
100+
<mat-chip-list>
101+
<mat-chip *ngFor="let chip of chips">{{chip}}</mat-chip>
102+
</mat-chip-list>
103+
`,
104+
`
105+
<mat-chip-listbox>
106+
<mat-chip-option *ngFor="let chip of chips">{{chip}}</mat-chip-option>
107+
</mat-chip-listbox>
108+
`,
109+
);
110+
});
111+
112+
it('should update a chip listbox with a nested ng-container', async () => {
113+
await runMigrationTest(
114+
`
115+
<mat-chip-list>
116+
<ng-container *ngFor="let category of categories">
117+
<ng-container *ngIf="category === 'something'">
118+
<mat-chip *ngFor="let chip of category.chips" [selectable]="false">{{chip}}</mat-chip>
119+
</ng-container>
120+
</ng-container>
121+
</mat-chip-list>
122+
`,
123+
`
124+
<mat-chip-listbox>
125+
<ng-container *ngFor="let category of categories">
126+
<ng-container *ngIf="category === 'something'">
127+
<mat-chip-option *ngFor="let chip of category.chips" [selectable]="false">{{chip}}</mat-chip-option>
128+
</ng-container>
129+
</ng-container>
130+
</mat-chip-listbox>
131+
`,
132+
);
133+
});
134+
135+
it('should update a chip with an *ngIf', async () => {
136+
await runMigrationTest(
137+
'<mat-chip *ngIf="isShown"></mat-chip>',
138+
'<mat-chip-option *ngIf="isShown"></mat-chip-option>',
139+
);
140+
});
141+
142+
it('should update a chip grid with an *ngFor', async () => {
143+
await runMigrationTest(
144+
`
145+
<mat-chip-list #chipList>
146+
<mat-chip *ngFor="let chip of chips">{{chip}}</mat-chip>
147+
<input type="text" matInput [matChipInputFor]="chipList">
148+
</mat-chip-list>
149+
`,
150+
`
151+
<mat-chip-grid #chipList>
152+
<mat-chip-row *ngFor="let chip of chips">{{chip}}</mat-chip-row>
153+
<input type="text" matInput [matChipInputFor]="chipList">
154+
</mat-chip-grid>
155+
`,
156+
);
157+
});
96158
});

src/material/schematics/ng-generate/mdc-migration/rules/tree-traversal.spec.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,49 @@ function runClearAttributeTest(html: string, result: string): void {
4848
}
4949

5050
describe('#visitElements', () => {
51+
describe('visitElements', () => {
52+
it('should traverse elements with an *ngFor', () => {
53+
const visitedElements: string[] = [];
54+
const template = `
55+
<parent>
56+
<child *ngFor="let c of children">
57+
<grandchild *ngFor="let g of c.children"></grandchild>
58+
</child>
59+
</parent>
60+
`;
61+
62+
visitElements(parseTemplate(template).nodes, node => visitedElements.push(node.name));
63+
expect(visitedElements).toEqual(['parent', 'child', 'grandchild']);
64+
});
65+
66+
it('should traverse elements inside ng-container', () => {
67+
const visitedElements: string[] = [];
68+
const template = `
69+
<ng-container>
70+
<parent>
71+
<ng-container>
72+
<child>
73+
<ng-container>
74+
<grandchild></grandchild>
75+
</ng-container>
76+
</child>
77+
</ng-container>
78+
</parent>
79+
</ng-container>
80+
`;
81+
82+
visitElements(parseTemplate(template).nodes, node => visitedElements.push(node.name));
83+
expect(visitedElements).toEqual([
84+
'ng-container',
85+
'parent',
86+
'ng-container',
87+
'child',
88+
'ng-container',
89+
'grandchild',
90+
]);
91+
});
92+
});
93+
5194
describe('tag name replacements', () => {
5295
it('should handle basic cases', async () => {
5396
runTagNameDuplicationTest('<a></a>', '<aa></aa>');

src/material/schematics/ng-generate/mdc-migration/rules/tree-traversal.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
ParsedTemplate,
1111
TmplAstElement,
1212
TmplAstNode,
13+
TmplAstTemplate,
1314
parseTemplate as parseTemplateUsingCompiler,
1415
} from '@angular/compiler';
1516

@@ -32,9 +33,18 @@ export function visitElements(
3233
nodes.reverse();
3334
for (let i = 0; i < nodes.length; i++) {
3435
const node = nodes[i];
35-
if (node instanceof TmplAstElement) {
36+
const isElement = node instanceof TmplAstElement;
37+
38+
if (isElement) {
3639
preorderCallback(node);
40+
}
41+
42+
// Descend both into elements and templates in order to cover cases like `*ngIf` and `*ngFor`.
43+
if (isElement || node instanceof TmplAstTemplate) {
3744
visitElements(node.children, preorderCallback, postorderCallback);
45+
}
46+
47+
if (isElement) {
3848
postorderCallback(node);
3949
}
4050
}
@@ -46,8 +56,8 @@ export function visitElements(
4656
*
4757
* For more details, see https://github.com/angular/angular/blob/4332897baa2226ef246ee054fdd5254e3c129109/packages/compiler-cli/src/ngtsc/annotations/component/src/resources.ts#L230.
4858
*
49-
* @param html text of the template to parse
50-
* @param filePath URL to use for source mapping of the parsed template
59+
* @param template text of the template to parse
60+
* @param templateUrl URL to use for source mapping of the parsed template
5161
* @returns the updated template html.
5262
*/
5363
export function parseTemplate(template: string, templateUrl: string = ''): ParsedTemplate {

0 commit comments

Comments
 (0)