Guidelines for waiting on two Actions in @ngrx/effects

Is it possible to wait for two actions like Promise.all in NgRx? For example:

@Effect()
public addUser() {
   return this.actions$.ofType(user.ADD)
      .switchMap(() => {
         return this.userService.add();
      })
      .map(() => {
         return new user.AddSuccessAction();
      });
}

@Effect()
public addUserOptions() {
   return this.actions$.ofType(userOptions.ADD)
      .switchMap(() => {
         return this.userOptionsService.add();
      })
      .map(() => {
         return new userOptions.AddSuccessAction();
      });
}

@Effect()
public complete() {
   return this.actions$.ofType(user.ADD_SUCCESS, userOptions.ADD_SUCCESS)
      // how to mimic the behavior of Promise.all here?
      .switchMap(() => {
         return this.statisticService.add();
      })
      .map(() => {
         return new account.CompleteAction();
      });
}

UPDATED My goal is to achieve similar functionality to Promise.all. How can I trigger two effects concurrently, wait for them to finish, and then dispatch a third action? Something like . With promises, it was straightforward:

Promise.all([fetch1, fetch2]).then(fetch3);

Is this possible with NgRx/effects? Or am I approaching this the wrong way?

ANSWER

There are several options you can explore:

1) Avoid using generic actions.

Follow the guidelines from Myke Ryan's presentation: https://youtu.be/JmnsEvoy-gY

Pros: easier debugging process

Cons: requires lots of boilerplate code and actions

2) Utilize a complex stream with nested actions.

Refer to this article:

Here's a simple example handling two actions concurrently:

@Effect()
public someAction(): Observable<Action> {
    return this.actions$.pipe(
        ofType(actions.SOME_ACTION),
        map((action: actions.SomeAction) => action.payload),
        mergeMap((payload) => {
            const firstActionSuccess$ = this.actions$.pipe(
                ofType(actions.FIRST_ACTION_SUCCESS),
                takeUntil(this.actions$.pipe(ofType(actions.FIRST_ACTION_FAIL))),
                first(),
            );

            const secondActionsSuccess$ = this.actions$.pipe(
                ofType(actions.SECOND_ACTION_SUCCESS),
                takeUntil(this.actions$.pipe(ofType(actions.SECOND_ACTION_FAIL))),
                first(),
            );

            const result$ = forkJoin(firstActionSuccess$, secondActionsSuccess$).pipe(
                first(),
            )
                .subscribe(() => {
                    // perform desired tasks here
                });

            return [
                new actions.FirstAction(),
                new actions.SecondAction(),
            ];
        }),
    );
}

Pros: achievable objective

Cons: complex streams may become unwieldy and prone to errors; observables remain active until completion or failure, allowing interference from external actions

3) Implement an aggregator pattern.

Refer to Victor Savkin's presentation on State Management Patterns and Best Practices with NgRx: https://www.youtube.com/watch?v=vX2vG0o-rpM

4) Steer clear of effects for API calls. Instead, use traditional services that mimic effects but return Observables.

In your service:

public dispatchFirstAction(): Observable<void> {
    this.store.dispatch(new actions.FirstAction(filter));

    return this.service.someCoolMethod().pipe(
        map((data) => this.store.dispatch(new actions.FirstActionSuccess(data))),
        catchError((error) => {
            this.store.dispatch(new actions.FirstActionFail());

            return Observable.throw(error);
        }),
    );
}

You can later combine these in various ways, such as:

const result1$ = this.service.dispatchFirstAction();
const result2$ = this.service.dispatchSecondAction();

forkJoin(result1$, result2$).subscribe();

5) Consider trying ngxs: https://github.com/ngxs/store

Pros: reduced boilerplate, integrates well with Angular ecosystem, rapid growth

Cons: fewer features compared to NgRx

Answer №1

As a newcomer to RXJS, I have a question.

If you switch the tap to a switchMap, you can eliminate {dispatch: false}.

@Effect({dispatch: false})
public waitForActions(): Observable<any> {
    const waitFor: string[] = [
        SomeAction.EVENT_1,
        SomeAction.EVENT_2,
        SomeAction.EVENT_3,
    ];

    return this._actions$
        .pipe(
            ofType(...waitFor),
            distinct((action: IAction<any>) => action.type),
            bufferCount(waitFor.length),
            tap(console.log),
        );
}

Answer №2

In my experience with ngrx 8, this solution proved to be effective

waitForTwoActions$ = createEffect(() =>
    combineLatest([
      this.actions$.pipe(ofType(actions.action1)),
      this.actions$.pipe(ofType(actions.action2)),
    ]).pipe(
      switchMap(() => ...),
    )
  );

Answer №3

After utilizing the power of Observable.combineLatest, my solution fell into place.

@Effect()
  complete$ = this.actions$.ofType<Action1>(ACTION1).combineLatest(this.actions$.ofType<Action2>(ACTION2),
    (action1, action2) => {

      return new Action3();
    }
  ).take(1);

The use of take(1) ensures that only one instance of Action3() is dispatched.

Answer №4

Here is an updated version of combineLatest using pipes and switchMap

import { Observable, of } from 'rxjs'
import { combineLatest, switchMap, withLatestFrom } from 'rxjs/operators'

@Effect()
anotherEffect$: Observable<Actions> = this.actions$.pipe(
  ofType(Action1),
  combineLatest(this.actions$.ofType(Action2)),
  switchMap(() => of({ type: Action3 }))
)

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Strange Behavior of ngIf and @Input in Angular 2

Despite console indicating false, NgIf appears to always evaluate as true. The issue stems from the component's HTML below: <product-component-tree itemSku="{{item.itemSku}}" selectable="false" classes="col-md-12 col-xs-12"></product-compo ...

Angular 2 utilizes a "template" for ng-content that can be incorporated within a component loop

I am currently developing a component that will perform certain actions and iterate over a set of results. My goal is to have the ability to specify a "template" for the items in the result set. Here is the concept I have in mind: <search-field> ...

Are auto-properties supported in TypeScript yet?

I've heard that properties in Typescript can be defined like they are in C# with automatic setters and getters. However, I have found it difficult to implement properties this way as the intellisense does not support such syntax in Typescript. I tried ...

What is the best way to convert an HTML table into an array of objects?

In my Angular Protractor end-to-end (e2e) tests, I need to perform assertions on an HTML fragment similar to the following: <table> <thead> <tr> <th>Name</th> <th>Age</th> ...

List out all the classes that implement an interface in Typescript

Greetings to all TypeScript enthusiasts! Here's a challenge I want to tackle: I aim to establish an interface -- let's name it IShape -- and define several classes (Rectangle, Circle, Triangle) that adhere to the IShape interface. Let's sa ...

The Angular RouterOutlet exhibiting a diverse array of components

app.component.html includes the standard layout and the Routes configuration looks like this: const accountRoutes: Routes = [ { path: 'login', component: LoginComponent, pathMatch: 'full' }, { path: 'register', component: ...

How can I line up columns in an HTML table?

I am facing a challenge while trying to set up a basic form with a table in the initial stages of my Angular project. I have been struggling to get the columns to align properly. Despite numerous attempts with various styling options for HTML <table> ...

"Integrating a Jwt-secured Express.js Rest API with an Angular application: A step

I have secured my api with a json web token. How can I integrate this with my Angular app without requiring a Login and Register feature? Despite extensive research on Google, Youtube, and Stack Overflow, I have not been able to find a suitable solution. ...

Can you explain the significance of these specific HTML attributes within the button tag?

While browsing the web, I stumbled upon this HTML snippet: <button class="button--standard" mat-button mat-flat-button [disabled]=true >Disabled State</button> The combination of attributes like mat-button mat-flat-button [disabled]=true is u ...

Can you explain the meaning of the TypeScript code snippet below?

Recently, while going through the declaration file of reflect-metadata library, I came across the following code snippet. It appears that the function metadata is expected to return an object with 2 functions. Could someone kindly explain the purpose of ...

Angular: defining a new class instance and implementing various services

Creating a class called User raises the concern of having access to services within it, especially when using the constructor() for dependency injection. Below is an implementation of the User class: import { ElementRef, Injectable } from '@angular/ ...

Stop fullscreen mode in Angular after initiating in fullscreen mode

Issue with exiting full screen on Angular when starting Chrome in full-screen mode. HTML: <hello name="{{ name }}"></hello> <a href="https://angular-go-full-screen-f11-key.stackblitz.io" target="_blank" style=& ...

In Typescript, what sets apart a generic written before a function compared to after a type declaration?

Can you explain the difference between these two type declarations for arrow functions? export type Sort = <D>(r: Rows<D>, f: Field<D>, o: Order) => Rows<D>; export type Sort<D> = (r: Rows<D>, f: Field<D>, o: ...

How can eslint be used to enforce a particular named export?

Is there a way to use eslint to make it mandatory for JavaScript/TypeScript files to have a named export of a specific name? For instance, in the src/pages folder, I want all files to necessitate an export named config: Example of incorrect usage src/page ...

What is the best way to store a set of tuples in a collection so that each tuple is distinct and

I am working with TypeScript and aiming to create a collection of unique objects, each with distinct properties. The combinations of these properties within the collection must be one-of-a-kind. For example, the following combinations would be considered ...

The conversion of the property value from type 'java.lang.String' to the required type 'java.time.LocalDate' failed in the JHipster generated file

I have created the files through JHipster and currently facing an issue when trying to execute a query involving a Date variable. The conversion is failing. Below is my typescript file method that sets the criteria for the search: loadSearchPage(page?: ...

How can I implement a feature in Angular where clicking the edit button loads a form (in a separate component) pre-populated with previous data, along with an update button for

Within the employee-list component, there is a table displaying a list of employees. This table includes an option to edit details. <button type="button" class="btn btn-primary" routerLink="../create-employee">Edit</b ...

Using @ViewChild and AfterViewInit to access the nativeElement of a component

Note: While my question may seem similar to others, the specifics are different. I am attempting to execute a function on an element that is contained within a <ng-template>. Due to the fact that it is not rendered in the DOM, I am encountering diff ...

The attempt to convert the value "NaN" to a number (data type number) at the specified path "markupPercentage" was unsuccessful

When I try to use Excel to update products, I encounter an error related to the presence of NaN. Here is the specific error message: CastError: Cast to Number failed for value "NaN" (type number) at path "markupPercentage" messageFormat: undefine ...

Retrieve information using a GET request with a status code of 500

I'm currently utilizing Prisma, NextJS, and NextAuth for user authentication. I've set up a route to fetch the logged-in user's name and username from the homepage. Check out the code snippet below: import prisma from '../../../lib/pris ...