Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 40 additions & 12 deletions packages/core/schematics/ng-generate/control-flow-migration/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down Expand Up @@ -425,20 +421,48 @@ export function getTemplates(template: string): Map<string, Template> {
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<string, Template>();
}

function countTemplateUsage(nodes: any[], templateName: string): number {
Comment thread
aparzi marked this conversation as resolved.
Outdated
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<string, Template>,
Expand Down Expand Up @@ -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);
Expand Down
33 changes: 33 additions & 0 deletions packages/core/schematics/test/control_flow_migration_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6932,5 +6932,38 @@ describe('control flow migration', () => {
const content = tree.readContent('/comp.ts');
expect(content).not.toContain('<ng-template #contentTemplate>');
});

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',
[
`<div>`,
`<div *ngIf="param; else loading">`,
`<div>content</div>`,
`</div>`,
`<ng-template #loading>`,
`<div>loading</div>`,
`</ng-template>`,
`</div>`,
].join('\n'),
);

await runMigration();
const content = tree.readContent('/comp.html');
expect(content).not.toContain('<ng-template #loading>');
});
});
});