English 中文(简体)
Angular 8 - Working Example
  • 时间:2024-09-17

Angular 8 - Working Example


Previous Page Next Page  

Here, we will study about the complete step by step working example with regards to Angular 8.

Let us create an Angular apppcation to check our day to day expenses. Let us give ExpenseManager as our choice for our new apppcation.

Create an apppcation

Use below command to create the new apppcation.


cd /path/to/workspace
ng new expense-manager

Here,

new is one of the command of the ng CLI apppcation. It will be used to create new apppcation. It will ask some basic question in order to create new apppcation. It is enough to let the apppcation choose the default choices. Regarding routing question as mentioned below, specify No.


Would you pke to add Angular routing? No

Once the basic questions are answered, the ng CLI apppcation create a new Angular apppcation under expense-manager folder.

Let us move into the our newly created apppcation folder.


cd expense-manager

Let us start the apppcation using below comman.


ng serve

Let us fire up a browser and opens http://localhost:4200. The browser will show the apppcation as shown below −

apppcations

Let us change the title of the apppcation to better reflect our apppcation. Open src/app/app.component.ts and change the code as specified below −


export class AppComponent { 
   title =  Expense Manager ;
}

Our final apppcation will be rendered in the browser as shown below −

apppcations

Add a component

Create a new component using ng generate component command as specified below −


ng generate component expense-entry

Output

The output is as follows −


CREATE src/app/expense-entry/expense-entry.component.html (28 bytes)
CREATE src/app/expense-entry/expense-entry.component.spec.ts (671 bytes)
CREATE src/app/expense-entry/expense-entry.component.ts (296 bytes)
CREATE src/app/expense-entry/expense-entry.component.css (0 bytes)
UPDATE src/app/app.module.ts (431 bytes)

Here,

    ExpenseEntryComponent is created under src/app/expense-entry folder.

    Component class, Template and stylesheet are created.

    AppModule is updated with new component.

Add title property to ExpenseEntryComponent (src/app/expense-entry/expense-entry.component.ts) component.


import { Component, OnInit } from  @angular/core ;

@Component({
   selector:  app-expense-entry ,
   templateUrl:  ./expense-entry.component.html ,
   styleUrls: [ ./expense-entry.component.css ]
})
export class ExpenseEntryComponent implements OnInit {
   title: string;
   constructor() { }

   ngOnInit() {
      this.title = "Expense Entry"
   }
}

Update template, src/app/expense-entry/expense-entry.component.html with below content.


<p>{{ title }}</p>

Open

src/app/app.component.html

and include newly created component.

<h1>{{ title }}</h1>
<app-expense-entry></app-expense-entry>

Here,

app-expense-entry is the selector value and it can be used as regular HTML Tag.

The output of the apppcation is as shown below −

HTML Tag

Include bootstrap

Let us include bootstrap into our ExpenseManager apppcation using styles option and change the default template to use bootstrap components.

Open command prompt and go to ExpenseManager apppcation.


cd /go/to/expense-manager

Install bootstrap and JQuery pbrary using below commands


npm install --save bootstrap@4.5.0 jquery@3.5.1

Here,

We have installed JQuery, because, bootstrap uses jquery extensively for advanced components.

Option angular.json and set bootstrap and jquery pbrary path.


{ 
   "projects": { 
      "expense-manager": { 
         "architect": { 
            "build": {
               "builder":"@angular-devkit/build-angular:browser", "options": { 
                  "outputPath": "dist/expense-manager", 
                  "index": "src/index.html", 
                  "main": "src/main.ts", 
                  "polyfills": "src/polyfills.ts", 
                  "tsConfig": "tsconfig.app.json", 
                  "aot": false, 
                  "assets": [ 
                     "src/favicon.ico", 
                     "src/assets" 
                  ], 
                  "styles": [ 
                     "./node_modules/bootstrap/dist/css/bootstrap.css", "src/styles.css" 
                  ], 
                  "scripts": [ 
                     "./node_modules/jquery/dist/jquery.js", "./node_modules/bootstrap/dist/js/bootstrap.js" 
                  ] 
               }, 
            }, 
         } 
   }}, 
   "defaultProject": "expense-manager" 
}

Here,

scripts option is used to include JavaScript pbrary. JavaScript registered through scripts will be available to all Angular components in the apppcation.

Open app.component.html and change the content as specified below


<!-- Navigation --> 
<nav class="navbar navbar-expand-lg navbar-dark bg-dark static-top"> 
   <span class="container"> 
      <a class="navbar-brand" href="#">{{ title }}</a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarResponsive" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation"> 
         <span class="navbar-toggler-icon">
         </span> 
      </button> 
      <span class="collapse navbar-collapse" id="navbarResponsive"> 
         <ul class="navbar-nav ml-auto"> 
            <p class="nav-item active"> 
            <a class="nav-pnk" href="#">Home
               <span class="sr-only">(current)
               </span>
            </a> 
            </p> 
            <p class="nav-item"> 
            <a class="nav-pnk" href="#">Report</a> 
            </p> 
            <p class="nav-item"> 
            <a class="nav-pnk" href="#">Add Expense</a> 
            </p> 
            <p class="nav-item"> 
            <a class="nav-pnk" href="#">About</a> 
            </p> 
         </ul> 
      </span> 
   </span> 
</nav> 
<app-expense-entry></app-expense-entry>

Here,

Used bootstrap navigation and containers.

Open src/app/expense-entry/expense-entry.component.html and place below content.


<!-- Page Content --> 
<span class="container"> 
   <span class="row"> 
      <span class="col-lg-12 text-center" style="padding-top: 20px;"> 
         <span class="container" style="padding-left: 0px; padding-right: 0px;"> 
            <span class="row"> 
            <span class="col-sm" style="text-apgn: left;"> {{ title }} 
            </span> 
            <span class="col-sm" style="text-apgn: right;"> 
               <button type="button" class="btn btn-primary">Edit</button> 
            </span> 
            </span> 
         </span> 
         <span class="container box" style="margin-top: 10px;"> 
         <span class="row"> 
         <span class="col-2" style="text-apgn: right;">  
            <strong><em>Item:</em></strong> 
         </span> 
         <span class="col" style="text-apgn: left;"> 
            Pizza 
         </span>
         </span> 
         <span class="row"> 
         <span class="col-2" style="text-apgn: right;">
            <strong><em>Amount:</em></strong> 
         </span> 
         <span class="col" style="text-apgn: left;"> 
            20 
         </span> 
         </span> 
         <span class="row"> 
         <span class="col-2" style="text-apgn: right;"> 
            <strong><em>Category:</em></strong> 
         </span> 
         <span class="col" style="text-apgn: left;"> 
            Food 
         </span> 
         </span> 
         <span class="row"> 
         <span class="col-2" style="text-apgn: right;"> 
            <strong><em>Location:</em></strong>
         </span> 
         <span class="col" style="text-apgn: left;"> 
            Zomato 
         </span> 
         </span> 
         <span class="row"> 
         <span class="col-2" style="text-apgn: right;"> 
            <strong><em>Spend On:</em></strong> 
         </span> 
         <span class="col" style="text-apgn: left;"> 
            June 20, 2020 
         </span> 
         </span> 
      </span> 
   </span> 
</span> 
</span>

Restart the apppcation.

The output of the apppcation is as follows −

Restart Tag

We will improve the apppcation to handle dynamic expense entry in next chapter.

Add an interface

Create ExpenseEntry interface (src/app/expense-entry.ts) and add id, amount, category, Location, spendOn and createdOn.


export interface ExpenseEntry {
   id: number;
   item: string;
   amount: number;
   category: string;
   location: string;
   spendOn: Date;
   createdOn: Date;
}

Import ExpenseEntry into ExpenseEntryComponent.


import { ExpenseEntry } from  ../expense-entry ;

Create a ExpenseEntry object, expenseEntry as shown below −


export class ExpenseEntryComponent implements OnInit {
   title: string;
   expenseEntry: ExpenseEntry;
   constructor() { }

   ngOnInit() {
      this.title = "Expense Entry";

      this.expenseEntry = {

         id: 1,
         item: "Pizza",
         amount: 21,
         category: "Food",
         location: "Zomato",
         spendOn: new Date(2020, 6, 1, 10, 10, 10),
         createdOn: new Date(2020, 6, 1, 10, 10, 10),
      };
   }
}

Update the component template using expenseEntry object, src/app/expense-entry/expense-entry.component.html as specified below −


<!-- Page Content -->
<span class="container">
   <span class="row">
      <span class="col-lg-12 text-center" style="padding-top: 20px;">
         <span class="container" style="padding-left: 0px; padding-right: 0px;">
            <span class="row">
               <span class="col-sm" style="text-apgn: left;">
                  {{ title }}
               </span>
               <span class="col-sm" style="text-apgn: right;">
                  <button type="button" class="btn btn-primary">Edit</button>
               </span>
            </span>
         </span>
         <span class="container box" style="margin-top: 10px;">
            <span class="row">
               <span class="col-2" style="text-apgn: right;">
                  <strong><em>Item:</em></strong>
               </span>
               <span class="col" style="text-apgn: left;">
                  {{ expenseEntry.item }} 
               </span>
            </span>
            <span class="row">
               <span class="col-2" style="text-apgn: right;">
                  <strong><em>Amount:</em></strong>
               </span>
               <span class="col" style="text-apgn: left;">
                  {{ expenseEntry.amount }}   
               </span>
            </span>
            <span class="row">
               <span class="col-2" style="text-apgn: right;">
                  <strong><em>Category:</em></strong>
               </span>
               <span class="col" style="text-apgn: left;">

                  {{ expenseEntry.category }} 
               </span>
            </span>
            <span class="row">
               <span class="col-2" style="text-apgn: right;">
                  <strong><em>Location:</em></strong>
               </span>
               <span class="col" style="text-apgn: left;">
                  {{ expenseEntry.location }} 
               </span>
            </span>
            <span class="row">
               <span class="col-2" style="text-apgn: right;">
                  <strong><em>Spend On:</em></strong>
               </span>
               <span class="col" style="text-apgn: left;">
                  {{ expenseEntry.spendOn }}  
               </span>
            </span>
         </span>
      </span>
   </span>
</span>

The output of the apppcation is as follows −

Interface

Using directives

Let us add a new component in our ExpenseManager apppcation to pst the expense entries.

Open command prompt and go to project root folder.


cd /go/to/expense-manager

Start the apppcation.


ng serve

Create a new component, ExpenseEntryListComponent using below command −


ng generate component ExpenseEntryList

Output

The output is as follows −


CREATE src/app/expense-entry-pst/expense-entry-pst.component.html (33 bytes) 
CREATE src/app/expense-entry-pst/expense-entry-pst.component.spec.ts (700 bytes) 
CREATE src/app/expense-entry-pst/expense-entry-pst.component.ts (315 bytes) 
CREATE src/app/expense-entry-pst/expense-entry-pst.component.css (0 bytes) 
UPDATE src/app/app.module.ts (548 bytes)

Here, the command creates the ExpenseEntryList Component and update the necessary code in AppModule.

Import ExpenseEntry into ExpenseEntryListComponent component (src/app/expense-entry-pst/expense-entry-pst.component)


import { ExpenseEntry } from  ../expense-entry ;

Add a method, getExpenseEntries() to return pst of expense entry (mock items) in ExpenseEntryListComponent (src/app/expense-entry-pst/expense-entry-pst.component)


getExpenseEntries() : ExpenseEntry[] { 
   let mockExpenseEntries : ExpenseEntry[] = [ 
      { id: 1, 
         item: "Pizza", 
         amount: Math.floor((Math.random() * 10) + 1), 
         category: "Food", 
         location: "Mcdonald", 
         spendOn: new Date(2020, 4, Math.floor((Math.random() * 30) + 1), 10, 10, 10), 
         createdOn: new Date(2020, 4, Math.floor((Math.random() * 30) + 1), 10, 10, 10) }, 
      { id: 1, 
         item: "Pizza", 
         amount: Math.floor((Math.random() * 10) + 1), 
         category: "Food", 
         location: "KFC", 
         spendOn: new Date(2020, 4, Math.floor((Math.random() * 30) + 1), 10, 10, 10), 
         createdOn: new Date(2020, 4, Math.floor((Math.random() * 30) + 1), 10, 10, 10) }, 
      { id: 1,
         item: "Pizza",
         amount: Math.floor((Math.random() * 10) + 1), 
         category: "Food", 
         location: "Mcdonald", 
         spendOn: new Date(2020, 4, Math.floor((Math.random() * 30) + 1), 10, 10, 10), 
         createdOn: new Date(2020, 4, Math.floor((Math.random() * 30) + 1), 10, 10, 10) }, 
      { id: 1, 
         item: "Pizza", 
         amount: Math.floor((Math.random() * 10) + 1), 
         category: "Food", 
         location: "KFC", 
         spendOn: new Date(2020, 4, Math.floor((Math.random() * 30) + 1), 10, 10, 10), 
         createdOn: new Date(2020, 4, Math.floor((Math.random() * 30) + 1), 10, 10, 10) }, 
      { id: 1, 
         item: "Pizza", 
         amount: Math.floor((Math.random() * 10) + 1), 
         category: "Food", 
         location: "KFC", 
         spendOn: new Date(2020, 4, Math.floor((Math.random() * 30) + 1), 10, 10, 10), 
         createdOn: new Date(2020, 4, Math.floor((Math.random() * 30) + 1), 10, 10, 10) 
      }, 
   ]; 
   return mockExpenseEntries; 
}

Declare a local variable, expenseEntries and load the mock pst of expense entries as mentioned below −


title: string; 
expenseEntries: ExpenseEntry[]; 
constructor() { } 
ngOnInit() { 
   this.title = "Expense Entry List"; 
   this.expenseEntries = this.getExpenseEntries(); 
}

Open the template file (src/app/expense-entry-pst/expense-entry-pst.component.html) and show the mock entries in a table.


<!-- Page Content -->
<span class="container"> 
   <span class="row"> 
      <span class="col-lg-12 text-center" style="padding-top: 20px;">
         <span class="container" style="padding-left: 0px; padding-right: 0px;"> 
            <span class="row"> 
               <span class="col-sm" style="text-apgn: left;"> 
                  {{ title }} 
               </span> 
               <span class="col-sm" style="text-apgn: right;"> 
                  <button type="button" class="btn btn-primary">Edit</button> 
               </span> 
            </span> 
         </span> 
         <span class="container box" style="margin-top: 10px;"> 
            <table class="table table-striped"> 
               <thead> 
                  <tr> 
                     <th>Item</th> 
                     <th>Amount</th> 
                     <th>Category</th> 
                     <th>Location</th> 
                     <th>Spent On</th> 
                  </tr> 
               </thead> 
               <tbody> 
                  <tr *ngFor="let entry of expenseEntries"> 
                     <th scope="row">{{ entry.item }}</th> 
                     <th>{{ entry.amount }}</th> 
                     <td>{{ entry.category }}</td> 
                     <td>{{ entry.location }}</td> 
                     <td>{{ entry.spendOn | date:  short  }}</td> 
                  </tr> 
               </tbody> 
            </table> 
         </span> 
      </span> 
   </span> 
</span>

Here,

    Used bootstrap table. table and table-striped will style the table according to Boostrap style standard.

    Used ngFor to loop over the expenseEntries and generate table rows.

Open AppComponent template, src/app/app.component.html and include ExpenseEntryListComponent and remove ExpenseEntryComponent as shown below −


... 
<app-expense-entry-pst></app-expense-entry-pst>

Finally, the output of the apppcation is as shown below.

AppComponent

Use pipes

Let us use the pipe in the our ExpenseManager apppcation

Open ExpenseEntryListComponent’s template, src/app/expense-entry-pst/expense-entry-pst.component.html and include pipe in entry.spendOn as mentioned below −


<td>{{ entry.spendOn | date:  short  }}</td>

Here, we have used the date pipe to show the spend on date in the short format.

Finally, the output of the apppcation is as shown below −

Pipes

Add debug service

Run the below command to generate an Angular service, DebugService.


ng g service debug

This will create two Typescript files (debug service & its test) as specified below −


CREATE src/app/debug.service.spec.ts (328 bytes) 
CREATE src/app/debug.service.ts (134 bytes)

Let us analyse the content of the DebugService service.


import { Injectable } from  @angular/core ; @Injectable({ 
   providedIn:  root  
}) 
export class DebugService { 
   constructor() { } 
}

Here,

    @Injectable decorator is attached to DebugService class, which enables the DebugService to be used in Angular component of the apppcation.

    providerIn option and its value, root enables the DebugService to be used in all component of the apppcation.

Let us add a method, Info, which will print the message into the browser console.


info(message : String) : void { 
   console.log(message); 
}

Let us initiapse the service in the ExpenseEntryListComponent and use it to print message.


import { Component, OnInit } from  @angular/core ; import { ExpenseEntry } from  ../expense-entry ; import { DebugService } from  ../debug.service ; @Component({ 
   selector:  app-expense-entry-pst , 
   templateUrl:  ./expense-entry-pst.component.html , styleUrls: [ ./expense-entry-pst.component.css ] 
}) 
export class ExpenseEntryListComponent implements OnInit { 
   title: string; 
   expenseEntries: ExpenseEntry[]; 
   constructor(private debugService: DebugService) { } 
   ngOnInit() { 
      this.debugService.info("Expense Entry List 
      component initiapzed"); 
      this.title = "Expense Entry List"; 
      this.expenseEntries = this.getExpenseEntries(); 
   } 
   // other coding 
}

Here,

    DebugService is initiapsed using constructor parameters. Setting an argument (debugService) of type DebugService will trigger the dependency injection to create a new DebugService object and set it into the ExpenseEntryListComponent component.

    Calpng the info method of DebugService in the ngOnInit method prints the message in the browser console.

The result can be viewed using developer tools and it looks similar as shown below −

Debug service

Let us extend the apppcation to understand the scope of the service.

Let us a create a DebugComponent by using below mentioned command.


ng generate component debug
CREATE src/app/debug/debug.component.html (20 bytes) CREATE src/app/debug/debug.component.spec.ts (621 bytes) 
CREATE src/app/debug/debug.component.ts (265 bytes) CREATE src/app/debug/debug.component.css (0 bytes) UPDATE src/app/app.module.ts (392 bytes)

Let us remove the DebugService in the root module.


// src/app/debug.service.ts
import { Injectable } from  @angular/core ; @Injectable() 
export class DebugService { 
   constructor() { 
   }
   info(message : String) : void {     
      console.log(message); 
   } 
}

Register the DebugService under ExpenseEntryListComponent component.


// src/app/expense-entry-pst/expense-entry-pst.component.ts @Component({ 
   selector:  app-expense-entry-pst , 
   templateUrl:  ./expense-entry-pst.component.html , 
   styleUrls: [ ./expense-entry-pst.component.css ] 
   providers: [DebugService] 
})

Here, we have used providers meta data (ElementInjector) to register the service.

Open DebugComponent (src/app/debug/debug.component.ts) and import DebugService and set an instance in the constructor of the component.


import { Component, OnInit } from  @angular/core ; import { DebugService } from  ../debug.service ; 
@Component({ 
   selector:  app-debug , 
   templateUrl:  ./debug.component.html , 
   styleUrls: [ ./debug.component.css ] 
}) 
export class DebugComponent implements OnInit { 
   constructor(private debugService: DebugService) { } 
   ngOnInit() { 
      this.debugService.info("Debug component gets service from Parent"); 
   } 
}

Here, we have not registered DebugService. So, DebugService will not be available if used as parent component. When used inside a parent component, the service may available from parent, if the parent has access to the service.

Open ExpenseEntryListComponent template (src/app/expense-entry-pst/expense-entry-pst.component.html) and include a content section as shown below:


// existing content 
<app-debug></app-debug>
<ng-content></ng-content>

Here, we have included a content section and DebugComponent section.

Let us include the debug component as a content inside the ExpenseEntryListComponent component in the AppComponent template. Open AppComponent template and change app-expense-entry-pst as below −


// navigation code
<app-expense-entry-pst>
<app-debug></app-debug>
</app-expense-entry-pst>

Here, we have included the DebugComponent as content.

Let us check the apppcation and it will show DebugService template at the end of the page as shown below −

Debug

Also, we could able to see two debug information from debug component in the console. This indicate that the debug component gets the service from its parent component.

Let us change how the service is injected in the ExpenseEntryListComponent and how it affects the scope of the service. Change providers injector to viewProviders injection. viewProviders does not inject the service into the content child and so, it should fail.


viewProviders: [DebugService]

Check the apppcation and you will see that the one of the debug component (used as content child) throws error as shown below −

Apppcation

Let us remove the debug component in the templates and restore the apppcation.

Open ExpenseEntryListComponent template (src/app/expense-entry-pst/expense-entry-pst.component.html) and remove below content

 
<app-debug></app-debug>
<ng-content></ng-content>

Open AppComponent template and change app-expense-entry-pst as below −


// navigation code
<app-expense-entry-pst>
</app-expense-entry-pst>

Change the viewProviders setting to providers in ExpenseEntryListComponent.


providers: [DebugService]

Rerun the apppcation and check the result.

Create expense service

Let us create a new service ExpenseEntryService in our ExpenseManager apppcation to interact with Expense REST API. ExpenseEntryService will get the latest expense entries, insert new expense entries, modify existing expense entries and delete the unwanted expense entries.

Open command prompt and go to project root folder.


cd /go/to/expense-manager

Start the apppcation.


ng serve

Run the below command to generate an Angular service, ExpenseService.


ng generate service ExpenseEntry

This will create two Typescript files (expense entry service & its test) as specified below −


CREATE src/app/expense-entry.service.spec.ts (364 bytes) 
CREATE src/app/expense-entry.service.ts (141 bytes)

Open ExpenseEntryService (src/app/expense-entry.service.ts) and import ExpenseEntry, throwError and catchError from rxjs pbrary and import HttpCpent, HttpHeaders and HttpErrorResponse from @angular/common/http package.


import { Injectable } from  @angular/core ; 
import { ExpenseEntry } from  ./expense-entry ; import { throwError } from  rxjs ;
import { catchError } from  rxjs/operators ; 
import { HttpCpent, HttpHeaders, HttpErrorResponse } from 
 @angular/common/http ;

Inject the HttpCpent service into our service.


constructor(private httpCpent : HttpCpent) { }

Create a variable, expenseRestUrl to specify the Expense Rest API endpoints.


private expenseRestUrl =  http://localhost:8000/api/expense ;

Create a variable, httpOptions to set the Http Header option. This will be used during the Http Rest API call by Angular HttpCpent service.


private httpOptions = { 
   headers: new HttpHeaders( {  Content-Type :  apppcation/json  }) 
};

The complete code is as follows −


import { Injectable } from  @angular/core ;
import { ExpenseEntry } from  ./expense-entry ;
import { Observable, throwError } from  rxjs ;
import { catchError, retry } from  rxjs/operators ;
import { HttpCpent, HttpHeaders, HttpErrorResponse } from  @angular/common/http ;

@Injectable({
   providedIn:  root 
})
export class ExpenseEntryService {
      private expenseRestUrl =  api/expense ;
      private httpOptions = {
         headers: new HttpHeaders( {  Content-Type :  apppcation/json  })
      };

   constructor(
      private httpCpent : HttpCpent) { }
}

Http programming using HttpCpent service

Start the Expense REST API apppcation as shown below −


cd /go/to/expense-rest-api 
node .server.js

Add getExpenseEntries() and httpErrorHandler() method in ExpenseEntryService (src/app/expense-entry.service.ts) service.


getExpenseEntries() : Observable<ExpenseEntry[]> {
   return this.httpCpent.get<ExpenseEntry[]>(this.expenseRestUrl, this.httpOptions)
   .pipe(retry(3),catchError(this.httpErrorHandler));
}

getExpenseEntry(id: number) : Observable<ExpenseEntry> {
   return this.httpCpent.get<ExpenseEntry>(this.expenseRestUrl + "/" + id, this.httpOptions)
   .pipe(
      retry(3),
      catchError(this.httpErrorHandler)
   );
}

private httpErrorHandler (error: HttpErrorResponse) {
      if (error.error instanceof ErrorEvent) {
      console.error("A cpent side error occurs. The error message is " + error.message);
      } else {
      console.error(
            "An error happened in server. The HTTP status code is "  + error.status + " and the error returned is " + error.message);
      }

      return throwError("Error occurred. Pleas try again");
}

Here,

    getExpenseEntries() calls the get() method using expense end point and also configures the error handler. Also, it configures httpCpent to try for maximum of 3 times in case of failure. Finally, it returns the response from server as typed (ExpenseEntry[]) Observable object.

    getExpenseEntry is similar to getExpenseEntries() except it passes the id of the ExpenseEntry object and gets ExpenseEntry Observable object.

The complete coding of ExpenseEntryService is as follows −


import { Injectable } from  @angular/core ;
import { ExpenseEntry } from  ./expense-entry ;

import { Observable, throwError } from  rxjs ;
import { catchError, retry } from  rxjs/operators ;
import { HttpCpent, HttpHeaders, HttpErrorResponse } from  @angular/common/http ;

@Injectable({

   providedIn:  root 
})
export class ExpenseEntryService {
   private expenseRestUrl =  http://localhost:8000/api/expense ;
   private httpOptions = {
      headers: new HttpHeaders( {  Content-Type :  apppcation/json  })
   };

   constructor(private httpCpent : HttpCpent) { } 

   getExpenseEntries() : Observable {
      return this.httpCpent.get(this.expenseRestUrl, this.httpOptions)
      .pipe(
         retry(3),
         catchError(this.httpErrorHandler)
      );
   }

   getExpenseEntry(id: number) : Observable {
      return this.httpCpent.get(this.expenseRestUrl + "/" + id, this.httpOptions)
      .pipe(
         retry(3),
         catchError(this.httpErrorHandler)
      );
   }

   private httpErrorHandler (error: HttpErrorResponse) {
      if (error.error instanceof ErrorEvent) {
         console.error("A cpent side error occurs. The error message is " + error.message);
      } else {
         console.error(
            "An error happened in server. The HTTP status code is "  + error.status + " and the error returned is " + error.message);
      }

      return throwError("Error occurred. Pleas try again");
   }
}

Open ExpenseEntryListComponent (src-entry-pst-entry-pst.component.ts) and inject ExpenseEntryService through constructor as specified below:


constructor(private debugService: DebugService, private restService : 
ExpenseEntryService ) { }

Change the getExpenseEntries() function. Call getExpenseEntries() method from ExpenseEntryService instead of returning the mock items.


getExpenseItems() {  
   this.restService.getExpenseEntries() 
      .subscribe( data =− this.expenseEntries = data ); 
}

The complete ExpenseEntryListComponent coding is as follows −


import { Component, OnInit } from  @angular/core ;
import { ExpenseEntry } from  ../expense-entry ;
import { DebugService } from  ../debug.service ;
import { ExpenseEntryService } from  ../expense-entry.service ;

@Component({
   selector:  app-expense-entry-pst ,
   templateUrl:  ./expense-entry-pst.component.html ,
   styleUrls: [ ./expense-entry-pst.component.css ],
   providers: [DebugService]
})
export class ExpenseEntryListComponent implements OnInit {
   title: string;
   expenseEntries: ExpenseEntry[];
   constructor(private debugService: DebugService, private restService : ExpenseEntryService ) { }

   ngOnInit() {
      this.debugService.info("Expense Entry List component initiapzed");
      this.title = "Expense Entry List";

      this.getExpenseItems();
   }

   getExpenseItems() {
      this.restService.getExpenseEntries()
      .subscribe( data => this.expenseEntries = data );
   }
}

Finally, check the apppcation and you will see the below response.

failed request

Add Expense functionapty

Let us add a new method, addExpenseEntry() in our ExpenseEntryService to add new expense entry as mentioned below −


addExpenseEntry(expenseEntry: ExpenseEntry): Observable<ExpenseEntry> {
   return this.httpCpent.post<ExpenseEntry>(this.expenseRestUrl, expenseEntry, this.httpOptions)
   .pipe(
      retry(3),
      catchError(this.httpErrorHandler)
   );
}

Update expense entry functionapty

Let us add a new method, updateExpenseEntry() in our ExpenseEntryService to update existing expense entry as mentioned below:


updateExpenseEntry(expenseEntry: ExpenseEntry): Observable<ExpenseEntry> {
   return this.httpCpent.put<ExpenseEntry>(this.expenseRestUrl + "/" + expenseEntry.id, expenseEntry, this.httpOptions)
   .pipe(
      retry(3),
      catchError(this.httpErrorHandler)
   );
}

Delete expense entry functionapty

Let us add a new method, deleteExpenseEntry() in our ExpenseEntryService to delete existing expense entry as mentioned below −


deleteExpenseEntry(expenseEntry: ExpenseEntry | number) : Observable<ExpenseEntry> {
   const id = typeof expenseEntry ==  number  ? expenseEntry : expenseEntry.id
   const url = `${this.expenseRestUrl}/${id}`;

   return this.httpCpent.delete<ExpenseEntry>(url, this.httpOptions)
   .pipe(
      retry(3),
      catchError(this.httpErrorHandler)
   );
}

Add Routing

Generate routing module using below command, if not done before.


ng generate module app-routing --module app --flat

Output

The output is mentioned below −


CREATE src/app/app-routing.module.ts (196 bytes) 
UPDATE src/app/app.module.ts (785 bytes)

Here,

CLI generate AppRoutingModule and then, configures it in AppModule

Update AppRoutingModule (src/app/app.module.ts) as mentioned below −


import { NgModule } from  @angular/core ; 
import { Routes, RouterModule } from  @angular/router ; import { ExpenseEntryComponent } from  ./expense-entry/expense-entry.component ; 
import { ExpenseEntryListComponent } from  ./expense-entry-pst/expense-entry-pst.component ; 
const routes: Routes = [ 
   { path:  expenses , component: ExpenseEntryListComponent }, 
   { path:  expenses/detail/:id , component: ExpenseEntryComponent }, 
   { path:   , redirectTo:  expenses , pathMatch:  full  }]; 
@NgModule({ 
   imports: [RouterModule.forRoot(routes)], 
   exports: [RouterModule] }) 
export class AppRoutingModule { }

Here, we have added route for our expense pst and expense details component.

Update AppComponent template (src/app/app.component.html) to include router-outlet and routerLink.


<!-- Navigation --> 
<nav class="navbar navbar-expand-lg navbar-dark bg-dark static-top"> 
<span class="container"> 
   <a class="navbar-brand" href="#">{{ title }}</a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarResponsive" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation"> 
      <span class="navbar-toggler-icon"></span> 
   </button> 
   <span class="collapse navbar-collapse" id="navbarResponsive"> 
      <ul class="navbar-nav ml-auto"> 
         <p class="nav-item active"> 
            <a class="nav-pnk" href="#">Home 
               <span class="sr-only" routerLink="/">(current)</span> 
            </a> 
         </p> 
         <p class="nav-item"> 
            <a class="nav-pnk" routerLink="/expenses">Report</a> 
         </p> 
         <p class="nav-item"> 
            <a class="nav-pnk" href="#">Add Expense</a> 
         </p> 
         <p class="nav-item"> 
            <a class="nav-pnk" href="#">About</a> 
         </p> 
      </ul> 
   </span> 
</span> 
</nav> 
<router-outlet></router-outlet>

Open ExpenseEntryListComponent template (src/app/expense-entry-pst/expense-entry-pst.component.html) and include view option for every expense entries.


<table class="table table-striped"> 
   <thead> 
      <tr> 
         <th>Item</th>
         <th>Amount</th> 
         <th>Category</th> 
         <th>Location</th> 
         <th>Spent On</th> 
         <th>View</th> 
      </tr> 
   </thead> 
   <tbody> 
      <tr *ngFor="let entry of expenseEntries"> 
         <th scope="row">{{ entry.item }}</th> 
         <th>{{ entry.amount }}</th> 
         <td>{{ entry.category }}</td> 
         <td>{{ entry.location }}</td> 
         <td>{{ entry.spendOn | date:  medium  }}</td> 
         <td><a routerLink="../expenses/detail/{{ entry.id }}">View</a></td> 
      </tr> 
   </tbody> 
</table>

Here, we have updated the expense pst table and added a new column to show the view option.

Open ExpenseEntryComponent (src/app/expense-entry/expense-entry.component.ts) and add functionapty to fetch the current selected expense entry. It can be done by first getting the id through the paramMap and then, using the getExpenseEntry() method from ExpenseEntryService.


this.expenseEntry$ = this.route.paramMap.pipe(  
   switchMap(params => { 
      this.selectedId = Number(params.get( id )); 
      return 
this.restService.getExpenseEntry(this.selectedId); })); 
   this.expenseEntry$.subscribe( (data) => this.expenseEntry = data );

Update ExpenseEntryComponent and add option to go to expense pst.


goToList() { 
   this.router.navigate([ /expenses ]); 
}

The complete code of ExpenseEntryComponent is as follows −


import { Component, OnInit } from  @angular/core ; import { ExpenseEntry } from  ../expense-entry ; import { ExpenseEntryService } from  ../expense-entry.service ; 
import { Router, ActivatedRoute } from  @angular/router ; 
import { Observable } from  rxjs ;
import { switchMap } from  rxjs/operators ; 
@Component({ 
   selector:  app-expense-entry , 
   templateUrl:  ./expense-entry.component.html , 
   styleUrls: [ ./expense-entry.component.css ] 
}) 
export class ExpenseEntryComponent implements OnInit { 
   title: string; 
   expenseEntry$ : Observable<ExpenseEntry>; 
   expenseEntry: ExpenseEntry = {} as ExpenseEntry; 
   selectedId: number; 
   constructor(private restService : ExpenseEntryService, private router : Router, private route : 
ActivatedRoute ) { } 
   ngOnInit() { 
      this.title = "Expense Entry"; 
   this.expenseEntry$ = this.route.paramMap.pipe( 
      switchMap(params => { 
         this.selectedId = Number(params.get( id )); 
         return 
this.restService.getExpenseEntry(this.selectedId); })); 
   this.expenseEntry$.subscribe( (data) => this.expenseEntry = data ); 
   } 
   goToList() { 
      this.router.navigate([ /expenses ]); 
   } 
}

Open ExpenseEntryComponent (src/app/expense-entry/expense-entry.component.html) template and add a new button to navigate back to expense pst page.


<span class="col-sm" style="text-apgn: right;"> 
   <button type="button" class="btn btn-primary" (cpck)="goToList()">Go to List</button>  
   <button type="button" class="btn btn-primary">Edit</button> 
</span>

Here, we have added Go to List button before Edit button.

Run the apppcation using below command −


ng serve

The final output of the apppcation is as follows −

Nested routing

Cpcking the view option of the first entry will navigate to details page and show the selected expense entry as shown below −

Nested routing

Enable login and logout feature

Create a new service, AuthService to authenticate the user.


ng generate service auth
CREATE src/app/auth.service.spec.ts (323 bytes)
CREATE src/app/auth.service.ts (133 bytes)

Open AuthService and include below code.


import { Injectable } from  @angular/core ;

import { Observable, of } from  rxjs ;
import { tap, delay } from  rxjs/operators ;

@Injectable({
   providedIn:  root 
})
export class AuthService {

   isUserLoggedIn: boolean = false;

   login(userName: string, password: string): Observable {
      console.log(userName);
      console.log(password);
      this.isUserLoggedIn = userName ==  admin  && password ==  admin ;
      localStorage.setItem( isUserLoggedIn , this.isUserLoggedIn ? "true" : "false"); 

   return of(this.isUserLoggedIn).pipe(
      delay(1000),
      tap(val => { 
         console.log("Is User Authentication is successful: " + val); 
      })
   );
   }

   logout(): void {
   this.isUserLoggedIn = false;
      localStorage.removeItem( isUserLoggedIn ); 
   }

   constructor() { }
}

Here,

    We have written two methods, login and logout.

    The purpose of the login method is to vapdate the user and if the user successfully vapdated, it stores the information in localStorage and then returns true.

    Authentication vapdation is that the user name and password should be admin.

    We have not used any backend. Instead, we have simulated a delay of 1s using Observables.

    The purpose of the logout method is to invapdate the user and removes the information stored in localStorage.

Create a login component using below command −


ng generate component login
CREATE src/app/login/login.component.html (20 bytes)
CREATE src/app/login/login.component.spec.ts (621 bytes)
CREATE src/app/login/login.component.ts (265 bytes)
CREATE src/app/login/login.component.css (0 bytes)
UPDATE src/app/app.module.ts (1207 bytes)

Open LoginComponent and include below code −


import { Component, OnInit } from  @angular/core ;

import { FormGroup, FormControl } from  @angular/forms ;
import { AuthService } from  ../auth.service ;
import { Router } from  @angular/router ;

@Component({
   selector:  app-login ,
   templateUrl:  ./login.component.html ,
   styleUrls: [ ./login.component.css ]
})
export class LoginComponent implements OnInit {

   userName: string;
   password: string;
   formData: FormGroup;

   constructor(private authService : AuthService, private router : Router) { }

   ngOnInit() {
      this.formData = new FormGroup({
         userName: new FormControl("admin"),
         password: new FormControl("admin"),
      });
   }

   onCpckSubmit(data: any) {
      this.userName = data.userName;
      this.password = data.password;

      console.log("Login page: " + this.userName);
      console.log("Login page: " + this.password);

      this.authService.login(this.userName, this.password)
         .subscribe( data => { 
            console.log("Is Login Success: " + data); 
      
           if(data) this.router.navigate([ /expenses ]); 
      });
   }
}

Here,

    Used reactive forms.

    Imported AuthService and Router and configured it in constructor.

    Created an instance of FormGroup and included two instance of FormControl, one for user name and another for password.

    Created a onCpckSubmit to vapdate the user using authService and if successful, navigate to expense pst.

Open LoginComponent template and include below template code.


<!-- Page Content -->
<span class="container">
   <span class="row">
      <span class="col-lg-12 text-center" style="padding-top: 20px;">
         <span class="container box" style="margin-top: 10px; padding-left: 0px; padding-right: 0px;">
            <span class="row">
               <span class="col-12" style="text-apgn: center;">
                                    <form [formGroup]="formData" (ngSubmit)="onCpckSubmit(formData.value)" 
                                          class="form-signin">
                                    <h2 class="form-signin-heading">Please sign in</h2>
                                    <label for="inputEmail" class="sr-only">Email address</label>
                                    <input type="text" id="username" class="form-control" 
                                          formControlName="userName" placeholder="Username" required autofocus>
                                    <label for="inputPassword" class="sr-only">Password</label>
                                    <input type="password" id="inputPassword" class="form-control" 
                                          formControlName="password" placeholder="Password" required>
                                    <button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
                                    </form>
               </span>
            </span>
         </span>
      </span>
   </span>
</span>

Here,

Created a reactive form and designed a login form.

Attached the onCpckSubmit method to the form submit action.

Open LoginComponent style and include below CSS Code.


.form-signin {
   max-width: 330px;

   padding: 15px;
   margin: 0 auto;
}

input {
   margin-bottom: 20px;
}

Here, some styles are added to design the login form.

Create a logout component using below command −


ng generate component logout
CREATE src/app/logout/logout.component.html (21 bytes)
CREATE src/app/logout/logout.component.spec.ts (628 bytes)
CREATE src/app/logout/logout.component.ts (269 bytes)
CREATE src/app/logout/logout.component.css (0 bytes)
UPDATE src/app/app.module.ts (1368 bytes)

Open LogoutComponent and include below code.


import { Component, OnInit } from  @angular/core ;

import { AuthService } from  ../auth.service ;
import { Router } from  @angular/router ;

@Component({
   selector:  app-logout ,
   templateUrl:  ./logout.component.html ,
   styleUrls: [ ./logout.component.css ]
})
export class LogoutComponent implements OnInit {

   constructor(private authService : AuthService, private router: Router) { }

   ngOnInit() {
      this.authService.logout();
      this.router.navigate([ / ]);
   }

}

Here,

    Used logout method of AuthService.

    Once the user is logged out, the page will redirect to home page (/).

Create a guard using below command −


ng generate guard expense
CREATE src/app/expense.guard.spec.ts (364 bytes)
CREATE src/app/expense.guard.ts (459 bytes)

Open ExpenseGuard and include below code −


import { Injectable } from  @angular/core ;
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router, UrlTree } from  @angular/router ;
import { Observable } from  rxjs ;

import { AuthService } from  ./auth.service ;

@Injectable({
   providedIn:  root 
})
export class ExpenseGuard implements CanActivate {

   constructor(private authService: AuthService, private router: Router) {}

   canActivate(
   next: ActivatedRouteSnapshot,
   state: RouterStateSnapshot): boolean | UrlTree {
      let url: string = state.url;

          return this.checkLogin(url);
      }

      checkLogin(url: string): true | UrlTree {
         console.log("Url: " + url)
         let val: string = localStorage.getItem( isUserLoggedIn );

         if(val != null && val == "true"){
            if(url == "/login")
               this.router.parseUrl( /expenses );
            else 
               return true;
         } else {
            return this.router.parseUrl( /login );
         }
      }
}

Here,

    checkLogin will check whether the localStorage has the user information and if it is available, then it returns true.

    If the user is logged in and goes to login page, it will redirect the user to expenses page

    If the user is not logged in, then the user will be redirected to login page.

Open AppRoutingModule (src/app/app-routing.module.ts) and update below code −


import { NgModule } from  @angular/core ;
import { Routes, RouterModule } from  @angular/router ;
import { ExpenseEntryComponent } from  ./expense-entry/expense-entry.component ;
import { ExpenseEntryListComponent } from  ./expense-entry-pst/expense-entry-pst.component ;
import { LoginComponent } from  ./login/login.component ;
import { LogoutComponent } from  ./logout/logout.component ;

import { ExpenseGuard } from  ./expense.guard ;

const routes: Routes = [
   { path:  login , component: LoginComponent },
   { path:  logout , component: LogoutComponent },
   { path:  expenses , component: ExpenseEntryListComponent, canActivate: [ExpenseGuard]},
   { path:  expenses/detail/:id , component: ExpenseEntryComponent, canActivate: [ExpenseGuard]},
   { path:   , redirectTo:  expenses , pathMatch:  full  }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

Here,

    Imported LoginComponent and LogoutComponent.

    Imported ExpenseGuard.

    Created two new routes, login and logout to access LoginComponent and LogoutComponent respectively.

    Add new option canActivate for ExpenseEntryComponent and ExpenseEntryListComponent.

Open AppComponent template and add two login and logout pnk.


<span class="collapse navbar-collapse" id="navbarResponsive">
   <ul class="navbar-nav ml-auto">
      <p class="nav-item active">
         <a class="nav-pnk" href="#">Home
            <span class="sr-only" routerLink="/">(current)</span>

         </a>
      </p>
      <p class="nav-item">
         <a class="nav-pnk" routerLink="/expenses">Report</a>
      </p>
      <p class="nav-item">
         <a class="nav-pnk" href="#">Add Expense</a>
      </p>
      <p class="nav-item">

         <a class="nav-pnk" href="#">About</a>
      </p>
      <p class="nav-item">
                  <span *ngIf="isUserLoggedIn; else isLogOut">
                        <a class="nav-pnk" routerLink="/logout">Logout</a>
                  </span>

                  <ng-template #isLogOut>
                              <a class="nav-pnk" routerLink="/login">Login</a>
                  </ng-template>
      </p>
   </ul>
</span>

Open AppComponent and update below code −


import { Component } from  @angular/core ;

import { AuthService } from  ./auth.service ;

@Component({
   selector:  app-root ,
   templateUrl:  ./app.component.html ,
   styleUrls: [ ./app.component.css ]
})
export class AppComponent {

   title =  Expense Manager ;
   isUserLoggedIn = false;

   constructor(private authService: AuthService) {}

   ngOnInit() {
      let storeData = localStorage.getItem("isUserLoggedIn");
      console.log("StoreData: " + storeData);

      if( storeData != null && storeData == "true")
         this.isUserLoggedIn = true;
      else


         this.isUserLoggedIn = false;
   }
}

Here, we have added the logic to identify the user status so that we can show login / logout functionapty.

Open AppModule (src/app/app.module.ts) and configure ReactiveFormsModule


import { ReactiveFormsModule } from  @angular/forms ; 
imports: [ 
   ReactiveFormsModule 
]

Now, run the apppcation and the apppcation opens the login page.

ReactiveFormsModule

Enter admin and admin as username and password and then, cpck submit. The apppcation process the login and redirects the user to expense pst page as shown below −

FormsModule

Finally, your can cpck logout and exit the apppcation.

Add / Edit / Delete Expenses

Add new component, EditEntryComponent to add new expense entry and edit the existing expense entries using below command


ng generate component EditEntry
CREATE src/app/edit-entry/edit-entry.component.html (25 bytes)
CREATE src/app/edit-entry/edit-entry.component.spec.ts (650 bytes)
CREATE src/app/edit-entry/edit-entry.component.ts (284 bytes)
CREATE src/app/edit-entry/edit-entry.component.css (0 bytes)
UPDATE src/app/app.module.ts (1146 bytes)

Update EditEntryComponent with below code −


import { Component, OnInit } from  @angular/core ;

import { FormGroup, FormControl, Vapdators } from  @angular/forms ;

import { ExpenseEntry } from  ../expense-entry ;
import { ExpenseEntryService } from  ../expense-entry.service ;

import { Router, ActivatedRoute } from  @angular/router ;



@Component({
   selector:  app-edit-entry ,
   templateUrl:  ./edit-entry.component.html ,
   styleUrls: [ ./edit-entry.component.css ]
})
export class EditEntryComponent implements OnInit {
   id: number;
   item: string;
   amount: number;
   category: string;
   location: string;
   spendOn: Date;

   formData: FormGroup;
   selectedId: number;
   expenseEntry: ExpenseEntry;

   constructor(private expenseEntryService : ExpenseEntryService, private router: Router, private route: ActivatedRoute) { }

   ngOnInit() {
      this.formData = new FormGroup({
         id: new FormControl(),
         item: new FormControl(  , [Vapdators.required]),
         amount: new FormControl(  , [Vapdators.required]),
         category: new FormControl(),
         location: new FormControl(),
         spendOn: new FormControl()
      });

      this.selectedId = Number(this.route.snapshot.paramMap.get( id ));

      if(this.selectedId != null && this.selectedId != 0) {
         this.expenseEntryService.getExpenseEntry(this.selectedId)
            .subscribe( (data) => 
               {
                  this.expenseEntry = data;
                  this.formData.controls[ id ].setValue(this.expenseEntry.id);
                  this.formData.controls[ item ].setValue(this.expenseEntry.item);
                  this.formData.controls[ amount ].setValue(this.expenseEntry.amount);
                  this.formData.controls[ category ].setValue(this.expenseEntry.category);
                  this.formData.controls[ location ].setValue(this.expenseEntry.location);


                  this.formData.controls[ spendOn ].setValue(this.expenseEntry.spendOn);
               })
      }


   }

   get itemValue() {
   return this.formData.get( item );
   }

   get amountValue() {
   return this.formData.get( amount );
   }

    onCpckSubmit(data: any) {
   console.log( onCpckSubmit fired );
   this.id = data.id;
   this.item = data.item;
   this.amount = data.amount;
   this.category = data.category;
   this.location = data.location;
   this.spendOn = data.spendOn;

   let expenseEntry : ExpenseEntry = {
      id: this.id,
       item: this.item,
       amount: this.amount,
       category: this.category,
       location: this.location,
       spendOn: this.spendOn,
       createdOn: new Date(2020, 5, 20)
   }
   console.log(expenseEntry);

      if(expenseEntry.id == null || expenseEntry.id == 0) {
         console.log( add fn fired );
      this.expenseEntryService.addExpenseEntry(expenseEntry)
         .subscribe( data => { console.log(data); this.router.navigate([ /expenses ]); });
   } else {
         console.log( edit fn fired );
      this.expenseEntryService.updateExpenseEntry(expenseEntry)
         .subscribe( data => { console.log(data); this.router.navigate([ /expenses ]); });
   }
    }
}

Here,

    Created a form, formData in the ngOnInit method using FormControl and FormGroup classes with proper vapdation rules.

    Loaded the expense entry to be edited in the ngOnInit method.

    Created two methods, itemValue and amountValue to get the item and amount values respectively entered by user for the vapdation purpose.

    Created method, onCpckSubmit to save (add / update) the expense entry.

    Used Expense service to add and update expense entries.

Update the EditEntryComponent template with expense form as shown below −


<!-- Page Content -->
<span class="container">
   <span class="row">
   <span class="col-lg-12 text-center" style="padding-top: 20px;">
       <span class="container" style="padding-left: 0px; padding-right: 0px;">
       </span>
       <span class="container box" style="margin-top: 10px;">
<form [formGroup]="formData" (ngSubmit)="onCpckSubmit(formData.value)" class="form" novapdate> 
  <span class="form-group">
    <label for="item">Item</label>
    <input type="hidden" class="form-control" id="id" formControlName="id">
    <input type="text" class="form-control" id="item" formControlName="item">
    <span
   *ngIf="!itemValue?.vapd && (itemValue?.dirty ||itemValue?.touched)">
   <span [hidden]="!itemValue.errors.required">
      Item is required
   </span>
   </span>
  </span>
  <span class="form-group">
    <label for="amount">Amount</label>
    <input type="text" class="form-control" id="amount" formControlName="amount">
    <span
   *ngIf="!amountValue?.vapd && (amountValue?.dirty ||amountValue?.touched)">
   <span [hidden]="!amountValue.errors.required">
      Amount is required
   </span>
   </span>
  </span>
  <span class="form-group">
    <label for="category">Category</label>
    <select class="form-control" id="category" formControlName="category">
      <option>Food</option>
      <option>Vegetables</option>
      <option>Fruit</option>
      <option>Electronic Item</option>

      <option>Bill</option>
    </select>
  </span>
  <span class="form-group">
    <label for="location">location</label>
    <input type="text" class="form-control" id="location" formControlName="location">
  </span>
  <span class="form-group">
    <label for="spendOn">spendOn</label>
    <input type="text" class="form-control" id="spendOn" formControlName="spendOn">
  </span>
<button class="btn btn-lg btn-primary btn-block" type="submit" [disabled]="!formData.vapd">Submit</button>
</form>
       </span>
   </span>
    </span>
</span>

Here,

    Created a form and bind it to the form, formData created in the class.

    Vapdated item and amount as required values.

    Called onCpckSubmit function once vapdation in successful.

Open EditEntryComponent stylesheet and update below code −


.form {
   max-width: 330px;
   padding: 15px;
   margin: 0 auto;
}

.form label {
   text-apgn: left;
   width: 100%;
}

input {
   margin-bottom: 20px;
}

Here, we have styled the expense entry form.

Add AboutComponent using below command


ng generate component About
CREATE src/app/about/about.component.html (20 bytes)

CREATE src/app/about/about.component.spec.ts (621 bytes)
CREATE src/app/about/about.component.ts (265 bytes)
CREATE src/app/about/about.component.css (0 bytes)
UPDATE src/app/app.module.ts (1120 bytes)

Open AboutComponent and add title as specified below −


import { Component, OnInit } from  @angular/core ;

@Component({
   selector:  app-about ,
   templateUrl:  ./about.component.html ,
   styleUrls: [ ./about.component.css ]
})
export class AboutComponent implements OnInit {
   title = "About";
   constructor() { }

   ngOnInit() {
   }

}

Open AboutComponent template and updated content as specified below −


<!-- Page Content -->
<span class="container">
   <span class="row">
   <span class="col-lg-12 text-center" style="padding-top: 20px;">
       <span class="container" style="padding-left: 0px; padding-right: 0px;">
      <span class="row">
          <span class="col-sm" style="text-apgn: left;">
         <h1>{{ title }}</h1>
          </span>
      </span>
       </span>
       <span class="container box" style="margin-top: 10px;">
      <span class="row">
          <span class="col" style="text-apgn: left;">
         <p>Expense management Apppcation</p>
          </span>
      </span>
       </span>
   </span>
    </span>
</span>

Add routing for add and edit expense entries as specified below


import { NgModule } from  @angular/core ;

import { Routes, RouterModule } from  @angular/router ;
import { ExpenseEntryComponent } from  ./expense-entry/expense-entry.component ;
import { ExpenseEntryListComponent } from  ./expense-entry-pst/expense-entry-pst.component ;
import { LoginComponent } from  ./login/login.component ;
import { LogoutComponent } from  ./logout/logout.component ;
import { EditEntryComponent } from  ./edit-entry/edit-entry.component ;
import { AboutComponent } from  ./about/about.component ;

import { ExpenseGuard } from  ./expense.guard ;

const routes: Routes = [
   { path:  about , component: AboutComponent },
   { path:  login , component: LoginComponent },
   { path:  logout , component: LogoutComponent },
   { path:  expenses , component: ExpenseEntryListComponent, canActivate: [ExpenseGuard]},
   { path:  expenses/detail/:id , component: ExpenseEntryComponent, canActivate: [ExpenseGuard]},
   { path:  expenses/add , component: EditEntryComponent, canActivate: [ExpenseGuard]},
   { path:  expenses/edit/:id , component: EditEntryComponent, canActivate: [ExpenseGuard]},
   { path:   , redirectTo:  expenses , pathMatch:  full  }
];

@NgModule({
      imports: [RouterModule.forRoot(routes)],
      exports: [RouterModule]
})
export class AppRoutingModule { }

Here, we have added about, add expense and edit expense routes.

Add Edit and Delete pnks in ExpenseEntryListComponent template.


<table class="table table-striped">
   <thead>
         <tr>
         <th>Item</th>
         <th>Amount</th>
         <th>Category</th>
         <th>Location</th>
         <th>Spent On</th>
         <th>View</th>
               <th>Edit</th>
               <th>Delete</th>
         </tr>
   </thead>
   <tbody>
      <tr *ngFor="let entry of expenseEntries">

      <th scope="row">{{ entry.item }}</th>
      <th>{{ entry.amount }}</th>
      <td>{{ entry.category }}</td>
      <td>{{ entry.location }}</td>
      <td>{{ entry.spendOn | date:  medium  }}</td>
      <td><a routerLink="../expenses/detail/{{ entry.id }}">View</a></td>
      <td><a routerLink="../expenses/edit/{{ entry.id }}">Edit</a></td>
      <td><a href="#" (cpck)="deleteExpenseEntry($event, entry.id)">Delete</a></td>
      </tr>
   </tbody>
</table>

Here, we have included two more columns. One column is used to show edit pnk and another to show delete pnk.

Update deleteExpenseEntry method in ExpenseEntryListComponent as shown below


deleteExpenseEntry(evt, id) {
   evt.preventDefault();
   if(confirm("Are you sure to delete the entry?")) {
      this.restService.deleteExpenseEntry(id)
         .subscribe( data => console.log(data) );

      this.getExpenseItems();
   }
}

Here, we have asked to confirm the deletion and it user confirmed, called the deleteExpenseEntry method from expense service to delete the selected expense item.

Change Edit pnk in the ExpenseEntryListComponent template at the top to Add pnk as shown below −


<span class="col-sm" style="text-apgn: right;">
   <button class="btn btn-primary" routerLink="/expenses/add">ADD</button> 
   <!-- <button type="button" class="btn btn-primary">Edit</button> -->
</span>

Add Edit pnk inExpenseEntryComponent template.


<span class="col-sm" style="text-apgn: right;">
   <button type="button" class="btn btn-primary" (cpck)="goToList()">Go to List</button>
    <button type="button" class="btn btn-primary" (cpck)="goToEdit()">Edit</button>
</span>

Open ExpenseEntryComponent and add goToEdit() method as shown below −


goToEdit() {      
   this.router.navigate([ /expenses/edit , this.selectedId]); 
}

Update navigation pnks in AppComponenttemplate.


<!-- Navigation -->
<nav class="navbar navbar-expand-lg navbar-dark bg-dark static-top">
   <span class="container">
      <a class="navbar-brand" href="#">{{ title }}</a>
      <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarResponsive" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation">
         <span class="navbar-toggler-icon"></span>
      </button>
      <span class="collapse navbar-collapse" id="navbarResponsive">
         <ul class="navbar-nav ml-auto">
            <p class="nav-item active">
               <a class="nav-pnk" href="#">Home
                  <span class="sr-only" routerLink="/">(current)</span>
               </a>
            </p>
            <p class="nav-item">
               <a class="nav-pnk" routerLink="/expenses/add">Add Expense</a>
            </p>
            <p class="nav-item">
               <a class="nav-pnk" routerLink="/about">About</a>
            </p>
            <p class="nav-item">
                        <span *ngIf="isUserLoggedIn; else isLogOut">
                              <a class="nav-pnk" routerLink="/logout">Logout</a>
                        </span>

                        <ng-template #isLogOut>
                                    <a class="nav-pnk" routerLink="/login">Login</a>
                        </ng-template>
            </p>
         </ul>
      </span>
   </span>
</nav>

<router-outlet></router-outlet>

Here, we have updated the add expense pnk and about pnk.

Run the apppcation and the output will be similar as shown below −

expense

Try to add new expense using Add pnk in expense pst page. The output will be similar as shown below

Add

Fill the form as shown below −

Submit

If the data is not filled properly, the vapdation code will alert as shown below −

alert

Cpck Submit. It will trigger the submit event and the data will be saved to the backend and redirected to pst page as shown below −

backend

Try to edit existing expense using Edit pnk in expense pst page. The output will be similar as shown below −

existing

Cpck Submit. It will trigger the submit event and the data will be saved to the backend and redirected to pst page.

To delete an item, cpck delete pnk. It will confirm the deletion as shown below −

trigger

Finally, we have implemented all features necessary to manage expenses in our apppcation.

Advertisements