@@ -5,12 +5,15 @@ title: 'Effects'
5
5
hide_title : true
6
6
---
7
7
8
+ import Tabs from ' @theme/Tabs' ;
9
+ import TabItem from ' @theme/TabItem' ;
10
+
8
11
# @rx-angular/state /effects
9
12
10
13
[ ![ npm] ( https://img.shields.io/npm/v/%40rx-angular%2Fstate.svg )] ( https://www.npmjs.com/package/%40rx-angular%2Fstate )
11
14
![ rx-angular CI] ( https://github.com/rx-angular/rx-angular/workflows/rx-angular%20CI/badge.svg?branch=master )
12
15
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 .
14
17
15
18
` @rx-angular/state/effects ` is a small set of helpers designed to handle effects.
16
19
@@ -74,53 +77,54 @@ To accomplish this, we need to make sure to clean up every side effect in the `O
74
77
75
78
![ rx-angular--state--effects--motivation-when-to-use--michael-hladky] ( https://user-images.githubusercontent.com/10064416/154174403-5ab34eb8-68e4-40f9-95de-12a62784ac40.png )
76
79
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.
81
84
82
85
``` typescript
83
86
import { rxEffects } from ' @rx-angular/state/effects' ;
84
87
import { inject , Component } from ' @angular/core' ;
88
+ import { fromEvent } from ' rxjs' ;
85
89
86
90
@Component ({})
87
91
export class MyComponent {
88
- private readonly util = inject (Util );
89
92
// 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 ' ) , () => {
93
96
console .log (' window was resized' );
94
97
});
95
98
96
99
// side effect that runs on component destruction
97
- registerUntilDestroy (() => {
100
+ onDestroy (() => {
98
101
console .log (' custom cleanup logic (e.g flushing local storage)' );
99
102
});
100
103
});
101
104
}
102
105
```
103
106
104
- ### Class Based
107
+ </TabItem >
108
+ <TabItem value = " class-based" label = " Class Based (Classic)" >
105
109
106
110
However, the class based approach is still valid and works exactly as before.
107
111
108
112
``` typescript
109
113
import { RxEffects } from ' @rx-angular/state/effects' ;
110
114
import { inject , Component } from ' @angular/core' ;
115
+ import { fromEvent } from ' rxjs' ;
111
116
112
117
@Component ({
113
118
// provide `RxEffects` as a local instance of your component
114
119
providers: [RxEffects ],
115
120
})
116
121
export class MyComponent {
117
- private readonly util = inject (Util );
118
122
// inject your provided instance
119
123
readonly effects = inject (RxEffects );
120
124
121
125
ngOnInit() {
122
126
// side effect that runs when `windowResize$` emits a value
123
- this .effects .register (this . util . windowResize$ , () => {
127
+ this .effects .register (fromEvent ( window , ' resize ' ) , () => {
124
128
console .log (' window was resized' );
125
129
});
126
130
// side effect that runs on component destruction
@@ -131,6 +135,9 @@ export class MyComponent {
131
135
}
132
136
```
133
137
138
+ </TabItem >
139
+ </Tabs >
140
+
134
141
### Register Multiple Observables
135
142
136
143
The register method can also be combined with tap or even subscribe:
@@ -158,24 +165,73 @@ effects.register(animationFrameScheduler.schedule(action));
158
165
159
166
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:
160
167
168
+ <Tabs >
169
+ <TabItem value = " new" label = " Functional Creation (_NEW_)" >
170
+
161
171
``` 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' ;
163
175
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
+ }
166
189
```
167
190
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
+
168
219
### Error handling
169
220
170
221
If an error is thrown inside one side-effect callback, other effects are not affected.
171
222
The built-in Angular ErrorHandler gets automatically notified of the error, so these errors should still show up in Rollbar reports.
172
223
173
224
However, there are additional ways to tweak the error handling.
174
225
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
+
175
229
We can hook into this process by providing a custom error handler:
176
230
177
231
``` 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' ;
179
235
180
236
@Component ({
181
237
providers: [
@@ -192,6 +248,59 @@ import { ErrorHandler } from '@angular/core';
192
248
class MyComponent {
193
249
readonly effects = rxEffects (({ register }) => {
194
250
// 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
+ );
195
304
});
196
305
}
197
306
```
@@ -377,10 +486,10 @@ export class MyComponent {
377
486
378
487
constructor () {
379
488
// [#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 );
381
490
382
491
// [#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 ();
384
493
}
385
494
}
386
495
```
0 commit comments