Skip to content

Commit

Permalink
fix(core): handle trackBy and aliased index in control flow migration (
Browse files Browse the repository at this point in the history
…#52423)

Currently, the migration always use `$index` in the migrated trackBy function, whereas this variable might be aliased.
The compiler then errors with:

```
error TS2339: Property '$index' does not exist on type 'UsersComponent'.

110 @for (user of users; track byId($index, user); let i = $index) {
```

This commit updates the migration to use the aliased index if there is one.

PR Close #52423
  • Loading branch information
cexbrayat authored and alxhub committed Oct 27, 2023
1 parent ce54ead commit 4461cef
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 0 deletions.
Expand Up @@ -328,6 +328,7 @@ function migrateNgFor(etm: ElementToMigrate, tmpl: string, offset: number): Resu
const condition = parts[0].replace('let ', '');
const loopVar = condition.split(' of ')[0];
let trackBy = loopVar;
let aliasedIndex: string|null = null;
for (let i = 1; i < parts.length; i++) {
const part = parts[i].trim();

Expand All @@ -343,15 +344,29 @@ function migrateNgFor(etm: ElementToMigrate, tmpl: string, offset: number): Resu
const aliasParts = part.split('=');
// -> 'let myIndex = $index'
aliases.push(` ${aliasParts[0].trim()} = $${aliasParts[1].trim()}`);
// if the aliased variable is the index, then we store it
if (aliasParts[1].trim() === 'index') {
// 'let myIndex' -> 'myIndex'
aliasedIndex = aliasParts[0].trim().split(/\s+as\s+/)[1];
}
}
// declared with `index as myIndex`
if (part.match(aliasWithAsRegexp)) {
// 'index as myIndex' -> ['index', 'myIndex']
const aliasParts = part.split(/\s+as\s+/);
// -> 'let myIndex = $index'
aliases.push(` let ${aliasParts[1].trim()} = $${aliasParts[0].trim()}`);
// if the aliased variable is the index, then we store it
if (aliasParts[0].trim() === 'index') {
aliasedIndex = aliasParts[1].trim();
}
}
}
// if an alias has been defined for the index, then the trackBy function must use it
if (aliasedIndex !== null && trackBy !== loopVar) {
// byId($index, user) -> byId(i, user)
trackBy = trackBy.replace('$index', aliasedIndex);
}

const aliasStr = (aliases.length > 0) ? `;${aliases.join(';')}` : '';

Expand Down
25 changes: 25 additions & 0 deletions packages/core/schematics/test/control_flow_migration_spec.ts
Expand Up @@ -920,6 +920,31 @@ describe('control flow migration', () => {
'template: `<ul>@for (itm of items; track itm; let myIndex = $index) {<li>{{itm.text}}</li>}</ul>`');
});

it('should migrate with a trackBy function and an aliased index', async () => {
writeFile('/comp.ts', `
import {Component} from '@angular/core';
import {NgFor} from '@angular/common';
interface Item {
id: number;
text: string;
}
@Component({
imports: [NgFor],
template: \`<ul><li *ngFor="let itm of items; trackBy: trackMeFn; index as i">{{itm.text}}</li></ul>\`
})
class Comp {
items: Item[] = [{id: 1, text: 'blah'},{id: 2, text: 'stuff'}];
}
`);

await runMigration();
const content = tree.readContent('/comp.ts');

expect(content).toContain(
'template: `<ul>@for (itm of items; track trackMeFn(i, itm); let i = $index) {<li>{{itm.text}}</li>}</ul>`');
});

it('should migrate with multiple aliases', async () => {
writeFile('/comp.ts', `
import {Component} from '@angular/core';
Expand Down

0 comments on commit 4461cef

Please sign in to comment.