diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2e78124783414..f0fd98cc2e02a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,47 @@
+
+# 17.1.0-next.0 (2023-11-15)
+### compiler-cli
+| Commit | Type | Description |
+| -- | -- | -- |
+| [56a76d73e0](https://github.com/angular/angular/commit/56a76d73e037aeea1975808d5c51608fd23d4fa6) | fix | modify `getConstructorDependencies` helper to work with reflection host after the previous change ([#52215](https://github.com/angular/angular/pull/52215)) |
+### core
+| Commit | Type | Description |
+| -- | -- | -- |
+| [94096c6ede](https://github.com/angular/angular/commit/94096c6ede67436a349ae07901f2bb418bf9f461) | feat | support TypeScript 5.3 ([#52572](https://github.com/angular/angular/pull/52572)) |
+| [bdd61c768a](https://github.com/angular/angular/commit/bdd61c768a28b56c68634b99c036986499829f45) | fix | replace assertion with more intentional error ([#52234](https://github.com/angular/angular/pull/52234)) |
+### router
+| Commit | Type | Description |
+| -- | -- | -- |
+| [726530a9af](https://github.com/angular/angular/commit/726530a9af9c8daf7295cc3548f24e70f380d70e) | feat | Allow `onSameUrlNavigation: 'ignore'` in `navigateByUrl` ([#52265](https://github.com/angular/angular/pull/52265)) |
+
+
+
+
+# 17.0.3 (2023-11-15)
+### animations
+| Commit | Type | Description |
+| -- | -- | -- |
+| [f5872c9921](https://github.com/angular/angular/commit/f5872c992181a2c231890b83f92ec03ec9606802) | fix | prevent the AsyncAnimationRenderer from calling the delegate when there is no element. ([#52570](https://github.com/angular/angular/pull/52570)) |
+### core
+| Commit | Type | Description |
+| -- | -- | -- |
+| [6a1d4ed667](https://github.com/angular/angular/commit/6a1d4ed6670f5965a654e40997aa266a99925f50) | fix | handle non-container environment injector cases ([#52774](https://github.com/angular/angular/pull/52774)) |
+| [5de7575be8](https://github.com/angular/angular/commit/5de7575be83b9829e65ad245034ee7ab1d966044) | fix | reset cached scope for components that were overridden using TestBed ([#52916](https://github.com/angular/angular/pull/52916)) |
+### http
+| Commit | Type | Description |
+| -- | -- | -- |
+| [7c066a4af4](https://github.com/angular/angular/commit/7c066a4af4faae25ee722c19576c63c3833066ee) | fix | Use the response `content-type` to set the blob `type`. ([#52840](https://github.com/angular/angular/pull/52840)) |
+### migrations
+| Commit | Type | Description |
+| -- | -- | -- |
+| [4e200bf13b](https://github.com/angular/angular/commit/4e200bf13b284fa89bbb0854cbb85dc8fe94d8bb) | fix | Add missing support for ngForOf ([#52903](https://github.com/angular/angular/pull/52903)) |
+| [d033540d0f](https://github.com/angular/angular/commit/d033540d0f874a7a05b79c00e3151ed076fa71c3) | fix | Add support for bound versions of NgIfElse and NgIfThenElse ([#52869](https://github.com/angular/angular/pull/52869)) |
+| [aa2d815648](https://github.com/angular/angular/commit/aa2d815648dbf3303cfe72bf976a4a87de406ee0) | fix | Add support for removing imports post migration ([#52763](https://github.com/angular/angular/pull/52763)) |
+| [3831942771](https://github.com/angular/angular/commit/38319427711f4dab4e4d64ff48aecc7727085031) | fix | Fixes issue with multiple if elses with same template ([#52863](https://github.com/angular/angular/pull/52863)) |
+| [e1f84a31dc](https://github.com/angular/angular/commit/e1f84a31dcac413251329c3b695a253234c6aae6) | fix | passed in paths will be respected in nx workspaces ([#52796](https://github.com/angular/angular/pull/52796)) |
+
+
+
# 17.0.2 (2023-11-09)
### compiler-cli
diff --git a/devtools/projects/shell-browser/src/manifest/manifest.chrome.json b/devtools/projects/shell-browser/src/manifest/manifest.chrome.json
index 68106d51a9b1a..f1b6e36cb52b8 100644
--- a/devtools/projects/shell-browser/src/manifest/manifest.chrome.json
+++ b/devtools/projects/shell-browser/src/manifest/manifest.chrome.json
@@ -3,8 +3,8 @@
"short_name": "Angular DevTools",
"name": "Angular DevTools",
"description": "Angular DevTools extends Chrome DevTools adding Angular specific debugging and profiling capabilities.",
- "version": "1.0.8",
- "version_name": "1.0.8",
+ "version": "1.0.9",
+ "version_name": "1.0.9",
"minimum_chrome_version": "102",
"content_security_policy": {
"extension_pages": "script-src 'self'; object-src 'self'"
diff --git a/devtools/projects/shell-browser/src/manifest/manifest.firefox.json b/devtools/projects/shell-browser/src/manifest/manifest.firefox.json
index 70bf635d3e301..d0620740c8e73 100644
--- a/devtools/projects/shell-browser/src/manifest/manifest.firefox.json
+++ b/devtools/projects/shell-browser/src/manifest/manifest.firefox.json
@@ -3,7 +3,7 @@
"short_name": "Angular DevTools",
"name": "Angular DevTools",
"description": "Angular DevTools extends Firefox DevTools adding Angular specific debugging and profiling capabilities.",
- "version": "1.0.8",
+ "version": "1.0.9",
"content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'",
"icons": {
"16": "assets/icon16.png",
diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/basic_for_template.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/basic_for_template.js
index fd44ceaa68f43..6967d3029f933 100644
--- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/basic_for_template.js
+++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/basic_for_template.js
@@ -18,6 +18,7 @@ function MyApp_Template(rf, ctx) {
if (rf & 2) {
$r3$.ɵɵadvance(1);
$r3$.ɵɵtextInterpolate1(" ", ctx.message, " ");
- $r3$.ɵɵrepeater(2, ctx.items);
+ $r3$.ɵɵadvance(1);
+ $r3$.ɵɵrepeater(ctx.items);
}
}
diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_aliased_template_variables_template.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_aliased_template_variables_template.js
index 4fca43229b52a..83ca7b4adf501 100644
--- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_aliased_template_variables_template.js
+++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_aliased_template_variables_template.js
@@ -19,6 +19,7 @@ function MyApp_Template(rf, ctx) {
if (rf & 2) {
$r3$.ɵɵadvance(1);
$r3$.ɵɵtextInterpolate1(" ", ctx.message, " ");
- $r3$.ɵɵrepeater(2, ctx.items);
+ $r3$.ɵɵadvance(1);
+ $r3$.ɵɵrepeater(ctx.items);
}
}
diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_data_slots_template.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_data_slots_template.js
index 8a8fd335f5727..4787e36332a31 100644
--- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_data_slots_template.js
+++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_data_slots_template.js
@@ -5,6 +5,7 @@ function MyApp_Template(rf, ctx) {
$r3$.ɵɵtemplate(4, MyApp_ng_template_4_Template, 0, 0, "ng-template");
}
if (rf & 2) {
- $r3$.ɵɵrepeater(1, ctx.items);
+ $r3$.ɵɵadvance(1);
+ $r3$.ɵɵrepeater(ctx.items);
}
}
diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_impure_track_reuse_template.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_impure_track_reuse_template.js
index 8c0e1e1172a56..7107664c2d911 100644
--- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_impure_track_reuse_template.js
+++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_impure_track_reuse_template.js
@@ -8,7 +8,8 @@ function MyApp_Template(rf, ctx) {
$r3$.ɵɵrepeaterCreate(2, MyApp_For_3_Template, 1, 1, null, null, $_forTrack0$, true);
}
if (rf & 2) {
- $r3$.ɵɵrepeater(0, ctx.items);
- $r3$.ɵɵrepeater(2, ctx.otherItems);
+ $r3$.ɵɵrepeater(ctx.items);
+ $r3$.ɵɵadvance(2);
+ $r3$.ɵɵrepeater(ctx.otherItems);
}
}
diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_pure_track_reuse_template.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_pure_track_reuse_template.js
index 9591ce6e32a85..6d900b5c362c7 100644
--- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_pure_track_reuse_template.js
+++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_pure_track_reuse_template.js
@@ -6,7 +6,8 @@ function MyApp_Template(rf, ctx) {
$r3$.ɵɵrepeaterCreate(2, MyApp_For_3_Template, 1, 1, null, null, $_forTrack0$);
}
if (rf & 2) {
- $r3$.ɵɵrepeater(0, ctx.items);
- $r3$.ɵɵrepeater(2, ctx.otherItems);
+ $r3$.ɵɵrepeater(ctx.items);
+ $r3$.ɵɵadvance(2);
+ $r3$.ɵɵrepeater(ctx.otherItems);
}
}
diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_template_variables_listener_template.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_template_variables_listener_template.js
index 45846900ac0b9..7b959f5a4b821 100644
--- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_template_variables_listener_template.js
+++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_template_variables_listener_template.js
@@ -23,6 +23,7 @@ function MyApp_Template(rf, ctx) {
if (rf & 2) {
$r3$.ɵɵadvance(1);
$r3$.ɵɵtextInterpolate1(" ", ctx.message, " ");
- $r3$.ɵɵrepeater(2, ctx.items);
+ $r3$.ɵɵadvance(1);
+ $r3$.ɵɵrepeater(ctx.items);
}
}
diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_template_variables_scope_template.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_template_variables_scope_template.js
index a94b4d6936ca8..b28a236753177 100644
--- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_template_variables_scope_template.js
+++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_template_variables_scope_template.js
@@ -17,8 +17,9 @@ function MyApp_Template(rf, ctx) {
}
if (rf & 2) {
$r3$.ɵɵtextInterpolate4(" ", ctx.$index, " ", ctx.$count, " ", ctx.$first, " ", ctx.$last, " ");
- $r3$.ɵɵrepeater(1, ctx.items);
- $r3$.ɵɵadvance(3);
+ $r3$.ɵɵadvance(1);
+ $r3$.ɵɵrepeater(ctx.items);
+ $r3$.ɵɵadvance(2);
$r3$.ɵɵtextInterpolate4(" ", ctx.$index, " ", ctx.$count, " ", ctx.$first, " ", ctx.$last, " ");
}
}
diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_template_variables_template.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_template_variables_template.js
index 6399e6cddf7ef..f7fab8bfe8a28 100644
--- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_template_variables_template.js
+++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_template_variables_template.js
@@ -19,6 +19,7 @@ function MyApp_Template(rf, ctx) {
if (rf & 2) {
$r3$.ɵɵadvance(1);
$r3$.ɵɵtextInterpolate1(" ", ctx.message, " ");
- $r3$.ɵɵrepeater(2, ctx.items);
+ $r3$.ɵɵadvance(1);
+ $r3$.ɵɵrepeater(ctx.items);
}
}
diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_track_by_field_template.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_track_by_field_template.js
index 52a63bffe47c9..77e263289aacb 100644
--- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_track_by_field_template.js
+++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_track_by_field_template.js
@@ -19,6 +19,7 @@ function MyApp_Template(rf, ctx) {
if (rf & 2) {
$r3$.ɵɵadvance(1);
$r3$.ɵɵtextInterpolate1(" ", ctx.message, " ");
- $r3$.ɵɵrepeater(2, ctx.items);
+ $r3$.ɵɵadvance(1);
+ $r3$.ɵɵrepeater(ctx.items);
}
}
diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_track_by_index_template.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_track_by_index_template.js
index adc50556651dc..a28345298fdde 100644
--- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_track_by_index_template.js
+++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_track_by_index_template.js
@@ -18,6 +18,7 @@ function MyApp_Template(rf, ctx) {
if (rf & 2) {
$r3$.ɵɵadvance(1);
$r3$.ɵɵtextInterpolate1(" ", ctx.message, " ");
- $r3$.ɵɵrepeater(2, ctx.items);
+ $r3$.ɵɵadvance(1);
+ $r3$.ɵɵrepeater(ctx.items);
}
}
diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_track_literals_template.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_track_literals_template.js
index 83c18b778068a..43e0ccd8f8571 100644
--- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_track_literals_template.js
+++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_track_literals_template.js
@@ -10,6 +10,6 @@ function MyApp_Template(rf, ctx) {
$r3$.ɵɵrepeaterCreate(0, MyApp_For_1_Template, 1, 1, null, null, $_forTrack0$, true);
}
if (rf & 2) {
- $r3$.ɵɵrepeater(0, ctx.items);
+ $r3$.ɵɵrepeater(ctx.items);
}
}
diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_with_empty_template.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_with_empty_template.js
index 59273aaa1add2..05ce09f3d27f0 100644
--- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_with_empty_template.js
+++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_with_empty_template.js
@@ -24,6 +24,7 @@ function MyApp_Template(rf, ctx) {
if (rf & 2) {
$r3$.ɵɵadvance(1);
$r3$.ɵɵtextInterpolate1(" ", ctx.message, " ");
- $r3$.ɵɵrepeater(2, ctx.items);
+ $r3$.ɵɵadvance(1);
+ $r3$.ɵɵrepeater(ctx.items);
}
}
diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_with_pipe_template.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_with_pipe_template.js
index 11a64cfb7d004..4ccab1e98630b 100644
--- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_with_pipe_template.js
+++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_with_pipe_template.js
@@ -9,6 +9,7 @@ function MyApp_Template(rf, ctx) {
if (rf & 2) {
$r3$.ɵɵadvance(1);
$r3$.ɵɵtextInterpolate1(" ", ctx.message, " ");
- $r3$.ɵɵrepeater(2, $r3$.ɵɵpipeBind1(4, 1, ctx.items));
+ $r3$.ɵɵadvance(1);
+ $r3$.ɵɵrepeater($r3$.ɵɵpipeBind1(4, 1, ctx.items));
}
}
diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/nested_for_template.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/nested_for_template.js
index 0f82c2acdbe49..c94011a2e3da8 100644
--- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/nested_for_template.js
+++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/nested_for_template.js
@@ -17,7 +17,8 @@ function MyApp_For_3_Template(rf, ctx) {
if (rf & 2) {
const $item_r1$ = ctx.$implicit;
$r3$.ɵɵtextInterpolate1(" ", $item_r1$.name, " ");
- $r3$.ɵɵrepeater(1, $item_r1$.subItems);
+ $r3$.ɵɵadvance(1);
+ $r3$.ɵɵrepeater($item_r1$.subItems);
}
}
…
@@ -31,6 +32,7 @@ function MyApp_Template(rf, ctx) {
if (rf & 2) {
$r3$.ɵɵadvance(1);
$r3$.ɵɵtextInterpolate1(" ", ctx.message, " ");
- $r3$.ɵɵrepeater(2, ctx.items);
+ $r3$.ɵɵadvance(1);
+ $r3$.ɵɵrepeater(ctx.items);
}
}
diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/nested_for_template_variables_template.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/nested_for_template_variables_template.js
index 12bbc9d44a88c..c2ee7b180d1f8 100644
--- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/nested_for_template_variables_template.js
+++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/nested_for_template_variables_template.js
@@ -17,7 +17,8 @@ function MyApp_For_3_Template(rf, ctx) {
if (rf & 2) {
const $item_r1$ = ctx.$implicit;
$r3$.ɵɵtextInterpolate1(" ", $item_r1$.name, " ");
- $r3$.ɵɵrepeater(1, $item_r1$.subItems);
+ $r3$.ɵɵadvance(1);
+ $r3$.ɵɵrepeater($item_r1$.subItems);
}
}
…
@@ -31,6 +32,7 @@ function MyApp_Template(rf, ctx) {
if (rf & 2) {
$r3$.ɵɵadvance(1);
$r3$.ɵɵtextInterpolate1(" ", ctx.message, " ");
- $r3$.ɵɵrepeater(2, ctx.items);
+ $r3$.ɵɵadvance(1);
+ $r3$.ɵɵrepeater(ctx.items);
}
}
diff --git a/packages/compiler/src/render3/view/template.ts b/packages/compiler/src/render3/view/template.ts
index 2a89c7ddd7343..01da9d2fe8548 100644
--- a/packages/compiler/src/render3/view/template.ts
+++ b/packages/compiler/src/render3/view/template.ts
@@ -1574,10 +1574,9 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver
// because its value isn't stored in the LView.
const value = block.expression.visit(this._valueConverter);
- // `repeater(0, iterable)`
- this.updateInstruction(
- block.sourceSpan, R3.repeater,
- () => [o.literal(blockIndex), this.convertPropertyBinding(value)]);
+ // `advance(x); repeater(iterable)`
+ this.updateInstructionWithAdvance(
+ blockIndex, block.sourceSpan, R3.repeater, () => [this.convertPropertyBinding(value)]);
}
private registerComputedLoopVariables(block: t.ForLoopBlock, bindingScope: BindingScope): void {
diff --git a/packages/compiler/src/template/pipeline/ir/src/ops/update.ts b/packages/compiler/src/template/pipeline/ir/src/ops/update.ts
index b06533c05894b..a57aaf7f64efb 100644
--- a/packages/compiler/src/template/pipeline/ir/src/ops/update.ts
+++ b/packages/compiler/src/template/pipeline/ir/src/ops/update.ts
@@ -534,7 +534,7 @@ export function createConditionalOp(
};
}
-export interface RepeaterOp extends Op {
+export interface RepeaterOp extends Op, DependsOnSlotContextOpTrait {
kind: OpKind.Repeater;
/**
@@ -562,6 +562,7 @@ export function createRepeaterOp(
collection,
sourceSpan,
...NEW_OP,
+ ...TRAIT_DEPENDS_ON_SLOT_CONTEXT,
};
}
diff --git a/packages/compiler/src/template/pipeline/src/instruction.ts b/packages/compiler/src/template/pipeline/src/instruction.ts
index 33c049ff45756..70ebf976f8797 100644
--- a/packages/compiler/src/template/pipeline/src/instruction.ts
+++ b/packages/compiler/src/template/pipeline/src/instruction.ts
@@ -293,9 +293,8 @@ export function repeaterCreate(
return call(Identifiers.repeaterCreate, args, sourceSpan);
}
-export function repeater(
- metadataSlot: number, collection: o.Expression, sourceSpan: ParseSourceSpan|null): ir.UpdateOp {
- return call(Identifiers.repeater, [o.literal(metadataSlot), collection], sourceSpan);
+export function repeater(collection: o.Expression, sourceSpan: ParseSourceSpan|null): ir.UpdateOp {
+ return call(Identifiers.repeater, [collection], sourceSpan);
}
export function deferWhen(
diff --git a/packages/compiler/src/template/pipeline/src/phases/reify.ts b/packages/compiler/src/template/pipeline/src/phases/reify.ts
index 60a66a8d4e379..38f77f0af173a 100644
--- a/packages/compiler/src/template/pipeline/src/phases/reify.ts
+++ b/packages/compiler/src/template/pipeline/src/phases/reify.ts
@@ -347,7 +347,7 @@ function reifyUpdateOperations(_unit: CompilationUnit, ops: ir.OpList 0) ? `;${aliases.join(';')}` : '';
+
+ let trackBy = forAttrs.item;
+ if (forAttrs.trackBy !== '') {
+ // build trackby value
+ trackBy = `${forAttrs.trackBy.trim()}(${aliasedIndex}, ${forAttrs.item})`;
+ }
+
+ const {start, middle, end} = getMainBlock(etm, tmpl, offset);
+ const startBlock = `@for (${condition}; track ${trackBy}${aliasStr}) {\n ${start}`;
+
+ const endBlock = `${end}\n}`;
+ const forBlock = startBlock + middle + endBlock;
+
+ const updatedTmpl = tmpl.slice(0, etm.start(offset)) + forBlock + tmpl.slice(etm.end(offset));
+
+ const pre = originals.start.length - startBlock.length;
+ const post = originals.end.length - endBlock.length;
+
+ return {tmpl: updatedTmpl, offsets: {pre, post}};
+}
+
function getNgForParts(expression: string): string[] {
const parts: string[] = [];
const commaSeparatedStack: string[] = [];
diff --git a/packages/core/schematics/ng-generate/control-flow-migration/ifs.ts b/packages/core/schematics/ng-generate/control-flow-migration/ifs.ts
index 5bcdc0407eea8..faa917dc8d1ad 100644
--- a/packages/core/schematics/ng-generate/control-flow-migration/ifs.ts
+++ b/packages/core/schematics/ng-generate/control-flow-migration/ifs.ts
@@ -15,7 +15,7 @@ export const ngif = '*ngIf';
export const boundngif = '[ngIf]';
export const nakedngif = 'ngIf';
-export const ifs = [
+const ifs = [
ngif,
nakedngif,
boundngif,
diff --git a/packages/core/schematics/ng-generate/control-flow-migration/switches.ts b/packages/core/schematics/ng-generate/control-flow-migration/switches.ts
index 50ba91948a993..9647189cdd090 100644
--- a/packages/core/schematics/ng-generate/control-flow-migration/switches.ts
+++ b/packages/core/schematics/ng-generate/control-flow-migration/switches.ts
@@ -18,7 +18,7 @@ export const nakedcase = 'ngSwitchCase';
export const switchdefault = '*ngSwitchDefault';
export const nakeddefault = 'ngSwitchDefault';
-export const switches = [
+const switches = [
ngswitch,
boundcase,
switchcase,
diff --git a/packages/core/schematics/ng-generate/control-flow-migration/types.ts b/packages/core/schematics/ng-generate/control-flow-migration/types.ts
index 818416030cd2d..f4bfaaa41cb0b 100644
--- a/packages/core/schematics/ng-generate/control-flow-migration/types.ts
+++ b/packages/core/schematics/ng-generate/control-flow-migration/types.ts
@@ -12,6 +12,7 @@ import ts from 'typescript';
export const ngtemplate = 'ng-template';
export const boundngifelse = '[ngIfElse]';
export const boundngifthenelse = '[ngIfThenElse]';
+export const nakedngfor = 'ngFor';
function allFormsOf(selector: string): string[] {
return [
@@ -30,6 +31,10 @@ const commonModuleDirectives = new Set([
...allFormsOf('ngStyle'),
...allFormsOf('ngTemplateOutlet'),
...allFormsOf('ngComponentOutlet'),
+ '[NgForOf]',
+ '[NgForTrackBy]',
+ '[ngIfElse]',
+ '[ngIfThenElse]',
]);
function pipeMatchRegExpFor(name: string): RegExp {
@@ -72,6 +77,13 @@ export type Result = {
offsets: Offsets,
};
+export interface ForAttributes {
+ forOf: string;
+ trackBy: string;
+ item: string;
+ aliases: Map;
+}
+
/**
* Represents an error that happened during migration
*/
@@ -88,16 +100,18 @@ export class ElementToMigrate {
attr: Attribute;
elseAttr: Attribute|undefined;
thenAttr: Attribute|undefined;
+ forAttrs: ForAttributes|undefined;
nestCount = 0;
hasLineBreaks = false;
constructor(
el: Element, attr: Attribute, elseAttr: Attribute|undefined = undefined,
- thenAttr: Attribute|undefined = undefined) {
+ thenAttr: Attribute|undefined = undefined, forAttrs: ForAttributes|undefined = undefined) {
this.el = el;
this.attr = attr;
this.elseAttr = elseAttr;
this.thenAttr = thenAttr;
+ this.forAttrs = forAttrs;
}
getCondition(targetStr: string): string {
@@ -236,12 +250,38 @@ export class ElementCollector extends RecursiveVisitor {
if (this._attributes.includes(attr.name)) {
const elseAttr = el.attrs.find(x => x.name === boundngifelse);
const thenAttr = el.attrs.find(x => x.name === boundngifthenelse);
- this.elements.push(new ElementToMigrate(el, attr, elseAttr, thenAttr));
+ const forAttrs = attr.name === nakedngfor ? this.getForAttrs(el) : undefined;
+ this.elements.push(new ElementToMigrate(el, attr, elseAttr, thenAttr, forAttrs));
}
}
}
super.visitElement(el, null);
}
+
+ private getForAttrs(el: Element): ForAttributes {
+ const aliases = new Map();
+ let item = '';
+ let trackBy = '';
+ let forOf = '';
+ for (const attr of el.attrs) {
+ if (attr.name === '[ngForTrackBy]') {
+ trackBy = attr.value;
+ }
+ if (attr.name === '[ngForOf]') {
+ forOf = attr.value;
+ }
+ if (attr.name.startsWith('let-')) {
+ if (attr.value === '') {
+ // item
+ item = attr.name.replace('let-', '');
+ } else {
+ // alias
+ aliases.set(attr.name.replace('let-', ''), attr.value);
+ }
+ }
+ }
+ return {forOf, trackBy, item, aliases};
+ }
}
/** Finds all elements with ngif structural directives. */
diff --git a/packages/core/schematics/ng-generate/control-flow-migration/util.ts b/packages/core/schematics/ng-generate/control-flow-migration/util.ts
index e9fbe29be9f0f..a3379cc85bd06 100644
--- a/packages/core/schematics/ng-generate/control-flow-migration/util.ts
+++ b/packages/core/schematics/ng-generate/control-flow-migration/util.ts
@@ -12,7 +12,10 @@ import ts from 'typescript';
import {AnalyzedFile, CommonCollector, ElementCollector, ElementToMigrate, Template, TemplateCollector} from './types';
-const importRemovals = ['NgIf', 'NgFor', 'NgSwitch', 'NgSwitchCase', 'NgSwitchDefault'];
+const importRemovals = [
+ 'NgIf', 'NgIfElse', 'NgIfThenElse', 'NgFor', 'NgForOf', 'NgForTrackBy', 'NgSwitch',
+ 'NgSwitchCase', 'NgSwitchDefault'
+];
const importWithCommonRemovals = [...importRemovals, 'CommonModule'];
/**
@@ -353,26 +356,34 @@ export function getOriginals(
return {start, end: ''};
}
+function isI18nTemplate(etm: ElementToMigrate, i18nAttr: Attribute|undefined): boolean {
+ return etm.el.name === 'ng-template' && i18nAttr !== undefined &&
+ (etm.el.attrs.length === 2 || (etm.el.attrs.length === 3 && etm.elseAttr !== undefined));
+}
+
+function isRemovableContainer(etm: ElementToMigrate): boolean {
+ return (etm.el.name === 'ng-container' || etm.el.name === 'ng-template') &&
+ (etm.el.attrs.length === 1 || etm.forAttrs !== undefined ||
+ (etm.el.attrs.length === 2 && etm.elseAttr !== undefined) ||
+ (etm.el.attrs.length === 3 && etm.elseAttr !== undefined && etm.thenAttr !== undefined));
+}
+
/**
* builds the proper contents of what goes inside a given control flow block after migration
*/
export function getMainBlock(etm: ElementToMigrate, tmpl: string, offset: number):
{start: string, middle: string, end: string} {
const i18nAttr = etm.el.attrs.find(x => x.name === 'i18n');
- if ((etm.el.name === 'ng-container' || etm.el.name === 'ng-template') &&
- (etm.el.attrs.length === 1 || (etm.el.attrs.length === 2 && etm.elseAttr !== undefined) ||
- (etm.el.attrs.length === 3 && etm.elseAttr !== undefined && etm.thenAttr !== undefined))) {
+ if (isRemovableContainer(etm)) {
// this is the case where we're migrating and there's no need to keep the ng-container
const childStart = etm.el.children[0].sourceSpan.start.offset - offset;
const childEnd = etm.el.children[etm.el.children.length - 1].sourceSpan.end.offset - offset;
const middle = tmpl.slice(childStart, childEnd);
return {start: '', middle, end: ''};
- } else if (
- etm.el.name === 'ng-template' && i18nAttr !== undefined &&
- (etm.el.attrs.length === 2 || (etm.el.attrs.length === 3 && etm.elseAttr !== undefined))) {
+ } else if (isI18nTemplate(etm, i18nAttr)) {
const childStart = etm.el.children[0].sourceSpan.start.offset - offset;
const childEnd = etm.el.children[etm.el.children.length - 1].sourceSpan.end.offset - offset;
- const middle = wrapIntoI18nContainer(i18nAttr, tmpl.slice(childStart, childEnd));
+ const middle = wrapIntoI18nContainer(i18nAttr!, tmpl.slice(childStart, childEnd));
return {start: '', middle, end: ''};
}
diff --git a/packages/core/schematics/test/control_flow_migration_spec.ts b/packages/core/schematics/test/control_flow_migration_spec.ts
index f7ad20a42e6b3..d5f172b45c014 100644
--- a/packages/core/schematics/test/control_flow_migration_spec.ts
+++ b/packages/core/schematics/test/control_flow_migration_spec.ts
@@ -1585,6 +1585,208 @@ describe('control flow migration', () => {
});
});
+ describe('ngForOf', () => {
+ it('should migrate a basic ngForOf', async () => {
+ writeFile('/comp.ts', `
+ import {Component} from '@angular/core';
+ import {NgFor} from '@angular/common';
+ interface Item {
+ id: number;
+ text: string;
+ }
+
+ @Component({
+ imports: [NgFor,NgForOf],
+ templateUrl: 'comp.html',
+ })
+ class Comp {
+ items: Item[] = [{id: 1, text: 'blah'},{id: 2, text: 'stuff'}];
+ }
+ `);
+
+ writeFile('/comp.html', [
+ ``,
+ ` `,
+ ` {{rowData}} |
`,
+ ` `,
+ ``,
+ ].join('\n'));
+
+ await runMigration();
+ const actual = tree.readContent('/comp.html');
+
+ const expected = [
+ ``,
+ ` @for (rowData of things; track rowData) {\n `,
+ ` {{rowData}} |
\n `,
+ `}`,
+ ``,
+ ].join('\n');
+
+ expect(actual).toBe(expected);
+ });
+
+ it('should migrate ngForOf with an alias', async () => {
+ writeFile('/comp.ts', `
+ import {Component} from '@angular/core';
+ import {NgFor} from '@angular/common';
+ interface Item {
+ id: number;
+ text: string;
+ }
+
+ @Component({
+ imports: [NgFor,NgForOf],
+ templateUrl: 'comp.html',
+ })
+ class Comp {
+ items: Item[] = [{id: 1, text: 'blah'},{id: 2, text: 'stuff'}];
+ }
+ `);
+
+ writeFile('/comp.html', [
+ ``,
+ ` `,
+ ` {{rowIndex}} | {{rowData}} |
`,
+ ` `,
+ ``,
+ ].join('\n'));
+
+ await runMigration();
+ const actual = tree.readContent('/comp.html');
+
+ const expected = [
+ ``,
+ ` @for (rowData of things; track rowData; let rowIndex = $index) {\n `,
+ ` {{rowIndex}} | {{rowData}} |
\n `,
+ `}`,
+ ``,
+ ].join('\n');
+
+ expect(actual).toBe(expected);
+ });
+
+ it('should migrate ngForOf with track by', async () => {
+ writeFile('/comp.ts', `
+ import {Component} from '@angular/core';
+ import {NgFor} from '@angular/common';
+ interface Item {
+ id: number;
+ text: string;
+ }
+
+ @Component({
+ imports: [NgFor,NgForOf],
+ templateUrl: 'comp.html',
+ })
+ class Comp {
+ items: Item[] = [{id: 1, text: 'blah'},{id: 2, text: 'stuff'}];
+ }
+ `);
+
+ writeFile('/comp.html', [
+ ``,
+ ` `,
+ ` {{rowData}} |
`,
+ ` `,
+ ``,
+ ].join('\n'));
+
+ await runMigration();
+ const actual = tree.readContent('/comp.html');
+
+ const expected = [
+ ``,
+ ` @for (rowData of things; track trackMe($index, rowData)) {\n `,
+ ` {{rowData}} |
\n `,
+ `}`,
+ ``,
+ ].join('\n');
+
+ expect(actual).toBe(expected);
+ });
+
+ it('should migrate ngForOf with track by and alias', async () => {
+ writeFile('/comp.ts', `
+ import {Component} from '@angular/core';
+ import {NgFor} from '@angular/common';
+ interface Item {
+ id: number;
+ text: string;
+ }
+
+ @Component({
+ imports: [NgFor,NgForOf],
+ templateUrl: 'comp.html',
+ })
+ class Comp {
+ items: Item[] = [{id: 1, text: 'blah'},{id: 2, text: 'stuff'}];
+ }
+ `);
+
+ writeFile('/comp.html', [
+ ``,
+ ` `,
+ ` {{rowIndex}} | {{rowData}} |
`,
+ ` `,
+ ``,
+ ].join('\n'));
+
+ await runMigration();
+ const actual = tree.readContent('/comp.html');
+
+ const expected = [
+ ``,
+ ` @for (rowData of things; track trackMe(rowIndex, rowData); let rowIndex = $index) {\n `,
+ ` {{rowIndex}} | {{rowData}} |
\n `,
+ `}`,
+ ``,
+ ].join('\n');
+
+ expect(actual).toBe(expected);
+ });
+
+ it('should migrate ngForOf with track by and multiple aliases', async () => {
+ writeFile('/comp.ts', `
+ import {Component} from '@angular/core';
+ import {NgFor} from '@angular/common';
+ interface Item {
+ id: number;
+ text: string;
+ }
+
+ @Component({
+ imports: [NgFor,NgForOf],
+ templateUrl: 'comp.html',
+ })
+ class Comp {
+ items: Item[] = [{id: 1, text: 'blah'},{id: 2, text: 'stuff'}];
+ }
+ `);
+
+ writeFile('/comp.html', [
+ ``,
+ ` `,
+ ` {{rowIndex}} | {{rowData}} |
`,
+ ` `,
+ ``,
+ ].join('\n'));
+
+ await runMigration();
+ const actual = tree.readContent('/comp.html');
+
+ const expected = [
+ ``,
+ ` @for (rowData of things; track trackMe(rowIndex, rowData); let rowIndex = $index; let rCount = $count) {\n `,
+ ` {{rowIndex}} | {{rowData}} |
\n `,
+ `}`,
+ ``,
+ ].join('\n');
+
+ expect(actual).toBe(expected);
+ });
+ });
+
describe('ngSwitch', () => {
it('should migrate an inline template', async () => {
writeFile(
diff --git a/packages/core/src/render3/instructions/control_flow.ts b/packages/core/src/render3/instructions/control_flow.ts
index 11acac874342c..5d32b5ef3f2dc 100644
--- a/packages/core/src/render3/instructions/control_flow.ts
+++ b/packages/core/src/render3/instructions/control_flow.ts
@@ -21,7 +21,7 @@ import {TNode} from '../interfaces/node';
import {CONTEXT, DECLARATION_COMPONENT_VIEW, HEADER_OFFSET, HYDRATION, LView, TVIEW, TView} from '../interfaces/view';
import {LiveCollection, reconcile} from '../list_reconciliation';
import {destroyLView, detachView} from '../node_manipulation';
-import {getLView, nextBindingIndex} from '../state';
+import {getLView, getSelectedIndex, nextBindingIndex} from '../state';
import {getTNode} from '../util/view_utils';
import {addLViewToLContainer, createAndRenderEmbeddedLView, getLViewFromLContainer, removeLViewFromLContainer, shouldAddViewToDom} from '../view_manipulation';
@@ -56,7 +56,8 @@ export function ɵɵconditional(containerIndex: number, matchingTemplateIndex
// Index -1 is a special case where none of the conditions evaluates to
// a truthy value and as the consequence we've got no view to show.
if (matchingTemplateIndex !== -1) {
- const templateTNode = getExistingTNode(hostLView[TVIEW], matchingTemplateIndex);
+ const templateTNode =
+ getExistingTNode(hostLView[TVIEW], HEADER_OFFSET + matchingTemplateIndex);
const dehydratedView = findMatchingDehydratedView(lContainer, templateTNode.tView!.ssrId);
const embeddedLView =
@@ -234,23 +235,20 @@ class LiveCollectionLContainerImpl extends
* The repeater instruction does update-time diffing of a provided collection (against the
* collection seen previously) and maps changes in the collection to views structure (by adding,
* removing or moving views as needed).
- * @param metadataSlotIdx - index in data where we can find an instance of RepeaterMetadata with
- * additional information (ex. differ) needed to process collection diffing and view
- * manipulation
* @param collection - the collection instance to be checked for changes
* @codeGenApi
*/
-export function ɵɵrepeater(
- metadataSlotIdx: number, collection: Iterable|undefined|null): void {
+export function ɵɵrepeater(collection: Iterable|undefined|null): void {
const prevConsumer = setActiveConsumer(null);
+ const metadataSlotIdx = getSelectedIndex();
try {
const hostLView = getLView();
const hostTView = hostLView[TVIEW];
- const metadata = hostLView[HEADER_OFFSET + metadataSlotIdx] as RepeaterMetadata;
+ const metadata = hostLView[metadataSlotIdx] as RepeaterMetadata;
if (metadata.liveCollection === undefined) {
const containerIndex = metadataSlotIdx + 1;
- const lContainer = getLContainer(hostLView, HEADER_OFFSET + containerIndex);
+ const lContainer = getLContainer(hostLView, containerIndex);
const itemTemplateTNode = getExistingTNode(hostTView, containerIndex);
metadata.liveCollection =
new LiveCollectionLContainerImpl(lContainer, hostLView, itemTemplateTNode);
@@ -270,7 +268,7 @@ export function ɵɵrepeater(
const isCollectionEmpty = liveCollection.length === 0;
if (bindingUpdated(hostLView, bindingIndex, isCollectionEmpty)) {
const emptyTemplateIndex = metadataSlotIdx + 2;
- const lContainerForEmpty = getLContainer(hostLView, HEADER_OFFSET + emptyTemplateIndex);
+ const lContainerForEmpty = getLContainer(hostLView, emptyTemplateIndex);
if (isCollectionEmpty) {
const emptyTemplateTNode = getExistingTNode(hostTView, emptyTemplateIndex);
const dehydratedView =
@@ -312,7 +310,7 @@ function getExistingLViewFromLContainer(lContainer: LContainer, index: number
}
function getExistingTNode(tView: TView, index: number): TNode {
- const tNode = getTNode(tView, index + HEADER_OFFSET);
+ const tNode = getTNode(tView, index);
ngDevMode && assertTNode(tNode);
return tNode;
diff --git a/packages/core/test/acceptance/control_flow_for_spec.ts b/packages/core/test/acceptance/control_flow_for_spec.ts
index 0b007c22cacc1..c993d2f4366ed 100644
--- a/packages/core/test/acceptance/control_flow_for_spec.ts
+++ b/packages/core/test/acceptance/control_flow_for_spec.ts
@@ -121,6 +121,40 @@ describe('control flow - for', () => {
expect(fixture.nativeElement.textContent).toBe('1|2|3|');
});
+ it('should be able to access a directive property that is reassigned in a lifecycle hook', () => {
+ @Directive({
+ selector: '[dir]',
+ exportAs: 'dir',
+ standalone: true,
+ })
+ class Dir {
+ data = [1];
+
+ ngDoCheck() {
+ this.data = [2];
+ }
+ }
+
+ @Component({
+ selector: 'app-root',
+ standalone: true,
+ imports: [Dir],
+ template: `
+
+
+ @for (x of dir.data; track $index) {
+ {{x}}
+ }
+ `,
+ })
+ class TestComponent {
+ }
+
+ const fixture = TestBed.createComponent(TestComponent);
+ fixture.detectChanges();
+ expect(fixture.nativeElement.textContent.trim()).toBe('2');
+ });
+
describe('trackBy', () => {
it('should have access to the host context in the track function', () => {
let offsetReads = 0;
diff --git a/packages/core/test/test_bed_spec.ts b/packages/core/test/test_bed_spec.ts
index 9fef0b46f5375..2dd3c1d9add87 100644
--- a/packages/core/test/test_bed_spec.ts
+++ b/packages/core/test/test_bed_spec.ts
@@ -175,6 +175,56 @@ describe('TestBed with Standalone types', () => {
TestBed.resetTestingModule();
});
+ it('should override dependencies of standalone components', () => {
+ @Component({
+ selector: 'dep',
+ standalone: true,
+ template: 'main dep',
+ })
+ class MainDep {
+ }
+
+ @Component({
+ selector: 'dep',
+ standalone: true,
+ template: 'mock dep',
+ })
+ class MockDep {
+ }
+
+ @Component({
+ selector: 'app-root',
+ standalone: true,
+ imports: [MainDep],
+ template: '',
+ })
+ class AppComponent {
+ }
+
+ TestBed.configureTestingModule({imports: [AppComponent]});
+
+ let fixture = TestBed.createComponent(AppComponent);
+ fixture.detectChanges();
+
+ // No overrides defined, expecting main dependency to be used.
+ expect(fixture.nativeElement.innerHTML).toBe('main dep');
+
+ // Emulate an end of a test.
+ TestBed.resetTestingModule();
+
+ // Emulate the start of a next test, make sure previous overrides
+ // are not persisted across tests.
+ TestBed.configureTestingModule({imports: [AppComponent]});
+ TestBed.overrideComponent(AppComponent, {set: {imports: [MockDep]}});
+
+ fixture = TestBed.createComponent(AppComponent);
+ fixture.detectChanges();
+
+ // Main dependency was overridden, expect to see a mock.
+ expect(fixture.nativeElement.innerHTML).toBe('mock dep');
+ });
+
+
it('should override providers on standalone component itself', () => {
const A = new InjectionToken('A');
diff --git a/packages/core/testing/src/test_bed_compiler.ts b/packages/core/testing/src/test_bed_compiler.ts
index 1039a77822165..4c2a7c87b220e 100644
--- a/packages/core/testing/src/test_bed_compiler.ts
+++ b/packages/core/testing/src/test_bed_compiler.ts
@@ -396,6 +396,9 @@ export class TestBedCompiler {
}
this.maybeStoreNgDef(NG_COMP_DEF, declaration);
+ if (USE_RUNTIME_DEPS_TRACKER_FOR_JIT) {
+ depsTracker.clearScopeCacheFor(declaration);
+ }
compileComponent(declaration, metadata);
});
this.pendingComponents.clear();