Introduction
One of the most common and ever-growing applications in the Web space these days are Single Page Applications (SPA) which are developed used JavaScript frameworks such as Angular, React, etc. for the application's front end. You will find many articles and tutorials on building Angular applications out there using Visual Studio Code. However, Visual Studio 2019 comes with a very neat template for building Angular applications. In this article, we will explore this template and how to enhance it and add our own functionality to it.
Visual Studio 2019 Angular Template
In order to build an Angular front-end application in Visual Studio, we need to follow the below steps:
Then, enter the solution and project name. After that,
Select the Angular template and click “Create”
This will create an Angular application with two components:
- A Web API back end application
- An Angular front-end application
Below is the structure of the application:
We can build and run this skeleton application. We will get the below screenshot:
If we click the fetch data link, a call is made to the Weather Forecast Controller on the server-side and the data is returned and displayed on the page, as shown below:
The version used was Angular 8.
Adding Functionality to our Angular Application
As an example, to add functionality to the application, we will add a Pages table in which we will store data related to a page. This will include the page name, header, content, etc. We will then build the Pages controller which will read data from the table using Entity Framework Core. Then, on the client-side, we will create a new Pages Component that will use a Pages Service to read the data from the Pages Web API on the server and display these pages on the client-side.
We first create the table and add some records, as shown below:
- CREATE TABLE [dbo].[Pages](
- [ID] [int] IDENTITY(1,1) NOT NULL,
- [Title] [nvarchar](max) NULL,
- [Header] [nvarchar](max) NULL,
- [Content] [nvarchar](max) NULL,
- CONSTRAINT [PK_Pages] PRIMARY KEY CLUSTERED
- (
- [ID] ASC
- )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
- ) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
- GO
- INSERT INTO [AngularDB].[dbo].[Pages] (Title,Header,Content) values ('home','Home','This is the Home Page')
- INSERT INTO [AngularDB].[dbo].[Pages] (Title,Header,Content) values ('about','About','This is the About Page')
- INSERT INTO [AngularDB].[dbo].[Pages] (Title,Header,Content) values ('privacy','Privacy','This is the Privacy Page')
Next, we create the Page Class (In a new Models folder), DB Context for Entity Framework Core (In a new Data folder), and the Pages Controller, as below, to complete the server-side Web API pieces:
- namespace AngularFrontend.Models
- {
- public class Page
- {
- public int ID { get; set; }
- public string Title { get; set; }
- public string Header { get; set; }
- public string Content { get; set; }
- }
- }
- namespace AngularFrontend.Data
- {
- public class AngularBackendContext : DbContext
- {
- public AngularBackendContext(DbContextOptions<AngularBackendContext> options) : base(options)
- {
- }
-
- public DbSet<Page> Pages { get; set; }
- }
- }
- using System.Linq;
- using AngularFrontend.Data;
- using Microsoft.AspNetCore.Mvc;
-
- namespace AngularFrontend.Controllers
- {
- [Route("api/[controller]")]
- [ApiController]
- public class PagesController : ControllerBase
- {
-
- private readonly AngularBackendContext _context;
- public PagesController(AngularBackendContext context)
- {
- _context = context;
- }
-
-
- [HttpGet]
- public IActionResult Get()
- {
- var pages = _context.Pages.ToList();
- return Ok(pages);
- }
-
-
- [HttpGet("{id}", Name = "Get")]
- public string Get(int id)
- {
- return "value";
- }
-
-
- [HttpPost]
- public void Post([FromBody] string value)
- {
- }
-
-
- [HttpPut("{id}")]
- public void Put(int id, [FromBody] string value)
- {
- }
-
-
- [HttpDelete("{id}")]
- public void Delete(int id)
- {
- }
- }
- }
We also need to add the connection string and add the DB context to the services collection, as shown below:
- {
- "Logging": {
- "LogLevel": {
- "Default": "Information",
- "Microsoft": "Warning",
- "Microsoft.Hosting.Lifetime": "Information"
- }
- },
- "ConnectionString": {
- "AngularCS": "Data Source=localhost\\SQLEXPRESS;Initial Catalog=AngularDB;Integrated Security=True"
- },
- "AllowedHosts": "*"
- }
- public void ConfigureServices(IServiceCollection services)
- {
- services.AddDbContext<AngularBackendContext>(opts => opts.UseSqlServer(Configuration["ConnectionString:AngularCS"]));
- services.AddControllersWithViews();
-
- services.AddSpaStaticFiles(configuration =>
- {
- configuration.RootPath = "ClientApp/dist";
- });
- }
Next, we move on to the Angular client application and create the following:
- Pages component
- Pages service. This will be used to communicate with the Pages Web API on the server-side
- import { Injectable, Inject } from '@angular/core';
- import { HttpClient } from '@angular/common/http';
-
- @Injectable()
- export class PageService {
-
- public _baseUrl: string;
-
- constructor(private http: HttpClient, @Inject('BASE_URL') baseUrl: string)
- {
- this._baseUrl = baseUrl;
- }
-
- getPages() {
- return this.http.get(this._baseUrl + 'api/pages');
- }
-
- }
- import { Component, Inject } from '@angular/core';
- import { PageService } from '../services/page.service';
- import { Router } from '@angular/router';
-
- @Component({
- selector: 'app-pages',
- templateUrl: './pages.component.html'
- })
- export class PagesComponent {
- public pages: any;
-
- constructor(private router: Router, private pageService: PageService) {
- this.pageService.getPages().subscribe(result => {
- this.pages = result;
- }, error => console.error(error));
- }
- }
The pages HTML template is shown below:
- <h1 class="page-title">Pages</h1>
- <br>
-
- <table class="table">
- <tr bgcolor="#f9f9f9">
- <th>Title</th>
- <th>Header</th>
- <th>Content</th>
- </tr>
-
- <tr *ngFor="let page of pages">
- <td>
- {{ page.title }}
- </td>
- <td>
- {{ page.header }}
- </td>
- <td>
- {{ page.content }}
- </td>
- </tr>
- </table>
We also need to add the component and service to the app.module.ts file and the nav-menu component html file, as shown below:
- import { BrowserModule } from '@angular/platform-browser';
- import { NgModule } from '@angular/core';
- import { FormsModule } from '@angular/forms';
- import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
- import { RouterModule } from '@angular/router';
-
- import { AppComponent } from './app.component';
- import { NavMenuComponent } from './nav-menu/nav-menu.component';
- import { HomeComponent } from './home/home.component';
- import { CounterComponent } from './counter/counter.component';
- import { FetchDataComponent } from './fetch-data/fetch-data.component';
- import { PagesComponent } from './pages/pages.component';
-
- import { PageService } from './services/page.service';
-
- @NgModule({
- declarations: [
- AppComponent,
- NavMenuComponent,
- HomeComponent,
- CounterComponent,
- FetchDataComponent,
- PagesComponent
- ],
- imports: [
- BrowserModule.withServerTransition({ appId: 'ng-cli-universal' }),
- HttpClientModule,
- FormsModule,
- RouterModule.forRoot([
- { path: '', component: HomeComponent, pathMatch: 'full' },
- { path: 'counter', component: CounterComponent },
- { path: 'fetch-data', component: FetchDataComponent },
- { path: 'pages', component: PagesComponent },
- ])
- ],
- providers: [PageService],
- bootstrap: [AppComponent]
- })
- export class AppModule { }
The nav-menu component HTML file:
- <header>
- <nav
- class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3"
- >
- <div class="container">
- <a class="navbar-brand" [routerLink]="['/']">AngularFrontend</a>
- <button
- class="navbar-toggler"
- type="button"
- data-toggle="collapse"
- data-target=".navbar-collapse"
- aria-label="Toggle navigation"
- [attr.aria-expanded]="isExpanded"
- (click)="toggle()"
- >
- <span class="navbar-toggler-icon"></span>
- </button>
- <div
- class="navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse"
- [ngClass]="{ show: isExpanded }"
- >
- <ul class="navbar-nav flex-grow">
- <li class="nav-item"
- [routerLinkActive]="['link-active']"
- [routerLinkActiveOptions]="{ exact: true }">
- <a class="nav-link text-dark" [routerLink]="['/']">Home</a>
- </li>
- <li class="nav-item" [routerLinkActive]="['link-active']">
- <a class="nav-link text-dark" [routerLink]="['/counter']">Counter</a>
- </li>
- <li class="nav-item" [routerLinkActive]="['link-active']">
- <a class="nav-link text-dark" [routerLink]="['/fetch-data']">Fetch data</a>
- </li>
- <li class="nav-item" [routerLinkActive]="['link-active']">
- <a class="nav-link text-dark" [routerLink]="['/pages']">Pages</a>
- </li>
- </ul>
- </div>
- </div>
- </nav>
- </header>
Now, when we run the application, we can see the below screenshot:
Next, we click the Pages link, we see the below screenshot:
Troubleshooting an Angular Application
While working on the front-end Angular application, I once saw the below error (via the Developer Tools on the Chrome browser)
Refused to load the image 'http://localhost:44380/favicon.ico' because it violates the following Content Security Policy directive: "default-src 'none'". Note that 'img-src' was not explicitly set, so 'default-src' is used as a fallback.
This was a puzzling error, but after some investigation, I found that this is a generic error that is masking some other error. To find the underlying error, I opened the Developer Command Prompt for Visual Studio 2019 at the client application location and used the below command:
Ng-build watch
This gave me details on what I had done wrong. After I fixed that, I ran it again and got the below screenshot:
After this, everything worked fine.
Summary
In this article, we looked at the creation of a Single Page Application in Angular using the Visual Studio 2019 built-in template. This is a nice template which gives us an ASP.NET Core Web API on the backend and an Angular application on the front-end. We can then customize both the server-side and client-side code to add our desired functionality. I have also mentioned some ways we can troubleshoot issues, especially on the front-end Angular application.