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
insrc/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, the tests will pass when you run npm run test
!
Click to see the solution
✏️ Update src/app/app.module.ts to inject theHttpClientModule
:
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, the tests will pass when you run npm run test
! 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.