---
title: Bitovi Academy - Item Total Pipe
description: Write an Item Total Pipe to help manage item total calculation across our application.
---

- ![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) [ ]

# Item Total Pipe `page`

Write an Item Total Pipe to help manage item total calculation across our application.

## Overview

In this part, we will:

- Create a new Pipe called Item Totals
- Use Angular Default Currency Pipe

## Problem 1: Implementing and Using Item Total Pipe

Now that our application is coming together nicely, you might have noticed we are repeating the same code to calculate the total prices of our items. We can simplify this by creating an Item Total Pipe which we will use in the `list.component.html` template file and call directly using `transform` within `order.component.ts` file. The Pipe will transform an array of items and return the total sum of the price of each item.

After implementing this Pipe, you should be able to remove the `total` method from `list.component.ts`.

## P1: What you need to know

How to:

- Generate a Pipe using Angular CLI (you previously learned this in the [Creating Pipes](https://www.bitovi.com/academy/learn-angular/creating-pipes.html?hsLang=en-us) section! ✔️)
  
  ```
  ng g pipe itemTotal
  ```
- Provide a Pipe globally to the entire application, so it can be used in any component

## Provide Pipe Globally

Unlike services, Pipes are not readily injectable into our components. In order to be able to use a Pipe in a component’s `.ts` file, the Pipe has to be provided by a module. To provide the **Pipe** just created in the app module, simply add the Pipe to the `providers` array in the `app.module.ts` file. [Learn More](https://angular.io/guide/providers).

By including the Pipe in a component’s constructor, you gain the ability to run the Pipe using its `transform` method.

✏️ Update **src/app/app.module.ts**

```
import { HttpClientModule } from '@angular/common/http';
import { NgModule } from '@angular/core';
import { ReactiveFormsModule } from '@angular/forms';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { TabsModule } from 'ngx-bootstrap/tabs';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { HomeComponent } from './home/home.component';
import { RestaurantComponent } from './restaurant/restaurant.component';
import { ImageUrlPipe } from './image-url.pipe';
import { DetailComponent } from './restaurant/detail/detail.component';
import { OrderComponent } from './order/order.component';
import { MenuItemsComponent } from './order/menu-items/menu-items.component';
import { OnlyNumbersDirective } from './only-numbers.directive';
import { HistoryComponent } from './order/history/history.component';
import { ListComponent } from './order/list/list.component';
import { ItemTotalPipe } from './item-total.pipe';

@NgModule({
  declarations: [
    AppComponent,
    HomeComponent,
    RestaurantComponent,
    ImageUrlPipe,
    DetailComponent,
    OrderComponent,
    MenuItemsComponent,
    OnlyNumbersDirective,
    HistoryComponent,
    ListComponent,
    ItemTotalPipe
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    HttpClientModule,
    ReactiveFormsModule,
    BrowserAnimationsModule,
    TabsModule.forRoot()
  ],
  providers: [ItemTotalPipe],
  bootstrap: [AppComponent]
})
export class AppModule { }

```

Now the Pipe is available to be used in components across the application.

The example below shows how to provide a Pipe globally in Angular. The Pipe being created will transform two separate words into one compound word. It will take a string value, then a parameter, that is used as a prefix to the string value.

```
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.2.1/rxjs.umd.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/core-js/2.5.7/core.js"/></script>
<script src="https://unpkg.com/@angular/core@7.2.0/bundles/core.umd.js"/></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/zone.js/0.8.26/zone.min.js"></script>
<script src="https://unpkg.com/@angular/common@7.2.0/bundles/common.umd.js"></script>
<script src="https://unpkg.com/@angular/compiler@7.2.0/bundles/compiler.umd.js"></script>
<script src="https://unpkg.com/@angular/platform-browser@7.2.0/bundles/platform-browser.umd.js"></script>
<script src="https://unpkg.com/@angular/platform-browser-dynamic@7.2.0/bundles/platform-browser-dynamic.umd.js"></script>
<my-app></my-app>
<script type="typescript">
// app.js
const { Component, VERSION, Pipe, PipeTransform} = ng.core;

@Pipe({ name: 'prefix' })
class PrefixPipe implements PipeTransform {
  transform(value: string, prefix: string): string {
    if (prefix && value) {
      return `${prefix}-${value}`;
    }
    return value;
  }
}

@Component({
  selector: 'my-app',
  template: `
    <h2>{{ word }}</h2>
    <br />
    <h2>{{ word2 | prefix: 'long' }}</h2>
  `,
})
class AppComponent implements OnInit {
  word = 'two';
  word2 = 'term';

  constructor(private prefixPipe: PrefixPipe) {}

  ngOnInit(): void {
    this.word = this.prefixPipe.transform(this.word, 'seventy');
  }
}

// main.js
const { BrowserModule } = ng.platformBrowser;
const { NgModule } = ng.core;
const { CommonModule } = ng.common;

@NgModule({
  imports: [BrowserModule, CommonModule],
  declarations: [AppComponent, PrefixPipe],
  bootstrap: [AppComponent],
  providers: [PrefixPipe]
})
class AppModule {}

const { platformBrowserDynamic } = ng.platformBrowserDynamic;

platformBrowserDynamic()
  .bootstrapModule(AppModule)
  .catch(err => console.error(err));
</script>

```

## P1: How to verify your solution is correct

✏️ Update the following spec files to include and provide the new Pipe:

- **src/app/order/order.component.spec.ts**

```
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { FormGroup, ReactiveFormsModule } from '@angular/forms';
import { ActivatedRoute } from '@angular/router';
import { RouterTestingModule } from '@angular/router/testing';
import { of } from 'rxjs';
import { ItemTotalPipe } from '../item-total.pipe';
import { RestaurantService } from '../restaurant/restaurant.service';
import { MenuItemsComponent } from './menu-items/menu-items.component';
import { OrderComponent, OrderForm } from './order.component';
import { CreateOrderDto, OrderService } from './order.service';

class MockRestaurantService {
  getRestaurant(slug: string) {
    return of({
      name: 'Poutine Palace',
      slug: 'poutine-palace',
      images: {
        thumbnail: 'node_modules/place-my-order-assets/images/4-thumbnail.jpg',
        owner: 'node_modules/place-my-order-assets/images/3-owner.jpg',
        banner: 'node_modules/place-my-order-assets/images/2-banner.jpg',
      },
      menu: {
        lunch: [
          {
            name: 'Crab Pancakes with Sorrel Syrup',
            price: 35.99,
          },
          {
            name: 'Steamed Mussels',
            price: 21.99,
          },
          {
            name: 'Spinach Fennel Watercress Ravioli',
            price: 35.99,
          },
        ],
        dinner: [
          {
            name: 'Gunthorp Chicken',
            price: 21.99,
          },
          {
            name: 'Herring in Lavender Dill Reduction',
            price: 45.99,
          },
          {
            name: 'Chicken with Tomato Carrot Chutney Sauce',
            price: 45.99,
          },
        ],
      },
      address: {
        street: '230 W Kinzie Street',
        city: 'Green Bay',
        state: 'WI',
        zip: '53205',
      },
      _id: '3ZOZyTY1LH26LnVw',
    });
  }
}

class MockOrderService {
  createOrder(order: CreateOrderDto) {
    return of({
      address: null,
      items: [
        { name: 'Onion fries', price: 15.99 },
        { name: 'Roasted Salmon', price: 23.99 },
      ],
      name: 'Jennifer Hungry',
      phone: null,
      restaurant: 'uPkA2jiZi24tCvXh',
      status: 'preparing',
      _id: '0awcHyo3iD6CpvhX',
    });
  }
}

const MockActivatedRoute = {
  snapshot: {
    paramMap: {
      get() {
        return 'poutine-palace';
      },
    },
  },
};

describe('OrderComponent', () => {
  let component: OrderComponent;
  let fixture: ComponentFixture<OrderComponent>;
  let orderService: OrderService;

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      declarations: [OrderComponent, MenuItemsComponent],
      imports: [ReactiveFormsModule, RouterTestingModule],
      providers: [
        {
          provide: RestaurantService,
          useClass: MockRestaurantService,
        },
        {
          provide: ActivatedRoute,
          useValue: MockActivatedRoute,
        },
        {
          provide: OrderService,
          useClass: MockOrderService,
        },
        ItemTotalPipe,
      ],
      schemas: [NO_ERRORS_SCHEMA],
    }).compileComponents();
    orderService = TestBed.inject(OrderService);
  });

  beforeEach(() => {
    fixture = TestBed.createComponent(OrderComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });

  it('should get a restaurant based on route slug', () => {
    const mockRestaurant = {
      name: 'Poutine Palace',
      slug: 'poutine-palace',
      images: {
        thumbnail: 'node_modules/place-my-order-assets/images/4-thumbnail.jpg',
        owner: 'node_modules/place-my-order-assets/images/3-owner.jpg',
        banner: 'node_modules/place-my-order-assets/images/2-banner.jpg',
      },
      menu: {
        lunch: [
          {
            name: 'Crab Pancakes with Sorrel Syrup',
            price: 35.99,
          },
          {
            name: 'Steamed Mussels',
            price: 21.99,
          },
          {
            name: 'Spinach Fennel Watercress Ravioli',
            price: 35.99,
          },
        ],
        dinner: [
          {
            name: 'Gunthorp Chicken',
            price: 21.99,
          },
          {
            name: 'Herring in Lavender Dill Reduction',
            price: 45.99,
          },
          {
            name: 'Chicken with Tomato Carrot Chutney Sauce',
            price: 45.99,
          },
        ],
      },
      address: {
        street: '230 W Kinzie Street',
        city: 'Green Bay',
        state: 'WI',
        zip: '53205',
      },
      _id: '3ZOZyTY1LH26LnVw',
    };
    expect(fixture.componentInstance.restaurant).toEqual(mockRestaurant);
  });

  it('should have an orderForm formGroup', () => {
    expect(
      fixture.componentInstance.orderForm?.controls['restaurant']
    ).toBeTruthy();
    expect(
      fixture.componentInstance.orderForm?.controls['address']
    ).toBeTruthy();
    expect(fixture.componentInstance.orderForm?.controls['phone']).toBeTruthy();
    expect(fixture.componentInstance.orderForm?.controls['items']).toBeTruthy();
  });

  it('should have a validator on items formControl', () => {
    const itemFormControl =
      fixture.componentInstance.orderForm?.controls['items'];
    expect(itemFormControl?.valid).toEqual(false);
  });

  it('should update items FormControl when setUpdatesItems is called', () => {
    fixture.detectChanges();
    const compiled = fixture.nativeElement as HTMLElement;
    const childInput = compiled
      .getElementsByTagName('pmo-menu-items')[0]
      .getElementsByTagName('input')[0];
    const formItems = fixture.componentInstance.orderForm?.get('items');
    childInput.click();
    fixture.detectChanges();
    expect(formItems?.value).toEqual([
      { name: 'Crab Pancakes with Sorrel Syrup', price: 35.99 },
    ]);
  });

  it('should update the order total when the items FormControl value changes', () => {
    fixture.detectChanges();
    const compiled = fixture.nativeElement as HTMLElement;
    const childInput1 = compiled
      .getElementsByTagName('pmo-menu-items')[0]
      .getElementsByTagName('input')[0];
    const childInput2 = compiled
      .getElementsByTagName('pmo-menu-items')[0]
      .getElementsByTagName('input')[1];
    childInput1.click();
    childInput2.click();
    fixture.detectChanges();
    const orderText = compiled.querySelector('.submit h4');
    expect(orderText?.textContent).toEqual('Total: $57.98');
  });

  it('should call the orderService createOrder on form submit with form values', () => {
    const createOrderSpy = spyOn(orderService, 'createOrder').and.callThrough();
    const expectedOrderValue: ReturnType<FormGroup<OrderForm>['getRawValue']> =
      {
        restaurant: '12345',
        name: 'Jennifer Hungry',
        address: '123 Main St',
        phone: '555-555-5555',
        items: [
          { name: 'Onion fries', price: 15.99 },
          { name: 'Roasted Salmon', price: 23.99 },
        ],
      };
    const compiled = fixture.nativeElement as HTMLElement;
    fixture.componentInstance.orderForm?.setValue(expectedOrderValue);
    fixture.detectChanges();
    (
      compiled.querySelector('button[type="submit"]') as HTMLButtonElement
    ).click();
    expect(createOrderSpy).toHaveBeenCalledWith(expectedOrderValue);
  });

  it('should show completed order when order is complete', () => {
    const expectedOrderValue: ReturnType<FormGroup<OrderForm>['getRawValue']> =
      {
        restaurant: '12345',
        name: 'Jennifer Hungry',
        address: '123 Main St',
        phone: '555-555-5555',
        items: [
          { name: 'Onion fries', price: 15.99 },
          { name: 'Roasted Salmon', price: 23.99 },
        ],
      };
    const compiled = fixture.nativeElement as HTMLElement;
    fixture.componentInstance.orderForm?.setValue(expectedOrderValue);
    fixture.detectChanges();
    (
      compiled.querySelector('button[type="submit"]') as HTMLButtonElement
    ).click();
    fixture.detectChanges();
    const displayedOrder = compiled.querySelector('h3');
    expect(displayedOrder?.textContent).toEqual(
      'Thanks for your order Jennifer Hungry!'
    );
  });

  it('should clear the form values when create new order is clicked', () => {
    const expectedOrderValue: ReturnType<FormGroup<OrderForm>['getRawValue']> =
      {
        restaurant: '12345',
        name: 'Jennifer Hungry',
        address: '123 Main St',
        phone: '555-555-5555',
        items: [
          { name: 'Onion fries', price: 15.99 },
          { name: 'Roasted Salmon', price: 23.99 },
        ],
      };
    const compiled = fixture.nativeElement as HTMLElement;
    fixture.componentInstance.orderForm?.setValue(expectedOrderValue);
    fixture.detectChanges();
    (
      compiled.querySelector('button[type="submit"]') as HTMLButtonElement
    ).click();
    fixture.detectChanges();
    (
      compiled.querySelector('button:nth-child(1)') as HTMLButtonElement
    ).click();
    const emptyform = {
      restaurant: '3ZOZyTY1LH26LnVw',
      name: '',
      address: '',
      phone: '',
      items: [],
    };
    expect(fixture.componentInstance.orderForm?.value).toEqual(emptyform);
  });
});

```

- **src/app/app.component.spec.ts**

```
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 { ItemTotalPipe } from './item-total.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,
        ItemTotalPipe,
      ],
      providers: [
        { provide: RestaurantService, useClass: MockRestaurantService },
        ItemTotalPipe,
      ],
      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();
  }));
});

```

- **src/app/order/history/history.component.spec.ts**

```
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { Observable, of } from 'rxjs';
import { ItemTotalPipe } from '../../item-total.pipe';
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, ItemTotalPipe],
      providers: [
        {
          provide: OrderService,
          useClass: MockOrderService,
        },
        ItemTotalPipe,
      ],
      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);
  });
});

```

- **src/app/order/list/list.component.spec.ts**

```
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { of } from 'rxjs';
import { ItemTotalPipe } from '../../item-total.pipe';
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, ItemTotalPipe],
      providers: [
        {
          provide: OrderService,
          useClass: MockOrderService,
        },
        ItemTotalPipe,
      ],
    }).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');
  });
});

```

✏️ Update the spec file **item-total.pipe.spec.ts** to be:

```
import { ItemTotalPipe } from './item-total.pipe';
import { Item } from './order/order.service';

describe('ItemTotalPipe', () => {
  const mockItemList: Item[] = [
    { name: 'Truffle Noodles', price: 14.99 },
    { name: 'Garlic Fries', price: 15.99 },
  ];
  const pipe = new ItemTotalPipe();

  it('create an instance', () => {
    expect(pipe).toBeTruthy();
  });

  it('transform Item[] and return the sum of each item price, total price should be 30.98', () => {
    const total = pipe.transform(mockItemList);
    expect(total).toEqual(30.98);
  });

  it('expect empty array to be 0', () => {
    const total = pipe.transform([]);
    expect(total).toEqual(0);
  });
});

```

If you’ve implemented the solution correctly, the tests will pass when you run `npm run test`!

## P1: Solution

Click to see the solution ✏️ Update **src/app/item-total.pipe.ts** to:

```
import { Pipe, PipeTransform } from '@angular/core';
import { Item } from './order/order.service';

@Pipe({
  name: 'itemTotal',
})
export class ItemTotalPipe implements PipeTransform {
  transform(items: Item[]): number {
    let itemTotal = 0.0;
    if (items.length) {
      for (const item of items) {
        itemTotal += item.price;
      }
      return Math.round(itemTotal * 100) / 100;
    }
    return itemTotal;
  }
}

```

✏️ Update **src/app/order/order.component.ts** to use the item total Pipe:

```
import { Component, OnInit, OnDestroy } from '@angular/core';
import {
  AbstractControl,
  FormBuilder,
  FormControl,
  FormGroup,
  ValidationErrors,
  ValidatorFn,
  Validators,
} from '@angular/forms';
import { ActivatedRoute } from '@angular/router';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { ItemTotalPipe } from '../item-total.pipe';
import { Restaurant } from '../restaurant/restaurant';
import { RestaurantService } from '../restaurant/restaurant.service';
import { Order, OrderService } from './order.service';

export interface Item {
  name: string;
  price: number;
}

export interface OrderForm {
  restaurant: FormControl<string>;
  name: FormControl<string>;
  address: FormControl<string>;
  phone: FormControl<string>;
  items: FormControl<Item[]>;
}

// CUSTOM VALIDATION FUNCTION TO ENSURE THAT THE ITEMS FORM VALUE CONTAINS AT LEAST ONE ITEM.
function minLengthArray(min: number): ValidatorFn {
  return (c: AbstractControl): ValidationErrors | null => {
    if (c.value.length >= min) {
      return null;
    }
    return { minLengthArray: { valid: false } };
  };
}

@Component({
  selector: 'pmo-order',
  templateUrl: './order.component.html',
  styleUrl: './order.component.css',
})
export class OrderComponent implements OnInit, OnDestroy {
  orderForm?: FormGroup<OrderForm>;
  restaurant?: Restaurant;
  isLoading = true;
  orderTotal = 0.0;
  completedOrder?: Order;
  orderComplete = false;
  orderProcessing = false;
  private onDestroy$ = new Subject<void>();

  constructor(
    private route: ActivatedRoute,
    private restaurantService: RestaurantService,
    private orderService: OrderService,
    private formBuilder: FormBuilder,
    private itemTotal: ItemTotalPipe
  ) {}

  ngOnInit(): void {
    // GETTING THE RESTAURANT FROM THE ROUTE SLUG
    const slug = this.route.snapshot.paramMap.get('slug');

    if (slug) {
      this.restaurantService
        .getRestaurant(slug)
        .pipe(takeUntil(this.onDestroy$))
        .subscribe((data: Restaurant) => {
          this.restaurant = data;
          this.isLoading = false;
          this.createOrderForm();
        });
    }
  }

  ngOnDestroy(): void {
    this.onDestroy$.next();
    this.onDestroy$.complete();
  }

  createOrderForm(): void {
    this.orderForm = this.formBuilder.nonNullable.group({
      restaurant: [this.restaurant?._id ?? '', Validators.required],
      name: ['', Validators.required],
      address: ['', Validators.required],
      phone: ['', Validators.required],
      // PASSING OUR CUSTOM VALIDATION FUNCTION TO THIS FORM CONTROL
      items: [[] as Item[], minLengthArray(1)],
    });
    this.onChanges();
  }

  getChange(newItems: Item[]): void {
    this.orderForm?.controls.items.patchValue(newItems);
  }

  onChanges(): void {
    // WHEN THE ITEMS CHANGE WE WANT TO CALCULATE A NEW TOTAL
    this.orderForm?.controls.items.valueChanges
      .pipe(takeUntil(this.onDestroy$))
      .subscribe((value) => {
        this.orderTotal = this.itemTotal.transform(value);
      });
  }

  onSubmit(): void {
    if (!this.orderForm?.valid) {
      return;
    }

    this.orderProcessing = true;
    this.orderService
      .createOrder(this.orderForm.getRawValue())
      .pipe(takeUntil(this.onDestroy$))
      .subscribe((res: Order) => {
        this.completedOrder = res;
        this.orderComplete = true;
        this.orderProcessing = false;
      });
  }

  startNewOrder(): void {
    this.orderComplete = false;
    this.completedOrder = undefined;
    this.orderTotal = 0.0;
    // CLEAR THE ORDER FORM
    this.createOrderForm();
  }
}

```

✏️ Update **src/app/order/list/list.component.html** to use the item total Pipe:

```
<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">${{ order.items | itemTotal }}</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>

```

## Problem 2: Using Currency Pipe

Now that we have finished implementing our Item Total Pipe, this is a good time for us to talk about using Currency Pipe. You might have noticed that our prices are in USD, which is a currency, and is a perfect use for the Currency Pipe. Angular provides us with a [Currency Pipe](https://angular.io/api/common/CurrencyPipe) that formats a number as currency using locale rules. Locale Rules refers to a user’s country or region specific details like currency, language and more.

Add the Currency Pipe to the `order.component.html` , `menu-items.component.html` and `list.component.html` template files:

## P2: What you need to know

How to:

- Apply a Pipe (you previously learned this ✔️)

Like all the other Pipes we have created, the Currency Pipe can be used the exact same way.

## P2: Solution

Click to see the solution ✏️ Update **src/app/order/order.component.html** to use the Currency Pipe:

```
<div class="order-form">
  <ng-container *ngIf="orderComplete && completedOrder; else showOrderForm">
    <h3>Thanks for your order {{ completedOrder.name }}!</h3>
    <div>
      <label class="control-label">
        Confirmation Number: {{ completedOrder._id }}</label
      >
    </div>

    <h4>Items ordered:</h4>
    <ul class="list-group panel">
      <li class="list-group-item" *ngFor="let item of completedOrder.items">
        <label>
          {{ item.name }}
          <span class="badge">{{ item.price | currency }}</span>
        </label>
      </li>

      <li class="list-group-item">
        <label>
          Total <span class="badge">{{ orderTotal | currency }}</span>
        </label>
      </li>
    </ul>

    <div>
      <label class="control-label"> Phone: {{ completedOrder.phone }} </label>
    </div>
    <div>
      <label class="control-label">
        Address: {{ completedOrder.address }}
      </label>
    </div>
    <p>
      <button (click)="startNewOrder()">Place another order</button>
    </p>
  </ng-container>
  <ng-template #showOrderForm>
    <h2>Order here</h2>
    <form
      *ngIf="orderForm && restaurant"
      [formGroup]="orderForm"
      (ngSubmit)="onSubmit()"
    >
      <tabset>
        <tab heading="Lunch Menu" *ngIf="restaurant.menu.lunch">
          <ul class="list-group">
            <pmo-menu-items
              [items]="restaurant.menu.lunch"
              formControlName="items"
            ></pmo-menu-items>
          </ul>
        </tab>
        <tab heading="Dinner Menu" *ngIf="restaurant.menu.dinner">
          <ul class="list-group">
            <pmo-menu-items
              [items]="restaurant.menu.dinner"
              formControlName="items"
            ></pmo-menu-items>
          </ul>
        </tab>
      </tabset>
      <div class="form-group">
        <label class="control-label">Name:</label>
        <input name="name" type="text" formControlName="name" />
        <p>Please enter your name.</p>
      </div>
      <div class="form-group">
        <label class="control-label">Address:</label>
        <input name="address" type="text" formControlName="address" />
        <p class="help-text">Please enter your address.</p>
      </div>
      <div class="form-group">
        <label class="control-label">Phone:</label>
        <input
          name="phone"
          type="text"
          pmoOnlyNumbers
          formControlName="phone"
        />
        <p class="help-text">Please enter your phone number.</p>
      </div>
      <div class="submit">
        <h4>Total: {{ orderTotal | currency }}</h4>
        <div class="loading" *ngIf="orderProcessing"></div>
        <button
          type="submit"
          [disabled]="!orderForm.valid || orderProcessing"
          class="btn"
        >
          Place My Order!
        </button>
      </div>
    </form>
  </ng-template>
</div>

```

✏️ Update **src/app/order/list/list.component.html** to use the currency Pipe:

```
<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">{{ order.items | itemTotal | currency }}</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>

```

✏️ Update **src/app/order/menu-items/menu-items.component.html** to use the Currency Pipe:

```
<li class="list-group-item" *ngFor="let item of items">
  <label>
    <input type="checkbox" (change)="updateItem(item)" />
    {{ item.name }} <span class="badge">{{ item.price | currency }}</span>
  </label>
</li>

```

## Set Default Currency Code

If we want to display a different currency than the one provided by the application’s locale, which defaults to `en-US` and therefore `USD`, we may override the locale’s currency by providing `DEFAULT_CURRENCY_CODE` in `app.module.ts`.

✏️ Update **app.module.ts** to provide currency code:

```
import { HttpClientModule } from '@angular/common/http';
import { DEFAULT_CURRENCY_CODE, NgModule } from '@angular/core';
import { ReactiveFormsModule } from '@angular/forms';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { TabsModule } from 'ngx-bootstrap/tabs';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { HomeComponent } from './home/home.component';
import { RestaurantComponent } from './restaurant/restaurant.component';
import { ImageUrlPipe } from './image-url.pipe';
import { DetailComponent } from './restaurant/detail/detail.component';
import { OrderComponent } from './order/order.component';
import { MenuItemsComponent } from './order/menu-items/menu-items.component';
import { OnlyNumbersDirective } from './only-numbers.directive';
import { HistoryComponent } from './order/history/history.component';
import { ListComponent } from './order/list/list.component';
import { ItemTotalPipe } from './item-total.pipe';

@NgModule({
  declarations: [
    AppComponent,
    HomeComponent,
    RestaurantComponent,
    ImageUrlPipe,
    DetailComponent,
    OrderComponent,
    MenuItemsComponent,
    OnlyNumbersDirective,
    HistoryComponent,
    ListComponent,
    ItemTotalPipe
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    HttpClientModule,
    ReactiveFormsModule,
    BrowserAnimationsModule,
    TabsModule.forRoot()
  ],
  providers: [
    ItemTotalPipe,
    { provide: DEFAULT_CURRENCY_CODE, useValue: 'USD' },
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

```

###### 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/item-total-pipe.html",
    "@type" : "WebPage",
    "description" : "Write an Item Total Pipe to help manage item total calculation across our application. "
  },
  "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"
}
```