Angular and NgRx
  • Introduction
  • Setup
  • 1. Create an application
  • 2. Create a home component
  • 3. Test HomeComponent
  • 4. Create event feature module
  • 5.Create AddAttendeeComponent
  • 6. Test AddAttendeeComponent
  • 7. Listen to child component events
  • 8. Add test for the event emitter
  • 9. Create EventListComponent
  • 10. Test EventListComponent
  • 11. Create EventService
  • 12. Test EventService
  • 13. Add simple NgRx spinner
  • 14. Test reducer
  • 15. Strongly type our store
  • 16. Update reducer tests
  • 17. Store dev tools
  • 18. Create selectors
  • 19. Create feature state
  • 20. Create effect
  • 21. Test an effect
  • 22. Use Entity Adapter
  • 23. Add attendee logic
  • 24. Router store
  • 25. Fix EventComponent tests
Powered by GitBook
On this page
  • Link to section slides
  • 1. npm i NgRx Store library
  • 2. Add a reducer
  • 3. Register NgRx in app module
  • 4. Inject store into the EventComponent
  • 5. Dispatch an action when adding new attendees
  • 6. Update the EventComponent to show a basic loading indicator
  • StackBlitz Link

13. Add simple NgRx spinner

In this section we will introduce redux and start converting our demo application to an NgRx powered application. We will skip strong typing and action creators till the next section.

Previous12. Test EventServiceNext14. Test reducer

Last updated 6 years ago

1. npm i NgRx Store library

  • npm install @ngrx/store

npm i @ngrx/store

2. Add a reducer

Reducers are at the core of the redux pattern NgRx follows. We will have a reducer for each "slice" of state that we will combine into a single "store" that is literally just a JavaScript object.

This is a very simple reducer, but it follows the basic principles of a reducer. Reducers are just pure functions that take in state and a action (the instructions to change state) and return the new state for this slice of state.

  • Create a state folder with a spinner folder inside of it.

  • Create a spinner.reducer.ts file and add the below spinner state logic.

src/app/state/spinner/spinner.reducer.ts
export function reducer(state = { isOn: false }, action) {
  switch (action.type) {
    case 'startSpinner': {
      return {
        isOn: true
      };
    }

    case 'stopSpinner': {
      return {
        isOn: false
      };
    }

    default:
      return state;
  }
}

3. Register NgRx in app module

Here we register the reducer we made and name this slice of state "spinner". You will see this piece of state in the dev tools under a property called "spinner", when we add the dev tools in the coming sections.

  • Add NgRx to AppModule

src/app/app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';
import { RouterModule } from '@angular/router';

import { AppComponent } from './app.component';
import { HomeComponent } from './home/containers/home/home.component';
import { HttpClientInMemoryWebApiModule } from 'angular-in-memory-web-api';
import { StoreModule } from '@ngrx/store';

import { InMemoryDataService } from './app.db';
import { reducer } from './state/spinner/spinner.reducer';

@NgModule({
  declarations: [AppComponent, HomeComponent],
  imports: [
    BrowserModule,
    RouterModule.forRoot([
      { path: '', pathMatch: 'full', redirectTo: 'home' },
      { path: 'home', component: HomeComponent },
      { path: 'event', loadChildren: './event/event.module#EventModule' }
    ]),
    HttpClientModule,
    HttpClientInMemoryWebApiModule.forRoot(InMemoryDataService, { delay: 1000 }),
    StoreModule.forRoot({ spinner: reducer })
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule {}

4. Inject store into the EventComponent

As we move towards fully implementing NgRx you will see our components become even simpler with just a bunch of subscriptions to the store and actions being dispatched and work normally done in components now delegated to our NgRx system.

  • Inject Store into the EventComponent.

  • Select the spinner$ state.

src/app/event/container/event/event.component.ts
import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { Store, select } from '@ngrx/store';

import { Attendee } from '../../../models';
import { EventService } from '../../services/event.service';

@Component({
  selector: 'app-event',
  templateUrl: './event.component.html',
  styleUrls: ['./event.component.scss']
})
export class EventComponent implements OnInit {
  spinner$: Observable<boolean>;
  attendees$: Observable<Attendee[]>;

  constructor(private store: Store<any>, private eventService: EventService) {}

  ngOnInit() {
    this.getAttendees();
    this.spinner$ = this.store.pipe(select(state => state.spinner.isOn));
  }

  getAttendees() {
    this.attendees$ = this.eventService.getAttendees();
  }

  addAttendee(attendee: Attendee) {
    this.eventService.addAttendee(attendee).subscribe(() => {
      this.getAttendees();
    });
  }
}

5. Dispatch an action when adding new attendees

  • Dispatch a startSpinner and stopSpinner action when loading and receiving data from the fake backend.

src/app/event/container/event/event.component.ts
import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { Store, select } from '@ngrx/store';

import { Attendee } from '../../../models';
import { EventService } from '../../services/event.service';

@Component({
  selector: 'app-event',
  templateUrl: './event.component.html',
  styleUrls: ['./event.component.scss']
})
export class EventComponent implements OnInit {
  spinner$: Observable<boolean>;
  attendees$: Observable<Attendee[]>;

  constructor(private store: Store<any>, private eventService: EventService) {}

  ngOnInit() {
    this.getAttendees();
    this.spinner$ = this.store.pipe(select(state => state.spinner.isOn));
  }

  getAttendees() {
    this.attendees$ = this.eventService.getAttendees();
  }

  addAttendee(attendee: Attendee) {
    this.store.dispatch({ type: 'startSpinner' });
    this.eventService.addAttendee(attendee).subscribe(() => {
      this.store.dispatch({ type: 'stopSpinner' });
      this.getAttendees();
    });
  }
}

6. Update the EventComponent to show a basic loading indicator

  • Add a loading div with an ngIf.

  • Add a *ngIf to the EventListComponent.

src/app/event/containers/event.component.ts
<app-add-attendee (addAttendee)="addAttendee($event)"></app-add-attendee>
<app-event-list *ngIf="!(spinner$ | async)" [attendees]="attendees$ | async"></app-event-list>
<div *ngIf="spinner$ | async">loading..</div>

StackBlitz Link

Link to section slides
Duncanhunter - Angular And Ngrx Demo App - StackBlitzStackBlitz
Web Link: Link to the demo app running in StackBlitz
Logo
Image: Showing 'spinner' state registered in AppModule and used in the component.