Exploring the utilization of async and await within NGRX effect in Angular

My current project involves working with NGRX for State Management in Angular 11.

On component load, I am displaying a list of tenants. To achieve this, I'm utilizing NGRX effects to make an HTTP request through a service.

This is the NGRX effect implementation:

@Injectable()
export class TenantsListEffects {
constructor(private actions$: Actions, private tenantListService: TenantAdministrationService) { }

loadTenants$ = createEffect(() => this.actions$.pipe(
ofType(TenantsListActions.TenantsListActionTypes.LoadTenantsLists),

switchMap(((action) => this.tenantListService.getAllTenantsUsingGET().pipe(

map(tenants => { return new TenantsListActions.LoadTenantsListsSuccess({ data: tenants }) }),
catchError(err => of(new TenantsListActions.LoadTenantsListsFailure({ error: err })))
)
))
))
}

Here is my tenantList.reducer.ts file:

export const tenantsListFeatureKey = 'tenantsListState';

export interface tenantsListState {
tenants: DisplayNameToTenantMappingResponse[],
loading: boolean,
error: string
}

export const initialState: tenantsListState = {
tenants: [],
loading: false,
error: ''
};

export function reducer(state = initialState, action: TenantsListActions): tenantsListState {
switch (action.type) {
case TenantsListActionTypes.LoadTenantsLists:
return {
...state,
loading: true
}
case TenantsListActionTypes.LoadTenantsListsSuccess:
return {
...state,
tenants: action.payload.data,
loading:false,
error: null
}
case TenantsListActionTypes.LoadTenantsListsFailure:
return {
...state,
error: action.payload.error,
loading: false
}

default:
return state;
}
}

And here is my selector:

const getTenantsListState = createFeatureSelector<tenantsListState>('tenantsListState');

export const getTenants = createSelector(
getTenantsListState,
state => state.tenants
)

export const getError = createSelector(
getTenantsListState,
state => state.error
)

Lastly, here is the service implementation:

@Injectable({
providedIn: 'root'
})
export class TenantAdministrationService {
public basePath: string ='';
constructor(private basePathService: BasePathService,private http: HttpClient) {
this.basePath = this.basePathService.selectEnv();
}

public getAllTenantsUsingGET():Observable<DisplayNameToTenantMappingResponse[]>{
let test =  this.http.get<Array<DisplayNameToTenantMappingResponse>> (`${this.basePath}/admin/tenant`);
console.log('get list ', test);

return test
}

The issue I'm facing:

Initially, when I call the service, it doesn't return anything, resulting in nothing being stored in my NGRX store. However, after 2-3 seconds, the tenant list is returned and stored in the store as a state.

I attempted to make the service call method asynchronous, but it led to errors in the Effect.

If anyone has suggestions on how to handle this situation, please share your insights.

https://i.sstatic.net/stw5E.png

Answer №1

To tackle the issue, I utilized a switchMap method to invoke an asynchronous function and then employed a map function to handle the outcome of the asynchronous operation. My current setup involves Angular 13 along with NgRx 13.

Here's my implementation:

  loadUser$ = createEffect(() => this.actions$.pipe(
    ofType(AuthEvent),
    concatLatestFrom( () => [
      this.store.select(getUid),
      this.store.select(getEmail)
    ]),
    switchMap( ([action, uid, email]) => this.service.loadUser(uid, email)),
    map( user => {
      console.log(user);
      switch (user.level) {
         ....
      }
    }),
    catchError( error => {
      console.error(error);
      return of(UnexpectedError());
    })    
  ));

This is how I'm approaching it:

  loadUser$ = createEffect(() => this.actions$.pipe(
    ofType(...),
    concatLatestFrom(...),
    switchMap( () => this.service.anAsyncFuntion()),
    map(...),
    catchError(...)    
  ));

Rather than this approach:

  loadUser$ = createEffect(() => this.actions$.pipe(
    ofType(...),
    concatLatestFrom(...),
    switchMap( () => this.service.anAsyncFuntion()).pipe(
       map(...),
       catchError(...)
    )
  ));

I have chosen not to use pipe method for handling the output from switchMap.

Answer №2

It appears that your setup is correct, but I noticed an extra parenthesis ( after switchMap. If you remove it, everything should work smoothly.

loadTenants$ = createEffect(() => this.actions$.pipe(
  ofType(TenantsListActions.TenantsListActionTypes.LoadTenantsLists),
  switchMap(action => this.tenantListService.getAllTenantsUsingGET().pipe(
    map(tenants => new TenantsListActions.LoadTenantsListsSuccess({ data: tenants }),
    catchError(err => of(new TenantsListActions.LoadTenantsListsFailure({ 
  error: err })))
  )
);

Just a reminder, ensure that your reducer adds the entities to state before the store selector emits them.

const reducer = createReducer(
  initialState,
  on(TenantsListActions.LoadTenantsListsSuccess, (state, { data }) => featureAdapter.setAll(data, {
    ...state,
    isLoading: false,
    error: null,
  })),
  ...
);

In the above code snippet, I'm utilizing an entity adapter to incorporate the entities into the state.

Answer №3

After successfully tackling the issue, I implemented a solution by utilizing the state property for loading and making it an observable within my component.

export interface tenantsListState {
     tenants: DisplayNameToTenantMappingResponse[],
     loading: boolean,
     error: string
    }

This mechanism allows for setting loading = true; during the http call, prompting a spinner to halt the component's loading process until the completion of the http request.

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

Encountering difficulties when transferring Angular2 example from Plnkr to a live application

Currently, I am attempting to integrate HighStocks into an Angular2 application by following the example provided in this plnkr link... http://plnkr.co/edit/2xSewTZ9b213vA0ALmFq?p=preview I am encountering a challenge in understanding how to include highs ...

Error message received when making an API call in React Native for Android and saving the response to a local database: 'Error: Network

Despite using axios and promises to make a call to a local database API, I am facing difficulties reaching the endpoint as I constantly receive a 'Error: Network Error' feedback in the console without any further explanation on the root cause of ...

How can I reverse engineer the output of "ng build prod" in Angular 2?

Can one examine the components, services, and functions that produced these files initially? ...

Ensuring Input Validity in Angular4 and Symfony3

Currently, I am utilizing angular 4 and symfony3. In my application, there is a textarea that is required. However, when I only press enter (code 13) in this textarea without entering any other character, the form gets submitted. How can I prevent this spe ...

angular exploring the ins and outs of implementing nested child routing

Within my app-routing.module.ts file, I define the loading of child components like so: { path: 'account', loadChildren: () => import('./components/account/account.module').then((esm) => esm.AccountModule) }, Followin ...

Challenges in Power BI Custom Visual Development: Trouble setting height for div elements

Currently, I am working on creating a custom visual for Power BI that includes a leaflet map within a div element. However, the issue arises when I fail to set a specific height for the map, resulting in an empty visual. I have managed to set a fixed heigh ...

Issue with Angular Material Mat Horizontal Stepper: Incorrect step selection when progressing with next button

I have a horizontal mat stepper that needs to be controlled using Next and Back buttons. The steps are generated using ngFor. I have created a variable called "stepIndex" which is bound to the "selectedIndex" input. The Next button should increment this va ...

Suggestions for enhancing or troubleshooting Typescript ts-node compilation speed?

Recently, I made the switch to TypeScript in my codebase. It consists of approximately 100k lines spread across hundreds of files. Prior to the migration, my launch time was an impressive 2 seconds when using ESLint with --fix --cache. However, after impl ...

What is the reason behind the ability to assign any single parameter function to the type `(val: never) => void` in TypeScript?

Take a look at the code snippet below interface Fn { (val: never): void } const fn1: Fn = () => {} const fn2: Fn = (val: number) => {} const fn3: Fn = (val: { canBeAnyThing: string }) => {} Despite the lack of errors, I find it puzzling. For ...

NVDA now prioritizes reading only the most recently loaded content in a div, disregarding the rest

When loading 3 different div elements dynamically based on search criteria, I have added attributes "role='alert'", "tabindex='0'", and "aria-live='polite'" to each div. The DOM loads all 3 div el ...

Error encountered during conversion from JavaScript to TypeScript

I am currently in the process of converting JavaScript to TypeScript and I've encountered the following error: Type '(props: PropsWithChildren) => (any[] | ((e: any) => void))[]' is not assignable to type 'FC'. Type '(a ...

The variable X has been defined, but it's never actually utilized. Despite declaring it, I have not accessed its

I have encountered warnings in VSCode while using certain properties in my Angular component. The warnings state: '_id' is declared but its value is never read.ts(6133) (property) ItemEditComponent._id: number | undefined '_isModeEdit' ...

Warning: React Hook Form, NumericFormat, and Material-UI's TextField combination may trigger a reference alert

I'm currently working on a Next.js project using TypeScript and MUI. I'm in the process of creating a form that includes a numeric field for monetary values. In order to validate all fields upon form submission, I have decided to utilize yup, rea ...

The Typescript interface requires that one prop representing a number is expected to be less than another number prop

Is there a way to create a TypeScript interface that enforces a condition where a number property must always be less than another number property in the interface? For instance, if I have a numberOfFiles property and a currentFileIndex property, and I wa ...

There was an issue while attempting to differentiate '[object Object]'. Ionic only allows arrays and iterables for this operation

I am looking for a way to extract all the "friend" objects from a JSON response and store them in an array so that I can iterate through them on an HTML webpage. ...

Creating dirty or touched input programmatically in Angular2 can be achieved by using the `.markAsT

Looking to integrate a JQuery plugin with Angular2? Essentially, this plugin generates a duplicate of specific HTML content containing inputs. As data is modified, Angular2 automatically re-renders the content along with any errors. However, the challenge ...

Angular 2 event of alteration - modification of the model

Is there a way to retrieve the values after a model has been updated? It seems like the (change) event triggers before the model update. I'm looking for an alternative to using event.target.value <input type="checkbox" (change)="mychange(event)" ...

Using ngFor to create dynamic columns in a Kendo Grid

Looking at this code snippet <kendo-grid-column title="{{localizationKeys.adempimenti.organizzazione.responsabile}}" field="addettiGrid"> <li *ngFor="let addetto of addettiGrid; let i=index"> <div>{{addetto}}</div> ...

Issue with the functionality of Angular reactive custom validator inoperable

Recently, I created a simple validator that compares the values of two date form controls within a form group. The basic rule is that the maturityDate has to be greater than the valueDate, otherwise the form group should be marked as invalid. Here is how ...

Creating an Angular 2 MVC 5 Razor application and looking to add angular attributes to an @Html.DropDownFor? Here's how to do

Is there a way to achieve this kind of functionality in HTML using Razor? <select class="form-control" ([ngModel])="selectedWorkout" (ngModelChange)="updateWorkout($event)" #selectList="ngModel"> <option value="44">Pick me!</option> & ...