Building A food Order system Part 12

in #utopian-io6 years ago (edited)

Contribution Repository

Angular Github Repository

We are in a world where technology and information is taking over everything, User experience is key in building web applications, our food app needs to be able to notify a user when ever he or she carryout an action such as Adding a food to the cart, removing etc.

Rating

This tutorial is rated Intermediate.

What Will I learn?

  1. Using the reduce function for carrying out calculations on an object.
  2. Using String Interpolation.
  3. managing the state of the carts total amount with observables.
  4. Understanding toaster alerts
  5. Building a an alert service.

Requirement.

Introduction

Previously on this series, we were able to increase and decrease the quantity of item in the cart, we also made use of rsjx observables for state management on the item-feed component and the other component.
Below is the last state or structure o our application.

  • Voltron
    • node_modules/
    • src/
      • app
        • app-navbar/
        • item/
        • order
        • order-item
        • items-feed/
        • signup/
        • login/
        • app-routing.module
        • app.component.html
        • app.component.sass
        • app.component.ts
        • app.module.ts
        • auth.service.ts
        • item.service
        • cache.service
        • cart.service
      • asset/
      • environments/
      • item.ts
      • user.ts
      • auth.ts
    • .angular-cli.json
    • .editorconfig
      +.....

We have a case on our hands where a user has finished adding all the items he or she wants to order, How do we run calculations on the cartItems to get the total price of the items?
Before we go further, lets talk about the JavaScript Reduce function.

Reduce method

The reduce method in JavaScript is an integral part of functional programming, it is used for aggregation of arrays and array of objects, the reduce method returns a single value when ran on an array.

This method accepts a callback function that takes two variable a total, and the current item in the array. take a look at the example below

arr = [1,2,3,4]
sum = arr.reduce((total, number)=>total + number )
console.log(sum)  // 10

When the loop starts the total value is the number on the far left (1) and the current amount is the one next to it (2). It works like a for loop and continues the iteration.

Lets take this to the food app, our goal is to be able to calculate the amount in the cartItem

generate a component to show the cart details

ng generate component cart-detail

Find the newly generated at scr/app/cart-detail
lets create the markup and styles for this component. open cart-detail.component.html and add the markup

<div class="cart-detail">
  <div class="subtotal">
      <h5>Subtotal</h5>
      <p>$1000</p>
  </div>
  <p>VAT</p>
  <div class="total">
      <h3>Order Total</h3>
      <h3>$1000</h3>
  </div>
</div>

The above, is a simple markup all we are doing is showing the total amount on every instance of the object.
The styles for the component show be added in cart-detail.component.sass

.cart-detail
    display: flex;
    flex-direction: column;
    padding: 40px;
    background-color: #00554a;
    color: #fff;


.subtotal
    display: flex;
    flex-direction: row;
    justify-content: space-between
.total
    display: flex;
    flex-direction: row;
    justify-content: space-between

In order for this component to function, we need to add a method to our cartService that watches and run a reduce function on the cartItems observable which is an array of item object and returns a single value.

Building the method that returns the total amount in the cart

Open up the cartservice and add a new method for that purpose, its a simple code just like the one we explained earlier.

total (){
    return this.cartItems.reduce((total, cartItem)=> (cartItem.qty * cartItem.price) + total,0)
  }

success.png

The method uses the Observable stream(cartItems) and accepts two variable, total and the current cartItem, the cartItem is an object so we are only interested in the qty and price property.
so we run the calculation to return the total amount on every instance in the observable.

 return this.cartItems.reduce((total, cartItem)=> (cartItem.qty * cartItem.price) + total,0)

Using the total method in the cart-detail.component.ts

To initiate the the method for calculating the total item in the cart, we need to call the total method from the cartService
Open the cart-detail.component.ts

Import the cartService and inject in the constructor

total: number

import { CartService } from '../cart.service';

constructor(private cartService : CartService ) {

    this.total = this.cartService.total()
   }

In the above, we set the value of total in the component to the function or method in the cartService that returns the total amount from the observabe

Using Interpolation to display the total amount.

Open cart-details.component.ts and update the hard coded figures with the total variable

<div class="cart-detail">
  <div class="subtotal">
      <h5>Subtotal</h5>
      <p>${{total}}</p>
  </div>
  <p>VAT</p>
  <div class="total">
      <h3>Order Total</h3>
      <h3>${{total}}</h3>
  </div>
</div>

Understanding toaster alerts

We found a need for alerts in our application, a toaster alert simple tells the user he or she has carried out a specific action, or an action refuses to propagate. Most web apps like facebook, twitter use notifications, how do we implement in app notifications?

We need to create interfaces or classes for our alert, create a file called alert.ts
Our alert interface, should have the following,

  1. type: The type would be and enum value where we can switch the type of alert.
  2. message: The message should be a string, which is the message on the alert.
  3. alertId: We need to save the id of the alert.
  4. keepAfterRouteChange: We want to set the value of the route change to a boolen.

Lastly, we add a constructor for our class and use the object.assign method to add the init with the type of alert to the object.

export class Alert {
    type: AlertType;
    message: string;
    alertId: string;
    keepAfterRouteChange: boolean;

    constructor(init?:Partial<Alert>) {
        Object.assign(this, init);
    }
}

The enum value for the type which is the AlertType

export enum AlertType {
    Success,
    Error,
    Info,
    Warning
}

Lets generate the service to initiate the alert service

ng generate service alert

Open up the generate service, we need to import a lot of modules

import { Injectable } from '@angular/core';
import { Router, NavigationStart } from '@angular/router';
import { Observable } from 'rxjs';
import { Subject } from 'rxjs/Subject';
import 'rxjs/add/operator/filter';
import { Alert, AlertType } from '../app/alert';

The Router and the NavigationStart Module are used for handling navigation, we are going to use this module to know once the route has changed so we can clear the message

Lets start building the alertService
we are going to start by creating an observable out of the alert and set a variable to know nce a particular route has changed.

private subject = new Subject<Alert>();
private keepAfterRouteChange = false;

lets injected the router module in the constructor and subscribe for the events, next we check if the event is an instance of NavigationStart, if this returns false, we set the variable keepafterRouteChange to false, else we call a method to clear the message.

 constructor(private router: Router) {
        // clear alert messages on route change unless 'keepAfterRouteChange' flag is true
        router.events.subscribe(event => {
            if (event instanceof NavigationStart) {
                if (this.keepAfterRouteChange) {
                    // only keep for a single route change
                    this.keepAfterRouteChange = false;
                } else {
                    // clear alert messages
                    this.clear();
                }
            }
        });
    }

success.png

Let create a method to subscribe for alerts. The method accepts the alertId and return the alert with the alertId.

getAlert(alertId?: string): Observable<any> {
        return this.subject.asObservable().filter((x: Alert) => x && x.alertId === alertId);
 }

Here, we are using the filter method to search through the observable and return the particular alert with the alertId.

 return this.subject.asObservable().filter((x: Alert) => x && x.alertId === alertId);

lets work on some convenience method for the alert service.

success(message: string) {
        this.alert(new Alert({ message, type: AlertType.Success }));
    }

    error(message: string) {
        this.alert(new Alert({ message, type: AlertType.Error }));
    }

    info(message: string) {
        this.alert(new Alert({ message, type: AlertType.Info }));
    }

    warn(message: string) {
        this.alert(new Alert({ message, type: AlertType.Warning }));
    }

Above, we are using the alert interface to create a different type of alert for different alert type (success, error, info and warning) by creating a new object with the different AlertType for different methods.

The method accept a message.

Creating the method that add alerts to the observable.

The observable.next() receives a new alert to emit
and changes the keepRouteChange to the private variable of alert.keepRouteChange which is in the interface (alert)

alert(alert: Alert) {
        this.keepAfterRouteChange = alert.keepAfterRouteChange;
        this.subject.next(alert);
}

Finally, the last method clears the alert from the screen, which uses the constructor method of the alert interface.

clear(alertId?: string) {
        this.subject.next(new Alert({ alertId }));
}

The clear method, update the observable with the new alert of an empty string.

Building the alert component.

The alert component has to be created, which would be displayed on the Dom

ng generate component alert

Open up the alert.component.ts. When the component is mounted or created on the DOM, we want to subscribe for an alert, if no alert message, we want t clear the alert array we are about create, but when there is an alert, we need to push it into the array.

ngOnInit() {
        this.alertService.getAlert(this.id).subscribe((alert: Alert) => {
            if (!alert.message) {
                // clear alerts when an empty alert is received
                this.alerts = [];
                return;
            }

            // add alert to array
            this.alerts.push(alert);
        });
    }

We also need a method to remove a particular alert, this method accepts the alert and uses the filter method to remove the particular alert from the array.

removeAlert(alert: Alert) {
        this.alerts = this.alerts.filter(x => x !== alert);
}

Since we are using bootstrap 4, we need to be able to switch the classes based on the alert type to return different class types.

cssClass(alert: Alert) {
        if (!alert) {
            return;
        }

        // return css class based on alert type
        switch (alert.type) {
            case AlertType.Success:
                return 'alert alert-success';
            case AlertType.Error:
                return 'alert alert-danger';
            case AlertType.Info:
                return 'alert alert-info';
            case AlertType.Warning:
                return 'alert alert-warning';
        }
}

success.png

If there was no alert, we want to return. The above cases returns various bootstrap alert classes.
which would be used for the alert component.

lastly, lets update the alert.component.html to show the markup for the alert
open up the alert.component.html and add the markup below.

<div *ngFor="let alert of alerts" class="{{ cssClass(alert) }} alert-dismissable">
  {{alert.message}}
  <a class="close" (click)="removeAlert(alert)">&times;</a>
</div>

If you noticed, are iterating through the alerts array and displaying the alert's message.
we are also displaying the class from the class method that returns different bootstrap class.
The click handle uses the removeAlert method of the component to dismiss the alert.

Using the alert component in another component.

In the item-feed, we would like to display a message to the user when he adds a particular item to the cart.

Lets add two methods to the item-feed.component.ts, a method called success and another method to clear the alert.

 success(message: string) { 
    this.alertService.success(message);
  }

  clear() {
    this.alertService.clear();
  }

Here we are using the alertService.success to pass a new alert. The success method returns the class of "alert-success" to the alert component.
and the clear method clears the alert from the observable.

finally, to call this method, we need to add it to the point where we add items to the cart. and add a setTieOut() method before cearing the alertfrom the dom.


    if (!this.getItemInCart(item)){
      this.onIncrementItemQuantity(item)
      this.cartService.cartItems.push(item);
      this.cartService.announceCartItem(item);
      this.cartItems = this.cartService.cartItems;
      this.success("Successfully added to cart")
      setTimeout(()=>{
        this.alertService.clear()
      }, 2000)
      
    }
  }

success.png

The success method receives a message of "successfully added to cart".

newgif.gif

Conclusion

In this series, we where able able to calculate the total amount in the order and create aert system for our application. To understand this tutorial, take a look at our previous tutorial and fork the voltron repo for the project

Curriculum

Resources

Sort:  

Congratulations @sirfreeman! You have completed some achievement on Steemit and have been rewarded with new badge(s) :

Award for the number of upvotes

Click on the badge to view your Board of Honor.
If you no longer want to receive notifications, reply to this comment with the word STOP

To support your work, I also upvoted your post!

Do not miss the last post from @steemitboard!


Participate in the SteemitBoard World Cup Contest!
Collect World Cup badges and win free SBD
Support the Gold Sponsors of the contest: @good-karma and @lukestokes


Do you like SteemitBoard's project? Then Vote for its witness and get one more award!

Thank you for your contribution.

Looking forward to your upcoming tutorials.

Your contribution has been evaluated according to Utopian policies and guidelines, as well as a predefined set of questions pertaining to the category.

To view those questions and the relevant answers related to your post, click here.


Need help? Write a ticket on https://support.utopian.io/.
Chat with us on Discord.
[utopian-moderator]

Hey @sirfreeman
Thanks for contributing on Utopian.
We’re already looking forward to your next contribution!

Contributing on Utopian
Learn how to contribute on our website or by watching this tutorial on Youtube.

Want to chat? Join us on Discord https://discord.gg/h52nFrV.

Vote for Utopian Witness!

Congratulations @sirfreeman! You have completed some achievement on Steemit and have been rewarded with new badge(s) :

Award for the number of upvotes

Click on the badge to view your Board of Honor.
If you no longer want to receive notifications, reply to this comment with the word STOP

Do not miss the last post from @steemitboard!


Participate in the SteemitBoard World Cup Contest!
Collect World Cup badges and win free SBD
Support the Gold Sponsors of the contest: @good-karma and @lukestokes


Do you like SteemitBoard's project? Then Vote for its witness and get one more award!

Congratulations @sirfreeman! You have completed the following achievement on Steemit and have been rewarded with new badge(s) :

Award for the number of upvotes

Click on the badge to view your Board of Honor.
If you no longer want to receive notifications, reply to this comment with the word STOP

Do not miss the last post from @steemitboard:
SteemitBoard World Cup Contest - The semi-finals are coming. Be ready!


Participate in the SteemitBoard World Cup Contest!
Collect World Cup badges and win free SBD
Support the Gold Sponsors of the contest: @good-karma and @lukestokes


Do you like SteemitBoard's project? Then Vote for its witness and get one more award!

@sirfreeman you were flagged by a worthless gang of trolls, so, I gave you an upvote to counteract it! Enjoy!!