E Learning
E Learning
E Learning
NET Core
In modern web applications, it is very common to call third-party Web APIs to enhance the
functionality of the application.
There are thousands of free and commercial APIs available and if you know how to consume those
APIs in your ASP.NET Core Applications, you can build very powerful business applications.
https://github.com/public-apis/public-apis
In my previous post A Developer’s Guide for Creating Web APIs with ASP.NET Core 5, I covered
ASP.NET Core Web APIs in detail.
In this post, I will explain how you can consume third-party Web APIs in ASP.NET Core
applications.
The third-party API we will consume is called Nager.Date which is a worldwide public holidays API.
It is a very simple API and you can easily test this API in Postman by entering the following URL.
https://date.nager.at/api/v2/PublicHolidays/2020/US
The response of this API is the list of public holidays in JSON format as shown below:
Understanding HttpClient Object
The most common and well knows class that allows us to consume third-party APIs in ASP.NET Core
application is HttpClient class.
This class gives us the ability to send HTTP requests to third-party APIs and receive HTTP
responses returned from those APIs.
Every instance of HttpClient maintains its own connection pool that allows it to isolate its
requests from requests executed by other instances of HttpClient.
This class also acts as a base class for more specific HTTP clients.
For example, you can create FacebookHttpClient or TwitterHttpClient as child classes of base
HttpClient and can communicate with Facebook and Twitter APIs using these specific HTTP clients.
Its recommended to create one instance of HttpClient and reuse it throughout the application
lifetime.
This is because instantiating a new instance of HttpClient for every request can easily exhaust
the number of sockets available under heavy loads.
This is mainly because when the HttpClient object is disposed of the underlying socket is not
immediately released.
You can read this wonderful blog post You’re using HttpClient wrong and it’s destabilizing your
software to get more information about the problem I just mentioned.
Let’s create an ASP.NET Core MVC Web Application and create the following interface.
This interface has just one method GetHolidays which has two parameters countryCode and year which
we will receive from the user shortly.
The GetHolidays method above is returning a list of HolidayModel which is a model class that has
the properties mapped with the response of Nager.Date API.
Note how I have declared a private and static HttpClient variable in the class and how it is
defined in the static constructor of the class.
To use our HolidaysApiService, we need to first register our service in Startup.cs class.
Finally, we need a Razor View to create a form where the user will input country code and year.
The form will be submitted to the above Index action which will then call the GetHolidays method.
Here is the code of Index.cshtml Razor view showing an HTML form and a table to display the public
holidays.
1 @model List<HolidayModel>
2 @{
3 ViewData["Title"] = "Home Page";
4 }
5
6 <div>
7 <h3 class="display-4">Public Holidays Finder</h3>
8 <center>
9 <form asp-controller="Home" asp-action="Index">
10 <table>
11 <tr>
12 <td>Country Code: </td>
13 <td><input type="text" id="txtCountryCode" name="CountryCode" /></td>
14 <td>Year: </td>
15 <td><input type="text" id="txtYear" name="Year" /></td>
16 <td><input type="submit" value="Submit" /></td>
17 </tr>
18 </table>
19 <hr />
20 </form>
21 </center>
22 @if (Model != null && Model.Count > 0)
23 {
24 <table class="table table-bordered table-striped table-sm">
25 <thead>
26 <tr>
27 <th>Date</th>
28 <th>Name</th>
29 <th>Local Name</th>
30 <th>Country Code</th>
31 <th>Global</th>
32 </tr>
33 </thead>
34 <tbody>
35
@foreach (var item in Model)
36
{
37
<tr>
38
<td>@item.Date.Value.ToShortDateString()</td>
39
<td>@Html.DisplayFor(modelItem => item.Name)</td>
40
<td>@Html.DisplayFor(modelItem => item.LocalName)</td>
41
<td>@Html.DisplayFor(modelItem => item.CountryCode)</td>
42
<td>@Html.DisplayFor(modelItem => item.Global)</td>
43
</tr>
44
}
45
</tbody>
46
</table>
47
}
48
</div>
49
It is now time to test our application and see if we will be able to consume the third Party API.
Press F5 in Visual Studio and you will see a page similar to the following. You can input a
country code e.g. US, DE, etc., and a year e.g. 2021, and click the “Submit” button and if
everything goes well you will see our code calling a third-party API, fetching a list of Public
Holidays from the API and displaying it on the page.
Managing HttpClient objects with IHttpClientFactory
To make HttpClient instances manageable, and to avoid the socket exhaustion issue mentioned above,
.NET Core 2.1 introduced the IHttpClientFactory interface which can be used to configure and
create HttpClient instances in an app through Dependency Injection (DI). To make use of
IHttpClientFactory, we can register it in Startup.cs file by calling
AddHttpClient(IServiceCollection).
It is possible to register multiple HTTP clients with different names using the AddHttpClient
method. The first parameter of the AddHttpClient method is the name of the client and the second
parameter is the Lamba expression that will configure the HttpClient.
In the above example, I am setting the BaseAddress property with the Url of the third-party API I
want to call using this particular HTTP client.
Once the HTTP Client is registered, we can inject IHttpClientFactory in our controllers and
services and call its CreateClient method to create a specific HTTP Client object we want to use
in our code. The CreateClient method needs the name of the HTTP Client you want to create as shown
below:
Prerequisites
Install .NET Core 5 or the latest SDK from the Microsoft website.
Install Visual Studio 2019 Community Edition from here.
Download and install the latest version of Node.js from here.
Install SQL Server 2008 or above.
Create a new SQL Server Database with the following two tables
Players – This table will store the data about football players. It has columns such as
ShirtNo, Name, PositionId (FK), Appearances, Goals, etc.
Positions – This table will store different positions e.g. Goalkeeper, Defender,
Midfielder, etc.
BACKEND
Open Visual Studio 2019 and create a new ASP.NET Core 5 Web API ASP.NET Core.
Microsoft.EntityFrameworkCore.SqlServer
Microsoft.EntityFrameworkCore.Design
Microsoft.EntityFrameworkCore.Tools
We are using an existing SQL Server database that’s why we want to use EF Core (database first)
approach to reverse engineer the entity models and DbContext. For this purpose, we can use the
Scaffold-DbContext command that has many options to customize the generated code.
Open the Package Manager Console of the project, copy/paste the following command, and press
Enter.
The following command will generate entity classes in the Models folder and FootballDbContext
class in the Data folder.
1 {
2 "ConnectionStrings": {
3 "DefaultConnection": "Server=DB_SERVER; Database=FootballDb; Trusted_Connection=True;
4 MultipleActiveResultSets=true"
5 },
6 "Logging": {
7 "LogLevel": {
8 "Default": "Information",
9 "Microsoft": "Warning",
10 "Microsoft.Hosting.Lifetime": "Information"
11 }
12 },
13 "AllowedHosts": "*"
14 }
1 services.AddDbContext<FootballDbContext>(options =>
2 options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
The application we are going to build will allow us to perform CRUD operations from an Angular
App. The Angular App will call ASP.NET Core Web APIs and those Web APIs will call the backend
application services to communicate with the database. As per our application requirements, we
need to create the following two services.
IPlayersService.cs
?
1 public interface IPlayersService
2 {
3 Task<IEnumerable<Player>> GetPlayersList();
4 Task<Player> GetPlayerById(int id);
5 Task<Player> CreatePlayer(Player player);
6 Task UpdatePlayer(Player player);
7 Task DeletePlayer(Player player);
8 }
Create a PlayersService class in the Services folder and implement the IPlayersService interface
on the class.
You need to inject FootballDbContext in the service class to perform database operations. The
methods of PlayersService are very straightforward as they are performing standard Create, Update,
Delete, List, etc. operations on the Players table.
PlayersService.cs
?
1 public class PlayersService : IPlayersService
2 {
3 private readonly FootballDbContext _context;
4
5 public PlayersService(FootballDbContext context)
6 {
7 _context = context;
8 }
9
10 public async Task<IEnumerable<Player>> GetPlayersList()
11 {
12 return await _context.Players.Include(x => x.Position).ToListAsync();
13 }
14
15 public async Task<Player> GetPlayerById(int id)
16 {
17 return await _context.Players.Include(x => x.Position)
18 .FirstOrDefaultAsync(x => x.Id == id);
19 }
20
21 public async Task<Player> CreatePlayer(Player player)
22 {
23 _context.Players.Add(player);
24 await _context.SaveChangesAsync();
25 return player;
26 }
27 public async Task UpdatePlayer(Player player)
28 {
29 _context.Players.Update(player);
30 await _context.SaveChangesAsync();
31 }
32
33 public async Task DeletePlayer(Player player)
34 {
35 _context.Players.Remove(player);
36 await _context.SaveChangesAsync();
37 }
38 }
Next, create the following IPositionsService interface in the Services folder. We can declare
all Positions table-related methods in IPositionsService but for this tutorial, we only need
a single GetPositionsList method.
IPositionsService.cs
?
1 public interface IPositionsService
2 {
3 Task<IEnumerable<Position>> GetPositionsList();
4 }
Create a new class PositionsService and implement the IPositionsService interface as follows:
PositionsService.cs
1 public class PositionsService : IPositionsService
2 {
3 private readonly FootballDbContext _context;
4
5 public PositionsService(FootballDbContext context)
6 {
7 _context = context;
8 }
9
10 public async Task<IEnumerable<Position>> GetPositionsList()
11 {
12 return await _context.Positions
13 .OrderBy(x => x.DisplayOrder)
14 .ToListAsync();
15 }
16 }
Implement ASP.NET Core Web APIs
Add the following PlayersController class in the Controllers folder.
PlayersController.cs
1 [ApiController]
2 [Route("api/[controller]")]
3 public class PlayersController : ControllerBase
4 {
5 private readonly IPlayersService _playerService;
6
7 public PlayersController(IPlayersService playerService)
8 {
9 _playerService = playerService;
10 }
11
12 [HttpGet]
13 public async Task<IEnumerable<Player>> Get()
14 {
15 return await _playerService.GetPlayersList();
16 }
17
18 [HttpGet("{id}")]
19 public async Task<ActionResult<Player>> Get(int id)
20 {
21 var player = await _playerService.GetPlayerById(id);
22 if (player == null)
23 return NotFound();
24 return Ok(player);
25 }
26
27 [HttpPost]
28 public async Task<ActionResult<Player>> Post(Player player)
29 {
30 await _playerService.CreatePlayer(player);
31 return CreatedAtAction("Post", new { id = player.Id }, player);
32 }
33
34 [HttpPut("{id}")]
35 public async Task<IActionResult> Put(int id, Player player)
36 {
37 if (id != player.Id)
38 return BadRequest("Not a valid player id");
39 await _playerService.UpdatePlayer(player);
40 return NoContent();
41 }
42
43 [HttpDelete("{id}")]
44 public async Task<IActionResult> Delete(int id)
45 {
46 if (id <= 0)
47 return BadRequest("Not a valid player id");
48 var player = await _playerService.GetPlayerById(id);
49 if (player == null)
50 return NotFound();
51 await _playerService.DeletePlayer(player);
52 return NoContent();
53 }
54 }
Next, add another Web API Controller with the name PositionsController.
PositionsController.cs
?
1 [ApiController]
2 [Route("api/[controller]")]
3 public class PositionsController : ControllerBase
4 {
5 private readonly IPositionsService _positionService;
6
7 public PositionsController(IPositionsService positionService)
8 {
9 _positionService = positionService;
10 }
11
12 [HttpGet]
13 public async Task<IEnumerable<Position>> Get()
14 {
15 return await _positionService.GetPositionsList();
16 }
17 }
To make sure everything is working as expected, run the project by pressing F5 and open the
following URL in the browser address bar. Please note that the port number shown in the following
URL can be different in your case.
http://localhost:50016/api/Players
You should be able to see the player’s information returned from the API in JSON format.
At this point, you may see the following error in your browser. This error comes if you have two objects
referencing each other. In the above JSON, you can see that every player has a position property and every
position object has an array of players inside which results in circular dependency between two objects.
Ideally, we should not return the database entities from Web APIs and we should create separate DTOs for
transforming and returning data but I avoid that approach in this tutorial to keep things simple.
If you ever receive above error, you can solve it easily by adding Microsoft.AspNetCore.Mvc.NewtonsoftJson
NuGet package in your project and adding AddNewtonsoftJson method in ConfigureServices method of Startup.cs
file.
1 services.AddControllersWithViews()
2 .AddNewtonsoftJson(options =>
3 options.SerializerSettings.ReferenceLoopHandling =
4 Newtonsoft.Json.ReferenceLoopHandling.Ignore
5 );
The APIs we created above will expose the following endpoints related to Players and we will use these
endpoints in our Angular App to perform CRUD operations.
API Description
GET /api/players Get all players
GET /api/players/{id} Get a player by Id
POST /api/players Create a new player
PUT /api/players/{id} Update an existing player
DELETE /api/players/{id} Delete a player
FRONTEND ANGULAR
ANTES CREAR UN PROYECTO ANGULAR, SOBRE EL PROYECTO REALIZAR ESTO
Use Angular CLI commands for generating everything related to the Angular App. Navigate to the
ClientApp folder in Windows Explorer and run the following command in command or Terminal window.
The above command will create a players folder inside the app folder and will generate the
players.module.ts and players-routing.module.ts typescript files inside the players module.
Next, we need to generate four angular components to perform CRUD operations. Run the following
command in the command window to generate the players list component.
Run the following three commands to generate details, create and edit components
While we are in the command window, we can also generate the players and positions angular
services using the following commands.
Angular services are similar to the back-end services we created above and they serve the same
purpose.
Back-end services normally have business logic related to backend operations whereas angular
services can have UI/UX related logic needed in front-end app.
Finally, generate Player and Position interfaces using the following commands. Angular interfaces
are similar to entity classes we create and use at the backend but they are used in the Angular
front-end app.
We will work on all these generated components and services shortly when we will implement CRUD
operations.
If all of the above commands are successful you will see all services, components, etc. related to
players in the players folders
The Angular module is a collection of all related modules, services, and components, and
generally, we declare the components belong to a certain module in a module file.
Open the players.module.ts and import the components we generated above using the import
statement.
We also need to specify all four components in the declarations section as shown below.
players.module.ts
1 import { NgModule } from '@angular/core';
2 import { CommonModule } from '@angular/common';
3
4 import { PlayersRoutingModule } from './players-routing.module';
5 import { ListComponent } from './list/list.component';
6 import { DetailsComponent } from './details/details.component';
7 import { CreateComponent } from './create/create.component';
8 import { EditComponent } from './edit/edit.component';
9
10 import { FormsModule, ReactiveFormsModule } from '@angular/forms';
11
12 @NgModule({
13 declarations: [
14 ListComponent,
15 DetailsComponent,
16 CreateComponent,
17 EditComponent
18 ],
19 imports: [
20 CommonModule,
21 PlayersRoutingModule,
22 FormsModule,
23 ReactiveFormsModule
24 ]
25 })
26 export class PlayersModule { }
We also need to import FormsModule and ReactiveFormsModule because we will use these modules to
create reactive angular forms shortly in create and edit player components.
The next step is to define the routes of the list, create, edit, and details components in
players-routing.module.ts file.
For example, to access the players list components we can use either /players or /players/list
URLs. View the details of a specific player, we can use /players/2/details URL in the browser.
players-routing.module.ts
1 import { NgModule } from '@angular/core';
2 import { Routes, RouterModule } from '@angular/router';
3 import { ListComponent } from './list/list.component';
4 import { DetailsComponent } from './details/details.component';
5 import { CreateComponent } from './create/create.component';
6 import { EditComponent } from './edit/edit.component';
7
8 const routes: Routes = [
9 { path: 'players', redirectTo: 'players/list', pathMatch: 'full' },
10 { path: 'players/list', component: ListComponent },
11 { path: 'players/:playerId/details', component: DetailsComponent },
12 { path: 'players/create', component: CreateComponent },
13 { path: 'players/:playerId/edit', component: EditComponent }
14 ];
15
16 @NgModule({
17 imports: [RouterModule.forChild(routes)],
18 exports: [RouterModule]
19 })
20 export class PlayersRoutingModule { }
We also want our Players list page to be accessible from the top navigation bar so add the new
Players link in nav-menu.component.ts file.
nav-menu.component.ts
1 <ul class="navbar-nav flex-grow">
2 <li class="nav-item"
3 [routerLinkActive]="['link-active']"
4 [routerLinkActiveOptions]="{ exact: true }">
5 <a class="nav-link text-dark" [routerLink]="['/']">Home</a>
6 </li>
7 <li class="nav-item" [routerLinkActive]="['link-active']">
8 <a class="nav-link text-dark" [routerLink]="['/counter']">Counter</a>
9 </li>
10 <li class="nav-item" [routerLinkActive]="['link-active']">
11 <a class="nav-link text-dark" [routerLink]="['/fetch-data']">Fetch data</a>
12 </li>
13 <li class="nav-item" [routerLinkActive]="['link-active']">
14 <a class="nav-link text-dark" [routerLink]="['/players']">Players</a>
15 </li>
16 </ul>
Finally, we need to import our players module in the application module as follows:
app.module.ts
1 import { BrowserModule } from '@angular/platform-browser';
2 import { NgModule } from '@angular/core';
3 import { FormsModule } from '@angular/forms';
4 import { HttpClientModule } from '@angular/common/http';
5 import { RouterModule } from '@angular/router';
6
7 import { PlayersModule } from './players/players.module';
8
9 import { AppComponent } from './app.component';
10 import { NavMenuComponent } from './nav-menu/nav-menu.component';
11 import { HomeComponent } from './home/home.component';
12 import { CounterComponent } from './counter/counter.component';
13 import { FetchDataComponent } from './fetch-data/fetch-data.component';
14 import { ListComponent } from "./players/list/list.component";
15
16 @NgModule({
17 declarations: [
18 AppComponent,
19 NavMenuComponent,
20 HomeComponent,
21 CounterComponent,
22 FetchDataComponent
23 ],
24 imports: [
25 BrowserModule.withServerTransition({ appId: 'ng-cli-universal' }),
26 HttpClientModule,
27 FormsModule,
28 FormsModule,
29 PlayersModule,
30 RouterModule.forRoot([
31 { path: '', component: HomeComponent, pathMatch: 'full' },
32 { path: 'counter', component: CounterComponent },
33 { path: 'fetch-data', component: FetchDataComponent },
34 { path: 'players', component: ListComponent },
35 ])
36 ],
37 providers: [],
38 bootstrap: [AppComponent]
39 })
40 export class AppModule { }
Run the application and you should be able to navigate to the empty Players list page using the
Players navigation link in the top bar.
The first service we are going to create is the PositionsService and it has only one method
getPositions that will call our back-end Positions Web API and return all positions.
The back-end Web API URL is declared as a private variable and to call the backend Web API we are
using the HttpClient object that is injected in the constructor of the service.
positions.service.ts
?
1 import { Injectable } from '@angular/core';
2 import { HttpClient, HttpHeaders } from '@angular/common/http';
3 import { Observable, throwError } from 'rxjs';
4 import { catchError } from 'rxjs/operators';
5
6 import { Position } from "./position";
7
8 @Injectable({
9 providedIn: 'root'
10 })
11 export class PositionsService {
12
13 private apiURL = "http://localhost:50016/api";
14
15 constructor(private httpClient: HttpClient) { }
16
17 getPositions(): Observable<Position[]> {
18 return this.httpClient.get<Position[]>(this.apiURL + '/positions')
19 .pipe(
20 catchError(this.errorHandler)
21 );
22 }
23
24 errorHandler(error) {
25 let errorMessage = '';
26
27 if (error.error instanceof ErrorEvent) {
28 errorMessage = error.error.message;
29 } else {
30 errorMessage = `Error Code: ${error.status}\nMessage: ${error.message}`;
31 }
32 return throwError(errorMessage);
33 }
34 }
The next service is our main PlayersService that will define all CRUD methods related to players.
All of these methods will call a specific back-end Web API endpoint using the same HttpClient
object.
players.service.ts
1 import { Injectable } from '@angular/core';
2 import { HttpClient, HttpHeaders } from '@angular/common/http';
3 import { Observable, throwError } from 'rxjs';
4 import { catchError } from 'rxjs/operators';
5
6 import { Player } from "./player";
7
8 @Injectable({
9 providedIn: 'root'
10 })
11 export class PlayersService {
12
13 private apiURL = "http://localhost:50016/api ";
14 httpOptions = {
15 headers: new HttpHeaders({
16 'Content-Type': 'application/json'
17 })
18 };
19
20 constructor(private httpClient: HttpClient) { }
21
22 getPlayers(): Observable<Player[]> {
23 return this.httpClient.get<Player[]>(this.apiURL + '/players')
24 .pipe(catchError(this.errorHandler));
25 }
26
27 getPlayer(id): Observable<Player> {
28 return this.httpClient.get<Player>(this.apiURL + '/players/' + id)
29 .pipe(catchError(this.errorHandler));
30 }
31
32 createPlayer(player): Observable<Player> {
33 return this.httpClient.post<Player>(this.apiURL + '/players/',
34 JSON.stringify(player), this.httpOptions).pipe(catchError(this.errorHandler));
35 }
36
37 updatePlayer(id, player): Observable<Player> {
38 return this.httpClient.put<Player>(this.apiURL + '/players/' + id,
39 JSON.stringify(player), this.httpOptions).pipe(catchError(this.errorHandler));
40 }
41
42 deletePlayer(id) {
43 return this.httpClient.delete<Player>(this.apiURL + '/players/' + id,
44 this.httpOptions).pipe(catchError(this.errorHandler));
45 }
46
47 errorHandler(error) {
48 let errorMessage = '';
49
50 if (error.error instanceof ErrorEvent) {
51 errorMessage = error.error.message;
52 } else {
53 errorMessage = `Error Code: ${error.status}\nMessage: ${error.message}`;
54 }
55 return throwError(errorMessage);
56 }
57 }
Implement Players List Component
The first component we are going to create is the list component.
This component has a players property that is initialized in the ngOnInit method by calling a
getPlayers function we defined in PlayersService.
The component also has a deletePlayer function that calls the deletePlayer function defined in
PlayersService.
list.component.ts
?
1 import { Component, OnInit } from '@angular/core';
2 import { Player } from "../player";
3 import { PlayersService } from "../players.service";
4
5 @Component({
6 selector: 'app-list',
7 templateUrl: './list.component.html',
8 styleUrls: ['./list.component.css']
9 })
10 export class ListComponent implements OnInit {
11
12 players: Player[] = [];
13
14 constructor(public playersService: PlayersService) { }
15
16 ngOnInit(): void {
17 this.playersService.getPlayers().subscribe((data: Player[]) => {
18 this.players = data;
19 });
20 }
21
22 deletePlayer(id) {
23 this.playersService.deletePlayer(id).subscribe(res => {
24 this.players = this.players.filter(item => item.id !== id);
25 });
26 }
27 }
The HTML template associated with the list component is mainly iterating all the players using the
ngFor loop and generating a table with the data and typical View, Edit and Delete buttons.
There is also a Create button on top of the page that will open a page where we can create new
player in database.
list.component.html
?
<div class="container">
1
<br />
2
<div class="row">
3
<div class="col">
4
<h3>Players</h3>
5
</div>
6
<div class="col text-right">
7
<a href="#" routerLink="/players/create/"
8
class="btn btn-success btn-sm">Create New</a>
9
</div>
10
</div>
11
<br />
12
<table class="table table-bordered table-sm">
13
<tr>
14
<th>Id</th>
15
<th>Shirt No</th>
16
<th>Name</th>
17
<th>Position</th>
18
<th>Appearances</th>
19
<th>Goals</th>
20
<th>Goals per match</th>
21
<th width="200px"></th>
22
</tr>
23
<tr *ngFor="let player of players">
24
<td>{{ player.id }}</td>
25
<td>{{ player.shirtNo }}</td>
26
<td>{{ player.name }}</td>
27
<td>{{ player.position?.name }}</td>
28
<td>{{ player.appearances }}</td>
29
<td>{{ player.goals }}</td>
30
<td>{{ player.goalsPerMatch }}</td>
31
<td class="text-right">
32
<a href="#" [routerLink]="['/players/', player.id, 'details']"
33
class="btn btn-info btn-sm">View</a>
34
<a href="#" [routerLink]="['/players/', player.id, 'edit']"
35
class="btn btn-primary btn-sm">Edit</a>
36
<button type="button" (click)="deletePlayer(player.id)"
37
class="btn btn-danger btn-sm">Delete</button>
38
</td>
39
</tr>
40
</table>
41
</div>
Implement Player Details Component
The next component we are going to create is the details component.
details.component.ts
1 import { Component, OnInit } from '@angular/core';
2 import { ActivatedRoute, Router } from '@angular/router';
3
4 import { Player } from "../player";
5 import { PlayersService } from "../players.service";
6
7 @Component({
8 selector: 'app-details',
9 templateUrl: './details.component.html',
10 styleUrls: ['./details.component.css']
11 })
12 export class DetailsComponent implements OnInit {
13
14 id: number;
15 player: Player;
16
17 constructor(
18 public playersService: PlayersService,
19 private route: ActivatedRoute,
20 private router: Router
21 ) { }
22
23 ngOnInit(): void {
24 this.id = this.route.snapshot.params['playerId'];
25 this.playersService.getPlayer(this.id).subscribe((data: Player) => {
26 this.player = data;
27 });
28 }
29 }
1 <div class="container">
2 <br />
3 <h3>Player Details</h3>
4 <br />
5 <div class="card">
6 <div class="card-header">
7 <div class="row">
8 <div class="col">
9 <h4>{{ player.name }}</h4>
10 </div>
11 <div class="col text-right">
12 <a href="#" routerLink="/players/list" class="btn btn-secondary btn-sm">Back To Play
13 </div>
14 </div>
15 </div>
16 <ul class="list-group list-group-flush">
17 <li class="list-group-item"><b>Id:</b> {{ player.id }}</li>
18 <li class="list-group-item"><b>Shirt No:</b> {{ player.shirtNo }}</li>
19 <li class="list-group-item"><b>Position:</b> {{ player.position?.name }}</li>
20 <li class="list-group-item"><b>Appearances:</b> {{ player.appearances }}</li>
21 <li class="list-group-item"><b>Goals:</b> {{ player.goals }}</li>
22 <li class="list-group-item"><b>Goals per match:</b> {{ player.goalsPerMatch }}</li>
23 </ul>
24 </div>
25 </div>
Press F5 to run the project and click the View button to view the details of any player. You
should be able to see the output similar to the following screenshot.
edit.component.ts
1 import { Component, OnInit } from '@angular/core';
2 import { FormBuilder, Validators } from '@angular/forms';
3 import { ActivatedRoute, Router } from '@angular/router';
4
5 import { Player } from "../player";
6 import { Position } from "../position";
7 import { PlayersService } from "../players.service";
8 import { PositionsService } from "../positions.service";
9
10 @Component({
11 selector: 'app-edit',
12 templateUrl: './edit.component.html',
13 styleUrls: ['./edit.component.css']
14 })
15 export class EditComponent implements OnInit {
16
17 id: number;
18 player: Player;
19 positions: Position[] = [];
20 editForm;
21
22 constructor(
23 public playersService: PlayersService, public positionsService: PositionsService,
24 private route: ActivatedRoute, private router: Router,
25 private formBuilder: FormBuilder
26 ) {
27 this.editForm = this.formBuilder.group({
28 id: [''],
29 shirtNo: ['', Validators.required],
30 name: ['', Validators.required],
31 positionId: [''],
32 appearances: [''],
33 goals: [''],
34 });
35 }
36
37 ngOnInit(): void {
38 this.id = this.route.snapshot.params['playerId'];
39 this.positionsService.getPositions().subscribe((data: Position[]) => {
40 this.positions = data;
41 });
42
43 this.playersService.getPlayer(this.id).subscribe((data: Player) => {
44 this.player = data;
45 this.editForm.patchValue(data);
46 });
47 }
48
49 onSubmit(formData) {
50 this.playersService.updatePlayer(this.id, formData.value).subscribe(res => {
51 this.router.navigateByUrl('players/list');
52 });
53 }
54 }
The HTML template for the edit component is shown below and it is very similar to the create
component template we created above.
edit.component.html
?
1 <div class="container">
2 <br />
3 <div class="row">
4 <div class="col">
5 <h3>Update Player</h3>
6 </div>
7 <div class="col text-right">
8 <a href="#" routerLink="/players/list" class="btn btn-secondary btn-sm">Back To Players List</a>
9 </div>
10 </div>
11 <br />
12 <form [formGroup]="editForm" (ngSubmit)="onSubmit(editForm)">
13
14 <input formControlName="id"
15 id="id"
16 type="hidden" />
17
18 <div class="form-group">
19 <label for="shirtNo">Shirt No:</label>
20 <input formControlName="shirtNo"
21 id="shirtNo"
22 type="text"
23 class="form-control" />
24 </div>
25
26 <div class="form-group">
27 <label for="name">Name:</label>
28 <input formControlName="name"
29 id="name"
30 type="text"
31 class="form-control" />
32 </div>
33
34 <div class="form-group">
35 <label for="name">Position:</label>
36 <select formControlName="positionId"
37 id="positionId"
38 class="form-control">
39 <option value="">- Select -</option>
40 <option *ngFor="let position of positions" [ngValue]="position.id">
41 {{position.name}}
42 </option>
43 </select>
44 </div>
45
46
47 <div class="form-group">
48 <label for="appearances">Appearances:</label>
49 <input formControlName="appearances"
50 id="appearances"
51 type="text"
52 class="form-control" />
53 </div>
54
55 <div class="form-group">
56 <label for="goals">Goals:</label>
57 <input formControlName="goals"
58 id="goals"
59 type="text"
60 class="form-control" />
61 </div>
62
63 <button class="btn btn-primary" type="submit" [disabled]="!editForm.valid">Update</button>
64 </form>
65 </div>
Run the project and click the Edit button shown with every player in the players list.
You will see the edit form similar to the following screenshot where the data of the selected
player will be pre-populated for us to update.
Update the player information and submit the form. You will see the updated information in the
players list.