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

Learn Angular Tutorial - Part 3

5.00/5 (5 votes)
21 Sep 2017CPOL16 min read 36.4K   577  
Implementing SPA using Angular routes and Angular validations.
This is Part 3 of Learn Angular Tutorial. In this article, we will see how to implement SPA using Angular routes. Also we will be looking into implementing Angular validations.

Contents

Links to Rest of the Articles

  • In the first part, we look into Node, TypeScript, Module loaders /Bundlers and VS Code
  • In the second article, we create a simple basic Angular application with a screen and we run through important concepts like components and modules
  • In the third article, we look into implementing SPA and validations
  • In the fourth article, we understand how to make HTTP calls and how to create custom Angular components using Input and Outputs
  • In the fifth part, we cover two labs - one is how to use Jquery with Angular and Lazy routing
  • In the sixth part, we again cover two labs - Pipes and DI using providers

In this article, we will see how to create SPA using Angular routing mechanism.

Lab 7: Implementing SPA using Angular Routing

Fundamentals of Single Page Application

Nowadays, Single Page Application (SPA) has become the style of creating websites. In SPA, we load only things which we need and nothing more than that.

At the right-hand side is a simple website where we have Logo and header at the top, Left menu links and footer at the bottom.

So the first time when user comes to the site, all the sections of the master page will be loaded. But when user clicks on Supplier link, only Supplier page will load and not logo, header and footer again. When user clicks on Customer link, only Customer page will be loaded and not all other sections.

Angular routing helps us to achieve the same.

Image 1

Step 1: Creating the Master Page

As everything revolves around the Master Page, so the first logical step would be is to create the “MasterPage”.

Image 2

In this master page, we will create placeholders for logo, header, menu, footer, copyright and so on. These sections will be loaded only once when the user browses the website for the first time. And in the later times, only pages which are needed will be loaded on demand.

Below is the sample HTML code which has all the placeholder sections. You can also see in this code we have kept a “DIV” tag section in which we would be loading the pages on-demand.

Image 3

Below is the overall main sections of “MasterPage”. Please note the “DIV” section with name “dynamicscreen” where we intend to load screens dynamically. We will fill these sections later.

HTML
<table border="1" width="448">
<tr>
<td>Logo</td>
<td width="257">Header</td>
</tr>
<tr>
<td>Left Menu</td>
<td width="257">
<div id="dynamicscreen">
  Dynamic screen will be loaded here
</div>
</td>
</tr>
<tr>
<td>Footer</td>
<td width="257">Copyright</td>
</tr>
</table>

Step 2: Creating the Supplier Page and Welcome Page

Let’s create two more HTML UI, one “Supplier” page and “Welcome” page. In both these HTML pages, we are not doing much, we have just greeting messages.

Below is the supplier pages text.

This is the Suppliers page

Below is welcome pages text.

Welcome to the website

Step 3: Renaming Placeholder in Index.html

As explained in Part 1, “Index.html” is the startup page and it bootstraps all other pages using systemjs. In the previous lesson inside “Index.html”, “Customer.html” page was loading. But now that we have master page so inside index page “MasterPage.html” will load.

So to make it more meaningful, let's rename “customer-ui” tag to “main-ui”. In this “main-ui” section, we will load the master page and when end user clicks on the master page left menu links, supplier, customer and welcome pages will load.

So if look at the flow, first index.html will get loaded and then inside the “main-ui”, “masterpage.html” gets loaded.

Image 4

Step 4: Removing Selector from CustomerComponent

Now the first page to load in the index.html will be Masterpage and not Customer page. So we need to remove the selector from “CustomerComponent.ts”. This selector will be moved to masterpage component in the later sections.

Image 5

The final code of “CustomerComponent.ts” would look something as shown below:

JavaScript
import {Component} from "@angular/core"
//importing the customer model
import {Customer} from '../Model/Customer'
@Component({
    templateUrl: "../UI/Customer.html"
})
export class CustomerComponent {
    //this is binding
    CurrentCustomer:Customer = new Customer();
}

Step 5: Creating Components for Master, Supplier and Welcome Page

Every UI which is Angular enabled should have component code file. We have created three user interfaces so we need three component code files for the same.

In the component folder, we will create three component TS files “MasterPageComponent.ts”, “SupplierComponent.ts” and “WelcomeComponent.ts”.

You can visualize component code files as code behind for Angular UI.

Image 6

So first, let’s start with “MasterPage.html” component which we have named as “MasterPageComponent.ts”. This master page will get loaded in “Index.html” in the initial bootstrapping process. You can see in this component we have put the selector and this will be the only component which will have the selector.

JavaScript
import {Component} from "@angular/core"

@Component({
    selector: "main-ui",
    templateUrl: "../UI/MasterPage.html"
})
export class MasterPageComponent {
}

Below is the component code for “Supplier.html”.

JavaScript
import {Component} from "@angular/core"

@Component({
  
    templateUrl: "../UI/Supplier.html"
})
export class SupplierComponent {
}

Below is the component code for “Welcome.html”. Both Supplier and Welcome component do not have the selector, only the master page component has it as it will be the startup UI which will get loaded in index page.

JavaScript
import {Component} from "@angular/core"

@Component({
    templateUrl: "../UI/Welcome.html"
})
export class WelcomeComponent {
}

Step 6: Creating the Routing Constant Collection

Once the master page is loaded in the index page, the end user will click on the master page links to browse to supplier page, customer page and so on. Now in order that the user can browse properly, we need to define the navigation paths. These paths will be specified in the “href” tags in the later steps.

When these paths will be browsed, it will invoke the components and components will load the UI. Below is a simple table with three columns. The first column specifies the path pattern, second which component will invoke when these paths are browsed and the final column specifies the UI which will be loaded.

Path/URL Component UI which will be loaded
/ WelcomeComponent.ts Welcome.html
/Customer CustomerComponent.ts Customer.html
/Supplier SupplierComponent.ts Supplier.html

The paths and component entries need to be defined in a simple literal collection as shown in the below code. You can see the “ApplicationRoutes” is a simple collection where we have defined path and the component which will be invoked. These entries are made as per the table specified at the top.

JavaScript
import {Component} from '@angular/core';
import {CustomerComponent} from '../Component/CustomerComponent';
import {SupplierComponent} from "../Component/SupplierComponent";
import {WelcomeComponent} from "../Component/WelcomeComponent";

export const ApplicationRoutes = [
    { path: 'Customer', component: CustomerComponent },
    { path: 'Supplier', component: SupplierComponent },
     { path: '', component:WelcomeComponent  }
];

As a good practice, we have defined all the above code in a separate folder “routing” and in a separate file “routing.ts”.

Image 7

Step 7: Defining routerLink and router-outlet

The navigation (routes) defined in “Step 6” in the collection needs to referred when we try to navigate inside the website. For example, in the master page, we have defined the left menu hyperlinks.

So rather than using the “href” tag of HTML, we need to use “[routerLink]”.

HTML
<a href="Supplier.html">Supplier</a>

We need to use “[routerLink]” and the value of “[routerLink]” will be the path specified in the routes collection define the previous step. For example, in the “ApplicationRoutes” collection, we have made one entry for Supplier path, we need to specify the path in the anchor tag as shown in the below code:

HTML
<a [routerLink]="['Supplier']">Supplier</a>

When the end user clicks on the left master page links the pages (supplier page, customer page and welcome page) will get loaded inside the “div” tag. For that, we need to define “router-outlet” placeholder. Inside this placeholder, pages will load and unload dynamically.

HTML
<div id="dynamicscreen">
<router-outlet></router-outlet>
</div>

So if we update the master page defined in “Step 1” with “router-link” and “router-outlet”, we would end up with code something as shown below:

HTML
<table border="1">
<tr>
<td><img src="http://www.questpond.com/img/logo.jpg" alt="Alternate Text" />
</td>
<td>Header</td></tr><tr>
<td>Left Menu<br /><br /><br />
<a [routerLink]="['Supplier']">Supplier</a> <br /><br />
<a [routerLink]="['Customer']">Customer</a></td><td>
<div id="dynamicscreen">
<router-outlet></router-outlet>
</div>
</td>
</tr>
<tr>
<td>Footer</td><td></td>
</tr>
</table>

Step 8: Loading the Routing in Main Modules

In order to enable routing collection paths defined in “ApplicationRoutes”, we need to load that in the “MainModuleLibrary” as shown in the below code. “RouterModule.forRoot” helps load the application routes at the module level.

Once loaded at the module level, it will be available to all the components for navigation purposes which is loaded inside this module.

JavaScript
@NgModule({
    imports: [RouterModule.forRoot(ApplicationRoutes),
             BrowserModule,
             FormsModule],
    declarations: [CustomerComponent,MasterPageComponent,SupplierComponent],
    bootstrap: [MasterPageComponent]
})
export class MainModuleLibrary { }

The complete code with routes would look something as shown below:

JavaScript
import { NgModule }      from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import {FormsModule} from "@angular/forms"
import { CustomerComponent }   from '../Component/CustomerComponent';
import { SupplierComponent }   from '../Component/SupplierComponent';
import { MasterPageComponent }   from '../Component/MasterPageComponent';
import { RouterModule }   from '@angular/router';
import { ApplicationRoutes }   from '../Routing/Routing';  

@NgModule({
    imports: [RouterModule.forRoot(ApplicationRoutes),
             BrowserModule,
             FormsModule],
    declarations: [CustomerComponent,MasterPageComponent,SupplierComponent],
    bootstrap: [MasterPageComponent]
})
export class MainModuleLibrary { }

Image 8

Step 9: Define APP BASE HREF

Router module requires a root path. In other words, “Supplier” route would become “companyname/Supplier”, “Customer” route would become “companyname/Customer” and so on. If you do not provide a route, you would end up with error as shown below:

Image 9

So in your “Index.html”, we need to add the HTML BASE HREF tag as shown in the highlighted code below. At this moment, we are not providing any root directory.

HTML
<!DOCTYPE html>
<html>
<head>
    <title></title>
    <meta charset="utf-8" />
</head>
<base href="./">
<!—Other code has been removed for clarity -->

Step 10: Seeing the Output

Now run the website and try to browse to UI folder and you should see the below animated video output. You can see that the logo gets loaded only once and later when the user clicks on the supplier links, customer links image is not loading again and again.

Image 10

Step 11: Fixing Cannot Match Any Routes Error

If you do a “F12” and check in the console part of the Chrome browser, you would see the below error. Can you guess what the error is?

Image 11

Your current Angular application is route enabled. So every URL which is browsed is looked up into routes collection.
So the first URL which you browse is “/UI” and it tries to lookup into your routes collection and does not find one.

So that’s why it throws up the above error.

Image 12

In order to fix the same, make one more entry for “UI” path and point it to “WelcomeComponent”.

JavaScript
export const ApplicationRoutes = [
    { path: 'Customer', component: CustomerComponent },
    { path: 'Supplier', component: SupplierComponent },
     { path: '', component:WelcomeComponent  },
    { path: 'UI', component:WelcomeComponent  }

];

Understanding the Flow

  1. End user loads index.html page.
  2. Index.html triggers systemjs and loads masterpage.html.
  3. When end users click on masterpage links, other pages are loaded in “router-outlet” placeholders.

Image 13

Lab 8: Implementing Validation Using Angular Form

In this lab, we will try to understand how we can implement validations using Angular framework.

Requirement of a Good Validation Structure

One of the important legs of software application is validations.

Validations are normally applied to user interfaces (Forms) and User interfaces have controls and on the controls, we apply validations.

Image 14

In Angular form validation, architecture structure is as follows.

At the top, we have FormGroup, FormGroup has FormControls and FormControls has one or many validations.

Image 15

There are three broader steps to implement Angular validation:

  1. Create FormGroup.
  2. Create FormControl and with proper validations.
  3. Map those validations to the HTML Form.

What Kind of Validation Will We Implement in this Lab?

In this lab, we will implement the following validation on our Customer screen:

  • Customer Name should be compulsory.
  • Customer code should be compulsory.
  • Customer code should be in the format of A1001, B4004 and so on. In other words, the first character should be a capital alphabet followed by 4 letter numeric.

Note: Customer code has composite validations.

Where to Put Validations?

Before we even start with validations, we need to decide which is the right place to put validations. If you see in Angular, we have three broader sections - UI, Component and Model. So let’s think over which is the right layer to put validations.

  • UI: UI is all about look and feel and positioning of controls. So putting validation in this layer is a bad idea. Yes on this layer, we will apply validations but the validation code should be in some other layer.
  • Component: Component is the code behind (Binding code) which binds the UI and Model. In this layer, more binding related activity should be put. One component can use many models so if we put the validation logic here, we need to duplicate that in other components as well.
  • Model: A Model represents a real world entity like person, user, admin, product, etc. Behavior a.k.a. validations are part of the model. A model has Data and Behavior. So the right place where validations should be present is this Layer.

So let us put validation as a part of the model.

Image 16

Step 1: Import Necessary Components for Angular Validators

So the first step is to import the necessary components for Angular validators in the customer model. All Angular validator components are present in “@angular/forms” folder. We need to import five components NgForm, FormGroup, FormControl and validators.

  • NgForm: Angular tags for validation
  • FormGroup: Helps us to create collection of validation
  • FormControl and Validators: Helps us to create individual validation inside FormGroup
  • FormBuilder: Helps us to create the structure of Form group and Form controls. Please note one Form group can have many FormControls.
JavaScript
import {NgForm,
    FormGroup,
    FormControl,
    Validators,
    FormBuilder } from '@angular/forms'

Step 2: Create FormGroup using FormBuilder

The first step is to create an object of FormGroup in which we will have collection of validation. The FormGroup object will be constructed using the “FormBuilder” class.

JavaScript
formGroup: FormGroup = null; // Create object of FormGroup
var _builder = new FormBuilder();
this.formGroup = _builder.group({}); // Use the builder to create object

Step 3: Adding a Simple Validation

Once the FormGroup object is created, the next step is to add controls in the FormGroup collection. To add a control, we need to use “addControl” method. The first parameter in “addControl” method is the name of the validation and second is the Angular validator type to be added.

Below is a simple code in which we are adding a “CustomerNameControl” using the “Validators.requiredFormControl. Please note “CustomerNameControl” is not a reserved keyword. It can be any name like “CustControl”.

JavaScript
this.formGroup.addControl('CustomerNameControl', new
            FormControl('',Validators.required));

Step 4: Adding a Composite Validation

If you want to create a composite validation, then you need to create a collection and add it using “compose” method as shown in the below code:

JavaScript
var validationcollection = [];
validationcollection.push(Validators.required);
validationcollection.push(Validators.pattern("^[A-Z]{1,1}[0-9]{4,4}$"));
this.formGroup.addControl('CustomerCodeControl', 
     new FormControl('', Validators.compose(validationcollection)));

Full Model Code With Validation

Below is the full Customer Model code with all three validations as discussed in the previous section. We have also commented the code so that you can follow it.

JavaScript
// import components from angular/form
import {NgForm,
    FormGroup,
    FormControl,
    Validators,
    FormBuilder } from '@angular/forms'
export class Customer {
    CustomerName: string = "";
    CustomerCode: string = "";
    CustomerAmount: number = 0;
    // create object of form group
    formGroup: FormGroup = null;
   
    constructor(){
        // use the builder to create the
        // the form object
        var _builder = new FormBuilder();
        this.formGroup = _builder.group({});

        // Adding a simple validation
        this.formGroup.addControl('CustomerNameControl', new
            FormControl('',Validators.required));
       
        // Adding a composite validation
        var validationcollection = [];
        validationcollection.push(Validators.required);
        validationcollection.push(Validators.pattern("^[A-Z]{1,1}[0-9]{4,4}$"));
        this.formGroup.addControl('CustomerCodeControl', new
            FormControl('', Validators.compose(validationcollection)));
    }  
}

Step 5: Reference “ReactiveFormsModule” in CustomerModule

JavaScript
// code has been removed for clarity.
import { FormsModule, ReactiveFormsModule } from "@angular/forms"
@NgModule({
    imports: [RouterModule.forChild(CustomerRoute),
                CommonModule,
                FormsModule,
                ReactiveFormsModule,
                HttpModule,
                InMemoryWebApiModule.forRoot(CustomerService)],
    declarations: [CustomerComponent, GridComponent],
    bootstrap: [CustomerComponent]
})
export class CustomerModule {
}

Step 6: Apply formGroup to HTML Form

The next thing is to apply ‘formGroup’ object to the HTML form. For that, we need to use “[formGroup]” Angular tag in that we need to specify the “formGroup” object exposed via the customer object.

HTML
<form [formGroup]="CurrentCustomer.formGroup">
</form>

Step 7: Apply Validations to HTML Control

The next step is to apply formgroup validation to the HTML input controls. That’s done by using “formControlName” Angular attribute. In “formControlName”, we need to provide the form control name which we have created while creating the validation.

HTML
<input type="text" formControlName="CustomerNameControl"
[(ngModel)]="CurrentCustomer.CustomerName"><br /><br />

Step 8: Check if Validations are OK

When user starts filling data and fulfilling the validations, we would like to check if all validations are fine and accordingly show error message or enable / disable UI controls.

In order to check if all validations are fine, we need to use the “valid” property of “formGroup”. Below is a simple example where the button will be disabled depending on whether validations are valid or not. “[disabled]” is an Angular attribute which enables and disables HTML controls.

HTML
<input type="button" 
value="Click" [disabled]="!(CurrentCustomer.formGroup.valid)"/>

Step 9: Checking Individual Validations

CurrentCustomer.formGroup.valid” checks all the validations of the “FormGroup” but what if we want to check individual validation of a control.

For that, we need to use “hasError” function.
CurrentCustomer.formGroup.controls['CustomerNameControl'].hasError('required')” checks that for “CustomerNameControl” has the “required” validation rule been fulfilled. Below is a simple code where we are displaying error message in a “div” tag which is visible and not visible depending on whether the “hasError” function returns true or false.

Also note the “!” (NOT) before “hasError” which says that if “hasError” is true, then hidden should be false and vice versa.

HTML
<div  [hidden]="!(CurrentCustomer.formGroup.controls['CustomerNameControl'].hasError
('required'))">Customer name is required </div>

Step 10: Standalone Elements

In our forms, we have three textboxes “CustomerName”, “CustomerCode” and “CustomerAmount”. In these three textboxes, only “CustomerName” and “CustomerCode” has validations while “CustomerAmount” does not have validations.

Now this is bit funny but if we do not specify validations for a usercontrol which is inside a “form” tag which has “formGroup” specified, you would end up with a long exception as shown below:

HTML
Error: Uncaught (in promise): Error: Error in ../UI/Customer.html:15:0 caused by: 
      ngModel cannot be used to register form controls with a 
      parent formGroup directive. Try using
      formGroup's partner directive "formControlName" instead.  Example:

    <div [formGroup]="myGroup">
      <input formControlName="firstName">
    </div>

    In your class:

    this.myGroup = new FormGroup({
       firstName: new FormControl()
    });

      Or, if you'd like to avoid registering this form control, 
      indicate that it's standalone in ngModelOptions:

      Example:
      
    <div [formGroup]="myGroup">
       <input formControlName="firstName">
       <input [(ngModel)]="showMoreControls" [ngModelOptions]="{standalone: true}">
    </div>
  
Error: 
      ngModel cannot be used to register form controls with a parent formGroup directive. 
      Try using
      formGroup's partner directive "formControlName" instead.  Example:

      
    <div [formGroup]="myGroup">
      <input formControlName="firstName">
    </div>

    In your class:

    this.myGroup = new FormGroup({
       firstName: new FormControl()
    });

      Or, if you'd like to avoid registering this form control, 
      indicate that it's standalone in ngModelOptions:

      Example:

      
    <div [formGroup]="myGroup">
       <input formControlName="firstName">
       <input [(ngModel)]="showMoreControls" [ngModelOptions]="{standalone: true}">
    </div>

The above error can be simplified in three simple points:

  1. It says that you have enclosed an HTML control inside a HTML FORM tag which has Angular form validations.
  2. All controls specified inside HTML FORM tag which have Angular validation applied SHOULD HAVE VALIDATIONS.
  3. If an HTML control inside Angular form validation does not have validation, you can do one of the below things to remove the exception:
    • You need to specify that it’s a standalone control.
    • Move the control outside the HTML FORM tag.

Below is the code on how to specify “standalone” for Angular validations.

HTML
<input type="text" [ngModelOptions]="{standalone:true}"
[(ngModel)]="CurrentCustomer.CustomerAmount"><br /><br />

Also talk about we can remove from the form control and what happens.

Complete Code of Customer UI with Validations Applied

Below is the complete Customer UI with all three validations applied to “CustomerName” and “CustomerCode” controls.

HTML
<form [formGroup]="CurrentCustomer.formGroup">
<div>
Name:
<input type="text" formControlName="CustomerNameControl"
[(ngModel)]="CurrentCustomer.CustomerName"><br /><br />
<div  [hidden]="!(CurrentCustomer.formGroup.controls['CustomerNameControl'].
hasError('required'))">Customer name is required </div>
Code:
<input type="text" formControlName="CustomerCodeControl"
[(ngModel)]="CurrentCustomer.CustomerCode"><br /><br />
<div  [hidden]="!(CurrentCustomer.formGroup.controls['CustomerCodeControl'].
hasError('required'))">Customer code is required </div>
<div  [hidden]="!(CurrentCustomer.formGroup.controls['CustomerCodeControl'].
hasError('pattern'))">Pattern not proper </div>

Amount:
<input type="text" 
[(ngModel)]="CurrentCustomer.CustomerAmount"><br /><br />
</div>
{{CurrentCustomer.CustomerName}}<br /><br />
{{CurrentCustomer.CustomerCode}}<br /><br />
{{CurrentCustomer.CustomerAmount}}<br /><br />
<input type="button" 
value="Click" [disabled]="!(CurrentCustomer.formGroup.valid)"/>
</form>

Write reactive forms.

Run and See Your Validation in Action

Once you are done, you should be able to see the validation in action as shown in the below figure:

Image 17

Dirty, Pristine, Touched and Untouched

In this lab, we covered “valid” and “hasError” property and function. “formGroup” also has lot of other properties which you will need when you are working with validations. Below are some important ones.

Property Explanation
dirty This property signals if Value has been modified
pristine This property says if the field has changed or not
touched When the lost focus for that control occurs
untouched The field is not touched

What's in the Next Article?

In the next section, we will look into how to create SPA application and validations using Angular 4. For reading the fourth part of Learn Angular, click here.

For further reading, do watch the below interview preparation videos and step by step video series.

History

  • 22nd September, 2017: Initial version

License

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