Skip to content

Commit

Permalink
docs(core): add documentation for errors NG0955 and NG0956 (#55591)
Browse files Browse the repository at this point in the history
This commit adds detailed description for the errors NG0955 and NG0956.
Those errors correspond to the check introduced in the built-in for loop.

PR Close #55591
  • Loading branch information
pkozlowski-opensource authored and AndrewKushnir committed Apr 30, 2024
1 parent 375e9a7 commit a4a82af
Show file tree
Hide file tree
Showing 6 changed files with 111 additions and 5 deletions.
10 changes: 10 additions & 0 deletions adev/src/app/sub-navigation-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1248,6 +1248,16 @@ const REFERENCE_SUB_NAVIGATION_DATA: NavigationItem[] = [
path: 'errors/NG0912',
contentPath: 'reference/errors/NG0912',
},
{
label: 'NG0955: Track expression resulted in duplicated keys for a given collection',
path: 'errors/NG0955',
contentPath: 'reference/errors/NG0955',
},
{
label: 'NG0956: Tracking expression caused re-creation of the DOM structure',
path: 'errors/NG0956',
contentPath: 'reference/errors/NG0956',
},
{
label: 'NG1001: Argument Not Literal',
path: 'errors/NG1001',
Expand Down
34 changes: 34 additions & 0 deletions adev/src/content/reference/errors/NG0955.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
@name Track expression resulted in duplicated keys for a given collection.
@category runtime
@shortDescription A track expression specified in the `@for` loop evaluated to duplicated keys for a given collection. Duplicate keys are problematic from the correctness point of view and the tracking expression should be updated.

@description
A track expression specified in the `@for` loop evaluated to duplicated keys for a given collection, ex.:

```typescript
@Component({
template: `@for (item of items; track item.value) {{{item.value}}}`,
})
class TestComponent {
items = [{key: 1, value: 'a'}, {key: 2, value: 'b'}, {key: 3, value: 'a'}];
}
```

In the provided example the `item.key` tracking expression will find two duplicate keys `a` (at index 0 and 2).

Duplicate keys are problematic from the correctness point of view: since the `@for` loop can't uniquely identify items it might choose DOM nodes corresponding to _another_ item (with the same key) when performing DOM moves or destroy.

There is also performance penalty associated with duplicated keys - internally Angular must use more sophisticated and slower data structures while repeating over collections with duplicated keys.

## Fixing the error

Change the tracking expression such that it uniquely identifies an item in a collection. In the discussed example the correct track expression would use the unique `key` property (`item.key`):

```typescript
@Component({
template: `@for (item of items; track item.key) {{{item.value}}}`,
})
class TestComponent {
items = [{key: 1, value: 'a'}, {key: 2, value: 'b'}, {key: 3, value: 'a'}];
}
```
60 changes: 60 additions & 0 deletions adev/src/content/reference/errors/NG0956.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
@name Tracking expression caused re-creation of the DOM structure.
@category runtime
@shortDescription The configured tracking expression (track by identity) caused re-creation of the DOM structure for the entire collection, which is problematic from the performance and correctness point of view.

@description
The identity track expression specified in the `@for` loop caused re-creation of the DOM corresponding to _all_ items. This is very expensive operation that commonly occurs when working with immutable data structures, Example:

```typescript
@Component({
template: `
<button (click)="toggleAllDone()">All done!</button>
<ul>
@for (todo of todos; track todo) {
<li>{{todo.task}}</li>
}
</ul>
`,
})
export class App {
todos = [
{ id: 0, task: 'understand trackBy', done: false },
{ id: 1, task: 'use proper tracking expression', done: false },
];

toggleAllDone() {
this.todos = this.todos.map(todo => ({ ...todo, done: true }));
}
}
```

In the provided example the entire list, with all the views (DOM nodes, Angular directives, components, queries etc.) are re-created (!) after toggling the "done" status of items. Here a relatively inexpensive binding update to the `done` property would suffice.

Apart from having hight performance penalty, re-creating DOM tree result in loose of state in the DOM elements (ex.: focus, text selection, sites loaded in an iframe etc.).

## Fixing the error

Change the tracking expression such that it uniquely identifies an item in a collection, regardless of its object identity. In the discussed example the correct track expression would use the unique `id` property (`item.id`):

```typescript
@Component({
template: `
<button (click)="toggleAllDone()">All done!</button>
<ul>
@for (todo of todos; track todo.id) {
<li>{{todo.task}}</li>
}
</ul>
`,
})
export class App {
todos = [
{ id: 0, task: 'understand trackBy', done: false },
{ id: 1, task: 'use proper tracking expression', done: false },
];

toggleAllDone() {
this.todos = this.todos.map(todo => ({ ...todo, done: true }));
}
}
```
4 changes: 3 additions & 1 deletion adev/src/content/reference/errors/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,13 @@
| `NG0507` | [HTML content was altered after SSR](errors/NG0507) |
| `NG0910` | [Unsafe bindings on an iframe element](errors/NG0910) |
| `NG0912` | [Component ID generation collision](errors/NG0912) |
| `NG0955` | [Track expression resulted in duplicated keys for a given collection](errors/NG0955) |
| `NG0956` | [Tracking expression caused re-creation of the DOM structure](errors/NG0956) |
| `NG01101` | [Wrong Async Validator Return Type](errors/NG01101) |
| `NG01203` | [Missing value accessor](errors/NG01203) |
| `NG02200` | [Missing Iterable Differ](errors/NG02200) |
| `NG02800` | [JSONP support in HttpClient configuration](errors/NG02800) |
| `NG05000` | [Hydration with unsupported Zone.js instance.](errors/NG05000) |
| `NG05000` | [Hydration with unsupported Zone.js instance.](errors/NG05000) |
| `NG05104` | [Root element was not found.](errors/NG05104) |

## Compiler errors
Expand Down
4 changes: 2 additions & 2 deletions goldens/public-api/core/errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,9 @@ export const enum RuntimeErrorCode {
// (undocumented)
INVALID_SKIP_HYDRATION_HOST = -504,
// (undocumented)
LOOP_TRACK_DUPLICATE_KEYS = 955,
LOOP_TRACK_DUPLICATE_KEYS = -955,
// (undocumented)
LOOP_TRACK_RECREATE = 956,
LOOP_TRACK_RECREATE = -956,
// (undocumented)
MISSING_DOCUMENT = 210,
// (undocumented)
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,8 @@ export const enum RuntimeErrorCode {
OUTPUT_REF_DESTROYED = 953,

// Repeater errors
LOOP_TRACK_DUPLICATE_KEYS = 955,
LOOP_TRACK_RECREATE = 956,
LOOP_TRACK_DUPLICATE_KEYS = -955,
LOOP_TRACK_RECREATE = -956,

// Runtime dependency tracker errors
RUNTIME_DEPS_INVALID_IMPORTED_TYPE = 1000,
Expand Down

0 comments on commit a4a82af

Please sign in to comment.