Building a Food Store using Redux and Angular

When applications become complex, it can be difficult to manage their data. In this tutorial, learn how to use the Redux state management library to create a grocery store that displays items and allows users to add them to a shopping cart.

Manage an application where surrounding components are required for the app to communicate directly with each other is tasking, since Angular doesn’t have a built-in store for the entire app. When applications are so complex, data management across the entire application becomes difficult. This is where the importance of state management libraries such as Redux, MobX, and ngrx/store comes into play.

A major advantage of state management libraries in large-scale applications, especially hierarchical ones, is the ability to abstract application state from components to an application-wide state. This way, data can be easily transferred and components can act independently of each other.

For Angular, Redux is an excellent state management library. Redux is a predictable state wrapper for JavaScript applications. Redux provides a single store for the entire application that is immutable and consistent with the application state. It uses a one-way data flow and uses actions to change the state of the application in response to an event. It uses an API consisting of actions, reducers, etc.

We’ll use a package that provides bindings for Redux in Angular applications. The @angular-redux/store library uses observables under the hood to enhance Redux features for Angular.

In this tutorial, we’ll build a grocery store using Angular. In this store, a user will see the items displayed in the store and will be able to add and remove items from the cart. We’ll set up a minimal server using Express that will serve the products to the Angular application.

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

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

We’ll use these tools to build our app:

  • Express
  • Node
  • Angular
  • Redux
  • @angular-redux/store

Here is a screenshot of the final product:

Initializing the application and installing dependencies

To get started, we’ll 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 to install 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 redux-store -style=scss

This command is used to initialize a new Angular project; the project will use SCSS as a preprocessor.

Then run the following command in the project root folder to install the dependencies.

// install the dependencies needed to build the server npm install express body -parser // front-end dependencies npm install redux @angular-redux/store

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

Building our server

We will build our server using Express. Express is a minimal, fast, no-opinion web framework for Node.js.

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

// server.js const express = require(‘express’); const bodyParser = require(‘body-parser’); const application = 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}`); });

Calls to our terminal will come from a different origin. Therefore, we need to make sure to include the CORS (Access-Control-Allow-Origin) headers. If you are not familiar with the concept of CORS headers, you can find more information here.

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

We are creating a server to feed data to our app so we can see how Effects can be used to get external resources to populate the store.

Create a file called fruit.js that will contain the products for our store.Open the file and fill it in with the following code:

//module fruit.js.exports = [ { “name”: “Berries”, “price”: 23.54, “image”: “https://images . unsplash.com/photo-1488900128323-21503983a07e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&h=400&q=80”, “description”: “Hot Pops” }, { “name ” :”orange”, “price”: 10.33, “image”: “https://images.unsplash.com/photo-1504185945330-7a3ca1380535?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&h=340&w=340&q = 80”, “description”: “Delicious burger. Who cares if it’s healthy” }, { “name”: “Lemons”, “price”: 12.13, “image”: “https://images.unsplash.com /photo-1504382262782-5b4ece78642b?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&h=400&q=80”, “description”: “Sumptuous Egg Sandwich” }, { “name”: “Bananas”, ” price “: 10.33, “image”: “https://images.unsplash.com/photo-1478369402113-1fd53f17e8b4?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto =format&fit=crop&w=400&h=4 00&q=80”, “description”: “A huge tower of pancakes. Dive in!” }, { “name”: “Apples”, “price”: 10.33, “image”: “https://images.unsplash.com/photo-1505253304499-671c55fb57fe?ixlib=rb-1.2.1&ixid= eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&h=400&q=80”, “description”: “Great looking waffle to start your day” }, { “name”: “Sharifa”, “price”: 10.33, “image”: “https : //images.unsplash.com/photo-1470119693884-47d3a1d1f180?ixlib=rb-1.2.1&auto=format&fit=crop&w=400&h=400&q=80”, “description”: “What’s more than 5 minutes with roasted corn” } ]

The image assets used were obtained from Unsplash

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

node server .js

Home View

To get started, we’ll define the app’s views, starting from the home page. The home page will house the product grid and header. Using the CLI, we’ll create a component called home inside the src/app folder. Cute the following command in the project folder to create the home ent component:

ng generate home component

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

The image assets used were sourced from Unsplash

In the above snippet, we’ve defined an area for the banners and product list. The banner area will house four banner images. Later in the tutorial, we’ll create the product list component.

See Also:  [2023 Newest] How to Mirror Android to Apple TV?

Styling the home component

Next, we’ll style the banner. home page area. We’ll give images a defined height and give the container a maximum width.

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

Next, we’ll create the banners property with an array of images. Open the home.component.ts file and update it to look like the following snippet:

import { Component, OnInit } from ‘@angular/core’; @Component({ selector: ‘app-home’, templateUrl: ‘./home.component.html’, styleUrls: [‘./home.component.scss’] }) export class HomeComponent implements OnInit { constructor() {} banners = [ { src: ‘https://images.unsplash.com/photo-1414235077428-338989a2e8c0?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=850&q=80’, alt: ‘A delight’ } , { src: ‘https://images.unsplash.com/photo-1504113888839-1c8eb50233d3?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=850&q=80’, alt: ‘Chocolate Covered Pancakes’ }, { src: ‘https://images.unsplash.com/photo-1460306855393-0410f61241c7?ixlib=rb-1.2.1&auto=format&fit=crop&w=850&q=80’, alt: ‘Burger and Fries’ }, { src: ‘ https ://images.unsplash.com/photo-1495195134817-aeb325a55b65?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=850&q=80’, alt: ‘Get ready to crop’ } ]; ngOnInit() { } }

Since we will be using external sources, we will update the src/index.html file with a link tag next to the src/styles.scss file.

MyStore

Next, we’ll select Dose as our default font family, and we’ll also deny 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: ‘Dose’, sans-serif; background-color: whitesmoke; }

Header Component

The header component will display the application logo and cart total. will subscribe to the store cart property and listen for changes, more on this when the @angular-redux/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 and update it to look like the following code:

avatar

The store food

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

//header.component.scss header { display: flex; background color: white; margin: 0; padding: 5px 5%; color: white smoke; box shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.1); .mark { flex: 1; flexible screen; align elements: center; image { height: 35px; edge-radius: 50%; right margin: 17px; } h5 { font-family: ‘Lobster’, italic; font size: 23px; margin: 0; letter spacing: 1px; colour: rgb(52, 186, 219); background: linear gradient(90 degrees, 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; flexible screen; li { display: flex; align elements: center; relative position; image { width: 40px; } .badge { height: 20px; width: 20px; font size: 11px; White color; background color: #35badb; flexible screen; justify-content: center; align elements: center; position: absolute; top: 0; right: -10px; edge-radius: 50%; } } } }

Open 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() { } car = []; ngOnInit() {} }

Application Component

After creating the init and header components, the next step is to render the components in the application root component. Open the app.component.html file inside the src/app/ directory. Update it to show the header and home components.

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

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

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

Introducing @angular – redux/store

The @angular-redux/store library uses a Redux-like syntax to transform data. It uses Observables to select and transform data on its way from the store before updating the UI with the latest changes. This library is used in conjunction with Redux to manage the flow of data throughout your application; when actions are dispatched, reducers act on them and mutate the store.

The first step is to create and assign actions. Action types will be mapped to constants using an enumeration. Create a folder called store inside the src/app directory. This folder will contain everything related to managing our application’s state.

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

// src/app/store/actions.ts export enum ActionTypes { Add = ‘[Product] Add to cart’, Remove = ‘[Product] Remove from cart ‘ , LoadItems = ‘[Products] Load items from server’, LoadSuccess = ‘[Products] Load successful’ } export const AddToCart = payload => { return { type: ActionTypes.Add, payload }; }; export const GetItems = () => ({ type: ActionTypes.load elements }); export const RemoveFromCart = payload => ({ type: ActionTypes.Remove, payload }); export const LoadItems = payload => ({ type: ActionTypes.LoadSuccess, payload });

Actions are generally used to describe events in the application: when an event is fired, a corresponding event is sent to handle the fired events. An action is made up of a simple object that contains a type property and an optional payload property. The type property is a unique identifier for the action.

An action type is commonly defined using the pattern: [Source] event: The source from which the event originates and the description of the event.

You can create actions using as a function that defines the type of action and the payload that is sent.

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

// src/app/store/reducer.ts import { ActionTypes } from ‘./actions’; import { Product } from ‘../product/product.component’; export interface InitialState { items: Array; cart: Array; } export const initialState = { items: [], cart: [] }; export function ShopReducer(state = initialstate, action) { switch(action.type) { event ActionTypes.LoadSuccess: return { … state, items: [… action.payload] }; case ActionTypes.Add: return { …status, cart: […status.cart, action.payload] }; case ActionTypes.Remove: return { …status, cart: […status.car.filter(item => item.name !== action.payload.name)] }; default: return status; } }

See Also:  What is The Easiest Way to Develop an Android App?

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

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

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

  • The first type of action is the LoadSuccess action, which is called when products are successfully loaded from the server. When that happens, the array of elements is populated with that response.
  • The next type of action is Add. This action is sent when a user wants to add an item to the cart. The action presents a payload property that contains item details. The reducer takes the item and adds it to the cart array and returns the status.
  • The final case is the Delete action. This is an event that tells the reducer to remove an item from the cart. The cart is filtered using the shipped item name and the item is left out of the next status.

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

Registering the reducer

After creating a reducer, you need to register it in the application’s AppModule. Open the app.module.ts file and import the NgReduxModule from the @angular-redux/store library, as well as the ShopReducer we just created. Also, NgRedux will be imported and used to configure the store.

//app.module.ts import { BrowserModule } from ‘@angular/platform-browser’; import {NgModule} from ‘@angular/core’; import {HttpClientModule} from ‘@angular/common/http’; import {NgReduxModule, NgRedux} from ‘@angular-redux/store’; import {AppComponent} from ‘./app.component’; import { HomeComponent } from ‘./home/home.component’; import {HeaderComponent} from ‘./header/header.component’; import { ShopReducer, InitialState, initialState } from ‘./store/reducer’; @NgModule({ declarations: [AppComponent, HomeComponent, HeaderComponent, ], imports: [BrowserModule, HttpClientModule, NgReduxModule], providers: [], bootstrap: [AppComponent] }) export class AppModule { constructor(ngRedux: NgRedux) { ngRedux.configureStore(ShopReducer, initialState); } }

After registering the NgReduxModule, we initialize the app store using NgRedux. This provider is used to configure and initialize the store. The configureStore method takes two parameters, the ShopReducer and the initial state.

Getting products from the server

To handle getting products from the server, we’ll use a provider that gets the products and then sends an action to add the products to the store.

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

ng generate service food

Then open the file and update the contents to look like the following code snippet:

// src/app/ food.service.ts import { Injectable } from ‘@angular/core’; import { HttpClient } from ‘@angular/common/http’; // This interface will be declared later in the article import { Product } from ‘./product/product.component’; import {NgRedux} from ‘@angular-redux/store’; import {InitialState} from ‘./store/reducer’; import { LoadItems } from ‘./store/actions’; @Injectable({ provideIn: ‘root’ }) export class FoodService { constructor( private http: HttpClient, private ngRedux: NgRedux ) {} getAll() { this.http .get(‘http://localhost:4000 /fruits’) .subscribe((products: Array) => { this.ngRedux.dispatch(LoadItems(products)); }); } }

Import HttpClient, create a method called getAll, and call back to the server to get products using HttpClient. When the products are returned, we’ll send an action to load the products into the store.

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

Product List View

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

ng generate component product

And for list of products execute:

ng generate component product-list

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

// src/app/product/product.component.html

{ { product.name }}

${{ product.price }}

{ { product.description }}

Here we have two buttons to add and remove an item from the cart. An enCart flag is used to determine which of the buttons 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 following styles:

// product.component.scss %button { border-radius: 50%; flexible screen; justify-content: center; align elements: 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 setting: cover; } &-details { screen: 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 { @extender %button; border: 2px solid indian red; } } }

See Also:  How To Spiritually Connect With Someone Far Away

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 } of ‘@angular/core’; import { AddToCart, RemoveFromCart } from ‘../store/actions’; import {NgRedux} from ‘@angular-redux/store’; import { InitialState } from ‘../store/reducer’; export interface Product { name: string; price: number; description: chain; image: string; } @Component({ selector: ‘app-product’, templateUrl: ‘./product.component.html’, styleUrls: [‘./product.component.scss’] }) export class ProductComponent implements OnInit { constructor(private ngRedux : NgRedux) {} inCart = false; @Input() product: Product; addToCart(item: Product) { this.ngRedux.dispatch(AddToCart(item)); this.inCart = true; } removeFromCart(item: Product) { this.ngRedux.dispatch(RemoveFromCart(item)); this.inCart = false; } ngOnInit() {} }

First we import the NgRedux observable from the @angular-redux/store library. The ngRedux property will be used to send actions.

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

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

A We will then render the Product component in the product list component. Open the file product-list.component.html and render the product, similar to the following code snippet:

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

.product-list { padding: 10px 0; top margin: 30px; flexible screen; flexible casing: casing; }

The product list component will receive input from the Home component, so let’s update the component to take input from an array of fruits. Update the product-list.component.ts file to look like the following snippet:

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: Array; ngOnInit() {} }

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

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

..

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

import { Component, OnInit } from ‘@angular/core’; import { GetItems } from ‘../store/actions’; import { Product } from ‘../product/product.component’; import {NgRedux,select} from ‘@angular-redux/store’; import { InitialState } from ‘../store/reducer’; import { FruitsService } from ‘../fruits.service’; import {Observable} from ‘rxjs’; @Component({ selector: ‘app-home’, templateUrl: ‘./home.component.html’, styleUrls: [‘./home.component.scss’] }) export class HomeComponent implements OnInit { constructor( private ngRedux: NgRedux, private foodservice: Foodservice) {} @select(‘items’) items$: Observable<Array>; banners = [ … ]; ngOnInit() { this.foodService.getAll(); } }

First we look up the products using FoodService: the service will send an action to fill the store. After dispatching the action, we use the NgRedux observable and select operator to select the item property in the store and subscribe to the store that we registered in the AppModule file.

When we subscribe 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 variety of items in the store.

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

If you try to add an item to the cart, you will notice that it is successful, but our cart does not update with the number of items in the cart. Well, this is because we are not subscribed to the store, so we won’t get the latest updates in the cart.

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

// src/app/header/header.component.ts import { Component, OnInit, Input } from ‘@angular/core’; import { Product } from ‘../product/product.component’; import {NgRedux} from ‘@angular-redux/store’; import { InitialState } from ‘../store/reducer’; @Component({ selector: ‘app-header’, templateUrl: ‘./header.component.html’, styleUrls: [‘./header.component.scss’] }) export class HeaderComponent implements OnInit { constructor(private ngRedux: NgRedux) { this.ngRedux .select<Array>(‘cart’) .subscribe((items: Array) => { this.cart = items; }); } cart: Array; ngOnInit() {} }

Just like in the Home component, where we subscribed to the store and got the cart array from the state, here we’ll subscribe to the cart state property.

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

al-06-p”/tp”/tp=”blockng”>Note: Make sure that both the Angular development server is running on port 4200 and the server is running on port 4000

Conclusion

In this tutorial, we have created a simple food store where you can add and remove cart items. We have been able to manage Show application state using Angular/Redux library. As we have seen, it is easier to manage the data flow in the application when the side effects and the data flow are abstracted from the components. You can find the source code for this demo here.

For more information on building apps with Angular

Check out our All Things Angular page, which has a wide range of information and tips for Angular Info: everything from hot topics and up-to-date information to getting started and creating a compelling user interface.

.

Leave a Reply

Your email address will not be published. Required fields are marked *