Angular: Support @boundary / @error error boundaries (#20015)

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: fisker Cheung <lionkay@gmail.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
authored and GitHub committed 2026-09-15 17:20:57 +08:00
1 parent 23787f7cab
commit eca37730aa
9 files changed
+402 -7

No files matched your search

+22
View File
@@ -0,0 +1,22 @@
#### Support `@boundary` error boundaries (#20014 by @Yahiro025)
<!-- prettier-ignore -->
```html
<!-- Input -->
@boundary {
<complex-chart [data]="data"/>
}
@error (let err) {
<p>{{err.message}}</p>
}
<!-- Prettier stable -->
SyntaxError: Unexpected character "EOF" (Do you have an unescaped "{" in your template? Use "{{ '{' }}") to escape it.) (6:1)
<!-- Prettier main -->
@boundary {
<complex-chart [data]="data" />
} @error (let err) {
<p>{{ err.message }}</p>
}
```
+1 -1
View File
@@ -60,7 +60,7 @@
"acorn": "8.18.0", "acorn": "8.18.0",
"acorn-jsx": "5.3.2", "acorn-jsx": "5.3.2",
"angular-estree-parser": "15.5.0", "angular-estree-parser": "15.5.0",
"angular-html-parser": "10.11.0", "angular-html-parser": "10.12.0",
"camelcase": "9.0.0", "camelcase": "9.0.0",
"ci-info": "4.4.0", "ci-info": "4.4.0",
"cjk-regex": "3.4.2", "cjk-regex": "3.4.2",
+4 -1
View File
@@ -28,7 +28,10 @@ const packageTransforms = new Map([
transforms["method-at"], transforms["method-at"],
["@glimmer/syntax", "angular-estree-parser", "espree"], ["@glimmer/syntax", "angular-estree-parser", "espree"],
], ],
[transforms["object-has-own"], ["@babel/parser", "graphql"]], [
transforms["object-has-own"],
["@babel/parser", "graphql", "angular-html-parser"],
],
[transforms["string-raw"], ["camelcase", "@angular/compiler", "js-yaml"]], [transforms["string-raw"], ["camelcase", "@angular/compiler", "js-yaml"]],
[transforms["method-is-well-formed"], ["meriyah"]], [transforms["method-is-well-formed"], ["meriyah"]],
/* spell-checker: enable */ /* spell-checker: enable */
@@ -15,6 +15,8 @@ const ANGULAR_CONTROL_FLOW_BLOCK_SETTINGS = new Map(
["placeholder", ["placeholder", "error", "loading"]], ["placeholder", ["placeholder", "error", "loading"]],
["error", ["placeholder", "error", "loading"]], ["error", ["placeholder", "error", "loading"]],
["loading", ["placeholder", "error", "loading"]], ["loading", ["placeholder", "error", "loading"]],
["boundary", ["error"]],
] ]
.filter(([, followingBlockNames]) => followingBlockNames.length > 0) .filter(([, followingBlockNames]) => followingBlockNames.length > 0)
.map(([name, followingBlockNames]) => [name, new Set(followingBlockNames)]), .map(([name, followingBlockNames]) => [name, new Set(followingBlockNames)]),
@@ -52,6 +52,258 @@ parsers: ["angular"]
================================================================================ ================================================================================
`; `;
exports[`boundary.html format 1`] = `
====================================options=====================================
parsers: ["angular"]
printWidth: 80 (default) |
=====================================input======================================
@boundary {
<app-risky-component />
} @error {
<p>Something went wrong!</p>
}
@boundary {
<app-risky-component />
} @error {
<p>Error occurred: {{ $error.message }}</p>
}
@boundary {
<app-flaky-component />
} @error {
<p>Loading failed.</p>
<button (click)="$reset()">Try again</button>
}
@boundary {
<app-risky-component />
} @error {
<app-error-fallback [error]="err" />
}
@boundary {
<app-risky-component />
} @error (let err; retry = $reset; when isNetworkError(err)) {
<app-network-error [error]="err" />
<button (click)="retry()">Retry</button>
} @error {
<app-generic-error />
}
@boundary {
<app-chart-dashboard />
} @error (let err; reset = $reset; when isRenderError(err)) {
<p>Network issue. Check your connection.</p>
<button (click)="reset()">Retry</button>
} @error {
<p>An unexpected error occurred: {{ $error.message }}</p>
}
@boundary {
<complex-chart [data]="data"/>
} @error (let err = $error; let r = $reset) {
<p>Failed to load chart: {{err.message}}</p>
<button (click)="r()">Retry</button>
}
@boundary {
Outer Main
@boundary {
Inner Main
} @error {
Inner Fallback
}
} @error {
Outer Fallback
}
@defer {
@boundary {
<app-c1/>
} @error {
<span>Render error</span>
}
} @error {
<span>Load error</span>
}
=====================================output=====================================
@boundary {
<app-risky-component />
} @error {
<p>Something went wrong!</p>
}
@boundary {
<app-risky-component />
} @error {
<p>Error occurred: {{ $error.message }}</p>
}
@boundary {
<app-flaky-component />
} @error {
<p>Loading failed.</p>
<button (click)="$reset()">Try again</button>
}
@boundary {
<app-risky-component />
} @error {
<app-error-fallback [error]="err" />
}
@boundary {
<app-risky-component />
} @error (let err; retry = $reset; when isNetworkError(err)) {
<app-network-error [error]="err" />
<button (click)="retry()">Retry</button>
} @error {
<app-generic-error />
}
@boundary {
<app-chart-dashboard />
} @error (let err; reset = $reset; when isRenderError(err)) {
<p>Network issue. Check your connection.</p>
<button (click)="reset()">Retry</button>
} @error {
<p>An unexpected error occurred: {{ $error.message }}</p>
}
@boundary {
<complex-chart [data]="data" />
} @error (let err = $error; let r = $reset) {
<p>Failed to load chart: {{ err.message }}</p>
<button (click)="r()">Retry</button>
}
@boundary {
Outer Main
@boundary {
Inner Main
} @error {
Inner Fallback
}
} @error {
Outer Fallback
}
@defer {
@boundary {
<app-c1 />
} @error {
<span>Render error</span>
}
} @error {
<span>Load error</span>
}
================================================================================
`;
exports[`boundary-error-parameters.html format 1`] = `
====================================options=====================================
parsers: ["angular"]
printWidth: 80 (default) |
=====================================input======================================
@boundary {
<app-risky-component />
} @error (let err; retry
= $reset; when
isNetworkError(err)) {
<app-network-error [error]="err" />
<button (click)="retry()">Retry</button>
} @error {
<app-generic-error />
}
@boundary {
<app-chart-dashboard />
} @error (let
err; reset
=
$reset; when
isRenderError(err)) {
<p>Network issue. Check your connection.</p>
<button (click)="reset()">Retry</button>
} @error {
<p>An unexpected error occurred: {{$error.message}}</p>
}
@boundary {
<complex-chart [data]="data"/>
} @error (let err=$error; let r=$reset) {
<p>Failed to load chart: {{err.message}}</p>
<button (click)="r()">Retry</button>
}
@boundary {
Main
} @error (let err; when err.message === '404') {
Not Found
}
@boundary {
Main
} @error (err = $error, retry
=
$reset) {
<button (click)="retry()">Retry</button>
}
=====================================output=====================================
@boundary {
<app-risky-component />
} @error (let err; retry
= $reset; when
isNetworkError(err)) {
<app-network-error [error]="err" />
<button (click)="retry()">Retry</button>
} @error {
<app-generic-error />
}
@boundary {
<app-chart-dashboard />
} @error (let
err; reset
=
$reset; when
isRenderError(err)) {
<p>Network issue. Check your connection.</p>
<button (click)="reset()">Retry</button>
} @error {
<p>An unexpected error occurred: {{ $error.message }}</p>
}
@boundary {
<complex-chart [data]="data" />
} @error (let err=$error; let r=$reset) {
<p>Failed to load chart: {{ err.message }}</p>
<button (click)="r()">Retry</button>
}
@boundary {
Main
} @error (let err; when err.message === '404') {
Not Found
}
@boundary {
Main
} @error (err = $error, retry
=
$reset) {
<button (click)="retry()">Retry</button>
}
================================================================================
`;
exports[`chid.html format 1`] = ` exports[`chid.html format 1`] = `
====================================options===================================== ====================================options=====================================
parsers: ["angular"] parsers: ["angular"]
@@ -0,0 +1,45 @@
@boundary {
<app-risky-component />
} @error (let err; retry
= $reset; when
isNetworkError(err)) {
<app-network-error [error]="err" />
<button (click)="retry()">Retry</button>
} @error {
<app-generic-error />
}
@boundary {
<app-chart-dashboard />
} @error (let
err; reset
=
$reset; when
isRenderError(err)) {
<p>Network issue. Check your connection.</p>
<button (click)="reset()">Retry</button>
} @error {
<p>An unexpected error occurred: {{$error.message}}</p>
}
@boundary {
<complex-chart [data]="data"/>
} @error (let err=$error; let r=$reset) {
<p>Failed to load chart: {{err.message}}</p>
<button (click)="r()">Retry</button>
}
@boundary {
Main
} @error (let err; when err.message === '404') {
Not Found
}
@boundary {
Main
} @error (err = $error, retry
=
$reset) {
<button (click)="retry()">Retry</button>
}
@@ -0,0 +1,70 @@
@boundary {
<app-risky-component />
} @error {
<p>Something went wrong!</p>
}
@boundary {
<app-risky-component />
} @error {
<p>Error occurred: {{ $error.message }}</p>
}
@boundary {
<app-flaky-component />
} @error {
<p>Loading failed.</p>
<button (click)="$reset()">Try again</button>
}
@boundary {
<app-risky-component />
} @error {
<app-error-fallback [error]="err" />
}
@boundary {
<app-risky-component />
} @error (let err; retry = $reset; when isNetworkError(err)) {
<app-network-error [error]="err" />
<button (click)="retry()">Retry</button>
} @error {
<app-generic-error />
}
@boundary {
<app-chart-dashboard />
} @error (let err; reset = $reset; when isRenderError(err)) {
<p>Network issue. Check your connection.</p>
<button (click)="reset()">Retry</button>
} @error {
<p>An unexpected error occurred: {{ $error.message }}</p>
}
@boundary {
<complex-chart [data]="data"/>
} @error (let err = $error; let r = $reset) {
<p>Failed to load chart: {{err.message}}</p>
<button (click)="r()">Retry</button>
}
@boundary {
Outer Main
@boundary {
Inner Main
} @error {
Inner Fallback
}
} @error {
Outer Fallback
}
@defer {
@boundary {
<app-c1/>
} @error {
<span>Render error</span>
}
} @error {
<span>Load error</span>
}
@@ -12,6 +12,7 @@ exports[`angular blocks 1`] = `
"@defer", "@defer",
"@placeholder", "@placeholder",
"@loading", "@loading",
"@boundary",
"@error", "@error",
"@content", "@content",
] ]
+5 -5
View File
@@ -3334,10 +3334,10 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"angular-html-parser@npm:10.11.0": "angular-html-parser@npm:10.12.0":
version: 10.11.0 version: 10.12.0
resolution: "angular-html-parser@npm:10.11.0" resolution: "angular-html-parser@npm:10.12.0"
checksum: 10/b311b3e984eaba422d41ecff8ea1255c1e5d5cd86ffd88f8651b12323b33104a0099711e1f4104dfe4f4383ff926ac43df4f072bf616f5dbbc5e86ef76e33383 checksum: 10/d71e0faa33c3ad91d07a71f4c5878eb047bb42f506fef3b66d15c5dd94a59eba12599de186adf4518ebe28a2f58b669394943281b91aebefed35987d8e29cbbe
languageName: node languageName: node
linkType: hard linkType: hard
@@ -8299,7 +8299,7 @@ __metadata:
acorn: "npm:8.18.0" acorn: "npm:8.18.0"
acorn-jsx: "npm:5.3.2" acorn-jsx: "npm:5.3.2"
angular-estree-parser: "npm:15.5.0" angular-estree-parser: "npm:15.5.0"
angular-html-parser: "npm:10.11.0" angular-html-parser: "npm:10.12.0"
base64-arraybuffer-es6: "npm:3.1.0" base64-arraybuffer-es6: "npm:3.1.0"
browserslist: "npm:4.28.8" browserslist: "npm:4.28.8"
browserslist-to-esbuild: "npm:2.1.1" browserslist-to-esbuild: "npm:2.1.1"