Angular 9 Tutorial: Learn to Build a CRUD Angular App Quickly

栏目: IT技术 · 发布时间: 4年前

内容简介:In this Angular 9 tutorial, we will create an Angular app of Coronavirus cases which basically implementing CRUD operation. This app will use REST API that gets the required data, post the case data, put case changes, and delete case data. So, we will lear

In this Angular 9 tutorial, we will create an Angular app of Coronavirus cases which basically implementing CRUD operation. This app will use REST API that gets the required data, post the case data, put case changes, and delete case data. So, we will learn how to use HTTPClient to access REST API, using Angular Form, using Routing and Navigation, and create a simple Calendar.

This tutorial divided into several steps:

  • Step #1. Create a New Angular 9 App
  • Step #2. Add the Angular 9 Routing and Navigation
  • Step #3. Add the Angular 9 Service
  • Step #4. Display List of Data using Angular Material
  • Step #5. Show and Delete Data Details using Angular Material
  • Step #6. Show Statistic using Ng2Charts and Chart.js
  • Step #7. Add a Data using Angular Material Form
  • Step #8. Edit a Data using Angular Material Form
  • Step #9. Run and Test the Angular 9 Coronavirus Cases App

In the end, the final Angular 9 Coronavirus Cases app will look like this.

Angular 9 Tutorial: Learn to Build a CRUD Angular App Quickly Angular 9 Tutorial: Learn to Build a CRUD Angular App Quickly Angular 9 Tutorial: Learn to Build a CRUD Angular App Quickly Angular 9 Tutorial: Learn to Build a CRUD Angular App Quickly Angular 9 Tutorial: Learn to Build a CRUD Angular App Quickly

The following tools, frameworks, and modules are required for this tutorial:

  1. Node.js (recommended version)
  2. Angular 9
  3. Coronavirus REST API
  4. Terminal (Mac/Linux) or Node Command Line (Windows)
  5. IDE or Text Editor (We are using Visual Studio Code)

We already provided the REST API for the Polyclinic reservation, you can just clone, install NPM, run MongoDB server, and Run the Express/MongoDB REST API server. We assume that you have installed Node.js. Now, we need to check the Node.js and NPM versions. Open the terminal or Node command line then type these commands.

node -v
v10.15.1
npm -v
6.13.6

Step #1. Create a New Angular 9 App

We will create a new Angular 9 App using Angular CLI. For that, we need to install or update the @angular/cli first to the latest version.

sudo npm install -g @angular/cli

Next, create a new Angular 9 app by running this command.

ng new coronavirus-cases

If you get the question like below, choose `Yes` and `SCSS` (or whatever you like to choose).

? Would you like to add Angular routing? Yes
? Which stylesheet format would you like to use? SCSS   [ https://sass-lang.com/
documentation/syntax#scss                ]

Next, go to the newly created Angular 9 project folder.

cd coronavirus-cases

Open this Angular project with your IDE or Text editor. To use VSCode type this command.

code .

Type this command to run the Angular 9 app for the first time.

ng serve --open

Using the "--open" parameter will automatically open this Angular 9 app in the default browser. Now, the Angular initial app looks like this.

Angular 9 Tutorial: Learn to Build a CRUD Angular App Quickly

Step #2. Add the Angular 9 Routing and Navigation

As you see in the first step of creating an Angular 9 app. We already add the Angular Routing for this Angular 9 app. Next, we just add the required Angular components for this Coronavirus cases app. Just type these commands to generate them.

ng g component cases
ng g component cases-details
ng g component add-cases
ng g component edit-cases 
ng g component cases-stat

Those components will automatically be registered to the app.module.ts. Next, open and edit `src/app/app-routing.module.ts` then add these imports.

import { CasesComponent } from './cases/cases.component';
import { CasesDetailsComponent } from './cases-details/cases-details.component';
import { CasesStatComponent } from './cases-stat/cases-stat.component';
import { AddCasesComponent } from './add-cases/add-cases.component';
import { EditCasesComponent } from './edit-cases/edit-cases.component';

Add these arrays to the existing routes constant that contain route for above-added components.

const routes: Routes = [
  {
    path: 'cases',
    component: CasesComponent,
    data: { title: 'List of Cases' }
  },
  {
    path: 'cases-details/:id',
    component: CasesDetailsComponent,
    data: { title: 'Cases Details' }
  },
  {
    path: 'cases-stat',
    component: CasesStatComponent,
    data: { title: 'Cases Statistic' }
  },
  {
    path: 'add-cases',
    component: AddCasesComponent,
    data: { title: 'Add Cases' }
  },
  {
    path: 'edit-cases/:id',
    component: EditCasesComponent,
    data: { title: 'Edit Cases' }
  },
  { path: '',
    redirectTo: '/cases',
    pathMatch: 'full'
  }
];

Open and edit `src/app/app.component.html` and you will see the existing router outlet. Next, modify this HTML page to fit the CRUD page.

<div>
  <router-outlet></router-outlet>
</div>

Open and edit `src/app/app.component.scss` then replace all SASS codes with this.

.container {
  padding: 20px;
}

Step #3. Add the Angular 9 Service

All-access (POST, GET, PUT, DELETE) to the REST API will put in the Angular 9 Service. The response from the REST API emitted by Observable that can subscribe and read from the Components. Before creating a service for REST API access, first, we have to install or register `HttpClientModule`. Open and edit `src/app/app.module.ts` then add these imports of FormsModule, ReactiveFormsModule (@angular/forms) and HttpClientModule (@angular/common/http).

import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';

Add it to `@NgModule` imports after `BrowserModule`.

imports: [
  BrowserModule,
  FormsModule,
  ReactiveFormsModule,
  HttpClientModule,
  AppRoutingModule
],

We will use the type specifier to get a typed result object. For that, create a new Typescript file `src/app/cases.ts` then add these lines of Typescript codes.

export class Cases {
  _id: string;
  name: string;
  gender: string;
  age: number;
  address: string;
  city: string;
  country: string;
  status: string;
  updated: Date;
}

And create a new Typescript file `src/app/statistic.ts` then add these lines of Typescript codes.

export class Statistic {
  _id: any;
  count: number;
}

Next, generate an Angular 9 service by typing this command.

ng g service api

Next, open and edit `src/app/api.service.ts` then add these imports.

import { Observable, of, throwError } from 'rxjs';
import { HttpClient, HttpHeaders, HttpErrorResponse } from '@angular/common/http';
import { catchError, tap, map } from 'rxjs/operators';
import { Cases } from './cases';
import { Statistic } from './statistic';

Add these constants before the `@Injectable`.

const httpOptions = {
  headers: new HttpHeaders({'Content-Type': 'application/json'})
};
const apiUrl = '/api/';

Inject the `HttpClient` module to the constructor.

constructor(private http: HttpClient) { }

Add the error handler function that returns as an Observable.

private handleError<T> (operation = 'operation', result?: T) {
  return (error: any): Observable<T> => {

    // TODO: send the error to remote logging infrastructure
    console.error(error); // log to console instead

    // Let the app keep running by returning an empty result.
    return of(result as T);
  };
}

Add the functions for all CRUD (create, read, update, delete) REST API call of cases and statistic data.

getCases(): Observable<Cases[]> {
    return this.http.get<Cases[]>(`${apiUrl}`)
      .pipe(
        tap(cases => console.log('fetched cases')),
        catchError(this.handleError('getCases', []))
      );
  }

  getCasesById(id: string): Observable<Cases> {
    const url = `${apiUrl}/${id}`;
    return this.http.get<Cases>(url).pipe(
      tap(_ => console.log(`fetched cases id=${id}`)),
      catchError(this.handleError<Cases>(`getCasesById id=${id}`))
    );
  }

  addCases(cases: Cases): Observable<Cases> {
    return this.http.post<Cases>(apiUrl, cases, httpOptions).pipe(
      tap((c: Cases) => console.log(`added product w/ id=${c._id}`)),
      catchError(this.handleError<Cases>('addCases'))
    );
  }

  updateCases(id: string, cases: Cases): Observable<any> {
    const url = `${apiUrl}/${id}`;
    return this.http.put(url, cases, httpOptions).pipe(
      tap(_ => console.log(`updated cases id=${id}`)),
      catchError(this.handleError<any>('updateCases'))
    );
  }

  deleteCases(id: string): Observable<Cases> {
    const url = `${apiUrl}/${id}`;
    return this.http.delete<Cases>(url, httpOptions).pipe(
      tap(_ => console.log(`deleted cases id=${id}`)),
      catchError(this.handleError<Cases>('deleteCases'))
    );
  }

  getStatistic(status: string): Observable<Statistic> {
    const url = `${apiUrl}/daily/${status}`;
    return this.http.get<Statistic>(url).pipe(
      tap(_ => console.log(`fetched statistic status=${status}`)),
      catchError(this.handleError<Statistic>(`getStatistic status=${status}`))
    );
  }

You can find more examples of Angular 9 Observable and RXJShere.

Step #4. Display List of Data using Angular Material

We will display the list of data using the Angular Material Table. The data published from the API service read by subscribing as a Cases model in the Angular 9 component. For that, open and edit `src/app/cases/cases.component.ts` then add this import of the previously created API Service.

import { ApiService } from '../api.service';

Next, inject the API Service to the constructor.

constructor(private api: ApiService) { }

Next, for the user interface (UI) we will use Angular Material and CDK. There's a CLI for generating a Material component like Table as a component, but we will create or add the Table component from scratch to the existing component. Type this command to install Angular Material (@angular/material).

ng add @angular/material

If there are questions like below, just use the default and "Yes" answer.

? Choose a prebuilt theme name, or "custom" for a custom theme: Indigo/Pink        [ Preview: http
s://material.angular.io?theme=indigo-pink ]
? Set up global Angular Material typography styles? Yes
? Set up browser animations for Angular Material? Yes

We will register all required Angular Material components or modules to `src/app/app.module.ts`. Open and edit that file then add these imports of required Angular Material Components.

import { MatInputModule } from '@angular/material/input';
import { MatPaginatorModule } from '@angular/material/paginator';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatSortModule } from '@angular/material/sort';
import { MatTableModule } from '@angular/material/table';
import { MatIconModule } from '@angular/material/icon';
import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatSliderModule } from '@angular/material/slider';
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
import { MatButtonToggleModule } from '@angular/material/button-toggle';
import { MatSelectModule } from '@angular/material/select';

Register the above modules to `@NgModule` imports.

  imports: [
    ...
    MatInputModule,
    MatPaginatorModule,
    MatProgressSpinnerModule,
    MatSortModule,
    MatTableModule,
    MatIconModule,
    MatButtonModule,
    MatCardModule,
    MatFormFieldModule,
    MatSliderModule,
    MatSlideToggleModule,
    MatButtonToggleModule,
    MatSelectModule,
  ],

Next, back to `src/app/cases/cases.component.ts` then add this import.

import { Cases } from '../cases';

Declare the variables of Angular Material Table Data Source before the constructor.

  displayedColumns: string[] = ['name', 'age', 'status'];
  data: Cases[] = [];
  isLoadingResults = true;

Modify the `ngOnInit` function to get a list of cases immediately.

  ngOnInit(): void {
    this.api.getCases()
    .subscribe((res: any) => {
      this.data = res;
      console.log(this.data);
      this.isLoadingResults = false;
    }, err => {
      console.log(err);
      this.isLoadingResults = false;
    });
  }

Next, open and edit `src/app/cases/cases.component.html` then replace all HTML tags with this Angular Material tags.

<div>
  <h2>Corona Virus Cases List</h2>
  <div
       *ngIf="isLoadingResults">
    <mat-spinner *ngIf="isLoadingResults"></mat-spinner>
  </div>
  <div>
    <a mat-flat-button color="primary" [routerLink]="['/add-cases']"><mat-icon>add</mat-icon> Cases</a>
    <a mat-flat-button color="accent" [routerLink]="['/cases-stat']"><mat-icon>bar_chart</mat-icon> Statistic</a>
  </div>
  <div>
    <table mat-table [dataSource]="data"
           matSort matSortActive="name" matSortDisableClear matSortDirection="asc">

      <!-- Cases Name Column -->
      <ng-container matColumnDef="name">
        <th mat-header-cell *matHeaderCellDef>Cases Name</th>
        <td mat-cell *matCellDef="let row">{{row.name}}</td>
      </ng-container>

      <!-- Cases Age Column -->
      <ng-container matColumnDef="age">
        <th mat-header-cell *matHeaderCellDef>Age</th>
        <td mat-cell *matCellDef="let row">{{row.age}}</td>
      </ng-container>

      <!-- Cases Status Column -->
      <ng-container matColumnDef="status">
        <th mat-header-cell *matHeaderCellDef>Status</th>
        <td mat-cell *matCellDef="let row">{{row.status}}</td>
      </ng-container>

      <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
      <tr mat-row *matRowDef="let row; columns: displayedColumns;" [routerLink]="['/cases-details/', row._id]"></tr>
    </table>
  </div>
</div>

Finally, to make a little UI adjustment, open and edit `src/app/cases/cases.component.scss` then add these CSS codes.

/* Structure */
.example-container {
  position: relative;
  padding: 5px;
}

.example-table-contai

Step #5. Show and Delete Data Details using Angular Material

On the list page, there are 2 buttons to navigate to the Details and Statistic page. For, Details page the button action also sends an ID parameter. Next, open and edit `src/app/cases-details/cases-details.component.ts` then add these lines of imports.

import { ActivatedRoute, Router } from '@angular/router';
import { ApiService } from '../api.service';
import { Cases } from '../cases';

Inject the above modules to the constructor.

constructor(private route: ActivatedRoute, private api: ApiService, private router: Router) { }

Declare the variables before the constructor for hold cases data that get from the API.

  cases: Cases = { _id: '', name: '', gender: '', age: null, address: '', city: '', country: '', status: '', updated: null };
  isLoadingResults = true;

Add a function for getting Cases data from the API.

  getCasesDetails(id: string) {
    this.api.getCasesById(id)
      .subscribe((data: any) => {
        this.cases = data;
        console.log(this.cases);
        this.isLoadingResults = false;
      });
  }

Call that function when the component is initiated.

  ngOnInit(): void {
    this.getCasesDetails(this.route.snapshot.params.id);
  }

Add this function to delete a case.

  deleteCases(id: any) {
    this.isLoadingResults = true;
    this.api.deleteCases(id)
      .subscribe(res => {
          this.isLoadingResults = false;
          this.router.navigate(['/cases']);
        }, (err) => {
          console.log(err);
          this.isLoadingResults = false;
        }
      );
  }

For the view, open and edit `src/app/cases-details/cases-details.component.html` then replace all HTML tags with this.

<div>
  <h2>Corona Virus Cases Details</h2>
  <div
       *ngIf="isLoadingResults">
    <mat-spinner *ngIf="isLoadingResults"></mat-spinner>
  </div>
  <div>
    <a mat-flat-button color="primary" [routerLink]="['/cases']"><mat-icon>list</mat-icon></a>
  </div>
  <mat-card>
    <mat-card-header>
      <mat-card-title><h2>{{cases.name}}</h2></mat-card-title>
      <mat-card-subtitle>{{cases.age}} year old</mat-card-subtitle>
    </mat-card-header>
    <mat-card-content>
      <dl>
        <dt>Gender:</dt>
        <dd>{{cases.gender}}</dd>
        <dt>Address:</dt>
        <dd>{{cases.address}}</dd>
        <dt>City:</dt>
        <dd>{{cases.city}}</dd>
        <dt>Country:</dt>
        <dd>{{cases.country}}</dd>
        <dt>Status:</dt>
        <dd><h2>{{cases.status}}</h2></dd>
      </dl>
    </mat-card-content>
    <mat-card-actions>
      <a mat-flat-button color="primary" [routerLink]="['/edit-cases', cases._id]"><mat-icon>edit</mat-icon> Cases</a>
      <a mat-flat-button color="warn" (click)="deleteCases(cases._id)"><mat-icon>delete</mat-icon> Cases</a>
    </mat-card-actions>
  </mat-card>
</div>

Finally, open and edit `src/app/cases-details/cases-details.component.scss` then add this lines of CSS codes.

/* Structure */
.example-container {
  position: relative;
  padding: 5px;
}

.example-loading-shade {
  position: absolute;
  top: 0;
  left: 0;
  bottom: 56px;
  right: 0;
  background: rgba(0, 0, 0, 0.15);
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: center;
}

.mat-flat-button {
  margin: 5px;
}

Step #6. Show Statistic using Ng2Charts and Chart.js

We will use a bar chart to display the statistic of Coronavirus cases. So, we need to install Ng2Charts and Chart.js modules by type this command.

npm i --save ng2-charts chart.js

Next, open and edit `src/app/app.module.ts` then add this import of ng2-charts.

import { ChartsModule } from 'ng2-charts';

Add this module to the @NgModule imports.

  imports: [
    ...
    ChartsModule
  ],

Next, open and edit `src/app/cases-stat/cases-stat.component.ts` then add these imports of chart.js ChartOptions, ChartType, ChartDataSets, ng2-charts Label, ApiService, and Statistic data type.

import { ChartOptions, ChartType, ChartDataSets } from 'chart.js';
import { Label } from 'ng2-charts';
import { ApiService } from '../api.service';
import { Statistic } from '../statistic';

Declare these required variables before the constructor for building a bar chart.

  stats: Statistic[] = [];
  label = 'Positive';
  isLoadingResults = true;
  barChartOptions: ChartOptions = {
    responsive: true,
  };
  barChartLabels: Label[] = [];
  barChartType: ChartType = 'bar';
  barChartLegend = true;
  barChartPlugins = [];
  barChartData: ChartDataSets[] = [{ data: [], backgroundColor: [], label: this.label }];

Inject ApiService to the constructor.

constructor(private api: ApiService) { }

Add a function to load statistic data from REST API then implement it as a bar chart.

  getStatistic(status: string) {
    this.barChartData = [{ data: [], backgroundColor: [], label: this.label }];
    this.barChartLabels = [];
    this.api.getStatistic(status)
    .subscribe((res: any) => {
      this.stats = res;
      const chartdata: number[] = [];
      const chartcolor: string[] = [];
      this.stats.forEach((stat) => {
        this.barChartLabels.push(stat._id.date);
        chartdata.push(stat.count);
        if (this.label === 'Positive') {
          chartcolor.push('rgba(255, 165, 0, 0.5)');
        } else if (this.label === 'Dead') {
          chartcolor.push('rgba(255, 0, 0, 0.5)');
        } else {
          chartcolor.push('rgba(0, 255, 0, 0.5)');
        }
      });
      this.barChartData = [{ data: chartdata, backgroundColor: chartcolor, label: this.label }];
      this.isLoadingResults = false;
    }, err => {
      console.log(err);
      this.isLoadingResults = false;
    });
  }

Call that function to the NgOnInit function.

  ngOnInit(): void {
    this.getStatistic(this.label);
  }

Add a function to switch or reload statistic data by status value.

  changeStatus() {
    this.isLoadingResults = true;
    this.getStatistic(this.label);
  }

Next, open and edit `src/app/cases-stat/cases-stat.component.html` then replace all HTML tags with this implementation of an ng2-charts/Chart.js bar chart with statistic data.

<div>
  <h2>Corona Virus Cases Statistic</h2>
  <div
       *ngIf="isLoadingResults">
    <mat-spinner *ngIf="isLoadingResults"></mat-spinner>
  </div>
  <div>
    <a mat-flat-button color="primary" [routerLink]="['/cases']"><mat-icon>list</mat-icon></a>
  </div>
  <div>
    <mat-button-toggle-group name="status" aria-label="Status" [(ngModel)]="label" (ngModelChange)="changeStatus()">
      <mat-button-toggle value="Positive">Positive</mat-button-toggle>
      <mat-button-toggle value="Dead">Dead</mat-button-toggle>
      <mat-button-toggle value="Recovered">Recovered</mat-button-toggle>
    </mat-button-toggle-group>
  </div>
  <div>
    <canvas baseChart
      [datasets]="barChartData"
      [labels]="barChartLabels"
      [options]="barChartOptions"
      [plugins]="barChartPlugins"
      [legend]="barChartLegend"
      [chartType]="barChartType">
    </canvas>
  </div>
</div>

Finally, give it a little style by modify `src/app/cases-stat/cases-stat.component.scss` with these.

/* Structure */
.example-container {
  position: relative;
  padding: 5px;
}

.example-loading-shade {
  position: absolute;
  top: 0;
  left: 0;
  bottom: 56px;
  right: 0;
  background: rgba(0, 0, 0, 0.15);
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: center;
}

.mat-flat-button {
  margin: 5px;
}

Step #7. Add a Data using Angular Material Form

To create a form for adding a Coronavirus case, open and edit `src/app/add-cases/add-cases.component.ts` then add these imports.

import { Router } from '@angular/router';
import { ApiService } from '../api.service';
import { FormControl, FormGroupDirective, FormBuilder, FormGroup, NgForm, Validators } from '@angular/forms';
import { ErrorStateMatcher } from '@angular/material/core';

Inject the above modules to the constructor.

constructor(private router: Router, private api: ApiService, private formBuilder: FormBuilder) { }

Declare variables for the Form Group and all of the required fields inside the form before the constructor.

  casesForm: FormGroup;
  name = '';
  gender = '';
  age: number = null;
  address = '';
  city = '';
  country = '';
  status = '';
  statusList = ['Positive', 'Dead', 'Recovered'];
  genderList = ['Male', 'Female'];
  isLoadingResults = false;
  matcher = new MyErrorStateMatcher();

Add initial validation for each field.

  ngOnInit(): void {
    this.casesForm = this.formBuilder.group({
      name : [null, Validators.required],
      gender : [null, Validators.required],
      age : [null, Validators.required],
      address : [null, Validators.required],
      city : [null, Validators.required],
      country : [null, Validators.required],
      status : [null, Validators.required]
    });
  }

Create a function for submitting or POST product form.

  onFormSubmit() {
    this.isLoadingResults = true;
    this.api.addCases(this.casesForm.value)
      .subscribe((res: any) => {
          const id = res._id;
          this.isLoadingResults = false;
          this.router.navigate(['/cases-details', id]);
        }, (err: any) => {
          console.log(err);
          this.isLoadingResults = false;
        });
  }

Create a new class before the main class `@Components`.

/** Error when invalid control is dirty, touched, or submitted. */
export class MyErrorStateMatcher implements ErrorStateMatcher {
  isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
    const isSubmitted = form && form.submitted;
    return !!(control && control.invalid && (control.dirty || control.touched || isSubmitted));
  }
}

Next, open and edit `src/app/add-cases/add-cases.component.html` then replace all HTML tags with this.

<div>
  <h2>Coronavirus Add Cases</h2>
  <div
        *ngIf="isLoadingResults">
    <mat-spinner *ngIf="isLoadingResults"></mat-spinner>
  </div>
  <div>
    <a mat-flat-button color="primary" [routerLink]="['/cases']"><mat-icon>list</mat-icon></a>
  </div>
  <mat-card>
    <form [formGroup]="casesForm" (ngSubmit)="onFormSubmit()">
      <mat-form-field>
        <mat-label>Name</mat-label>
        <input matInput placeholder="Name" formControlName="name"
                [errorStateMatcher]="matcher">
        <mat-error>
          <span *ngIf="!casesForm.get('name').valid && casesForm.get('name').touched">Please enter Name</span>
        </mat-error>
      </mat-form-field>
      <mat-form-field>
        <mat-label>Gender</mat-label>
        <mat-select formControlName="gender">
          <mat-option *ngFor="let gl of genderList" [value]="gl">
            {{gl}}
          </mat-option>
        </mat-select>
        <mat-error>
          <span *ngIf="!casesForm.get('gender').valid && casesForm.get('gender').touched">Please choose Gender</span>
        </mat-error>
      </mat-form-field>
      <mat-form-field>
        <mat-label>Age</mat-label>
        <input matInput type="number" placeholder="Age" formControlName="age"
                [errorStateMatcher]="matcher">
        <mat-error>
          <span *ngIf="!casesForm.get('age').valid && casesForm.get('age').touched">Please enter Age</span>
        </mat-error>
      </mat-form-field>
      <mat-form-field>
        <mat-label>Address</mat-label>
        <input matInput placeholder="Address" formControlName="address"
                [errorStateMatcher]="matcher">
        <mat-error>
          <span *ngIf="!casesForm.get('address').valid && casesForm.get('address').touched">Please enter Address</span>
        </mat-error>
      </mat-form-field>
      <mat-form-field>
        <mat-label>City</mat-label>
        <input matInput placeholder="City" formControlName="city"
                [errorStateMatcher]="matcher">
        <mat-error>
          <span *ngIf="!casesForm.get('city').valid && casesForm.get('city').touched">Please enter City</span>
        </mat-error>
      </mat-form-field>
      <mat-form-field>
        <mat-label>Country</mat-label>
        <input matInput placeholder="Country" formControlName="country"
                [errorStateMatcher]="matcher">
        <mat-error>
          <span *ngIf="!casesForm.get('country').valid && casesForm.get('country').touched">Please enter Country</span>
        </mat-error>
      </mat-form-field>
      <mat-form-field>
        <mat-label>Status</mat-label>
        <mat-select formControlName="status">
          <mat-option *ngFor="let sl of statusList" [value]="sl">
            {{sl}}
          </mat-option>
        </mat-select>
        <mat-error>
          <span *ngIf="!casesForm.get('status').valid && casesForm.get('status').touched">Please select Status</span>
        </mat-error>
      </mat-form-field>
      <div>
        <button type="submit" [disabled]="!casesForm.valid" mat-flat-button color="primary"><mat-icon>save</mat-icon></button>
      </div>
    </form>
  </mat-card>
</div>

Finally, open and edit `src/app/add-cases/add-cases.component.scss` then add this CSS codes.

/* Structure */
.example-container {
  position: relative;
  padding: 5px;
}

.example-form {
  min-width: 150px;
  max-width: 500px;
  width: 100%;
}

.example-full-width {
  width: 100%;
}

.example-full-width:nth-last-child(0) {
  margin-bottom: 10px;
}

.button-row {
  margin: 10px 0;
}

.mat-flat-button {
  margin: 5px;
}

Step #8. Edit a Data using Angular Material Form

We already put an edit button inside the Cases Details component for the call Edit page. Now, open and edit `src/app/edit-cases/edit-cases.component.ts` then add these imports.

import { Router, ActivatedRoute } from '@angular/router';
import { ApiService } from '../api.service';
import { FormControl, FormGroupDirective, FormBuilder, FormGroup, NgForm, Validators } from '@angular/forms';
import { ErrorStateMatcher } from '@angular/material/core';

Inject the above modules to the constructor.

constructor(private router: Router, private route: ActivatedRoute, private api: ApiService, private formBuilder: FormBuilder) { }

Declare the Form Group variable and all of the required variables for the cases-form before the constructor.

  casesForm: FormGroup;
  _id = '';
  name = '';
  gender = '';
  age: number = null;
  address = '';
  city = '';
  country = '';
  status = '';
  statusList = ['Positive', 'Dead', 'Recovered'];
  genderList = ['Male', 'Female'];
  isLoadingResults = false;
  matcher = new MyErrorStateMatcher();

Next, add validation for all fields when the component is initiated.

  ngOnInit(): void {
    this.getCasesById(this.route.snapshot.params.id);
    this.casesForm = this.formBuilder.group({
      name : [null, Validators.required],
      gender : [null, Validators.required],
      age : [null, Validators.required],
      address : [null, Validators.required],
      city : [null, Validators.required],
      country : [null, Validators.required],
      status : [null, Validators.required]
    });
  }

Create a function for getting cases data that filled to each form field.

  getCasesById(id: any) {
    this.api.getCasesById(id).subscribe((data: any) => {
      this._id = data._id;
      this.casesForm.setValue({
        name: data.name,
        gender: data.gender,
        age: data.age,
        address: data.address,
        city: data.city,
        country: data.country,
        status: data.status
      });
    });
  }

Create a function to update the case changes.

  onFormSubmit() {
    this.isLoadingResults = true;
    this.api.updateCases(this._id, this.casesForm.value)
      .subscribe((res: any) => {
          const id = res._id;
          this.isLoadingResults = false;
          this.router.navigate(['/cases-details', id]);
        }, (err: any) => {
          console.log(err);
          this.isLoadingResults = false;
        }
      );
  }

Add a function for handling the show product details button.

  casesDetails() {
    this.router.navigate(['/cases-details', this._id]);
  }

Create a new class before the main class `@Components`.

/** Error when invalid control is dirty, touched, or submitted. */
export class MyErrorStateMatcher implements ErrorStateMatcher {
  isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
    const isSubmitted = form && form.submitted;
    return !!(control && control.invalid && (control.dirty || control.touched || isSubmitted));
  }
}

Next, open and edit `src/app/edit-cases/edit-cases.component.html` then replace all HTML tags with this.

<div>
  <h2>Coronavirus Edit Cases</h2>
  <div
        *ngIf="isLoadingResults">
    <mat-spinner *ngIf="isLoadingResults"></mat-spinner>
  </div>
  <div>
      <a mat-flat-button color="primary" (click)="casesDetails()"><mat-icon>info</mat-icon></a>
  </div>
  <mat-card>
    <form [formGroup]="casesForm" (ngSubmit)="onFormSubmit()">
      <mat-form-field>
        <mat-label>Name</mat-label>
        <input matInput placeholder="Name" formControlName="name"
                [errorStateMatcher]="matcher">
        <mat-error>
          <span *ngIf="!casesForm.get('name').valid && casesForm.get('name').touched">Please enter Name</span>
        </mat-error>
      </mat-form-field>
      <mat-form-field>
        <mat-label>Gender</mat-label>
        <mat-select formControlName="gender">
          <mat-option *ngFor="let gl of genderList" [value]="gl">
            {{gl}}
          </mat-option>
        </mat-select>
        <mat-error>
          <span *ngIf="!casesForm.get('gender').valid && casesForm.get('gender').touched">Please choose Gender</span>
        </mat-error>
      </mat-form-field>
      <mat-form-field>
        <mat-label>Age</mat-label>
        <input matInput type="number" placeholder="Age" formControlName="age"
                [errorStateMatcher]="matcher">
        <mat-error>
          <span *ngIf="!casesForm.get('age').valid && casesForm.get('age').touched">Please enter Age</span>
        </mat-error>
      </mat-form-field>
      <mat-form-field>
        <mat-label>Address</mat-label>
        <input matInput placeholder="Address" formControlName="address"
                [errorStateMatcher]="matcher">
        <mat-error>
          <span *ngIf="!casesForm.get('address').valid && casesForm.get('address').touched">Please enter Address</span>
        </mat-error>
      </mat-form-field>
      <mat-form-field>
        <mat-label>City</mat-label>
        <input matInput placeholder="City" formControlName="city"
                [errorStateMatcher]="matcher">
        <mat-error>
          <span *ngIf="!casesForm.get('city').valid && casesForm.get('city').touched">Please enter City</span>
        </mat-error>
      </mat-form-field>
      <mat-form-field>
        <mat-label>Country</mat-label>
        <input matInput placeholder="Country" formControlName="country"
                [errorStateMatcher]="matcher">
        <mat-error>
          <span *ngIf="!casesForm.get('country').valid && casesForm.get('country').touched">Please enter Country</span>
        </mat-error>
      </mat-form-field>
      <mat-form-field>
        <mat-label>Status</mat-label>
        <mat-select formControlName="status">
          <mat-option *ngFor="let sl of statusList" [value]="sl">
            {{sl}}
          </mat-option>
        </mat-select>
        <mat-error>
          <span *ngIf="!casesForm.get('status').valid && casesForm.get('status').touched">Please select Status</span>
        </mat-error>
      </mat-form-field>
      <div>
        <button type="submit" [disabled]="!casesForm.valid" mat-flat-button color="primary"><mat-icon>save</mat-icon></button>
      </div>
    </form>
  </mat-card>
</div>

Finally, open and edit `src/app/edit-cases/edit-cases.component.scss` then add these lines of CSS codes.

/* Structure */
.example-container {
  position: relative;
  padding: 5px;
}

.example-form {
  min-width: 150px;
  max-width: 500px;
  width: 100%;
}

.example-full-width {
  width: 100%;
}

.example-full-width:nth-last-child(0) {
  margin-bottom: 10px;
}

.button-row {
  margin: 10px 0;
}

.mat-flat-button {
  margin: 5px;
}

Step #9. Run and Test the Angular 9 Coronavirus Cases App

Let's see the performance of the Angular 9 with the Ivy CRUD App. Now, we have to build the Angular 9 app using this command.

ng build --prod

Now, we have ES5 and ES2015 build of the Angular 9 app build for production. Next, we have to test the whole application, first, we have to run MongoDB server and Node/Express API in the different terminal.

mongod
nodemon

Then run the Angular 9 app build, simply type this command.

ng serve

Now, you will see the Coronavirus Cases app the same as you saw in the first paragraph of this tutorial. That it's the Angular 9 Tutorial: Learn to Build a CRUD Angular App Quickly. You can find the full source code in our GitHub .

If you don’t want to waste your time design your own front-end or your budget to spend by hiring a web designer then Angular Templates is the best place to go. So, speed up your front-end web development with premium Angular templates . Choose your template for your front-end project here .

That just the basic. If you need more deep learning about MEAN Stack, Angular, and Node.js, you can take the following cheap course:

Thanks!


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

The Web Designer's Idea Book, Vol. 2

The Web Designer's Idea Book, Vol. 2

Patrick McNeil / How / 2010-9-19 / USD 30.00

Web Design Inspiration at a Glance Volume 2 of The Web Designer's Idea Book includes more than 650 new websites arranged thematically, so you can easily find inspiration for your work. Auth......一起来看看 《The Web Designer's Idea Book, Vol. 2》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

随机密码生成器
随机密码生成器

多种字符组合密码

SHA 加密
SHA 加密

SHA 加密工具