diff --git a/packages/core/schematics/ng-generate/control-flow-migration/util.ts b/packages/core/schematics/ng-generate/control-flow-migration/util.ts index c737a23b0548..4c3fd969adce 100644 --- a/packages/core/schematics/ng-generate/control-flow-migration/util.ts +++ b/packages/core/schematics/ng-generate/control-flow-migration/util.ts @@ -385,10 +385,6 @@ export function calculateNesting( } } -function escapeRegExp(val: string) { - return val.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string -} - /** * determines if a given template string contains line breaks */ @@ -425,20 +421,48 @@ export function getTemplates(template: string): Map { const visitor = new TemplateCollector(); visitAll(visitor, parsed.tree.rootNodes); - // count usages of each ng-template for (let [key, tmpl] of visitor.templates) { - const escapeKey = escapeRegExp(key.slice(1)); - const regex = new RegExp(`[^a-zA-Z0-9-<(\']${escapeKey}\\W`, 'gm'); - const matches = template.match(regex); - tmpl.count = matches?.length ?? 0; + tmpl.count = countTemplateUsage(parsed.tree.rootNodes, key); tmpl.generateContents(template); } - return visitor.templates; } return new Map(); } +function countTemplateUsage(nodes: any[], templateName: string): number { + let count = 0; + let isReferencedInTemplateOutlet = false; + + for (const node of nodes) { + if (node.attrs) { + for (const attr of node.attrs) { + if (attr.name === '*ngTemplateOutlet' && attr.value === templateName.slice(1)) { + isReferencedInTemplateOutlet = true; + break; + } + + if (attr.name.trim() === templateName) { + count++; + } + } + } + + if (node.children) { + if (node.name === 'for') { + for (const child of node.children) { + if (child.value?.includes(templateName.slice(1))) { + count++; + } + } + } + count += countTemplateUsage(node.children, templateName); + } + } + + return isReferencedInTemplateOutlet ? count + 2 : count; +} + export function updateTemplates( template: string, templates: Map, @@ -497,8 +521,12 @@ export function processNgTemplates( } else { template = template.replace(replaceRegex, t.children); } - // the +1 accounts for the t.count's counting of the original template - if (t.count === matches.length + 1 && safeToRemove) { + + const dist = matches.filter( + (obj, index, self) => index === self.findIndex((t) => t.input === obj.input), + ); + + if ((t.count === dist.length || t.count - matches.length === 1) && safeToRemove) { const refsInComponentFile = getViewChildOrViewChildrenNames(sourceFile); if (refsInComponentFile?.length > 0) { const templateRefs = getTemplateReferences(template); diff --git a/packages/core/schematics/test/control_flow_migration_spec.ts b/packages/core/schematics/test/control_flow_migration_spec.ts index 46cbb5736a94..1f97d204ee0f 100644 --- a/packages/core/schematics/test/control_flow_migration_spec.ts +++ b/packages/core/schematics/test/control_flow_migration_spec.ts @@ -6932,5 +6932,38 @@ describe('control flow migration', () => { const content = tree.readContent('/comp.ts'); expect(content).not.toContain(''); }); + + it('should remove ng-template reference when use in if-else block', async () => { + writeFile( + '/comp.ts', + ` + import {Component} from '@angular/core'; + + @Component({ + templateUrl: './comp.html' + }) + class Comp { + } + `, + ); + + writeFile( + '/comp.html', + [ + `
`, + `
`, + `
content
`, + `
`, + ``, + `
loading
`, + `
`, + `
`, + ].join('\n'), + ); + + await runMigration(); + const content = tree.readContent('/comp.html'); + expect(content).not.toContain(''); + }); }); });