Skip to content

control-flow migration misparses else template references starting with then, causing content loss #69914

Description

@fgfb

Which @angular/* package(s) are the source of the bug?

core

Is this a regression?

Yes

Description

The control flow migration incorrectly interpreting an else template reference whose name begins with then

This causes the migration to:

  • take the then + else migration path instead of the else path,
  • drop the original contents of the *ngIf host,
  • generate an invalid placeholder that later becomes:
<ng-template [ngTemplateOutlet]=""></ng-template>

The migration completes without any warnings, resulting in silent content loss.

Reproduction:

Input:

<ng-container *ngIf="data.teamMember; else thenBlock">
  <h2>Hello team member!</h2>
</ng-container>

<ng-template #thenBlock>
  <h2>No team member</h2>
</ng-template>

Run the control flow migration.

Expected behavior

The migration should produce:

@if (data.teamMember) {
  <h2>Hello team member!</h2>
} @else {
  <h2>No team member</h2>
}

Actual behavior:

The @else branch is migrated correctly, but the @if branch loses its original content and is replaced with an empty template outlet similar to:

@if (data.teamMember) {
  <ng-template [ngTemplateOutlet]=""></ng-template>
} @else {
  <h2>No team member</h2>
}

Root cause:

The migration determines whether an *ngIf uses then/else by regex matching the raw microsyntax string.

In ifs.ts:

const matchThen = etm.attr.value.match(/[^\w\d];?\s*then/gm);
const matchElse = etm.attr.value.match(/[^\w\d];?\s*else/gm);

the regex matches:

  • the real else keyword
  • the "then" prefix of the template reference thenBlock

As a result, the migration incorrectly assumes both then and else are present and follows the then + else migration path.

Later, getTemplateName() attempts to extract the template names using these match locations. Since the "then" occurs after the real else, it ends up calling slice(start, end) with start > end, producing an empty template name.

This empty placeholder is never resolved and is ultimately emitted as:

<ng-template [ngTemplateOutlet]=""></ng-template>

Please provide a link to a minimal reproduction of the bug

No response

Please provide the exception or error you saw


Please provide the environment you discovered this bug in (run ng version)

Angular CLI: 20.3.32
Node: 22.21.0
Package manager: npm 10.9.4

Anything else?

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: migrationsIssues related to `ng update`/`ng generate` migrationsgemini-triagedLabel noting that an issue has been triaged by gemini

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions