- Node.js/npm
- Visual Code
- Express REST API (fruits-rest-api) running on port 3000.
- Create a new folder for this application and open a new terminal and cd into this folder.
- Type
npm i @angular/cli -g
. (This can take several minutes).In case you are using PowerShell, you need to set a policy
Set-ExecutionPolicy -ExecutionPolicy Unrestricted
- Create a new Angular app using the following command:
ng new frontend-fruits
. When prompted for Angular routing answer N. Then chose CSS Stylesheet. (This can take several minutes). - Once the installation is done, cd into
frontend-fruits
folder and then start the server usingng serve
. This will compile and build the site. - Browse the site with
http://localhost:4200
to get the default page. - You can define specific port using
ng serve --host 0.0.0.0 --port 8080
.
- Type
code .
in your terminal to open Visual Code. - Add
HttpClient
class to access the REST backend API. Navigate throughsrc/app/app.module.ts
and open this file and import the class as followed:import { HttpClientModule } from '@angular/common/http';
- Add the module
HttpClientModule
in@NgModule
imports section.@NgModule({ declarations: [ AppComponent ], imports: [ BrowserModule, HttpClientModule ], providers: [], bootstrap: [AppComponent] })
-
Type
ng g service app
in the terminal. This will create two files under the Angular projectsrc/app/app.service.spec.ts
andsrc/app/app.service.ts
. -
Then create a class with this command
ng g cl fruit
, this will create two files as wellsrc/app/fruit.spec.ts
andsrc/app/fruit.ts
. -
Remove
src/app/fruit.spec.ts
, we don't need this file for now. -
Open file
src/app/fruit.ts
and add the following code:export class Fruit { public id: number; public title: string; constructor(id: number, title: string) { this.id = id; this.title = title; } }
-
Edit file
src/app/app.service.ts
and import HttpClient class from '@angular/common/http';import { HttpClient } from '@angular/common/http';
-
Replace the existing constructor passing the HttpClient object:
constructor(private http: HttpClient) { }
-
Declare a variable before the constructor pointing to the REST API endpoint:
private API_ENDPOINT='http://localhost:3000';
-
Import the fruits model in the top with:
import { Fruit } from "./fruit";
-
Use an Observable, this is used frequently in Angular and it is a technique for event handling, asynchronous programming, and handling multiple values, import the module using:
import { Observable } from 'rxjs';
-
Add the following method inside AppService class to implement the Observable:
getFruits(): Observable<Fruit> { return this.http.get<Fruit>(this.API_ENDPOINT + '/fruits') }
-
The final structure should be like this:
import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { Fruit } from "./fruit"; import { Observable } from 'rxjs'; @Injectable({ providedIn: 'root' }) export class AppService { private API_ENDPOINT='http://localhost:3000'; constructor(private http: HttpClient) { } getFruits(): Observable<Fruit> { return this.http.get<Fruit>(this.API_ENDPOINT + '/fruits') } }
-
Modify
src/app/app.component.ts
and import the service with:import { AppService } from './app.service';
-
Add
OnInit
class to the existing '@angular/core' line:import { Component, OnInit } from '@angular/core';
-
Replace the AppComponent class with the following code:
export class AppComponent implements OnInit { title = 'frontend-fruits'; fruits: any = []; constructor(private appService: AppService) { } ngOnInit() { this.getFruits(); } getFruits() { return this.appService.getFruits().subscribe((data: {}) => { this.fruits = data; }) } }
This code is calling the service on the ngOnInit() method and passing the result into an array object.
-
Modify
src/app/app.component.html
and replace the existing html with the following:<div class="container"> <br/> <h2>Fruits List</h2> <table class="table table-bordered"> <thead> <tr> <th scope="col">#Id</th> <th scope="col">Title</th> </tr> </thead> <tbody> <tr *ngFor="let fruit of fruits"> <td>{{ fruit.id }}</td> <td>{{ fruit.title }}</td> </tr> </tbody> </table> </div>
Use ngFor to iterate in the array object and access the fields.
-
Save all and browse to
http://localhost:4200
to review the changes.
-
To implement a http post, open
src/app/app.service.ts
and import this class:import { HttpHeaders } from '@angular/common/http';
-
Create a httpOptions object after the imported modules and before Injectable section:
const httpOptions = { headers: new HttpHeaders({ 'Content-Type': 'application/json' }) };
-
Then add the following post method:
addFruit(fruit: Fruit): Observable<Fruit> { return this.http.post<Fruit>(this.API_ENDPOINT + '/fruits', fruit, httpOptions) }
-
Final structure of this file will be like this:
import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { Fruit } from "./fruit"; import { Observable } from 'rxjs'; import { HttpHeaders } from '@angular/common/http'; const httpOptions = { headers: new HttpHeaders({ 'Content-Type': 'application/json' }) }; @Injectable({ providedIn: 'root' }) export class AppService { private API_ENDPOINT='http://localhost:3000'; constructor(private http: HttpClient) { } getFruits(): Observable<Fruit> { return this.http.get<Fruit>(this.API_ENDPOINT + '/fruits') } addFruit(fruit: Fruit): Observable<Fruit> { return this.http.post<Fruit>(this.API_ENDPOINT + '/fruits', fruit, httpOptions) } }
-
Modify
src/app/app.module.ts
and import the following modules in the top:import { FormsModule, ReactiveFormsModule } from '@angular/forms';
-
Then add these modules inside @NgModule imports section as followed:
@NgModule({ declarations: [ AppComponent ], imports: [ BrowserModule, HttpClientModule, FormsModule, ReactiveFormsModule ], providers: [], bootstrap: [AppComponent] })
-
Open the file
src/app/app.component.ts
and importFormGroup
andFormControl
clases from Angular forms.import { FormGroup, FormControl } from '@angular/forms';
-
Create a FormGroup to track the value and validity state of a group of FormControl.
title = 'frontend-fruits'; fruits: any = []; fruitsForm = new FormGroup({ id: new FormControl(''), title: new FormControl('') });
-
Add the following method to pass the form values to the service on the submit button event.
addFruit(){ this.appService.addFruit(this.fruitsForm.value) .subscribe(fruit => { this.fruits.push(fruit); this.getFruits(); this.fruitsForm.reset(); }); }
-
Final structure of this document will be like this:
import { Component, OnInit } from '@angular/core'; import { AppService } from './app.service'; import { FormGroup, FormControl } from '@angular/forms'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent implements OnInit { title = 'frontend-fruits'; fruits: any = []; fruitsForm = new FormGroup({ id: new FormControl(''), title: new FormControl('') }); constructor(private appService: AppService) { } ngOnInit() { this.getFruits(); } getFruits() { return this.appService.getFruits().subscribe((data: {}) => { this.fruits = data; }) } addFruit(){ this.appService.addFruit(this.fruitsForm.value) .subscribe(fruit => { this.fruits.push(fruit); this.getFruits(); this.fruitsForm.reset(); }); } }
-
Open
src/app/app.component.html
and add the following section at the end of the existing content:<div class="container"> <form (ngSubmit)="addFruit()" [formGroup]="fruitsForm"> <div class="form-group col-md-4"> <label for="id">Id:</label> <input formControlName="id" class="form-control" id="id" placeholder="Enter id"><br/> <label for="title">Title:</label> <input formControlName="title" class="form-control" id="title" placeholder="Enter title"><br/> <button type="submit" class="btn btn-primary">Submit</button> </div> </form> </div>
This form pattern is using ReactiveForms https://angular.io/guide/reactive-forms
-
Save all and browse to
http://localhost:4200
to review the changes.
Note for Angular 14 version:
If you are getting issues when passing this.fruitsForm.value
and you are using Angular 14, previous method will fail because reactive forms in Angular 14 are strictly typed by default.
To fix this scenario, you need to modify the current FormGroup and add a nonNullable parameter:
fruitsForm = new FormGroup({
id: new FormControl(0, { nonNullable: true }),
title: new FormControl('', {nonNullable: true})
});
And then cast the forms value into Fruit, and for this you need first to import Fruit with: import { Fruit } from './fruit';
addFruit(){
this.appService.addFruit(this.fruitsForm.value as Fruit)
.subscribe(fruit => {
this.fruits.push(fruit);
this.getFruits();
this.fruitsForm.reset();
});
}
There are several ways to implement CSS specially for Bootstrap, in this lab will be taking the easy one.
- Open
src/styles.css
and add this line:@import url('https://unpkg.com/[email protected]/dist/css/bootstrap.min.css');
- Save all and browse to
http://localhost:4200
to review the changes.