Skip to content

Commit f394215

Browse files
tomer953kirjs
authored andcommitted
fix(migrations): detect structural ngTemplateOutlet and ngComponentOutlet
the common-to-standalone migration only matched [ngTemplateOutlet] and [ngComponentOutlet] bindings and missed their structural forms (*ngTemplateOutlet and *ngComponentOutlet). This caused missing imports when removing CommonModule. This change adds structural directive patterns so the migration correctly identifies needed imports. (cherry picked from commit a4f50bd)
1 parent 70bc311 commit f394215

2 files changed

Lines changed: 64 additions & 2 deletions

File tree

packages/core/schematics/migrations/common-to-standalone-migration/util.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,10 @@ const PATTERN_IMPORTS = [
3636
{pattern: /\[ngStyle\]/g, imports: ['NgStyle']},
3737
// Match ngSwitch as property binding [ngSwitch] or attribute ngSwitch="value"
3838
{pattern: /(\[ngSwitch\]|\s+ngSwitch\s*=)/g, imports: ['NgSwitch']},
39-
{pattern: /\[ngTemplateOutlet\]/g, imports: ['NgTemplateOutlet']},
40-
{pattern: /\[ngComponentOutlet\]/g, imports: ['NgComponentOutlet']},
39+
// Match ngTemplateOutlet as structural (*ngTemplateOutlet) or property binding [ngTemplateOutlet]
40+
{pattern: /(\*ngTemplateOutlet\b|\[ngTemplateOutlet\])/g, imports: ['NgTemplateOutlet']},
41+
// Match ngComponentOutlet as structural (*ngComponentOutlet) or property binding [ngComponentOutlet]
42+
{pattern: /(\*ngComponentOutlet\b|\[ngComponentOutlet\])/g, imports: ['NgComponentOutlet']},
4143

4244
// Common pipes
4345
{pattern: /\|\s*async\b/g, imports: ['AsyncPipe']},

packages/core/schematics/test/common_to_standalone_migration_spec.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -495,6 +495,66 @@ describe('Common → standalone imports migration', () => {
495495
expect(content).not.toContain('CommonModule');
496496
expectImportDeclarationToContain(content, 'NgComponentOutlet');
497497
});
498+
499+
it('should migrate NgComponentOutlet with structural directive syntax', async () => {
500+
writeFile(
501+
'/comp.ts',
502+
dedent`
503+
import {Component} from '@angular/core';
504+
import {CommonModule} from '@angular/common';
505+
506+
@Component({
507+
selector: 'app-dynamic-structural',
508+
imports: [CommonModule],
509+
template: \`<ng-container *ngComponentOutlet="dynamicComponent"></ng-container>\`
510+
})
511+
export class DynamicStructuralComponent {
512+
dynamicComponent: any;
513+
}
514+
`,
515+
);
516+
517+
await runMigration();
518+
const content = tree.readContent('/comp.ts');
519+
520+
expectImportsToContain(content, 'NgComponentOutlet');
521+
expect(content).not.toContain('CommonModule');
522+
expectImportDeclarationToContain(content, 'NgComponentOutlet');
523+
});
524+
525+
it('should migrate both NgTemplateOutlet and NgComponentOutlet with mixed syntax', async () => {
526+
writeFile(
527+
'/comp.ts',
528+
dedent`
529+
import {Component} from '@angular/core';
530+
import {CommonModule} from '@angular/common';
531+
532+
@Component({
533+
selector: 'app-mixed-outlets',
534+
imports: [CommonModule],
535+
template: \`
536+
<ng-container *ngTemplateOutlet="template1"></ng-container>
537+
<ng-container [ngTemplateOutlet]="template2"></ng-container>
538+
<ng-container *ngComponentOutlet="component1"></ng-container>
539+
<ng-container [ngComponentOutlet]="component2"></ng-container>
540+
\`
541+
})
542+
export class MixedOutletsComponent {
543+
template1: any;
544+
template2: any;
545+
component1: any;
546+
component2: any;
547+
}
548+
`,
549+
);
550+
551+
await runMigration();
552+
const content = tree.readContent('/comp.ts');
553+
554+
expectImportsToContain(content, 'NgTemplateOutlet', 'NgComponentOutlet');
555+
expect(content).not.toContain('CommonModule');
556+
expectImportDeclarationToContain(content, 'NgComponentOutlet', 'NgTemplateOutlet');
557+
});
498558
});
499559

500560
describe('Pipes', () => {

0 commit comments

Comments
 (0)