Skip to content

Commit dffeac8

Browse files
fix(migrations): cf migration removes unnecessary bound ngifelse attribute (#53236)
this removes a no longer necessary attribute in bound ngIfElse cases. fixes: #53230 PR Close #53236
1 parent b2e3b3e commit dffeac8

File tree

3 files changed

+99
-18
lines changed

3 files changed

+99
-18
lines changed

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

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,35 @@ export class ElementToMigrate {
141141
.trim();
142142
}
143143

144+
getValueEnd(offset: number): number {
145+
return (this.attr.valueSpan ? (this.attr.valueSpan.end.offset + 1) :
146+
this.attr.keySpan!.end.offset) -
147+
offset;
148+
}
149+
150+
hasChildren(): boolean {
151+
return this.el.children.length > 0;
152+
}
153+
154+
getChildSpan(offset: number): {childStart: number, childEnd: number} {
155+
const childStart = this.el.children[0].sourceSpan.start.offset - offset;
156+
const childEnd = this.el.children[this.el.children.length - 1].sourceSpan.end.offset - offset;
157+
return {childStart, childEnd};
158+
}
159+
160+
shouldRemoveElseAttr(): boolean {
161+
return (this.el.name === 'ng-template' || this.el.name === 'ng-container') &&
162+
this.elseAttr !== undefined;
163+
}
164+
165+
getElseAttrStr(): string {
166+
if (this.elseAttr !== undefined) {
167+
const elseValStr = this.elseAttr.value !== '' ? `="${this.elseAttr.value}"` : '';
168+
return `${this.elseAttr.name}${elseValStr}`;
169+
}
170+
return '';
171+
}
172+
144173
start(offset: number): number {
145174
return this.el.sourceSpan?.start.offset - offset;
146175
}

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

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -428,35 +428,40 @@ function isRemovableContainer(etm: ElementToMigrate): boolean {
428428
export function getMainBlock(etm: ElementToMigrate, tmpl: string, offset: number):
429429
{start: string, middle: string, end: string} {
430430
const i18nAttr = etm.el.attrs.find(x => x.name === 'i18n');
431+
432+
// removable containers are ng-templates or ng-containers that no longer need to exist
433+
// post migration
431434
if (isRemovableContainer(etm)) {
432-
// this is the case where we're migrating and there's no need to keep the ng-container
433-
const childStart = etm.el.children[0].sourceSpan.start.offset - offset;
434-
const childEnd = etm.el.children[etm.el.children.length - 1].sourceSpan.end.offset - offset;
435-
const middle = tmpl.slice(childStart, childEnd);
436-
return {start: '', middle, end: ''};
435+
const {childStart, childEnd} = etm.getChildSpan(offset);
436+
return {start: '', middle: tmpl.slice(childStart, childEnd), end: ''};
437437
} else if (isI18nTemplate(etm, i18nAttr)) {
438-
const childStart = etm.el.children[0].sourceSpan.start.offset - offset;
439-
const childEnd = etm.el.children[etm.el.children.length - 1].sourceSpan.end.offset - offset;
438+
// here we're removing an ng-template used for control flow and i18n and
439+
// converting it to an ng-container with i18n
440+
const {childStart, childEnd} = etm.getChildSpan(offset);
440441
return generatei18nContainer(i18nAttr!, tmpl.slice(childStart, childEnd));
441442
}
442443

444+
// the index of the start of the attribute adjusting for offset shift
443445
const attrStart = etm.attr.keySpan!.start.offset - 1 - offset;
444-
const valEnd =
445-
(etm.attr.valueSpan ? (etm.attr.valueSpan.end.offset + 1) : etm.attr.keySpan!.end.offset) -
446-
offset;
447446

448-
let childStart = valEnd;
449-
let childEnd = valEnd;
447+
// the index of the very end of the attribute value adjusted for offset shift
448+
const valEnd = etm.getValueEnd(offset);
450449

451-
if (etm.el.children.length > 0) {
452-
childStart = etm.el.children[0].sourceSpan.start.offset - offset;
453-
childEnd = etm.el.children[etm.el.children.length - 1].sourceSpan.end.offset - offset;
454-
}
450+
// the index of the children start and end span, if they exist. Otherwise use the value end.
451+
const {childStart, childEnd} =
452+
etm.hasChildren() ? etm.getChildSpan(offset) : {childStart: valEnd, childEnd: valEnd};
455453

456-
let start = tmpl.slice(etm.start(offset), attrStart);
457-
start += tmpl.slice(valEnd, childStart);
454+
// the beginning of the updated string in the main block, for example: <div some="attributes">
455+
let start = tmpl.slice(etm.start(offset), attrStart) + tmpl.slice(valEnd, childStart);
456+
457+
if (etm.shouldRemoveElseAttr()) {
458+
// this removes a bound ngIfElse attribute that's no longer needed
459+
start = start.replace(etm.getElseAttrStr(), '');
460+
}
458461

462+
// the middle is the actual contents of the element
459463
const middle = tmpl.slice(childStart, childEnd);
464+
// the end is the closing part of the element, example: </div>
460465
const end = tmpl.slice(childEnd, etm.end(offset));
461466

462467
return {start, middle, end};

packages/core/schematics/test/control_flow_migration_spec.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3445,6 +3445,53 @@ describe('control flow migration', () => {
34453445
expect(content).toBe(result);
34463446
});
34473447

3448+
it('should migrate template with ngTemplateOutlet on if else template', async () => {
3449+
writeFile('/comp.ts', `
3450+
import {Component} from '@angular/core';
3451+
import {NgIf} from '@angular/common';
3452+
3453+
@Component({
3454+
selector: 'declare-comp',
3455+
templateUrl: 'comp.html',
3456+
})
3457+
class DeclareComp {}
3458+
`);
3459+
3460+
writeFile('/comp.html', [
3461+
`<ng-template`,
3462+
` [ngIf]="false"`,
3463+
` [ngIfElse]="fooTemplate"`,
3464+
` [ngTemplateOutlet]="barTemplate"`,
3465+
`>`,
3466+
`</ng-template>`,
3467+
`<ng-template #fooTemplate>`,
3468+
` Foo`,
3469+
`</ng-template>`,
3470+
`<ng-template #barTemplate>`,
3471+
` Bar`,
3472+
`</ng-template>`,
3473+
].join('\n'));
3474+
3475+
await runMigration();
3476+
const content = tree.readContent('/comp.html');
3477+
3478+
const result = [
3479+
`@if (false) {`,
3480+
` <ng-template`,
3481+
` [ngTemplateOutlet]="barTemplate"`,
3482+
` >`,
3483+
` </ng-template>`,
3484+
`} @else {`,
3485+
` Foo`,
3486+
`}`,
3487+
`<ng-template #barTemplate>`,
3488+
` Bar`,
3489+
`</ng-template>`,
3490+
].join('\n');
3491+
3492+
expect(content).toBe(result);
3493+
});
3494+
34483495
it('should move templates after they were migrated to new syntax', async () => {
34493496
writeFile('/comp.ts', `
34503497
import {Component} from '@angular/core';

0 commit comments

Comments
 (0)