Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / HTML

Angular 2 & .NET Core Developing Web App from Scratch Part 2: Implementing Front-end Part

5.00/5 (11 votes)
4 Feb 2017CPOL6 min read 24.1K   326  
Great article to learn how you can create web application from scratch using Angular2 & .NET CORE WEB API
This is the second part of an article that shows how to implement the front end part of developing a web app from scratch using Angular 2 and .NET Core.

Contents

Introduction

After preparing the server part, we will start to build the client part (front-end part) using Angular 2 framework.

Our application is a single page application composed by four modules:

  • home: is the main page, used to expose available products to clients.
  • Login: is the authentication page, that allows users to log into the application and have access to the back-office side.
  • Subscription: is the subscription page, which provides a form to create a new user's account.
  • Product Management: This module provides a form to insert new products into the database.

In this article, I relied on the following links:

Background

To better understand this demo, it is preferable that you have a good knowledge about:

  • Programming in C#, JavaScript and HTML
  • Angular 2
  • MVC architecture
  • Data Binding
  • Entity Framework
  • Visual Studio Code (optional)

Prerequisites

Using the Code

A) Create and Configure Angular 2 Project

First, use CMD to create a new folder (new project) named 'angular2fromscratch' in which you download the Angular 2 project from the offical Angular 2 documentation.

Later, you should create the same configuration files (based on the official Angular 2 documentation):

  • package.json
  • tsconfig.json
  • typings.json

Next, you should install TypeScript, typings, webpack, cookies service and angular2-material using npm:

  • Npm install –g typescript
  • Npm install -g typings
  • Npm install angular2-cookie
  • npm install @angular2-material/sidenav
  • npm install @angular2-material/input
  • npm install @angular2-material/button
  • npm install @angular2-material/core
  • npm install @angular2-material/card
  • npm install @angular2-material/icon
  • npm install @angular2-material/toolbar
  • npm install @angular2-material/progress-circle
  • npm install @angular2-material/sidenav

The final treeview should look like this:

Image 1

Before you start your implementation, you should make some changes on the systemjs.config.js file, to define the alias for the Angular 2 packages:

JavaScript
(function (global) {
  System.config({
    paths: {
      // paths serve as alias
      'npm:': 'node_modules/'
    },
    // map tells the System loader where to look for things
    map: {
      // our app is within the app folder
      app: 'app',
      // angular bundles
      '@angular/core': 'npm:@angular/core/bundles/core.umd.js',
      '@angular/common': 'npm:@angular/common/bundles/common.umd.js',
      '@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js',
      '@angular/platform-browser': 
      'npm:@angular/platform-browser/bundles/platform-browser.umd.js',
      '@angular/platform-browser-dynamic': 
      'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js',
      '@angular/http': 'npm:@angular/http/bundles/http.umd.js',
      '@angular/router': 'npm:@angular/router/bundles/router.umd.js',
      '@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js',
      '@angular2-material/input': 'npm:@angular2-material/input/input.umd.js',
      '@angular2-material/core': 'npm:@angular2-material/core/core.umd.js',
      '@angular2-material/card': 'npm:@angular2-material/card/card.umd.js',
      '@angular2-material/button': 'npm:@angular2-material/button/button.umd.js',
      '@angular2-material/icon': 'npm:@angular2-material/icon/icon.umd.js',
      '@angular2-material/toolbar': 'npm:@angular2-material/toolbar/toolbar.umd.js',
      '@angular2-material/progress-circle': 
      'npm:@angular2-material/progress-circle/progress-circle.umd.js',
      '@angular2-material/sidenav': 'npm:@angular2-material/sidenav/sidenav.umd.js',
       'angular2-cookie': 'npm:angular2-cookie',
      // other libraries
      'rxjs':                      'npm:rxjs',
      'angular-in-memory-web-api': 'npm:angular-in-memory-web-api',
    },
    // packages tells the System loader how to load when no filename and/or no extension
    packages: {
    
      app: {
        main: './main.js',
        defaultExtension: 'js'
      },
      rxjs: {
        defaultExtension: 'js'
      },
      'angular-in-memory-web-api': {
        main: './index.js',
        defaultExtension: 'js'
      },
       'angular2-cookie': {
        main: './core.js',
        defaultExtension: 'js'
      },
    }
  });
})(this);

B) Setup Angular 2 Project

In the app folder, create the following files:

  • app.module.ts

    This file is used to:

    • Define routes using Router Module: "RouterModule"
    • Import needed Angular 2 modules via the word key: "imports"
    • Declare components via the word key: "declarations"
    • Declare services via the word key: "providers",
    • Specify the root components to include into index.html file via the word key: "bootstrap"
    JavaScript
    import { NgModule }             from '@angular/core';
    import { BrowserModule }        from '@angular/platform-browser';
    import { FormsModule }          from '@angular/forms';
    import { RouterModule, Routes } from '@angular/router';
    import { MdInputModule } from '@angular2-material/input';
    import { MdCardModule } from '@angular2-material/card';
    import { MdButtonModule } from '@angular2-material/button';
    import { MdIconModule } from '@angular2-material/icon';
    import { MdToolbarModule } from '@angular2-material/toolbar';
    import { MdIconRegistry } from '@angular2-material/icon';
    import { CookieService } from 'angular2-cookie/services/cookies.service';
    import { AppComponent }   from './app.component';
    import { loginComponenent }   from './app.loginComponenent';
    import { homeComponenent }   from './app.homeComponenent';
    import { subscriptionComponenent }   from './app.subscriptionComponenent';
    import { productManageComponent }   from './app.productManageComponent';
    import { PageNotFoundComponent }   from './app.pageNotFoundComponent';
    import { MdProgressCircleModule } from '@angular2-material/progress-circle';
    import { MdSidenavModule } from '@angular2-material/sidenav';
    
    import { AuthentificationService } from './services/authentificationService';
    import { ManageProductService } from './services/manageProductService'
    
    const appRoutes: Routes = [
      { path: 'home', component: homeComponenent },
      { path: 'login', component: loginComponenent },
      { path: 'subscription', component: subscriptionComponenent },
      { path: 'productManagement', component: productManageComponent },
      { path: '', redirectTo: '/home',  pathMatch: 'full'},
      { path: '**', component: PageNotFoundComponent }
    ];
    
    @NgModule({
    
       imports:  [ FormsModule , BrowserModule,MdSidenavModule, 
          MdProgressCircleModule, MdInputModule, MdToolbarModule, 
          MdCardModule, MdButtonModule, MdIconModule,  RouterModule.forRoot(appRoutes)],
        declarations: [ AppComponent, loginComponenent, homeComponenent, 
        subscriptionComponenent, productManageComponent, PageNotFoundComponent],
        providers: [ CookieService, MdIconRegistry, 
                     AuthentificationService, ManageProductService ],
        bootstrap: [ AppComponent ]
    })
    export class AppModule { }
  • app.component.ts

    This file is 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 the router listener that controls user navigation depending on current session value (session is managed by cookies).
    • logOut event: this event is used to free the current session by clearing cookies, and redirect user to home page.
    JavaScript
    import { Component } from '@angular/core';
    import {MdToolbar} from '@angular2-material/toolbar';
    import {CookieService} from 'angular2-cookie/core';
    import {Router, NavigationStart, NavigationEnd} from '@angular/router'
    @Component({
      selector: 'my-app',
      templateUrl: 'app/templates/app.html',
         
    })
    export class AppComponent {
      title = 'Angular 2 Application';
      showMenu : boolean = false;
      login : string = "";
      constructor(private _cookieService: CookieService, private router : Router){
          this.showMenu = false;
          router.events.subscribe((val) => {
            // see also 
            if(val instanceof NavigationStart) {
                this.login = this._cookieService.get("login");
                if(this.login != null && this.login != ""){
                    this.showMenu = true;
                    if(val.url.endsWith("login") || val.url.endsWith("subscription")){
                        router.navigateByUrl("/home");
                    }
                } 
                else{
                    this.showMenu = false;
                    if(val.url.endsWith("productManagement")){
                          router.navigateByUrl("/home");
                      }
                }
            }
         });
      }
      public logOut(right){
        this.showMenu = false;
        right.close();
        this._cookieService.remove("login");
        location.reload();
        //this.router.navigateByUrl("/home");
      } 
    }
  • app.homeComponent.ts

    Constitute the code behind for our home template, in which you will have implementation of:

    • ngOnInit event: refresh the product list on each page initialization
    • Remove event: deletes an existing product (identified by its "unique key") by calling "Delete method" of "_service variable"
    • loadProducts event: load all available products form remote server via the call of loadProducts method of _service variable
    JavaScript
    import { Component , OnInit} from '@angular/core';
    import { Product } from './model/product'
    import { ManageProductService } from './services/manageProductService'
    import {CookieService} from 'angular2-cookie/core';
    
    @Component({
      templateUrl: 'app/templates/home.html'
    })
    export class homeComponenent extends OnInit {
      title = 'Products';
      showProgress : boolean = false; 
      showError : boolean = false;
      showEmpty : boolean = false;
      isLogged  : boolean = false;
      listProduct : Product[];
    
      constructor(private _service : ManageProductService, 
                  private _cookieService: CookieService){
             super();
             console.log("constructor");
      }
    
      ngOnInit() {
         this.loadProducts();
    
         this.isLogged = (this._cookieService.get("login")!= null)?true: false;
      }
    
      public Remove(id: number) {
        var result = confirm("Do you want to continue the suppression process ?");
        let _self = this;
        _self.showError = false;
        _self.showEmpty = false;
        if (result == true) {
           
               this._service.deleteProduct(id).then(function(){
                    _self.listProduct = _self.listProduct.filter(value=> {
                        return (value.id != id);
                    });
                    if(_self.listProduct.length==0){
                        _self.showEmpty = true;
                    }
               }).catch(function(){
                   _self.showError = true;
               });
        } else {
            
        }
      }
      public loadProducts(){
          
          let _self = this;
          _self.showError = false;
          _self.showEmpty = false;
          _self.showProgress = true;
          _self._service.loadProducts().then(function(response){
            
             _self.showProgress = false;
             if(response.length >0){
             
               _self.listProduct = response;
          
             }else{
                _self.showEmpty = true;
             }
             
          }).catch(function(error: any){
             _self.showProgress = false;
              _self.showError = true;
          });
      }
    }
  • app.loginComponent.ts

    Constitute the code behind for the login template, in which you will have the implementation of:

    • tryToConnect event: open a new session with server using user credential (email, password)
    JavaScript
    import { Component } from '@angular/core';
    import {Router} from '@angular/router'
    import { User } from './model/user';
    import { AuthentificationService } from './services/authentificationService';
    import {CookieService} from 'angular2-cookie/core';
    
    @Component({
      templateUrl: 'app/templates/login.html'
    })
    
    export class loginComponenent {
      public title = 'Login';
      public model : User = new User();
      constructor(private _service : AuthentificationService, 
                  private _cookieService:CookieService, private router: Router){
    
      }
    
      tryToConnect(){
         let _self = this;
          _self._service.Login(_self.model).then(function (response){
              
              _self._cookieService.put("login", _self.model.login);
              _self.router.navigateByUrl('/home');
           })
           .catch(function(error : any){
              alert("Fail to login");
           });
      }
    }
  • app.productManageComponent.ts

    Constitute the code behind for our productManagement template, in which you will have the implementation of:

    • addNewProduct event: called when the user clicks on the submit button for submitting the form data to register a new product. It will call the addProduct method of the _service variable.
    JavaScript
     import { Component } from '@angular/core';
    
    import { Product } from './model/Product';
    import { ManageProductService } from './services/manageProductService';
    @Component({
      templateUrl: 'app/templates/productManagement.html'
    })
    export class productManageComponent {
      title = 'Manage Product';
      model : Product;
      constructor(private _service : ManageProductService){
          this.model = new Product();
      }
      onChange(event) {
            var file = event.srcElement.files[0];
            this.model.pictureFile = file;
        
        }
      public addNewProduct(){
    
        let _self = this;
        let formData = new FormData();
        formData.append("title",_self.model.title);
        formData.append("fullDescription",_self.model.fullDescription);
        formData.append("price",_self.model.price);
        formData.append("file",_self.model.pictureFile, _self.model.pictureFile.name);
        _self._service.addProduct(formData).then(function(response){
          alert("new product was successfully created");
        }).catch(function(error: any){
          alert("Server error");
        });
      }
    }
  • app.subscriptionComponent.ts

    Constitute the code behind for our singup template, in which you will have the implementation of:

    • Subscription event: raised when user try to submit new user details. It will call the addProduct method of the _service variable.
    JavaScript
    import { Component } from '@angular/core';
    import { User } from './model/user';
    import { AuthentificationService } from './services/authentificationService';
    import {  NgModule }  from '@angular/core';
    @Component({
      templateUrl: 'app/templates/singup.html'
    })
    export class subscriptionComponenent {
        title = 'Subscription';
      public model : User;
    
      constructor(private _service : AuthentificationService){
        this.model = new User();
      }
         
      public Subscription(){
        let _self = this;
        this._service.Signup(this.model).then(function(response){
     
           alert("Congratulation, the user with login : 
                 "+ _self.model.login  + " has been created");
        })
         .catch(function(error : any){
              alert("Fail to create new user");
          });
      }
    }

Next, you should create the different folders and files:

1) model folder:

  • product.ts

    Used to deserialize JSON data into a product object.

    JavaScript
    export class Product{
         id : number;
         title : string = '';
         fullDescription : string = '';
         price : number = 0;
         picture : string;
         pictureFile : any;
    }
  • user.ts

    Used to deserialize JSON data into a user object.

    JavaScript
    export class User{
         login : string = '';
         pwd : string = '';
         pwdConfirmation : string = '';
    }

2) services folder:

  • authentificationService.ts

    Implements needed methods to ensure user authentication and user registration:

    • Login: invokes an external web service "api/Services/Login" to create session with server.
    • Signup: calls a remote web service "api/Services/Subscription" to register the user.
    JavaScript
    import { Injectable } from '@angular/core';
    import { Http, Response, RequestOptions, Headers } from '@angular/http';
    import 'rxjs/add/operator/toPromise';
    import {User} from '../model/user';
    
    @Injectable()
    export class AuthentificationService {
    
        constructor( private _http : Http){
        }
    
         Login(model : User): Promise<any> {
                
                let _url = "http://localhost:5000/api/Services/Login";
    
                let bodyString = JSON.stringify(model);
                let headers    = new Headers
                ({ 'Content-Type': 'application/json' }); // ... Set content type 
                                                          // to JSON
                let options    = new RequestOptions({ headers: headers });
        
                return this._http.post(_url, bodyString, options)
                .toPromise(); 
        }
        
        Signup(model : any): Promise<any> {
                
                let _url = "http://localhost:5000/api/Services/Subscription";
    
                let bodyString = JSON.stringify(model);
                let headers    = new Headers
                ({ 'Content-Type': 'application/json' }); // ... Set content 
                                                          // type to JSON
                let options    = new RequestOptions({ headers: headers });
        
                return this._http.post(_url, bodyString, options)
                .toPromise();
        }
     
        protected handleErrorPromise(error: any): Promise<void> {
    
            try {
                error = JSON.parse(error._body);
            } catch (e) {
            }
            
            return Promise.reject(error);
        } 
    }
  • manageProductService.ts

    This class offers services to manage product such as:

    • loadProducts: loads the available products list by calling an external web service via "api/Services/GetProducts" address
    • addProduct: it calls an external web service "api/Services/AddProduct" to create a new product
    • deleteProduct: deletes a specific product from database by calling an existing web service "api/Services/DeleteProduct/"
    JavaScript
    import { Injectable } from '@angular/core';
    import { Http, Response, RequestOptions, Headers } from '@angular/http';
    import { Product} from '../model/Product';
    import 'rxjs/add/operator/toPromise';
    
    @Injectable()
    export class ManageProductService {
        constructor(private _http: Http) { }
    
       loadProducts(): Promise<Product[]> {
                    
           let _url = "http://localhost:5000/api/Services/GetProducts";
            return this._http.get(_url)
                .toPromise()
                .then(response => this.extractArray(response));
        }      
    
        addProduct(formData: any): Promise<any>{
              let _url = "http://localhost:5000/api/Services/AddProduct";
            return new Promise(function (resolve, reject) {
    
                let xhr = new XMLHttpRequest();
                xhr.open('POST', _url, true);
    
                xhr.onload = function (e) {
                    resolve(JSON.parse(xhr.response));
                };
                xhr.onerror = function (e) {
                    reject(e);
                }
    
                xhr.send(formData);
            });
        }
      
        deleteProduct(id: number) {
            let _url = "http://localhost:5000/api/Services/DeleteProduct/";
            return this._http.delete(_url+'?id=' + id).toPromise();
        }    
    
        protected extractArray(res: Response, showprogress: boolean = true) {
            let data = res.json();
            return data || [];
        }
    }

3) templates folder:

  • app.html

    This is an HTML template and is used as a master page. It contains:

    • md-toolbar: it considered as a naviation menu, from which you can navigate to home, login and subscription pages.
    • md-sidenav: is the right-side navigation menu, which displays information about:
      • email of current user
      • navigation button: used to redirect to the productManagement page
      • logout button: to delete current session and redirect to the home page
    • router-outlet: is placeholder used to display specific view based on router state (read more about router-outlet).
HTML
<md-toolbar >
  <button md-icon-button  class="md-raised md-primary" routerLink="/home">
      <md-icon >home</md-icon>
  </button> 

  <span>{{title}}</span>
     <span flex></span>
    
        <a md-button routerLink="/login"   *ngIf="showMenu == false"  
                     routerLinkActive="active">
            <i md-icon></i>Login
        </a>
        <a md-button routerLink="/subscription"  *ngIf="showMenu == false"  
           routerLinkActive="active">
                <i md-icon></i>Sign up
        </a>
 
      <button md-icon-button *ngIf="showMenu == true"  
              class="md-raised md-primary" (click)="right.toggle()">
          <md-icon  class="md-24">list</md-icon>  
    </button>
</md-toolbar>
 <md-sidenav-layout>
 
    <md-sidenav  #right align="end" layout-padding>
      <h5 style="text-align:center"><md-icon>account_circle</md-icon></h5>
      <h6 style="text-align:center"> {{login}}</h6>
      <hr>
          <div style="text-align:center">
            <button md-button  routerLink="/productManagement"  
             class="md-icon-button" style="width: 100%" (click)="right.close()" 
             routerLinkActive="active">
                      <h5> Manage</h5>
              </button>
        </div>
       <div style="text-align:center">
            <button  md-button    class="md-icon-button"  
             style="width: 100%" 
             (click)="logOut(right)" routerLinkActive="active">
                   <h5>Logout</h5>
            </button>
         </div>
    </md-sidenav>
    <div  layout-padding>
             <router-outlet></router-outlet>    
    </div>
  </md-sidenav-layout>
  • home.html

    Through this page, the user can see a list of available products with details. If the user is connected, it will have a possibility to remove some products from the current list.

    HTML
    <div >
        <h1>{{title}}</h1>
        <div class="row">
        
            <md-progress-circle color="primary" mode="indeterminate" 
             *ngIf="showProgress" [style.width]="'40px'"  
             [style.margin]="'0 auto'" ></md-progress-circle>
            <div *ngIf="showEmpty" class="alert alert-info">
                <strong>Info :</strong> Empty Result
            </div>
    
            <div *ngIf="showError" class="alert alert-danger">
                <strong>Error :</strong> Problem happened in server side.
            </div>
        <div>
     
        <div class="row"  >
            <div  *ngFor="let obj of listProduct" 
                  class="col col-lg-4" style="margin-top:1px;  " > 
                 <md-card >
                    <md-card-title-group>
                        <img md-card-md-image  [src]="obj.picture">
                        <md-card-title>{{obj.title}}</md-card-title>
                        <md-card-subtitle>Price : {{obj.price}} $</md-card-subtitle>
                      
                    </md-card-title-group>
                    
                    <md-card-actions *ngIf="isLogged == true" 
                     style="text-align: center">
                          <button md-icon-button  
                           class="md-primary" (click)="Remove(obj.id)" >
                                <md-icon    class="md-24" >delete</md-icon>
                          </button>
                    </md-card-actions>
                </md-card> 
                <div class="clearfix" ></div>
            </div>
        </div>
    </div>
  • login.html

    This page offers a connection form, that allows the user to enter his credential (email and password) and to open new session.

    HTML
    <div>
        <h1>{{title}}</h1>
        <div >
            <form>
                <div> 
                    <label class="vSpace">  Login :</label>  
                    <md-input type="email" name="login" 
                    [(ngModel)]="model.login" placeholder="Email" >  </md-input>
                </div>
                <div> 
                     <label class="vSpace"> Password : </label>  
                     <md-input type="password" name="pwd" 
                     [(ngModel)]="model.pwd" placeholder="Password" >  </md-input>
                </div>
                <div> 
                    <button type="button" md-raised-button   
                    [disabled]="(model.login == '' || model.pwd =='')"  
                    (click)="tryToConnect()"  class="md-primary" value="submit"  > 
                            submit
                    </button>
                </div>
            </form>
        </div>
    </div>
  • productManagement.html

    This page offers user possibility to add a new product through using a form. He must provide information about: product Name (title), description, price, thumbnail (Picture).

    HTML
    <div>
        <h1>{{title}}</h1>
        <div >
            <form>
                <div> 
                    <label class="vSpace">  Title :</label>  
                    <md-input type="text" name="title" 
                    [(ngModel)]="model.title" placeholder="title">  </md-input>
                </div>
                 <div> 
                    <label class="vSpace">  Full description :</label>  
                    <md-input type="text"name="fullDescription" 
                    [(ngModel)]="model.fullDescription" 
                    placeholder="Description">  </md-input>
                </div>
                <div> 
                     <label class="vSpace"> Price ($) : </label>  
                     <md-input type="number" name="price" 
                     [(ngModel)]="model.price"  placeholder="Price" ></md-input>
                </div>
                <div> 
                    <label class="vSpace"> Piture : </label>
                    <label class="btn btn-default btn-file">
                        Load File <input type="file" id="file" 
                        name="pictureFile" (change)="onChange($event)" 
                        class="md-primary" class="hidden">
                    </label>
                </div>
                <div> 
       
                    <button type="button" md-raised-button  
                    [disabled]="(model.title == '' || model.fullDescription =='')" 
                    (click)="addNewProduct()"   class="md-primary" value="submit"  > 
                            Add
                    </button>
                </div>
            </form>
        </div>
    </div>
  • singup.html

    Through this page, any anonymous user can create their own account, to have the privileges to access the back-office of application.

    To create a new account, the user should complete the subscription form by filling following information: login, password.

    HTML
    <div>
        <h1>{{title}}</h1>
        <div>
              <form id="newProductForm" >
                <div> 
                <label class="vSpace">Login :</label> 
                     <md-input type="email"  name="login" 
                     [(ngModel)]="model.login" placeholder="Email" >  </md-input>
                </div>
                <div> 
                <label class="vSpace">Password :</label>
                     <md-input type="password" name="pwd"  
                     [(ngModel)]="model.pwd" placeholder="Password" >  </md-input>
                </div>
                <div> 
                <label class="vSpace">Confirm password :</label> 
                   <md-input type="password"  name="pwdConfirmation"  
                   [(ngModel)]="model.pwdConfirmation" 
                   placeholder="Confirm password" >  </md-input>
                </div>
                <div> 
     
                <button md-raised-button type="button"  
                [disabled]="(model.login == '' || model.pwd =='' || 
                model.pwdConfirmation != model.pwd)" (click)="Subscription()"  
                class="md-primary" value="submit"  > 
                                submit
                </button>
                </div>
            </form>
        </div>
    </div>

To run the demo, 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.

Image 2

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

Image 3

To get this result, you should start your server application (see Part 1 of this article: Server side implementation). Make sure your web service is accessible from this address: http://localhost:5000.

References

Points of Interest

I hope that you appreciated this series of articles. Try to download the source code, and I'm waiting for your questions and comments.

History

  • v1 29th January, 2017: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)