---
title: Bitovi Academy - Order History Component
description: Writing the Order History Component
---

- ![AI implementation](https://www.bitovi.com/hubfs/AIConsultingIcon.svg)
  
  [AI implementation](https://www.bitovi.com/services/ai-consulting?hsLang=en-us)
- ![Systems engineering](https://www.bitovi.com/hubfs/icon%20-%20backend.svg)
  
  [Systems engineering](https://www.bitovi.com/services/systems-engineering-consulting?hsLang=en-us)
- ![Project management](https://www.bitovi.com/hubfs/icon%20-%20PM.svg)
  
  [Project management](https://www.bitovi.com/services/agile-project-management-consulting?hsLang=en-us)
- ![Product design](https://www.bitovi.com/hubfs/icon%20-%20design.svg)
  
  [Product design](https://www.bitovi.com/services/product-design-consulting?hsLang=en-us)
- ![Frontend development](https://www.bitovi.com/hubfs/icon%20-%20frontend.svg)
  
  [Frontend development](https://www.bitovi.com/services/frontend-development-consulting?hsLang=en-us)
- [View more
  
  →
  
  ](https://www.bitovi.com/digital-consulting-services?hsLang=en-us)

We're Experts in...

- [JavaScript](https://www.bitovi.com/services/frontend/javascript-consulting?hsLang=en-us)
- [AI training](https://www.bitovi.com/ai-training-for-software-engineers?hsLang=en-us)
- [Angular](https://www.bitovi.com/services/frontend/angular-consulting?hsLang=en-us)
- [Design Systems](https://www.bitovi.com/services/axure-figma-migration?hsLang=en-us)
- [React](https://www.bitovi.com/services/frontend/react-consulting?hsLang=en-us)
- [Temporal](https://www.bitovi.com/services/backend/temporal-consulting?hsLang=en-us)
- [React Native](https://www.bitovi.com/services/frontend/react-consulting/react-native?hsLang=en-us)
- [Node.js](https://www.bitovi.com/services/backend/nodejs-consulting-1?hsLang=en-us)

Showcase

![Yum! Brands](https://www.bitovi.com/hubfs/yum-showcase-link-1.png)

[View case study](https://www.bitovi.com/en/bitovi-yum-case-study?hsLang=en-us)

More Projects

- [![Levi's](https://www.bitovi.com/hubfs/levis.svg)](https://www.bitovi.com/web-application-consulting-work/levis-ecommerce-responsive-redesign?hsLang=en-us)
- [![Christie's International Real Estate](https://www.bitovi.com/hubfs/christies.svg)](https://design.bitovi.com/christies)
- [![BAFS](https://www.bitovi.com/hubfs/bafs.svg)](https://www.bitovi.com/ux-design-consulting/ux-case-studies/bafs-ppp?hsLang=en-us)
- [View more
  
  →
  
  ](https://www.bitovi.com/our-software-consulting-work?hsLang=en-us)

Open Source Tools

We build powerful tools and open source them to support the community.

[See what we've built →](https://www.bitovi.com/open-source?hsLang=en-us)

- [![Blog](https://www.bitovi.com/hubfs/icon%20-%20blog.svg)
  
  BlogWe post about perfecting software delivery
  
  ](https://bitovi.com/blog)
- [![Partnerships](https://www.bitovi.com/hubfs/Handshake-1.svg)
  
  PartnershipsLearn about Bitovi's technology partners
  
  ](https://www.bitovi.com/partnerships?hsLang=en-us)
- [![Academy](https://www.bitovi.com/hubfs/limbo/icons/icon-academy.svg)
  
  AcademyFree courses to build delivery skills
  
  ](https://www.bitovi.com/academy/?hsLang=en-us)
- [![Open source tools](https://www.bitovi.com/hubfs/icon%20-%20open%20source.svg)
  
  Open source toolsUse our tools or contribute to our open source mission.
  
  ](https://www.bitovi.com/open-source?hsLang=en-us)

Let's Connect

- [![Discord](https://www.bitovi.com/hubfs/DiscordLogo.svg)
  
  Discord
  
  ](https://discord.gg/J7ejFsZnJ4)
- [![LinkedIn](https://www.bitovi.com/hubfs/LinkedinLogo.svg)
  
  LinkedIn
  
  ](https://www.linkedin.com/company/bitovi/)
- [![GitHub](https://www.bitovi.com/hubfs/GithubLogo.svg)
  
  GitHub
  
  ](https://github.com/bitovi/)

![Eggbot](https://www.bitovi.com/hubfs/build_assets/bitovi-limbo-cms-react/338/js_client_assets/assets/eggbot-LTGhdSGL.png)

Name *

Work Email *

Phone

What's your project?

Send

### Contact Us

(312) 620-0386contact@bitovi.com

- [ Advanced JavaScript ](https://www.bitovi.com/academy/learn-advanced-javascript.html?hsLang=en-us)
- [ DOM and jQuery ](https://www.bitovi.com/academy/learn-dom-jquery.html?hsLang=en-us)
- [ Debug JavaScript ](https://www.bitovi.com/academy/learn-to-debug-javascript.html?hsLang=en-us)
- [ Learn TypeScript ](https://www.bitovi.com/academy/learn-typescript.html?hsLang=en-us)
- [ Learn React Native ](https://www.bitovi.com/academy/learn-react-native.html?hsLang=en-us)
- [ Learn Web Components ](https://www.bitovi.com/academy/learn-web-components.html?hsLang=en-us)
- [ Agile Program Management With Jira ](https://www.bitovi.com/academy/learn-agile-program-management-with-jira.html?hsLang=en-us)
- [ Learn RxJS ](https://www.bitovi.com/academy/learn-rxjs.html?hsLang=en-us)
- [ Giving a Training ](https://www.bitovi.com/academy/training.html?hsLang=en-us)
- [ Learn Technology Consulting ](https://www.bitovi.com/academy/learn-technology-consulting.html?hsLang=en-us)
- [ Learn React ](https://www.bitovi.com/academy/learn-react.html?hsLang=en-us)
- [ Learn NgRx ](https://www.bitovi.com/academy/learn-ngrx.html?hsLang=en-us)
- [ Learn Graphql ](https://www.bitovi.com/academy/learn-graphql.html?hsLang=en-us)
- [ Learn Docker ](https://www.bitovi.com/academy/learn-docker.html?hsLang=en-us)
- [ Learn CanJS ](https://www.bitovi.com/academy/learn-canjs.html?hsLang=en-us)
- [ Learn Angular ](https://www.bitovi.com/academy/learn-angular.html?hsLang=en-us) 
    - [ Why Angular? ](https://www.bitovi.com/academy/learn-angular/why-angular.html?hsLang=en-us)
    - [ Generate an App ](https://www.bitovi.com/academy/learn-angular/building-our-first-app.html?hsLang=en-us)
    - [ Creating Components ](https://www.bitovi.com/academy/learn-angular/creating-components.html?hsLang=en-us)
    - [ Creating Pipes ](https://www.bitovi.com/academy/learn-angular/creating-pipes.html?hsLang=en-us)
    - [ Adding Routing ](https://www.bitovi.com/academy/learn-angular/adding-routing.html?hsLang=en-us)
    - [ Creating Navigation ](https://www.bitovi.com/academy/learn-angular/creating-navigation.html?hsLang=en-us)
    - [ Fetching Data with Services ](https://www.bitovi.com/academy/learn-angular/restaurant-service.html?hsLang=en-us)
    - [ Rendering Data in Components ](https://www.bitovi.com/academy/learn-angular/pull-restaurant-data-into-view.html?hsLang=en-us)
    - [ Reactive Forms Data Binding ](https://www.bitovi.com/academy/learn-angular/state-city-options.html?hsLang=en-us)
    - [ Listening to Form Changes ](https://www.bitovi.com/academy/learn-angular/form-value-changes.html?hsLang=en-us)
    - [ Multi-parameter Services ](https://www.bitovi.com/academy/learn-angular/updating-service-params.html?hsLang=en-us)
    - [ Optional: Declarative State ](https://www.bitovi.com/academy/learn-angular/declarative-state.html?hsLang=en-us)
    - [ Writing Unit Tests ](https://www.bitovi.com/academy/learn-angular/writing-unit-tests.html?hsLang=en-us)
    - [ Creating Nested Routes ](https://www.bitovi.com/academy/learn-angular/nested-routes.html?hsLang=en-us)
    - [ Binding Component Data ](https://www.bitovi.com/academy/learn-angular/building-order-form.html?hsLang=en-us)
    - [ Creating Directives ](https://www.bitovi.com/academy/learn-angular/creating-directive.html?hsLang=en-us)
    - [ Order Service ](https://www.bitovi.com/academy/learn-angular/order-service.html?hsLang=en-us)
    - [ Order History Component ](https://www.bitovi.com/academy/learn-angular/order-history-component.html?hsLang=en-us)
    - [ Item Total Pipe ](https://www.bitovi.com/academy/learn-angular/item-total-pipe.html?hsLang=en-us)
    - [ Real Time Connections ](https://www.bitovi.com/academy/learn-angular/real-time-connection.html?hsLang=en-us)
    - [ Deploy App ](https://www.bitovi.com/academy/learn-angular/deploy-app.html?hsLang=en-us)
- [ Advanced TypeScript ](https://www.bitovi.com/academy/advanced-typescript.html?hsLang=en-us)

[View More Courses](https://www.bitovi.com/academy/#courses) [ ]

# 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

1. Generate a `HistoryComponent` in `src/app/order/history/history.component.ts`
2. Show `HistoryComponent` when 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](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.ts` to 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

1. Add a **Order History** link to the navigation bar at the top of the page.
2. Add the class name `active` to the link if we are on the `OrderHistory` page.

## 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](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

1. List all orders in the `HistoryComponent`.
2. Make sure the `<div>` for each order has a class name of *'order'* and a class name that is the `order.status` value. 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

1. Group the orders by status.
2. Allow the user to change the status of an order.
3. 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 `ListComponent` in **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>
  ```
- `ListComponent` will take the following values: 
    - `orders` - an array of orders based on status property
    - `listTitle` - 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"
- `ListComponent` will have the following methods: 
    - `markAs(order, action)` that will update an order based on the action
    - `delete(order._id)` that will delete an order
    - `total(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;
  }
}

```

###### On this page...

###### Get help

If you need [help](https://discord.gg/J7ejFsZnJ4) with an exercise, please reach out to us on [Discord](https://discord.gg/J7ejFsZnJ4)

If you find a bug, please [create an issue](https://github.com/bitovi/academy/issues/new) or email [contact@bitovi.com](mailto:contact@bitovi.com?Subject=Training%20Suggestion)

## Join the Bitovi Newsletter

 Do you want to receive updates about new Bitovi Academy courses, Bitovi open source projects, and dev community news? If so, please provide your email. We won’t spam you or give it away for any reason!  
*By providing your email you agree to receive communications from Bitovi*

![](https://bitovi.github.io/academy/static/img/close.svg)

```json
{
  "@context" : "http://schema.org",
  "@type" : "Organization",
  "address" : {
    "@type" : "PostalAddress",
    "addressCountry" : "United States",
    "addressLocality" : "Libertyville",
    "addressRegion" : "IL",
    "postalCode" : "60048",
    "streetAddress" : "1134 Pine Tree Lane "
  },
  "alternateName" : "Bitovi",
  "areaServed" : {
    "@type" : "GeoCircle",
    "geoMidpoint" : {
      "@type" : "GeoCoordinates",
      "latitude" : "41.8781",
      "longitude" : "87.6298"
    },
    "geoRadius" : "5000 km"
  },
  "description" : "Bitovi is a UX, UI design and front-end JavaScript development consulting company",
  "email" : "contact@bitovi.com",
  "image" : "https://www.bitovi.com/hubfs/bitovi-logo-x2.png",
  "logo" : "https://www.bitovi.com/hubfs/bitovi-logo-23-1.svg",
  "mainEntityOfPage" : {
    "@id" : "https://www.bitovi.com/academy/learn-angular/order-history-component.html",
    "@type" : "WebPage",
    "description" : "Writing the Order History Component "
  },
  "naics" : "541511",
  "name" : "Bitovi Web App Consulting",
  "sameAs" : [ "https://www.facebook.com/BitoviLLC/", "https://twitter.com/bitovi", "https://www.linkedin.com/company/bitovi" ],
  "telephone" : "312-620-0386",
  "url" : "http://bitovi.com"
}
```