Introduction
In the previous article, we will learn how to create a .NET 5 API that can do CRUD operations on Azure Cosmos DB using EF Core. We will continue this article to build an Angular 11 app to connect to the REST API and perform CRUD operations using the website.
Please note that I have added more endpoints and logic to the .NET 5 API. Please refer to Github repo for the most updated code.
Prerequisites
- Azure account - If you don't have it already you can create one for free by visiting Cloud Computing Services | Microsoft Azure.
- Azure Cosmos DB Setup - Create a database in Azure Cosmos DB called VideogamesDB and a container called Videogames. If you want step-by-step instructions on how to do that please follow this article
- .NET 5 REST API - Please follow this article to create .NET 5 REST API
- Angular CLI version 11. You can check the installed version using the command ng version.
- Visual Studio Code or any other IDE. If you don't have VS Code installed you can download it from here
Step 1 - Create a new Angular project using Angular CLI
1) Open VS code terminal and go to folder where you want to create your project. Run the command ng new angular-signalr-crud. Open the folder in VS Code.
2) We will be using jQuery datatable. Run the following command on terminal ng add angular-datatables. When you run this command you will get the latest version but to ensure compatibility with Angular 11 we might have to change some versions in package.json to the following versions. To learn more about using jQuery datatables in Angular visit their official website
"@types/datatables.net": "^1.10.18"
"datatables.net-dt": "^1.10.20"
"datatables.net": "^1.10.20"
"angular-datatables": "^11.1.1"
3. To add bootstrap run the following command on terminal npm install bootstrap --save and add the following line to style.css @import "~bootstrap/dist/css/bootstrap.css";
Step 2 - Edit app.module.ts file
Make sure DataTablesModule and HttpClientModule is added to the imports array in app.module.ts file
Step 3 - Add Model classes
Add two files - Company.ts and VideogameModel.ts. Copy-paste the following code into class files
import { Company } from './Company';
export class VideogameModel {
id:string='';
name:string='';
genere:string='';
platform:string='';
company: Company = new Company;
}
export class Company {
name:string='';
city:string='';
province:string='';
country:string='';
}
Step 4 - Add Service
- Open VS Code terminal and add a new service using the command ng g s videogameservice
- Copy-paste the following code into videogameservice.service.ts file. Remember to change the baseURL to the URL of your .NET 5 REST API
- For making the Http Get call on line 14 we have to make sure the name of the .NET 5 Controller method is appended to base URL.
- getVideogameList - To get all the videogames to display the table on list page
- getVideogameFromId - To get a videogame based on Id
- getCompanies - To get all the distinct companies
- addVideogame - To add new videogame
- updateVideogame - To update an existing videogame
- deleteVideogame - To delete a videogame. Remember to use .subscribe as delete call won't work without it.
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { VideogameModel } from './VideogameModel';
@Injectable({
providedIn: 'root'
})
export class VideogameserviceService {
constructor(private http: HttpClient)
{ }
readonly baseURL = 'https://localhost:44371/api/Videogame';
getVideogameList(): Observable<VideogameModel[]> {
var result = this.http.get<VideogameModel[]>(this.baseURL+"/GetAllVideogames");
console.log(result);
return result;
}
getVideogameFromId(id:string): Observable<VideogameModel> {
var result = this.http.get<VideogameModel>(this.baseURL+"/GetVideogameById?id="+id);
return result;
}
getCompanies():Observable<string[]>{
var companies = this.http.get<string[]>(this.baseURL+"/GetDistinctCompanies");
console.log(companies);
return companies;
}
addVideogame(videogame:VideogameModel)
{
return this.http.post(this.baseURL,videogame);
}
updateVideogame(videogame:VideogameModel)
{
return this.http.put(this.baseURL,videogame);
}
//delete wont work without subscribe
deleteVideogame(id:string)
{
return this.http.delete(this.baseURL+"?id="+id).subscribe(()=>{});
}
}
Step 5 - Create a new component to display available videogames in the data table.
1. Run the command ng g c videogamelist VS Code terminal to generate a new component.
2. Copy-paste the following code in videogamelist.component.html file.
- In the table element notice "datatable" attribute. This is the only thing needed to convert a simple table into datatable which gives you options like paging, search and sorting, etc.
- Attribute "dtOptions" is used to set options for datatable
- dtTrigger is used to manually render the table
- We are using ngFor to dynamically render the data into the table.
- The edit button click will take the id from the row and redirect the user to another screen using angular routing
<div class="jumbotron jumbotron-fluid">
<div class="container">
<h1 class="display-4">Videogames</h1>
<p class="lead">This page lists all available video games </p>
</div>
</div>
<div class="tablesize" >
<table id ="videogameslist" datatable [dtOptions]="dtOptions" [dtTrigger]="dtTrigger" class="table table-striped table-bordered" >
<thead class="thead-light">
<tr>
<th >Id</th>
<th>Name</th>
<th>Platform</th>
<th>Genere</th>
<th>Company Name</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
<tr *ngFor="let videogame of videogames">
<td data-visible="false">{{ videogame.id }}</td>
<td>{{ videogame.name }}</td>
<td>{{ videogame.platform }}</td>
<td>{{ videogame.genere }}</td>
<td>{{ videogame.company.name }}</td>
<td><button render="renderer" id="editbutton" (click)="editbuttonclicked(videogame.id)" class="btn btn-primary">Edit</button></td>
<td><button render="renderer" id="editbutton" (click)="deletebuttonclicked(videogame.id)" class="btn btn-primary">Delete</button></td>
</tr>
</tbody>
</table>
</div>
3. Copy-paste the following code into videogamelist.component.ts
- We have to set dtOptions in ngOnInit
- dtTrigger is used to manually render the table.
- editbuttonclicked method is used to call the router to navigate to the edit screen
- ngOnDestroy is used to unsubscribe from dtTrigger
- deletebuttonclicked is used to call service method to delete a record
import { VideogameModel } from './../VideogameModel';
import { VideogameserviceService } from './../videogameservice.service';
import { Component, OnDestroy, OnInit } from '@angular/core';
import { Subject } from 'rxjs';
import { Router } from '@angular/router';
@Component({
selector: 'app-videogamelist',
templateUrl: './videogamelist.component.html',
styleUrls: ['./videogamelist.component.css']
})
export class VideogamelistComponent implements OnInit, OnDestroy {
constructor( private service: VideogameserviceService, private router: Router) { }
dtOptions: DataTables.Settings = {};
videogames: VideogameModel[] = [];
dtTrigger: Subject<any> = new Subject<any>();
ngOnInit(): void {
this.dtOptions = {
pagingType: 'full_numbers',
pageLength: 10,
}
}
ngOnDestroy(): void {
// Do not forget to unsubscribe the event
this.dtTrigger.unsubscribe();
//this.id="";
}
ngAfterViewInit(): void {
this.loadVideogameList();
}
loadVideogameList()
{
this.service.getVideogameList().subscribe((data) => {
this.videogames = data as any;
console.log(this.videogames);
// Calling the DT trigger to manually render the table
this.dtTrigger.next();
},
()=>{
alert("Internal Server Error.There was an error retrieving your request. Please contact support");
});
}
editbuttonclicked(data:any)
{
if (data!=undefined && data!=null) {
this.router.navigate(["/videogame-edit",data]);
}
}
deletebuttonclicked(data:any)
{
if (data!=undefined && data!=null) {
this.service.deleteVideogame(data);
window.location.reload();
}
}
}
Step 6 - Add component to add/edit videogames
Copy-paste the following code into videogameedit.component.html.
- Submit button text will be either Add or Update based on whether we are adding a new Videogame or updating an existing videogame.
- Cancel button will redirect us to list page.
<div class="jumbotron jumbotron-fluid">
<div class="container">
<h1 class="display-4">Upsert Videogames</h1>
<p class="lead">This page is used to insert/update video games</p>
</div>
</div>
<div class="formsize">
<form [formGroup]="form" class="form-width" (ngSubmit)="submitForm()">
<div class="form-group">
<label for="name">Videogame Name:</label>
<input
maxlength="100"
required
type="text"
formControlName="name"
id="name"
class="form-control"
/>
<div
*ngIf="name?.invalid && (name?.dirty || name?.touched)"
class="alert alert-danger">
Videogame Name is required
</div>
<div style="height: 2ch"></div>
<label for="platform"> Platform: </label>
<input
type="text"
maxlength="50"
formControlName="platform"
id="platform"
class="form-control"/>
<div
*ngIf="platform?.invalid && (platform?.dirty || platform?.touched)"
class="alert alert-danger">
Platform is required
</div>
<div style="height: 2ch"></div>
<label> Genere:</label>
<input
type="text"
formControlName="genere"
class="form-control"
maxlength="50"/>
<div
*ngIf="genere?.invalid && (genere?.dirty || genere?.touched)"
class="alert alert-danger">
Genere is required
</div>
<div style="height: 2ch"></div>
<label> Company Name:</label>
<select
formControlName="companyName"
class="form-select"
style="width: 50%">
<option [ngValue]="null">--Select One--</option>
<option *ngFor="let company of companies">{{ company }}</option>
</select>
<div
*ngIf="
companyName?.invalid && (companyName?.dirty || companyName?.touched)
"class="alert alert-danger">
Company Name is required
</div>
<div style="height: 2ch"></div>
</div>
<div class="button-style">
<button
#submitButton
[disabled]="form.invalid"
type="submit"
class="btn btn-primary mr-1"
id="upsert">
{{ btnName }}
</button>
<button
(click)="cancel()"
type="button"
class="btn btn-danger"
id="cancel">
Cancel
</button>
</div>
</form>
</div>
Copy-paste the following code into videogameedit.component.ts
- In ngOnInit we have to call getCompanies in the service to get all the distinct Companies for Company dropdown list.
- When we click Edit button on the list page Id will be sent in URL and Update button will be shown and form gets auto populated with data.
- If there is no id in the URL Add button will be displayed and an empty form will be displayed for user to fill the values.
- submitForm method will call either PUT or POST method based on whether id is supplied in URL or not.
import { VideogameModel } from './../VideogameModel';
import { Company } from './../Company';
import { VideogameserviceService } from './../videogameservice.service';
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
@Component({
selector: 'app-videogameedit',
templateUrl: './videogameedit.component.html',
styleUrls: ['./videogameedit.component.css']
})
export class VideogameeditComponent implements OnInit {
form: FormGroup;
selectedId: any;
btnName:string="";
companies:any;
videogameModel:VideogameModel= new VideogameModel;
constructor(public fb: FormBuilder,
private route: ActivatedRoute,
public service: VideogameserviceService,
private router: Router) {
this.form = this.fb.group({
id:[''],
name: ['',Validators.required],
platform: ['',Validators.required],
genere: ['',Validators.required],
companyName: ['',Validators.required],
});
}
ngOnInit(): void {
this.service.getCompanies().subscribe((res)=>{
this.companies=res;
});
this.selectedId = this.route.snapshot.paramMap.get('id')!=undefined?this.route.snapshot.paramMap.get('id'):0;
if (this.selectedId!=null && this.selectedId!=undefined && this.selectedId!=0)
{
this.btnName="Update";
this.service.getVideogameFromId(this.selectedId).subscribe(
(res)=>
{
this.form.setValue({
id:res.id,
name:res.name,
platform:res.platform,
genere:res.genere,
companyName:res.company.name
});
});
}
else
{
this.btnName="Add";
}
}
submitForm() {
if (this.selectedId!=null && this.selectedId!=undefined && this.selectedId!=0)
{
this.videogameModel.id=this.form.value.id;
this.videogameModel.platform=this.form.value.platform;
this.videogameModel.genere=this.form.value.genere;
this.videogameModel.name=this.form.value.name;
this.videogameModel.company.name=this.form.value.companyName;
this.service.updateVideogame(this.videogameModel).subscribe(
() => {
alert(this.btnName+" successful")
this.router.navigate(["/"]);
},
(err) => {
alert("there was an error in add/update please try again later")
}
);
}
else
{
this.videogameModel.id=this.form.value.id;
this.videogameModel.platform=this.form.value.platform;
this.videogameModel.genere=this.form.value.genere;
this.videogameModel.name=this.form.value.name;
this.videogameModel.company.name=this.form.value.companyName;
this.service.addVideogame(this.videogameModel).subscribe(
() => {
alert(this.btnName+" successful")
this.router.navigate(["/"]);
},
(err) => {
alert("there was an error in add/update please try again later")
}
);
}
}
cancel()
{
this.router.navigate(["/"]);
}
get name() { return this.form.get('name'); }
get platform() { return this.form.get('platform'); }
get genere() { return this.form.get('genere'); }
get companyName() { return this.form.get('companyName'); }
}
Step 7 - Edit app.component.html file
Copy-paste the following code into app. component.html file
<nav class="navbar navbar-expand-lg navbar-light bg-light ">
<a routerLink="/videogame-edit" routerLinkActive="active" class="navbar-brand ">Add New Videogame</a>
<a routerLink="/videogame-list" routerLinkActive="active" class="navbar-brand">Videogame Catalog</a>
</nav>
<router-outlet></router-outlet>
Step - Edit app-routing.module.ts file
Copy-paste the following code into app-routing.module.ts file
import { VideogamelistComponent } from './videogamelist/videogamelist.component';
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { VideogameeditComponent } from './videogameedit/videogameedit.component';
const routes: Routes = [
{ path: 'videogame-list', component: VideogamelistComponent },
{ path: 'videogame-edit/:id', component: VideogameeditComponent },
{ path: 'videogame-edit', component: VideogameeditComponent },
{ path: '', redirectTo: '/videogame-list', pathMatch: 'full' }
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
Step 7 - Run and Test
On the VS code terminal type, the command ng serve -o. If everything goes well home page should load
Click on Add New Videogame menu item and a new page will open. Add some data and click Add button. You should be redirected to home page with new record.
Click the edit button next to any record on home page. Edit form will open with data prepopulated. Edit the data you want and click update
On the home page click the delete button next to any record to delete the record.
Summary
In this article, we saw how to create an Angular app to perform CRUD operations on Azure Cosmos DB using a .NET 5 REST API.
Github Repo - https://github.com/tanujgyan/angular-signalr-crud