Skip to content

Commit 8a52674

Browse files
fix(migrations): Update CF migration to skip templates with duplicate ng-template names (#53204)
This adds a message to the console and skips any templates that detect duplicate ng-template names in the same component. fixes: #53169 PR Close #53204
1 parent 53912fd commit 8a52674

File tree

4 files changed

+73
-31
lines changed

4 files changed

+73
-31
lines changed

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,11 @@ export function migrateTemplate(
2929
const forResult = migrateFor(ifResult.migrated);
3030
const switchResult = migrateSwitch(forResult.migrated);
3131
const caseResult = migrateCase(switchResult.migrated);
32-
migrated = processNgTemplates(caseResult.migrated);
32+
const templateResult = processNgTemplates(caseResult.migrated);
33+
if (templateResult.err !== undefined) {
34+
return {migrated: template, errors: [{type: 'template', error: templateResult.err}]};
35+
}
36+
migrated = templateResult.migrated;
3337
const changed =
3438
ifResult.changed || forResult.changed || switchResult.changed || caseResult.changed;
3539
if (format && changed) {

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -338,9 +338,13 @@ export class TemplateCollector extends RecursiveVisitor {
338338
templateAttr = attr;
339339
}
340340
}
341-
if (templateAttr !== null) {
342-
this.elements.push(new ElementToMigrate(el, templateAttr));
341+
if (templateAttr !== null && !this.templates.has(templateAttr.name)) {
343342
this.templates.set(templateAttr.name, new Template(el, templateAttr.name, i18n));
343+
this.elements.push(new ElementToMigrate(el, templateAttr));
344+
} else if (templateAttr !== null) {
345+
throw new Error(
346+
`A duplicate ng-template name "${templateAttr.name}" was found. ` +
347+
`The control flow migration requires unique ng-template names within a component.`);
344348
}
345349
}
346350
super.visitElement(el, null);

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

Lines changed: 32 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -309,40 +309,44 @@ function wrapIntoI18nContainer(i18nAttr: Attribute, content: string) {
309309
/**
310310
* Counts, replaces, and removes any necessary ng-templates post control flow migration
311311
*/
312-
export function processNgTemplates(template: string): string {
312+
export function processNgTemplates(template: string): {migrated: string, err: Error|undefined} {
313313
// count usage
314-
const templates = getTemplates(template);
315-
316-
// swap placeholders and remove
317-
for (const [name, t] of templates) {
318-
const replaceRegex = new RegExp(`${name}\\|`, 'g');
319-
const forRegex = new RegExp(`${name}\\#`, 'g');
320-
const forMatches = [...template.matchAll(forRegex)];
321-
const matches = [...forMatches, ...template.matchAll(replaceRegex)];
322-
let safeToRemove = true;
323-
if (matches.length > 0) {
324-
if (t.i18n !== null) {
325-
const container = wrapIntoI18nContainer(t.i18n, t.children);
326-
template = template.replace(replaceRegex, container);
327-
} else if (t.children.trim() === '' && t.isNgTemplateOutlet) {
328-
template = template.replace(replaceRegex, t.generateTemplateOutlet());
329-
} else if (forMatches.length > 0) {
330-
if (t.count === 2) {
331-
template = template.replace(forRegex, t.children);
314+
try {
315+
const templates = getTemplates(template);
316+
317+
// swap placeholders and remove
318+
for (const [name, t] of templates) {
319+
const replaceRegex = new RegExp(`${name}\\|`, 'g');
320+
const forRegex = new RegExp(`${name}\\#`, 'g');
321+
const forMatches = [...template.matchAll(forRegex)];
322+
const matches = [...forMatches, ...template.matchAll(replaceRegex)];
323+
let safeToRemove = true;
324+
if (matches.length > 0) {
325+
if (t.i18n !== null) {
326+
const container = wrapIntoI18nContainer(t.i18n, t.children);
327+
template = template.replace(replaceRegex, container);
328+
} else if (t.children.trim() === '' && t.isNgTemplateOutlet) {
329+
template = template.replace(replaceRegex, t.generateTemplateOutlet());
330+
} else if (forMatches.length > 0) {
331+
if (t.count === 2) {
332+
template = template.replace(forRegex, t.children);
333+
} else {
334+
template = template.replace(forRegex, t.generateTemplateOutlet());
335+
safeToRemove = false;
336+
}
332337
} else {
333-
template = template.replace(forRegex, t.generateTemplateOutlet());
334-
safeToRemove = false;
338+
template = template.replace(replaceRegex, t.children);
339+
}
340+
// the +1 accounts for the t.count's counting of the original template
341+
if (t.count === matches.length + 1 && safeToRemove) {
342+
template = template.replace(t.contents, '');
335343
}
336-
} else {
337-
template = template.replace(replaceRegex, t.children);
338-
}
339-
// the +1 accounts for the t.count's counting of the original template
340-
if (t.count === matches.length + 1 && safeToRemove) {
341-
template = template.replace(t.contents, '');
342344
}
343345
}
346+
return {migrated: template, err: undefined};
347+
} catch (err) {
348+
return {migrated: template, err: err as Error};
344349
}
345-
return template;
346350
}
347351

348352
/**

packages/core/schematics/test/control_flow_migration_spec.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3190,6 +3190,36 @@ describe('control flow migration', () => {
31903190
expect(warnOutput.join(' '))
31913191
.toContain('IMPORTANT! This migration is in developer preview. Use with caution.');
31923192
});
3193+
3194+
it('should log a migration error when duplicate ng-template names are detected', async () => {
3195+
writeFile('/comp.ts', `
3196+
import {Component} from '@angular/core';
3197+
import {NgIf} from '@angular/common';
3198+
3199+
@Component({
3200+
imports: [NgIf],
3201+
templateUrl: './comp.html'
3202+
})
3203+
class Comp {
3204+
toggle = false;
3205+
}
3206+
`);
3207+
3208+
writeFile('./comp.html', [
3209+
`<div *ngIf="show; else elseTmpl">Content</div>`,
3210+
`<div *ngIf="hide; else elseTmpl">Content</div>`,
3211+
`<ng-template #elseTmpl>Else Content</ng-template>`,
3212+
`<ng-template #elseTmpl>Duplicate</ng-template>`,
3213+
].join('\n'));
3214+
3215+
await runMigration();
3216+
tree.readContent('/comp.ts');
3217+
3218+
expect(warnOutput.join(' '))
3219+
.toContain(
3220+
`A duplicate ng-template name "#elseTmpl" was found. ` +
3221+
`The control flow migration requires unique ng-template names within a component.`);
3222+
});
31933223
});
31943224

31953225
describe('template', () => {

0 commit comments

Comments
 (0)