Skip to content

Commit f4a96a9

Browse files
thePunderWomandylhunn
authored andcommitted
fix(migrations): handle aliases on bound ngIf migrations (#53261)
This fixes a reported issue where ngIf is used on an ng-template with let aliases. fixes: #53251 PR Close #53261
1 parent 9325687 commit f4a96a9

File tree

5 files changed

+96
-18
lines changed

5 files changed

+96
-18
lines changed

packages/core/schematics/ng-generate/control-flow-migration/fors.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -173,10 +173,11 @@ function migrateStandardNgFor(etm: ElementToMigrate, tmpl: string, offset: numbe
173173

174174
function migrateBoundNgFor(etm: ElementToMigrate, tmpl: string, offset: number): Result {
175175
const forAttrs = etm.forAttrs!;
176-
const aliasMap = forAttrs.aliases;
176+
const aliasAttrs = etm.aliasAttrs!;
177+
const aliasMap = aliasAttrs.aliases;
177178

178179
const originals = getOriginals(etm, tmpl, offset);
179-
const condition = `${forAttrs.item} of ${forAttrs.forOf}`;
180+
const condition = `${aliasAttrs.item} of ${forAttrs.forOf}`;
180181

181182
const aliases = [];
182183
let aliasedIndex = '$index';
@@ -188,10 +189,10 @@ function migrateBoundNgFor(etm: ElementToMigrate, tmpl: string, offset: number):
188189
}
189190
const aliasStr = (aliases.length > 0) ? `;${aliases.join(';')}` : '';
190191

191-
let trackBy = forAttrs.item;
192+
let trackBy = aliasAttrs.item;
192193
if (forAttrs.trackBy !== '') {
193194
// build trackby value
194-
trackBy = `${forAttrs.trackBy.trim()}(${aliasedIndex}, ${forAttrs.item})`;
195+
trackBy = `${forAttrs.trackBy.trim()}(${aliasedIndex}, ${aliasAttrs.item})`;
195196
}
196197

197198
const {start, middle, end} = getMainBlock(etm, tmpl, offset);

packages/core/schematics/ng-generate/control-flow-migration/ifs.ts

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -85,12 +85,22 @@ function migrateNgIf(etm: ElementToMigrate, tmpl: string, offset: number): Resul
8585
}
8686

8787
function buildIfBlock(etm: ElementToMigrate, tmpl: string, offset: number): Result {
88+
const aliasAttrs = etm.aliasAttrs!;
89+
const aliases = [...aliasAttrs.aliases.keys()];
90+
8891
// includes the mandatory semicolon before as
8992
const lbString = etm.hasLineBreaks ? '\n' : '';
90-
const condition = etm.attr.value
91-
.replace(' as ', '; as ')
92-
// replace 'let' with 'as' whatever spaces are between ; and 'let'
93-
.replace(/;\s*let/g, '; as');
93+
let condition = etm.attr.value
94+
.replace(' as ', '; as ')
95+
// replace 'let' with 'as' whatever spaces are between ; and 'let'
96+
.replace(/;\s*let/g, '; as');
97+
if (aliases.length > 1 || (aliases.length === 1 && condition.indexOf('; as') > -1)) {
98+
// only 1 alias allowed
99+
throw new Error(
100+
'Found more than one alias on your ngIf. Remove one of them and re-run the migration.');
101+
} else if (aliases.length === 1) {
102+
condition += `; as ${aliases[0]}`;
103+
}
94104

95105
const originals = getOriginals(etm, tmpl, offset);
96106

@@ -121,8 +131,18 @@ function buildStandardIfElseBlock(
121131
}
122132

123133
function buildBoundIfElseBlock(etm: ElementToMigrate, tmpl: string, offset: number): Result {
134+
const aliasAttrs = etm.aliasAttrs!;
135+
const aliases = [...aliasAttrs.aliases.keys()];
136+
124137
// includes the mandatory semicolon before as
125-
const condition = etm.attr.value.replace(' as ', '; as ');
138+
let condition = etm.attr.value.replace(' as ', '; as ');
139+
if (aliases.length > 1 || (aliases.length === 1 && condition.indexOf('; as') > -1)) {
140+
// only 1 alias allowed
141+
throw new Error(
142+
'Found more than one alias on your ngIf. Remove one of them and re-run the migration.');
143+
} else if (aliases.length === 1) {
144+
condition += `; as ${aliases[0]}`;
145+
}
126146
const elsePlaceholder = `#${etm.elseAttr!.value}|`;
127147
if (etm.thenAttr !== undefined) {
128148
const thenPlaceholder = `#${etm.thenAttr!.value}|`;

packages/core/schematics/ng-generate/control-flow-migration/types.ts

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,9 @@ export type Result = {
8181
export interface ForAttributes {
8282
forOf: string;
8383
trackBy: string;
84+
}
85+
86+
export interface AliasAttributes {
8487
item: string;
8588
aliases: Map<string, string>;
8689
}
@@ -102,17 +105,20 @@ export class ElementToMigrate {
102105
elseAttr: Attribute|undefined;
103106
thenAttr: Attribute|undefined;
104107
forAttrs: ForAttributes|undefined;
108+
aliasAttrs: AliasAttributes|undefined;
105109
nestCount = 0;
106110
hasLineBreaks = false;
107111

108112
constructor(
109113
el: Element, attr: Attribute, elseAttr: Attribute|undefined = undefined,
110-
thenAttr: Attribute|undefined = undefined, forAttrs: ForAttributes|undefined = undefined) {
114+
thenAttr: Attribute|undefined = undefined, forAttrs: ForAttributes|undefined = undefined,
115+
aliasAttrs: AliasAttributes|undefined = undefined) {
111116
this.el = el;
112117
this.attr = attr;
113118
this.elseAttr = elseAttr;
114119
this.thenAttr = thenAttr;
115120
this.forAttrs = forAttrs;
121+
this.aliasAttrs = aliasAttrs;
116122
}
117123

118124
getCondition(): string {
@@ -319,16 +325,16 @@ export class ElementCollector extends RecursiveVisitor {
319325
const thenAttr =
320326
el.attrs.find(x => x.name === boundngifthenelse || x.name === boundngifthen);
321327
const forAttrs = attr.name === nakedngfor ? this.getForAttrs(el) : undefined;
322-
this.elements.push(new ElementToMigrate(el, attr, elseAttr, thenAttr, forAttrs));
328+
const aliasAttrs = this.getAliasAttrs(el);
329+
this.elements.push(
330+
new ElementToMigrate(el, attr, elseAttr, thenAttr, forAttrs, aliasAttrs));
323331
}
324332
}
325333
}
326334
super.visitElement(el, null);
327335
}
328336

329337
private getForAttrs(el: Element): ForAttributes {
330-
const aliases = new Map<string, string>();
331-
let item = '';
332338
let trackBy = '';
333339
let forOf = '';
334340
for (const attr of el.attrs) {
@@ -338,6 +344,14 @@ export class ElementCollector extends RecursiveVisitor {
338344
if (attr.name === '[ngForOf]') {
339345
forOf = attr.value;
340346
}
347+
}
348+
return {forOf, trackBy};
349+
}
350+
351+
private getAliasAttrs(el: Element): AliasAttributes {
352+
const aliases = new Map<string, string>();
353+
let item = '';
354+
for (const attr of el.attrs) {
341355
if (attr.name.startsWith('let-')) {
342356
if (attr.value === '') {
343357
// item
@@ -348,7 +362,7 @@ export class ElementCollector extends RecursiveVisitor {
348362
}
349363
}
350364
}
351-
return {forOf, trackBy, item, aliases};
365+
return {item, aliases};
352366
}
353367
}
354368

packages/core/schematics/ng-generate/control-flow-migration/util.ts

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -416,10 +416,24 @@ function isI18nTemplate(etm: ElementToMigrate, i18nAttr: Attribute|undefined): b
416416
}
417417

418418
function isRemovableContainer(etm: ElementToMigrate): boolean {
419-
return (etm.el.name === 'ng-container' || etm.el.name === 'ng-template') &&
420-
(etm.el.attrs.length === 1 || etm.forAttrs !== undefined ||
421-
(etm.el.attrs.length === 2 && etm.elseAttr !== undefined) ||
422-
(etm.el.attrs.length === 3 && etm.elseAttr !== undefined && etm.thenAttr !== undefined));
419+
let attrCount = countAttributes(etm);
420+
const safeToRemove = etm.el.attrs.length === attrCount;
421+
return (etm.el.name === 'ng-container' || etm.el.name === 'ng-template') && safeToRemove;
422+
}
423+
424+
function countAttributes(etm: ElementToMigrate): number {
425+
let attrCount = 1;
426+
if (etm.elseAttr !== undefined) {
427+
attrCount++;
428+
}
429+
if (etm.thenAttr !== undefined) {
430+
attrCount++;
431+
}
432+
attrCount += etm.aliasAttrs?.aliases.size ?? 0;
433+
attrCount += etm.aliasAttrs?.item ? 1 : 0;
434+
attrCount += etm.forAttrs?.trackBy ? 1 : 0;
435+
attrCount += etm.forAttrs?.forOf ? 1 : 0;
436+
return attrCount;
423437
}
424438

425439
/**

packages/core/schematics/test/control_flow_migration_spec.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,35 @@ describe('control flow migration', () => {
327327
].join('\n'));
328328
});
329329

330+
it('should migrate an if case as a binding with let variables', async () => {
331+
writeFile('/comp.ts', `
332+
import {Component} from '@angular/core';
333+
import {NgIf} from '@angular/common';
334+
335+
@Component({
336+
templateUrl: './comp.html'
337+
})
338+
class Comp {
339+
show = false;
340+
}
341+
`);
342+
343+
writeFile('/comp.html', [
344+
`<ng-template [ngIf]="data$ | async" let-data="ngIf">`,
345+
` {{ data }}`,
346+
`</ng-template>`,
347+
].join('\n'));
348+
349+
await runMigration();
350+
const content = tree.readContent('/comp.html');
351+
352+
expect(content).toBe([
353+
`@if (data$ | async; as data) {`,
354+
` {{ data }}`,
355+
`}`,
356+
].join('\n'));
357+
});
358+
330359
it('should migrate an if else case as bindings', async () => {
331360
writeFile('/comp.ts', `
332361
import {Component} from '@angular/core';

0 commit comments

Comments
 (0)