0% found this document useful (0 votes)
82 views31 pages

Easily Create SPA With .NET 6.0 and Angular 13 and Deploy To Azure

This document describes how to create a single-page application (SPA) using .NET 6.0 and Angular 13 in Visual Studio 2022. It discusses using the ASP.NET Core with Angular template to generate the project structure, which includes an Angular app within the ClientApp folder. It also covers adding backend functionality with ASP.NET Core, using Entity Framework to connect to a SQL database, and consuming an RSS feed with web scraping to populate the database. The SPA can then be deployed to Azure.

Uploaded by

kumar12131
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
82 views31 pages

Easily Create SPA With .NET 6.0 and Angular 13 and Deploy To Azure

This document describes how to create a single-page application (SPA) using .NET 6.0 and Angular 13 in Visual Studio 2022. It discusses using the ASP.NET Core with Angular template to generate the project structure, which includes an Angular app within the ClientApp folder. It also covers adding backend functionality with ASP.NET Core, using Entity Framework to connect to a SQL database, and consuming an RSS feed with web scraping to populate the database. The SPA can then be deployed to Azure.

Uploaded by

kumar12131
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 31

Easily Create SPA With .NET 6.

0 And
Angular 13 And Deploy To Azure
Easily Create SPA With .NET 6.0 And
Angular 13 And Deploy To Azure


 

 Sarathlal Saseendran
 

 

 Updated date Mar 07, 2022


 

 


 

 28.7k
 

 10
 

 11
 facebook

 twitter

 linkedIn

 Reddit


o
o
o
o
 Expand

Angular13ASP.NET6SPA.zip

Download Free .NET & JAVA Files API

Introduction 
A single-page application (SPA) is a web application or website that interacts with the user by
dynamically rewriting the current web page with new data from the web server, instead of the
default method of a web browser loading entire new pages. The goal is faster transitions that
make the website feel more like a native app. 
In an SPA, a page refresh never occurs; instead, all necessary HTML, JavaScript, and CSS code
is either retrieved by the browser with a single page load, or the proper resources are
dynamically loaded and added to the page as necessary, usually in response to user actions. 

Single-page applications perform well compared to traditional web applications. We can create
SPAs in Angular or React very easily.  

When you are creating an Angular or React application, you need to create a backend
application. Usually, we use .NET, Node, Java, PHP or Python as backend applications. We
must create and run separate backend applications. Visual Studio supports creating both Angular
(or react) and .NET Core in a single application. The advantage is that we can host(publish) both
these applications in a single domain as a single application.  

Visual Studio 2022 latest version (17.1.0 as of 6th March 2022) allows us to create a single-page
application with .NET 6.0 and Angular 13. (Earlier versions of Visual Studio 2022 create Angular
12 version by default) 

We can see all the steps to create an SPA and we will also see how to publish this application
into Azure.  

Create ASP.NET Core and Angular application in Visual Studio 2022 


Currently Visual Studio has two templates for Angular applications. 

We can choose the first template ASP.NET Core with Angular. The second template is used to
create standalone Angular applications.  
We can give a valid file name and click the next button.  

I have unchecked the HTTPS configuration. You can choose HTTPS if needed.  

Our project will be created in a few moments.  

We can see the structure of the entire SPA application. 

If you open the project file (.csproj) you will see the details below. 

By default, Angular 13 application is created inside a ClientApp folder. It is set inside


the SpaRoot property. If you rename the ClientApp folder, you must change the property value
in this project file as well. Angular running port is set inside the SpaProxyServerUrl property. If
you want to change the Angular running port number, you can change inside this property. At the
same time, you must change the package.json file of Angular application also.  
We can see the Angular app structure. 

As we discussed earlier, Angular app is created under the ClientApp folder. The project structure
is like a normal Angular 13 project, but it has two modules. It also has a new file
named proxy.conf.js. This is a configuration file which has the ASP.NET backend controller
names. We will discuss more about this file later.  

We will be deploying this application to Azure. Hence, we can create a simple working
application instead of default template. We will be creating an application to analyze the C#
Corner posts (articles/blogs) details of an author.  
I have already written two detailed articles about this topic and published. Please read the below
articles.  

Easily Do Web Scraping In .NET Core 6.0


Easily Create Charts In Angular 13 with Dynamic Data
Complete the Backend ASP.NET Core 6.0 application 
We must install the libraries below using NuGet package manger.  

 HtmlAgilityPack  
 Microsoft.EntityFrameworkCore.SqlServer  
 Microsoft.EntityFrameworkCore.Tools 

We are using Entity framework in this application.  

We can add database connection string and parallel task counts inside the appsettings.json file. 

appsettings.json 
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"ConnStr": "Data Source=(localdb)\\MSSQLLocalDB;Initial
Catalog=AnalyticsDB;Integrated
Security=True;ApplicationIntent=ReadWrite;MultiSubnetFailover=False"
},
"ParallelTasksCount": 20
}
C#

Copy

Database connection string will be used by entity framework to connect SQL database and
parallel task counts will be used by web scraping parallel foreach code. 

We can create a Feed class inside a Models folder. This class will be used to get required
information from C# Corner RSS feeds. 
Feed.cs 
namespace Angular13ASP.NET6SPA.Models
{
public class Feed
{
public string Link { get; set; }
public string Title { get; set; }
public string FeedType { get; set; }
public string Author { get; set; }
public string Content { get; set; }
public DateTime PubDate { get; set; }

public Feed()
{
Link = "";
Title = "";
FeedType = "";
Author = "";
Content = "";
PubDate = DateTime.Today;
}
}
}
C#

Copy

We can create an ArticleMatrix class inside the Models folder. This class will be used to get
information for each article/blog once we get it after web scraping. 

ArticleMatrix.cs 
using System.ComponentModel.DataAnnotations.Schema;

namespace Angular13ASP.NET6SPA.Models
{
public class ArticleMatrix
{
public int Id { get; set; }
public string? AuthorId { get; set; }
public string? Author { get; set; }
public string? Link { get; set; }
public string? Title { get; set; }
public string? Type { get; set; }
public string? Category { get; set; }
public string? Views { get; set; }
[Column(TypeName = "decimal(18,4)")]
public decimal ViewsCount { get; set; }
public int Likes { get; set; }
public DateTime PubDate { get; set; }
}
}
C#

Copy

We can create an Authors class inside the Models folder.  

Authors.cs 
namespace Angular13ASP.NET6SPA.Models
{
public class Authors
{
public string? AuthorId { get; set; }
public string? Author { get; set; }
public int Count { get; set; }
}
}
C#

Copy

We can create Category class. 


Category.cs 
namespace Angular13ASP.NET6SPA.Models
{
public class Category
{
public string? Name { get; set; }
public int Count { get; set; }
}
}
C#

Copy

We can create our DB context class for Entity framework. 

MyDbContext.cs 
using Microsoft.EntityFrameworkCore;

namespace Angular13ASP.NET6SPA.Models
{
public class MyDbContext : DbContext
{
public MyDbContext(DbContextOptions<MyDbContext> options)
: base(options)
{
}
public DbSet<ArticleMatrix>? ArticleMatrices { get; set; }

protected override void OnModelCreating(ModelBuilder builder)


{
base.OnModelCreating(builder);
}
}
}
C#

Copy

We can create our API controller AnalyticsController and add web scraping code inside it.  

AnalyticsController.cs 
using Angular13ASP.NET6SPA.Models;
using HtmlAgilityPack;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System.Globalization;
using System.Net;
using System.Xml.Linq;

namespace Angular13ASP.NET6SPA.Controllers
{
[Route("[controller]")]
[ApiController]
public class AnalyticsController : ControllerBase
{
readonly CultureInfo culture = new("en-US");
private readonly MyDbContext _dbContext;
private readonly IConfiguration _configuration;
public AnalyticsController(MyDbContext context, IConfiguration
configuration)
{
_dbContext = context;
_configuration = configuration;
}

[HttpPost]
[Route("CreatePosts/{authorId}")]
public async Task<bool> CreatePosts(string authorId)
{
try
{
XDocument doc = XDocument.Load("https://www.c-
sharpcorner.com/members/" + authorId + "/rss");
if (doc == null)
{
return false;
}
var entries = from item in doc.Root.Descendants().First(i =>
i.Name.LocalName == "channel").Elements().Where(i => i.Name.LocalName ==
"item")
select new Feed
{
Content = item.Elements().First(i =>
i.Name.LocalName == "description").Value,
Link = (item.Elements().First(i =>
i.Name.LocalName == "link").Value).StartsWith("/") ? "https://www.c-
sharpcorner.com" + item.Elements().First(i => i.Name.LocalName ==
"link").Value : item.Elements().First(i => i.Name.LocalName == "link").Value,
PubDate =
Convert.ToDateTime(item.Elements().First(i => i.Name.LocalName ==
"pubDate").Value, culture),
Title = item.Elements().First(i =>
i.Name.LocalName == "title").Value,
FeedType = (item.Elements().First(i =>
i.Name.LocalName == "link").Value).ToLowerInvariant().Contains("blog") ?
"Blog" : (item.Elements().First(i => i.Name.LocalName ==
"link").Value).ToLowerInvariant().Contains("news") ? "News" : "Article",
Author = item.Elements().First(i =>
i.Name.LocalName == "author").Value
};

List<Feed> feeds = entries.OrderByDescending(o =>


o.PubDate).ToList();
string urlAddress = string.Empty;
List<ArticleMatrix> articleMatrices = new();
_ = int.TryParse(_configuration["ParallelTasksCount"], out int
parallelTasksCount);

Parallel.ForEach(feeds, new ParallelOptions


{ MaxDegreeOfParallelism = parallelTasksCount }, feed =>
{
urlAddress = feed.Link;

var httpClient = new HttpClient


{
BaseAddress = new Uri(urlAddress)
};
var result = httpClient.GetAsync("").Result;

string strData = "";

if (result.StatusCode == HttpStatusCode.OK)
{
strData = result.Content.ReadAsStringAsync().Result;

HtmlDocument htmlDocument = new();


htmlDocument.LoadHtml(strData);

ArticleMatrix articleMatrix = new()


{
AuthorId = authorId,
Author = feed.Author,
Type = feed.FeedType,
Link = feed.Link,
Title = feed.Title,
PubDate = feed.PubDate
};

string category = "Uncategorized";


if (htmlDocument.GetElementbyId("ImgCategory") !=
null)
{
category =
htmlDocument.GetElementbyId("ImgCategory").GetAttributeValue("title", "");
}

articleMatrix.Category = category;

var view =
htmlDocument.DocumentNode.SelectSingleNode("//span[@id='ViewCounts']");
if (view != null)
{
articleMatrix.Views = view.InnerText;

if (articleMatrix.Views.Contains('m'))
{
articleMatrix.ViewsCount =
decimal.Parse(articleMatrix.Views[0..^1]) * 1000000;
}
else if (articleMatrix.Views.Contains('k'))
{
articleMatrix.ViewsCount =
decimal.Parse(articleMatrix.Views[0..^1]) * 1000;
}
else
{
_ = decimal.TryParse(articleMatrix.Views, out
decimal viewCount);
articleMatrix.ViewsCount = viewCount;
}
}
else
{
articleMatrix.ViewsCount = 0;
}
var like =
htmlDocument.DocumentNode.SelectSingleNode("//span[@id='LabelLikeCount']");
if (like != null)
{
_ = int.TryParse(like.InnerText, out int likes);
articleMatrix.Likes = likes;
}

articleMatrices.Add(articleMatrix);
}

});

_dbContext.ArticleMatrices.RemoveRange(_dbContext.ArticleMatrices.Where(x =>
x.AuthorId == authorId));

foreach (ArticleMatrix articleMatrix in articleMatrices)


{
await _dbContext.ArticleMatrices.AddAsync(articleMatrix);
}

await _dbContext.SaveChangesAsync();
return true;
}
catch
{
return false;
}
}

[HttpGet]
[Route("GetAuthors")]
public IQueryable<Authors> GetAuthors()
{
return _dbContext.ArticleMatrices.GroupBy(author =>
author.AuthorId)
.Select(group =>
new Authors
{
AuthorId = group.FirstOrDefault().AuthorId,
Author = group.FirstOrDefault().Author,
Count = group.Count()
})
.OrderBy(group => group.Author);
}

[HttpGet]
[Route("GetCategory/{authorId}")]
public IQueryable<Category> GetCategory(string authorId)
{
return from x in _dbContext.ArticleMatrices.Where(x => x.AuthorId
== authorId).GroupBy(x => x.Category)
select new Category
{
Name = x.FirstOrDefault().Category,
Count = x.Count()
};
}
}
}
C#

Copy

Please note that route of the API controller should not carry “api/” prefix. Otherwise, SPA will not
work.  

We have added a new API controller now. We must add this controller entry in Angular proxy
configuration file inside the context property. Otherwise, API call from Angular will fail.  

proxy.conf.js 
const { env } = require('process');

const target = env.ASPNETCORE_HTTPS_PORT ? `https://localhost:$


{env.ASPNETCORE_HTTPS_PORT}` :
env.ASPNETCORE_URLS ? env.ASPNETCORE_URLS.split(';')[0] :
'http://localhost:38078';

const PROXY_CONFIG = [
{
context: [
"/weatherforecast",
"/analytics",
],
target: target,
secure: false,
headers: {
Connection: 'Keep-Alive'
}
}
]

module.exports = PROXY_CONFIG;
JavaScript

Copy

Also note that the above context entry is case sensitive.  

Finally, we can make the changes below inside the Program.cs file. 

Program.cs 
using Angular13ASP.NET6SPA.Models;
using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);


ConfigurationManager configuration = builder.Configuration;
// Add services to the container.

builder.Services.AddControllersWithViews();

builder.Services.AddDbContext<MyDbContext>(options =>
options.UseSqlServer(configuration.GetConnectionString("ConnStr")));

var app = builder.Build();

// Configure the HTTP request pipeline.


if (!app.Environment.IsDevelopment())
{
}

app.UseStaticFiles();
app.UseRouting();

app.MapControllerRoute(
name: "default",
pattern: "{controller}/{action=Index}/{id?}");

app.MapFallbackToFile("index.html"); ;

app.Run();
C#

Copy

We are using a local database. You must run the migration commands below in Package
Manager Console to create database and table.  

PM > add-migration InititalScript 


PM> update-database 

We have completed the backend code. 

Complete the Angular 13 application 


We can run the npm command to install the node modules for Angular application. 

We can open the ClientApp folder in command prompt and install node modules. (If you have not
installed node modules, the system will automatically install while running the application for the
first time.) 

npm install

We must install the client libraries below in our Angular application. 

 chart.js  
 ng2-charts  
 bootstrap  
 font-awesome 
We can use below single npm command to install all these libraries. 

npm install bootstrap chart.js font-awesome ng2-charts  

We can change styles.css file with code changes given below.  

styles.css 
@import "~bootstrap/dist/css/bootstrap.css";
@import "~font-awesome/css/font-awesome.css";
CSS

Copy

We can create a new Angular component Analytics using the command below. 

ng generate component analytics 

We have received an error. The reason is, we have two different modules in Angular app.  

We must specify the correct module name. 

ng generate component analytics --module=app.module.ts 

We can change the component class file with code below. 

analytics.component.ts 
import { HttpClient } from '@angular/common/http';
import { Component, Inject, OnInit } from '@angular/core';
import { FormBuilder, FormGroup } from '@angular/forms';
import { ChartData, ChartOptions } from 'chart.js';

@Component({
selector: 'app-analytics',
templateUrl: './analytics.component.html',
styleUrls: ['./analytics.component.css']
})

export class AnalyticsComponent implements OnInit {


private url!: string;

constructor(private http: HttpClient, private fb: FormBuilder,


@Inject('BASE_URL') baseUrl: string) {
this.url = baseUrl + 'analytics';
}

chartData: ChartData<'pie'> = {
labels: [],
datasets: [
{
data: [],
}
]
};
chartOptions: ChartOptions = {
responsive: true,
plugins: {
title: {
display: true,
text: '',
},
legend: {
display: false
},
},
};

authors: Author[] = [];


authorForm!: FormGroup;
showLoader!: boolean;
totalPosts!: number;
categories: string[] = [];
counts: number[] = [];

ngOnInit(): void {
this.authorForm = this.fb.group({
authorId: '',
chartType: 'pie',
author: null,
category: '',
showLegend: false
});
this.showAuthors();
}

showAuthors() {
this.showLoader = true;
this.http.get<Author[]>(this.url + '/getauthors')
.subscribe({
next: (result) => {
this.authors = result;
this.showLoader = false;
},
error: (err) => {
console.error(err);
this.showLoader = false;
},
complete: () => console.info('Get authors completed')
});
}

populateData() {
if (!this.authorForm.value.authorId) {
alert('Please give a valid Author Id');
return;
}
this.categories = [];
this.showLoader = true;
this.clearChart();
this.http.post(this.url + '/createposts/' +
this.authorForm.value.authorId, null)
.subscribe({
next: (result) => {
this.showAuthors();
this.showLoader = false;
if (result == true) {
alert('Author data successfully populated!');
}
else {
alert('Invalid Author Id');
}
this.authorForm.patchValue({
author: '',
chartType: 'pie',
showLegend: false
});
},
error: (err) => {
console.error(err);
this.authorForm.patchValue({
author: ''
});
},
complete: () => console.info('Populate data completed')
});
}

fillCategory() {
this.counts = [];
this.authorForm.patchValue({
category: ''
});
this.totalPosts = 0;
this.categories = [];
this.counts = [];
this.authorForm.patchValue({
authorId: this.authorForm.value.author.authorId,
});
if (!this.authorForm.value.author.authorId) {
return;
}
this.showLoader = true;
this.http.get<Categroy[]>(this.url + '/getcategory/' +
this.authorForm.value.author.authorId)
.subscribe({
next: (result) => {
result.forEach(x => {
this.totalPosts += x.count;
this.categories.push(x.name);
this.counts.push(x.count);
});
if (!result || result.length == 0) return;

this.chartData = {
labels: this.categories,
datasets: [
{
data: this.counts,
}
]
};

this.chartOptions = {
responsive: true,
plugins: {
title: {
display: true,
text: 'C# Corner Article Categories for : ' +
this.authorForm.value.author.author,
},
legend: {
display: this.authorForm.value.showLegend
},
},
};
this.showLoader = false;
},
error: (err) => {
console.error(err);
this.showLoader = false;
},
complete: () => { console.info('Fill category completed') }
});
}

changeLegends() {
this.chartOptions = {
responsive: true,
plugins: {
title: {
display: true,
text: 'C# Corner Article Categories for : ' +
this.authorForm.value.author.author,
},
legend: {
display: this.authorForm.value.showLegend
},
},
};
}

clearChart() {
this.chartData = {
labels: [],
datasets: [
{
data: [],
}
]
};
this.chartOptions = {
responsive: true,
plugins: {
title: {
display: true,
text: '',
},
legend: {
display: false
},
},
};
}

interface Author {
authorId: string;
author: string;
count: number;
}

interface Categroy {
name: string;
count: number;
}
JavaScript

Copy

Change the template file with code below. 

analytics.component.html 
<form novalidate [formGroup]="authorForm">
<div class="card row card-row">
<div class="card-header">
<div class="row">
<div class="col-md-6">
<img src="../assets/c-sharpcorner.png" class="logo"> C# Corner
Author Analytics
</div>
<div class="col-md-6 total-author-position">
<label>Total</label>&nbsp;<label class="total-author-color-
change">Authors</label> populated so far : {{authors.length}}
</div>
</div>
</div>

<div class="card-body">
<div class="row">
<div class="col-md-6">
<div class="form-group row mb-4">
<label class="col-md-3 col-form-label" for="authorId">Author
Id</label>
<div class="col-md-4">
<input class="form-control" id="authorId"
formControlName="authorId" type="text"
placeholder="Eg: sarath-lal7" />
</div>
<div class="col-md-5">
<button class="btn btn-primary mr-3" (click)="populateData()">
Populate Author Data
</button>
</div>
</div>

<div class="form-group row mb-4">


<label class="col-md-3 col-form-label" for="authorId">Author
Name</label>
<div class="col-md-4">
<select class="form-select" formControlName="author"
(ngModelChange)="fillCategory()" id="authorId">
<option value="" disabled>Select an Author</option>
<option *ngFor="let myauthor of authors"
[ngValue]="myauthor">{{myauthor.author}} </option>
</select>
</div>
<label class="col-md-2 col-form-label" for="chartType">Chart
Type</label>
<div class="col-md-3">
<select id="chartType" class="form-select"
formControlName="chartType">
<option value="pie">Pie</option>
<option value="doughnut">Doughnut</option>
<option value="polarArea">Polar Area</option>
<option value="radar">Radar</option>
<option value="bar">Bar</option>
<option value="line">Line</option>
</select>
</div>
</div>

<div class="form-group row mb-4" *ngIf="categories.length>0">


<b class="col-md-7">
Total Categories : {{ categories.length}} &nbsp; &nbsp; Total
Posts :
{{totalPosts}}
</b>
<div class="col-md-4">
<div class="form-check">
<input class="form-check-input" type="checkbox"
formControlName="showLegend"
(ngModelChange)="changeLegends()">
<label class="form-check-label" for="flexCheckChecked">
Show Chart Legends
</label>
</div>
</div>
</div>
</div>
<div class="col-md-6">
<div class="chart-container chart-position"
*ngIf="categories.length>0">
<canvas baseChart [data]="chartData"
[type]="authorForm.value.chartType" [options]="chartOptions">
</canvas>
</div>
<div class="file-loader" *ngIf="showLoader">
<div class="upload-loader">
<div class="loader"></div>
</div>
</div>
</div>
</div>
</div>
</div>
</form>
Markup

Copy

Change the style sheet with code below. 

analytics.component.css 
/* Spin Start*/

.file-loader {
background-color: rgba(0, 0, 0, .5);
overflow: hidden;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 100000 !important;
}

.upload-loader {
position: absolute;
width: 60px;
height: 60px;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
}

.upload-loader .loader {
border: 5px solid #f3f3f3 !important;
border-radius: 50%;
border-top: 5px solid #005eb8 !important;
width: 100% !important;
height: 100% !important;
-webkit-animation: spin 2s linear infinite;
animation: spin 2s linear infinite;
}

@-webkit-keyframes spin {
0% {
-webkit-transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
}
}

@keyframes spin {
0% {
transform: rotate(0deg);
}

100% {
transform: rotate(360deg);
}
}

/* Spin End*/

.card-row {
margin: 40px;
height: 600px;
}

.card-header {
background-color: azure;
font-weight: bold;
}

.total-author-position {
text-align: right;
}

.total-author-color-change {
color: blue;
}

.logo {
width: 30px;
}

.chart-position {
position: relative;
height: 17vh;
width: 34vw
}
CSS

Copy

We can change the NavMenu component now. 

nav-menu.component.html 
<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]="['/']">Angular13ASP.NET6SPA</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 justify-content-
end"
[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]="['/analytic-data']">C#
Corner Analytics</a>
</li>
</ul>
</div>
</div>
</nav>
</header>
<footer>
<nav class="navbar navbar-light bg-
white mt-5 fixed-bottom">
<div class="navbar-expand m-auto navbar-text">
Developed with <i class="fa fa-heart"></i> by <b>
Sarathlal
Saseendran
</b>
</div>
</nav>
</footer>
Markup

Copy

nav-menu.component.css 
.fa-heart {
color: hotpink;
}

.align-center {
text-align: center
}

.title {
color: black;
font-weight: bold;
font-size: large;
}
a.navbar-brand {
white-space: normal;
text-align: center;
word-break: break-all;
}

html {
font-size: 14px;
}

@media (min-width: 768px) {


html {
font-size: 16px;
}
}

.box-shadow {
box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05);
}
CSS

Copy

We can change the Home component 

home.component.html 
<div style="text-align:center">
<img src="../../assets/Dotnet Core with Angular 13.png" width="700" />
<h1>Easily create SPA with .NET 6.0 and Angular 13</h1>
<div>
<a class="nav-link text-dark" [routerLink]="['/analytic-data']">Click here
to see <img src="../assets/c-sharpcorner.png" width="75"> C# Corner Author
Analytics</a>
</div>

</div>
Markup

Copy

We can change the AppModule with the code changes below. 

app.module.ts 
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { HttpClientModule } 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 { AnalyticsComponent } from './analytics/analytics.component';
import { NgChartsModule } from 'ng2-charts';

@NgModule({
declarations: [
AppComponent,
NavMenuComponent,
HomeComponent,
CounterComponent,
FetchDataComponent,
AnalyticsComponent
],
imports: [
BrowserModule.withServerTransition({ appId: 'ng-cli-universal' }),
HttpClientModule,
FormsModule,
ReactiveFormsModule,
NgChartsModule,
RouterModule.forRoot([
{ path: '', component: HomeComponent, pathMatch: 'full' },
{ path: 'counter', component: CounterComponent },
{ path: 'fetch-data', component: FetchDataComponent },
{ path: 'analytic-data', component: AnalyticsComponent },
])
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
JavaScript

Copy

We have completed the entire application. We can run the application now. 

You can notice that Angular is running in port 44456 and .NET Core is running in port 5172.  

First, the application is running in port 5172 and once the Angular application is ready it will be
redirected to port 44456. 

This will take some time to start the application locally. We must be patient. Also, sometimes you
will get two command prompts for Angular application before the application starts fully. You can
close the second command prompt.  
Click the menu in the navigation bar or click the link in the center of the page to open the C#
Corner analytics page. 

We can populate the author data by giving proper author id.  


We can deploy this application to Azure now.  

We must create a Web App in Azure portal first.  


We must choose .NET 6 as the runtime stack.  

We can choose a valid App Service plan name and size. I have chosen a basic plan. It will be a
shared resource with 240 minutes (about 4 hours) of compute per day.  

I have not enabled the Application Insights for this Web app. We can review it and create. 

Web App will be ready in a few moments.  

I have already created an Azure SQL server database in Azure portal. We must change the
database connection string to this Azure SQL database in appsettings.  

Now we can deploy our SPA to this web app.  

You can click the Publish menu from solution (right click) and choose Azure option. 
Click the next button and choose App Service Plan (Windows) 

We must choose our Azure subscription.  


We can see that the already created Web app is available under the resource group. 

Choose the Web app and click on the finish button.  

Our publishing profile is created now. We can click the Publish button to deploy our application. 
Both Angular and .NET applications will be deployed to a single Azure Web app soon. 

We can see that the Web app is working as expected.  


You can try this Live App and populate your favorite author’s data. 
Conclusion 
In this post, we have seen how to create an SPA with ASP.NET Core 6.0 and Angular 13. We
have created a Web app which populate the C# Corner Author’s data and display the post
category as a chart. We have run this application locally and then created an Azure Web app in
Azure portal and deployed our SPA to this Web App. I am still working on this Analytics
application and will be adding more interesting features in coming days. Please feel free to give
your valuable feedback about this application and article.

You might also like