Order Service page
Writing the Order Service
Overview
In this part, we will:
- Create a new order service
- Write interfaces to describe orders and items in orders
- Add data methods to our service
- Import our new order service to our component & create an order
- Show completed order in UI
Problem 1: Create Order Service and Export Items and Order interfaces
We need to create a new service to handle creating and updating orders. We’ll need three interfaces - one to describe the order form data, one to describe the order, and one to describe items in the order.
P1: Technical requirements
Create a new service order
in the order directory, and write and export CreateOrderDto
, Order
and Item
interfaces representing these objects in the new service:
const createOrderDto = {
restaurant: '12345',
name: 'Jennifer',
address: '123 Main st',
phone: '867-5309',
items: [
{
name: 'tacos',
price: 6.99,
},
],
};
const order = {
_id: 'a123123bdd',
restaurant: '12345',
name: 'Jennifer',
address: '123 Main st',
phone: '867-5309',
status: 'new',
items: [
{
name: 'tacos',
price: 6.99,
},
],
};
P1: What you Need to Know
How to create services (you learned this in previous sections! ✔️)
How to write interfaces (you learned this in previous sections! ✔️)
ng g service order/order
P1: Solution
Click to see the solution
✏️ Update src/app/order/order.service.tsimport { Injectable } from '@angular/core';
export interface Item {
name: string;
price: number;
}
export interface CreateOrderDto {
restaurant: string;
name: string;
address: string;
phone: string;
items: Item[];
}
export interface Order {
_id: string;
restaurant: string;
name: string;
address: string;
phone: string;
status: string;
items: Item[];
}
@Injectable({
providedIn: 'root'
})
export class OrderService {
constructor() { }
}
Problem 2: Finish the Order Service
With our order service we’ll want to be able to create new orders, updating existing orders, delete orders, and view all orders.
P2: Setup
✏️ Paste the following into src/app/order/order.service.spec.ts:
import { HttpRequest } from '@angular/common/http';
import {
HttpClientTestingModule,
HttpTestingController,
} from '@angular/common/http/testing';
import { TestBed } from '@angular/core/testing';
import { OrderService } from './order.service';
describe('OrderService', () => {
let httpTestingController: HttpTestingController;
let orderService: OrderService;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
providers: [OrderService],
});
httpTestingController = TestBed.inject(HttpTestingController);
orderService = TestBed.inject(OrderService);
});
it('should make a GET request to get orders', () => {
const mockOrders = {
data: [
{
_id: 'adsfsdf',
name: 'Jennifer',
restaurant: 'FoodLand',
address: '123 main',
phone: '555-555-5555',
status: 'new',
items: [
{
name: 'nummy fries',
price: 2.56,
},
],
},
],
};
orderService.getOrders().subscribe((orders) => {
expect(orders).toEqual(mockOrders);
});
const url = 'http://localhost:7070/orders';
const req = httpTestingController.expectOne(url);
expect(req.request.method).toEqual('GET');
req.flush(mockOrders);
httpTestingController.verify();
});
it('should make a post request to create an order', () => {
const mockOrder = {
_id: 'adsfsdf',
restaurant: '12345',
name: 'Jennifer',
address: '123 main',
phone: '555-555-5555',
status: 'new',
items: [
{
name: 'nummy fries',
price: 2.56,
},
],
};
const orderForm = {
restaurant: '12345',
name: 'Jennifer',
address: '123 main',
phone: '555-555-5555',
items: [
{
name: 'nummy fries',
price: 2.56,
},
],
};
orderService.createOrder(orderForm).subscribe((order) => {
expect(order).toEqual(mockOrder);
});
const url = 'http://localhost:7070/orders';
httpTestingController
.expectOne(
(request: HttpRequest<any>) =>
request.method == 'POST' &&
request.url == url &&
JSON.stringify(request.body) ==
JSON.stringify({ ...orderForm, status: 'new' })
)
.flush(mockOrder);
httpTestingController.verify();
});
it('should make a put request to update an order', () => {
const mockOrder = {
_id: 'adsfsdf',
restaurant: '12345',
name: 'Jennifer',
address: '123 main',
phone: '555-555-5555',
status: 'old',
items: [
{
name: 'nummy fries',
price: 2.56,
},
],
};
const updatedOrder = {
_id: 'adsfsdf',
restaurant: '12345',
name: 'Jennifer',
address: '123 main',
phone: '555-555-5555',
status: 'delivered',
items: [
{
name: 'nummy fries',
price: 2.56,
},
],
};
orderService.updateOrder(mockOrder, 'delivered').subscribe((order) => {
expect(order).toEqual(updatedOrder);
});
const url = 'http://localhost:7070/orders/adsfsdf';
httpTestingController
.expectOne(
(request: HttpRequest<any>) =>
request.method == 'PUT' &&
request.url == url &&
JSON.stringify(request.body) == JSON.stringify(updatedOrder)
)
.flush(updatedOrder);
httpTestingController.verify();
});
it('should make a delete request to delete an order', () => {
const mockOrder = {
_id: 'adsfsdf',
restaurant: '12345',
name: 'Jennifer',
address: '123 main',
phone: '555-555-5555',
status: 'old',
items: [
{
name: 'nummy fries',
price: 2.56,
},
],
};
orderService.deleteOrder('adsfsdf').subscribe((order) => {
expect(order).toEqual(mockOrder);
});
const url = 'http://localhost:7070/orders/adsfsdf';
const req = httpTestingController.expectOne(url);
expect(req.request.method).toEqual('DELETE');
req.flush(mockOrder);
httpTestingController.verify();
});
});
Now, try to understand what this test is doing and
implement the remainder of order.service.ts
to
get the tests to pass.
✏️ Run your tests with:
ng test
P2: What you need to know
- The method signatures for the methods you’ll be adding to
OrderService
:getOrders(): Observable<{data: Order[]}>
should make aGET
requestcreateOrder(orderForm: CreateOrderDto): Observable<Order>
should make aPOST
requestupdateOrder(order: Order, status: string): Observable<Order>
should make aPUT
request to/orders/<order-id>
deleteOrder(orderId: string): Observable<Order>
should make aDELETE
request to/orders/<order-id>
- You will need to make sure
HttpClient
is imported and added as a property in theOrderService
constructor. - You can pass a request
body
using the second argument ofHttpClient
post
andput
methods.
P2: Solution
Click to see the solution
✏️ Update src/app/order/order.service.tsimport { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { environment } from '../../environments/environment';
export interface Item {
name: string;
price: number;
}
export interface CreateOrderDto {
restaurant: string;
name: string;
address: string;
phone: string;
items: Item[];
}
export interface Order {
_id: string;
restaurant: string;
name: string;
address: string;
phone: string;
status: string;
items: Item[];
}
@Injectable({
providedIn: 'root'
})
export class OrderService {
constructor(private httpClient: HttpClient) {}
getOrders(): Observable<{ data: Order[] }> {
return this.httpClient.get<{ data: Order[] }>(
environment.apiUrl + '/orders'
);
}
createOrder(order: CreateOrderDto): Observable<Order> {
const orderData = {
...order,
status: 'new',
};
return this.httpClient.post<Order>(
environment.apiUrl + '/orders',
orderData
);
}
updateOrder(order: Order, action: string): Observable<Order> {
const orderData = {
...order,
status: action,
};
return this.httpClient.put<Order>(
environment.apiUrl + '/orders/' + orderData._id,
orderData
);
}
deleteOrder(id: string): Observable<Order> {
return this.httpClient.delete<Order>(environment.apiUrl + '/orders/' + id);
}
}
Problem 3: Use the OrderService in the OrderComponent to Create an Order
P3: Technical requirements
For this problem, we will:
- Create an order when the user submits the form using the service.
- Disable the button while the order is being processed.
- Show the completed order to the user.
- Let the user start a new order.
How we will solve this:
- We will import the order service, and save it as
orderService
in theOrderComponent
’sconstructor
. - Call
orderService
’screateOrder
with theorderForm
’s values. - While the order is being created
orderProcessing
should betrue
. - Once complete,
orderComplete
should be set totrue
and set back tofalse
whenstartNewOrder()
is called. - We will save the completed order in
completedOrder
.
A FormGroup’s
value
property is wrapped inPartial
type because controls are removed from the form’s value when disabled. For our case, we don’t need to disable controls. We can use a FormGroup’sgetRawValue()
method to access its value with the full type.
P3: Setup
Before starting:
1. ✏️ Update src/app/order/order.component.html to show the completed order:
<div class="order-form">
<ng-container *ngIf="orderComplete && completedOrder; else showOrderForm">
<h3>Thanks for your order {{ completedOrder.name }}!</h3>
<div>
<label class="control-label">
Confirmation Number: {{ completedOrder._id }}</label
>
</div>
<h4>Items ordered:</h4>
<ul class="list-group panel">
<li class="list-group-item" *ngFor="let item of completedOrder.items">
<label>
{{ item.name }} <span class="badge">${{ item.price }}</span>
</label>
</li>
<li class="list-group-item">
<label>
Total <span class="badge">${{ orderTotal }}</span>
</label>
</li>
</ul>
<div>
<label class="control-label"> Phone: {{ completedOrder.phone }} </label>
</div>
<div>
<label class="control-label">
Address: {{ completedOrder.address }}
</label>
</div>
<p>
<button (click)="startNewOrder()">Place another order</button>
</p>
</ng-container>
<ng-template #showOrderForm>
<h2>Order here</h2>
<form
*ngIf="orderForm && restaurant"
[formGroup]="orderForm"
(ngSubmit)="onSubmit()"
>
<tabset>
<tab heading="Lunch Menu" *ngIf="restaurant.menu.lunch">
<ul class="list-group">
<pmo-menu-items
[items]="restaurant.menu.lunch"
formControlName="items"
></pmo-menu-items>
</ul>
</tab>
<tab heading="Dinner Menu" *ngIf="restaurant.menu.dinner">
<ul class="list-group">
<pmo-menu-items
[items]="restaurant.menu.dinner"
formControlName="items"
></pmo-menu-items>
</ul>
</tab>
</tabset>
<div class="form-group">
<label class="control-label">Name:</label>
<input name="name" type="text" formControlName="name" />
<p>Please enter your name.</p>
</div>
<div class="form-group">
<label class="control-label">Address:</label>
<input name="address" type="text" formControlName="address" />
<p class="help-text">Please enter your address.</p>
</div>
<div class="form-group">
<label class="control-label">Phone:</label>
<input
name="phone"
type="text"
pmoOnlyNumbers
formControlName="phone"
/>
<p class="help-text">Please enter your phone number.</p>
</div>
<div class="submit">
<h4>Total: ${{ orderTotal }}</h4>
<div class="loading" *ngIf="orderProcessing"></div>
<button
type="submit"
[disabled]="!orderForm.valid || orderProcessing"
class="btn"
>
Place My Order!
</button>
</div>
</form>
</ng-template>
</div>
2. ✏️ Update src/app/order/order.component.ts to have a onSubmit
method and
a startNewOrder
that will start a new order.
import { Component, OnInit, OnDestroy } from '@angular/core';
import {
AbstractControl,
FormBuilder,
FormControl,
FormGroup,
ValidationErrors,
ValidatorFn,
Validators,
} from '@angular/forms';
import { ActivatedRoute } from '@angular/router';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { Restaurant } from '../restaurant/restaurant';
import { RestaurantService } from '../restaurant/restaurant.service';
export interface Item {
name: string;
price: number;
}
export interface OrderForm {
restaurant: FormControl<string>;
name: FormControl<string>;
address: FormControl<string>;
phone: FormControl<string>;
items: FormControl<Item[]>;
}
// CUSTOM VALIDATION FUNCTION TO ENSURE THAT THE ITEMS FORM VALUE CONTAINS AT LEAST ONE ITEM.
function minLengthArray(min: number): ValidatorFn {
return (c: AbstractControl): ValidationErrors | null => {
if (c.value.length >= min) {
return null;
}
return { minLengthArray: { valid: false } };
};
}
@Component({
selector: 'pmo-order',
templateUrl: './order.component.html',
styleUrl: './order.component.css',
})
export class OrderComponent implements OnInit, OnDestroy {
orderForm?: FormGroup<OrderForm>;
restaurant?: Restaurant;
isLoading = true;
orderTotal = 0.0;
completedOrder: any;
orderComplete = false;
orderProcessing = false;
private onDestroy$ = new Subject<void>();
constructor(
private route: ActivatedRoute,
private restaurantService: RestaurantService,
private formBuilder: FormBuilder
) {}
ngOnInit(): void {
// GETTING THE RESTAURANT FROM THE ROUTE SLUG
const slug = this.route.snapshot.paramMap.get('slug');
if (slug) {
this.restaurantService
.getRestaurant(slug)
.pipe(takeUntil(this.onDestroy$))
.subscribe((data: Restaurant) => {
this.restaurant = data;
this.isLoading = false;
this.createOrderForm();
});
}
}
ngOnDestroy(): void {
this.onDestroy$.next();
this.onDestroy$.complete();
}
createOrderForm(): void {
this.orderForm = this.formBuilder.nonNullable.group({
restaurant: [this.restaurant?._id ?? '', Validators.required],
name: ['', Validators.required],
address: ['', Validators.required],
phone: ['', Validators.required],
// PASSING OUR CUSTOM VALIDATION FUNCTION TO THIS FORM CONTROL
items: [[] as Item[], minLengthArray(1)],
});
this.onChanges();
}
getChange(newItems: Item[]): void {
this.orderForm?.controls.items.patchValue(newItems);
}
onChanges(): void {
// WHEN THE ITEMS CHANGE WE WANT TO CALCULATE A NEW TOTAL
this.orderForm?.controls.items.valueChanges
.pipe(takeUntil(this.onDestroy$))
.subscribe((value) => this.calculateTotal(value));
}
calculateTotal(items: Item[]): void {
let total = 0.0;
if (items.length) {
for (const item of items) {
total += item.price;
}
this.orderTotal = Math.round(total * 100) / 100;
} else {
this.orderTotal = total;
}
}
onSubmit(): void {
this.orderProcessing = true;
// call createOrder here
}
startNewOrder(): void {
this.orderComplete = false;
this.completedOrder = undefined;
// CLEAR THE ORDER FORM
this.createOrderForm();
}
}
P3: How to verify your solution is correct
If you’ve implemented everything correctly, you should now be able to create an order from the UI and see a record of your completed order once it’s created.
✏️ Update the menu-items spec file src/app/order/order.component.spec.ts to be:
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { FormGroup, ReactiveFormsModule } from '@angular/forms';
import { ActivatedRoute } from '@angular/router';
import { RouterTestingModule } from '@angular/router/testing';
import { of } from 'rxjs';
import { RestaurantService } from '../restaurant/restaurant.service';
import { MenuItemsComponent } from './menu-items/menu-items.component';
import { OrderComponent, OrderForm } from './order.component';
import { CreateOrderDto, OrderService } from './order.service';
class MockRestaurantService {
getRestaurant(slug: string) {
return of({
name: 'Poutine Palace',
slug: 'poutine-palace',
images: {
thumbnail: 'node_modules/place-my-order-assets/images/4-thumbnail.jpg',
owner: 'node_modules/place-my-order-assets/images/3-owner.jpg',
banner: 'node_modules/place-my-order-assets/images/2-banner.jpg',
},
menu: {
lunch: [
{
name: 'Crab Pancakes with Sorrel Syrup',
price: 35.99,
},
{
name: 'Steamed Mussels',
price: 21.99,
},
{
name: 'Spinach Fennel Watercress Ravioli',
price: 35.99,
},
],
dinner: [
{
name: 'Gunthorp Chicken',
price: 21.99,
},
{
name: 'Herring in Lavender Dill Reduction',
price: 45.99,
},
{
name: 'Chicken with Tomato Carrot Chutney Sauce',
price: 45.99,
},
],
},
address: {
street: '230 W Kinzie Street',
city: 'Green Bay',
state: 'WI',
zip: '53205',
},
_id: '3ZOZyTY1LH26LnVw',
});
}
}
class MockOrderService {
createOrder(order: CreateOrderDto) {
return of({
address: null,
items: [
{ name: 'Onion fries', price: 15.99 },
{ name: 'Roasted Salmon', price: 23.99 },
],
name: 'Jennifer Hungry',
phone: null,
restaurant: 'uPkA2jiZi24tCvXh',
status: 'preparing',
_id: '0awcHyo3iD6CpvhX',
});
}
}
const MockActivatedRoute = {
snapshot: {
paramMap: {
get() {
return 'poutine-palace';
},
},
},
};
describe('OrderComponent', () => {
let component: OrderComponent;
let fixture: ComponentFixture<OrderComponent>;
let orderService: OrderService;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [OrderComponent, MenuItemsComponent],
imports: [ReactiveFormsModule, RouterTestingModule],
providers: [
{
provide: RestaurantService,
useClass: MockRestaurantService,
},
{
provide: ActivatedRoute,
useValue: MockActivatedRoute,
},
{
provide: OrderService,
useClass: MockOrderService,
},
],
schemas: [NO_ERRORS_SCHEMA],
}).compileComponents();
orderService = TestBed.inject(OrderService);
});
beforeEach(() => {
fixture = TestBed.createComponent(OrderComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should get a restaurant based on route slug', () => {
const mockRestaurant = {
name: 'Poutine Palace',
slug: 'poutine-palace',
images: {
thumbnail: 'node_modules/place-my-order-assets/images/4-thumbnail.jpg',
owner: 'node_modules/place-my-order-assets/images/3-owner.jpg',
banner: 'node_modules/place-my-order-assets/images/2-banner.jpg',
},
menu: {
lunch: [
{
name: 'Crab Pancakes with Sorrel Syrup',
price: 35.99,
},
{
name: 'Steamed Mussels',
price: 21.99,
},
{
name: 'Spinach Fennel Watercress Ravioli',
price: 35.99,
},
],
dinner: [
{
name: 'Gunthorp Chicken',
price: 21.99,
},
{
name: 'Herring in Lavender Dill Reduction',
price: 45.99,
},
{
name: 'Chicken with Tomato Carrot Chutney Sauce',
price: 45.99,
},
],
},
address: {
street: '230 W Kinzie Street',
city: 'Green Bay',
state: 'WI',
zip: '53205',
},
_id: '3ZOZyTY1LH26LnVw',
};
expect(fixture.componentInstance.restaurant).toEqual(mockRestaurant);
});
it('should have an orderForm formGroup', () => {
expect(
fixture.componentInstance.orderForm?.controls['restaurant']
).toBeTruthy();
expect(
fixture.componentInstance.orderForm?.controls['address']
).toBeTruthy();
expect(fixture.componentInstance.orderForm?.controls['phone']).toBeTruthy();
expect(fixture.componentInstance.orderForm?.controls['items']).toBeTruthy();
});
it('should have a validator on items formControl', () => {
const itemFormControl =
fixture.componentInstance.orderForm?.controls['items'];
expect(itemFormControl?.valid).toEqual(false);
});
it('should update items FormControl when setUpdatesItems is called', () => {
fixture.detectChanges();
const compiled = fixture.nativeElement as HTMLElement;
const childInput = compiled
.getElementsByTagName('pmo-menu-items')[0]
.getElementsByTagName('input')[0];
const formItems = fixture.componentInstance.orderForm?.get('items');
childInput.click();
fixture.detectChanges();
expect(formItems?.value).toEqual([
{ name: 'Crab Pancakes with Sorrel Syrup', price: 35.99 },
]);
});
it('should update the order total when the items FormControl value changes', () => {
fixture.detectChanges();
const compiled = fixture.nativeElement as HTMLElement;
const childInput1 = compiled
.getElementsByTagName('pmo-menu-items')[0]
.getElementsByTagName('input')[0];
const childInput2 = compiled
.getElementsByTagName('pmo-menu-items')[0]
.getElementsByTagName('input')[1];
childInput1.click();
childInput2.click();
fixture.detectChanges();
const orderText = compiled.querySelector('.submit h4');
expect(orderText?.textContent).toEqual('Total: $57.98');
});
it('should call the orderService createOrder on form submit with form values', () => {
const createOrderSpy = spyOn(orderService, 'createOrder').and.callThrough();
const expectedOrderValue: ReturnType<FormGroup<OrderForm>['getRawValue']> =
{
restaurant: '12345',
name: 'Jennifer Hungry',
address: '123 Main St',
phone: '555-555-5555',
items: [
{ name: 'Onion fries', price: 15.99 },
{ name: 'Roasted Salmon', price: 23.99 },
],
};
const compiled = fixture.nativeElement as HTMLElement;
fixture.componentInstance.orderForm?.setValue(expectedOrderValue);
fixture.detectChanges();
(
compiled.querySelector('button[type="submit"]') as HTMLButtonElement
).click();
expect(createOrderSpy).toHaveBeenCalledWith(expectedOrderValue);
});
it('should show completed order when order is complete', () => {
const expectedOrderValue: ReturnType<FormGroup<OrderForm>['getRawValue']> =
{
restaurant: '12345',
name: 'Jennifer Hungry',
address: '123 Main St',
phone: '555-555-5555',
items: [
{ name: 'Onion fries', price: 15.99 },
{ name: 'Roasted Salmon', price: 23.99 },
],
};
const compiled = fixture.nativeElement as HTMLElement;
fixture.componentInstance.orderForm?.setValue(expectedOrderValue);
fixture.detectChanges();
(
compiled.querySelector('button[type="submit"]') as HTMLButtonElement
).click();
fixture.detectChanges();
const displayedOrder = compiled.querySelector('h3');
expect(displayedOrder?.textContent).toEqual(
'Thanks for your order Jennifer Hungry!'
);
});
it('should clear the form values when create new order is clicked', () => {
const expectedOrderValue: ReturnType<FormGroup<OrderForm>['getRawValue']> =
{
restaurant: '12345',
name: 'Jennifer Hungry',
address: '123 Main St',
phone: '555-555-5555',
items: [
{ name: 'Onion fries', price: 15.99 },
{ name: 'Roasted Salmon', price: 23.99 },
],
};
const compiled = fixture.nativeElement as HTMLElement;
fixture.componentInstance.orderForm?.setValue(expectedOrderValue);
fixture.detectChanges();
(
compiled.querySelector('button[type="submit"]') as HTMLButtonElement
).click();
fixture.detectChanges();
(
compiled.querySelector('button:nth-child(1)') as HTMLButtonElement
).click();
const emptyform = {
restaurant: '3ZOZyTY1LH26LnVw',
name: '',
address: '',
phone: '',
items: [],
};
expect(fixture.componentInstance.orderForm?.value).toEqual(emptyform);
});
});
If you’ve implemented the solution correctly, the tests will pass when you run npm run test
!
P3: What you need to know
- How to import a service
- How to call a method on a service and get the result
- How to show/hide content using *ngIf
P3: Solution
Click to see the solution
✏️ Update src/app/order/order.component.tsimport { Component, OnInit, OnDestroy } from '@angular/core';
import {
AbstractControl,
FormBuilder,
FormControl,
FormGroup,
ValidationErrors,
ValidatorFn,
Validators,
} from '@angular/forms';
import { ActivatedRoute } from '@angular/router';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { Restaurant } from '../restaurant/restaurant';
import { RestaurantService } from '../restaurant/restaurant.service';
import { Order, OrderService } from './order.service';
export interface Item {
name: string;
price: number;
}
export interface OrderForm {
restaurant: FormControl<string>;
name: FormControl<string>;
address: FormControl<string>;
phone: FormControl<string>;
items: FormControl<Item[]>;
}
// CUSTOM VALIDATION FUNCTION TO ENSURE THAT THE ITEMS FORM VALUE CONTAINS AT LEAST ONE ITEM.
function minLengthArray(min: number): ValidatorFn {
return (c: AbstractControl): ValidationErrors | null => {
if (c.value.length >= min) {
return null;
}
return { minLengthArray: { valid: false } };
};
}
@Component({
selector: 'pmo-order',
templateUrl: './order.component.html',
styleUrl: './order.component.css',
})
export class OrderComponent implements OnInit, OnDestroy {
orderForm?: FormGroup<OrderForm>;
restaurant?: Restaurant;
isLoading = true;
orderTotal = 0.0;
completedOrder?: Order;
orderComplete = false;
orderProcessing = false;
private onDestroy$ = new Subject<void>();
constructor(
private route: ActivatedRoute,
private restaurantService: RestaurantService,
private orderService: OrderService,
private formBuilder: FormBuilder
) {}
ngOnInit(): void {
// GETTING THE RESTAURANT FROM THE ROUTE SLUG
const slug = this.route.snapshot.paramMap.get('slug');
if (slug) {
this.restaurantService
.getRestaurant(slug)
.pipe(takeUntil(this.onDestroy$))
.subscribe((data: Restaurant) => {
this.restaurant = data;
this.isLoading = false;
this.createOrderForm();
});
}
}
ngOnDestroy(): void {
this.onDestroy$.next();
this.onDestroy$.complete();
}
createOrderForm(): void {
this.orderForm = this.formBuilder.nonNullable.group({
restaurant: [this.restaurant?._id ?? '', Validators.required],
name: ['', Validators.required],
address: ['', Validators.required],
phone: ['', Validators.required],
// PASSING OUR CUSTOM VALIDATION FUNCTION TO THIS FORM CONTROL
items: [[] as Item[], minLengthArray(1)],
});
this.onChanges();
}
getChange(newItems: Item[]): void {
this.orderForm?.controls.items.patchValue(newItems);
}
onChanges(): void {
// WHEN THE ITEMS CHANGE WE WANT TO CALCULATE A NEW TOTAL
this.orderForm?.controls.items.valueChanges
.pipe(takeUntil(this.onDestroy$))
.subscribe((value) => this.calculateTotal(value));
}
calculateTotal(items: Item[]): void {
let total = 0.0;
if (items.length) {
for (const item of items) {
total += item.price;
}
this.orderTotal = Math.round(total * 100) / 100;
} else {
this.orderTotal = total;
}
}
onSubmit(): void {
if (!this.orderForm?.valid) {
return;
}
this.orderProcessing = true;
this.orderService
.createOrder(this.orderForm.getRawValue())
.pipe(takeUntil(this.onDestroy$))
.subscribe((res: Order) => {
this.completedOrder = res;
this.orderComplete = true;
this.orderProcessing = false;
});
}
startNewOrder(): void {
this.orderComplete = false;
this.completedOrder = undefined;
this.orderTotal = 0.0;
// CLEAR THE ORDER FORM
this.createOrderForm();
}
}