Angular 8 App With Cosmos DB And Azure Functions

Introduction

 
Azure Functions is an efficient solution to run small pieces of code, or "functions," easily in the cloud. We can write just the code we need for the problem without worrying about a whole application or the infrastructure to run it. Functions can make development even more productive and we can use our development language of choice, such as C#, F#, Node.js, Java, or PHP.
 
We pay only for the time our code runs and trust Azure to scale as needed. Azure Functions lets you develop serverless applications on Microsoft Azure. We will create our Azure function in Visual Studio for testing purpose. We can publish this function to the Azure portal very easily. We will use Cosmos DB to store and retrieve the data. Cosmos DB is the most demanding No SQL database in current days. For testing purpose, we will use local Cosmos DB emulator. We will create an Angular 8 Employee application and consume these Azure functions as a REST API service.
 
Create Cosmos DB database and collection in local emulator.
 
You can download Cosmos DB free emulator from this URL. After downloading and installing the emulator you can run it. This will be opened in the browser.
 
Angular 8 App with Cosmos DB and Azure Function
 
We will later use the URI and Key in our Azure function app to connect with Cosmos DB.
 
We can click the “Explorer” tab and create a new Cosmos DB database and collection.
 
Angular 8 App with Cosmos DB and Azure Function 
 
We must give a valid name for the database and collection. Please give partition key as well. Partition key is very important in Cosmos DB. We cannot change the partition key once we have created the collection. So, we must be very careful to choose the partition key. All the data is stored based on the partition key.
 

Create Azure Function in Visual Studio

 
We can open Visual Studio 2017 or later version and choose Azure function template. You must install Azure SDK to get this template. You can even install Azure function extension separately too. Choose “Azure Functions” template from the “Cloud” tab.
 
Angular 8 App with Cosmos DB and Azure Function
 
Various trigger types are supported in Azure Function. We can choose "Http trigger" for our purposes.
 
Angular 8 App with Cosmos DB and Azure Function
 
A static "Function1" class will be created automatically with our function. We can rename this file to "CosmosFunctions". We can remove the default code lines from this class file. We will add separate functions for CRUD actions inside this file later.
 
As I mentioned earlier, we are creating an Employee app. Hence, we can create “Employee” model class inside a new “Data” folder.
 
Employee.cs
  1. using Newtonsoft.Json;  
  2.   
  3. namespace AzureFunctionCosmos.Data  
  4. {  
  5.     public class Employee  
  6.     {  
  7.         [JsonProperty(PropertyName = "id")]  
  8.         public string Id { getset; }  
  9.         public string Name { getset; }  
  10.         public string Address { getset; }  
  11.         public string Gender { getset; }  
  12.         public string Company { getset; }  
  13.         public string Designation { getset; }  
  14.         public string Cityname { getset; }  
  15.     }  
  16. }  
I have added all the field names required for our Cosmos DB collection. Also, note that I have added a “JsonProperty” attribute for “Id” property. Because, Cosmos DB automatically creates an “id” field for each record.
 
We can install “Microsoft.Azure.DocumentDB.Core” NuGet package in our project. This will be used to connect with Cosmos DB. You can install the latest version.
 
Angular 8 App with Cosmos DB and Azure Function
 
We can create an “IDocumentDBRepository” interface inside it. This interface contains all the methods declaration for our Cosmos DB repository. We will implement this interface in the “DocumentDBRepository” class.
 
IDocumentDBRepository.cs
  1. using Microsoft.Azure.Documents;  
  2. using System;  
  3. using System.Collections.Generic;  
  4. using System.Linq.Expressions;  
  5. using System.Threading.Tasks;  
  6.   
  7. namespace AzureFunctionCosmos.Data  
  8. {  
  9.     public interface IDocumentDBRepository<T> where T : class  
  10.     {  
  11.         Task<Document> CreateItemAsync(T item, string collectionId);  
  12.         Task DeleteItemAsync(string id, string collectionId, string partitionKey);  
  13.         Task<IEnumerable<T>> GetItemsAsync(Expression<Func<T, bool>> predicate, string collectionId);  
  14.         Task<IEnumerable<T>> GetItemsAsync(string collectionId);  
  15.         Task<Document> UpdateItemAsync(string id, T item, string collectionId);  
  16.     }  
  17. }  
I have added all the methods declaration for CRUD actions for Azure functions in the above interface.
 
We can implement this interface in the “DocumentDBRepository” class.
 
DocumentDBRepository.cs
  1. using Microsoft.Azure.Documents;  
  2. using Microsoft.Azure.Documents.Client;  
  3. using Microsoft.Azure.Documents.Linq;  
  4. using System;  
  5. using System.Collections.Generic;  
  6. using System.Linq;  
  7. using System.Linq.Expressions;  
  8. using System.Threading.Tasks;  
  9.   
  10. namespace AzureFunctionCosmos.Data  
  11. {  
  12.     public class DocumentDBRepository<T> : IDocumentDBRepository<T> where T : class  
  13.     {  
  14.   
  15.         private readonly string Endpoint = "https://localhost:8081/";  
  16.         private readonly string Key = "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==";  
  17.         private readonly string DatabaseId = "SarathCosmosDB";  
  18.         private DocumentClient client;  
  19.   
  20.         public DocumentDBRepository()  
  21.         {  
  22.             client = new DocumentClient(new Uri(Endpoint), Key);  
  23.         }  
  24.   
  25.         public async Task<IEnumerable<T>> GetItemsAsync(Expression<Func<T, bool>> predicate, string collectionId)  
  26.         {  
  27.             IDocumentQuery<T> query = client.CreateDocumentQuery<T>(  
  28.                 UriFactory.CreateDocumentCollectionUri(DatabaseId, collectionId),  
  29.                 new FeedOptions { MaxItemCount = -1 })  
  30.                 .Where(predicate)  
  31.                 .AsDocumentQuery();  
  32.   
  33.             List<T> results = new List<T>();  
  34.             while (query.HasMoreResults)  
  35.             {  
  36.                 results.AddRange(await query.ExecuteNextAsync<T>());  
  37.             }  
  38.   
  39.             return results;  
  40.         }  
  41.   
  42.         public async Task<IEnumerable<T>> GetItemsAsync(string collectionId)  
  43.         {  
  44.             IDocumentQuery<T> query = client.CreateDocumentQuery<T>(  
  45.                 UriFactory.CreateDocumentCollectionUri(DatabaseId, collectionId),  
  46.                 new FeedOptions { MaxItemCount = -1 })  
  47.                 .AsDocumentQuery();  
  48.   
  49.             List<T> results = new List<T>();  
  50.             while (query.HasMoreResults)  
  51.             {  
  52.                 results.AddRange(await query.ExecuteNextAsync<T>());  
  53.             }  
  54.   
  55.             return results;  
  56.         }  
  57.   
  58.         public async Task<Document> CreateItemAsync(T item, string collectionId)  
  59.         {  
  60.             return await client.CreateDocumentAsync(UriFactory.CreateDocumentCollectionUri(DatabaseId, collectionId), item);  
  61.         }  
  62.   
  63.         public async Task<Document> UpdateItemAsync(string id, T item, string collectionId)  
  64.         {  
  65.             return await client.ReplaceDocumentAsync(UriFactory.CreateDocumentUri(DatabaseId, collectionId, id), item);  
  66.         }  
  67.   
  68.         public async Task DeleteItemAsync(string id, string collectionId, string partitionKey)  
  69.         {  
  70.             await client.DeleteDocumentAsync(UriFactory.CreateDocumentUri(DatabaseId, collectionId, id),  
  71.             new RequestOptions() { PartitionKey = new PartitionKey(partitionKey) });  
  72.         }  
  73.   
  74.         private async Task CreateDatabaseIfNotExistsAsync()  
  75.         {  
  76.             try  
  77.             {  
  78.                 await client.ReadDatabaseAsync(UriFactory.CreateDatabaseUri(DatabaseId));  
  79.             }  
  80.             catch (DocumentClientException e)  
  81.             {  
  82.                 if (e.StatusCode == System.Net.HttpStatusCode.NotFound)  
  83.                 {  
  84.                     await client.CreateDatabaseAsync(new Database { Id = DatabaseId });  
  85.                 }  
  86.                 else  
  87.                 {  
  88.                     throw;  
  89.                 }  
  90.             }  
  91.         }  
  92.   
  93.         private async Task CreateCollectionIfNotExistsAsync(string collectionId)  
  94.         {  
  95.             try  
  96.             {  
  97.                 await client.ReadDocumentCollectionAsync(UriFactory.CreateDocumentCollectionUri(DatabaseId, collectionId));  
  98.             }  
  99.             catch (DocumentClientException e)  
  100.             {  
  101.                 if (e.StatusCode == System.Net.HttpStatusCode.NotFound)  
  102.                 {  
  103.                     await client.CreateDocumentCollectionAsync(  
  104.                         UriFactory.CreateDatabaseUri(DatabaseId),  
  105.                         new DocumentCollection { Id = collectionId },  
  106.                         new RequestOptions { OfferThroughput = 1000 });  
  107.                 }  
  108.                 else  
  109.                 {  
  110.                     throw;  
  111.                 }  
  112.             }  
  113.         }  
  114.     }  
  115. }  
I have implemented all the CRUD actions inside the above class. We can use these methods inside our Azure functions.
 
Create a “GetAll” function to get all Cosmos DB employee collection data. We will create this function as HTTP Trigger.
  1. public static class GetAll  
  2. {  
  3.     [FunctionName("GetAll")]  
  4.     public static async Task<IEnumerable<Employee>> Run([HttpTrigger(AuthorizationLevel.Function, "get", Route = "Get")]HttpRequest req, TraceWriter log)  
  5.     {  
  6.         log.Info("C# HTTP trigger function to get all data from Cosmos DB");  
  7.   
  8.         IDocumentDBRepository<Employee> Respository = new DocumentDBRepository<Employee>();  
  9.         return await Respository.GetItemsAsync("Employee");  
  10.     }  
  11. }  
Create remaining functions for other CRUD actions in the same file as Http trigger functions.
 
We have created the same function ”CreateOrUpdate” for POST and PUT HTTP methods.
 
CosmosFunctions.cs
  1. using AzureFunctionCosmos.Data;  
  2. using Microsoft.AspNetCore.Http;  
  3. using Microsoft.Azure.WebJobs;  
  4. using Microsoft.Azure.WebJobs.Extensions.Http;  
  5. using Microsoft.Azure.WebJobs.Host;  
  6. using Newtonsoft.Json;  
  7. using System.Collections.Generic;  
  8. using System.IO;  
  9. using System.Threading.Tasks;  
  10.   
  11. namespace AzureFunctionCosmos  
  12. {  
  13.     public static class GetAll  
  14.     {  
  15.         [FunctionName("GetAll")]  
  16.         public static async Task<IEnumerable<Employee>> Run([HttpTrigger(AuthorizationLevel.Function, "get", Route = "Get")]HttpRequest req, TraceWriter log)  
  17.         {  
  18.             log.Info("C# HTTP trigger function to get all data from Cosmos DB");  
  19.   
  20.             IDocumentDBRepository<Employee> Respository = new DocumentDBRepository<Employee>();  
  21.             return await Respository.GetItemsAsync("Employee");  
  22.         }  
  23.     }  
  24.   
  25.     public static class GetSingle  
  26.     {  
  27.         [FunctionName("GetSingle")]  
  28.         public static async Task<Employee> Run([HttpTrigger(AuthorizationLevel.Function, "get", Route = "Get/{id}/{cityName}")]HttpRequest req, TraceWriter log, string id, string cityName)  
  29.         {  
  30.             log.Info("C# HTTP trigger function to get a single data from Cosmos DB");  
  31.   
  32.             IDocumentDBRepository<Employee> Respository = new DocumentDBRepository<Employee>();  
  33.             var employees = await Respository.GetItemsAsync(d => d.Id == id && d.Cityname == cityName, "Employee");  
  34.             Employee employee = new Employee();  
  35.             foreach (var emp in employees)  
  36.             {  
  37.                 employee = emp;  
  38.                 break;  
  39.             }  
  40.             return employee;  
  41.         }  
  42.     }  
  43.   
  44.     public static class CreateOrUpdate  
  45.     {  
  46.         [FunctionName("CreateOrUpdate")]  
  47.         public static async Task<bool> Run([HttpTrigger(AuthorizationLevel.Function, "post""put", Route = "CreateOrUpdate")]HttpRequest req, TraceWriter log)  
  48.         {  
  49.             log.Info("C# HTTP trigger function to create a record into Cosmos DB");  
  50.             try  
  51.             {  
  52.                 IDocumentDBRepository<Employee> Respository = new DocumentDBRepository<Employee>();  
  53.                 string requestBody = await new StreamReader(req.Body).ReadToEndAsync();  
  54.                 var employee = JsonConvert.DeserializeObject<Employee>(requestBody);  
  55.                 if (req.Method == "POST")  
  56.                 {  
  57.                     employee.Id = null;  
  58.                     await Respository.CreateItemAsync(employee, "Employee");  
  59.                 }  
  60.                 else  
  61.                 {  
  62.                     await Respository.UpdateItemAsync(employee.Id, employee, "Employee");  
  63.                 }  
  64.                 return true;  
  65.             }  
  66.             catch  
  67.             {  
  68.                 log.Info("Error occured while creating a record into Cosmos DB");  
  69.                 return false;  
  70.             }  
  71.   
  72.         }  
  73.     }  
  74.   
  75.     public static class Delete  
  76.     {  
  77.         [FunctionName("Delete")]  
  78.         public static async Task<bool> Run([HttpTrigger(AuthorizationLevel.Function, "delete", Route = "Delete/{id}/{cityName}")]HttpRequest req, TraceWriter log, string id, string cityName)  
  79.         {  
  80.             log.Info("C# HTTP trigger function to delete a record from Cosmos DB");  
  81.   
  82.             IDocumentDBRepository<Employee> Respository = new DocumentDBRepository<Employee>();  
  83.             try  
  84.             {  
  85.                 await Respository.DeleteItemAsync(id, "Employee", cityName);  
  86.                 return true;  
  87.             }  
  88.             catch  
  89.             {  
  90.                 return false;  
  91.             }  
  92.         }  
  93.     }  
  94. }  
We have completed all the functions for CRUD actions. We can run the function and test using Postman or any other tool.
 
When we start the Azure function app, Microsoft Azure Emulator will be loaded automatically.
 
Angular 8 App with Cosmos DB and Azure Function
 
You can also notice that a “func.exe” will be executed and showing below details.
 
Angular 8 App with Cosmos DB and Azure Function
 
We can see endpoints for all four functions in the console screen. We can test these functions in the same way we test Web APIs.
 
We are going to consume these functions inside our Angular 8 application. Hence, we must enable CORS (Cross-Origin Resource Sharing) in “local.settings.json” file.
 
local.settings.json
  1. {  
  2.   "IsEncrypted"false,  
  3.   "Values": {  
  4.     "AzureWebJobsStorage""UseDevelopmentStorage=true",  
  5.     "FUNCTIONS_WORKER_RUNTIME""dotnet"  
  6.   },  
  7.   "Host": {  
  8.     "CORS""*"  
  9.   }  
  10. }  
We have successfully completed the Azure functions and connected with Cosmos DB. We can create an Angular 8 Employee application now.
 

Create Angular 8 Application using CLI

 
Please ensure that you have already installed Angular CLI version 8 and node.js version 12 or later in your machine before creating the Angular application.
  1. ng new Angular8Cosmos  
Above command will create a new Angular 8 application and create all node dependencies.
 
We are using some bootstrap classes and font awesome icons. Hence, we can install both packages for our project.
 
We can import these libraries inside the styles.css file.
 
styles.css
  1. /* You can add global styles to this file, and also import other style files */  
  2. @import "~bootstrap/dist/css/bootstrap.css";  
  3. @import "~font-awesome/css/font-awesome.css";  
Now, these classes and icons can be used anywhere in the project without further references.
 
We can create a header, footer, and layout components using below commands.
  1. ng g component ui/header  
  2. ng g component ui/footer  
  3. ng g component ui/layout  
Copy the below code and paste into corresponding files.
 
header.component.html
  1. <nav class="navbar navbar-dark bg-dark mb-5">  
  2.   <a class="navbar-brand" href="/">Employee App</a>  
  3.   <div class="navbar-expand mr-auto">  
  4.     <div class="navbar-nav">  
  5.       <a class="nav-item nav-link active" routerLink="home" routerLinkActive="active">Home</a>  
  6.       <a class="nav-item nav-link" routerLink="employees">Employee</a>  
  7.     </div>  
  8.   </div>  
  9.   <div class="navbar-expand ml-auto navbar-nav">  
  10.     <div class="navbar-nav">  
  11.       <a class="nav-item nav-link" href="https://github.com/sarathlalsaseendran" target="_blank">  
  12.         <i class="fa fa-github"></i>  
  13.       </a>  
  14.       <a class="nav-item nav-link" href="https://twitter.com/sarathlalsasee1" target="_blank">  
  15.         <i class="fa fa-twitter"></i>  
  16.       </a>  
  17.       <a class="nav-item nav-link" href="https://codewithsarath.com" target="_blank">  
  18.         <i class="fa fa-wordpress"></i>  
  19.       </a>  
  20.     </div>  
  21.   </div>  
  22. </nav>  
footer.component.html
  1. <nav class="navbar navbar-dark bg-dark mt-5 fixed-bottom">  
  2.   <div class="navbar-expand m-auto navbar-text">  
  3.     Developed with <i class="fa fa-heart"></i> by <a href="https://codewithsarath.com" target="_blank">Sarath Lal</a>  
  4.   </div>  
  5. </nav>  
layout.component.html
  1. <app-header></app-header>  
  2. <div class="container">  
  3.   <ng-content></ng-content>  
  4. </div>  
  5. <app-footer></app-footer>  
Please copy below code and paste in the file.
 
app.component.html
  1. <app-layout>  
  2.   <router-outlet></router-outlet>  
  3. </app-layout>  
We can create a Home component using the below command.
  1. ng g component home  
We can modify the corresponding template file with the below code.
 
home.component.html
  1. <div style="text-align:center">  
  2.   <h2>  
  3.     Angular 8 App with Azure Function and Cosmos DB  
  4.   </h2>  
  5.   <p>  
  6.     Welcome to our new single-page application build with below technologies.  
  7.   </p>  
  8.   <img width="700" alt="Angular Logo"  
  9.     src="../../assets/angular-function-cosmos.png">  
  10. </div>  
We can create a GenericValidator class for validating Employee Name and City using the below command.
  1. ng g class shared/genericvalidator  
genericvalidator.ts
  1. import { FormGroup } from '@angular/forms';  
  2.   
  3. export class GenericValidator {  
  4.   
  5.   constructor(private validationMessages: { [key: string]: { [key: string]: string } }) {  
  6.   }  
  7.   
  8.   processMessages(container: FormGroup): { [key: string]: string } {  
  9.     const messages = {};  
  10.     for (const controlKey in container.controls) {  
  11.       if (container.controls.hasOwnProperty(controlKey)) {  
  12.         const c = container.controls[controlKey];  
  13.         // If it is a FormGroup, process its child controls.  
  14.         if (c instanceof FormGroup) {  
  15.           const childMessages = this.processMessages(c);  
  16.           Object.assign(messages, childMessages);  
  17.         } else {  
  18.           // Only validate if there are validation messages for the control  
  19.           if (this.validationMessages[controlKey]) {  
  20.             messages[controlKey] = '';  
  21.             if ((c.dirty || c.touched) && c.errors) {  
  22.               Object.keys(c.errors).map(messageKey => {  
  23.                 if (this.validationMessages[controlKey][messageKey]) {  
  24.                   messages[controlKey] += this.validationMessages[controlKey][messageKey] + ' ';  
  25.                 }  
  26.               });  
  27.             }  
  28.           }  
  29.         }  
  30.       }  
  31.     }  
  32.     return messages;  
  33.   }  
  34.   
  35.   getErrorCount(container: FormGroup): number {  
  36.     let errorCount = 0;  
  37.     for (const controlKey in container.controls) {  
  38.       if (container.controls.hasOwnProperty(controlKey)) {  
  39.         if (container.controls[controlKey].errors) {  
  40.           errorCount += Object.keys(container.controls[controlKey].errors).length;  
  41.           console.log(errorCount);  
  42.         }  
  43.       }  
  44.     }  
  45.     return errorCount;  
  46.   }  
  47. }  
We can create an Employee interface inside the Employee folder using the below command.
  1. ng g interface employee/employee  
employee.ts
  1. export interface Employee {  
  2.     id: string,  
  3.     name: string,  
  4.     address: string,  
  5.     gender: string,  
  6.     company: string,  
  7.     designation: string,  
  8.     cityname: string  
  9. }  
Let us create Employee service now. This is the very important service class in our project. We can add all the logic for calling Azure functions inside this service.
  1. ng g service employee/employee  
employee.service.ts
  1. import { Injectable } from '@angular/core';  
  2. import { HttpClient, HttpHeaders } from '@angular/common/http';  
  3. import { Observable, throwError, of } from 'rxjs';  
  4. import { catchError, map } from 'rxjs/operators';  
  5. import { Employee } from './employee';  
  6.   
  7. @Injectable()  
  8. export class EmployeeService {  
  9.   private employeesUrl = 'http://localhost:7071/api/';  
  10.   
  11.   constructor(private http: HttpClient) { }  
  12.   
  13.   getEmployees(): Observable<Employee[]> {  
  14.     return this.http.get<Employee[]>(this.employeesUrl + 'Get')  
  15.       .pipe(  
  16.         catchError(this.handleError)  
  17.       );  
  18.   }  
  19.   
  20.   getEmployee(id: string, cityName: string): Observable<Employee> {  
  21.     if (id === '') {  
  22.       return of(this.initializeEmployee());  
  23.     }  
  24.     const url = `${this.employeesUrl + 'Get'}/${id}/${cityName}`;  
  25.     return this.http.get<Employee>(url)  
  26.       .pipe(  
  27.         catchError(this.handleError)  
  28.       );  
  29.   }  
  30.   
  31.   createEmployee(employee: Employee): Observable<Employee> {  
  32.     const headers = new HttpHeaders({ 'Content-Type''application/json' });  
  33.     return this.http.post<Employee>(this.employeesUrl + 'CreateOrUpdate', employee, { headers: headers })  
  34.       .pipe(  
  35.         catchError(this.handleError)  
  36.       );  
  37.   }  
  38.   
  39.   deleteEmployee(id: string, cityname: string): Observable<{}> {  
  40.     const headers = new HttpHeaders({ 'Content-Type''application/json' });  
  41.     const url = `${this.employeesUrl + 'Delete'}/${id}/${cityname}`;  
  42.     return this.http.delete<Employee>(url, { headers: headers })  
  43.       .pipe(  
  44.         catchError(this.handleError)  
  45.       );  
  46.   }  
  47.   
  48.   updateEmployee(employee: Employee): Observable<Employee> {  
  49.     const headers = new HttpHeaders({ 'Content-Type''application/json' });  
  50.     const url = this.employeesUrl + 'CreateOrUpdate';  
  51.     return this.http.put<Employee>(url, employee, { headers: headers })  
  52.       .pipe(  
  53.         map(() => employee),  
  54.         catchError(this.handleError)  
  55.       );  
  56.   }  
  57.   
  58.   private handleError(err) {  
  59.     let errorMessage: string;  
  60.     if (err.error instanceof ErrorEvent) {  
  61.       errorMessage = `An error occurred: ${err.error.message}`;  
  62.     } else {  
  63.       errorMessage = `Backend returned code ${err.status}: ${err.body.error}`;  
  64.     }  
  65.     console.error(err);  
  66.     return throwError(errorMessage);  
  67.   }  
  68.   
  69.   private initializeEmployee(): Employee {  
  70.     return {  
  71.       id: null,  
  72.       name: null,  
  73.       address: null,  
  74.       gender: null,  
  75.       company: null,  
  76.       designation: null,  
  77.       cityname: null  
  78.     };  
  79.   }  
  80. }    
Create employee list component now.
  1. ng g component employee/EmployeeList  
employee-list.component.ts
  1. import { Component, OnInit } from '@angular/core';    
  2. import { Employee } from '../employee';  
  3. import { EmployeeService } from '../employee.service';  
  4.     
  5. @Component({    
  6.   selector: 'app-employee-list',    
  7.   templateUrl: './employee-list.component.html',    
  8.   styleUrls: ['./employee-list.component.css']    
  9. })    
  10. export class EmployeeListComponent implements OnInit {    
  11.   pageTitle = 'Employee List';    
  12.   filteredEmployees: Employee[] = [];    
  13.   employees: Employee[] = [];    
  14.   errorMessage = '';    
  15.     
  16.   _listFilter = '';    
  17.   get listFilter(): string {    
  18.     return this._listFilter;    
  19.   }    
  20.   set listFilter(value: string) {    
  21.     this._listFilter = value;    
  22.     this.filteredEmployees = this.listFilter ? this.performFilter(this.listFilter) : this.employees;    
  23.   }    
  24.     
  25.   constructor(private employeeService: EmployeeService) { }    
  26.     
  27.   performFilter(filterBy: string): Employee[] {    
  28.     filterBy = filterBy.toLocaleLowerCase();    
  29.     return this.employees.filter((employee: Employee) =>    
  30.       employee.name.toLocaleLowerCase().indexOf(filterBy) !== -1);    
  31.   }    
  32.     
  33.   ngOnInit(): void {    
  34.     this.employeeService.getEmployees().subscribe(    
  35.       employees => {    
  36.         this.employees = employees;    
  37.         this.filteredEmployees = this.employees;    
  38.       },    
  39.       error => this.errorMessage = <any>error    
  40.     );    
  41.   }    
  42.     
  43.   deleteEmployee(id: string, name: string, cityname: string): void {    
  44.     if (id === '') {    
  45.       this.onSaveComplete();    
  46.     } else {    
  47.       if (confirm(`Are you sure want to delete this Employee: ${name}?`)) {    
  48.         this.employeeService.deleteEmployee(id, cityname)    
  49.           .subscribe(    
  50.             () => this.onSaveComplete(),    
  51.             (error: any) => this.errorMessage = <any>error    
  52.           );    
  53.       }    
  54.     }    
  55.   }    
  56.     
  57.   onSaveComplete(): void {    
  58.     this.employeeService.getEmployees().subscribe(    
  59.       employees => {    
  60.         this.employees = employees;    
  61.         this.filteredEmployees = this.employees;    
  62.       },    
  63.       error => this.errorMessage = <any>error    
  64.     );    
  65.   }    
  66.     
  67. }    
employee-list.component.html
  1. <div class="card">  
  2.   <div class="card-header">  
  3.     {{pageTitle}}  
  4.   </div>  
  5.   <div class="card-body">  
  6.     <div class="row" style="margin-bottom:15px;">  
  7.       <div class="col-md-2">Filter by:</div>  
  8.       <div class="col-md-4">  
  9.         <input type="text" [(ngModel)]="listFilter" />  
  10.       </div>  
  11.       <div class="col-md-3"></div>  
  12.       <div class="col-md-3">  
  13.         <button class="btn btn-primary mr-3" [routerLink]="['/employees/0/0/edit']">  
  14.           <i class="fa fa-plus"></i> New Employee  
  15.         </button>  
  16.       </div>  
  17.     </div>  
  18.     <div class="row" *ngIf="listFilter">  
  19.       <div class="col-md-6">  
  20.         <h4>Filtered by: {{listFilter}}</h4>  
  21.       </div>  
  22.     </div>  
  23.     <div class="table-responsive">  
  24.       <table class="table mb-0" *ngIf="employees && employees.length">  
  25.         <thead>  
  26.           <tr>  
  27.             <th>Name</th>  
  28.             <th>Address</th>  
  29.             <th>Gender</th>  
  30.             <th>Company</th>  
  31.             <th>Designation</th>  
  32.             <th></th>  
  33.             <th></th>  
  34.           </tr>  
  35.         </thead>  
  36.         <tbody>  
  37.           <tr *ngFor="let employee of filteredEmployees">  
  38.             <td>  
  39.               <a [routerLink]="['/employees', employee.id,employee.cityname]">  
  40.                 {{ employee.name }}  
  41.               </a>  
  42.             </td>  
  43.             <td>{{ employee.address }}</td>  
  44.             <td>{{ employee.gender }}</td>  
  45.             <td>{{ employee.company }}</td>  
  46.             <td>{{ employee.designation}} </td>  
  47.             <td>  
  48.               <button class="btn btn-outline-primary btn-sm"  
  49.                 [routerLink]="['/employees', employee.id, employee.cityname, 'edit']">  
  50.                 <i class="fa fa-edit"></i> Edit  
  51.               </button>  
  52.             </td>  
  53.             <td>  
  54.               <button class="btn btn-outline-warning btn-sm"  
  55.                 (click)="deleteEmployee(employee.id,  employee.name,employee.cityname);">  
  56.                 <i class="fa fa-trash"></i> Delete  
  57.               </button>  
  58.             </td>  
  59.           </tr>  
  60.         </tbody>  
  61.       </table>  
  62.     </div>  
  63.   </div>  
  64. </div>  
  65. <div *ngIf="errorMessage" class="alert alert-danger">  
  66.   Error: {{ errorMessage }}  
  67. </div>  
employee-list.component.css
  1. thead {    
  2.     color#337AB7;    
  3.   }    
We can create the employee edit component using the below command.
  1. ng g component employee/EmployeeEdit  
employee-edit.component.ts
  1. import { Component, OnInit, OnDestroy } from '@angular/core';    
  2. import { FormGroup, FormBuilder, Validators } from '@angular/forms';    
  3. import { Subscription } from 'rxjs';    
  4. import { ActivatedRoute, Router } from '@angular/router';    
  5. import { Employee } from '../employee';  
  6. import { EmployeeService } from '../employee.service';  
  7. import { GenericValidator } from 'src/app/shared/genericvalidator';  
  8.     
  9. @Component({    
  10.   selector: 'app-employee-edit',    
  11.   templateUrl: './employee-edit.component.html',    
  12.   styleUrls: ['./employee-edit.component.css']    
  13. })    
  14. export class EmployeeEditComponent implements OnInit, OnDestroy {    
  15.   pageTitle = 'Employee Edit';    
  16.   errorMessage: string;    
  17.   employeeForm: FormGroup;    
  18.   tranMode: string;    
  19.   employee: Employee;    
  20.   private sub: Subscription;    
  21.     
  22.   displayMessage: { [key: string]: string } = {};    
  23.   private validationMessages: { [key: string]: { [key: string]: string } };    
  24.   genericValidator: GenericValidator;    
  25.     
  26.     
  27.   constructor(private fb: FormBuilder,    
  28.     private route: ActivatedRoute,    
  29.     private router: Router,    
  30.     private employeeService: EmployeeService) {    
  31.     
  32.     this.validationMessages = {    
  33.       name: {    
  34.         required: 'Employee name is required.',    
  35.         minlength: 'Employee name must be at least three characters.',    
  36.         maxlength: 'Employee name cannot exceed 50 characters.'    
  37.       },    
  38.       cityname: {    
  39.         required: 'Employee city name is required.',    
  40.       }    
  41.     };    
  42.     this.genericValidator = new GenericValidator(this.validationMessages);    
  43.   }    
  44.     
  45.   ngOnInit() {    
  46.     this.tranMode = "new";    
  47.     this.employeeForm = this.fb.group({    
  48.       name: ['', [Validators.required,    
  49.       Validators.minLength(3),    
  50.       Validators.maxLength(50)    
  51.       ]],    
  52.       address: '',    
  53.       cityname: ['', [Validators.required]],    
  54.       gender: '',    
  55.       company: '',    
  56.       designation: '',    
  57.     });    
  58.     
  59.     this.sub = this.route.paramMap.subscribe(    
  60.       params => {    
  61.         const id = params.get('id');    
  62.         const cityname = params.get('cityname');    
  63.         if (id == '0') {    
  64.           const employee: Employee = { id: "0", name: "", address: "", gender: "", company: "", designation: "", cityname: "" };    
  65.           this.displayEmployee(employee);    
  66.         }    
  67.         else {    
  68.           this.getEmployee(id, cityname);    
  69.         }    
  70.       }    
  71.     );    
  72.   }    
  73.     
  74.   ngOnDestroy(): void {    
  75.     this.sub.unsubscribe();    
  76.   }    
  77.     
  78.   getEmployee(id: string, cityname: string): void {    
  79.     this.employeeService.getEmployee(id, cityname)    
  80.       .subscribe(    
  81.         (employee: Employee) => this.displayEmployee(employee),    
  82.         (error: any) => this.errorMessage = <any>error    
  83.       );    
  84.   }    
  85.     
  86.   displayEmployee(employee: Employee): void {    
  87.     if (this.employeeForm) {    
  88.       this.employeeForm.reset();    
  89.     }    
  90.     this.employee = employee;    
  91.     if (this.employee.id == '0') {    
  92.       this.pageTitle = 'Add Employee';    
  93.     } else {    
  94.       this.pageTitle = `Edit Employee: ${this.employee.name}`;    
  95.     }    
  96.     this.employeeForm.patchValue({    
  97.       name: this.employee.name,    
  98.       address: this.employee.address,    
  99.       gender: this.employee.gender,    
  100.       company: this.employee.company,    
  101.       designation: this.employee.designation,    
  102.       cityname: this.employee.cityname    
  103.     });    
  104.   }    
  105.     
  106.   deleteEmployee(): void {    
  107.     if (this.employee.id == '0') {    
  108.       this.onSaveComplete();    
  109.     } else {    
  110.       if (confirm(`Are you sure want to delete this Employee: ${this.employee.name}?`)) {    
  111.         this.employeeService.deleteEmployee(this.employee.id, this.employee.cityname)    
  112.           .subscribe(    
  113.             () => this.onSaveComplete(),    
  114.             (error: any) => this.errorMessage = <any>error    
  115.           );    
  116.       }    
  117.     }    
  118.   }    
  119.     
  120.   saveEmployee(): void {    
  121.     if (this.employeeForm.valid) {    
  122.       if (this.employeeForm.dirty) {    
  123.         const p = { ...this.employee, ...this.employeeForm.value };    
  124.         if (p.id === '0') {    
  125.           this.employeeService.createEmployee(p)    
  126.             .subscribe(    
  127.               () => this.onSaveComplete(),    
  128.               (error: any) => this.errorMessage = <any>error    
  129.             );    
  130.         } else {    
  131.           this.employeeService.updateEmployee(p)    
  132.             .subscribe(    
  133.               () => this.onSaveComplete(),    
  134.               (error: any) => this.errorMessage = <any>error    
  135.             );    
  136.         }    
  137.       } else {    
  138.         this.onSaveComplete();    
  139.       }    
  140.     } else {    
  141.       this.errorMessage = 'Please correct the validation errors.';    
  142.     }    
  143.   }    
  144.     
  145.   onSaveComplete(): void {    
  146.     this.employeeForm.reset();    
  147.     this.router.navigate(['/employees']);    
  148.   }    
  149. }    
employee-edit.component.html
  1. <div class="card">    
  2.   <div class="card-header">    
  3.     {{pageTitle}}    
  4.   </div>    
  5.     
  6.   <div class="card-body">    
  7.     <form novalidate    
  8.           (ngSubmit)="saveEmployee()"    
  9.           [formGroup]="employeeForm">    
  10.     
  11.       <div class="form-group row mb-2">    
  12.         <label class="col-md-2 col-form-label"    
  13.                for="employeeNameId">Employee Name</label>    
  14.         <div class="col-md-8">    
  15.           <input class="form-control"    
  16.                  id="employeeNameId"    
  17.                  type="text"    
  18.                  placeholder="Name (required)"    
  19.                  formControlName="name"    
  20.                  [ngClass]="{'is-invalid': displayMessage.name }" />    
  21.           <span class="invalid-feedback">    
  22.             {{displayMessage.name}}    
  23.           </span>    
  24.         </div>    
  25.       </div>    
  26.     
  27.       <div class="form-group row mb-2">    
  28.         <label class="col-md-2 col-form-label"    
  29.                for="citynameId">City</label>    
  30.         <div class="col-md-8">    
  31.           <input class="form-control"    
  32.                  id="citynameid"    
  33.                  type="text"    
  34.                  placeholder="Cityname (required)"    
  35.                  formControlName="cityname"    
  36.                  [ngClass]="{'is-invalid': displayMessage.cityname}" />    
  37.           <span class="invalid-feedback">    
  38.             {{displayMessage.cityname}}    
  39.           </span>    
  40.         </div>    
  41.       </div>    
  42.     
  43.       <div class="form-group row mb-2">    
  44.         <label class="col-md-2 col-form-label"    
  45.                for="addressId">Address</label>    
  46.         <div class="col-md-8">    
  47.           <input class="form-control"    
  48.                  id="addressId"    
  49.                  type="text"    
  50.                  placeholder="Address"    
  51.                  formControlName="address" />    
  52.         </div>    
  53.       </div>    
  54.     
  55.       <div class="form-group row mb-2">    
  56.         <label class="col-md-2 col-form-label"    
  57.                for="genderId">Gender</label>    
  58.         <div class="col-md-3">    
  59.           <select id="genderId" formControlName="gender" class="form-control">    
  60.             <option value="" disabled selected>Select an Option</option>    
  61.             <option value="Male">Male</option>    
  62.             <option value="Female">Female</option>    
  63.           </select>    
  64.     
  65.         </div>    
  66.       </div>    
  67.     
  68.       <div class="form-group row mb-2">    
  69.         <label class="col-md-2 col-form-label"    
  70.                for="companyId">Company</label>    
  71.         <div class="col-md-8">    
  72.           <input class="form-control"    
  73.                  id="companyId"    
  74.                  type="text"    
  75.                  placeholder="Company"    
  76.                  formControlName="company" />    
  77.         </div>    
  78.       </div>    
  79.     
  80.       <div class="form-group row mb-2">    
  81.         <label class="col-md-2 col-form-label"    
  82.                for="designationId">Designation</label>    
  83.         <div class="col-md-8">    
  84.           <input class="form-control"    
  85.                  id="designationId"    
  86.                  type="text"    
  87.                  placeholder="Designation"    
  88.                  formControlName="designation" />    
  89.         </div>    
  90.       </div>    
  91.     
  92.       <div class="form-group row mb-2">    
  93.         <div class="offset-md-2 col-md-4">    
  94.           <button class="btn btn-primary mr-3"    
  95.                   style="width:80px;"    
  96.                   type="submit"    
  97.                   [title]="employeeForm.valid ? 'Save your entered data' : 'Disabled until the form data is valid'"    
  98.                   [disabled]="!employeeForm.valid">    
  99.             Save    
  100.           </button>    
  101.           <button class="btn btn-outline-secondary mr-3"    
  102.                   style="width:80px;"    
  103.                   type="button"    
  104.                   title="Cancel your edits"    
  105.                   [routerLink]="['/employees']">    
  106.             Cancel    
  107.           </button>    
  108.           <button class="btn btn-outline-warning" *ngIf="pageTitle != 'Add Employee'"    
  109.                   style="width:80px"    
  110.                   type="button"    
  111.                   title="Delete this product"    
  112.                   (click)="deleteEmployee()">    
  113.             Delete    
  114.           </button>    
  115.         </div>    
  116.       </div>    
  117.     </form>    
  118.   </div>    
  119.     
  120.   <div class="alert alert-danger"    
  121.        *ngIf="errorMessage">{{errorMessage}}    
  122.   </div>    
  123. </div>    
We can create an employee edit guard class using the below command.
  1. ng g class employee/employee-edit/EmployeeEditGuard  
This class will be used to prevent employee edit screen from accidental closure. This will ask for a confirmation if the user leaves the screen before saving the data.
 
employee-edit-guard.ts
  1. import { Injectable } from '@angular/core';    
  2. import { CanDeactivate } from '@angular/router';    
  3. import { Observable } from 'rxjs';    
  4. import { EmployeeEditComponent } from './employee-edit.component';    
  5.     
  6.     
  7. @Injectable({    
  8.   providedIn: 'root'    
  9. })    
  10. export class EmployeeEditGuard implements CanDeactivate<EmployeeEditComponent> {    
  11.   canDeactivate(component: EmployeeEditComponent): Observable<boolean> | Promise<boolean> | boolean {    
  12.     if (component.employeeForm.dirty) {    
  13.       const name = component.employeeForm.get('name').value || 'New Employee';    
  14.       return confirm(`Navigate away and lose all changes to ${name}?`);    
  15.     }    
  16.     return true;    
  17.   }    
  18. }     
We can create employee detail component now.
  1. ng g component employee/EmployeeDetail  
employee-detail.component.ts
  1. import { Component, OnInit } from '@angular/core';    
  2. import { ActivatedRoute, Router } from '@angular/router';    
  3. import { Employee } from '../employee';  
  4. import { EmployeeService } from '../employee.service';  
  5.     
  6. @Component({    
  7.   selector: 'app-employee-detail',    
  8.   templateUrl: './employee-detail.component.html',    
  9.   styleUrls: ['./employee-detail.component.css']    
  10. })    
  11. export class EmployeeDetailComponent implements OnInit {    
  12.   pageTitle = 'Employee Detail';    
  13.   errorMessage = '';    
  14.   employee: Employee | undefined;    
  15.     
  16.   constructor(private route: ActivatedRoute,    
  17.     private router: Router,    
  18.     private employeeService: EmployeeService) { }    
  19.     
  20.   ngOnInit() {    
  21.     const id = this.route.snapshot.paramMap.get('id');    
  22.     const cityname = this.route.snapshot.paramMap.get('cityname');    
  23.     if (id && cityname) {    
  24.       this.getEmployee(id, cityname);    
  25.     }    
  26.   }    
  27.     
  28.   getEmployee(id: string, cityName: string) {    
  29.     this.employeeService.getEmployee(id, cityName).subscribe(    
  30.       employee => this.employee = employee,    
  31.       error => this.errorMessage = <any>error);    
  32.   }    
  33.     
  34.   onBack(): void {    
  35.     this.router.navigate(['/employees']);    
  36.   }    
  37. }    
employee-detail.component.html
  1. <div class="card">    
  2.   <div class="card-header"    
  3.        *ngIf="employee">    
  4.     {{pageTitle + ": " + employee.name}}    
  5.   </div>    
  6.   <div class="card-body"    
  7.        *ngIf="employee">    
  8.     <div class="row">    
  9.       <div class="col-md-8">    
  10.         <div class="row">    
  11.           <div class="col-md-3">Name:</div>    
  12.           <div class="col-md-6">{{employee.name}}</div>    
  13.         </div>    
  14.         <div class="row">    
  15.           <div class="col-md-3">City:</div>    
  16.           <div class="col-md-6">{{employee.cityname}}</div>    
  17.         </div>    
  18.         <div class="row">    
  19.           <div class="col-md-3">Address:</div>    
  20.           <div class="col-md-6">{{employee.address}}</div>    
  21.         </div>    
  22.         <div class="row">    
  23.           <div class="col-md-3">Gender:</div>    
  24.           <div class="col-md-6">{{employee.gender}}</div>    
  25.         </div>    
  26.         <div class="row">    
  27.           <div class="col-md-3">Company:</div>    
  28.           <div class="col-md-6">{{employee.company}}</div>    
  29.         </div>    
  30.         <div class="row">    
  31.           <div class="col-md-3">Designation:</div>    
  32.           <div class="col-md-6">{{employee.designation}}</div>    
  33.         </div>    
  34.       </div>    
  35.     </div>    
  36.     <div class="row mt-4">    
  37.       <div class="col-md-4">    
  38.         <button class="btn btn-outline-secondary mr-3"    
  39.                 style="width:80px"    
  40.                 (click)="onBack()">    
  41.           <i class="fa fa-chevron-left"></i> Back    
  42.         </button>    
  43.         <button class="btn btn-outline-primary"    
  44.                 style="width:80px"    
  45.                 [routerLink]="['/employees', employee.id, employee.cityname, 'edit']">    
  46.           Edit    
  47.         </button>    
  48.       </div>    
  49.     </div>    
  50.   </div>    
  51.   <div class="alert alert-danger"    
  52.        *ngIf="errorMessage">    
  53.     {{errorMessage}}    
  54.   </div>    
  55. </div>    
We can add reference to HttpClientModule and other routing details to app.module.ts file.
 
app.module.ts
  1. import { BrowserModule } from '@angular/platform-browser';  
  2. import { NgModule } from '@angular/core';  
  3. import { HttpClientModule } from '@angular/common/http';  
  4. import { ReactiveFormsModule, FormsModule } from '@angular/forms';  
  5. import { RouterModule } from '@angular/router';  
  6.   
  7. import { AppRoutingModule } from './app-routing.module';  
  8. import { AppComponent } from './app.component';  
  9. import { HeaderComponent } from './ui/header/header.component';  
  10. import { FooterComponent } from './ui/footer/footer.component';  
  11. import { LayoutComponent } from './ui/layout/layout.component';  
  12. import { HomeComponent } from './home/home.component';  
  13. import { EmployeeListComponent } from './employee/employee-list/employee-list.component';  
  14. import { EmployeeEditComponent } from './employee/employee-edit/employee-edit.component';  
  15. import { EmployeeDetailComponent } from './employee/employee-detail/employee-detail.component';  
  16. import { EmployeeEditGuard } from './employee/employee-edit/employee-edit-guard';  
  17. import { EmployeeService } from './employee/employee.service';  
  18.   
  19.   
  20. @NgModule({  
  21.   declarations: [  
  22.     AppComponent,  
  23.     HeaderComponent,  
  24.     FooterComponent,  
  25.     LayoutComponent,  
  26.     HomeComponent,  
  27.     EmployeeListComponent,  
  28.     EmployeeEditComponent,  
  29.     EmployeeDetailComponent  
  30.   ],  
  31.   imports: [  
  32.     BrowserModule,  
  33.     AppRoutingModule,  
  34.     HttpClientModule,  
  35.     ReactiveFormsModule,  
  36.     FormsModule,  
  37.     RouterModule.forRoot([  
  38.       {  
  39.         path: 'home',  
  40.         component: HomeComponent  
  41.       },  
  42.       {  
  43.         path: 'employees',  
  44.         component: EmployeeListComponent  
  45.       },  
  46.       {  
  47.         path: 'employees/:id/:cityname',  
  48.         component: EmployeeDetailComponent  
  49.       },  
  50.       {  
  51.         path: 'employees/:id/:cityname/edit',  
  52.         canDeactivate: [EmployeeEditGuard],  
  53.         component: EmployeeEditComponent  
  54.       },  
  55.       {  
  56.         path: '',  
  57.         redirectTo: 'home',  
  58.         pathMatch: 'full'  
  59.       },  
  60.       {  
  61.         path: '**',  
  62.         redirectTo: 'home',  
  63.         pathMatch: 'full'  
  64.       }  
  65.     ])  
  66.   ],  
  67.   providers: [  
  68.     EmployeeService  
  69.   ],  
  70.   bootstrap: [AppComponent]  
  71. })  
  72. export class AppModule { }  
We have completed the coding part. We can run the application now. Please ensure that the Azure Functions app and Cosmos DB emulator is running properly before running the Angular application.
  1. ng serve  
Angular 8 App with Cosmos DB and Azure Function
 
Choose “Employee” menu and add a new employee.
 
Angular 8 App with Cosmos DB and Azure Function
 
We can add one more employee and show the details in a grid.
 
Angular 8 App with Cosmos DB and Azure Function
 
We can choose any employee and click the “Edit” button to modify the data.
 
Angular 8 App with Cosmos DB and Azure Function
 
We can click any employee name and show the employee details in read-only mode.
 
Angular 8 App with Cosmos DB and Azure Function
 

Conclusion

 
In this post, we have seen how to create an Azure function in Visual Studio 2017 and connected it with Cosmos DB. We have created separate functions for each CRUD action for Cosmos DB in the Azure function app. Later, we have created an Angular 8 application using CLI and consumed all Azure functions as REST API. It is easy to deploy these Azure functions to the Azure portal and use them globally. For that, we must create a Cosmos DB account in the Azure portal and change the connection string in our application. Please note that there is a default Cosmos DB trigger available with Azure function, but I have used Cosmos DB connection differently with this application. In this approach, we will get full freedom to populate code according to our requirement. Please give your valuable comments on this article, so that I can improve my upcoming articles.


Similar Articles