Order History Component page
Writing the Order History Component
Overview
In this part, we will:
- Create a new order history component
 - Get all orders from our order service
 - Create a child component to handle different states of orders
 - Create ways to update and delete orders in the view
 - Add order history link to our main navigation
 
Problem 1: Generate a HistoryComponent and create a route for it
We want to create a component that will show the app’s order history.
P1: Technical requirements
- Generate a 
HistoryComponentinsrc/app/order/history/history.component.ts - Show 
HistoryComponentwhen we navigate to/order-history 
P1: How to verify your solution is correct
If you’ve implemented the solution correctly you should be able to navigate to http://localhost:4200/order-history and see 'history works!'.
✏️ Update the spec file src/app/app.component.spec.ts to be:
import { Location } from '@angular/common';
import { HttpClientModule } from '@angular/common/http';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import {
  ComponentFixture,
  fakeAsync,
  flush,
  TestBed,
  tick,
} from '@angular/core/testing';
import { ReactiveFormsModule } from '@angular/forms';
import { Router } from '@angular/router';
import { of } from 'rxjs';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { HomeComponent } from './home/home.component';
import { ImageUrlPipe } from './image-url.pipe';
import { HistoryComponent } from './order/history/history.component';
import { OrderComponent } from './order/order.component';
import { DetailComponent } from './restaurant/detail/detail.component';
import { RestaurantComponent } from './restaurant/restaurant.component';
import { RestaurantService } from './restaurant/restaurant.service';
class MockRestaurantService {
  getRestaurants() {
    return of({
      data: [
        {
          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',
        },
        {
          name: 'Cheese Curd City',
          slug: 'cheese-curd-city',
          images: {
            thumbnail:
              'node_modules/place-my-order-assets/images/2-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: 'Ricotta Gnocchi',
                price: 15.99,
              },
              {
                name: 'Gunthorp Chicken',
                price: 21.99,
              },
              {
                name: 'Garlic Fries',
                price: 15.99,
              },
            ],
            dinner: [
              {
                name: 'Herring in Lavender Dill Reduction',
                price: 45.99,
              },
              {
                name: 'Truffle Noodles',
                price: 14.99,
              },
              {
                name: 'Charred Octopus',
                price: 25.99,
              },
            ],
          },
          address: {
            street: '2451 W Washburne Ave',
            city: 'Green Bay',
            state: 'WI',
            zip: '53295',
          },
          _id: 'Ar0qBJHxM3ecOhcr',
        },
      ],
    });
  }
  getStates() {
    return of({
      data: [
        { short: 'MO', name: 'Missouri' },
        { short: 'CA  ', name: 'California' },
        { short: 'MI', name: 'Michigan' },
      ],
    });
  }
  getCities(state: string) {
    return of({
      data: [
        { name: 'Sacramento', state: 'CA' },
        { name: 'Oakland', state: 'CA' },
      ],
    });
  }
  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',
    });
  }
}
describe('AppComponent', () => {
  let fixture: ComponentFixture<AppComponent>;
  let location: Location;
  let router: Router;
  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [AppRoutingModule, HttpClientModule, ReactiveFormsModule],
      declarations: [
        AppComponent,
        HomeComponent,
        RestaurantComponent,
        ImageUrlPipe,
        DetailComponent,
        OrderComponent,
        HistoryComponent,
      ],
      providers: [
        { provide: RestaurantService, useClass: MockRestaurantService },
      ],
      schemas: [NO_ERRORS_SCHEMA],
    })
      .overrideComponent(RestaurantComponent, {
        set: { template: '<p>I am a fake restaurant component</p>' },
      })
      .compileComponents();
    fixture = TestBed.createComponent(AppComponent);
    location = TestBed.inject(Location);
    router = TestBed.inject(Router);
  });
  it('should create the app', () => {
    const app = fixture.componentInstance;
    expect(app).toBeTruthy();
  });
  it(`should have as title 'place-my-order'`, () => {
    const app = fixture.componentInstance;
    expect(app.title).toEqual('place-my-order');
  });
  it('should render title in a h1 tag', () => {
    fixture.detectChanges();
    const compiled = fixture.nativeElement as HTMLElement;
    expect(compiled.querySelector('h1')?.textContent).toContain(
      'place-my-order.com'
    );
  });
  it('should render the HomeComponent with router navigates to "/" path', fakeAsync(() => {
    fixture.detectChanges();
    const compiled = fixture.nativeElement as HTMLElement;
    router.navigate(['']).then(() => {
      expect(location.path()).toBe('');
      expect(compiled.querySelector('pmo-home')).not.toBe(null);
    });
  }));
  it('should render the RestaurantsComponent with router navigates to "/restaurants" path', fakeAsync(() => {
    fixture.detectChanges();
    const compiled = fixture.nativeElement as HTMLElement;
    router.navigate(['restaurants']).then(() => {
      expect(location.path()).toBe('/restaurants');
      expect(compiled.querySelector('pmo-restaurant')).not.toBe(null);
    });
  }));
  it('should render the DetailComponent with router navigates to "/restaurants/slug" path', fakeAsync(() => {
    fixture.detectChanges();
    const compiled = fixture.nativeElement as HTMLElement;
    router.navigate(['restaurants/crab-shack']).then(() => {
      expect(location.path()).toBe('/restaurants/crab-shack');
      expect(compiled.querySelector('pmo-detail')).not.toBe(null);
    });
  }));
  it('should render the OrderComponent with router navigates to "/restaurants/slug/order" path', fakeAsync(() => {
    fixture.detectChanges();
    const compiled = fixture.nativeElement as HTMLElement;
    router.navigate(['restaurants/crab-shack/order']).then(() => {
      expect(location.path()).toBe('/restaurants/crab-shack/order');
      expect(compiled.querySelector('pmo-order')).not.toBe(null);
    });
  }));
  it('should render the HistoryComponent with router navigates to "/order-history" path', fakeAsync(() => {
    fixture.detectChanges();
    const compiled = fixture.nativeElement as HTMLElement;
    router.navigate(['order-history']).then(() => {
      expect(location.path()).toBe('/order-history');
      expect(compiled.querySelector('pmo-history')).not.toBe(null);
    });
  }));
  it('should have the home navigation link href set to "/"', () => {
    fixture.detectChanges();
    const compiled = fixture.nativeElement as HTMLElement;
    const homeLink = compiled.querySelector('li a');
    const href = homeLink?.getAttribute('href');
    expect(href).toEqual('/');
  });
  it('should have the restaurants navigation link href set to "/restaurants"', () => {
    fixture.detectChanges();
    const compiled = fixture.nativeElement as HTMLElement;
    const restaurantsLink = compiled.querySelector('li:nth-child(2) a');
    const href = restaurantsLink?.getAttribute('href');
    expect(href).toEqual('/restaurants');
  });
  it('should make the home navigation link class active when the router navigates to "/" path', fakeAsync(() => {
    const compiled = fixture.nativeElement as HTMLElement;
    router.navigate(['']);
    fixture.detectChanges();
    tick();
    fixture.detectChanges();
    const homeLinkLi = compiled.querySelector('li');
    expect(homeLinkLi?.classList).toContain('active');
    expect(compiled.querySelectorAll('.active').length).toBe(1);
    flush();
  }));
  it('should make the restaurants navigation link class active when the router navigates to "/restaurants" path', fakeAsync(() => {
    const compiled = fixture.nativeElement as HTMLElement;
    router.navigate(['restaurants']);
    fixture.detectChanges();
    tick();
    fixture.detectChanges();
    expect(location.path()).toBe('/restaurants');
    const restaurantsLinkLi = compiled.querySelector('li:nth-child(2)');
    expect(restaurantsLinkLi?.classList).toContain('active');
    expect(compiled.querySelectorAll('.active').length).toBe(1);
    flush();
  }));
});
 
If you’ve implemented the solution correctly, the tests will pass when you run npm run test!
P1: What you need to know
You got this already, but just in case, here’s some hints:
- How to generate a component
ng g component PATH - Update 
app-routing.module.tsto import the component you want and create a path to it. 
P1: solution
Click to see the solution
✏️ First, run:
ng g component order/history
Then route to the component:
✏️ Update src/app/app-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { HomeComponent } from './home/home.component';
import { HistoryComponent } from './order/history/history.component';
import { OrderComponent } from './order/order.component';
import { DetailComponent } from './restaurant/detail/detail.component';
import { RestaurantComponent } from './restaurant/restaurant.component';
const routes: Routes = [
  {
    path: '',
    component: HomeComponent,
  },
  {
    path: 'restaurants',
    component: RestaurantComponent,
  },
  {
    path: 'restaurants/:slug',
    component: DetailComponent,
  },
  {
    path: 'restaurants/:slug/order',
    component: OrderComponent,
  },
  {
    path: 'order-history',
    component: HistoryComponent,
  },
];
@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule],
})
export class AppRoutingModule {}
Problem 2: Add HistoryComponent to navigation
We want a user to be able to navigate to the HistoryComponent via a link in the main navigation.
P2: Technical requirements
- Add a Order History link to the navigation bar at the top of the page.
 - Add the class name 
activeto the link if we are on theOrderHistorypage. 
P2: What you need to know
You’ve seen this before. Check out how the Home link works in
app.component.html.
P2: How to verify your solution is correct
If you’ve implemented the solution correctly you should now be able to navigate to http://localhost:4200/order-history and see a list of all orders.
P2: Solution
Click to see the solution
✏️ Update src/app/app.component.html
<header>
  <nav>
    <h1>place-my-order.com</h1>
    <ul>
      <li routerLinkActive="active" [routerLinkActiveOptions]="{ exact: true }">
        <a routerLink="/">Home</a>
      </li>
      <li [ngClass]="{ active: rla.isActive }">
        <a routerLink="/restaurants" routerLinkActive #rla="routerLinkActive">
          Restaurants
        </a>
      </li>
      <li routerLinkActive="active" [routerLinkActiveOptions]="{ exact: true }">
        <a routerLink="/order-history">Order History</a>
      </li>
    </ul>
  </nav>
</header>
<router-outlet />
 
Problem 3: List All Orders
We want to be able to see a list of all created orders and their varying statuses of "new", "preparing", "delivery", and "delivered".
P3: Technical requirements
- List all orders in the 
HistoryComponent. - Make sure the 
<div>for each order has a class name of 'order' and a class name that is theorder.statusvalue. Make sure you’ve created a new order. 
P3: Setup
1. ✏️ Copy the following into src/app/order/history/history.component.ts. You will fill out its
getOrders method. The getters newOrders, preparingOrders, deliveryOrders, and deliveredOrders will be used later.
import { Component, OnDestroy, OnInit } from '@angular/core';
import { Order } from '../order.service';
interface Data<T> {
  value: T[];
  isPending: boolean;
}
@Component({
  selector: 'pmo-history',
  templateUrl: './history.component.html',
  styleUrl: './history.component.css',
})
export class HistoryComponent implements OnInit, OnDestroy {
  constructor() {}
  ngOnInit(): void {
    this.getOrders();
  }
  ngOnDestroy(): void {
    // unsubscribe here
  }
  getOrders(): void {
    // get orders here
  }
  get newOrders(): Order[] {
    const orders = this.orders.value.filter((order) => {
      return order.status === 'new';
    });
    return orders;
  }
  get preparingOrders(): Order[] {
    const orders = this.orders.value.filter((order) => {
      return order.status === 'preparing';
    });
    return orders;
  }
  get deliveryOrders(): Order[] {
    const orders = this.orders.value.filter((order) => {
      return order.status === 'delivery';
    });
    return orders;
  }
  get deliveredOrders(): Order[] {
    const orders = this.orders.value.filter((order) => {
      return order.status === 'delivered';
    });
    return orders;
  }
}
2. ✏️ Copy the following into src/app/order/history/history.component.html. You will need to
iterate through orders and add the right class names to the outer <div> for each order.
<div class="order-history">
  <div class="order header">
    <address>Name / Address / Phone</address>
    <div class="items">Order</div>
    <div class="total">Total</div>
    <div class="actions">Action</div>
  </div>
  <ng-container LOOP_HERE>
    <div class="ADD RIGHT CLASS NAMES">
      <address>
        {{ order.name }} <br />{{ order.address }} <br />{{ order.phone }}
      </address>
      <div class="items">
        <ul>
          <li *ngFor="let item of order.items">{{ item.name }}</li>
        </ul>
      </div>
      <div class="total">$order total?</div>
      <div class="actions">
        <span class="badge">Status title?</span>
        <p class="action" *ngIf="false">
          Mark as:
          <button class="btn-link">next step</button>
        </p>
        <p class="action">
          <button class="btn-link">Delete</button>
        </p>
      </div>
    </div>
  </ng-container>
</div>
P3: How to verify your solution is correct
✏️ Update the menu-items spec file src/app/order/history/history.component.spec.ts to be:
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { Observable, of } from 'rxjs';
import { Order, OrderService } from '../order.service';
import { HistoryComponent } from './history.component';
class MockOrderService {
  getOrders(): Observable<{ data: Order[] }> {
    return of({
      data: [
        {
          address: '',
          items: [
            { name: 'Onion fries', price: 15.99 },
            { name: 'Roasted Salmon', price: 23.99 },
          ],
          name: 'Client 1',
          phone: '',
          restaurant: 'uPkA2jiZi24tCvXh',
          status: 'new',
          _id: '0awcHyo3iD6CpvhX',
        },
        {
          address: '',
          items: [
            { name: 'Steak Tacos', price: 15.99 },
            { name: 'Guacamole', price: 3.99 },
          ],
          name: 'Client 2',
          phone: '',
          restaurant: 'uPkA2jiZi24tCvXh',
          status: 'preparing',
          _id: '0awcHyo3iD6CpvhX',
        },
        {
          address: '',
          items: [
            { name: 'Mac & Cheese', price: 15.99 },
            { name: 'Grilled chicken', price: 23.99 },
          ],
          name: 'Client 3',
          phone: '',
          restaurant: 'uPkA2jiZi24tCvXh',
          status: 'delivery',
          _id: '0awcHyo8iD7XjahX',
        },
        {
          address: '',
          items: [
            { name: 'Eggrolls', price: 5.99 },
            { name: 'Fried Rice', price: 18.99 },
          ],
          name: 'Client 4',
          phone: '',
          restaurant: 'uPkA2jiZi24tCvXh',
          status: 'delivered',
          _id: '1awcJyo3iD6CpvhZ',
        },
      ],
    });
  }
}
describe('HistoryComponent', () => {
  let component: HistoryComponent;
  let fixture: ComponentFixture<HistoryComponent>;
  beforeEach(async () => {
    await TestBed.configureTestingModule({
      declarations: [HistoryComponent],
      providers: [
        {
          provide: OrderService,
          useClass: MockOrderService,
        },
      ],
    }).compileComponents();
  });
  beforeEach(() => {
    fixture = TestBed.createComponent(HistoryComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });
  it('should create', () => {
    expect(component).toBeTruthy();
  });
  it('should set response from getOrders service to orders member', () => {
    const expectedOrders: Order[] = [
      {
        address: '',
        items: [
          { name: 'Onion fries', price: 15.99 },
          { name: 'Roasted Salmon', price: 23.99 },
        ],
        name: 'Client 1',
        phone: '',
        restaurant: 'uPkA2jiZi24tCvXh',
        status: 'new',
        _id: '0awcHyo3iD6CpvhX',
      },
      {
        address: '',
        items: [
          { name: 'Steak Tacos', price: 15.99 },
          { name: 'Guacamole', price: 3.99 },
        ],
        name: 'Client 2',
        phone: '',
        restaurant: 'uPkA2jiZi24tCvXh',
        status: 'preparing',
        _id: '0awcHyo3iD6CpvhX',
      },
      {
        address: '',
        items: [
          { name: 'Mac & Cheese', price: 15.99 },
          { name: 'Grilled chicken', price: 23.99 },
        ],
        name: 'Client 3',
        phone: '',
        restaurant: 'uPkA2jiZi24tCvXh',
        status: 'delivery',
        _id: '0awcHyo8iD7XjahX',
      },
      {
        address: '',
        items: [
          { name: 'Eggrolls', price: 5.99 },
          { name: 'Fried Rice', price: 18.99 },
        ],
        name: 'Client 4',
        phone: '',
        restaurant: 'uPkA2jiZi24tCvXh',
        status: 'delivered',
        _id: '1awcJyo3iD6CpvhZ',
      },
    ];
    const orders = fixture.componentInstance.orders;
    expect(orders.value).toEqual(expectedOrders);
  });
  it('should display orders in UI', () => {
    const compiled = fixture.nativeElement as HTMLElement;
    const orderDivs = compiled.querySelectorAll(
      '.order:not(.header):not(.empty)'
    );
    expect(orderDivs.length).toEqual(4);
  });
  it('should display orders with appropriate classes', () => {
    const compiled = fixture.nativeElement as HTMLElement;
    const newOrder = compiled.getElementsByClassName('new');
    const preparingOrder = compiled.getElementsByClassName('preparing');
    const deliveryOrder = compiled.getElementsByClassName('delivery');
    const deliveredOrder = compiled.getElementsByClassName('delivered');
    expect(newOrder.length).toEqual(1);
    expect(preparingOrder.length).toEqual(1);
    expect(deliveryOrder.length).toEqual(1);
    expect(deliveredOrder.length).toEqual(1);
  });
});
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 and get data out of it. Hint: Import it and create a property in the constructor.
 - How to loop through values in HTML. Hint: 
*ngFor. 
For this step, you’ll need to know how to add multiple class names. You can do this with
[ngClass] and setting it to an array like:
<div [ngClass]="['first','second']"></div>
P3: Solution
Click to see the solution
✏️ Update src/app/order/history.component.ts
import { Component, OnDestroy, OnInit } from '@angular/core';
import { Subject, takeUntil } from 'rxjs';
import { ResponseData } from '../../restaurant/restaurant.service';
import { Order, OrderService } from '../order.service';
interface Data<T> {
  value: T[];
  isPending: boolean;
}
@Component({
  selector: 'pmo-history',
  templateUrl: './history.component.html',
  styleUrl: './history.component.css',
})
export class HistoryComponent implements OnInit, OnDestroy {
  orders: Data<Order> = { value: [], isPending: true };
  private onDestroy$ = new Subject<void>();
  constructor(private orderService: OrderService) {}
  ngOnInit(): void {
    this.getOrders();
  }
  ngOnDestroy(): void {
    this.onDestroy$.next();
    this.onDestroy$.complete();
  }
  getOrders(): void {
    this.orderService
      .getOrders()
      .pipe(takeUntil(this.onDestroy$))
      .subscribe((res: ResponseData<Order>) => {
        this.orders.value = res.data;
      });
  }
  get newOrders(): Order[] {
    const orders = this.orders.value.filter((order) => {
      return order.status === 'new';
    });
    return orders;
  }
  get preparingOrders(): Order[] {
    const orders = this.orders.value.filter((order) => {
      return order.status === 'preparing';
    });
    return orders;
  }
  get deliveryOrders(): Order[] {
    const orders = this.orders.value.filter((order) => {
      return order.status === 'delivery';
    });
    return orders;
  }
  get deliveredOrders(): Order[] {
    const orders = this.orders.value.filter((order) => {
      return order.status === 'delivered';
    });
    return orders;
  }
}
 
✏️ Update src/app/order/history.component.html
<div class="order-history">
  <div class="order header">
    <address>Name / Address / Phone</address>
    <div class="items">Order</div>
    <div class="total">Total</div>
    <div class="actions">Action</div>
  </div>
  <ng-container *ngFor="let order of orders.value">
    <div [ngClass]="['order', order.status]">
      <address>
        {{ order.name }} <br />{{ order.address }} <br />{{ order.phone }}
      </address>
      <div class="items">
        <ul>
          <li *ngFor="let item of order.items">{{ item.name }}</li>
        </ul>
      </div>
      <div class="total">$order total?</div>
      <div class="actions">
        <span class="badge">Status title?</span>
        <p class="action" *ngIf="false">
          Mark as:
          <button class="btn-link">next step</button>
        </p>
        <p class="action">
          <button class="btn-link">Delete</button>
        </p>
      </div>
    </div>
  </ng-container>
</div>
 
Problem 4: Creating a Child Component to Handle Order States
We want to create a child component that will take a list of orders by status and display them, as well as actions a user can perform on an order.
P4: Technical requirements
- Group the orders by status.
 - Allow the user to change the status of an order.
 - Allow the user to delete an order.
 
NOTE!! To see that an order has changed, you will have to refresh the page!
To solve this problem:
- Create a 
ListComponentin src/app/order/list/list.component.ts that will take a list of orders and other values like:<pmo-list [orders]="newOrders" listTitle="New Orders" status="new" statusTitle="New Order!" action="preparing" actionTitle="Preparing" emptyMessage="No new orders" > </pmo-list> ListComponentwill take the following values:orders- an array of orders based on status propertylistTitle- A title for the list: "NewOrders" , "Preparing" , "Out for Delivery" , "Delivery"status- Which status the list is "new", "preparing", "delivery", "delivered"statusTitle- Another title for the status: "New Order!", "Preparing", "Out for delivery", "Delivered"action- What status items can be moved to: "preparing", "delivery", "delivered"actionTitle- A title for the action: "Preparing", "Out for delivery", "Delivered"emptyMessage- What to show when there are no orders in the list: "No new orders", "No orders preparing", "No orders are being delivered", "No delivered orders"
ListComponentwill have the following methods:markAs(order, action)that will update an order based on the actiondelete(order._id)that will delete an ordertotal(items)that will return the order total
P4: Setup
1. ✏️ Create the ListComponent:
ng g component order/list
2. ✏️ Update src/app/order/history.component.html to use <pmo-list>:
<div class="order-history">
  <div class="order header">
    <address>Name / Address / Phone</address>
    <div class="items">Order</div>
    <div class="total">Total</div>
    <div class="actions">Action</div>
  </div>
  <pmo-list
    [orders]="newOrders"
    listTitle="New Orders"
    status="new"
    statusTitle="New Order!"
    action="preparing"
    actionTitle="Preparing"
    emptyMessage="No new orders"
  >
  </pmo-list>
  <pmo-list
    [orders]="preparingOrders"
    listTitle="Preparing"
    status="preparing"
    statusTitle="Preparing"
    action="delivery"
    actionTitle="Out for delivery"
    emptyMessage="No orders preparing"
  >
  </pmo-list>
  <pmo-list
    [orders]="deliveryOrders"
    listTitle="Out for delivery"
    status="delivery"
    statusTitle="Out for delivery"
    action="delivered"
    actionTitle="Delivered"
    emptyMessage="No orders are being delivered"
  >
  </pmo-list>
  <pmo-list
    [orders]="deliveredOrders"
    listTitle="Delivery"
    status="delivered"
    statusTitle="Delivered"
    emptyMessage="No delivered orders"
  >
  </pmo-list>
</div>
 
3. ✏️ Update src/app/order/list/list.component.html to its final html:
<h4>{{ listTitle }}</h4>
<ng-container *ngFor="let order of orders">
  <div [ngClass]="['order', order.status ? order.status : '']">
    <address>
      {{ order.name }} <br />{{ order.address }} <br />{{ order.phone }}
    </address>
    <div class="items">
      <ul>
        <li *ngFor="let item of order.items">{{ item.name }}</li>
      </ul>
    </div>
    <div class="total">${{ total(order.items) }}</div>
    <div class="actions">
      <span class="badge">{{ statusTitle }}</span>
      <p class="action" *ngIf="action">
        Mark as:
        <button class="btn-link" (click)="markAs(order, action)">
          {{ actionTitle }}
        </button>
      </p>
      <p class="action">
        <button class="btn-link" (click)="deleteOrder(order._id)">
          Delete
        </button>
      </p>
    </div>
  </div>
</ng-container>
<div class="order empty">{{ emptyMessage }}</div>
P4: How to verify your solution is correct
✏️ Update src/app/order/history/history.component.spec.ts to be:
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { Observable, of } from 'rxjs';
import { ListComponent } from '../list/list.component';
import { Order, OrderService } from '../order.service';
import { HistoryComponent } from './history.component';
class MockOrderService {
  getOrders(): Observable<{ data: Order[] }> {
    return of({
      data: [
        {
          address: '',
          items: [
            { name: 'Onion fries', price: 15.99 },
            { name: 'Roasted Salmon', price: 23.99 },
          ],
          name: 'Client 1',
          phone: '',
          restaurant: 'uPkA2jiZi24tCvXh',
          status: 'new',
          _id: '0awcHyo3iD6CpvhX',
        },
        {
          address: '',
          items: [
            { name: 'Steak Tacos', price: 15.99 },
            { name: 'Guacamole', price: 3.99 },
          ],
          name: 'Client 2',
          phone: '',
          restaurant: 'uPkA2jiZi24tCvXh',
          status: 'preparing',
          _id: '0awcHyo3iD6CpvhX',
        },
        {
          address: '',
          items: [
            { name: 'Mac & Cheese', price: 15.99 },
            { name: 'Grilled chicken', price: 23.99 },
          ],
          name: 'Client 3',
          phone: '',
          restaurant: 'uPkA2jiZi24tCvXh',
          status: 'delivery',
          _id: '0awcHyo8iD7XjahX',
        },
        {
          address: '',
          items: [
            { name: 'Eggrolls', price: 5.99 },
            { name: 'Fried Rice', price: 18.99 },
          ],
          name: 'Client 4',
          phone: '',
          restaurant: 'uPkA2jiZi24tCvXh',
          status: 'delivered',
          _id: '1awcJyo3iD6CpvhZ',
        },
      ],
    });
  }
}
describe('HistoryComponent', () => {
  let component: HistoryComponent;
  let fixture: ComponentFixture<HistoryComponent>;
  beforeEach(async () => {
    await TestBed.configureTestingModule({
      declarations: [HistoryComponent, ListComponent],
      providers: [
        {
          provide: OrderService,
          useClass: MockOrderService,
        },
      ],
      schemas: [NO_ERRORS_SCHEMA],
    }).compileComponents();
  });
  beforeEach(() => {
    fixture = TestBed.createComponent(HistoryComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });
  it('should create', () => {
    expect(component).toBeTruthy();
  });
  it('should set response from getOrders service to orders member', () => {
    const expectedOrders: Order[] = [
      {
        address: '',
        items: [
          { name: 'Onion fries', price: 15.99 },
          { name: 'Roasted Salmon', price: 23.99 },
        ],
        name: 'Client 1',
        phone: '',
        restaurant: 'uPkA2jiZi24tCvXh',
        status: 'new',
        _id: '0awcHyo3iD6CpvhX',
      },
      {
        address: '',
        items: [
          { name: 'Steak Tacos', price: 15.99 },
          { name: 'Guacamole', price: 3.99 },
        ],
        name: 'Client 2',
        phone: '',
        restaurant: 'uPkA2jiZi24tCvXh',
        status: 'preparing',
        _id: '0awcHyo3iD6CpvhX',
      },
      {
        address: '',
        items: [
          { name: 'Mac & Cheese', price: 15.99 },
          { name: 'Grilled chicken', price: 23.99 },
        ],
        name: 'Client 3',
        phone: '',
        restaurant: 'uPkA2jiZi24tCvXh',
        status: 'delivery',
        _id: '0awcHyo8iD7XjahX',
      },
      {
        address: '',
        items: [
          { name: 'Eggrolls', price: 5.99 },
          { name: 'Fried Rice', price: 18.99 },
        ],
        name: 'Client 4',
        phone: '',
        restaurant: 'uPkA2jiZi24tCvXh',
        status: 'delivered',
        _id: '1awcJyo3iD6CpvhZ',
      },
    ];
    const orders = fixture.componentInstance.orders;
    expect(orders.value).toEqual(expectedOrders);
  });
  it('should display orders in UI', () => {
    const compiled = fixture.nativeElement as HTMLElement;
    const orderDivs = compiled.querySelectorAll(
      '.order:not(.header):not(.empty)'
    );
    expect(orderDivs.length).toEqual(4);
  });
  it('should display orders with appropriate classes', () => {
    const compiled = fixture.nativeElement as HTMLElement;
    const newOrder = compiled.getElementsByClassName('new');
    const preparingOrder = compiled.getElementsByClassName('preparing');
    const deliveryOrder = compiled.getElementsByClassName('delivery');
    const deliveredOrder = compiled.getElementsByClassName('delivered');
    expect(newOrder.length).toEqual(1);
    expect(preparingOrder.length).toEqual(1);
    expect(deliveryOrder.length).toEqual(1);
    expect(deliveredOrder.length).toEqual(1);
  });
});
 
✏️ Update src/app/order/list/list.component.spec.ts to be:
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { of } from 'rxjs';
import { Order, OrderService } from '../order.service';
import { ListComponent } from './list.component';
class MockOrderService {
  updateOrder(order: Order, status: string) {
    return of({});
  }
  deleteOrder(orderId: string) {
    return of({});
  }
}
describe('ListComponent', () => {
  let component: ListComponent;
  let fixture: ComponentFixture<ListComponent>;
  let injectedOrderService: OrderService;
  beforeEach(async () => {
    await TestBed.configureTestingModule({
      declarations: [ListComponent],
      providers: [
        {
          provide: OrderService,
          useClass: MockOrderService,
        },
      ],
    }).compileComponents();
    injectedOrderService = TestBed.inject(OrderService);
  });
  beforeEach(() => {
    fixture = TestBed.createComponent(ListComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });
  it('should create', () => {
    expect(component).toBeTruthy();
  });
  it('should display the order total', () => {
    fixture.componentInstance.orders = [
      {
        address: '',
        items: [
          { name: 'Onion fries', price: 15.99 },
          { name: 'Roasted Salmon', price: 23.99 },
        ],
        name: 'Client 1',
        phone: '',
        restaurant: 'uPkA2jiZi24tCvXh',
        status: 'new',
        _id: '0awcHyo3iD6CpvhX',
      },
    ];
    fixture.detectChanges();
    const compiled = fixture.nativeElement as HTMLElement;
    expect(compiled.querySelector('.total')?.textContent).toEqual('$39.98');
  });
  it('should call orderService updateOrder with order and action when "mark as" is clicked', () => {
    const updateOrderSpy = spyOn(
      injectedOrderService,
      'updateOrder'
    ).and.callThrough();
    const order1 = {
      address: '',
      items: [
        { name: 'Onion fries', price: 15.99 },
        { name: 'Roasted Salmon', price: 23.99 },
      ],
      name: 'Client 1',
      phone: '',
      restaurant: 'uPkA2jiZi24tCvXh',
      status: 'new',
      _id: '0awcHyo3iD6CpvhX',
    };
    fixture.componentInstance.orders = [order1];
    fixture.componentInstance.action = 'preparing';
    fixture.componentInstance.actionTitle = 'Preparing';
    fixture.detectChanges();
    const compiled = fixture.nativeElement as HTMLElement;
    const markAsLink = compiled.querySelector(
      '.actions .action button'
    ) as HTMLButtonElement;
    markAsLink?.click();
    expect(updateOrderSpy).toHaveBeenCalledWith(order1, 'preparing');
  });
  it('should call orderService deleteOrder with id when delete item is clicked', () => {
    const deleteOrderSpy = spyOn(
      injectedOrderService,
      'deleteOrder'
    ).and.callThrough();
    const order1 = {
      address: '',
      items: [
        { name: 'Onion fries', price: 15.99 },
        { name: 'Roasted Salmon', price: 23.99 },
      ],
      name: 'Client 1',
      phone: '',
      restaurant: 'uPkA2jiZi24tCvXh',
      status: 'new',
      _id: '0awcHyo3iD6CpvhX',
    };
    fixture.componentInstance.orders = [order1];
    fixture.detectChanges();
    const compiled = fixture.nativeElement as HTMLElement;
    const deleteLink = compiled.querySelector(
      '.actions .action button'
    ) as HTMLButtonElement;
    deleteLink.click();
    expect(deleteOrderSpy).toHaveBeenCalledWith('0awcHyo3iD6CpvhX');
  });
});
If you’ve implemented the solution correctly, the tests will pass when you run npm run test!
P4: What you need to know
- How to add 
@Input()s to a component so it can be passed values. - How to call methods on a service that you get from the 
constructor. 
P4: Solution
Click to see the solution
✏️ Update src/app/order/list.component.ts
import { Component, Input, OnDestroy } from '@angular/core';
import { Subject, takeUntil } from 'rxjs';
import { Item, Order, OrderService } from '../order.service';
@Component({
  selector: 'pmo-list',
  templateUrl: './list.component.html',
  styleUrl: './list.component.css',
})
export class ListComponent implements OnDestroy {
  @Input() orders?: Order[];
  @Input() listTitle?: string;
  @Input() status?: string;
  @Input() statusTitle?: string;
  @Input() action?: string;
  @Input() actionTitle?: string;
  @Input() emptyMessage?: string;
  private onDestroy$ = new Subject<void>();
  constructor(private orderService: OrderService) {}
  ngOnDestroy(): void {
    this.onDestroy$.next();
    this.onDestroy$.complete();
  }
  markAs(order: Order, action: string): void {
    this.orderService
      .updateOrder(order, action)
      .pipe(takeUntil(this.onDestroy$))
      .subscribe();
  }
  deleteOrder(id: string): void {
    this.orderService
      .deleteOrder(id)
      .pipe(takeUntil(this.onDestroy$))
      .subscribe();
  }
  total(items: Item[]): number {
    let total = 0.0;
    for (const item of items) {
      total += item.price;
    }
    return Math.round(total * 100) / 100;
  }
}