Angular Example: Coffee Ordering

This is the final version of our Angular example. It allows the user to input their coffee orders, saves them to a list of orders in our controller, then sends those orders to the backend (PHP) for processing.

Please consider this as an example and a start: the details of processing the data and handling the responses have been left up to you.

Angular index.html

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Ordersup</title>
  <base href="/">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" type="image/x-icon" href="favicon.ico">
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script>
</head>
<body>
  <app-root></app-root>
</body>
</html>

Angular Root Module

Root Module Definition: app.module.ts

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { FeedbackModule } from './feedback/feedback.module';
import { HttpClientModule } from '@angular/common/http';

import { AppComponent } from './app.component';
import { OrdersComponent } from './orders/orders.component';
import { NavbarComponent } from './navbar/navbar.component';

@NgModule({
  declarations: [
    AppComponent,
    OrdersComponent,
    NavbarComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
    FeedbackModule,
    HttpClientModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Root Component Controller: app.component.ts

import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'Orders Up';
  author = "Robbie";
}

Root Component View: app.component.html

<!-- NAVBAR -->
<app-navbar></app-navbar>

<div class="container">
    <div class="row">
        <div class="col-md-8">
            <h1></h1>
            <p>By: </p>
            
            <app-orders></app-orders>
        </div>
        <div class="col-md-4">
            <app-contact></app-contact>
        </div>
    </div>
</div>

Drink Model (Class): drink.ts

export class Drink {
    constructor(
        public description: string,
        public size: string,
        public temp: string,
        public name: string,
        public email: string) {}
}

Orders Component Controller: orders/orders.component.ts

import { Component, OnInit } from '@angular/core';
import { Drink } from '../drink';
import { OrderService } from '../order.service';

@Component({
  selector: 'app-orders',
  templateUrl: './orders.component.html',
  styleUrls: ['./orders.component.css']
})
export class OrdersComponent implements OnInit {

  coffee: string;
  
  drink: Drink;
  
  sizes: Array<string> = ["small", "medium", "large"];
  temps: Array<string> = ["hot", "cold"];
  
  orders: Array<Drink> = [];
  
  placeMsg: string;

  constructor( private orderService: OrderService ) {
    this.coffee = "Peppermint Mocha";
    this.placeMsg = "";
    this.drink = new Drink("", "", "", "", "");
                            
  }
  
  placeOrder(): void {
    this.placeMsg = "Thank you, " + this.drink.name + ".";
    this.placeMsg += "You ordered a " + this.drink.description + ", size"
                        + this.drink.size + ".";
  }
  
  submitForm(data: any):void {
     let dr = new Drink(data.description, data.size, data.temp,
                        data.name, data.email);
     this.orders.push(dr);
  }
  
  response: any;
  orderTime: string = "";
  sendOrder(): void {
    this.orderService.processOrder(this.orders).subscribe(
        (respData) =>  { 
            this.response = respData; 
            this.orderTime = respData.time; 
            },
        (error) => { console.log("Error: ", error); }
    );
  }

  ngOnInit(): void {
  }
}

Orders Component View: orders/orders.component.html

<p>Coffee: </p>

<p>Drink Desc: </p>

<p>Drink Model: </p>

<form #orderForm="ngForm" (ngSubmit)="submitForm(orderForm.value)">
    <div class="mb-3">
        <label for="description" class="form-label">Drink Description</label>
        <input [(ngModel)]="drink.description"
            type="text" class="form-control" name="description" placeholder="ex: Pumpkin Spice Latte"/>
    </div>
    <div class="mb-3">
        <label for="size" class="form-label">Size</label>
        <select [(ngModel)]="drink.size"
                class="form-select" name="size">
            <option *ngFor="let size of sizes"></option>
        </select>
    </div>
    <div class="mb-3">
        <label for="temp" class="form-label">Temperature</label>
        <select [(ngModel)]="drink.temp"
                class="form-select" name="temp">
            <option *ngFor="let temp of temps"></option>
        </select>
    </div>
    <div class="mb-3">
        <label for="name" class="form-label">Name</label>
        <input [(ngModel)]="drink.name"
            required
            #name="ngModel"
            minlength="4" maxlength="100"
            [class.is-invalid] = "name.invalid && name.touched"
            type="text" class="form-control" name="name"/>
        <small class="text-danger" 
                [class.d-none]= "name.valid || name.untouched">
                Name is required</small>
    </div>
    <div class="mb-3">
        <label for="email" class="form-label">Email</label>
        <input [(ngModel)]="drink.email"
            required
            #email="ngModel"
            [class.is-invalid] = "email.invalid && email.touched"
            pattern="^([a-zA-Z0-9_\-\.]+)@([a-zA-Z0-9_\-\.]+)\.([a-zA-Z]{2,10})$"
            type="email" class="form-control" name="email" placeholder="Your contact email"/>        
        <small class="text-danger" 
                [class.d-none]= "email.valid || email.untouched">
                Email is required</small>
        
    </div>
    <div class="mb-3">
        <button class="btn btn-primary" 
            [disabled]="!orderForm.form.valid"
            (click)="placeOrder()">
            Add Item
        </button>
    </div>
</form>

<p>Description from form: </p>

<p>Entire form: </p>

<p style="color: blue;"></p>

<hr>

<h4>Your Orders</h4>

<p></p>

<table class="table">
    <thead>
        <tr>
            <th>Drink</th>
            <th>Temp</th>
            <th>Size</th>
            <th>Name</th>
        </tr>
    </thead>
    <tbody>
        <tr *ngFor="let order of orders">
            <td></td>
            <td></td>
            <td></td>
            <td></td>
        </tr>
    </tbody>
</table>

<div class="mb-3">
    <button class="btn btn-success" 
        [disabled]="orders.length == 0"
        (click)="sendOrder()">
        Send Order
    </button>
</div>

<hr>

<p> PHP Response:  </p>

<p> Order time:  </p>
import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-navbar',
  templateUrl: './navbar.component.html',
  styleUrls: ['./navbar.component.css']
})
export class NavbarComponent implements OnInit {

  constructor() { }

  ngOnInit(): void {
  }

}
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
  <div class="container">
    <a class="navbar-brand" href="#">Orders Up</a>
    <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
      <span class="navbar-toggler-icon"></span>
    </button>
    <div class="collapse navbar-collapse" id="navbarSupportedContent">
      <ul class="navbar-nav me-auto mb-2 mb-lg-0">
        <li class="nav-item">
          <a class="nav-link active" aria-current="page" href="#">Home</a>
        </li>
        <li class="nav-item">
          <a class="nav-link" href="#">Prices</a>
        </li>
      </ul>
    </div>
  </div>
</nav>

Feedback Module

Feedback Module Definition: feedback/feedback.module.ts

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ContactComponent } from './contact/contact.component';



@NgModule({
  declarations: [
    ContactComponent
  ],
  imports: [
    CommonModule
  ], exports: [
    ContactComponent
  ]
})
export class FeedbackModule { }

Contact Component Controller: feedback/contact/contact.component.ts

import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-contact',
  templateUrl: './contact.component.html',
  styleUrls: ['./contact.component.css']
})
export class ContactComponent implements OnInit {

  constructor() { }

  ngOnInit(): void {
  }

}

Contact Component View: feedback/contact/contact.component.html

<p>contact works!</p>

PHP Back-End Code

drinks.php

<?php
// REQUIRED HEADERS FOR CORS
// Allow access to our development server, localhost:4200
header("Access-Control-Allow-Origin: http://localhost:4200");
header("Access-Control-Allow-Headers: X-Requested-With, Content-Type, Origin, Authorization, Accept, Client-Security-Token, Accept-Encoding");
header("Access-Control-Max-Age: 1000");
header("Access-Control-Allow-Methods: POST, GET, OPTIONS, PUT");

$request = file_get_contents("php://input");
$data = json_decode($request, true);

// Do processing of the data

$output = [
    "time" => date("Y-m-d g:i a"),
    "request" => $data
];

$drinks = [];

foreach ($data as $drink) {
    array_push($drinks, "{$drink["description"]} for {$drink["name"]}");
}

$output["drinks"] = $drinks;

// Send the result to the client (print it out)

header("Content-Type: application/json");
echo json_encode($output, JSON_PRETTY_PRINT);