refactor
refactor

Reputation: 15064

Executing code after dispatch is completed while using ngrx

In my sample Angular 2 application , I am using ngrx/store and ngrx/effects for state management.

Below is one of the function in a component to add a new item.

addAuthor() {
    
    this.store.dispatch(addAuthorAction(this.fg.value));
    console.log('2')        
} 

In the above code this.store.dispatch(addAuthorAction(this.fg.value)); takes care of making an AJAX call to server and adding a new author to database, which is working fine.

And because this.store.dispatch(addAuthorAction(this.fg.value)); is an async action , console.log("2") statement gets executed even before the AJAX call is completed.

My question is , what needs to be modified so that console.log gets executed after store.dispatch is done.

Upvotes: 24

Views: 45055

Answers (6)

Edward Olamisan
Edward Olamisan

Reputation: 891

The accepted answer is correct, but I find the accepted answer is missing an expanded example.

private destroyRef = inject(DestroyRef);

submitToServer(formData): Observable<any> {
  const correlationid = ...;
  setTimeout(() => {
    this.store.dispatch(
      userActions.FETCH_USER({ correlationid }),
    );
  });

  return this.actions$.pipe(
    ofType(userActions.FETCH_USER_SUCCESS),
    filter((action) => action.correlationid === correlationid)
    tap((action) => {
      console.log('2');
      console.log('FETCH_USER_SUCCESS', action);
    }),
    takeUntilDestroyed(this.destroyRef),
  );
}

Summary:

  • we generally need three actions per http request (e.g. FETCH_USER, FETCH_USER_SUCCESS, FETCH_USER_ERROR)
  • correlationid - optional. something uniquely identifying the request
  • setTimeout(...) - it's important to listen to the FETCH_USER_SUCCESS action before dispatch
  • this.actions$.pipe(...) - filter store actions for desired action (i.e. FETCH_USER_SUCCESS)
  • filter(...) - optional. additional filter if concurrent actions are expected
  • tap(..) - optional. execute any component code.
  • takeUntilDestroyed - stop piping events once the component has been destroyed

Upvotes: 0

Mateusz Wąsik
Mateusz Wąsik

Reputation: 33

Today I have similar issue and I use tap() to solve this. When are you on your effect import tap from "rxjs/operators"

import { tap } from "rxjs/operators";

and next use it in your switchMap after map operation on SuccessAction

this.service.save(option).pipe(
                map((result) => action.saveSuccess({ option: option})),
                tap(() => this.router.navigate(['link'])),
                catchError((error) => of(actions.saveError({ error }))

this code work form me.

Upvotes: 0

vinsinraw
vinsinraw

Reputation: 2125

As dispatch is asynchronous (fire and forget), we can subscribe to the returned observable object and then execute the next statement once we get hold of observable object.

addAuthor() {
    this.store.dispatch(addAuthorAction(this.fg.value))
    .subscribe(() => {
        console.log('2')
    });           
}

Alternatively,

myObservableObject$: Observable<any>;
this.myObservableObject$ = this.store.dispatch(addAuthorAction(this.fg.value));
this.myObservableObject$.subscribe((response) => {
    console.log('2')
});

The above is per ngxs state management framework. More @ https://www.ngxs.io/advanced/actions-life-cycle#asynchronous-actions

Upvotes: -4

maxime1992
maxime1992

Reputation: 23803

Quick answer : You can't.

As you said, dispatch is asynchronous.

What you should do is use @ngrx/effects. It's nearly the same as using addAuthorAction except that instead of calling a function, you "catch" the dispatched actions and do something just after they've been applied by the reducers.

So what I do in general, is that I divide my actions in 3, for example :

  • FETCH_USER

  • FETCH_USER_SUCCESS

  • FETCH_USER_ERROR

  • FETCH_USER is just used to toggle a boolean so I can display a spinner while fetching the user

  • I catch this action from an effect and make an http request to fetch the user

  • If the http response is OK and I have the info I'm looking for, I dispatch from the effect FETCH_USER_SUCCESS with the response as payload, otherwise I dispatch FETCH_USER_ERROR and I toggle the boolean to false (so we can try to fetch him again for example).

So in your example, if you want to console.log something AFTER the FETCH_USER_SUCCESS, just use another effect to catch the FETCH_USER_SUCCESS and do what you want to here.

Upvotes: 24

danday74
danday74

Reputation: 57026

With ngrx you can listen to actions like this:

constructor(private actionsSubject$: ActionsSubject, private store: Store<AppState>) {}

ngOnInit() {
  this.actionsSubject$.pipe(
    takeUntil(this.unsubscribe$), // optional
    filter((action) => action.type === SimsActionTypes.SimEditedSuccess)
  ).subscribe(({payload}) => {
    console.log(payload)
  )
}

When you dispatch FIRST_ACTION use an effect to make the HTTP request. In the effect, when you have the response back, fire off a SECOND_ACTION with the response as the payload. Then just listen for SECOND_ACTION in your controller .ts file

Upvotes: 7

Abhay
Abhay

Reputation: 6760

Tested with

"@ngrx/core": "^1.2.0",
"@ngrx/effects": "^7.4.0",
"@ngrx/router-store": "^7.4.0",
"@ngrx/store": "^7.4.0",
"@ngrx/store-devtools": "^7.4.0",

This will also work:

import { ofType, Actions } from '@ngrx/effects';

// Constructor
constructor(
   private _actions$: Actions,
   private _store: Store<any>,
) { }

// YOUR METHOD    
this._store.dispatch(<ACTION>);
this._actions$.pipe(ofType(<ACTION_NAME>)).subscribe((data: any) => {
  console.log(data);  // returned state data        
})

Upvotes: 18

Related Questions