Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

ASP.NET Core 2.0 & Angular 4: Build from Scratch a Web Application for Vehicles Management

0.00/5 (No votes)
15 Dec 2017 2  
ASP.NET Core 2.0 & Angular 4: through this tutorial, you will learn how to build from scratch a web application for vehicles management

Introduction

The main objective of this article is to discover more features about Angular4 and .NET CORE Framework such as:

  • Create a RestFul Server using WebApi v2
  • Using Entity framework for .NET Core code first approach
  • AutoMapper
  • Using .NET Core Dependency Injection
  • Using Swagger API within .NET Core project
  • Create Single Page Application using Angular
  • Create components, services, and classes in Angular
  • Angular Routing system
  • Angular Form Validation
  • Communicate between components using Subject pattern
  • Working with Angular-datatables

Background

To better understand this article, it is recommended to have good knowledge about:

  • Programming in C#
  • SQL language
  • Entity Framework
  • Visual Studio code

Using the Code

I) Server Side

a) Prerequisites

Before starting the implementation of WebApi service project, below you will find all useful links:

  • Visual Studio Code (link)
  • DotNetCore 2.0.0 (link)
  • NET Core Command Line Tools (link)
  • AutoMapper (link)
  • Swagger Api (link)

b) Setting Up the Project

Using Visual Studio code integrated terminal, we run the following command lines to create a new DotNet Core MVC project with individual authentification system inside demobackend folder.

mkdir demobackend
dotnet new mvc -au Individual -f netcoreapp2.0

Here, it's the project structure:

To use only WebApi with identity system, you should introduce some changes into project:

Replace the startup program:

public void ConfigureServices(IServiceCollection services)
       {
           services.AddDbContext<ApplicationDbContext>(options =>
           options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")))
           services.AddIdentity<applicationuser, identityrole="">()
               .AddEntityFrameworkStores<applicationdbcontext>()
               .AddDefaultTokenProviders();
           // Add application services.
           services.AddMvc();
       }
       // This method gets called by the runtime.
       // Use this method to configure the HTTP request pipeline.
       public void Configure(IApplicationBuilder app, IHostingEnvironment env)
       {
           if (env.IsDevelopment())
           {
               app.UseDeveloperExceptionPage();
           }
            // Enable middleware to serve generated Swagger as a JSON endpoint.
            app.UseMvc();
       }

Remove all controller classes present into Controller folder except AccountController class.

Replace AccountController by the following code to have only useful actions like login, logout and signup that return only http status code (200, 400).

namespace demobackend.Controllers
{
    [Authorize]
    [Route("[controller]/[action]")]
    public class AccountController : Controller
    {
        private readonly UserManager<applicationuser> _userManager;
        private readonly SignInManager<applicationuser> _signInManager;
        private readonly ILogger _logger;

        public AccountController(
            UserManager<applicationuser> userManager,
            SignInManager<applicationuser> signInManager,
            ILogger<accountcontroller> logger)
        {
            _userManager = userManager;
            _signInManager = signInManager;
            _logger = logger;
        }

        [TempData]
        public string ErrorMessage { get; set; }     
        [HttpPost]
        [AllowAnonymous]
        public async Task<iactionresult> Login([FromBody]LoginViewModel model)
        {
             if (ModelState.IsValid)
            {
                // This doesn't count login failures towards account lockout
                // To enable password failures to trigger account lockout, set lockoutOnFailure: true
                var result = await _signInManager.PasswordSignInAsync
                    (model.Email, model.Password, model.RememberMe, lockoutOnFailure: false);
                if (result.Succeeded)
                {
                    var msg = "User logged in.";
                    return Ok(msg);
                }
            }
            // If we got this far, something failed, redisplay form
            return BadRequest("Fail to login with this account");
        }
        [HttpPost]
        [AllowAnonymous]
        public async Task<iactionresult> Register([FromBody] RegisterViewModel model)
        {
            var msg = "Bad Request";
            if (ModelState.IsValid)
            {
                var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
                var result = await _userManager.CreateAsync(user, model.Password);
                if (result.Succeeded)
                {
                    _logger.LogInformation("User created a new account with password."); 

                    var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
                     //await _signInManager.SignInAsync(user, isPersistent: false);
                    _logger.LogInformation("User created a new account with password.");
                    msg = "User created a new account with password.";
                    return Ok(msg);
                }
            }
            // If we got this far, something failed, redisplay form
            return BadRequest(msg);
       }

       [HttpGet]
       [AllowAnonymous]
        public async Task<iactionresult> Logout()
        {
            await _signInManager.SignOutAsync();
          //  await HttpContext.SignOutAsync("MyCookieAuthenticationScheme");
            _logger.LogInformation("User logged out.");
            var msg = "User logged out.";
            return Ok(msg);
        }
    }

c) Setting Up the Database

First, you should create an empty database named demoangulardatabase using SSMS.

Next, you should modify default connection string inside appsettings.json:

"ConnectionStrings": {

"DefaultConnection": "Server=(LocalDb)\\MSSQLLocalDB;Database=demoangulardatabase;
                        Trusted_Connection=True;MultipleActiveResultSets=true"
},

For this example, you need only Car entity, to do that, you need to create Car class into Data folder:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace demobackend.Data
{
    public class Car
    {        
        [Key]
        public int Id {get; set;}
        [StringLength(50),Required]
        public string Name {get; set;}
        [StringLength(50),Required]
        public string Mark {get; set;}
        [StringLength(50),Required]
        public string Model {get; set;}
        [Required]
        public DateTime Registered { get; set; }
    }
}

To update database schema, we will use Entity Framework Core migrations tools:

  1. Add a new migration script:
    dotnet ef migrations add initialMigration -c ApplicationDbContext -v
  2. Update database schema:
    dotnet ef database update -c ApplicationDbContext -v

Finally, when you refresh your database server, you will get this result:

d) Using AutoMapper

The purpose is to create mapping from ViewModel objects to Entities objects and vice versa:

First, create CarViewModel class into Model folder:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace demobackend.Models
{
    public class CarViewModel
    {
        public int Id {get; set;}
        [StringLength(50),Required]
        public string Name {get; set;}
        [StringLength(50),Required]
        public string Mark {get; set;}
        [StringLength(50),Required]
        public string Model {get; set;}
        [Required]
        public DateTime Registered { get; set; }
    }
}

Second, configure mapping via AutoMapper Profile Configuration file: create this file inside AutoMapperProfile folder:

using demobackend.Data;
using demobackend.Models;
using System.Collections.Generic;
using AutoMapper;
using System;

namespace demobackend.AutoMapperProfile
{
    public class AutoMapperProfileConfiguration : Profile
    {
        public AutoMapperProfileConfiguration()
        : this("MyProfile")
        {
        }
        protected AutoMapperProfileConfiguration(string profileName)
        : base(profileName)
        {
          
            CreateMap<Car, CarViewModel>();
            CreateMap<CarViewModel, Car>();
        }
    }
}

Finally, in startup.cs, add the following lines in ConfigureServices method to create and initiate IMapper service that will be injected into controllers:

var config = new AutoMapper.MapperConfiguration(cfg =>
            {
                cfg.AddProfile(new AutoMapperProfileConfiguration());
            });

var mapper = config.CreateMapper();
services.AddSingleton(mapper);

Creating Car Management API using ASP.NET Core Web API 2.0

Create a new controller named ManageCarController, that will contain endpoints for each CRUD method:

  • Get(): Returns HttpResponseMessage that contains all available cars
  • Get(int id): Returns HttpResponseMessage that contains a specific car object identified by id parameter.
  • Post([FromBody] CarViewModel _car): It will create a new car and will return an Http ok status code (http 200) if operation is checked, else it will return (http 400)
  • Put(int id, [FromBody] CarViewModel value): It will modify a specific car (identified by id parameter). It will return Http not found code if car doesn't exist, http 200 status code if update operation is valid, else Http bad request.
  • Delete(int id): It will delete a specific car (identified by id parameter) and will return an Http ok status code (http 200) if operation is valid.
using System;
using System.Collections.Generic;
using System.Linq;
using AutoMapper;
using demobackend.Data;
using demobackend.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Http;

namespace demobackend.Controllers
{
        [Authorize]
        [Route("api/[controller]")]
        public class ManageCarController : Controller
        {
            private IMapper _mapper;
            private ApplicationDbContext dbContext;
            public ManageCarController(IMapper mapper, ApplicationDbContext context)
            {
                this._mapper = mapper;
                this.dbContext = context;
            }
            // GET api/values
            [HttpGet]
            public IEnumerable<carviewmodel> Get()
            {
                IEnumerable<carviewmodel> list = 
                    this._mapper.Map<ienumerable<carviewmodel>>(this.dbContext.cars.AsEnumerable());
                return list;
            }
            // GET api/values/5
            [HttpGet("{id}")]
            public  IActionResult Get(int id)
            {
                var _car = this._mapper.Map<carviewmodel>(this.dbContext.cars.Find(id));
                return Ok(_car);
            }

            // POST api/values
            [HttpPost]
            public IActionResult Post([FromBody] CarViewModel _car)
            {
               if (ModelState.IsValid)
                {
                    _car.Registered = DateTime.Now;
                    var newcar = this._mapper.Map<car>(_car);
                    this.dbContext.cars.Add(newcar);
                    this.dbContext.SaveChanges();
                    return Ok();
                }else{
                    return BadRequest();
                }
            }

            // PUT api/values/5
            [HttpPut("{id}")]
            public IActionResult Put(int id, [FromBody] CarViewModel value)
            {   
                if (ModelState.IsValid)
                {
                    var existingCar = this.dbContext.cars.Find(id);
                    if(existingCar == null){
                          return NotFound();
                     }else{ 
                        existingCar.Name = value.Name;
                        existingCar.Mark = value.Mark;
                        existingCar.Model = value.Model;
                        this.dbContext.cars.Update(existingCar);
                        this.dbContext.SaveChanges();
                        return Ok();
                    }
                }else{
                    return BadRequest();
                }
            }

            // DELETE api/values/5
            [HttpDelete("{id}")]
            public IActionResult Delete(int id)
            {
                this.dbContext.cars.Remove(this.dbContext.cars.Find(id));
                this.dbContext.SaveChanges();
                return Ok();
            }
    }
}

e) Using Swagger

To test each action in our ManageCar WebApi, we will use swagger API:

First, we should install it:

dotnet add package Swashbuckle.AspNetCore --version 1.0.0

Next, in startup.cs:

  • Add the following lines inside ConfigureServices section:
    services.AddSwaggerGen(c =>
    {
    c.SwaggerDoc("v1", new Info { Title = "My API", Version = "v1" });
    });
  • Add the following lines inside Configure section:
    app.UseSwagger();
    // Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.), specifying the Swagger JSON endpoint.
    app.UseSwaggerUI(c =>
    {
    c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
    })

f) Running API

To run project, execute the following command-line:

dotnet run 

Open browser and write the following URL:

http://localhost:5000/swagger/

Finally, you wil get Swagger UI interface to test your API:

Here are some examples for how to use Account & ManageCar services:

Test Subscription Service

Result:

Test Authentification Service

Result:

Test the Creation of a New Car

Result:

Test the Get of All Available Cars

II ) Client Side

a) Prerequisites

Before starting the implementation of Client side project, below you will find all useful links:

  • Install Visual Studio Code (link)
  • Install nodejs + npm (link)
  • Set up the Development Environment (link)
  • Install Angular-Datatables (link)
  • Install Angular2-busy (link)
  • Angular form validation (link)
  • Subject pattern (link)

b) Setting Up the Project

To create a new Angular project, execute the following command-lines inside your workspace folder:

First, install Angular CLI tools:

npm install -g @angular/cli

Second, generate a new Angular project using a template:

ng new demo 

Finally, run the application:

cd demo 
ng serve --open

c) Implement Services

Before starting implementation of services, we should declare model class first:

  • ICar: Used to deserialize json data into a ICar object.
    export interface ICar {
    id: number,
    name: string,
    mark: string,
    model: string,
    registered : Date
    }
  • Message: Used to hold information about thrown notifications:
    • Type: It’s an Alert Type, It Can Take as value: ‘Error’ or ‘Success
    • Text: It’s an Alert Description
    export class Message {
     constructor(public type : string, public text: string) {
     }
    }
  • User: Used to deserialize json data into a user object.
    export class User {
    email : string = "";
    password : string = "";
    rememberMe : boolean = false;
    }

LoginService

Contains methods that manage session with server:

  • loginSubject: It implements Subject pattern. Used to track session state, when user creates new session, he will receive a notification (loginSubject.next(1)) to show email address on top of page, else when he logs out, the email will disappear.
  • login(currentUser : User): Sends a POST http request to Account/Login service to try a new connection with server. As parameter, it will pass a User object.
  • logout(): Sends POST http request to Account/Logout service trying to end the current session.
import { Injectable } from '@angular/core';
import { Http, Headers, RequestOptions, Response, RequestMethod } from '@angular/http';
import {User} from '../models/user';
import  'rxjs/add/operator/toPromise';
import { Subject } from 'rxjs';

@Injectable()
export class LoginService {
    public loginSubject = new Subject<any>();
    _baseUrl : string = "http://localhost:5000/Account";
     
    options = new RequestOptions({
           withCredentials : true
    }); 
    constructor(private http: Http) { }
   
    public login(currentUser : User) {     
      
        let _currentUser = JSON.stringify(currentUser);
        return this.http.post(this._baseUrl + '/Login', currentUser, this.options)
         .toPromise()
         .catch(this.handleError);

    }   
    public logout(){ 
        return this.http.get( this._baseUrl + '/Logout', this.options)
        .toPromise()
        .catch(this.handleError);
    }
    private handleError(error: any): Promise<any> {
        return Promise.reject(error.message || error);
      }    
}   

CarService

Contains methods that manage cars data into database.

  • getCars(): Sends a Get http request to /api/ManageCar service to get all available cars
  • getCar(id : number): Sends a Get http request to /api/ManageCar service to get a specific car (identified by id parameter)
  • addNewCar(_car : ICar): Sends a POST http request to /api/ManageCar service with _car object as parameter to create a new row into CAR table.
  • updateCar(_car : ICar): Sends a PUT http request to /api/ManageCar service with _car object as parameter to update existing car (identified by _car.id).
  • deleteCar(id : number): Sends a Delete http request to /api/ManageCar service to remove a specific car (identified by an id).
    import { Injectable } from '@angular/core';
    import { Observable, Subject } from 'rxjs/Rx';
    import { Http, Headers, RequestOptions, Response, RequestMethod } from '@angular/http';
    import { ICar } from './../models/ICar';
    
    @Injectable()
    export class CarService {
        carsList : ICar[];
        _baseUrl : string = "http://localhost:5000/api/";
        _getCarsUrl : string = "ManageCar"; 
        options = new RequestOptions({
            withCredentials : true
        }); 
        constructor(private http: Http) { 
     
        }
        public getCars() {
            return this.http.get(this._baseUrl + this._getCarsUrl, this.options)
            .toPromise();
        }
        public getCar(id : number) {
            return this.http.get(this._baseUrl + this._getCarsUrl + "/"+ id, this.options)
            .toPromise();
        }
        public addNewCar(_car : ICar){
           return this.http.post(this._baseUrl + this._getCarsUrl, _car, this.options)
           .toPromise();
         }
        public updateCar(_car : ICar){
            return this.http.put(this._baseUrl + this._getCarsUrl + "/"+  
                                 _car.id, _car, this.options)
            .toPromise();
        }
        public deleteCar(id : number){
             return this.http.delete(this._baseUrl + this._getCarsUrl + "/"+ id, this.options)
            .toPromise();    
        }
    }

CanActivateService

The purpose is to secure some route access by checking if the user is authorized or not to read more about this service via this link.

It implements canActivate method from CanActivate interface.

import { Injectable } from '@angular/core';
import { Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } 
from '@angular/router';

@Injectable()
export class CanActivateService implements CanActivate {
constructor(private router: Router) { }
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
if (localStorage.getItem('loggedUser')) {
return true;
}else{
// not logged in so redirect to login page with the return url
this.router.navigate(['/login'], { queryParams: { returnUrl: state.url }});
return false;
}
}
}

NotifService

It implements notification system, it will use to display success and error messages inside page throw alert dialog.

This service implements Subject pattern to capture observer’s notifications for showing or hiding messages on page.

import { Message } from './../models/Message';
import { Injectable } from '@angular/core';
import { Subject, Observable} from 'rxjs/Rx';
import { Router, NavigationStart, Event} from '@angular/router';

@Injectable()
export class NotifService {
subject = new Subject<any>();
constructor(private router: Router) {
router.events.subscribe( event =>
{
if(event instanceof NavigationStart) {
this.subject.next();
}
});
}
success(message: string) {
this.subject.next(new Message('alert-success', message));
}
error(message: string) {
this.subject.next(new Message('alert-danger', message));
}
getMessage(): Observable<any> {
return this.subject.asObservable();
}
}

d) Implement Components

Login Component
LoginComponent.ts
  • login(): It will call login method from LoginService by passing username and password through model object.

In case of success operation, it will create a new session with Server and navigate to list page using router service. Else, it will show error message by calling error method from notifService.

import { Headers } from '@angular/http';
import { Component, NgModule, OnInit } from '@angular/core';
import { FormBuilder, Validators } from '@angular/forms';
import { Router, ActivatedRoute } from '@angular/router';
import { User } from '../../models/user';
import { LoginService } from './../../services/login-service.service';
import { NotifService } from './../../services/notif-service.service';

@Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit {
EMAIL_REGEXP = "^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9]([a-z0-9-]*[a-z0-9])?
                   (\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$";
model : User;
loading = false;
message = "";
busy: Promise<any>;
constructor(
private router: Router,
private notifService : NotifService,
private loginService : LoginService
) {
this.model = new User();
}

ngOnInit() {
localStorage.removeItem('loggedUser');
this.loading = false;
this.loginService.logout().then(resp => {
this.loginService.loginSubject.next(1);
});
}

ngDestroy()
{
}
login() {
//clean notifications message on page
this.notifService.subject.next();
this.loading = true;
this.busy = this.loginService.login(this.model).then(resp => {
this.loading = false;
localStorage.setItem('loggedUser', this.model.email);
this.loginService.loginSubject.next(1);
this.router.navigate(["/list"]);
}).catch(exp => {
this.notifService.error(exp._body);
this.loading = false;
}) ;
}
}
Login.component.html

This page offers a login interface for user to enter his credentials (email and password) to open a new session with server and have access to application.

<form [ngBusy]="busy" class="form-horizontal" (ngSubmit)="f.form.valid && login()" 

        #f="ngForm" novalidate>
<div class="row">
<div class="col-md-3"></div>
<div class="col-md-6">
<h4>Please Login</h4>
<hr>
</div>
</div>
<div class="row">
<div class="col-md-3"></div>
<div class="col-md-6">
<div class="form-group">
<label class="sr-only" for="email">E-Mail Address</label>
<div class="input-group mb-2 mr-sm-2 mb-sm-0">
<div class="input-group-addon" style="width: 2.6rem">
<i class="fa fa-at"></i></div>
<input type="email" class="form-control" name="email"

[(ngModel)]="model.email" #username="ngModel" [pattern]="EMAIL_REGEXP" required />
</div>
</div>
</div>
<div class="col-md-3">
<div class="form-control-feedback" *ngIf="f.submitted && !username.valid">
<span class="text-danger align-middle">
<i class="fa fa-close"></i> Username is required
</span>
</div>
</div>
</div>
<div class="row">
<div class="col-md-3"></div>
<div class="col-md-6">
<div class="form-group">
<label class="sr-only" for="password">Password</label>
<div class="input-group mb-2 mr-sm-2 mb-sm-0">
<div class="input-group-addon" style="width: 2.6rem"><i class="fa fa-key"></i></div>
<input type="password" class="form-control" name="password"

[(ngModel)]="model.password" #password="ngModel" required />
</div>
</div>
</div>
<div class="col-md-3">
<div class="form-control-feedback" *ngIf="f.submitted && !password.valid">
<span class="text-danger align-middle">
<i class="fa fa-close"></i> Password is required
</span>
</div>
</div>
</div>
<div class="row">
<div class="col-md-3"></div>
<div class="col-md-6" style="padding-top: .35rem">
<div class="form-check mb-2 mr-sm-2 mb-sm-0">
<label class="form-check-label">
<input [(ngModel)]="model.rememberMe" class="form-check-input" name="rememberMe"

type="checkbox" >
<span style="padding-bottom: .15rem">Remember me</span>
</label>
</div>
</div>
</div>
<div class="row" style="padding-top: 1rem">
<div class="col-md-3"></div>
<div class="col-md-6">
<button [disabled]="loading" class="btn btn-primary"><i class="fa fa-sign-in"></i> Login</button>
</div>
</div>
</form>
List Component
List.component.ts
  • init(): Initializes datatable object by configuration paginType and pageLength properties. Then, it will load available cars by calling getCars method from CarService. If exception raised, an error message will appear at the top of the page by calling error method from NofifService.
  • searchCar (): It filters displayed cars by id. It is called when user chooses car name from dropdown menu.
  • deleteCar(id : number): It deletes a specific item (identified by id argument) from database and removes it from view. This method will call deleteCar action from CarService. If exception raised, an error message will appear at the top of the page by calling error method from NofifService.
import { NotifService } from './../../services/notif-service.service';
import { Component, NgModule , OnInit } from '@angular/core';
import { CarService } from '../../services/car-service.service';
import { Subscription } from 'rxjs/Subscription';
import { ICar } from './../../models/ICar';
import { Subject } from 'rxjs/Rx';
import { RouterLink, Event } from '@angular/router';

@Component({
selector: 'app-list',
templateUrl: './list.component.html',
styleUrls: ['./list.component.css']
})
export class ListComponent implements OnInit {
listCars : any = [];
filtredCars : any = [];
carName : string = "";
selectedItem : number;
dtTrigger = new Subject();
dtOptions: DataTables.Settings = {};

constructor(private _carService : CarService, private notifService : NotifService ) {
this.init();
}

private init()
{
this.dtOptions = {
pagingType: 'full_numbers',
pageLength: 10
};

this.selectedItem = -1;
this._carService.getCars() .then( response => {
this.listCars = response.json() as ICar[];
this.filtredCars = this.listCars.slice(0);
// Calling the DT trigger to manually render the table
this.dtTrigger.next();
}).catch((resp) => {
console.log(resp);
this.notifService.error("Server Exception was raised");
});
}
public searchCar ()
{
if(this.selectedItem == -1)
{
this.filtredCars = this.listCars.slice(0);
}else
{
this.filtredCars = this.listCars.filter(
car => car.id == this.selectedItem );
}
}

public deleteCar(id : number)
{
this._carService.deleteCar(id).then( response => {
this.filtredCars = this.filtredCars.filter(
(item : ICar) => {
return (item.id != id)
})
this.notifService.success("Delete was well done");
// this.dtTrigger.next();
}).catch((resp) => {
this.notifService.error("Server Exception was raised");
});
}
ngOnInit()
{
}
}
List.component.html

This page displays all available cars with details, and lets user to filter by car id using dropdown filter or using native search box of datatable component.

Through datatable, you can remove, update a specific car, or add new one.

To update or to create a car, you will navigate to a dedicated page.

<div class="row">
<div class="col-lg-8">
<p><b>Filter by car id :</b>
<select [(ngModel)]="selectedItem" (ngModelChange)="searchCar()" >
<option [value]="-1" selected>choose option</option>
<option *ngFor="let item of listCars" [value]="item.id" >{{item.name}}</option>
</select>
</p>
</div>
<div class="col-lg-4">
<a class="btn btn-primary btn-xs pull-right " routerLink="/newcar" 

preserveQueryParams preserveFragment><b>+</b> Add new car </a>
</div>
</div>
<br>
<div class="row">
<div class="col-lg-12">
<table datatable [dtOptions]="dtOptions" [dtTrigger]="dtTrigger" class="row-border hover">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Mark</th>
<th>Model</th>
<th>Registred Date</th>
<th></th>
</tr>
</thead>
<tbody>
<tr *ngFor="let car of filtredCars">
<td>{{car.id}} </td>
<td>{{car.name}}</td>
<td>{{car.mark}}</td>
<td>{{car.model}}</td>
<td>{{car.registered | date }}</td>
<td>
<button class="btn btn-link" routerLink="/updatecar/{{car.id}}" 

preserveQueryParams preserveFragment>
<i class="fa fa-pencil" aria-hidden="true"></i> Update
</button>
<button class="btn btn-link" (click)="deleteCar(car.id)">
<i class="fa fa-trash" aria-hidden="true"></i> Delete
</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
Notificationcar Component
Notification.component.ts

It uses notifService to subscribe on Subject Obsevable, it will listen to all notifications send from other components (observers) and show the appropriate message.

import { Component, OnInit } from '@angular/core';
import { NotifService } from './../../services/notif-service.service';
import { Message } from './../../models/Message';

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

export class NotificationComponent implements OnInit {

message : Message;
constructor(public notifService : NotifService ){}
ngOnInit() {
this.notifService.getMessage().subscribe(p =>
{
this.message = p;
});
}
}
Add New Car Component
Newcar.component.ts
  • complexForm: used for Form validation, to validate the following inputs before submitting : name, mark and model.
  • newCar(model: ICar): will create a new car entry into database by calling addNewCar action of CarService.

If exception raised, an error message will appear at the top of the page by calling error method of NofifService instance.

import { Component,NgModule, OnInit } from '@angular/core';
import { FormGroup, FormControl, FormBuilder, Validators } from '@angular/forms';
import { ICar} from "../../models/ICar"
import { CarService } from './../../services/car-service.service';
import { NotifService } from './../../services/notif-service.service';
@Component({
selector: 'app-newcar',
templateUrl: './newcar.component.html',
styleUrls: ['./newcar.component.css']
})
export class Newcar implements OnInit {
complexForm : FormGroup;
constructor(fb: FormBuilder, private carService : CarService
,private notifService : NotifService
){
// Here we are using the FormBuilder to build out our form.
this.complexForm = fb.group({
// We can set default values by passing in the corresponding value or leave blank 
// if we wish to not set the value. For our example, we’ll default the gender to female.
'name' : [null, Validators.required],
'mark': [null, Validators.required],
'model' : [null, Validators.required],
});
}
ngOnInit() {
}
public newCar(model: ICar){
this.carService.addNewCar(model).then(resp => {
this.notifService.success("Insertion operation was well done");
}).catch(exp => {
this.notifService.error("Server Exception was raised");
}) ;
}
}
Newcar.component.html

Via this page, user can enter information (name, mark, model) about new car and submit it to server.

All inputs are required to achieve this action, else he will get validation error messages.

<h3> New Car</h3>
<form [formGroup]="complexForm" (ngSubmit)="newCar(complexForm.value)">
<div class="form-group">
<label for=""><b>Name</b></label>
<input type="text" class="form-control" [formControl]="complexForm.controls['name']" />
<div *ngIf="complexForm.controls['name'].hasError('required')" class="has-error">
field required</div>
</div>
<div class="form-group">
<label for=""><b>Mark</b></label>
<input type="text" class="form-control" [formControl]="complexForm.controls['mark']"/>
<div *ngIf="complexForm.controls['mark'].hasError('required')" class="has-error">
field required</div>
</div>
<div class="form-group">
<label for=""><b>Model</b></label>
<input type="text" class="form-control" [formControl]="complexForm.controls['model']"/>
<div *ngIf="complexForm.controls['model'].hasError('required')" class="has-error">
field required</div>
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary" [disabled]="!complexForm.valid">Submit</button>
</div>
</form>
Update Car Component
Update.component.ts
  • constructor(…): Gets details of specific car (using id parameters given from route url arguments) and initialize FormGroup object (complexForm) to fill and validate form fields.
  • updateCar(model: ICar): will update specific car by calling updateCar action of CarService.

If exception raised, an error message will appear on the top of the page by calling error method of NofifService instance.

import { Component,NgModule, OnInit } from '@angular/core';
import { FormGroup, FormControl, FormBuilder, Validators } from '@angular/forms';
import { ICar } from "../../models/ICar"
import { CarService } from './../../services/car-service.service';
import { NotifService } from './../../services/notif-service.service';
import { ActivatedRoute } from '@angular/router';

@Component({

selector: 'app-updatecar',
templateUrl: './update.component.html',
styleUrls: ['./update.component.css']
})
export class UpdateCarComponent implements OnInit {
complexForm : FormGroup ;

constructor(private fb: FormBuilder
, private carService : CarService
, private notifService : NotifService
, private route: ActivatedRoute ){
// Here we are using the FormBuilder to build out our form.
this.route.params.subscribe(params => {

let id = +params['id']; // (+) converts string 'id' to a number
this.complexForm = fb.group({
// We can set default values by passing in the corresponding value or leave blank 
// if we wish to not set the value. For our example, we’ll default the gender to female.
'id' : [""],
'name' : ["", Validators.required],
'mark': ["", Validators.required],
'model' : ["", Validators.required],
});
this.carService.getCar(id).then(resp => {
let car = resp.json() as ICar;
this.complexForm = fb.group({
// We can set default values by passing in the corresponding value or leave blank 
// if we wish to not set the value. For our example, we’ll default the gender to female.
'id' : [car.id],
'name' : [car.name, Validators.required],
'mark': [car.mark, Validators.required],
'model' : [car.model, Validators.required],
});
}).catch(exp => {
this.notifService.error("Server Exception was raised");
}) ;
});
}
ngOnInit() {
}

public updateCar(model: ICar){
console.log(model);
this.carService.updateCar(model).then(resp => {
this.notifService.success("Update operation was well done");
}).catch(exp => {
this.notifService.error("Server Exception was raised");
}) ;
}
}
Update.component.html

This page will show specific car details inside an HTML form that allows user to modify some information like (name, mark, model).

App Component
App.component.ts

Used as code behind for the app.html template, in which you will have implementation of:

  • constructor: used to initialize our master page (app.html), and to declare router listener that controls user navigation depending on current session value (session is managed by cookies)
  • logOut: This event is used to free current session by clearing cookies, and redirect user to Login page.
import { Component } from '@angular/core';
import { User } from '../../models/user';
import { LoginService } from '../../services/login-service.service';

@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'Angular 4 Demo';
userEmail : string = "";

constructor(private loginService : LoginService) {
loginService.loginSubject.asObservable().subscribe(p =>
{
this.userEmail = localStorage.getItem('loggedUser') || "";
});
}
ngOnInit() {
//Called after the constructor, initializing input properties, and the first call to ngOnChanges.
//Add 'implements OnInit' to the class.
this.userEmail = localStorage.getItem('loggedUser') || "";
}
}
App.component.html
<div class="container">
<div style="text-align:center">
<h1>
{{title}}
</h1>
</div>
<div *ngIf="userEmail">
<p><b>Welcome</b> {{userEmail}} 
(<a [routerLink]="['/login']">Logout</a>) </p>
</div>
<notification></notification>
<router-outlet></router-outlet>
</div>
App Routing Component
app.routing.component.ts

Used to configure routing system. Only list, update and create path are not allowed to anonymous users, that’s why we add canActivate attribute inside path declaration.

import { Routes, RouterModule } from '@angular/router';
import { LoginComponent } from '../login/login.component';
import { Newcar } from '../newcar/newcar.component';
import { AppComponent } from '../shared/app.component';
import { ListComponent } from '../list/list.component';
import { CanActivateService } from '../../services/canActivate.service';
import { PageNotFoundComponent } from './../pageNotFound/PageNotFound.component';
import { UpdateCarComponent } from './../updatecar/update.component';

const appRoutes: Routes = [
{ path: 'login', component: LoginComponent },
{ path: 'list', component: ListComponent, canActivate: [CanActivateService] },
{ path: 'newcar', component: Newcar , canActivate: [CanActivateService]},
{ path: 'updatecar/:id', component: UpdateCarComponent },
// otherwise redirect to home
{ path: '**', component: PageNotFoundComponent }
];

export const routing = RouterModule.forRoot(appRoutes);
App.module.ts

This file is used to:

  1. define routes using Router Module: "RouterModule"
  2. import needed Angular modules via the word key: "imports"
  3. declare components via the word key: "declarations"
  4. declare services via the word key: "providers"
  5. specify the root components to include into index.html file via the word key: "bootstrap"
import { CanActivate } from '@angular/router';
import { BrowserModule } from '@angular/platform-browser';
import { NgModule, Component } from '@angular/core';

import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';
import { routing } from './components/app.routing/app.routing.component';
import { CanActivateService } from './services/canActivate.service';
import { NotificationComponent } from './components/notification/notification.component';
import { NotifService } from './services/notif-service.service';
import { LoginService } from './services/login-service.service';
import { CarService } from './services/car-service.service';

import { DataTablesModule } from 'angular-datatables';
import { BusyModule, BusyConfig} from 'angular2-busy';

import { LoginComponent } from './components/login/login.component';
import { ListComponent } from './components/list/list.component';
import { Newcar } from './components/newcar/newcar.component';
import { AppComponent } from './components/shared/app.component';
import { UpdateCarComponent } from './components/updatecar/update.component';

import { PageNotFoundComponent } from './components/pageNotFound/PageNotFound.component';

export function getBusyConfig() {
return new BusyConfig({
message: 'Please wait ...',
backdrop: false,
delay: 300,
minDuration: 800,
wrapperClass: 'ng-busy'
});
}
@NgModule({
declarations: [
AppComponent,
ListComponent,
Newcar,
LoginComponent,
UpdateCarComponent,
NotificationComponent,
PageNotFoundComponent
],
imports: [
BrowserModule,
BusyModule.forRoot(getBusyConfig()),
FormsModule,
ReactiveFormsModule,
HttpModule,
routing,
DataTablesModule
],
providers: [CarService, CanActivateService, NotifService, LoginService ],
bootstrap: [AppComponent, NotificationComponent]
})
export class AppModule { }

III) Running Project

To run the front-end project, you should write the following command-line using CMD, but first be sure that you are in the root directory of your application:

  • npm start: To transpile TS files to JavaScript files and start application

When you try to open application via the given URL (http://localhost:4200), you will get the below result:

Note: The above steps are used when you start a project from scratch, if you want to use attached projects, you should first install required packages, to do that, you need to execute the following commands-lines inside each project root folder:

  • dotnet restore for dotnet project
  • npm install for angular project

Before running dotnetcore project: you need to create a database using SSMS and run available migration scripts by executing the following command-line: "dotnet ef database update -c ApplicationDbContext -v"

References

Points of Interest

I hope that you appreciated this post. Try to download the source code and don't hesitate to leave your questions and comments.

History

  • v1 05/11/2017: Initial version

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here