In this Angular Store tutorial, we’re going to show you how to load application wide values from a web service into the Angular store and then how to use those values in your application. All the application code is at https://github.com/ThomHehl/tutorial-store if you want to cheat and download from there.
Setting Up
To begin with, we need to create a new application in your workspace and change to that directory. If you haven’t installed angular yet, you need to follow the directions at http://angular.io.
~/workspace$ng new tutorial-store
~/workspace$cd tutorial-store
Now we’re going to install the store.
~/workspace/tutorial-store$npm i @ngrx/core @ngrx/store
Create several directories inside your app for later use. We’re going to add some services and a component. The greeting component is going to be the visual part of this tutorial. The app-load service is going to execute commands on application load. The name service will call our endpoint for names. We’ll dig into these in a little bit.
~/workspace/tutorial-store$cd src/app
~/workspace/tutorial-store/src/app$ng generate component greeting
~/workspace/tutorial-store/src/app$ mkdir actions
~/workspace/tutorial-store/src/app$ mkdir model
~/workspace/tutorial-store/src/app$ mkdir reducers
~/workspace/tutorial-store/src/app$ mkdir services
~/workspace/tutorial-store/src/app$ cd services
~/workspace/tutorial-store/src/app/services$ ng generate service app-load
~/workspace/tutorial-store/src/app/services$ ng generate service name
Now that we’ve created all of the parts for our app, let’s start building it. The first thing we need is a server that we’re going to test against. I’ve chosen to build mine in node here, because few platforms are easier to build a server.
~/workspace/tutorial-store/src$ mkdir test
~/workspace/tutorial-store/src$ cd test
~/workspace/tutorial-store/src/test$ mkdir node
The Test Web Service for the Angular Store Tutorial
In the new directory src/test/node, we going to create a server.js to be the web service for our Angular Store Tutorial. Here’s the code.
var cors = require('cors');
var express = require('express');
var app = express();
app.options('*', cors());
app.use(cors());
var names = ['World', 'Ford', 'Arthur', 'Trillian', 'Zaphod', 'Marvin'];
app.listen(8080, function () {
console.log('CORS-enabled web server listening on port 8080');
});
app.get('/getName', function (request, response) {
response.writeHead(200, {'Content-Type': 'text/plain'});
var idx = Math.floor(Math.random() * names.length);
var answer = {name:names[idx]};
response.end(JSON.stringify(answer));
});
This code simply returns a JSON object with a random name for use in our client code. Some of the overhead is to handle the CORS stuff. You should be able to test this by starting the server and then hitting the URL in a browser.
~/workspace/tutorial-store/src/test/node$ node server.js
CORS-enabled web server listening on port 8080
Building the Angular Client Code
Back to the Angular code, we’re going to create the model object that we’re transferring from the server. In the models directory created above, create greeting-target.ts
export class GreetingTarget {
name: string;
}
Next, open the name.service.ts we created above. Here’s the source.
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { GreetingTarget } from "../model/greeting-target";
const GET_NAME_URL = 'http://localhost:8080/getName';
@Injectable({
providedIn: 'root'
})
export class NameService {
constructor(private http: HttpClient) { }
getName(): Observable<GreetingTarget> {
return this.http.get<GreetingTarget>(GET_NAME_URL);
}
}
We’re not going to talk a lot about this, since you can read about this part in many Angular tutorials.
Starting the Angular Store
We’re finally getting to the meaty part of this. It’s time to start building our store. It starts with where the actual state of the store is going to be kept. Create a file called app.state.ts. I put it in the app directory.
export const STORE_NAME = 'name';
export interface AppState {
readonly name: string;
}
Creating the Actions
After creating the store, we’re going to create the actions we’re going to use in the store. These are like store events.
In the actions directory, create a new name.actions.ts. Here’s the code. Right now, there’s only the single event, which is the load event that populates our store from the server. You can add your other events here.
import { Action } from '@ngrx/store'; export const LOAD_NAME = '[string] Load Name'; export class LoadName implements Action { readonly type = LOAD_NAME; constructor(public payload: string) {} } export type Actions = LoadName;
Inside the reducers directory created above, create a new file called name.reducer.ts. This is going to be the heart of our store. This is going to handle all of the events.
import * as NameActions from '../actions/name.actions'; const initial: string = ''; export function nameReducer(state: string = initial, action: NameActions.Actions): string { switch (action.type) { case NameActions.LOAD_NAME: return action.payload; default: console.log('default state', state); return state; } }
The reducer handles the events for our store. The method gets called and the state, if not provided, is initialized to a blank string. Then a switch statement handles our actions. In this case, there is only the load action, which sets the state to the payload and returns it. A default is included as good form in case the reducer is called in some other fashion.
Populating Store Values on App Load for your Angular Store Tutorial
Following the reducer, we need to create our service that will load values on application load. Edit the app-load.service.ts with the following code.
import { Injectable } from '@angular/core';
import { AppState } from '../app.state';
import { Store } from '@ngrx/store';
import { take } from 'rxjs/operators';
import { NameService } from './name.service';
import * as NameActions from '../actions/name.actions';
import {GreetingTarget} from "../model/greeting-target";
@Injectable({
providedIn: 'root'
})
export class AppLoadService {
constructor(private store: Store<AppState>, private nameService: NameService) {}
loadName(): void {
this.nameService.getName().pipe(take(1))
.subscribe((target: GreetingTarget) => {
this.store.dispatch(new NameActions.LoadName(target.name));
});
}
}
At last, this is where the pieces begin to come together. As you can see, we pass in our store and the NameService into our constructor.
The loadName method is what we’re going to call to load the values from our web service and dispatch them into the store. We pass the retrieved name the load action payload, which will then be passed into our reducer.
It’s time for the final bit of glue code to pull everything together. Here is the app.module.ts file after changes for our app.
import { BrowserModule } from '@angular/platform-browser';
import {APP_INITIALIZER, NgModule} from '@angular/core';
import { AppComponent } from './app.component';
import { GreetingComponent } from './greeting/greeting.component';
import { AppLoadService } from './services/app-load.service';
import { NameService } from './services/name.service';
import { HttpClientModule } from '@angular/common/http';
import { nameReducer } from './reducers/name.reducer';
import { StoreModule } from '@ngrx/store';
export function load_name(appLoadService: AppLoadService) { // (1)
return () => appLoadService.loadName();
}
@NgModule({
declarations: [
AppComponent,
GreetingComponent
],
imports: [
BrowserModule,
StoreModule.forRoot({ //(2)
name: nameReducer
}),
HttpClientModule
],
providers: [
AppLoadService, //(3)
{ provide: APP_INITIALIZER, useFactory: load_name, deps: [AppLoadService], multi: true }, //(4)
NameService
],
bootstrap: [AppComponent]
})
export class AppModule { }
(1) First, we define and export our load_name function. This simply calls the loadName() method we just created on the AppLoadService.
(2) We introduce our reducer to the app and give it the name “name”. Note that this name must be the same as the constant defined in app.state.ts. The app will reference our store by this name using that constant.
(3) The AppLoadService is defined as a provider.
(4) The load_name() is defined as the APP_INITIALIZER.
Finally we’ll create the component that uses this value from the store.
The Greeting Component
Here is the greeting.component.ts for our component.
import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { AppState, STORE_NAME } from '../app.state';
import { Store } from '@ngrx/store';
@Component({
selector: 'app-greeting',
templateUrl: './greeting.component.html',
styleUrls: ['./greeting.component.sass']
})
export class GreetingComponent implements OnInit {
protected nameFromServer: Observable<string>;
constructor(private store: Store<AppState>) { }
ngOnInit() {
this.nameFromServer = this.store.select(STORE_NAME);
}
}
Above, you can see that we simply inject the store into our component. Then, we select the store by the name we provided in the app.module.ts and an observable of the state of the store defined in app.state.ts will be stored in the variable nameFromServer.
And here is the greeting.component.html.
<h2> Hello {{nameFromServer | async}}! </h2>
Not a lot to talk about here. We are simply getting the value from the observable and displaying on the screen.
Finally, we need to reference our component from app.component.ts.
<app-greeting></app-greeting>
It’s time to run our app and you should see this.
Congratulations! You have completed this angular store tutorial.
Don’t forget to check back for other great tutorials at https://www.heavyweightsoftware.com.