diff --git a/changelog_unreleased/angular/18722.md b/changelog_unreleased/angular/18722.md
new file mode 100644
index 000000000..61e69535e
--- /dev/null
+++ b/changelog_unreleased/angular/18722.md
@@ -0,0 +1,41 @@
+#### Support Angular v21.2 (#18722, #19034 by @fisker)
+
+Exhaustive typechecking with `@default never;`
+
+
+```html
+
+@switch (foo) {
+ @case (1) {}
+ @default never;
+}
+
+
+SyntaxError: Incomplete block "default never". If you meant to write the @ character, you should use the "@" HTML entity instead. (3:3)
+
+
+@switch (foo) {
+ @case (1) {}
+ @default never;
+}
+```
+
+`arrow function` and `instanceof` expressions.
+
+
+```html
+
+@let fn = (a) => a? 1:2;
+
+{{ fn ( a instanceof b)}}
+
+
+@let fn = (a) => a? 1:2;
+
+{{ fn ( a instanceof b)}}
+
+
+@let fn = (a) => (a ? 1 : 2);
+
+{{ fn(a instanceof b) }}
+```
diff --git a/package.json b/package.json
index 717074756..8281c04dc 100644
--- a/package.json
+++ b/package.json
@@ -42,7 +42,7 @@
"bin"
],
"dependencies": {
- "@angular/compiler": "21.2.4",
+ "@angular/compiler": "21.2.8",
"@babel/code-frame": "8.0.0-rc.3",
"@babel/parser": "8.0.0-rc.3",
"@babel/types": "8.0.0-rc.3",
@@ -55,7 +55,7 @@
"@typescript-eslint/visitor-keys": "8.58.1",
"acorn": "8.16.0",
"acorn-jsx": "5.3.2",
- "angular-estree-parser": "15.3.0",
+ "angular-estree-parser": "15.4.3",
"angular-html-parser": "10.5.0",
"camelcase": "9.0.0",
"ci-info": "4.4.0",
diff --git a/src/language-js/print/function-parameters.js b/src/language-js/print/function-parameters.js
index 880954573..4c92e632c 100644
--- a/src/language-js/print/function-parameters.js
+++ b/src/language-js/print/function-parameters.js
@@ -185,7 +185,11 @@ function printFunctionParameters(
typeParametersDoc,
"(",
indent([softline, ...printed]),
- !hasRestParameter(functionNode) ? printTrailingComma(options, "all") : "",
+ !hasRestParameter(functionNode) &&
+ // Angular does not allow trailing comma
+ path.root.type !== "NGRoot"
+ ? printTrailingComma(options, "all")
+ : "",
softline,
")",
];
diff --git a/tests/format/angular/expression/__snapshots__/format.test.js.snap b/tests/format/angular/expression/__snapshots__/format.test.js.snap
index a90a900a6..f2fc7d3b9 100644
--- a/tests/format/angular/expression/__snapshots__/format.test.js.snap
+++ b/tests/format/angular/expression/__snapshots__/format.test.js.snap
@@ -1,5 +1,63 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
+exports[`arrow-function.html format 1`] = `
+====================================options=====================================
+parsers: ["angular"]
+ printWidth: 80 (default) |
+=====================================input======================================
+@let fn = (a, b) => componentValue + a + b;
+One: {{fn(0, 1)}}
+
+@if (true) {
+ Two: {{fn(1, 1)}}
+
+
+
+}
+
+
+{{[(a) => a + 1][0](1000)}}
+{{[(a ) => a + 1 + componentProp][0](1000)}}
+
+
+@let a = 1;
+
+@if (true) {
+ @let b = 2;
+
+ @if (true) {
+ @let c = 3;
+ {{(() => a + b + c)()}}
+ }
+}
+
+=====================================output=====================================
+@let fn = (a, b) => componentValue + a + b; One: {{ fn(0, 1) }}
+
+@if (true) {
+ Two: {{ fn(1, 1) }}
+
+
+
+}
+
+{{ [(a) => a + 1][0](1000) }}
+{{ [(a) => a + 1 + componentProp][0](1000) }}
+
+@let a = 1;
+
+@if (true) {
+ @let b = 2;
+
+ @if (true) {
+ @let c = 3;
+ {{ (() => a + b + c)() }}
+ }
+}
+
+================================================================================
+`;
+
exports[`exponentiation-operators.html format 1`] = `
====================================options=====================================
parsers: ["angular"]
@@ -31,6 +89,22 @@ parsers: ["angular"]
================================================================================
`;
+exports[`instanceof-operator.html format 1`] = `
+====================================options=====================================
+parsers: ["angular"]
+ printWidth: 80 (default) |
+=====================================input======================================
+{{
+
+ ( (a instanceof (b)))
+}}
+
+=====================================output=====================================
+{{ a instanceof b }}
+
+================================================================================
+`;
+
exports[`spread-element.html format 1`] = `
====================================options=====================================
parsers: ["angular"]
diff --git a/tests/format/angular/expression/arrow-function-options/__snapshots__/format.test.js.snap b/tests/format/angular/expression/arrow-function-options/__snapshots__/format.test.js.snap
new file mode 100644
index 000000000..71cf5f605
--- /dev/null
+++ b/tests/format/angular/expression/arrow-function-options/__snapshots__/format.test.js.snap
@@ -0,0 +1,49 @@
+// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
+
+exports[`snippet: #0 - {"arrowParens":"avoid"} format 1`] = `
+====================================options=====================================
+arrowParens: "avoid"
+parsers: ["angular"]
+ printWidth: 80 (default) |
+=====================================input======================================
+@let fn = ( oneArgument) => 1;
+=====================================output=====================================
+@let fn = oneArgument => 1;
+
+================================================================================
+`;
+
+exports[`snippet: #0 - {"printWidth":1,"trailingComma":"all"} format 1`] = `
+====================================options=====================================
+parsers: ["angular"]
+printWidth: 1
+trailingComma: "all"
+ | printWidth: 1
+=====================================input======================================
+@let fn = ( should, not, print,trailing, comma) => 1;
+=====================================output=====================================
+@let fn =
+ (
+ should,
+ not,
+ print,
+ trailing,
+ comma
+ ) =>
+ 1;
+
+================================================================================
+`;
+
+exports[`snippet: #1 - {"arrowParens":"avoid"} format 1`] = `
+====================================options=====================================
+arrowParens: "avoid"
+parsers: ["angular"]
+ printWidth: 80 (default) |
+=====================================input======================================
+@let fn = oneArgument => 1;
+=====================================output=====================================
+@let fn = oneArgument => 1;
+
+================================================================================
+`;
diff --git a/tests/format/angular/expression/arrow-function-options/format.test.js b/tests/format/angular/expression/arrow-function-options/format.test.js
new file mode 100644
index 000000000..f0dd65135
--- /dev/null
+++ b/tests/format/angular/expression/arrow-function-options/format.test.js
@@ -0,0 +1,20 @@
+runFormatTest(
+ {
+ importMeta: import.meta,
+ snippets: ["@let fn = ( should, not, print,trailing, comma) => 1;"],
+ },
+ ["angular"],
+ { printWidth: 1, trailingComma: "all" },
+);
+
+runFormatTest(
+ {
+ importMeta: import.meta,
+ snippets: [
+ "@let fn = ( oneArgument) => 1;",
+ "@let fn = oneArgument => 1;",
+ ],
+ },
+ ["angular"],
+ { arrowParens: "avoid" },
+);
diff --git a/tests/format/angular/expression/arrow-function.html b/tests/format/angular/expression/arrow-function.html
new file mode 100644
index 000000000..86c4b42d4
--- /dev/null
+++ b/tests/format/angular/expression/arrow-function.html
@@ -0,0 +1,25 @@
+@let fn = (a, b) => componentValue + a + b;
+One: {{fn(0, 1)}}
+
+@if (true) {
+ Two: {{fn(1, 1)}}
+
+
+
+}
+
+
+{{[(a) => a + 1][0](1000)}}
+{{[(a ) => a + 1 + componentProp][0](1000)}}
+
+
+@let a = 1;
+
+@if (true) {
+ @let b = 2;
+
+ @if (true) {
+ @let c = 3;
+ {{(() => a + b + c)()}}
+ }
+}
diff --git a/tests/format/angular/expression/instanceof-operator.html b/tests/format/angular/expression/instanceof-operator.html
new file mode 100644
index 000000000..4ed44ca48
--- /dev/null
+++ b/tests/format/angular/expression/instanceof-operator.html
@@ -0,0 +1,4 @@
+{{
+
+ ( (a instanceof (b)))
+}}
diff --git a/yarn.lock b/yarn.lock
index ab9fbbb50..ef8e7aaba 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5,12 +5,12 @@ __metadata:
version: 8
cacheKey: 10
-"@angular/compiler@npm:21.2.4":
- version: 21.2.4
- resolution: "@angular/compiler@npm:21.2.4"
+"@angular/compiler@npm:21.2.8":
+ version: 21.2.8
+ resolution: "@angular/compiler@npm:21.2.8"
dependencies:
tslib: "npm:^2.3.0"
- checksum: 10/f1071dbfa38d6183fd9fdccff10a6d0e77a08afcb471d631effe3ae4e242ceadd4960dd58c8f596647bf4a39e4b88fc4616bee5d80af393f214363b4455ab288
+ checksum: 10/5bcf866cf01fbc19b016df4c77aa662440ba5ea3b1615ea21b6c69cd52c5b6cf55fbf8003ee8bcfd9199deac7d33a37ccbf4b9feb4bcc21e65cae388d2fa6a88
languageName: node
linkType: hard
@@ -3039,12 +3039,12 @@ __metadata:
languageName: node
linkType: hard
-"angular-estree-parser@npm:15.3.0":
- version: 15.3.0
- resolution: "angular-estree-parser@npm:15.3.0"
+"angular-estree-parser@npm:15.4.3":
+ version: 15.4.3
+ resolution: "angular-estree-parser@npm:15.4.3"
peerDependencies:
- "@angular/compiler": ">=21.0.7 || >= 21.1.0-0"
- checksum: 10/9266d6bf73fac7f2d96e6a7b306bef0a6b6563f4f11a36aa308d4ca2fda2868839c8831c2e95d5ea008636e061c1cdc0668c01fc9f9876eb1b5f6846217fc2ec
+ "@angular/compiler": ">=21.0.7 || >= 21.2.0-0"
+ checksum: 10/a31569c99bb89e20564c0caa77c8db28070170bbbb145fd5afdda77a0d535af71cd3bd13d1d25bd6fe6cffa81ae6ab57f37cd1a9ea1dcdb5039ac419945515ce
languageName: node
linkType: hard
@@ -8579,7 +8579,7 @@ __metadata:
version: 0.0.0-use.local
resolution: "prettier@workspace:."
dependencies:
- "@angular/compiler": "npm:21.2.4"
+ "@angular/compiler": "npm:21.2.8"
"@babel/code-frame": "npm:8.0.0-rc.3"
"@babel/generator": "npm:8.0.0-rc.3"
"@babel/parser": "npm:8.0.0-rc.3"
@@ -8598,7 +8598,7 @@ __metadata:
"@typescript-eslint/visitor-keys": "npm:8.58.1"
acorn: "npm:8.16.0"
acorn-jsx: "npm:5.3.2"
- angular-estree-parser: "npm:15.3.0"
+ angular-estree-parser: "npm:15.4.3"
angular-html-parser: "npm:10.5.0"
base64-arraybuffer-es6: "npm:3.1.0"
browserslist: "npm:4.28.2"