From fedc10caa10d7d4d10c54699f776ec0573b82423 Mon Sep 17 00:00:00 2001 From: fabioemoutinho Date: Mon, 8 Jan 2024 13:34:26 -0300 Subject: [PATCH] update order-service --- .../child-component/order-2.component.ts | 9 +-- .../order.component-solution.ts | 9 +-- .../order.component-starter.ts | 7 +- .../16-order-service/order-2.service.ts | 6 +- src/angular/16-order-service/order-service.md | 8 +- .../order.component-solution.ts | 46 +++++++---- .../16-order-service/order.component.html | 26 +++---- .../16-order-service/order.component.spec.ts | 77 ++++++++++--------- .../16-order-service/order.component.ts | 40 ++++++---- .../order.service-interfaces.ts | 2 +- 10 files changed, 128 insertions(+), 102 deletions(-) diff --git a/src/angular/14-building-order-form/child-component/order-2.component.ts b/src/angular/14-building-order-form/child-component/order-2.component.ts index a41418883..b5096c476 100644 --- a/src/angular/14-building-order-form/child-component/order-2.component.ts +++ b/src/angular/14-building-order-form/child-component/order-2.component.ts @@ -20,8 +20,8 @@ export interface Item { price: number; } -interface OrderForm { - restaurant: FormControl; +export interface OrderForm { + restaurant: FormControl; name: FormControl; address: FormControl; phone: FormControl; @@ -47,7 +47,6 @@ export class OrderComponent implements OnInit, OnDestroy { orderForm?: FormGroup; restaurant?: Restaurant; isLoading = true; - items?: FormArray; orderTotal = 0.0; completedOrder: any; orderComplete = false; @@ -83,7 +82,7 @@ export class OrderComponent implements OnInit, OnDestroy { createOrderForm(): void { this.orderForm = this.formBuilder.nonNullable.group({ - restaurant: [this.restaurant?._id], + restaurant: [this.restaurant?._id ?? '', Validators.required], name: ['', Validators.required], address: ['', Validators.required], phone: ['', Validators.required], @@ -118,7 +117,7 @@ export class OrderComponent implements OnInit, OnDestroy { startNewOrder(): void { this.orderComplete = false; - this.completedOrder = this.orderForm?.value; + this.completedOrder = undefined; // CLEAR THE ORDER FORM this.createOrderForm(); } diff --git a/src/angular/14-building-order-form/order.component-solution.ts b/src/angular/14-building-order-form/order.component-solution.ts index e9a30ba20..de44c101d 100644 --- a/src/angular/14-building-order-form/order.component-solution.ts +++ b/src/angular/14-building-order-form/order.component-solution.ts @@ -20,8 +20,8 @@ export interface Item { price: number; } -interface OrderForm { - restaurant: FormControl; +export interface OrderForm { + restaurant: FormControl; name: FormControl; address: FormControl; phone: FormControl; @@ -47,7 +47,6 @@ export class OrderComponent implements OnInit, OnDestroy { orderForm?: FormGroup; restaurant?: Restaurant; isLoading = true; - items?: FormArray; orderTotal = 0.0; completedOrder: any; orderComplete = false; @@ -83,7 +82,7 @@ export class OrderComponent implements OnInit, OnDestroy { createOrderForm(): void { this.orderForm = this.formBuilder.nonNullable.group({ - restaurant: [this.restaurant?._id], + restaurant: [this.restaurant?._id ?? '', Validators.required], name: ['', Validators.required], address: ['', Validators.required], phone: ['', Validators.required], @@ -114,7 +113,7 @@ export class OrderComponent implements OnInit, OnDestroy { startNewOrder(): void { this.orderComplete = false; - this.completedOrder = this.orderForm?.value; + this.completedOrder = undefined; // CLEAR THE ORDER FORM this.createOrderForm(); } diff --git a/src/angular/14-building-order-form/order.component-starter.ts b/src/angular/14-building-order-form/order.component-starter.ts index 9c91a6650..4a6ad28ed 100644 --- a/src/angular/14-building-order-form/order.component-starter.ts +++ b/src/angular/14-building-order-form/order.component-starter.ts @@ -19,8 +19,8 @@ export interface Item { price: number; } -interface OrderForm { - restaurant: FormControl; +export interface OrderForm { + restaurant: FormControl; name: FormControl; address: FormControl; phone: FormControl; @@ -46,7 +46,6 @@ export class OrderComponent implements OnInit, OnDestroy { orderForm?: FormGroup; restaurant?: Restaurant; isLoading = true; - items?: FormArray; orderTotal = 0.0; completedOrder: any; orderComplete = false; @@ -80,7 +79,7 @@ export class OrderComponent implements OnInit, OnDestroy { startNewOrder(): void { this.orderComplete = false; - this.completedOrder = this.orderForm?.value; + this.completedOrder = undefined; // CLEAR THE ORDER FORM this.createOrderForm(); } diff --git a/src/angular/16-order-service/order-2.service.ts b/src/angular/16-order-service/order-2.service.ts index db068c6de..4a45d9011 100644 --- a/src/angular/16-order-service/order-2.service.ts +++ b/src/angular/16-order-service/order-2.service.ts @@ -1,14 +1,14 @@ import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { Observable } from 'rxjs'; -import { environment } from 'src/environments/environment'; +import { environment } from '../../environments/environment'; export interface Item { name: string; price: number; } -export interface OrderForm { +export interface CreateOrderDto { restaurant: string; name: string; address: string; @@ -38,7 +38,7 @@ export class OrderService { ); } - createOrder(order: OrderForm): Observable { + createOrder(order: CreateOrderDto): Observable { const orderData = { ...order, status: 'new', diff --git a/src/angular/16-order-service/order-service.md b/src/angular/16-order-service/order-service.md index a7141ac61..2706435ae 100644 --- a/src/angular/16-order-service/order-service.md +++ b/src/angular/16-order-service/order-service.md @@ -20,10 +20,10 @@ We need to create a new service to handle creating and updating orders. We'll ne ## P1: Technical Requirements -Create a new service `order` in the order directory, and write and export `OrderForm`, `Order` and `Item` interfaces representing these objects in the new service: +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: ```typescript -const orderForm = { +const createOrderDto = { restaurant: '12345', name: 'Jennifer', address: '123 Main st', @@ -96,7 +96,7 @@ ng test - The method signatures for the methods you'll be adding to `OrderService`: - `getOrders(): Observable<{data: Order[]}>` should make a `GET` request - - `createOrder(orderForm: OrderForm): Observable` should make a `POST` request + - `createOrder(orderForm: CreateOrderDto): Observable` should make a `POST` request - `updateOrder(order: Order, status: string): Observable` should make a `PUT` request to `/orders/` - `deleteOrder(orderId: string): Observable` should make a `DELETE` request to `/orders/` - You will need to make sure `HttpClient` is imported and @@ -133,6 +133,8 @@ How we will solve this: and set back to `false` when `startNewOrder()` is called. 5. We will save the completed order in `completedOrder`. +> A FormGroup's `value` property is wrapped in `Partial` 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's `getRawValue()` method to access its value with the full type. + ## P3: Setup Before starting: diff --git a/src/angular/16-order-service/order.component-solution.ts b/src/angular/16-order-service/order.component-solution.ts index b53e599fe..2824a3154 100644 --- a/src/angular/16-order-service/order.component-solution.ts +++ b/src/angular/16-order-service/order.component-solution.ts @@ -3,6 +3,7 @@ import { AbstractControl, FormArray, FormBuilder, + FormControl, FormGroup, ValidationErrors, ValidatorFn, @@ -15,6 +16,19 @@ 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; + name: FormControl; + address: FormControl; + phone: FormControl; + items: FormControl; +} + // 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 => { @@ -31,10 +45,9 @@ function minLengthArray(min: number): ValidatorFn { styleUrls: ['./order.component.less'], }) export class OrderComponent implements OnInit, OnDestroy { - orderForm?: FormGroup; + orderForm?: FormGroup; restaurant?: Restaurant; isLoading = true; - items?: FormArray; orderTotal = 0.0; completedOrder?: Order; orderComplete = false; @@ -70,26 +83,25 @@ export class OrderComponent implements OnInit, OnDestroy { } createOrderForm(): void { - this.orderForm = this.formBuilder.group({ - restaurant: [this.restaurant?._id], - name: [null, Validators.required], - address: [null, Validators.required], - phone: [null, Validators.required], + 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: [[], minLengthArray(1)], + items: [[] as Item[], minLengthArray(1)], }); this.onChanges(); } - getChange(newItems: []): void { - this.orderForm?.get('items')?.patchValue(newItems); + 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 - ?.get('items') - ?.valueChanges.pipe(takeUntil(this.onDestroy$)) + this.orderForm?.controls.items.valueChanges + .pipe(takeUntil(this.onDestroy$)) .subscribe((val) => { let total = 0.0; if (val.length) { @@ -104,9 +116,13 @@ export class OrderComponent implements OnInit, OnDestroy { } onSubmit(): void { + if (!this.orderForm?.valid) { + return; + } + this.orderProcessing = true; this.orderService - .createOrder(this.orderForm?.value) + .createOrder(this.orderForm.getRawValue()) .pipe(takeUntil(this.onDestroy$)) .subscribe((res: Order) => { this.completedOrder = res; @@ -117,7 +133,7 @@ export class OrderComponent implements OnInit, OnDestroy { startNewOrder(): void { this.orderComplete = false; - this.completedOrder = this.orderForm?.value; + this.completedOrder = undefined; this.orderTotal = 0.0; // CLEAR THE ORDER FORM this.createOrderForm(); diff --git a/src/angular/16-order-service/order.component.html b/src/angular/16-order-service/order.component.html index d12998617..043f5051d 100644 --- a/src/angular/16-order-service/order.component.html +++ b/src/angular/16-order-service/order.component.html @@ -36,20 +36,24 @@

Items ordered:

Order here

-
+ - +
- +
@@ -57,20 +61,12 @@

Order here

- +

Please enter your name.

- +

Please enter your address.

diff --git a/src/angular/16-order-service/order.component.spec.ts b/src/angular/16-order-service/order.component.spec.ts index 2ce2da400..28d7fc11b 100644 --- a/src/angular/16-order-service/order.component.spec.ts +++ b/src/angular/16-order-service/order.component.spec.ts @@ -1,13 +1,13 @@ import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { ReactiveFormsModule } from '@angular/forms'; +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 } from './order.component'; -import { OrderForm, OrderService } from './order.service'; +import { OrderComponent, OrderForm } from './order.component'; +import { CreateOrderDto, OrderService } from './order.service'; class MockRestaurantService { getRestaurant(slug: string) { @@ -61,7 +61,7 @@ class MockRestaurantService { } class MockOrderService { - createOrder(order: OrderForm) { + createOrder(order: CreateOrderDto) { return of({ address: null, items: [ @@ -224,16 +224,17 @@ describe('OrderComponent', () => { it('should call the orderService createOrder on form submit with form values', () => { const createOrderSpy = spyOn(orderService, 'createOrder').and.callThrough(); - const expectedOrderValue: OrderForm = { - 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 expectedOrderValue: ReturnType['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(); @@ -244,16 +245,17 @@ describe('OrderComponent', () => { }); it('should show completed order when order is complete', () => { - const expectedOrderValue: OrderForm = { - 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 expectedOrderValue: ReturnType['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(); @@ -268,16 +270,17 @@ describe('OrderComponent', () => { }); it('should clear the form values when create new order is clicked', () => { - const expectedOrderValue: OrderForm = { - 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 expectedOrderValue: ReturnType['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(); @@ -290,9 +293,9 @@ describe('OrderComponent', () => { ).click(); const emptyform = { restaurant: '3ZOZyTY1LH26LnVw', - name: null, - address: null, - phone: null, + name: '', + address: '', + phone: '', items: [], }; expect(fixture.componentInstance.orderForm?.value).toEqual(emptyform); diff --git a/src/angular/16-order-service/order.component.ts b/src/angular/16-order-service/order.component.ts index 04d690511..3fc4171cd 100644 --- a/src/angular/16-order-service/order.component.ts +++ b/src/angular/16-order-service/order.component.ts @@ -3,6 +3,7 @@ import { AbstractControl, FormArray, FormBuilder, + FormControl, FormGroup, ValidationErrors, ValidatorFn, @@ -14,6 +15,19 @@ 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; + name: FormControl; + address: FormControl; + phone: FormControl; + items: FormControl; +} + // 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 => { @@ -30,10 +44,9 @@ function minLengthArray(min: number): ValidatorFn { styleUrls: ['./order.component.less'], }) export class OrderComponent implements OnInit, OnDestroy { - orderForm?: FormGroup; + orderForm?: FormGroup; restaurant?: Restaurant; isLoading = true; - items?: FormArray; orderTotal = 0.0; completedOrder: any; orderComplete = false; @@ -68,26 +81,25 @@ export class OrderComponent implements OnInit, OnDestroy { } createOrderForm(): void { - this.orderForm = this.formBuilder.group({ - restaurant: [this.restaurant?._id], - name: [null, Validators.required], - address: [null, Validators.required], - phone: [null, Validators.required], + 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: [[], minLengthArray(1)], + items: [[] as Item[], minLengthArray(1)], }); this.onChanges(); } - getChange(newItems: []): void { - this.orderForm?.get('items')?.patchValue(newItems); + 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 - ?.get('items') - ?.valueChanges.pipe(takeUntil(this.onDestroy$)) + this.orderForm?.controls.items.valueChanges + .pipe(takeUntil(this.onDestroy$)) .subscribe((val) => { let total = 0.0; if (val.length) { @@ -108,7 +120,7 @@ export class OrderComponent implements OnInit, OnDestroy { startNewOrder(): void { this.orderComplete = false; - this.completedOrder = this.orderForm?.value; + this.completedOrder = undefined; // CLEAR THE ORDER FORM this.createOrderForm(); } diff --git a/src/angular/16-order-service/order.service-interfaces.ts b/src/angular/16-order-service/order.service-interfaces.ts index 1cb4d70cf..3c5789422 100644 --- a/src/angular/16-order-service/order.service-interfaces.ts +++ b/src/angular/16-order-service/order.service-interfaces.ts @@ -5,7 +5,7 @@ export interface Item { price: number; } -export interface OrderForm { +export interface CreateOrderDto { restaurant: string; name: string; address: string;