Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit d3ea0e3

Browse files
committed
docs(state): improve rx effects docs
1 parent 17d4823 commit d3ea0e3

File tree

1 file changed

+128
-19
lines changed

1 file changed

+128
-19
lines changed

apps/docs/docs/state/effects/effects.mdx

Lines changed: 128 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,15 @@ title: 'Effects'
55
hide_title: true
66
---
77

8+
import Tabs from '@theme/Tabs';
9+
import TabItem from '@theme/TabItem';
10+
811
# @rx-angular/state/effects
912

1013
[![npm](https://img.shields.io/npm/v/%40rx-angular%2Fstate.svg)](https://www.npmjs.com/package/%40rx-angular%2Fstate)
1114
![rx-angular CI](https://github.com/rx-angular/rx-angular/workflows/rx-angular%20CI/badge.svg?branch=master)
1215

13-
> A small typed convenience helper to handle side effects and Observable subscriptions.
16+
> A small convenience helper to handle side effects based on Observable inputs.
1417
1518
`@rx-angular/state/effects` is a small set of helpers designed to handle effects.
1619

@@ -74,53 +77,54 @@ To accomplish this, we need to make sure to clean up every side effect in the `O
7477

7578
![rx-angular--state--effects--motivation-when-to-use--michael-hladky](https://user-images.githubusercontent.com/10064416/154174403-5ab34eb8-68e4-40f9-95de-12a62784ac40.png)
7679

77-
### Functional Creation (_NEW_)
78-
79-
The new functional creation API lets you create and configure `RxEffects` in only one place.
80-
Thanks to the new `DestroyRef`, there is no need for manually providing an instance anymore.
80+
<Tabs>
81+
<TabItem value="new" label="Functional Creation (_NEW_)">
82+
The new functional creation API lets you create and configure `RxEffects` in only one place.
83+
Thanks to the new `DestroyRef`, there is no need for manually providing an instance anymore.
8184

8285
```typescript
8386
import { rxEffects } from '@rx-angular/state/effects';
8487
import { inject, Component } from '@angular/core';
88+
import { fromEvent } from 'rxjs';
8589

8690
@Component({})
8791
export class MyComponent {
88-
private readonly util = inject(Util);
8992
// create and configure `RxEffects` in a single step
90-
readonly effects = rxEffects(({ register, registerUntilDestroy }) => {
91-
// side effect that runs when `windowResize$` emits a value
92-
register(this.util.windowResize$, () => {
93+
readonly effects = rxEffects(({ register, onDestroy }) => {
94+
// side effect that runs when `window resize` emits a value
95+
register(fromEvent(window, 'resize'), () => {
9396
console.log('window was resized');
9497
});
9598

9699
// side effect that runs on component destruction
97-
registerUntilDestroy(() => {
100+
onDestroy(() => {
98101
console.log('custom cleanup logic (e.g flushing local storage)');
99102
});
100103
});
101104
}
102105
```
103106

104-
### Class Based
107+
</TabItem>
108+
<TabItem value="class-based" label="Class Based (Classic)">
105109

106110
However, the class based approach is still valid and works exactly as before.
107111

108112
```typescript
109113
import { RxEffects } from '@rx-angular/state/effects';
110114
import { inject, Component } from '@angular/core';
115+
import { fromEvent } from 'rxjs';
111116

112117
@Component({
113118
// provide `RxEffects` as a local instance of your component
114119
providers: [RxEffects],
115120
})
116121
export class MyComponent {
117-
private readonly util = inject(Util);
118122
// inject your provided instance
119123
readonly effects = inject(RxEffects);
120124

121125
ngOnInit() {
122126
// side effect that runs when `windowResize$` emits a value
123-
this.effects.register(this.util.windowResize$, () => {
127+
this.effects.register(fromEvent(window, 'resize'), () => {
124128
console.log('window was resized');
125129
});
126130
// side effect that runs on component destruction
@@ -131,6 +135,9 @@ export class MyComponent {
131135
}
132136
```
133137

138+
</TabItem>
139+
</Tabs>
140+
134141
### Register Multiple Observables
135142

136143
The register method can also be combined with tap or even subscribe:
@@ -158,24 +165,73 @@ effects.register(animationFrameScheduler.schedule(action));
158165

159166
All registered effects are automatically unsubscribed when the component is destroyed. If you wish to cancel a specific effect earlier, you can do this either declaratively (obs$.pipe(takeUntil(otherObs$))) or imperatively using the returned effect ID:
160167

168+
<Tabs>
169+
<TabItem value="new" label="Functional Creation (_NEW_)">
170+
161171
```typescript
162-
this.effectId = this.effects.register(obs$, doSideEffect);
172+
import { rxEffects } from '@rx-angular/state/effects';
173+
import { Component } from '@angular/core';
174+
import { fromEvent } from 'rxjs';
163175

164-
// later
165-
this.effects.unregister(this.effectId); // doSideEffect will no longer be called
176+
@Component({})
177+
export class MyComponent {
178+
// create and configure `RxEffects` in a single step
179+
effects = rxEffects();
180+
resizeEffect = this.effects.register(fromEvent(window, 'resize'), () => {
181+
console.log('window was resized');
182+
});
183+
184+
undoResizeEffect() {
185+
// effect is now unsubscribed
186+
this.resizeEffect();
187+
}
188+
}
166189
```
167190

191+
</TabItem>
192+
<TabItem value="class-based" label="Class Based (Classic)">
193+
194+
```typescript
195+
import { RxEffects } from '@rx-angular/state/effects';
196+
import { Component, inject } from '@angular/core';
197+
import { fromEvent } from 'rxjs';
198+
199+
@Component({
200+
providers: [RxEffects],
201+
})
202+
export class MyComponent {
203+
// create and configure `RxEffects` in a single step
204+
effects = inject(RxEffects);
205+
resizeEffect = this.effects.register(fromEvent(window, 'resize'), () => {
206+
console.log('window was resized');
207+
});
208+
209+
undoResizeEffect() {
210+
// effect is now unsubscribed
211+
this.effects.unregister(this.resizeEffect);
212+
}
213+
}
214+
```
215+
216+
</TabItem>
217+
</Tabs>
218+
168219
### Error handling
169220

170221
If an error is thrown inside one side-effect callback, other effects are not affected.
171222
The built-in Angular ErrorHandler gets automatically notified of the error, so these errors should still show up in Rollbar reports.
172223

173224
However, there are additional ways to tweak the error handling.
174225

226+
Note that your subscription ends after an error occurred. If the stream encountered an error once, it is done
227+
Read more about how to recover from this in the next section.
228+
175229
We can hook into this process by providing a custom error handler:
176230

177231
```typescript
178-
import { ErrorHandler } from '@angular/core';
232+
import { ErrorHandler, Component } from '@angular/core';
233+
import { throwError } from 'rxjs';
234+
import { rxEffects } from '@rx-angular/state/effects';
179235

180236
@Component({
181237
providers: [
@@ -192,6 +248,59 @@ import { ErrorHandler } from '@angular/core';
192248
class MyComponent {
193249
readonly effects = rxEffects(({ register }) => {
194250
// if your effects runs into an error, your custom errorHandler will be informed
251+
register(throwError('E'));
252+
});
253+
}
254+
```
255+
256+
### Retry on error
257+
258+
In order to recover from an error state and keep the side effect alive, you have two options:
259+
260+
- [`retry`](https://rxjs.dev/api/operators/retry)
261+
- [`catchError`](https://rxjs.dev/api/index/function/catchError)
262+
263+
```typescript
264+
import { Component, inject } from '@angular/core';
265+
import { HttpClient } from '@angular/common/http';
266+
import { retry, catchError, of, exhaustMap, Subject } from 'rxjs';
267+
import { rxEffects } from '@rx-angular/state/effects';
268+
269+
@Component()
270+
class MyComponent {
271+
http = inject(HttpClient);
272+
login$ = new Subject<{ user: string; pass: string }>();
273+
274+
readonly effects = rxEffects(({ register }) => {
275+
register(
276+
this.login$.pipe(
277+
exhaustMap(({ user, pass }) =>
278+
this.http.post('/auth/', { user, pass })
279+
),
280+
// retry when an error occurs
281+
retry()
282+
),
283+
(data) => {
284+
alert(`welcome ${data.user}`);
285+
}
286+
);
287+
288+
register(
289+
this.login$.pipe(
290+
exhaustMap(({ user, pass }) =>
291+
this.http.post('/auth/', { user, pass })
292+
),
293+
// catch the error and return a custom value
294+
catchError((err) => {
295+
return of(null);
296+
})
297+
),
298+
(data) => {
299+
if (data) {
300+
alert(`welcome ${data.user}`);
301+
}
302+
}
303+
);
195304
});
196305
}
197306
```
@@ -377,10 +486,10 @@ export class MyComponent {
377486

378487
constructor() {
379488
// [#1st approach] The subscribe's next callback it used to wrap and execute the side effect
380-
effect$.subscribe(this.runSideEffect);
489+
this.effect$.subscribe(this.runSideEffect);
381490

382491
// [#2nd approach] `tap` is used to wrap and execute the side effect
383-
effect$.pipe(tap(this.runSideEffect)).subscribe();
492+
this.effect$.pipe(tap(this.runSideEffect)).subscribe();
384493
}
385494
}
386495
```

0 commit comments

Comments
 (0)