Telerik blogs
AngularL_870x220

In this tutorial, we'll build a simple store where items can be added and removed from cart, and we’ll manage the application’s state using ngrx/store. As we’ll see, it is easier to manage data flow in the application when side effects and data flow are abstracted from components.

Managing an application is tasking, as the application grows to a never ending maze that requires a makeshift map to navigate. When applications grow to be that complex, managing data throughout the application becomes a major headache. This is where the importance of state management libraries like Redux, MobX and ngrx/store arises.

An important advantage of state management libraries in large-scale applications, especially hierarchical ones, is the ability to abstract the state of the application from components into an application-wide state. This way, data can be passed around with ease and components can act independently of each other.

For Angular, a great state management library is ngrx/store. This is an RxJS-powered state management library. It uses a similar syntax to Redux: actions, reducers, stores, effects, and RxJS’s reactive API.

In this tutorial, we’ll be building a fruit store using Angular. In our small store, a user will be able to add and remove fruits from the cart. We’ll also look at how we can use Effects for handling network requests, reducers and actions for data management. We’ll be setting up a minimal server using Express that will serve products to the Angular application.

To follow this tutorial, a basic understanding of Angular and Node.js is required. Please ensure that you have Node and npm installed before you begin.

If you have no prior knowledge of Angular, kindly follow the tutorial here. Come back and finish this tutorial when you’re done.

We’ll be using these tools to build our application:

Here’s a screenshot of the final product:

finalproduct

Initializing Application and Installing Dependencies

To get started, we will use the CLI (Command Line Interface) provided by the Angular team to initialize our project.

First, install the CLI by running npm install -g @angular/cli. npm is a package manager used for installing packages. It will be available on your PC if you have Node installed; if not, download Node here.

To create a new Angular project using the CLI, open a terminal and run:
ng new fruit-store --style=scss

This command is used to initialize a new Angular project; the project will be using SCSS as the pre-processor.

Next, run the following command in the root folder of the project to install dependencies.

  // install depencies required to build the server
  npm install express body-parser
    
  // front-end dependencies
  npm install @ngrx/store @ngrx/effects

Start the Angular development server by running ng serve in a terminal in the root folder of your project.

Building Our Server

We’ll build our server using Express. Express is a fast, unopinionated, minimalist web framework for Node.js.

Create a file called server.js in the root of the project and update it with the code snippet below

    // server.js
    
    const express = require('express');
    const bodyParser = require('body-parser');
    
    const app = express();
    const port = process.env.PORT || 4000;
    const fruits = require('./fruits');
    
    app.use(bodyParser.json());
    app.use(bodyParser.urlencoded({ extended: false }));
    app.use((req, res, next) => {
      res.header('Access-Control-Allow-Origin', '*');
      res.header(
        'Access-Control-Allow-Headers',
        'Origin, X-Requested-With, Content-Type, Accept'
      );
      next();
    });
    
    app.get('/fruits', (req, res) => {
      res.json(fruits);
    });
    
    app.listen(port, () => {
      console.log(`Server started on port ${port}`);
    });

The calls to our endpoint will be coming in from a different origin. Therefore, we need to make sure we include the CORS headers (Access-Control-Allow-Origin). If you are unfamiliar with the concept of CORS headers, you can find more information here.

This is a standard Node application configuration, nothing specific to our app.

We’re creating a server to feed data to our application so we can see how Effects can be used to fetch external resources to populate the store.

Create a file named fruits.js that will hold the products for our store. Open the file and populate it with the code below:


    //fruits.js
    
    module.exports = [
      {
        "name": "Berries",
        "price": 23.54,
        "image": "/assets/images/berries.jpeg",
        "description": "The bestest fruit known to man. Sweet yet sour but beautiful"
      },
      {
        "name": "Orange",
        "price": 10.33,
        "image": "/assets/images/oranges.jpeg",
        "description": "Succulent and watery, you'll never run out of water"
      },
      {
        "name": "Lemons",
        "price": 12.13,
        "image": "/assets/images/lemons.jpeg",
        "description": "Sour but important for revitalization"
      },
      {
        "name": "Bananas",
        "price": 10.33,
        "image": "/assets/images/banana.jpeg",
        "description": "An every day fruit, can be served with every dish"
      },
      {
        "name": "Apples",
        "price": 10.33,
        "image": "/assets/images/apple-item.png",
        "description": "Sliced and served with your salad. Served as snacks midway through the day"
      },
      {
        "name": "Sharifa",
        "price": 10.33,
        "image": "/assets/images/unknown.jpeg",
        "description": "A great fruit, also known as custard apple"
      }
    ]

Note: All image assets can be found in the GitHub repository here. Images were gotten from https://pexels.com.

Start the server by running the following command in a terminal within the project folder:

node server.js

Home View

To get started, we’ll define the views for the application, starting from the home page. The home page will house the products grid and the header. Using the CLI, we’ll create a component named home within the src/app folder. Run the command below in the project folder to create the home component:

ng generate component home

Open the home.component.html file and replace it with the content below.

    <!-- /src/app/home/home.component.html -->
    <main>
      <section class="banners">
        <div>
          <img src="/assets/images/fruits.jpeg" alt="Apples in a bunch" />
        </div>
        <div>
          <img src="/assets/images/berry.jpeg" alt="A bunch of berries" />
        </div>
        <div>
          <img src="/assets/images/banner-1.jpeg" alt="Slushy oranges" />
        </div>
        <div>
          <img src="/assets/images/banner-2.jpeg" alt="A diverse set of fruits" />
        </div>
      </section>
      <section class="product-area">
        <!-- TODO: Create product list component -->
      </section>
    </main>

You can find image assets used here.

In the snippet above, we’ve defined an area for the banners and products list. The banner area will house four banner images. We’ll go about creating the product list component later in the tutorial.

Styling the Home Component

Next, we’ll go about styling the home page, the banner area to be exact. We’ll give the images a defined height and give the container a max width.

    // src/app/home/home.component.scss
    
    main{
      width: 90%;
      margin: auto;
      padding: 20px 15px;
    
      .banners{
        display: flex;
        align-items: center;
        justify-content: center;
    
        div{
          width: 26%;
          margin-right: 10px;
          img{
            height: 200px;
            width: 100%;
            max-width: 100%;
            border-radius: 10px;
            object-fit: cover;
          }
        }
      }
    }

Since we’ll be using external fonts, we’ll update the src/index.html file with a link tag alongside the src/styles.scss file.


    <!-- index.html -->
    
    <!doctype html>
    <html lang="en">
    <head>
      <meta charset="utf-8">
      <title>MyStore</title>
      <base href="/">
    
      <meta name="viewport" content="width=device-width, initial-scale=1">
      <link href="https://fonts.googleapis.com/css?family=Dosis:400,500,700|Lobster" rel="stylesheet">
      <link rel="icon" type="image/x-icon" href="favicon.ico">
    </head>
    <body>
      <app-root></app-root>
    </body>
    </html>
      

Then we’ll select Dosis as our default font family. We’ll also negate the default padding and margin on the body and html elements. Open the styles.scss file and update it with the following content:

    // styles.scss
    
    /* You can add global styles to this file, and also import other style files */
    body, html{
      margin: 0;
      padding: 0;
      font-family: 'Dosis', sans-serif;
      background-color: whitesmoke;
    }

Header Component

The header component will display the the application logo and the cart total. The component will be subscribed to the store listening for changes to the cart array. More light on this when the NgRx/store library is introduced later in the article.

Run the following command to create the header component:

ng generate component header

Next, open the src/app/header/header.component.html file and update it to look like the code below:

  <!-- src/app/header/header.component.html -->
    
    <header>
      <div class="brand">
        <img src="/assets/images/logo.png" alt="avatar" />
        <h5>The Store</h5>
      </div>
      <div class="nav">
        <ul>
          <li>
            <img src="/assets/images/shopping-bag.png" alt="cart" />
            <span class="badge" *ngIf="cart.length > 0">{{ cart.length }}</span>
          </li>
        </ul>
      </div>
    </header>

Note: Any image asset used can be found here in the GitHub repository.

Next, we’ll style the header. Open the header.component.scss file and update it with the snippet below:

    //header.component.scss
    
    header {
      display: flex;
      background-color: white;
      margin: 0;
      padding: 5px 5%;
      color: whitesmoke;
      box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.1);
    
      .brand {
        flex: 1;
        display: flex;
        align-items: center;
    
        img {
          height: 35px;
          border-radius: 50%;
          margin-right: 17px;
        }
    
        h5 {
          font-family: 'Lobster', cursive;
          font-size: 23px;
          margin: 0;
          letter-spacing: 1px;
          color: rgb(52, 186, 219);
          background: linear-gradient(
            90deg,
            rgba(52, 186, 219, 0.9878326330532213) 44%,
            rgba(0, 255, 190, 1) 100%
          );
          -webkit-background-clip: text;
          -webkit-text-fill-color: transparent;
        }
      }
    
      ul {
        list-style: none;
        padding-left: 0;
        display: flex;
    
        li {
          display: flex;
          align-items: center;
          position: relative;
    
          img {
            width: 40px;
          }
    
          .badge {
            height: 20px;
            width: 20px;
            font-size: 11px;
            color: white;
            background-color: #35badb;
            display: flex;
            justify-content: center;
            align-items: center;
            position: absolute;
            top: 0;
            right: -10px;
            border-radius: 50%;
          }
        }
      }
    }
    

Open up the header.component.ts file and declare the cart variable used in the HTML file.


    import { Component, OnInit, Input } from '@angular/core';
    
    @Component({
      selector: 'app-header',
      templateUrl: './header.component.html',
      styleUrls: ['./header.component.scss']
    })
    export class HeaderComponent implements OnInit {
      constructor() {
      }
    
      cart = [];
      ngOnInit() {}
    }
    

App Component

After creating the home and header components, the next step is to render the components in the root App component. Open the app.component.html file within the src/app/ directory. Update it to render both Header and Home components.

    <!-- app.component.html -->
    <div>
      <app-header></app-header>
      <app-home></app-home>
    </div>

Start the application server by running the following command: npm start or ng serve.

Then navigate to http://localhost:4200 on your browser. You should see the something similar to the screenshot below:

store-home

Make sure to get the image assets from GitHub or use your preferred images.

Introducing NgRx/store

NgRx/store is a library for managing state in your Angular applications, it is a reactive state management library powered by RxJS. Similar to Redux, this library can be used to manage the flow of data throughout your application, when actions are dispatched, reducers act on them and mutate the store. Another library we’ll be working with is NgRx/effects. Effects are commonly used to handle side effects in your application, like fetching data from an external resource.

The first step is to create and assign actions. The actions will be mapped to constants using an enum. Create a folder named store within the src/app directory, this folder will hold everything relating to our application’s state management.

Within the store folder, create a file called actions.ts. Open the file and update it with the code below:

    // src/app/store/actions.ts
    
    import { Action } from '@ngrx/store';
    
    interface Product {
      name: string;
      price: number;
      description: string;
      image: string;
    }
    
    export enum ActionTypes {
      Add = '[Product] Add to cart',
      Remove = '[Product] Remove from cart',
      LoadItems = '[Products] Load items from server',
      LoadSuccess = '[Products] Load success'
    }
    
    export class AddToCart implements Action {
      readonly type = ActionTypes.Add;
    
      constructor(public payload: Product) {}
    }
    
    export class GetItems implements Action {
      readonly type = ActionTypes.LoadItems;
    }
    
    export class RemoveFromCart implements Action {
      readonly type = ActionTypes.Remove;
    
      constructor(public payload: Product) {}
    }
    
    export class LoadItems implements Action {
      readonly type = ActionTypes.LoadSuccess;
    
      constructor(public payload: Product[]) {}
    }
    
    export type ActionsUnion = AddToCart | RemoveFromCart | LoadItems | GetItems;    

First, we declare an interface that defines the properties of the Product object. Then we go on to declare unique actions to be used.

Actions are typically used to describe events in the application. When an event is triggered, a corresponding event is dispatched to handle the triggered events. An action is made up of a simple interface with a single property type, the type property is a unique identifier for the action.

An action type is commonly defined using the following pattern [Source] event — the source where the event originates and the event description.

You can create actions using as an interface or a class. Classes are easier to use if you need to extend the action with a payload property, so that’s what we did.

After creating actions, a type ActionsUnion is exported. This export helps define all Actions in this feature area; it exposes the type information of the actions exported. You can read more on creating actions union here.

After creating actions, the next step is to create a reducer that handles transitions of state from the initial to the next based on the action dispatched. Create a file named reducer.ts in the src/app/store directory. Open the file and update it with the code below:

    // src/app/store/reducer.ts
    
    import { ActionsUnion, ActionTypes } from './actions';
    
    export const initialState = {
      items: [],
      cart: []
    };
    
    export function ShopReducer(state = initialState, action: ActionsUnion) {
      switch (action.type) {
        case ActionTypes.LoadSuccess:
          return {
            ...state,
            items: [...action.payload]
          };
    
        case ActionTypes.Add:
          return {
            ...state,
            cart: [...state.cart, action.payload]
          };
    
        case ActionTypes.Remove:
          return {
            ...state,
            cart: [...state.cart.filter(item => item.name !== action.payload.name)]
          };
    
        default:
          return state;
      }
    }

A reducer is simple pure function that transitions your application’s state from one state to the next. A reducer doesn’t handle side effects — it is a pure function because it returns an expected output for a given input.

First, we have to define the initial state of the application. Our application will display a list of items and also allow user add and remove items from the cart. So the initialState of our application will feature an empty array of items and an empty cart array.

Next, we’ll define the reducer which is a function featuring a switch statement that acts on the type of action dispatched.

  • The first action type is the LoadSuccess action, which is called when products are successfully loaded from the server. When that happens, the items array is populated with that response.
  • The next action type is Add. This action is dispatched when a user wishes to add an item to cart. The action features a payload property containing details of the item. The reducer takes the item and appends it to the cart array and returns the state.
  • The final case is the Remove action. This is an event telling the reducer to remove an item from cart. The cart is filtered using the name of the item dispatched, and the item is left out of the next state.

You’re probably thinking that the numbers don’t add up. We created four actions but we’re only acting on three of them. Well, actions can also be used for effects network requests; in our case, fetching items from the server. We’ll look at creating a service to handle fetching the products from the server.

Registering the Reducer

After creating a reducer, it needs to registered in the StoreModule. Open the app.module.ts file and import the StoreModule from the ngrx/store library as well as the ShopReducer we just created.

    //app.module.ts
    
    import { BrowserModule } from '@angular/platform-browser';
    import { NgModule } from '@angular/core';
    import { StoreModule } from '@ngrx/store';
    import {HttpClientModule} from '@angular/common/http';
    
    import { AppComponent } from './app.component';
    import { HomeComponent } from './home/home.component';
    import { HeaderComponent } from './header/header.component';
    
    import { ShopReducer } from './store/reducer';
    
    @NgModule({
      declarations: [
        AppComponent,
        HomeComponent,
        HeaderComponent,
      ],
      imports: [
        BrowserModule,
        HttpClientModule,
        StoreModule.forRoot({ shop: ShopReducer }),
      ],
      providers: [],
      bootstrap: [AppComponent]
    })
    export class AppModule {}  

When registering the ShopReducer, we assign it a unique identifier (shop). This is useful in case you need to register multiple reducers. This need will arise in a larger application where several reducers are created to handle different areas of the application.

Fetching Products from the Server

To handle fetching products from the server, we’ll make use of the ngrx/effects library. The library can be used interact with services, abstracting them from components. Effects are used in collaboration with actions and reducers to transition state with the data returned after a network request.

First, we’ll create a service that will handle fetching items from the server. To create this service using the CLI, run the command below:

ng generate service fruits 

Then open the file and update the content to be similar to the snippet below:


    // src/app/fruits.service.ts
    
    import { Injectable } from '@angular/core';
    import { HttpClient } from '@angular/common/http';
    
    @Injectable({
      providedIn: 'root'
    })
    export class FruitsService {
      constructor(private http: HttpClient) {}
    
      getAll() {
        return this.http.get('http://localhost:4000/fruits');
      }
    }

Import the HttpClient, create a method called getAll and return a call to the server to get fruits using the HttpClient. Next, we’ll create an effects file that will make the network request using the FruitService when the appropriate action is triggered.

Create a file named effects.ts within the src/app/store directory. Open the file and copy the following code into the file:

    import { Injectable } from '@angular/core';
    import { Actions, Effect, ofType } from '@ngrx/effects';
    import { EMPTY } from 'rxjs';
    import { catchError, map, mergeMap } from 'rxjs/operators';
    import { ActionTypes } from './actions';
    import { FruitsService } from '../fruits.service';
    
    @Injectable()
    export class ShopEffects {
      constructor(
        private actions$: Actions,
        private fruitsService: FruitsService
      ) {}
      
      @Effect()
      loadFruits$ = this.actions$.pipe(
        ofType(ActionTypes.LoadItems),
        mergeMap(() =>
          this.fruitsService.getAll().pipe(
            map(fruits => {
              return { type: ActionTypes.LoadSuccess, payload: fruits };
            }),
            catchError(() => EMPTY)
          )
        )
      );
    }

An effect is simple a service with a @Effect decorator. There’s a bit going on here so we’ll explain each strange keyword used here.

  • Actions is an observable stream of all the actions dispatched after the application’s state has been reduced.
  • From the actions dispatched, we use the ofType operator provided by the library to filter the actions with the provided type (LoadItems in our case). One or more action types can be provided to the pipeable stream.
  • The mergeMap operator by RxJS is for flattening and merging the actions into an Observable.
  • The getAll method of the FruitService returns an observable that is mapped, and the response is then dispatched as an action, provided there was no error.
  • The catchError operator handles any errors encountered during the process.

After creating effects, we have to register it in the root module. Open the app.module.ts file and update it to fit the snippet below:

    import { BrowserModule } from '@angular/platform-browser';
    ...
    import { EffectsModule } from '@ngrx/effects';
    import { ShopEffects } from './store/effects';
    
    @NgModule({
      declarations: [
        ...
      ],
      imports: [
        ...
        EffectsModule.forRoot([ShopEffects])
      ],
      ...
    })
    export class AppModule {}

In the EffectsModule, we can register our effects ShopEffects. Multiple effects can be registered by adding the effects to the array.

Now that we’ve created actions to handle events in our application and reducers to transition state, let’s populate the store with items from the server using the effects. Before we do that, let’s define views for the product and products list.

Products List View

Run the following commands to generate components for the product item and product list:

ng generate component product        

And for the product list run:

ng generate component product-list

Open the product.component.html file in the src/app/product directory and update with the code below:

    // src/app/product/product.component.html
    
    <div class="product">
      <div class="product-image-holder">
        <img [src]="product.image" [alt]="product.name" class="product-image" />
      </div>
      <div class="product-details">
        <p class="product-details__name">{{ product.name }}</p>
        <p class="product-details__price">${{ product.price }}</p>
      </div>
      <div class="product-description">
        <p>{{ product.description }}</p>
      </div>
      <div class="product-actions">
        <button
          class="product-actions__add"
          (click)="addToCart(product)"
          *ngIf="!inCart"
        >
          <img src="/assets/images/add-to-cart.png" alt="add to cart" />
        </button>
        <button
          class="product-actions__remove"
          (click)="removeFromCart(product)"
          *ngIf="inCart"
        >
          <img src="/assets/images/remove-from-cart.png" alt="remove from cart" />
        </button>
      </div>
    </div>

Here we have two buttons for adding to and removing an item from the cart. A flag inCart is used to determine which of the button to display.

Note: All image assets can be found in the GitHub repository here.

Let’s style the component by updating the product.component.scss file with the styles below:

    // product.component.scss
    
    %button {
      border-radius: 50%;
      display: flex;
      justify-content: center;
      align-items: center;
      height: 32px;
      width: 32px;
      cursor: pointer;
    
      &:hover {
        transform: scale(1.1);
      }
    
      img {
        width: 16px;
        height: 16px;
      }
    }
    
    .product {
      box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.2);
      border-radius: 5px;
      margin: 0 15px 30px 0;
      width: 286px;
      max-height: 400px;
      height: 320px;
    
      &:hover {
        transform: scale(1.05);
        border: 1px solid #35BADB;
    
        .product-actions {
          display: flex;
        }
      }
    
      &-image {
        max-width: 100%;
        width: 300px;
        border-top-right-radius: 5px;
        border-top-left-radius: 5px;
        height: 180px;
        object-fit: cover;
      }
      &-details {
        display: flex;
        justify-content: space-between;
        padding: 8px 15px;
    
        &__price {
          font-weight: 500;
          opacity: 0.7;
          letter-spacing: 1px;
          margin: 0;
        }
    
        &__name {
          opacity: 0.8;
          font-weight: 500;
          margin: 0;
        }
      }
    
      &-description {
        padding: 10px 15px;
    
        p {
          opacity: 0.6;
          margin: 0;
        }
      }
    
      &-actions {
        display: none;
        justify-content: flex-end;
        padding: 0 15px;
    
        &__add {
          @extend %button;
          border: 2px solid rgb(52, 186, 219);
        }
    
        &__remove {
          @extend %button;
          border: 2px solid indianred;
        }
      }
    }

Open the product.component.ts file and update it with the variables and methods used in the HTML file.


    // src/app/product/product.component.ts
    
    import { Component, Input, OnInit } from '@angular/core';
    import { Store } from '@ngrx/store';
    import { AddToCart, RemoveFromCart } from '../store/actions';
    
    export interface Product {
      name: string;
      price: number;
      description: string;
      image: string;
    }
    
    @Component({
      selector: 'app-product',
      templateUrl: './product.component.html',
      styleUrls: ['./product.component.scss']
    })
    export class ProductComponent implements OnInit {
      constructor(private store: Store<{ items: []; cart: [] }>) {}
    
      inCart = false;
      @Input() product: Product;
    
      addToCart(item: Product) {
        this.store.dispatch(new AddToCart(item));
        this.inCart = true;
      }
    
      removeFromCart(item: Product) {
        this.store.dispatch(new RemoveFromCart(item));
        this.inCart = false;
      }
      ngOnInit() {}
    }

First we import the Store observable from the ngrx/store library. The store property will be used to dispatch actions.

The addToCart method takes one parameter (item); the method dispatches an action to add an item to cart. After dispatching the action, the inCart property is set to true. This flag is for identifying which items are in cart.

Meanwhile, the removeFromCart method dispatches an action to remove an item from cart and updates the inCart property to false.

Next we’ll render the Product component in the product-list component. Open the product-list.component.html file and render the Product similar to the snippet below:

    <!-- product-list.component.html -->
    <div class="product-list">
      <app-product *ngFor="let fruit of fruits" [product]="fruit"></app-product>
    </div>

We’ll add some styles to the component’s stylesheet. Open the product-list.component.scss file and add the styles below:

    .product-list {
      padding: 10px 0;
      margin-top: 30px;
      display: flex;
      flex-wrap: wrap;
    }

The product list component will receive an Input from the Home component, so let’s update the component to take an Input an array of fruits. Update the product-list.component.ts file to be similar to the snippet below:

    import { Component, Input, OnInit } from '@angular/core';
    import { Product } from '../product/product.component';
    
    @Component({
      selector: 'app-product-list',
      templateUrl: './product-list.component.html',
      styleUrls: ['./product-list.component.scss']
    })
    export class ProductListComponent implements OnInit {
      constructor() {}
    
      @Input() fruits: Product[] = [];
    
      ngOnInit() {}
    }

After making this change, the final step is to render the product list component in the home.component.html file and dispatch an action to load the products from the server in the OnInit lifecycle of the component.

Open the home.component.html file and render the product list component within the element with the product-area class attribute:

    <main>
      <section class="banners">
        ...
      </section>
      <section class="product-area">
        <app-product-list [fruits]="items"></app-product-list>
      </section>
    </main>

Then update the home component and make it similar to the snippet below:

    import { Component, OnInit } from '@angular/core';
    import { select, Store } from '@ngrx/store';
    import { GetItems } from '../store/actions';
    import { Product } from '../product/product.component';
    
    @Component({
      selector: 'app-home',
      templateUrl: './home.component.html',
      styleUrls: ['./home.component.scss']
    })
    export class HomeComponent implements OnInit {
      constructor(private store: Store<{ items: Product[]; cart: [] }>) {
        store.pipe(select('shop')).subscribe(data => (this.items = data.items));
      }
    
      items: Product[] = [];
    
      ngOnInit() {
        this.store.dispatch(new GetItems());
      }
    }

First we dispatch a new action GetItems. The action type was registered in the effect that handled fetching products from the server. After dispatching the action, we use the Store observable and the select operator to select and subscribe to the store we registered in the AppModule file.

When subscribed to the store, the data returned is the current state of our store. If you remember, the initial state of our store had two properties, both of which are arrays. In the home component, we need the array of items in the store, so using dot notation we’ll get the current items from the state.

After this change, if you visit http://localhost:4200, you should see all the latest changes we’ve made, including the ability to add and remove an item from cart.

store-able-to-add-or-remove

If you try adding an item to the cart, you’ll notice it is successful, but our cart doesn’t update with the number of items in the cart. Well, this is because we’re not subscribed to the store, so we won’t get the latest updates on the cart.

To fix this, open the header.component.html file and update the component to subscribe to the store in the component’s constructor.

    import { Component, OnInit, Input } from '@angular/core';
    import { select, Store } from '@ngrx/store';
    import { Product } from '../product/product.component';
    
    @Component({
      ..
    })
    export class HeaderComponent implements OnInit {
      constructor(private store: Store<{ items: []; cart: [] }>) {
        store.pipe(select('shop')).subscribe(data => (this.cart = data.cart));
      }
    
      cart: Product[] = [];
      ngOnInit() {}
    }

Similar to the Home component where we subscribed to the store and got the items array from the state, here we’ll be subscribing to the cart property of the state.

After this update, you should see the amount of items in cart when an item is added or removed from the cart.

cart-updating

Note: Ensure both that the Angular dev server is running on port 4200 and that the server is running on port 4000.

Conclusion

In this tutorial, we’ve built a simple store where items can be added and removed from cart. We’ve been able to manage the application’s state using NgRx/store. As we’ve seen, it is easier to manage data flow in the application when side effects and data flow are abstracted from components. The decision to pick a state management library is sometimes difficult. Some people introduce them too early, which adds another level of complexity to the application, and some people introduce them too late, but no matter what the case may be, state management libraries are helpful whenever they’re introduced.

I remember a popular quote about Flux:

You’ll know when you need Flux. If you aren’t sure if you need it, you don’t need it.

The same should be applied in this case. The choice rests on you. You can find the source code for this demo here.

We are our choices.” - Jean-Paul Sartre

This blog has been brought to you by Kendo UI

Want to learn more about creating great web apps? It all starts out with Kendo UI - the complete UI component library that allows you to quickly build high-quality, responsive apps. It includes everything you need, from grids and charts to dropdowns and gauges.

KendoJSft


About the Author

Christian Nwamba

Chris Nwamba is a Senior Developer Advocate at AWS focusing on AWS Amplify. He is also a teacher with years of experience building products and communities.

Related Posts

Comments

Comments are disabled in preview mode.