Skip to content

Commit ac9cd61

Browse files
AnthonyFr75AndrewKushnir
authored andcommitted
fix(migrations): control flow migration fails for async pipe with unboxing of observable (#52756) (#52972)
Update control flow syntax to use 'as' for proper async pipe handling, accounting for variable whitespace before 'let'. Fixes #52756 PR Close #52972
1 parent 7f1c557 commit ac9cd61

2 files changed

Lines changed: 75 additions & 4 deletions

File tree

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

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,10 @@ function migrateNgIf(etm: ElementToMigrate, tmpl: string, offset: number): Resul
8484
function buildIfBlock(etm: ElementToMigrate, tmpl: string, offset: number): Result {
8585
// includes the mandatory semicolon before as
8686
const lbString = etm.hasLineBreaks ? '\n' : '';
87-
const condition = etm.attr.value.replace(' as ', '; as ');
87+
const condition = etm.attr.value
88+
.replace(' as ', '; as ')
89+
// replace 'let' with 'as' whatever spaces are between ; and 'let'
90+
.replace(/;\s*let/g, '; as');
8891

8992
const originals = getOriginals(etm, tmpl, offset);
9093

@@ -106,7 +109,10 @@ function buildIfBlock(etm: ElementToMigrate, tmpl: string, offset: number): Resu
106109
function buildStandardIfElseBlock(
107110
etm: ElementToMigrate, tmpl: string, elseString: string, offset: number): Result {
108111
// includes the mandatory semicolon before as
109-
const condition = etm.getCondition(elseString).replace(' as ', '; as ');
112+
const condition = etm.getCondition(elseString)
113+
.replace(' as ', '; as ')
114+
// replace 'let' with 'as' whatever spaces are between ; and 'let'
115+
.replace(/;\s*let/g, '; as');
110116
const elsePlaceholder = `#${etm.getTemplateName(elseString)}|`;
111117
return buildIfElseBlock(etm, tmpl, condition, elsePlaceholder, offset);
112118
}
@@ -151,7 +157,10 @@ function buildStandardIfThenElseBlock(
151157
etm: ElementToMigrate, tmpl: string, thenString: string, elseString: string,
152158
offset: number): Result {
153159
// includes the mandatory semicolon before as
154-
const condition = etm.getCondition(thenString).replace(' as ', '; as ');
160+
const condition = etm.getCondition(thenString)
161+
.replace(' as ', '; as ')
162+
// replace 'let' with 'as' whatever spaces are between ; and 'let'
163+
.replace(/;\s*let/g, '; as');
155164
const thenPlaceholder = `#${etm.getTemplateName(thenString, elseString)}|`;
156165
const elsePlaceholder = `#${etm.getTemplateName(elseString)}|`;
157166
return buildIfThenElseBlock(etm, tmpl, condition, thenPlaceholder, elsePlaceholder, offset);

packages/core/schematics/test/control_flow_migration_spec.ts

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,6 @@ describe('control flow migration', () => {
100100
} catch (e: any) {
101101
error = e.message;
102102
}
103-
104103
expect(error).toBe('Cannot run control flow migration outside of the current project.');
105104
});
106105

@@ -3513,5 +3512,68 @@ describe('control flow migration', () => {
35133512
expect(content).toContain(
35143513
'template: `<div>@if (toggle) {<div>@if (show) {<span>shrug</span>}</div>}</div>`');
35153514
});
3515+
3516+
it('should update let value in a build if block to as value for the new control flow',
3517+
async () => {
3518+
writeFile('/comp.ts', `
3519+
import {Component} from '@angular/core';
3520+
import {NgIf} from '@angular/common';
3521+
3522+
@Component({
3523+
imports: [NgIf],
3524+
template: \`<ng-container *ngIf="value$ | async; let value"> {{value}} </ng-container>\`
3525+
})
3526+
class Comp {
3527+
value$ = of('Rica');
3528+
}
3529+
`);
3530+
3531+
await runMigration();
3532+
const content = tree.readContent('/comp.ts');
3533+
expect(content).toContain('template: `@if (value$ | async; as value) { {{value}} }`');
3534+
});
3535+
3536+
it('should update let value in a standard if else block to as value for the new control flow',
3537+
async () => {
3538+
writeFile('/comp.ts', `
3539+
import {Component} from '@angular/core';
3540+
import {CommonModule} from '@angular/common';
3541+
3542+
@Component({
3543+
imports: [CommonModule],
3544+
template: \`<div *ngIf="(logStatus$ | async)?.name; let userName; else loggedOut"> Hello {{ userName }} ! </div><ng-template #loggedOut></ng-template>\`
3545+
})
3546+
class Comp {
3547+
logStatus$ = of({ name: 'Robert' });
3548+
}
3549+
`);
3550+
3551+
await runMigration();
3552+
const content = tree.readContent('/comp.ts');
3553+
expect(content).toContain(
3554+
'template: `@if ((logStatus$ | async)?.name; as userName) {<div> Hello {{ userName }} ! </div>} @else {}`');
3555+
});
3556+
3557+
it('should update let value in a standard if else, then block to as value for the new control flow',
3558+
async () => {
3559+
writeFile('/comp.ts', `
3560+
import {Component} from '@angular/core';
3561+
import {CommonModule} from '@angular/common';
3562+
3563+
@Component({
3564+
imports: [CommonModule],
3565+
template: \`<div *ngIf="isLoggedIn$ | async; let logIn; then loggedIn; else loggedOut"></div><ng-template #loggedIn>Log In</ng-template>
3566+
<ng-template #loggedOut>Log Out</ng-template>\`
3567+
})
3568+
class Comp {
3569+
isLoggedIn$ = of(true);
3570+
}
3571+
`);
3572+
3573+
await runMigration();
3574+
const content = tree.readContent('/comp.ts');
3575+
expect(content).toContain(
3576+
'template: `@if (isLoggedIn$ | async; as logIn) {\n Log In\n} @else {\n Log Out\n}`');
3577+
});
35163578
});
35173579
});

0 commit comments

Comments
 (0)