Skip to content

Commit 55e3580

Browse files
committed
translate: translation for zoneless guide
Fixes #117
1 parent 84148e5 commit 55e3580

File tree

2 files changed

+216
-88
lines changed

2 files changed

+216
-88
lines changed
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
# Angular without ZoneJS (Zoneless)
2+
3+
## Why use Zoneless?
4+
5+
The main advantages to removing ZoneJS as a dependency are:
6+
7+
- **Improved performance**: ZoneJS uses DOM events and async tasks as indicators of when application state _might_ have updated and subsequently triggers application synchronization to run change detection on the application's views. ZoneJS does not have any insight into whether application state actually changed and so this synchronization is triggered more frequently than necessary.
8+
- **Improved Core Web Vitals**: ZoneJS brings a fair amount of overhead, both in payload size and in startup time cost.
9+
- **Improved debugging experience**: ZoneJS makes debugging code more difficult. Stack traces are harder to understand with ZoneJS. It's also difficult to understand when code breaks as a result of being outside the Angular Zone.
10+
- **Better ecosystem compatibility**: ZoneJS works by patching browser APIs but does not automatically have patches for every new browser API. Some APIs cannot be patched effectively, such as `async`/`await`, and have to be downleveled to work with ZoneJS. Sometimes libraries in the ecosystem are also incompatible with the way ZoneJS patches the native APIs. Removing ZoneJS as a dependency ensures better long-term compatibility by removing a source of complexity, monkey patching, and ongoing maintenance.
11+
12+
## Enabling Zoneless in an application
13+
14+
```typescript
15+
// standalone bootstrap
16+
bootstrapApplication(MyApp, {providers: [
17+
provideZonelessChangeDetection(),
18+
]});
19+
20+
// NgModule bootstrap
21+
platformBrowser().bootstrapModule(AppModule);
22+
@NgModule({
23+
providers: [provideZonelessChangeDetection()]
24+
})
25+
export class AppModule {}
26+
```
27+
28+
## Removing ZoneJS
29+
30+
Zoneless applications should remove ZoneJS entirely from the build to reduce bundle size. ZoneJS is typically
31+
loaded via the `polyfills` option in `angular.json`, both in the `build` and `test` targets. Remove `zone.js`
32+
and `zone.js/testing` from both to remove it from the build. Projects which use an explicit `polyfills.ts` file
33+
should remove `import 'zone.js';` and `import 'zone.js/testing';` from the file.
34+
35+
After removing ZoneJS from the build, there is no longer a need for a `zone.js` dependency either and the
36+
package can be removed entirely:
37+
38+
```shell
39+
npm uninstall zone.js
40+
```
41+
42+
## Requirements for Zoneless compatibility
43+
44+
Angular relies on notifications from core APIs in order to determine when to run change detection and on which views.
45+
These notifications include:
46+
47+
- `ChangeDetectorRef.markForCheck` (called automatically by `AsyncPipe`)
48+
- `ComponentRef.setInput`
49+
- Updating a signal that's read in a template
50+
- Bound host or template listeners callbacks
51+
- Attaching a view that was marked dirty by one of the above
52+
53+
### `OnPush`-compatible components
54+
55+
One way to ensure that a component is using the correct notification mechanisms from above is to
56+
use [ChangeDetectionStrategy.OnPush](/best-practices/skipping-subtrees#using-onpush).
57+
58+
The `OnPush` change detection strategy is not required, but it is a recommended step towards zoneless compatibility for application components. It is not always possible for library components to use `ChangeDetectionStrategy.OnPush`.
59+
When a library component is a host for user-components which might use `ChangeDetectionStrategy.Default`, it cannot use `OnPush` because that would prevent the child component from being refreshed if it is not `OnPush` compatible and relies on ZoneJS to trigger change detection. Components can use the `Default` strategy as long as they notify Angular when change detection needs to run (calling `markForCheck`, using signals, `AsyncPipe`, etc.).
60+
Being a host for a user component means using an API such as `ViewContainerRef.createComponent` and not just hosting a portion of a template from a user component (i.e. content projection or a using a template ref input).
61+
62+
### Remove `NgZone.onMicrotaskEmpty`, `NgZone.onUnstable`, `NgZone.isStable`, or `NgZone.onStable`
63+
64+
Applications and libraries need to remove uses of `NgZone.onMicrotaskEmpty`, `NgZone.onUnstable` and `NgZone.onStable`.
65+
These observables will never emit when an Application enables zoneless change detection.
66+
Similarly, `NgZone.isStable` will always be `true` and should not be used as a condition for code execution.
67+
68+
The `NgZone.onMicrotaskEmpty` and `NgZone.onStable` observables are most often used to wait for Angular to
69+
complete change detection before performing a task. Instead, these can be replaced by `afterNextRender`
70+
if they need to wait for a single change detection or `afterEveryRender` if there is some condition that might span
71+
several change detection rounds. In other cases, these observables were used because they happened to be
72+
familiar and have similar timing to what was needed. More straightforward or direct DOM APIs can be used instead,
73+
such as `MutationObserver` when code needs to wait for certain DOM state (rather than waiting for it indirectly
74+
through Angular's render hooks).
75+
76+
<docs-callout title="NgZone.run and NgZone.runOutsideAngular are compatible with Zoneless">
77+
`NgZone.run` and `NgZone.runOutsideAngular` do not need to be removed in order for code to be compatible with
78+
Zoneless applications. In fact, removing these calls can lead to performance regressions for libraries that
79+
are used in applications that still rely on ZoneJS.
80+
</docs-callout>
81+
82+
### `PendingTasks` for Server Side Rendering (SSR)
83+
84+
If you are using SSR with Angular, you may know that it relies on ZoneJS to help determine when the application
85+
is "stable" and can be serialized. If there are asynchronous tasks that should prevent serialization, an application
86+
not using ZoneJS must make Angular aware of these with the [PendingTasks](/api/core/PendingTasks) service. Serialization
87+
will wait for the first moment that all pending tasks have been removed.
88+
89+
The two most straightforward uses of pending tasks are the `run` method:
90+
91+
```typescript
92+
const taskService = inject(PendingTasks);
93+
taskService.run(async () => {
94+
const someResult = await doSomeWorkThatNeedsToBeRendered();
95+
this.someState.set(someResult);
96+
});
97+
```
98+
99+
For more complicated use-cases, you can manually add and remove a pending task:
100+
101+
```typescript
102+
const taskService = inject(PendingTasks);
103+
const taskCleanup = taskService.add();
104+
try {
105+
await doSomeWorkThatNeedsToBeRendered();
106+
} catch {
107+
// handle error
108+
} finally {
109+
taskCleanup();
110+
}
111+
```
112+
113+
In addition, the [pendingUntilEvent](/api/core/rxjs-interop/pendingUntilEvent#) helper in `rxjs-interop` ensures
114+
the application remains unstable until the observable emits, completes, errors, or is unsubscribed.
115+
116+
```typescript
117+
readonly myObservableState = someObservable.pipe(pendingUntilEvent());
118+
```
119+
120+
The framework uses this service internally as well to prevent serialization until asynchronous tasks are complete. These include, but are not limited to,
121+
an ongoing Router navigation and an incomplete `HttpClient` request.
122+
123+
## Testing and Debugging
124+
125+
### Using Zoneless in `TestBed`
126+
127+
The zoneless provider function can also be used with `TestBed` to help
128+
ensure the components under test are compatible with a Zoneless
129+
Angular application.
130+
131+
```typescript
132+
TestBed.configureTestingModule({
133+
providers: [provideZonelessChangeDetection()]
134+
});
135+
136+
const fixture = TestBed.createComponent(MyComponent);
137+
await fixture.whenStable();
138+
```
139+
140+
To ensure tests have the most similar behavior to production code,
141+
avoid using `fixture.detectChanges()` when possible. This forces
142+
change detection to run when Angular might otherwise have not
143+
scheduled change detection. Tests should ensure these notifications
144+
are happening and allow Angular to handle when to synchronize
145+
state rather than manually forcing it to happen in the test.
146+
147+
For existing test suites, using `fixture.detectChanges()` is a common pattern
148+
and it is likely not worth the effort of converting these to
149+
`await fixture.whenStable()`. `TestBed` will still enforce that the
150+
fixture's component is `OnPush` compatible and throws `ExpressionChangedAfterItHasBeenCheckedError`
151+
if it finds that template values were updated without a
152+
change notification (i.e. `fixture.componentInstance.someValue = 'newValue';`).
153+
If the component is used in production, this issue should be addressed by updating
154+
the component to use signals for state or call `ChangeDetectorRef.markForCheck()`.
155+
If the component is only used as a test wrapper and never used in an application,
156+
it is acceptable to use `fixture.changeDetectorRef.markForCheck()`.
157+
158+
### Debug-mode check to ensure updates are detected
159+
160+
Angular also provides an additional tool to help verify that an application is making
161+
updates to state in a zoneless-compatible way. `provideCheckNoChangesConfig({exhaustive: true, interval: <milliseconds>})`
162+
can be used to periodically check to ensure that no bindings have been updated
163+
without a notification. Angular throws `ExpressionChangedAfterItHasBeenCheckedError`
164+
if there is an updated binding that would not have refreshed by the zoneless change
165+
detection.

0 commit comments

Comments
 (0)