by Retail illustration Sail Ho Studio Shopping and food delivery carts are a well-resourced topic but what if we take it a little further and create a shared cart for an office community or group of friends that will be useable, clear, and secure. This would take a load of at least one human in that group. Let me tell you how it goes sometimes in our office. Every now and then we need to make a group order of something or sign up for a corporate event with a choice of food. create a shared Google Doc and a clusterf**k of anonymous animals starts an onslaught. We If there was a service you could attach to any business and automate the complicated order and payment procedures, it could be useful. This is how you build such a service using Angular and Firebase Project source code on . GitHub Setting up a project 1. Add a GitHub repository, come up with a name, description, specify the type, the license, and the file. I called it FoodOrder: .gitignore 2. Then, clone the repository. For this, enter the following command in terminal: git clone git@github.com:denismaster/FoodOrder.git 3. Change the repository address on yours: 4. Then, navigate to the created catalog: cd FoodOrder 5. Install Angular CLI and create a new Angular application with it: npm install -g @angular/cli 6. Create a new project: ng FoodOrder --directory. --force new We are using the –directory option to create a project in the existing directory and the –force option to rewrite the README and . files. gitignore 7. Open the project in an editor. I use Visual Studio Code: 8. For design, we are using the Bootstrap 4.0 library. Install it with the following command: npm install bootstrap Developing the menu We’ll group the menu items into categories with different titles, prices, size in grams, and all sorts of additional details. Each category is defined by the title and the list of items. 1. Create a file with the contents . models.ts from here The Product class contains basic information about the product. describes the category of the product. The final menu is just a massive. ProductCategory ProductCategory[] 2. Add some items. Create a new service, let’s call it MenuService with a list of our products : from here In our case, the service returns the list of predefined objects but it can be changed at any given time, for example, for it to make a backend request. 3. Add components to display the menu, categories, and products. For this, use the following folder structure: The component displays the menu as a whole. This component is simple and is there just to show the complete list of items. Use the HTML code . And the Typescript . MenuComponent from here from here 4. Create the component. It is used to display the categories. Create a card with the category name and the list of products as a list of components. Use the HTML code for . And the Typescript . MenuCategoryComponent MenuCategoryItemComponent MenuCategoryComponent from here from here 5. Create the component. It is used to display the product itself, as well as the UI connected to it. In this component, we react to a click and display additional interface items for portion sizes. MenuCategoryItemComponent 6. You’ll need an additional class: UpdateOrderAction UpdateOrderAction { action: | name: ; category?: ; price: ; } export interface "add" "remove" string string number This class describes the changes that happen to the order itself. Use the HTML code for . And the Typescript . MenuCategoryItemComponent from here from here 7. Collect the entire code. For that, create the component. At this stage, it will only display MenuComponent: HomeComponent <app-menu [menu]= >< "menu$ | async" /app-menu> And the following Typescript: ({ selector: , templateUrl: , }) HomeComponent OnInit { menu$: Observable<ProductCategory[]>; ( ) { } ngOnInit() { .menu$ = .menuService.getMenu().pipe(take( )); } } @Component 'app-home' './home.component.html' export class implements constructor menuService: MenuService private this this 1 Here’s what the menu looks like at this point. Everything works fine but there is no total cost calculation and no sync. To implement those, we’ll need Firebase. Integrating Firebase As a backend, we’ll be using Google Firebase. We use Cloud FireStore to store the data. This is a NoSQL document-oriented database which uses collections and documents inside of them. The collections can be attached to different documents. Each table allows sending and changing data to all the subscribers in real time. This creates an instant update for all the clients. For this, use the menu we’ve developed. Install the AngularFire2 library . from here npm install firebase /fire --save @angular Add new models to models.ts to describe the order. OrderItem { id: , name: ; category: , price: } Order { dishes: OrderItem[] } export interface string string string number export interface Inside the Firestore, we’ll be storing the order as an object with the dishes property as an massive. Inside this massive, we store the menu item name required to delete the order. OrderItem[] 1. Create a new service. We’ll be using it for the Firebase interaction. Creating an order: OrderService { Injectable } ; { Subject } ; { Router } ; { AngularFirestore } ; { v4 uuid } ; { Order } ; ({ providedIn: }) OrderService { ( ) { } createOrder() { orderId = uuid(); sessionStorage.setItem( , orderId) .afs.collection( ).doc<Order>(orderId).set({ dishes: [] }) .then(_ => { .router.navigate([ ], { queryParams: { : orderId } }); }) .catch( { alert(err); }) } } import from '@angular/core' import from 'rxjs' import from '@angular/router' import from '@angular/fire/firestore' import as from 'uuid' import from './models' @Injectable 'root' export class constructor router: Router, afs: AngularFirestore, private private let 'current_order' this "orders" this '/' 'order' => err 2. To check the order code, use ActivatedRoute. We also save the existing order code value in the SessionStorage to easily recover it when reloading the page. { Component, OnInit } ; { ProductCategory } ; { Observable } ; { take } { MenuService } ; { ActivatedRoute } ; { OrderService } ; ({ selector: , templateUrl: , }) HomeComponent OnInit { menu$: Observable<ProductCategory[]>; orderId: ; ( ) { } ngOnInit() { order = .activatedRoute.snapshot.queryParams.order; (!order) { .orderService.createOrder(); .orderId = sessionStorage.getItem( ); } { .orderId = order; sessionStorage.setItem( , order); } .menu$ = .menuService.getMenu().pipe(take( )); } } import from '@angular/core' import from '../models' import from 'rxjs' import from 'rxjs/operators' import from '../menu/menu.service' import from '@angular/router' import from '../order.service' @Component 'app-home' './home.component.html' export class implements string constructor activatedRoute: ActivatedRoute, menuService: MenuService, orderService: OrderService private private private const this if this this 'current_order' else this 'current_order' this this 1 3. Create a special component to display the selected items (the cart). Let’s call it : OrderStatusComponent {{category}}: {{dish.name}} x {{dish.amount}} {{dish.name}} Total: {{ totalPrice}} ₽ < = > div class "card mb-3" < = > div class "card-body" < * = > div ngFor "let category of dishCategories" < > strong </ > strong < * = > span ngFor "let dish of categorizedDishes[category]" < * = > span ngIf "dish.amount>1" </ > span < * = > span ngIf "dish.amount==1" </ > span </ > span </ > div < > div < > strong </ > strong </ > div </ > div </ > div { Order } ; { Input, Component } ; ({ selector: , templateUrl: , }) OrderStatusComponent{ () order: Order = ; dishes() { (! .order || ! .order.dishes) []; .order.dishes; } categorizedDishes() { .dishes.reduce( { (!prev[current.category]) { prev[current.category] = [{ name: current.name, amount: }] } { productsInCategory: { name: , amount: }[] = prev[current.category]; currentDishIndex = productsInCategory.findIndex( p.name == current.name) (currentDishIndex !== ) { productsInCategory[currentDishIndex].amount++; } { productsInCategory.push({ name: current.name, amount: }); } } prev; }, {}); } dishCategories() { .keys( .categorizedDishes); } totalPrice() { .dishes.reduce( { p + c.price }, ) } } import from '../models' import from '@angular/core' @Component 'app-order-status' './order-status.component.html' export class @Input null get if this this return return this get return this ( ) => prev, current if 1 else let string number let => p if -1 else 1 return get return Object this get return this ( ) => p, c return 0 4. Add the new method in : getOrder() OrderService getOrder(orderId: ): Observable<Order> { orderDoc = .afs.doc<Order>( ); orderDoc.valueChanges(); } string const this `orders/ ` ${orderId} return With this method, we subscribe to the changes in the document in Firestore. This is how the data sync happens between different users who use the same order code. 5. Finally, add the method in , which will be updating the contents of the order in Firestore: updateOrder() OrderService updateOrder(orderId: , update: UpdateOrderAction) { order = .afs.doc<Order>( ).valueChanges().pipe(take( )).toPromise(); (update.action == ) { .afs.doc<Order>( ).update({ dishes: < >firebase.firestore.FieldValue.arrayUnion({ id: uuid(), name: update.name, category: update.category, price: update.price }) }) } { dishIds = order.dishes.filter( d.name==update.name).map( d.id); idToRemove = dishIds[ ]; (!idToRemove) ; .afs.doc<Order>( ).update({ dishes: < >firebase.firestore.FieldValue.arrayRemove({ id: idToRemove, name: update.name, category: update.category, price: update.price }) }) } } async string const await this `orders/ ` ${orderId} 1 if "add" this `orders/ ` ${orderId} any else const => d => d const 0 if return this `orders/ ` ${orderId} any Because of Firebase we also have a shareable cart between different users: 6. Add the ability to clear the cart with this code in : OrderService _clear$ = Subject< >(); clearOrder$() { ._clear$.asObservable(); } clearOrder(orderId: ) { .afs.doc<Order>( ).update({ dishes: [] }) ._clear$.next(); } handleOrderClearing(){ ._clear$.next(); } private new void get return this string this `orders/ ` ${orderId} this this 7. Modify : MenuCategoryItemComponent ( ) { .orderService.clearOrder$.subscribe( { .amount= ; sessionStorage.removeItem( ) }) } constructor orderService: OrderService private this => () this 0 ` - ` ${this.orderId} ${this.product.name} 8. Here’s how another user can clear the cart: .order$.pipe(filter( !!order)).subscribe( { (order.dishes && !order.dishes.length) { .orderService.handleOrderClearing(); } }) this => order => order if this That’s basically it. Now the website has shared access and all it takes to start working is implement the order feature. Setting up CI/CD To set up CI/CD, use TravisCI. This product is really simple and sustainable, especially in the GitHub integration. For the setup, you’ll need the file in the root of the repository: .travis.yml This script assembles your Angular application and deploys the master branch in Firebase. For this, you’ll need the token which you receive with the following command: firebase login :ci An example build on TravisCI: written by Denis Obolensky & Moses Kim