Fetching Data with Services page

Learn how to write an Angular service that gets data from the server.

Overview

In this part, we will:

  • Install the place-my-order API
  • Update npm start script
  • Create an environment variable for API URL
  • Generate a new service via the CLI
  • Write a method to make an HTTP request
  • Write interfaces to describe response object and restaurant object

Problem 1: Write a Restaurant Service to Fetch a List of Restaurants

We want to create an Angular service with a method that will get a list of restaurants from our Place My Order API.

P1: What you need to know

To complete this problem, you’ll need to know:

  • The basics of Angular Services.
  • How to generate a service.
  • How to inject HttpClientModule into your app.
  • How to use HttpClient to make an HTTP request in a service.

Angular Service Basics

Angular Services are pieces of functionality that may not need to be tied to a view like components. A common example of a service is making an HTTP request to get data. Many components may require functionality to fetch data, and a Service can help abstract that logic into one place to be used across components.

The following example shows a UsersService with methods on it that return a list of users and get a user by ID, and shows how the UsersService is injected into the AppComponent and calls the getUsers to get the list of users to display in the template.

<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/router@7.2.0/bundles/router.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>
<base href="/">
<my-app></my-app>
<script type="typescript">
const { Component, NgModule, VERSION, OnInit, Injectable } = ng.core;
const { BrowserModule } = ng.platformBrowser;
const { CommonModule } = ng.common;
const { Routes, RouterModule } = ng.router;

@Injectable({
  providedIn: 'root'
})
class UsersService {
  private users = [
  {
    name: 'Jennifer',
    id: 1,
    role: 'admin'
  },
  {
    name: 'Steve',
    id: 2,
    role: 'user'
  },
  {
    name: 'Alice',
    id: 3,
    role: 'developer'
  }]

  constructor() { }

  getUsers() {
    return this.users;
  }

  getUser(id: number) {
    return this.users.find(x => x.id === id)
  }
}

@Component({
  selector: 'my-app',
  template: `
    <ul class="nav">
      <li routerLinkActive="active">
        <a routerLink="/about">About</a>
      </li>
    </ul>
    <router-outlet></router-outlet>
  `
})
class AppComponent {
  constructor() {}
}

@Component({
  selector: 'about-component',
  template: `
    <p>An about component!</p>
  `
})
class AboutComponent {
  constructor() {
  }
}

@Component({
  selector: 'home-component',
  template: `
    <p>A home component!</p>
    <ul>
      <li *ngFor="let user of users">
        {{user.name}}
      </li>
    </ul>
  `
})
class HomeComponent implements OnInit{
  private users: any[] = [];

  constructor(private usersService: UsersService) {
  
  }

  ngOnInit() {
    this.users = this.usersService.getUsers();
  }
}
//THIS IS A HACK JUST FOR CODEPEN TO WORK
HomeComponent.parameters = [UsersService];

const routes: Routes = [
  { path: 'about', component: AboutComponent },
  { path: '**', component: HomeComponent }
]
@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
class AppRoutingModule { }

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

const { platformBrowserDynamic } = ng.platformBrowserDynamic;

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


Generating a service

To generate a UsersService, you run:

ng g service users

This will create a src/app/users.service.ts file and associated spec test file.

Hint: You can generate a service in a folder ng g service folder/users

Injectable

Injectable is an Angular decorator that makes the class it’s decorating available to Angular’s Injector for creation. In the case of creating service to get data to use in our application, we want those services to be able to be injected into the app components we need the services in.

Angular uses the injector to create dependencies using providers - which know how to create said dependencies. We can then inject our service into our components constructor to take advantage of Angular’s dependency injection pattern.

Importing HttpClientModule into app.module.ts

For making HTTP requests to interact with an API, Angular provides HttpClient Module. To use it we’ll need to import it in the root module of our app and include it the imports array.

src/app/app.module.ts

import { HttpClientModule } from '@angular/common/http';
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

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';

@NgModule({
  declarations: [
    AppComponent,
    HomeComponent,
    RestaurantComponent,
    ImageUrlPipe
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    HttpClientModule,
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Using HttpClient to Make a Request

HttpClient is a class with methods for making HTTP requests. Methods will return RxJS Observables.

<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/router@7.2.0/bundles/router.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>
<base href="/">
<my-app></my-app>
<script type="typescript">
const { Component, NgModule, VERSION, OnInit, Injectable } = ng.core;
const { BrowserModule } = ng.platformBrowser;
const { CommonModule, HttpClient } = ng.common;
const { Routes, RouterModule } = ng.router;

@Injectable({
  providedIn: 'root'
})
class UsersService {

  constructor(private httpClient: HttpClient) { }

  getUsers() {
    return this.httpClient.get<any>('/api/users');
  }

  getUser(id: number) {
    return this.httpClient.get<any>('/api/users/' + id);
  }
}


@Component({
  selector: 'my-app',
  template: `
    <ul class="nav">
      <li routerLinkActive="active">
        <a routerLink="/about">About</a>
      </li>
    </ul>
    <router-outlet></router-outlet>
  `
})
class AppComponent {
  constructor() {}

}

@Component({
  selector: 'about-component',
  template: `
    <p>An about component!</p>
  `
})
class AboutComponent {
  constructor() {
  }
}


@Component({
  selector: 'home-component',
  template: `
    <p>A home component!</p>
    <ul>
      <li *ngFor="let user of users">
        {{user.name}}
      </li>
    </ul>
  `
})
class HomeComponent implements OnInit{
  private users: any[] = [];

  constructor(private usersService: UsersService) {}

  ngOnInit() {
    this.users = usersService.getUsers();
  }
}
//THIS IS A HACK JUST FOR CODEPEN TO WORK
HomeComponent.parameters = [UsersService];

const routes: Routes = [
  { path: 'about', component: AboutComponent },
  { path: '**', component: HomeComponent }
]
@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
class AppRoutingModule { }

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

const { platformBrowserDynamic } = ng.platformBrowserDynamic;

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


This tutorial won’t cover RxJS in depth, but it’s worth being aware of Angular’s heavy use of it. Check out our Learn RxJS tutorial for more information.

P1: Technical requirements

Write a RestaurantService with a method getRestaurants that uses httpClient to get a list of restaurants from an environment variable + /restaurants. For example, we could get restaurants like:

const httpClient = new HttpClient();
const restaurantService = new RestaurantService(httpClient);

restaurantService.getRestaurants(); //-> Observable<Array<Object>>

Note:

  • getRestaurants will return an RxJS observable that emits an array of restaurants.
  • Typically, services and HttpClient are injected into components and not created as shown above.
  • We want to create RestaurantService in src/app/restaurant/restaurant.service.ts.

P1: Setup

Before we begin making services, we must:

  • Install the place-my-order API
  • Create an environment variable to point to the API

Installing the Place My Order API

We’ve done some work to create a Place My Order API for use in this app by creating an npm package that will generate fake restaurant data and serve it from port 7070.

✏️ Run:

npm install place-my-order-api@1

✏️ Next add an API script to your package.json

{
  "name": "place-my-order",
  "version": "0.0.0",
  "scripts": {
    "ng": "ng",
    "start": "ng serve",
    "api": "place-my-order-api --port 7070",
    "build": "ng build",
    "watch": "ng build --watch --configuration development",
    "test": "ng test"
  },
  "private": true,
  "dependencies": {
    "@angular/animations": "^17.1.0",
    "@angular/common": "^17.1.0",
    "@angular/compiler": "^17.1.0",
    "@angular/core": "^17.1.0",
    "@angular/forms": "^17.1.0",
    "@angular/platform-browser": "^17.1.0",
    "@angular/platform-browser-dynamic": "^17.1.0",
    "@angular/router": "^17.1.0",
    "place-my-order-api": "^1.3.0",
    "place-my-order-assets": "^0.2.2",
    "rxjs": "~7.8.0",
    "tslib": "^2.3.0",
    "zone.js": "~0.14.3"
  },
  "devDependencies": {
    "@angular-devkit/build-angular": "^17.1.0",
    "@angular/cli": "^17.1.0",
    "@angular/compiler-cli": "^17.1.0",
    "@types/jasmine": "~5.1.0",
    "jasmine-core": "~5.1.0",
    "karma": "~6.4.0",
    "karma-chrome-launcher": "~3.2.0",
    "karma-coverage": "~2.2.0",
    "karma-jasmine": "~5.1.0",
    "karma-jasmine-html-reporter": "~2.1.0",
    "typescript": "~5.3.2"
  }
}

✏️ In new terminal window, start the API server by running:

npm run api

Double check the api by navigating to localhost:7070/restaurants. You should see a JSON list of restaurant data. It will be helpful to have a second terminal tab to run the api command from.

Create an Environment Variable

The way we’re accessing our locally run API during development may be different than how we access it in production. To prepare for this, we’ll set an environment variable to do what we need.

✏️ To generate the environment files, run:

ng generate environments

Before v15, Angular used to generate the environment files with ng new command.

The command will generate two files, src/environments/environment.ts and src/environments/environment.development.ts, with the following content:

export const environment = {};

When developing locally Angular will use the environment.development.ts file, but when we create a production build the environment.ts file will be used. Update environment.ts and environment.development.ts files to include a production key and an apiUrl key with the value of where our API is being served from: http://localhost:7070.

✏️ Update src/environments/environment.ts:

export const environment = {
  production: true,
  apiUrl: 'http://localhost:7070',
};

✏️ Update src/environments/environment.development.ts:

export const environment = {
  production: false,
  apiUrl: 'http://localhost:7070',
};

Now generate the restaurant service:

✏️ Run

ng g service restaurant/restaurant

✏️ Update src/app/restaurant/restaurant.service.ts:

import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { environment } from '../../environments/environment';

@Injectable({
  providedIn: 'root'
})
export class RestaurantService {

  constructor() { }
}

Having issues with your local setup?

You can get through most of this tutorial by using an online code editor. You won’t be able to run our tests to verify your solution, but you will be able to make changes to your app and see them live.

You can use one of these two online editors:

P1: How to verify your solution is correct

✏️ Update the spec file src/app/restaurant/restaurant.service.spec.ts to be:

import {
  HttpClientTestingModule,
  HttpTestingController,
} from '@angular/common/http/testing';
import { TestBed } from '@angular/core/testing';

import { RestaurantService } from './restaurant.service';

describe('RestaurantService', () => {
  let httpTestingController: HttpTestingController;
  let service: RestaurantService;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [HttpClientTestingModule],
      providers: [RestaurantService],
    });

    httpTestingController = TestBed.inject(HttpTestingController);
    service = TestBed.inject(RestaurantService);
  });

  it('should be created', () => {
    expect(service).toBeTruthy();
  });

  it('should make a GET request to restaurants', () => {
    const mockRestaurants = {
      data: [
        {
          name: 'Brunch Place',
          slug: 'brunch-place',
          images: {
            thumbnail:
              'node_modules/place-my-order-assets/images/4-thumbnail.jpg',
            owner: 'node_modules/place-my-order-assets/images/2-owner.jpg',
            banner: 'node_modules/place-my-order-assets/images/2-banner.jpg',
          },
          menu: {
            lunch: [
              { name: 'Ricotta Gnocchi', price: 15.99 },
              { name: 'Garlic Fries', price: 15.99 },
              { name: 'Charred Octopus', price: 25.99 },
            ],
            dinner: [
              { name: 'Steamed Mussels', price: 21.99 },
              { name: 'Roasted Salmon', price: 23.99 },
              { name: 'Crab Pancakes with Sorrel Syrup', price: 35.99 },
            ],
          },
          address: {
            street: '2451 W Washburne Ave',
            city: 'Ann Arbor',
            state: 'MI',
            zip: '53295',
          },
          _id: 'xugqxQIX5rPJTLBv',
        },
        {
          name: 'Taco Joint',
          slug: 'taco-joint',
          images: {
            thumbnail:
              'node_modules/place-my-order-assets/images/4-thumbnail.jpg',
            owner: 'node_modules/place-my-order-assets/images/2-owner.jpg',
            banner: 'node_modules/place-my-order-assets/images/2-banner.jpg',
          },
          menu: {
            lunch: [
              { name: 'Beef Tacos', price: 15.99 },
              { name: 'Chicken Tacos', price: 15.99 },
              { name: 'Guacamole', price: 25.99 },
            ],
            dinner: [
              { name: 'Shrimp Tacos', price: 21.99 },
              { name: 'Chicken Enchilada', price: 23.99 },
              { name: 'Elotes', price: 35.99 },
            ],
          },
          address: {
            street: '13 N 21st St',
            city: 'Chicago',
            state: 'IL',
            zip: '53295',
          },
          _id: 'xugqxQIX5dfgdgTLBv',
        },
      ],
    };

    service.getRestaurants().subscribe((restaurants: any) => {
      expect(restaurants).toEqual(mockRestaurants);
    });

    const url = 'http://localhost:7070/restaurants';
    const req = httpTestingController.expectOne(url);

    expect(req.request.method).toEqual('GET');
    req.flush(mockRestaurants);

    httpTestingController.verify();
  });
});

✏️ Quit the previous tests running and restart them:

npm run test

P1: Solution

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

Click to see the solution ✏️ Update src/app/app.module.ts to inject the HttpClientModule:

import { HttpClientModule } from '@angular/common/http';
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

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';

@NgModule({
  declarations: [
    AppComponent,
    HomeComponent,
    RestaurantComponent,
    ImageUrlPipe
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    HttpClientModule,
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

✏️ Update src/app/restaurant/restaurant.service.ts to make a request to the API server /restaurants:

import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { environment } from '../../environments/environment';

@Injectable({
  providedIn: 'root'
})
export class RestaurantService {

  constructor(private httpClient: HttpClient) {}

  getRestaurants(): Observable<any> {
    return this.httpClient.get<any>(environment.apiUrl + '/restaurants');
  }
}

Problem 2: Write an Interface to Describe the Restaurant Object and Data Response

Currently, from TypeScript’s perspective, getRestaurants() can return anything. This means if we use the data from getRestaurants(), TypeScript will not be able to notice any mistakes. This undermines the whole point of TypeScript!

P2: What you need to know

To solve this problem, you’ll need to:

  • Understand interfaces in TypeScript
  • How to generate an interface with Angular’s CLI.

Interfaces in TypeScript

Thanks to TypeScript we can write interfaces to describe what we expect objects to look like. Consider the user service below returning an array of users. This interface describes what a user (and array of users) should look like:

<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/router@7.2.0/bundles/router.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>
<base href="/">
<my-app></my-app>
<script type="typescript">
const { Component, NgModule, VERSION, OnInit, Injectable } = ng.core;
const { BrowserModule } = ng.platformBrowser;
const { CommonModule } = ng.common;
const { Routes, RouterModule } = ng.router;

interface User {
  name: string;
  id: number;
  role: string;
}

@Injectable({
  providedIn: 'root'
})
class UsersService {
  private users: User[] = [
  {
    name: 'Jennifer',
    id: 1,
    role: 'admin'
  },
  {
    name: 'Steve',
    id: 2,
    role: 'user'
  },
  {
    name: 'Alice',
    id: 3,
    role: 'developer'
  }]

  constructor() { }

  getUsers() {
    return this.users;
  }

  getUser(id: number) {
    return this.users.find(x => x.id === id)
  }
}

@Component({
  selector: 'my-app',
  template: `
    <ul class="nav">
      <li routerLinkActive="active">
        <a routerLink="/about">About</a>
      </li>
    </ul>
    <router-outlet></router-outlet>
  `
})
class AppComponent {
  constructor() {}

}

@Component({
  selector: 'about-component',
  template: `
    <p>An about component!</p>
  `
})
class AboutComponent {
  constructor() {
  }
}


@Component({
  selector: 'home-component',
  template: `
    <p>A home component!</p>
    <ul>
      <li *ngFor="let user of users">
        {{user.name}}
      </li>
    </ul>
  `
})
class HomeComponent implements OnInit{
  private users: User[] = [];

  constructor(private usersService: UsersService) {}

  ngOnInit() {
    this.users = this.usersService.getUsers();
  }
}
//THIS IS A HACK JUST FOR CODEPEN TO WORK
HomeComponent.parameters = [UsersService];

const routes: Routes = [
  { path: 'about', component: AboutComponent },
  { path: '**', component: HomeComponent }
]
@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
class AppRoutingModule { }

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

const { platformBrowserDynamic } = ng.platformBrowserDynamic;

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


Arrays can also be written as:

users: Array<User>

To learn more about interfaces in TypeScript, check out our TypeScript guide.

Generate an Interface

Use the following to generate an interface with the CLI:

ng g interface user

This will generate:

export interface User {}

P2: Technical requirements

Write interfaces to tell TypeScript what we expect restaurant and other related objects to look like and use them in our restaurant service. A Restaurant interface should represent an object like this:

let restaurant = {
  name: '', //string
  slug: '', //string
  images: {
    thumbnail: '', //string
    owner: '', //string
    banner: '', //string
  },
  menu: {
    lunch: [
      {
        name: '', //string
        price: '', //number
      },
    ],
    dinner: [
      {
        name: '', //string
        price: '', //number
      },
    ],
  },
  address: {
    street: '', //string
    city: '', //string
    state: '', //string
    zip: '', //string
  },
  _id: '', //string
};

This interface should be written in the src/app/restaurant/restaurant.ts file.

P2: Setup

We’ve already written a ResponseData interface that will take an array of restaurants for you. Here’s the code to get you started:

✏️ Generate the restaurant interface:

ng g interface restaurant/restaurant

✏️ Update src/app/restaurant/restaurant.ts with some starter code that includes some scaffolding for some of the sub-interfaces within the Restaurant interfaces:

interface Item {
  name: string;
}

interface Menu {
  lunch: Item[];
}

interface Address {
  street: string;
}

interface Images {
  thumbnail: string;
}

export interface Restaurant {
  name: string;
}

✏️ Update src/app/restaurant/restaurant.service.ts to import the Restaurant interface, use it within the ResponseData interface which is used by httpClient.get:

import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { environment } from '../../environments/environment';
import { Restaurant } from './restaurant';

export interface ResponseData {
  data: Restaurant[];
}

@Injectable({
  providedIn: 'root'
})
export class RestaurantService {

  constructor(private httpClient: HttpClient) {}

  getRestaurants(): Observable<ResponseData> {
    return this.httpClient.get<ResponseData>(
      environment.apiUrl + '/restaurants'
    );
  }
}

P2: How to verify your solution is correct

✏️ Update the spec file src/app/restaurant/restaurant.service.spec.ts to be:

import {
  HttpClientTestingModule,
  HttpTestingController,
} from '@angular/common/http/testing';
import { TestBed } from '@angular/core/testing';
import { Restaurant } from './restaurant';
import { ResponseData, RestaurantService } from './restaurant.service';

describe('RestaurantService', () => {
  let httpTestingController: HttpTestingController;
  let service: RestaurantService;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [HttpClientTestingModule],
    });

    httpTestingController = TestBed.inject(HttpTestingController);
    service = TestBed.inject(RestaurantService);
  });

  it('should be created', () => {
    expect(service).toBeTruthy();
  });

  it('should make a GET request to restaurants', () => {
    const mockRestaurants = {
      data: [
        {
          name: 'Brunch Place',
          slug: 'brunch-place',
          images: {
            thumbnail:
              'node_modules/place-my-order-assets/images/4-thumbnail.jpg',
            owner: 'node_modules/place-my-order-assets/images/2-owner.jpg',
            banner: 'node_modules/place-my-order-assets/images/2-banner.jpg',
          },
          menu: {
            lunch: [
              { name: 'Ricotta Gnocchi', price: 15.99 },
              { name: 'Garlic Fries', price: 15.99 },
              { name: 'Charred Octopus', price: 25.99 },
            ],
            dinner: [
              { name: 'Steamed Mussels', price: 21.99 },
              { name: 'Roasted Salmon', price: 23.99 },
              { name: 'Crab Pancakes with Sorrel Syrup', price: 35.99 },
            ],
          },
          address: {
            street: '2451 W Washburne Ave',
            city: 'Ann Arbor',
            state: 'MI',
            zip: '53295',
          },
          _id: 'xugqxQIX5rPJTLBv',
        },
        {
          name: 'Taco Joint',
          slug: 'taco-joint',
          images: {
            thumbnail:
              'node_modules/place-my-order-assets/images/4-thumbnail.jpg',
            owner: 'node_modules/place-my-order-assets/images/2-owner.jpg',
            banner: 'node_modules/place-my-order-assets/images/2-banner.jpg',
          },
          menu: {
            lunch: [
              { name: 'Beef Tacos', price: 15.99 },
              { name: 'Chicken Tacos', price: 15.99 },
              { name: 'Guacamole', price: 25.99 },
            ],
            dinner: [
              { name: 'Shrimp Tacos', price: 21.99 },
              { name: 'Chicken Enchilada', price: 23.99 },
              { name: 'Elotes', price: 35.99 },
            ],
          },
          address: {
            street: '13 N 21st St',
            city: 'Chicago',
            state: 'IL',
            zip: '53295',
          },
          _id: 'xugqxQIX5dfgdgTLBv',
        },
      ],
    };

    service.getRestaurants().subscribe((restaurants: ResponseData) => {
      expect(restaurants).toEqual(mockRestaurants);
    });

    const url = 'http://localhost:7070/restaurants';
    const req = httpTestingController.expectOne(url);

    expect(req.request.method).toEqual('GET');
    req.flush(mockRestaurants);

    httpTestingController.verify();
  });

  it('can set proper properties on restaurant type', () => {
    const restaurant: Restaurant = {
      name: 'Taco Joint',
      slug: 'taco-joint',
      images: {
        thumbnail: 'node_modules/place-my-order-assets/images/4-thumbnail.jpg',
        owner: 'node_modules/place-my-order-assets/images/2-owner.jpg',
        banner: 'node_modules/place-my-order-assets/images/2-banner.jpg',
      },
      menu: {
        lunch: [
          { name: 'Beef Tacos', price: 15.99 },
          { name: 'Chicken Tacos', price: 15.99 },
          { name: 'Guacamole', price: 25.99 },
        ],
        dinner: [
          { name: 'Shrimp Tacos', price: 21.99 },
          { name: 'Chicken Enchilada', price: 23.99 },
          { name: 'Elotes', price: 35.99 },
        ],
      },
      address: {
        street: '13 N 21st St',
        city: 'Chicago',
        state: 'IL',
        zip: '53295',
      },
      _id: 'xugqxQIX5dfgdgTLBv',
    };
    // will error if interface isn’t implemented correctly
    expect(true).toBe(true);
  });
});

If you’ve implemented the solution correctly, when you run npm run test all tests will pass! If you haven’t written the interfaces correctly, you’ll see a compile error before the tests runs. You might need to restart the test script to see the compile error.

P2: Solution

Click to see the solution ✏️ Update src/app/restaurant/restaurant.ts to:

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

interface Menu {
  lunch: Item[];
  dinner: Item[];
}

interface Address {
  street: string;
  city: string;
  state: string;
  zip: string;
}

interface Images {
  thumbnail: string;
  owner: string;
  banner: string;
}

export interface Restaurant {
  name: string;
  slug: string;
  images: Images;
  menu: Menu;
  address: Address;
  _id: string;
}

In the next step we’ll call the getRestaurants method in our component to get the list of restaurants.