.Net + Microsoft SQL Server + Angular 15 CRUD Application Example

In this section, we will learn how to develop a full-stack web application that is a basic User Management Application using .Net 6Microsoft SQL ServerMicrosoft Entity Framework Core provider for SQL Server, and Angular 15. You could download the source code from our Github repository, the download link is provided at the end of this tutorial.

After completing this tutorial what we will build?

We will build a full-stack web application that is a basic User Management Application with CRUD features: 

• Create User 
• List User 
• Update User
• View User
• Delete User

We divided this tutorial into two parts. 

PART 1 - Restful API Development with .Net 6 & Microsoft SQL Server.
PART 2 - UI development using Angular 15.


PART 1 - Restful API Development with .Net 6 & Microsoft SQL Server.

These are APIs that .Net backend App will export:


Create database and table.

First, you need to create a database in the Microsoft server. 
CREATE DATABASE testdb;

Create table:
CREATE TABLE users (
  id              INT           NOT NULL    IDENTITY    PRIMARY KEY,
  first_name      VARCHAR(100)  NOT NULL,
  last_name       VARCHAR(100),
  email           VARCHAR(100)  NOT NULL,
);


Project directory:


User Entity

Path: /Entities/User.cs

namespace WebApi.Entities;

using System.Text.Json.Serialization;

public class User
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Email { get; set; }
 
}


App Exception

Path: /Helpers/AppException.cs

The app exception is a custom exception class used to differentiate between handled and unhandled exceptions in the .NET API. Handled exceptions are generated by application code and used to return custome error messages.

namespace WebApi.Helpers;

using System.Globalization;

public class AppException : Exception
{
    public AppException() : base() {}

    public AppException(string message) : base(message) { }

    public AppException(string message, params object[] args)
        : base(String.Format(CultureInfo.CurrentCulture, message, args))
    {
    }
}


AutoMapper Profile

Path: /Helpers/AutoMapperProfile.cs

AutoMapper is an object mapper that helps you transform one object of one type into an output object of a different type. This type of transformation is often needed when you work on business logic. In this example we're using it to map between User entities and a couple of different model types - CreateRequest and UpdateRequest.

namespace WebApi.Helpers;

using AutoMapper;
using WebApi.Entities;
using WebApi.Models.Users;

public class AutoMapperProfile : Profile
{
    public AutoMapperProfile()
    {
        // CreateRequest -> User
        CreateMap<CreateRequest, User>();

        // UpdateRequest -> User
        CreateMap<UpdateRequest, User>()
            .ForAllMembers(x => x.Condition(
                (src, dest, prop) =>
                {
                    // ignore both null & empty string properties
                    if (prop == null) return false;
                    if (prop.GetType() == typeof(string) && string.IsNullOrEmpty((string)prop)) return false;

                    return true;
                }
            ));
    }
}


Data Context

Path: /Helpers/DataContext.cs

The primary class that is responsible for interacting with data as objects is the DbContext. The recommended way to work with context is to define a class that derives from the DbContext and exposes the DbSet properties that represent collections of the specified entities in the context.

options.UseSqlServer(Configuration.GetConnectionString("ApiDatabase") configures Entity Framework to create and connect to a SQL Server.

UseSnakeCaseNamingConvention(): This will automatically make all your table and column names have snake_case naming.

namespace WebApi.Helpers;

using Microsoft.EntityFrameworkCore;
using WebApi.Entities;

public class DataContext : DbContext
{
    protected readonly IConfiguration Configuration;

    public DataContext(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    protected override void OnConfiguring(DbContextOptionsBuilder options)
    {
        // connect to sql server with connection string from app settings
        options.UseSqlServer(Configuration.GetConnectionString("ApiDatabase")).
        UseSnakeCaseNamingConvention();
    }

    public DbSet<User> Users { get; set; }
}


Global Error Handler Middleware

Path: /Helpers/ErrorHandlerMiddleware.cs

Using the global error handler, we can extract all the exception handling logic into a single centralized place. It's configured as middleware in the Program.cs file.

namespace WebApi.Helpers;

using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Net;
using System.Text.Json;
using System.Threading.Tasks;

public class ErrorHandlerMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger _logger;

    public ErrorHandlerMiddleware(RequestDelegate next, ILogger<ErrorHandlerMiddleware> logger)
    {
        _next = next;
        _logger = logger;
    }

    public async Task Invoke(HttpContext context)
    {
        try
        {
            await _next(context);
        }
        catch (Exception error)
        {
            var response = context.Response;
            response.ContentType = "application/json";

            switch (error)
            {
                case AppException e:
                    // custom application error
                    response.StatusCode = (int)HttpStatusCode.BadRequest;
                    break;
                case KeyNotFoundException e:
                    // not found error
                    response.StatusCode = (int)HttpStatusCode.NotFound;
                    break;
                default:
                    // unhandled error
                    _logger.LogError(error, error.Message);
                    response.StatusCode = (int)HttpStatusCode.InternalServerError;
                    break;
            }

            var result = JsonSerializer.Serialize(new { message = error?.Message });
            await response.WriteAsync(result);
        }
    }
}


Interface User Service

Path: /Services/IUserService.cs

The user service is responsible for all database interaction and core business logic related to user CRUD operations.

namespace WebApi.Services;

using WebApi.Entities;
using WebApi.Models.Users;

public interface IUserService
{
    IEnumerable<User> GetAll();
    User GetById(int id);
    void Create(CreateRequest model);
    void Update(int id, UpdateRequest model);
    void Delete(int id);
}


User Service Implementation

Path: /Services/UserService.cs

The UserService.cs is a concrete user service class that implements the interface. 

using AutoMapper;
using WebApi.Entities;
using WebApi.Helpers;
using WebApi.Models.Users;

namespace WebApi.Services
{
    public class UserService : IUserService
    {
        private DataContext _context;
        private readonly IMapper _mapper;

        public UserService(
            DataContext context,
            IMapper mapper)
        {
            _context = context;
            _mapper = mapper;
        }

        public IEnumerable<User> GetAll()
        {
            return _context.Users;
        }

        public User GetById(int id)
        {
            return getUser(id);
        }

        public void Create(CreateRequest model)
        {
            // validate
            if (_context.Users.Any(x => x.Email == model.Email))
                throw new AppException("User with the email '" + model.Email + "' already exists");

            // map model to new user object
            var user = _mapper.Map<User>(model);

            // save user
            _context.Users.Add(user);
            _context.SaveChanges();
        }

        public void Update(int id, UpdateRequest model)
        {
            var user = getUser(id);

            // validate
            if (model.Email != user.Email && _context.Users.Any(x => x.Email == model.Email))
                throw new AppException("User with the email '" + model.Email + "' already exists");

            // copy model to user and save
            _mapper.Map(model, user);
            _context.Users.Update(user);
            _context.SaveChanges();
        }

        public void Delete(int id)
        {
            var user = getUser(id);
            _context.Users.Remove(user);
            _context.SaveChanges();
        }

        // helper methods

        private User getUser(int id)
        {
            var user = _context.Users.Find(id);
            if (user == null) throw new KeyNotFoundException("User not found");
            return user;
        }
    }
}


CreateRequest.cs

Path: /Models/Users/CreateRequest.cs

namespace WebApi.Models.Users;

using System.ComponentModel.DataAnnotations;
using WebApi.Entities;

public class CreateRequest
{

    [Required]
    public string FirstName { get; set; }

    [Required]
    public string LastName { get; set; }

    [Required]
    [EmailAddress]
    public string Email { get; set; }

}


UpdateRequest.cs

Path: /Models/Users/UpdateRequest.cs

namespace WebApi.Models.Users;

using System.ComponentModel.DataAnnotations;
using WebApi.Entities;

public class UpdateRequest
{
    public string FirstName { get; set; }
    public string LastName { get; set; }

    [EmailAddress]
    public string Email { get; set; }


    private string replaceEmptyWithNull(string value)
    {
        return string.IsNullOrEmpty(value) ? null : value;
    }
}


Users Controller

Path: /Controllers/UsersController.cs

The controller defines and handles all routes / endpoints for the api that relate to users, this includes standard CRUD operations for retrieving, updating, creating and deleting users.

namespace WebApi.Controllers;

using AutoMapper;
using Microsoft.AspNetCore.Mvc;
using WebApi.Models.Users;
using WebApi.Services;

[ApiController]
[Route("api/v1/[controller]")]
public class UsersController : ControllerBase
{
    private IUserService _userService;
    private IMapper _mapper;

    public UsersController(
        IUserService userService,
        IMapper mapper)
    {
        _userService = userService;
        _mapper = mapper;
    }

    [HttpGet]
    public IActionResult GetAll()
    {
        var users = _userService.GetAll();
        return Ok(users);
    }

    [HttpGet("{id}")]
    public IActionResult GetById(int id)
    {
        var user = _userService.GetById(id);
        return Ok(user);
    }

    [HttpPost]
    public IActionResult Create(CreateRequest model)
    {
        _userService.Create(model);
        return Ok(new { message = "User created" });
    }

    [HttpPut("{id}")]
    public IActionResult Update(int id, UpdateRequest model)
    {
        _userService.Update(id, model);
        return Ok(new { message = "User updated" });
    }

    [HttpDelete("{id}")]
    public IActionResult Delete(int id)
    {
        _userService.Delete(id);
        return Ok(new { message = "User deleted" });
    }
}


.NET 6 Program

Path: /Program.cs

Program.cs is the entry point of our app, where we configure the Web host. The program class configures the application infrastructure like Web host, logging, Dependency injection container, IIS integration, etc.

using System.Text.Json.Serialization;
using WebApi.Helpers;
using WebApi.Services;

var builder = WebApplication.CreateBuilder(args);

// add services to DI container
{
    var services = builder.Services;
    var env = builder.Environment;
 
    services.AddDbContext<DataContext>();
    services.AddCors();
    services.AddControllers().AddJsonOptions(x =>
    {
        // serialize enums as strings in api responses (e.g. Role)
        x.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());

        // ignore omitted parameters on models to enable optional params (e.g. User update)
        x.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
    });
    services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());

    // configure DI for application services
    services.AddScoped<IUserService, UserService>();
}

var app = builder.Build();

// configure HTTP request pipeline
{
    // global cors policy
    app.UseCors(x => x
        .AllowAnyOrigin()
        .AllowAnyMethod()
        .AllowAnyHeader());

    // global error handler
    app.UseMiddleware<ErrorHandlerMiddleware>();

    app.MapControllers();
}

app.Run("http://localhost:9080");


.NET App Settings JSON

Path: /appsettings.json

The appsettings.json file is an application configuration file used to store configuration settings such as database connections strings, any application scope global variables, etc.

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "ConnectionStrings": {
    "ApiDatabase": "Data Source=localhost:1433; Initial Catalog=testdb; User Id=knfuser; Password=root;Encrypt=False;"
  }
}


WebApi.csproj

Path: /WebApi.csproj

The csproj (C# project) is an MSBuild based file that contains target framework and NuGet package dependency information for the application.

The EFCore.NamingConventions plugin to automatically set all your table and column names to snake_case instead.

The Microsoft.EntityFrameworkCore.SqlServer database provider allows Entity Framework Core to be used with Microsoft SQL Server (including SQL Azure). The provider is maintained as part of the Entity Framework Core Project.

<Project Sdk="Microsoft.NET.Sdk.Web">
    <PropertyGroup>
        <TargetFramework>net6.0</TargetFramework>
        <ImplicitUsings>enable</ImplicitUsings>
    </PropertyGroup>
    <ItemGroup>
        <PackageReference Include="AutoMapper" Version="11.0.1" />
        <PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="11.0.0" />
        <PackageReference Include="EFCore.NamingConventions" Version="7.0.2" />
        <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="7.0.2" />
    </ItemGroup>
</Project>


Run the application and verify REST APIs

You can start the api by running dotnet run from the command line in the project root folder (where the WebApi.csproj file is located), you should see the message Now listening on: http://localhost:9080.


OR


You can also start the application in debug mode in Visual Studio by opening the project root folder in Visual Studio and pressing F5 or by selecting Debug -> Start Debugging from the top menu, running in debug mode.


Add User:


Update User by ID:


Fetch all Users:


Get User by ID:


Delete User by ID:



PART 2 - UI development using Angular 

Frontend Project Directory


package.json

Path: /package.json

The package.json is a JSON file that is added in the root directory of your project. It contains human-readable metadata about the project as well as functional metadata like the package version number and a list of dependencies required by the application.

{
  "name": "angular-crud-application",
  "version": "0.0.0",
  "scripts": {
    "ng": "ng",
    "start": "ng serve",
    "build": "ng build",
    "watch": "ng build --watch --configuration development",
    "test": "ng test"
  },
  "private": true,
  "dependencies": {
    "@angular/animations": "^15.0.0",
    "@angular/common": "^15.0.0",
    "@angular/compiler": "^15.0.0",
    "@angular/core": "^15.0.0",
    "@angular/forms": "^15.0.0",
    "@angular/platform-browser": "^15.0.0",
    "@angular/platform-browser-dynamic": "^15.0.0",
    "@angular/router": "^15.0.0",
    "bootstrap": "^4.6.2",
    "rxjs": "~7.5.0",
    "tslib": "^2.3.0",
    "zone.js": "~0.12.0"
  },
  "devDependencies": {
    "@angular-devkit/build-angular": "^15.0.3",
    "@angular/cli": "~15.0.3",
    "@angular/compiler-cli": "^15.0.0",
    "@types/jasmine": "~4.3.0",
    "jasmine-core": "~4.5.0",
    "karma": "~6.4.0",
    "karma-chrome-launcher": "~3.1.0",
    "karma-coverage": "~2.2.0",
    "karma-jasmine": "~5.1.0",
    "karma-jasmine-html-reporter": "~2.0.0",
    "typescript": "~4.8.2"
  }
}


The User.ts file(User Model)

Path - src/app/service/user.ts
export class User {
  id!: string;
  firstName!: String;
  lastName!: String;
  email!: String;
}

CRUD Service

Path - src/app/service/crud.service.ts 

The CrudService will be used to get the data from the backend by calling  APIs. Update the crud.service.ts file inside src/app/service directory with the following code to it -
import { Injectable } from '@angular/core';
import { User } from './User';
import { catchError, map } from 'rxjs/operators';
import { Observable, throwError } from 'rxjs';
import {
  HttpClient,
  HttpHeaders,
  HttpErrorResponse,
} from '@angular/common/http';

@Injectable({
  providedIn: 'root',
})
export class CrudService {
 
  REST_API: string = 'http://localhost:9080/api/v1';

  // Http Header
  httpHeaders = new HttpHeaders().set('Content-Type', 'application/json');

  constructor(private httpClient: HttpClient) {}

  // Add
  AddUser(data: User): Observable<any> {
    let API_URL = `${this.REST_API}/users`;
    return this.httpClient
      .post(API_URL, data)
      .pipe(catchError(this.handleError));
  }

  // Get all objects
  GetUsers() {
    return this.httpClient.get(`${this.REST_API}/users`);
  }

  // Get single object
  GetUser(id: any): Observable<any> {
    let API_URL = `${this.REST_API}/users/${id}`;
    return this.httpClient.get(API_URL, { headers: this.httpHeaders }).pipe(
      map((res: any) => {
        return res || {};
      }),
      catchError(this.handleError)
    );
  }

  // Update
  updateUser(id: any, data: any): Observable<any> {
    let API_URL = `${this.REST_API}/users/${id}`;
    return this.httpClient
      .put(API_URL, data, { headers: this.httpHeaders })
      .pipe(catchError(this.handleError));
  }

  // Delete
  deleteUser(id: any): Observable<any> {
    let API_URL = `${this.REST_API}/users/${id}`;
    return this.httpClient
      .delete(API_URL, { headers: this.httpHeaders })
      .pipe(catchError(this.handleError));
  }

  // Error
  handleError(error: HttpErrorResponse) {
    let errorMessage = '';
    if (error.error instanceof ErrorEvent) {
      // Handle client error
      errorMessage = error.error.message;
    } else {
      // Handle server error
      errorMessage = `Error Code: ${error.status}\nMessage: ${error.message}`;
    }
    console.log(errorMessage);
    return throwError(() => {
      errorMessage;
    });
  }
}

User List Component

Path - src/app/user-list/user-list.component.ts

Let's create the UserListComponent component which will be used to display a list of users, create a new user, and delete a user. 
import { Component, OnInit } from '@angular/core';
import { CrudService } from '../../service/crud.service';

@Component({
  selector: 'app-users-list',
  templateUrl: './users-list.component.html',
  styleUrls: ['./users-list.component.scss'],
})
export class UsersListComponent implements OnInit {
  Users: any = [];

  constructor(private crudService: CrudService) {}

  ngOnInit(): void {
    this.crudService.GetUsers().subscribe((res: any) => {
      console.log(res);
      this.Users = res;
    });
  }

  delete(id: any, i: any) {
    console.log(id);
    if (window.confirm('Do you want to go ahead?')) {
      this.crudService.deleteUser(id).subscribe(() => {
        this.Users.splice(i, 1);
      });
    }
  }
}

User List Template

Path - src/app/user-list/user-list.component.html 

Add user-list.component.html file with the following code to it -
<div class="container">
  <div
    class="d-flex justify-content-between flex-wrap
flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
    <h2 class="h2">Users Collection</h2>
  </div>

  <div class="table-responsive">
    <table class="table table-bordered">
      <thead>
        <tr>
          <th scope="col">First Name</th>
          <th scope="col">Last Name</th>
          <th scope="col">Email</th>
          <th class="text-center" scope="col">Action</th>
        </tr>
      </thead>
      <tbody>
        <tr *ngFor="let user of Users; let i = index">
          <td>{{ user.firstName }}</td>
          <td>{{ user.lastName }}</td>
          <td>{{ user.email }}</td>
          <td class="text-center">
            <button
              class="btn btn-sm btn-primary"
              routerLink="/edit-user/{{ user.id }}"
            >
              Edit
            </button>
            <button class="btn btn-sm btn-danger"
(click)="delete(user.id, i)">
              Delete
            </button>
          </td>
        </tr>
      </tbody>
    </table>
  </div>
</div>

Add User Component

Path - src/app/add-user/add-user.component.ts

AddUserComponent is used to create and handle new user form data. Add the following code to it -
import { Component, OnInit, NgZone } from '@angular/core';
import { Router } from '@angular/router';
import { CrudService } from '../../service/crud.service';
import { FormGroup, FormBuilder } from '@angular/forms';

@Component({
  selector: 'app-add-user',
  templateUrl: './add-user.component.html',
  styleUrls: ['./add-user.component.scss'],
})
export class AddUserComponent implements OnInit {
 
  userForm: FormGroup;

  constructor(
    public formBuilder: FormBuilder,
    private router: Router,
    private ngZone: NgZone,
    private crudService: CrudService
  ) {
    this.userForm = this.formBuilder.group({
      firstName: [''],
      lastName: [''],
      email: [''],
    });
  }

  ngOnInit() {}

  onSubmit(): any {
    this.crudService.AddUser(this.userForm.value).subscribe(
      () => {
        console.log('Data added successfully!');
        this.ngZone.run(() => this.router.navigateByUrl('/users-list'));
      },
      (err) => {
        console.log(err);
      }
    );
  }
}

Add User Template

Path - src/app/add-user/add-user.component.html

The add-user.component.html shows the add using HTML form. Add the following code to it - 
<div class="row justify-content-center mt-5">
  <div class="col-md-4">
    <form [formGroup]="userForm" (ngSubmit)="onSubmit()">
      <div class="form-group">
        <label>First Name</label>
        <input
          class="form-control"
          type="text"
          formControlName="firstName"
          required
        />
      </div>

      <div class="form-group">
        <label>Last Name</label>
        <input
          class="form-control"
          type="text"
          formControlName="lastName"
          required
        />
      </div>

      <div class="form-group">
        <label>Email</label>
        <input
          class="form-control"
          type="text"
          formControlName="email"
          required
        />
      </div>
      <br>
      <div class="form-group">
        <button class="btn btn-primary btn-block" type="submit">
          Add User
        </button>
      </div>
    </form>
  </div>
</div>

User Detail Component

Path - src/app/user-detail/user-detail.component.ts 

The UserDetailComponent component is used to display a particular user detail. Add the following code to it -
import { Component, OnInit, NgZone } from '@angular/core';
import { Router, ActivatedRoute } from '@angular/router';
import { CrudService } from '../../service/crud.service';
import { FormGroup, FormBuilder } from '@angular/forms';

@Component({
  selector: 'app-user-detail',
  templateUrl: './user-detail.component.html',
  styleUrls: ['./user-detail.component.scss'],
})
export class UserDetailComponent implements OnInit {
  getId: any;
  updateForm: FormGroup;

  constructor(
    public formBuilder: FormBuilder,
    private router: Router,
    private ngZone: NgZone,
    private activatedRoute: ActivatedRoute,
    private crudService: CrudService
  ) {
    this.getId = this.activatedRoute.snapshot.paramMap.get('id');

    this.crudService.GetUser(this.getId).subscribe((res: { [x: string]: any; }) => {
      this.updateForm.setValue({
        firstName: res['firstName'],
        lastName: res['lastName'],
        email: res['email'],
      });
    });

    this.updateForm = this.formBuilder.group({
      firstName: [''],
      lastName: [''],
      email: [''],
    });
  }

  ngOnInit() {}

  onUpdate(): any {
    this.crudService.updateUser(this.getId, this.updateForm.value).subscribe(
      () => {
        console.log('Data updated successfully!');
        this.ngZone.run(() => this.router.navigateByUrl('/users-list'));
      },
      (err) => {
        console.log(err);
      }
    );
  }
}

User Details  Template

Path - src/app/user-detail/user-detail.component.html 

The user-detail.component.html displays a particular user detail. Add the following code to it -
<div class="row justify-content-center mt-5">
  <div class="col-md-4">
    <form [formGroup]="updateForm" (ngSubmit)="onUpdate()">
      <div class="form-group">
        <label>First Name</label>
        <input
          class="form-control"
          type="text"
          formControlName="firstName"
          required
        />
      </div>

      <div class="form-group">
        <label>Last Name</label>
        <input
          class="form-control"
          type="text"
          formControlName="lastName"
          required
        />
      </div>

      <div class="form-group">
        <label>Email</label>
        <input
          class="form-control"
          type="text"
          formControlName="email"
          required
        />
      </div>
      <br>
      <div class="form-group">
        <button class="btn btn-primary btn-block" type="submit">Update</button>
      </div>
    </form>
  </div>
</div>

App Routing Module

Path: /src/app/app.routing.module.ts 

Routing for the Angular app is configured as an array of Routes, each component is mapped to a path so the Angular Router knows which component to display based on the URL in the browser address bar.
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

import { UsersListComponent } from './components/users-list/users-list.component';
import { AddUserComponent } from './components/add-user/add-user.component';
import { UserDetailComponent } from './components/user-detail/user-detail.component';

const routes: Routes = [
  { path: '', pathMatch: 'full', redirectTo: 'add-user' },
  { path: 'users-list', component: UsersListComponent },
  { path: 'add-user', component: AddUserComponent },
  { path: 'edit-user/:id', component: UserDetailComponent },
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule],
})
export class AppRoutingModule {}

App Module

Path: /src/app/app.module.ts 

Defines the root module, named AppModule, that tells Angular how to assemble the application. Initially declares only the AppComponent.
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';

import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';

import { AppRoutingModule } from './app-routing.module';
import { AddUserComponent } from './components/add-user/add-user.component';
import { UserDetailComponent } from './components/user-detail/user-detail.component';
import { UsersListComponent } from './components/users-list/users-list.component';

@NgModule({
  declarations: [
    AppComponent,
    AddUserComponent,
    UserDetailComponent,
    UsersListComponent,
  ],
  imports: [
    BrowserModule,
    HttpClientModule,
    FormsModule,
    ReactiveFormsModule,
    AppRoutingModule,
  ],
  providers: [],
  bootstrap: [AppComponent],
})
export class AppModule {}

App Component

Path: /src/app/app.component.ts 

The app component is the root component of the application, it defines the root tag of the app as with the selector property of the @Component decorator.
import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent {
  title = '.Net + SQL Server + Angular 15 CRUD Application';
}

App Component Template

Path: /src/app/app.component.html 

Defines the HTML template associated with the root
AppComponent.
<nav class="navbar navbar-expand-sm bg-success navbar-dark">
  <!-- Links -->
  <ul class="navbar-nav">
    <li class="nav-item">
      <a class="nav-link" routerLinkActive="active" routerLink="/users-list"
      >Show Users</a>
    </li>
    <li class="nav-item">
      <a class="nav-link" routerLinkActive="active" routerLink="/add-user"
      >Add Users</a
    >
    </li>
  </ul>
</nav>
<div class="container">
  <br>
  <h2 style="text-align: center;">{{title}}</h2>
  <hr>  
  <router-outlet></router-outlet>
</div>

<router-outlet></router-outlet>

index.html

Path: /src/index.html 

The main index.html file is the initial page loaded by the browser that kicks everything off.
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>CrudApp</title>
  <base href="/">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
  <app-root></app-root>
</body>
</html>

Main/Bootstrap File

Path: /src/main.ts 

The main file is the entry point used by angular to launch and bootstrap the application.
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

import { AppModule } from './app/app.module';
import { environment } from './environments/environment';

if (environment.production) {
  enableProdMode();
}

platformBrowserDynamic().bootstrapModule(AppModule)
  .catch((err: any) => console.error(err));

Local Setup and Run the application

Download or clone the source code from GitHub to the local machine - Click here!



Backend


You can start the api by running dotnet run from the command line in the project root folder (where the WebApi.csproj file is located), you should see the message Now listening on: http://localhost:9080.


OR


You can also start the application in debug mode in Visual Studio by opening the project root folder in Visual Studio and pressing F5 or by selecting Debug -> Start Debugging from the top menu, running in debug mode.



Frontend


npm install


ng serve

From the browser call the endpoint http://localhost:4200

Download Source Code

Popular posts from this blog

Learn Java 8 streams with an example - print odd/even numbers from Array and List

Java Stream API - How to convert List of objects to another List of objects using Java streams?

Registration and Login with Spring Boot + Spring Security + Thymeleaf

Java, Spring Boot Mini Project - Library Management System - Download

ReactJS, Spring Boot JWT Authentication Example

Spring Boot + Mockito simple application with 100% code coverage

Top 5 Java ORM tools - 2024

Java - Blowfish Encryption and decryption Example

Spring boot video streaming example-HTML5

Google Cloud Storage + Spring Boot - File Upload, Download, and Delete