Introduction To TypeScript

Some tools are so useful that once you've used them in one project, it is hard to work on other projects without them. React was certainly that way for me when I first started, and now TypeScript has become the same way. This talk will cover why typed JavaScript, especially in large codebases, is worth the added effort. I will discuss my personal experiences - the good, the bad, and the ugly - and talk about why I can't seem to start a project without <code>npm install typescript</code>.

Some tools are so useful that once you've used them in one project, it is hard to work on other projects without them. React was certainly that way for me when I first started, and now TypeScript has become the same way. This talk will cover why typed JavaScript, especially in large codebases, is worth the added effort. I will discuss my personal experiences - the good, the bad, and the ugly - and talk about why I can't seem to start a project without npm install typescript.


Learn more


Understanding TypeScript

http://learnstartup.net/p/HkVUp2y8e

Typescript Masterclass & FREE E-Book

http://learnstartup.net/p/BJKmIUFP2g

Angular Tutorial: Create a CRUD App with Angular CLI and TypeScript

http://dev.thegeeknews.net/0774add31e

Introduction to TypeScript Development

http://learnstartup.net/p/HyEiI-lWx

6 Essential VSCode Extensions for Angular Developers

http://go.learn4startup.com/8d20682b41

Angular Essentials (Angular 2+ with TypeScript)

http://learnstartup.net/p/SkdU19JFZ

Backend API with Node, Express & TypeScript - Fast Track

http://learnstartup.net/p/rtgZjWg_g

Learn Protractor(Angular Automation) from scratch +Framework

http://learnstartup.net/p/fiGYxoVhe

Introduction New Features in TypeScript 3.7 and How to Use Them

Introduction New Features in TypeScript 3.7 and How to Use Them

The TypeScript 3.7 release is coming soon, and it's going to be a big one.

The target release date is November 5th, and there are some seriously exciting headline features included:

  • Assert signatures.
  • Recursive type aliases.
  • Top-level await.
  • Null coalescing.
  • Optional chaining.

Personally, I'm super excited about this, they're going to whisk away all sorts of annoyances that I've been fighting in TypeScript while building HTTP Toolkit.

If you haven't been paying close attention to the TypeScript development process though, it's probably not clear what half of these mean, or why you should care. Let's talk through all of them.

Assert Signatures

This is a brand-new and little-known TypeScript feature, which allows you to write functions that act like type guards as a side-effect, rather than explicitly returning their boolean result.

It's easiest to demonstrate this with a JavaScript example:

function assertString(input) { 
  if (typeof input === 'string') 
    return; 
  else 
    throw new Error('Input must be a string!'); 
} 
function doSomething(input) { 
  assertString(input); 
  // ... Use input, confident that it's a string 
} 
doSomething('abc'); 
// All good doSomething(123); // Throws an error

This pattern is neat and useful, and you can't use it in TypeScript today.

TypeScript can't know that you've guaranteed the type of input after it's run assertString. Typically, people just make the argument input: string to avoid this, and that's good. But, it also just pushes the type checking problem somewhere else, and in cases where you just want to fail hard, it's useful to have this option available.

Fortunately, soon we will:

// With TS 3.7 
function assertString(input: any): 
	asserts input is string { 
      // <-- the magic 
      if (typeof input === 'string') 
        return; 
      else 
        throw new Error('Input must be a string!'); 
    } 
function doSomething(input: string | number) { 
  assertString(input); 
  // input's type is just 'string' here }

Here assert input is string means that if this function ever returns, TypeScript can narrow the type of input to string, just as if it was inside an if block with a type guard.

To make this safe, that means if the assert statement isn't true then your assert function must either throw an error or not return at all (kill the process, infinite loop, you name it).

That's the basics, but this actually lets you pull some really neat tricks:

// With TS 3.7 
// Asserts that input is truthy, throwing immediately if not: 
function assert(input: any): 
	asserts input { // <-- not a typo 
      if (!input) 
        throw new Error('Not a truthy value'); 
    } 
declare const x: number | string | undefined; 
assert(x); // Narrows x to number | string 
// Also usable with type guarding expressions! 
assert(typeof x === 'string'); 
// Narrows x to string // -- Or use assert in your tests: -- 
const a: Result | Error = doSomethingTestable(); 
expect(a).is.instanceOf(result); 
// 'instanceOf' could 'asserts a is Result' 
expect(a.resultValue).to.equal(123); 
// a.resultValue is now legal // -- Use as a safer ! that throws immediately if 
// you're wrong -- 
function assertDefined<T>(obj: T): 
	asserts obj is NonNullable<T> { 
      if (obj === undefined || obj === null) { 
        throw new Error('Must not be a nullable value'); 
      } 
    } 
declare const x: string | undefined; 
// Gives y just 'string' as a type, could throw elsewhere later: 
const y = x!; 
// Gives y 'string' as a type, or throws immediately if you're wrong: 
assertDefined(x); const z = x; 
// -- Or even update types to track a function's side-effects -- 
type X<T extends string | {}> = { value: T }; 
// Use asserts to narrow types according to side effects: 
function setX<T extends string | {}>(x: X<any>, v: T): 
	asserts x is X<T> { 
      x.value = v; 
    } 
	declare let x: X<any>; 
// x is now { value: any }; 
setX(x, 123); 
// x is now { value: number };

This is still in flux, so don't take it as the definite result, and keep an eye on the pull request if you want the final details.

There's even a discussion there about allowing functions to assert something and return a type, which would let you extend the final example above to track a much wider variety of side effects, but we'll have to wait and see how that plays out.

Top-Level Await

Async/await is amazing and makes promises dramatically cleaner to use.

Unfortunately, though, you can't use them at the top level. This might not be something you care about much in a TS library or application, but if you're writing a runnable script or using TypeScript in a REPL, then this gets super annoying.

It's even worse if you're used to frontend development, since top-level await has been working nicely in the Chrome and Firefox console for a couple of years now.

Fortunately though, a fix is coming. This is actually a general stage-3 JS proposal, so it'll be everywhere else eventually too, but for TS devs 3.7 is where the magic happens.

This one's simple, but let's have another quick demo anyway:


// Your only solution right now for a script that does something async: 
async function doEverything() { 
  ... 
  const response = await fetch('http://example.com'); 
  ... 
} 
  
doEverything(); // <- eugh (could use an IIFE instead, but even more eugh)

With top-level await:

// With TS 3.7: 
// Your script: ... 
const response = await fetch('http://example.com'); 
// ...

There's a notable gotcha here: if you're not writing a script, or using a REPL, don't write this at the top level, unless you really know what you're doing!

It's totally possible to use this to write modules that do blocking async steps when imported. That can be useful for some niche cases, but people tend to assume that their import statement is a synchronous, reliable, and fairly quick operation, and you could easily hose your codebase's startup time if you start blocking imports for complex async processes (even worse, processes that can fail).

This is somewhat mitigated by the semantics of imports of async modules: they're imported and run in parallel, so the importing module effectively waits for Promise.all(importedModules) before being executed.

Rich Harris wrote an excellent piece on a previous version of this spec before that change when imports ran sequentially and this problem was much worse), which makes for good background reading on the risks here if you're interested.

It's also worth noting that this is only useful for module systems that support asynchronous imports. There isn't yet a formal spec for how TS will handle this, but that likely means that a very recent target configuration, and, either ES Modules or Webpack v5 (whose alphas have experimental support), will be used at runtime.

Recursive Type Aliases

If you're ever tried to define a recursive type in TypeScript, you may have run into StackOverflow questions like this: https://stackoverflow.com/questions/47842266/recursive-types-in-typescript.

Right now, you can't. Interfaces can be recursive, but there are limitations to their expressiveness, and type aliases can't. That means right now, you need to combine the two: define a type alias and extract the recursive parts of the type into interfaces. It works, but it's messy, and we can do better.

As a concrete example, this is the suggested type definition for JSON data:

type JSONValue = | string | number | boolean | JSONObject | JSONArray; 
interface JSONObject { [x: string]: JSONValue; } 
interface JSONArray extends Array<JSONValue> { }

That works, but the extra interfaces are only there because they're required to get around the recursion limitation.

Fixing this requires no new syntax; it just removes that restriction, so the below compiles:

// With TS 3.7: 
type JSONValue = | string | number | boolean | { [x: string]: JSONValue } | Array<JSONValue>;

Right now, that fails to compile with Type alias 'JSONValue' circularly references itself. Soon though, soon...

Null Coalescing

Aside from being difficult to spell, this one is quite simple and easy. It's based on a JavaScript stage-3 proposal, which means it'll also be coming to your favorite vanilla JavaScript environment soon (if it hasn't already).

In JavaScript, there's a common pattern for handling default values, and falling back to the first valid result of a defined group. It looks something like this:

// Use the first of firstResult/secondResult which is truthy: 
const result = firstResult || secondResult; 
// Use configValue from provided options if truthy, or 'default' if not: 
this.configValue = options.configValue || 'default';

This is useful in a host of cases, but due to some interesting quirks in JavaScript, it can catch you out. If firstResult or options.configValue can meaningfully be set to false, an empty string or 0, then this code has a bug. If those values are set, then when considered as booleans they're falsy, so the fallback value (secondResult / 'default') is used anyway.

Null coalescing fixes this. Instead of the above, you'll be able to write:

// With TS 3.7: 
// Use the first of firstResult/secondResult which is *defined*: 
const result = firstResult ?? secondResult; 
// Use configSetting from provided options if *defined*, or 'default' if not: 
this.configValue = options.configValue ?? 'default';

?? differs from || in that it falls through to the next value only if the first argument is null or undefined, not falsy. That fixes our bug. If you pass false as firstResult, that will be used instead of secondResult because, while it's falsy, it is still defined, and that's all that's required.

It's simple but super-useful, as it takes away a whole class of bugs.

Optional Chaining

Last but not least, optional chaining is another stage-3 proposal that is making its way into TypeScript.

This is designed to solve an issue faced by developers in every language: how do you get data out of a data structure when some or all of it might not be present?

Right now, you might do something like this:

// To get data.key1.key2, if any level could be null/undefined: 
let result = data ? (data.key1 ? data.key1.key2 : undefined) : undefined; 
// Another equivalent alternative: 
let result = ((data || {}).key1 || {}).key2;

Nasty! This gets much much worse if you need to go deeper, and although the second example works at runtime, it won't even compile in TypeScript, since the first step could be {}, in which case key1 isn't a valid key at all.

This gets still more complicated if you're trying to get into an array, or there's a function call somewhere in this process.

There's a host of other approaches to this, but they're all noisy, messy & error-prone. With optional chaining, you can do this:

// With TS 3.7: 
// Returns the value is it's all defined & non-null, or undefined if not. 
let result = data?.key1?.key2; 
// The same, through an array index or property, if possible: 
array?.[0]?.['key']; 
// Call a method, but only if it's defined: 
obj.method?.(); 
// Get a property, or return 'default' if any step is not defined: 
let result = data?.key1?.key2 ?? 'default';

The last case shows how neatly some of these dovetails together: null coalescing + optional chaining is a match made in heaven.

One gotcha: this will return undefined for missing values, even if they were null, e.g. in cases like (null)?.key (returns undefined). A small point, but one to watch out for if you have a lot of null in your data structures.

That's the lot! That should outline all the essentials for these features, but there are lots of smaller improvements, fixes, and editor support improvements coming too, so take a look at the official roadmap if you want to get into the nitty-gritty.

Creating a Modal Dialog in Angular 8 with TypeScript

Creating a Modal Dialog in Angular 8 with TypeScript

In this tutorial we'll cover how to implement modal windows (dialog boxes) in Angular 8 with TypeScript. The example is a custom modal without the need for any 3rd party libraries.

There are plenty of plugins and libraries out there that include modal windows, in the past I used them myself when I needed to add a modal to a new project. The main issue I have with 3rd party plugins is that they usually contain a lot of features I don't need which adds unnecessary bloat to my Angular app, so a while ago I took some time to implement a custom modal window to see how difficult it would be and also to remove the magic & mystery I had in my mind about exactly how modals work.

When I finished I was pleasantly surprised at the relatively small amount of code required to implement a custom modal window, most of the modal 'magic' is done with a handful of CSS styles (see modal.component.less) while Angular / TypeScript is just used for showing and hiding the modal windows.

Running the Angular 8 Modal Dialog Locally
  1. Install NodeJS and NPM from https://nodejs.org/en/download/.
  2. Download or clone the project source code from https://github.com/cornflourblue/angular-8-custom-modal
  3. Install all required npm packages by running npm install from the command line in the project root folder (where the package.json is located).
  4. Start the application by running npm start from the command line in the project root folder.

NOTE: You can also run the app directly using the Angular CLI command ng serve --open. To do this first install the Angular CLI globally on your system with the command npm install -g @angular/cli.

Adding Custom Modals to Your Angular 8 App

To add modals to your Angular 8 application you'll need to copy the /src/app/_modal folder and contents from the example project, the folder contains the modal module and associated files, including:

  • modal.model.less - LESS/CSS styles for displaying modal dialogs, this is where the modal "magic" happens.
  • modal.component.html - modal component template that contains the wrapper html for displaying modal dialogs.
  • modal.component.ts - modal component with the logic for displaying modal dialogs.
  • modal.module.ts - modal module that encapsulates the modal component so it can be imported by the app module.
  • modal.service.ts - modal service that can be used by any angular component to open and close modal dialogs.
  • index.ts - barrel file that re-exports the modal module and service so they can be imported using only the folder path instead of the full path to each file, and also enables importing from multiple files with a single import.

Import the Modal Module into your App Module

To make the modal component available to your Angular 8 application you need to add the ModalModule to the imports array of your App Module (app.module.ts). See the app module from the example app below, the modal module is imported on line 5 and added to the imports array of the app module on line 16.

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';

import { ModalModule } from './_modal';
import { appRoutingModule } from './app.routing';

import { AppComponent } from './app.component';
import { HomeComponent } from './home';
import { TestPageComponent } from './test-page';

@NgModule({
    imports: [
        BrowserModule,
        FormsModule,
        ModalModule,
        appRoutingModule
    ],
    declarations: [
        AppComponent,
        HomeComponent,
        TestPageComponent
    ],
    bootstrap: [AppComponent]
})

export class AppModule { }

Add the tag to pages where you want to display modals

To add a modal dialog to any page simply add the <jw-modal id="[insert unique id]"></jw-modal> tag along with the content for the modal. You can put any content you like inside the <jw-modal> element. You can also update the modal LESS/CSS if you want to change the styles of the modals, e.g to make them smaller or add CSS animation transitions.

IMPORTANT: A unique id is required for each modal on a page, it can be any string e.g. 'custom-modal-1'. The id string is used by the modal service to keep track of each active modal in the angular app, so the service knows which modal to open/close based on the id passed to the modalService.open() and modalService.close() methods e.g. modalService.open('custom-modal-1').

Here is the home component template from the example app (/src/app/home/home.component.html) that contains two modals, each is opened by a button click, and the first modal contains an input text field that allows you to edit the bodyText displayed in the template.

<div>
    <h1>Home</h1>
    <p>{{bodyText}}</p>
    <button (click)="openModal('custom-modal-1')">Open Modal 1</button>
    <button (click)="openModal('custom-modal-2')">Open Modal 2</button>
</div>

<jw-modal id="custom-modal-1">
    <h1>A Custom Modal!</h1>
    <p>Home page text: <input type="text" [(ngModel)]="bodyText" /></p>
    <button (click)="closeModal('custom-modal-1');">Close</button>
</jw-modal>

<jw-modal id="custom-modal-2">
    <h1 style="height:1000px">A Tall Custom Modal!</h1>
    <button (click)="closeModal('custom-modal-2');">Close</button>
</jw-modal>	

Opening & Closing Angular 8 Modal Dialogs

To open a modal call the modalService.open() method with the id of the modal you want to open, e.g. modalService.open('custom-modal-1'). To close a modal call the modalService.close() method with the id of the modal you want to close, e.g. modalService.close('custom-modal-1').

By default modals are closed on background click, to disable this remove the chunk of code in the modal component (/src/app/_modal/modal.component.ts) located directly below the comment // close modal on background click.

Here is the home component from the example app (/src/app/home/home.component.ts), it contains methods for opening and closing modals (openModal() and closeModal()) that call the corresponding methods of the modal service.

import { Component, OnInit } from '@angular/core';

import { ModalService } from '../_modal';

@Component({ templateUrl: 'home.component.html' })
export class HomeComponent implements OnInit {
    bodyText: string;

    constructor(private modalService: ModalService) { }

    ngOnInit() {
        this.bodyText = 'This text can be updated in modal 1';
    }

    openModal(id: string) {
        this.modalService.open(id);
    }

    closeModal(id: string) {
        this.modalService.close(id);
    }
}	
Breakdown of the Angular 8 Custom Modal Code

Below is a breakdown of the pieces of code used to implement custom modal dialogs in Angular 8 & TypeScript, you don't need to know the details of how it all works to use the modals in your project, it's only if you're interested in the nuts and bolts or if you want to modify the underlying code or behaviour.

LESS/CSS Styles for Angular 8 Modal Dialogs

These are the styles applied to the custom modal dialogs in this example, they could also be used in non-angular projects as it's just pure LESS/CSS.

I prefixed the modal element and classes with jw- to prevent conflicts with 3rd party css libraries such as Bootstrap.

/* MODAL STYLES
-------------------------------*/
jw-modal {
    /* modals are hidden by default */
    display: none;

    .jw-modal {
        /* modal container fixed across whole screen */
        position: fixed;
        top: 0;
        right: 0;
        bottom: 0;
        left: 0;

        /* z-index must be higher than .jw-modal-background */
        z-index: 1000;
        
        /* enables scrolling for tall modals */
        overflow: auto;

        .jw-modal-body {
            padding: 20px;
            background: #fff;

            /* margin exposes part of the modal background */
            margin: 40px;
        }
    }

    .jw-modal-background {
        /* modal background fixed across whole screen */
        position: fixed;
        top: 0;
        right: 0;
        bottom: 0;
        left: 0;

        /* semi-transparent black  */
        background-color: #000;
        opacity: 0.75;
        
        /* z-index must be below .jw-modal and above everything else  */
        z-index: 900;
    }
}

body.jw-modal-open {
    /* body overflow is hidden to hide main scrollbar when modal window is open */
    overflow: hidden;
}	
Angular 8 Modal Service

The Angular 8 modal service manages the communication that's required between page components and modal components. It maintains a list of available modals on the page and exposes methods for interacting with those modals.

import { Injectable } from '@angular/core';

@Injectable({ providedIn: 'root' })
export class ModalService {
    private modals: any[] = [];

    add(modal: any) {
        // add modal to array of active modals
        this.modals.push(modal);
    }

    remove(id: string) {
        // remove modal from array of active modals
        this.modals = this.modals.filter(x => x.id !== id);
    }

    open(id: string) {
        // open modal specified by id
        const modal = this.modals.find(x => x.id === id);
        modal.open();
    }

    close(id: string) {
        // close modal specified by id
        const modal = this.modals.find(x => x.id === id);
        modal.close();
    }
}	
Angular 8 Modal Component

The custom modal component is used to add modal windows anywhere in your angular application by using the <jw-modal> tag. Each modal instance adds itself to the modal service when it loads by calling modalService.add(this) from the ngOnInit Angular lifecycle method, and removes itself from the modal service when it is destroyed by calling modalService.remove(this.id) from the ngOnDestroy Angular lifecycle method.

import { Component, ViewEncapsulation, ElementRef, Input, OnInit, OnDestroy } from '@angular/core';

import { ModalService } from './modal.service';

@Component({ 
    selector: 'jw-modal', 
    templateUrl: 'modal.component.html', 
    styleUrls: ['modal.component.less'],
    encapsulation: ViewEncapsulation.None
})
export class ModalComponent implements OnInit, OnDestroy {
    @Input() id: string;
    private element: any;

    constructor(private modalService: ModalService, private el: ElementRef) {
        this.element = el.nativeElement;
    }

    ngOnInit(): void {
        // ensure id attribute exists
        if (!this.id) {
            console.error('modal must have an id');
            return;
        }

        // move element to bottom of page (just before </body>) so it can be displayed above everything else
        document.body.appendChild(this.element);

        // close modal on background click
        this.element.addEventListener('click', el => {
            if (el.target.className === 'jw-modal') {
                this.close();
            }
        });

        // add self (this modal instance) to the modal service so it's accessible from controllers
        this.modalService.add(this);
    }

    // remove self from modal service when component is destroyed
    ngOnDestroy(): void {
        this.modalService.remove(this.id);
        this.element.remove();
    }

    // open modal
    open(): void {
        this.element.style.display = 'block';
        document.body.classList.add('jw-modal-open');
    }

    // close modal
    close(): void {
        this.element.style.display = 'none';
        document.body.classList.remove('jw-modal-open');
    }
}	
Angular 8 Modal Component Template

The modal component template contains just a couple of wrapper divs for the modal content and a div for the modal background. The <ng-content> element is replaced by Angular with the contents you set inside the <jw-modal> element, this is called Angular content projection.

<div class="jw-modal">
    <div class="jw-modal-body">
        <ng-content></ng-content>
    </div>
</div>
<div class="jw-modal-background"></div>	

Thanks for reading

If you liked this post, share it with all of your programming buddies!

Follow us on Facebook | Twitter

Further reading about AngularJS

Angular 8 (formerly Angular 2) - The Complete Guide

Angular & NodeJS - The MEAN Stack Guide

The Complete Node.js Developer Course (3rd Edition)

The Web Developer Bootcamp

Best 50 Angular Interview Questions for Frontend Developers in 2019

MEAN Stack Angular 8 CRUD Web Application

Angular 8 Tutorial - User Registration and Login Example

How to build a CRUD Web App with Angular 8.0

Building CRUD Mobile App using Ionic 4, Angular 8

Angular 8 Material Design Tutorial & Example

How to Use External and Custom JavaScript in Angular 8|7

How to Use External and Custom JavaScript in Angular 8|7

In this tutorial, we'll learn how to use external and custom JavaScript libraries/code in Angular 8 projects which are based on TypeScript.

Prerequisites

You will need to have the following prerequisites:

  • Node and NPM installed on your machine,
  • Angular CLI 8 (npm install -g @angular/cli),
  • An Angular project.

You can create an Angular project by running the following command in your terminal:

$ ng new angular-javascript-demo

In the recent versions of Angular, you'll be prompted by the CLI for a couple of questions such as if Would you like to add Angular routing? (y/N) and Which stylesheet format would you like to use?. You can answer these questions as you see fit because this won't affect how to use JavaScript libraries in your Angular project.

Let's now see how we can use external JavaScript in Angular 8. We'll make use of the popular jQuery library as an example.

Note: Please note that it's not recommended to use jQuery for maniplulating the DOM in Angular. This is simply an example of including an external JS library in Angular.

If your library is popular you'll most likl you can install it from npm. In you terminal, navigate to your project's folder and run install jquery:

$ npm install jquery --save

Next, open the angular.json file and locate the scripts array and update as follows:

"scripts": [
  "node_modules/jquery/dist/jquery.min.js"
]

Next, in the component where you want to call your external library you need to declare the JavaScript symbol you want to call. For example, for jQuery, we need add the following line:

declare  var jQuery:  any;

Next, you can call the required functions as in the following example:

import { Component, OnInit } from '@angular/core';
declare var jQuery: any;

@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent implements OnInit {
name = 'Angular';

ngOnInit(){
(function ($) {
$(document).ready(function(){
console.log("Hello from jQuery!");
});
})(jQuery);
}
}

How to Use Custom JavaScript in Angular

Let's now see how we can use custom JavaScript in Angular.

First, create a JavaScript file inside the src/ folder, add the following code as example:

(function hello() {
alert('Hello!!!');
})()

Next, add the script to the scripts array in the angular.json file:

            "scripts": [
"src/custom.js"
]

You can also simply declare and call your JavaScript functions in any component since it's a TypeScript file. For example:

import { Component, OnInit } from '@angular/core';

function hello() {
alert('Hello!!!');
}

@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent implements OnInit {
name = 'Angular';

ngOnInit(){
hello()
}
}


Originally published at techiediaries.com on 22 Jul 2019

==========================================

Thanks for reading :heart: If you liked this post, share it with all of your programming buddies! Follow me on Facebook | Twitter

Learn More

☞ Angular 8 (formerly Angular 2) - The Complete Guide

☞ Complete Angular 8 from Zero to Hero | Get Hired

☞ Learn and Understand AngularJS

☞ The Complete Angular Course: Beginner to Advanced

☞ Angular Crash Course for Busy Developers

☞ Angular Essentials (Angular 2+ with TypeScript)

☞ Angular (Full App) with Angular Material, Angularfire & NgRx

☞ Angular & NodeJS - The MEAN Stack Guide

Angular 8 Tutorial - User Registration and Login Example

Angular 8 Tutorial - User Registration and Login Example

In this tutorial we'll go through an example of how to build a simple user registration and login system using Angular 8, TypeScript and webpack 4.

In this tutorial we'll go through an example of how to build a simple user registration and login system using Angular 8, TypeScript and webpack 4.

The project is available on GitHub at https://github.com/cornflourblue/angular-8-registration-login-example.

Running the Angular 8 Login Tutorial Example Locally

The tutorial example uses Webpack 4 to transpile the TypeScript code and bundle the Angular 8 modules together, and the webpack dev server is used as the local web server, to learn more about using webpack with TypeScript you can check out the webpack docs.

  1. Install NodeJS and NPM from https://nodejs.org/en/download/.
  2. Download or clone the tutorial project source code from https://github.com/cornflourblue/angular-8-registration-login-example
  3. Install all required npm packages by running npm install from the command line in the project root folder (where the package.json is located).
  4. Start the application by running npm start from the command line in the project root folder.
  5. Your browser should automatically open at [http://localhost:8080](http://localhost:8080) with the demo Angular 8 login page displayed.
Running the Tutorial Example with a Real Backend API

The tutorial uses a fake backend that stores users in HTML5 local storage, to switch to using a real web service simply remove the fake backend providers in the app.module.ts file below the comment // providers used to create fake backend.

Angular 8 Tutorial Project Structure

The project and code structure of the tutorial mostly follows the best practice recommendations in the official Angular Style Guide, with a few of my own tweaks here and there.

The project structure has a folder per feature (home, login & register), while other shared/common code (services, models, content, components & helpers) is placed in folders prefixed with an underscore "_" to easily differentiate between shared code and feature specific code, the prefix also groups shared component folders together at the top of the folder structure.

The index.ts files in each folder are barrel files that group the exported modules from a folder together so they can be imported using the folder path instead of the full module path and to enable importing multiple modules in a single import (e.g. import { AlertService, UserService, AuthenticationService } from '@/_services').

A path alias '@' has been configured in the tsconfig.json and webpack.config.js that maps to the '/src/app' directory. This allows imports to be relative to the '/src/app' folder by prefixing the import path with '@', removing the need to use long relative paths like import MyComponent from '../../../MyComponent'.

Here's the tutorial project structure:

Below is all the tutorial project code along with brief descriptions of each file to explain how it all fits together.

Alert Component Template

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

The alert component template contains the html for displaying alert messages at the top of the page.

{{message.text}}

Alert Component

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

The alert component passes alert messages to the template whenever a message is received from the alert service. It does this by subscribing to the alert service's getMessage() method which returns an Observable.

import { Component, OnInit, OnDestroy } from '@angular/core';
import { Subscription } from 'rxjs';

import { AlertService } from '@/_services';

@Component({ selector: 'alert', templateUrl: 'alert.component.html' })
export class AlertComponent implements OnInit, OnDestroy {
    private subscription: Subscription;
    message: any;

    constructor(private alertService: AlertService) { }

    ngOnInit() {
        this.subscription = this.alertService.getAlert()
            .subscribe(message => {
                switch (message && message.type) {
                    case 'success':
                        message.cssClass = 'alert alert-success';
                        break;
                    case 'error':
                        message.cssClass = 'alert alert-danger';
                        break;
                }

                this.message = message;
            });
    }

    ngOnDestroy() {
        this.subscription.unsubscribe();
    }
}
Global App LESS/CSS Styles

Path: /src/app/_content/app.less

Global LESS/CSS for styling any part of the application.

// global application styles
a {
    cursor: pointer;
}
Auth Guard

Path: /src/app/_helpers/auth.guard.ts

The auth guard is used to prevent unauthenticated users from accessing restricted routes, in this example it's used in app.routing.ts to protect the home page route.

NOTE: While technically it's possible to bypass this client side authentication check by manually adding a 'currentUser' object to local storage using browser dev tools, this would only give access to the client side routes/components, it wouldn't give access to any real secure data from the server api because a valid authentication token (JWT) is required for this.

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

import { AuthenticationService } from '@/_services';

@Injectable({ providedIn: 'root' })
export class AuthGuard implements CanActivate {
    constructor(
        private router: Router,
        private authenticationService: AuthenticationService
    ) {}

    canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
        const currentUser = this.authenticationService.currentUserValue;
        if (currentUser) {
            // authorised so return true
            return true;
        }

        // not logged in so redirect to login page with the return url
        this.router.navigate(['/login'], { queryParams: { returnUrl: state.url }});
        return false;
    }
}
Http Error Interceptor

Path: /src/app/_helpers/error.interceptor.ts

The Error Interceptor intercepts http responses from the api to check if there were any errors. If there is a 401 Unauthorized response the user is automatically logged out of the application, all other errors are re-thrown up to the calling service so an alert can be displayed to the user.

It's implemented using the HttpInterceptor class that was introduced in Angular 4.3 as part of the new HttpClientModule. By extending the HttpInterceptor class you can create a custom interceptor to catch all error responses from the server in a single location.

Http interceptors are added to the request pipeline in the providers section of the app.module.ts file.

import { Injectable } from '@angular/core';
import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';

import { AuthenticationService } from '@/_services';

@Injectable()
export class ErrorInterceptor implements HttpInterceptor {
    constructor(private authenticationService: AuthenticationService) {}

    intercept(request: HttpRequest, next: HttpHandler): Observable> {
        return next.handle(request).pipe(catchError(err => {
            if (err.status === 401) {
                // auto logout if 401 response returned from api
                this.authenticationService.logout();
                location.reload(true);
            }
            
            const error = err.error.message || err.statusText;
            return throwError(error);
        }))
    }
}
Fake Backend Provider

Path: /src/app/_helpers/fake-backend.ts

The fake backend provider enables the example to run without a backend / backendless, it uses HTML5 local storage for storing registered user data and provides fake implementations for authentication and CRUD methods, these would be handled by a real api and database in a production application.

It's implemented using the HttpInterceptor class that was introduced in Angular 4.3 as part of the new HttpClientModule. By extending the HttpInterceptor class you can create a custom interceptor to modify http requests before they get sent to the server. In this case the FakeBackendInterceptor intercepts certain requests based on their URL and provides a fake response instead of going to the server.

import { Injectable } from '@angular/core';
import { HttpRequest, HttpResponse, HttpHandler, HttpEvent, HttpInterceptor, HTTP_INTERCEPTORS } from '@angular/common/http';
import { Observable, of, throwError } from 'rxjs';
import { delay, mergeMap, materialize, dematerialize } from 'rxjs/operators';

// array in local storage for registered users
let users = JSON.parse(localStorage.getItem('users')) || [];

@Injectable()
export class FakeBackendInterceptor implements HttpInterceptor {
    intercept(request: HttpRequest, next: HttpHandler): Observable> {
        const { url, method, headers, body } = request;

        // wrap in delayed observable to simulate server api call
        return of(null)
            .pipe(mergeMap(handleRoute))
            .pipe(materialize()) // call materialize and dematerialize to ensure delay even if an error is thrown (https://github.com/Reactive-Extensions/RxJS/issues/648)
            .pipe(delay(500))
            .pipe(dematerialize());

        function handleRoute() {
            switch (true) {
                case url.endsWith('/users/authenticate') && method === 'POST':
                    return authenticate();
                case url.endsWith('/users/register') && method === 'POST':
                    return register();
                case url.endsWith('/users') && method === 'GET':
                    return getUsers();
                case url.match(/\/users\/\d+$/) && method === 'DELETE':
                    return deleteUser();
                default:
                    // pass through any requests not handled above
                    return next.handle(request);
            }    
        }

        // route functions

        function authenticate() {
            const { username, password } = body;
            const user = users.find(x => x.username === username && x.password === password);
            if (!user) return error('Username or password is incorrect');
            return ok({
                id: user.id,
                username: user.username,
                firstName: user.firstName,
                lastName: user.lastName,
                token: 'fake-jwt-token'
            })
        }

        function register() {
            const user = body

            if (users.find(x => x.username === user.username)) {
                return error('Username "' + user.username + '" is already taken')
            }

            user.id = users.length ? Math.max(...users.map(x => x.id)) + 1 : 1;
            users.push(user);
            localStorage.setItem('users', JSON.stringify(users));

            return ok();
        }

        function getUsers() {
            if (!isLoggedIn()) return unauthorized();
            return ok(users);
        }

        function deleteUser() {
            if (!isLoggedIn()) return unauthorized();

            users = users.filter(x => x.id !== idFromUrl());
            localStorage.setItem('users', JSON.stringify(users));
            return ok();
        }

        // helper functions

        function ok(body?) {
            return of(new HttpResponse({ status: 200, body }))
        }

        function error(message) {
            return throwError({ error: { message } });
        }

        function unauthorized() {
            return throwError({ status: 401, error: { message: 'Unauthorised' } });
        }

        function isLoggedIn() {
            return headers.get('Authorization') === 'Bearer fake-jwt-token';
        }

        function idFromUrl() {
            const urlParts = url.split('/');
            return parseInt(urlParts[urlParts.length - 1]);
        }
    }
}

export const fakeBackendProvider = {
    // use fake backend in place of Http service for backend-less development
    provide: HTTP_INTERCEPTORS,
    useClass: FakeBackendInterceptor,
    multi: true
};
JWT Interceptor

Path: /src/app/_helpers/jwt.interceptor.ts

The JWT Interceptor intercepts http requests from the application to add a JWT auth token to the Authorizationheader if the user is logged in.

It's implemented using the HttpInterceptor class that was introduced in Angular 4.3 as part of the new HttpClientModule. By extending the HttpInterceptor class you can create a custom interceptor to modify http requests before they get sent to the server.

Http interceptors are added to the request pipeline in the providers section of the app.module.ts file.

import { Injectable } from '@angular/core';
import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http';
import { Observable } from 'rxjs';

import { AuthenticationService } from '@/_services';

@Injectable()
export class JwtInterceptor implements HttpInterceptor {
    constructor(private authenticationService: AuthenticationService) {}

    intercept(request: HttpRequest, next: HttpHandler): Observable> {
        // add authorization header with jwt token if available
        let currentUser = this.authenticationService.currentUserValue;
        if (currentUser && currentUser.token) {
            request = request.clone({
                setHeaders: { 
                    Authorization: `Bearer ${currentUser.token}`
                }
            });
        }

        return next.handle(request);
    }
}
User Model

Path: /src/app/_models/user.ts

The user model is a small class that defines the properties of a user. The token property is used to hold the JWT token that is returned from the api on successful authentication.

export class User {
    id: number;
    username: string;
    password: string;
    firstName: string;
    lastName: string;
    token: string;
}
Alert Service

Path: /src/app/_services/alert.service.ts

The alert service enables any component in the application to display alert messages at the top of the page via the alert component.

It has methods for displaying success and error messages, and a getMessage() method that returns an Observable that is used by the alert component to subscribe to notifications for whenever a message should be displayed.

import { Injectable } from '@angular/core';
import { Router, NavigationStart } from '@angular/router';
import { Observable, Subject } from 'rxjs';

@Injectable({ providedIn: 'root' })
export class AlertService {
    private subject = new Subject();
    private keepAfterRouteChange = false;

    constructor(private router: Router) {
        // clear alert messages on route change unless 'keepAfterRouteChange' flag is true
        this.router.events.subscribe(event => {
            if (event instanceof NavigationStart) {
                if (this.keepAfterRouteChange) {
                    // only keep for a single route change
                    this.keepAfterRouteChange = false;
                } else {
                    // clear alert message
                    this.clear();
                }
            }
        });
    }

    getAlert(): Observable {
        return this.subject.asObservable();
    }

    success(message: string, keepAfterRouteChange = false) {
        this.keepAfterRouteChange = keepAfterRouteChange;
        this.subject.next({ type: 'success', text: message });
    }

    error(message: string, keepAfterRouteChange = false) {
        this.keepAfterRouteChange = keepAfterRouteChange;
        this.subject.next({ type: 'error', text: message });
    }

    clear() {
        // clear by calling subject.next() without parameters
        this.subject.next();
    }
}
Authentication Service

Path: /src/app/_services/authentication.service.ts

The authentication service is used to login and logout of the application, to login it posts the users credentials to the api and checks the response for a JWT token, if there is one it means authentication was successful so the user details including the token are added to local storage.

The logged in user details are stored in local storage so the user will stay logged in if they refresh the browser and also between browser sessions until they logout. If you don't want the user to stay logged in between refreshes or sessions the behaviour could easily be changed by storing user details somewhere less persistent such as session storage which would persist between refreshes but not browser sessions, or in a private variable in the authentication service which would be cleared when the browser is refreshed.

There are two properties exposed by the authentication service for accessing the currently logged in user. The currentUser observable can be used when you want a component to reactively update when a user logs in or out, for example in the app.component.ts so it can show/hide the main nav bar when the user logs in/out. The currentUserValue property can be used when you just want to get the current value of the logged in user but don't need to reactively update when it changes, for example in the auth.guard.ts which restricts access to routes by checking if the user is currently logged in.

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { BehaviorSubject, Observable } from 'rxjs';
import { map } from 'rxjs/operators';

import { User } from '@/_models';

@Injectable({ providedIn: 'root' })
export class AuthenticationService {
    private currentUserSubject: BehaviorSubject;
    public currentUser: Observable;

    constructor(private http: HttpClient) {
        this.currentUserSubject = new BehaviorSubject(JSON.parse(localStorage.getItem('currentUser')));
        this.currentUser = this.currentUserSubject.asObservable();
    }

    public get currentUserValue(): User {
        return this.currentUserSubject.value;
    }

    login(username, password) {
        return this.http.post(`${config.apiUrl}/users/authenticate`, { username, password })
            .pipe(map(user => {
                // store user details and jwt token in local storage to keep user logged in between page refreshes
                localStorage.setItem('currentUser', JSON.stringify(user));
                this.currentUserSubject.next(user);
                return user;
            }));
    }

    logout() {
        // remove user from local storage and set current user to null
        localStorage.removeItem('currentUser');
        this.currentUserSubject.next(null);
    }
}
User Service

Path: /src/app/_services/user.service.ts

The user service contains a standard set of CRUD methods for managing users, it acts as the interface between the Angular application and the backend api.

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';

import { User } from '@/_models';

@Injectable({ providedIn: 'root' })
export class UserService {
    constructor(private http: HttpClient) { }

    getAll() {
        return this.http.get(`${config.apiUrl}/users`);
    }

    register(user: User) {
        return this.http.post(`${config.apiUrl}/users/register`, user);
    }

    delete(id: number) {
        return this.http.delete(`${config.apiUrl}/users/${id}`);
    }
}
Home Component Template

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

The home component template contains html and angular 8 template syntax for displaying a simple welcome message and a list of users with a link to delete any user.

# Hi {{currentUser.firstName}}!


You're logged in with Angular 8!!

### All registered users:


    
        {{user.username}} ({{user.firstName}} {{user.lastName}})
        - Delete
    

Home Component

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

The home component gets the current user from the authentication service by subscribing to the currentUser observable in the authentication service. The subscription for the current user is stored in a variable so it can be unsubscribed from when the home component is destroyed, this is to prevent memory leaks from orphaned subscriptions in the Angular 8 app.

The home component gets all users from the user service and makes them available to the home template via the users property.

import { Component, OnInit } from '@angular/core';
import { first } from 'rxjs/operators';

import { User } from '@/_models';
import { UserService, AuthenticationService } from '@/_services';

@Component({ templateUrl: 'home.component.html' })
export class HomeComponent implements OnInit {
    currentUser: User;
    users = [];

    constructor(
        private authenticationService: AuthenticationService,
        private userService: UserService
    ) {
        this.currentUser = this.authenticationService.currentUserValue;
    }

    ngOnInit() {
        this.loadAllUsers();
    }

    deleteUser(id: number) {
        this.userService.delete(id)
            .pipe(first())
            .subscribe(() => this.loadAllUsers());
    }

    private loadAllUsers() {
        this.userService.getAll()
            .pipe(first())
            .subscribe(users => this.users = users);
    }
}
Login Component Template

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

The login component template contains a login form with username and password fields. It displays validation messages for invalid fields when the submit button is clicked. The form submit event is bound to the onSubmit() method of the login component.

## Login


    
        Username
        
        
            Username is required

        
    
    
        Password
        
        
            Password is required

        
    
    
        
            
            Login
        
        Register
    

Login Component

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

The login component uses the authentication service to login to the application. If the user is already logged in they are automatically redirected to the home page.

The loginForm: FormGroup object defines the form controls and validators, and is used to access data entered into the form. The FormGroup is part of the Angular Reactive Forms module and is bound to the login template above with the [formGroup]="loginForm" directive.

import { Component, OnInit } from '@angular/core';
import { Router, ActivatedRoute } from '@angular/router';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { first } from 'rxjs/operators';

import { AlertService, AuthenticationService } from '@/_services';

@Component({ templateUrl: 'login.component.html' })
export class LoginComponent implements OnInit {
    loginForm: FormGroup;
    loading = false;
    submitted = false;
    returnUrl: string;

    constructor(
        private formBuilder: FormBuilder,
        private route: ActivatedRoute,
        private router: Router,
        private authenticationService: AuthenticationService,
        private alertService: AlertService
    ) {
        // redirect to home if already logged in
        if (this.authenticationService.currentUserValue) {
            this.router.navigate(['/']);
        }
    }

    ngOnInit() {
        this.loginForm = this.formBuilder.group({
            username: ['', Validators.required],
            password: ['', Validators.required]
        });

        // get return url from route parameters or default to '/'
        this.returnUrl = this.route.snapshot.queryParams['returnUrl'] || '/';
    }

    // convenience getter for easy access to form fields
    get f() { return this.loginForm.controls; }

    onSubmit() {
        this.submitted = true;

        // reset alerts on submit
        this.alertService.clear();

        // stop here if form is invalid
        if (this.loginForm.invalid) {
            return;
        }

        this.loading = true;
        this.authenticationService.login(this.f.username.value, this.f.password.value)
            .pipe(first())
            .subscribe(
                data => {
                    this.router.navigate([this.returnUrl]);
                },
                error => {
                    this.alertService.error(error);
                    this.loading = false;
                });
    }
}
Register Component Template

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

The register component template contains a simple registration form with fields for first name, last name, username and password. It displays validation messages for invalid fields when the submit button is clicked. The form submit event is bound to the onSubmit() method of the register component.

## Register


    
        First Name
        
        
            First Name is required

        
    
    
        Last Name
        
        
            Last Name is required

        
    
    
        Username
        
        
            Username is required

        
    
    
        Password
        
        
            Password is required

            Password must be at least 6 characters

        
    
    
        
            
            Register
        
        Cancel
    

Register Component

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

The register component creates a new user with the user service when the register form is submitted. If the user is already logged in they are automatically redirected to the home page.

The registerForm: FormGroup object defines the form controls and validators, and is used to access data entered into the form. The FormGroup is part of the Angular Reactive Forms module and is bound to the login template above with the [formGroup]="registerForm" directive.

import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { first } from 'rxjs/operators';

import { AlertService, UserService, AuthenticationService } from '@/_services';

@Component({ templateUrl: 'register.component.html' })
export class RegisterComponent implements OnInit {
    registerForm: FormGroup;
    loading = false;
    submitted = false;

    constructor(
        private formBuilder: FormBuilder,
        private router: Router,
        private authenticationService: AuthenticationService,
        private userService: UserService,
        private alertService: AlertService
    ) {
        // redirect to home if already logged in
        if (this.authenticationService.currentUserValue) {
            this.router.navigate(['/']);
        }
    }

    ngOnInit() {
        this.registerForm = this.formBuilder.group({
            firstName: ['', Validators.required],
            lastName: ['', Validators.required],
            username: ['', Validators.required],
            password: ['', [Validators.required, Validators.minLength(6)]]
        });
    }

    // convenience getter for easy access to form fields
    get f() { return this.registerForm.controls; }

    onSubmit() {
        this.submitted = true;

        // reset alerts on submit
        this.alertService.clear();

        // stop here if form is invalid
        if (this.registerForm.invalid) {
            return;
        }

        this.loading = true;
        this.userService.register(this.registerForm.value)
            .pipe(first())
            .subscribe(
                data => {
                    this.alertService.success('Registration successful', true);
                    this.router.navigate(['/login']);
                },
                error => {
                    this.alertService.error(error);
                    this.loading = false;
                });
    }
}
App Component Template

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

The app component template is the root component template of the application, it contains the main nav bar which is only displayed for authenticated users, a router-outlet directive for displaying the contents of each view based on the current route, and an alert component for displaying alert messages from anywhere in the application.



    
        Home
        Logout
    




    
        
            
                
                
            
        
    

App Component

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

The app component is the root component of the application, it defines the root tag of the app as `` with the selector property of the @Component decorator.

It subscribes to the currentUser observable in the authentication service so it can reactively show/hide the main navigation bar when the user logs in/out of the application. I didn't worry about unsubscribing from the observable here because it's the root component of the application, the only time the component will be destroyed is when the application is closed which would destroy any subscriptions as well.

The app component contains a logout() method which is called from the logout link in the main nav bar above to log the user out and redirect them to the login page.

import { Component } from '@angular/core';
import { Router } from '@angular/router';

import { AuthenticationService } from './_services';
import { User } from './_models';

import './_content/app.less';

@Component({ selector: 'app', templateUrl: 'app.component.html' })
export class AppComponent {
    currentUser: User;

    constructor(
        private router: Router,
        private authenticationService: AuthenticationService
    ) {
        this.authenticationService.currentUser.subscribe(x => this.currentUser = x);
    }

    logout() {
        this.authenticationService.logout();
        this.router.navigate(['/login']);
    }
}
App Module

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

The app module defines the root module of the application along with metadata about the module. For more info about angular modules check out this page on the official docs site.

This is where the fake backend provider is added to the application, to switch to a real backend simply remove the providers located below the comment // providers used to create fake backend.

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { ReactiveFormsModule } from '@angular/forms';
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';

// used to create fake backend
import { fakeBackendProvider } from './_helpers';

import { appRoutingModule } from './app.routing';
import { JwtInterceptor, ErrorInterceptor } from './_helpers';
import { AppComponent } from './app.component';
import { HomeComponent } from './home';
import { LoginComponent } from './login';
import { RegisterComponent } from './register';
import { AlertComponent } from './_components';

@NgModule({
    imports: [
        BrowserModule,
        ReactiveFormsModule,
        HttpClientModule,
        appRoutingModule
    ],
    declarations: [
        AppComponent,
        HomeComponent,
        LoginComponent,
        RegisterComponent,
        AlertComponent
    ],
    providers: [
        { provide: HTTP_INTERCEPTORS, useClass: JwtInterceptor, multi: true },
        { provide: HTTP_INTERCEPTORS, useClass: ErrorInterceptor, multi: true },

        // provider used to create fake backend
        fakeBackendProvider
    ],
    bootstrap: [AppComponent]
})
export class AppModule { };
App Routing Module

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

Routing for the Angular app is configured as an array of Routes, each component is mapped to a path so the Angular Router knows which component to display based on the URL in the browser address bar. The home route is secured by passing the AuthGuard to the canActivate property of the route.

The Routes array is passed to the RouterModule.forRoot() method which creates a routing module with all of the app routes configured, and also includes all of the Angular Router providers and directives such as the `` directive. For more information on Angular Routing and Navigation see https://angular.io/guide/router.

import { Routes, RouterModule } from '@angular/router';

import { HomeComponent } from './home';
import { LoginComponent } from './login';
import { RegisterComponent } from './register';
import { AuthGuard } from './_helpers';

const routes: Routes = [
    { path: '', component: HomeComponent, canActivate: [AuthGuard] },
    { path: 'login', component: LoginComponent },
    { path: 'register', component: RegisterComponent },

    // otherwise redirect to home
    { path: '**', redirectTo: '' }
];

export const appRoutingModule = RouterModule.forRoot(routes);
Main Index Html File

Path: /src/index.html

The main index.html file is the initial page loaded by the browser that kicks everything off. Webpack bundles all of the javascript files together and injects them into the body of the index.html page so the scripts get loaded and executed by the browser.




    
    Angular 8 User Registration and Login Example
    

    
    


    Loading...


Main (Bootstrap) File

Path: /src/main.ts

The main file is the entry point used by angular to launch and bootstrap the application.

import './polyfills';

import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';

platformBrowserDynamic().bootstrapModule(AppModule);
Polyfills

Path: /src/polyfills.ts

Some features used by Angular 8 are not yet supported natively by all major browsers, polyfills are used to add support for features where necessary so your Angular 8 application works across all major browsers.

import 'core-js/features/reflect';
import 'zone.js/dist/zone';
Custom Typings File

Path: /src/typings.d.ts

A custom typings file is used to declare types that are created outside of your angular application, so the TypeScript compiler is aware of them and doesn't give you errors about unknown types. This typings file contains a declaration for the global config object that is created by webpack (see webpack.config.js below).

// so the typescript compiler doesn't complain about the global config object
declare var config: any;
npm package.json

Path: /package.json

The package.json file contains project configuration information including package dependencies which get installed when you run npm install. Full documentation is available on the npm docs website.

{
    "name": "angular-8-registration-login-example",
    "version": "1.0.0",
    "repository": {
        "type": "git",
        "url": "https://github.com/cornflourblue/angular-8-registration-login-example.git"
    },
    "scripts": {
        "build": "webpack --mode production",
        "start": "webpack-dev-server --mode development --open"
    },
    "license": "MIT",
    "dependencies": {
        "@angular/common": "^8.0.0",
        "@angular/compiler": "^8.0.0",
        "@angular/core": "^8.0.0",
        "@angular/forms": "^8.0.0",
        "@angular/platform-browser": "^8.0.0",
        "@angular/platform-browser-dynamic": "^8.0.0",
        "@angular/router": "^8.0.0",
        "core-js": "^3.1.3",
        "rxjs": "^6.3.3",
        "zone.js": "^0.9.1"
    },
    "devDependencies": {
        "@types/node": "^12.0.7",
        "angular2-template-loader": "^0.6.2",
        "css-loader": "^2.1.1",
        "html-loader": "^0.5.5",
        "html-webpack-plugin": "^3.2.0",
        "less": "^3.0.4",
        "less-loader": "^5.0.0",
        "style-loader": "^0.23.1",
        "ts-loader": "^6.0.1",
        "typescript": "^3.1.3",
        "webpack": "^4.32.2",
        "webpack-cli": "^3.1.2",
        "webpack-dev-server": "^3.7.0"
    }
}
TypeScript tsconfig.json

Path: /tsconfig.json

The tsconfig.json file configures how the TypeScript compiler will convert TypeScript into JavaScript that is understood by the browser. More information is available on the TypeScript docs.

{
    "compilerOptions": {
        "emitDecoratorMetadata": true,
        "experimentalDecorators": true,
        "target": "ES5",
        "baseUrl": "src",
        "paths": {
            "@/*": [
                "app/*"
            ]
        }
    }
}
Webpack 4 Config

Path: /webpack.config.js

Webpack 4 is used to compile and bundle all the project files so they're ready to be loaded into a browser, it does this with the help of loaders and plugins that are configured in the webpack.config.js file. For more info about webpack check out the webpack docs.

This is a fairly basic webpack.config.js for bundling an Angular 8 application, it:

  • compiles Angular TypeScript files using ts-loader.
  • loads angular templates with the angular2-template-loader and html-loader.
  • converts LESS files into CSS and loads them into the application with the style-loader, css-loader and less-loader.
  • injects the bundled scripts into the body of the index.html page using the HtmlWebpackPlugin.
  • defines a global config object with the plugin webpack.DefinePlugin.

A path alias '@' is configured in the webpack.config.js and the tsconfig.json that maps to the '/src/app' directory. This allows imports to be relative to the '/src/app' folder by prefixing the import path with '@', removing the need to use long relative paths like import MyComponent from '../../../MyComponent'.

It also includes a couple of workarounds to prevent the following warnings from appearing in the console when running the app: "System.import() is deprecated and will be removed soon. Use import() instead." and "Critical dependency: the request of a dependency is an expression". The warnings themselves are harmless, this is just to prevent them from displaying.

const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const path = require('path');

module.exports = {
    entry: './src/main.ts',
    resolve: {
        extensions: ['.ts', '.js'],
        alias: {
            '@': path.resolve(__dirname, 'src/app/'),
        }
    },
    module: {
        rules: [
            {
                test: /\.ts$/,
                use: ['ts-loader', 'angular2-template-loader']
            },
            {
                test: /\.html$/,
                use: 'html-loader'
            },
            {
                test: /\.less$/,
                use: ['style-loader', 'css-loader', 'less-loader']
            },

            // workaround for warning: System.import() is deprecated and will be removed soon. Use import() instead.
            {
                test: /[\/\\]@angular[\/\\].+\.js$/,
                parser: { system: true }
            }
        ]
    },
    plugins: [
        new HtmlWebpackPlugin({ template: './src/index.html' }),
        new webpack.DefinePlugin({
            // global app config object
            config: JSON.stringify({
                apiUrl: 'http://localhost:4000'
            })
        }),

        // workaround for warning: Critical dependency: the request of a dependency is an expression
        new webpack.ContextReplacementPlugin(
            /\@angular(\\|\/)core(\\|\/)fesm5/,
            path.resolve(__dirname, 'src')
        )
    ],
    optimization: {
        splitChunks: {
            chunks: 'all',
        },
        runtimeChunk: true
    },
    devServer: {
        historyApiFallback: true
    }
}

Angular — How To Proxy To Backend Server

Angular — How To Proxy To Backend Server

In the Angular app, We often talk to backend servers in the development phase, we will explore all the scenarios in this article. Here are the topics we cover.

Explaining how to configure a proxy for backend API calls with an example.

In the Angular app, We often talk to backend servers in the development phase, we will explore all the scenarios in this article. Here are the topics we cover.

  • What is proxying
  • Example Project
  • proxy.config.json options
  • Proxy Setup with Angular CLI
  • Different Ways to configure
  • Rewrite the Path URL
  • Multiple app entries to one API endpoint
  • Multiple app entries with multiple endpoints
  • Summary

What is proxying

In general, A proxy or proxy server serves as a gateway between your app and the internet. It’s an intermediate server between client and servers by forwarding client requests to resources.

In Angular, we often use this proxying in the development environment. Angular uses webpack dev server to serve the app in development mode. If we look at the following diagram, app UI is running on port 4200 and backend server is running on port 3700. All the calls start with **/api **will be redirected to the backend server and rest all fall back to the same port.

In subsequent sections, we will see how we can accomplish this and other options as well.

proxying all URLs start with /api

Example Project

Let’s follow these commands for the example project and you are ready for angular CLI proxy setup.

//clone the project
git clone https://github.com/bbachi/angular-proxy-example
// install dependencies for node server
npm install
//cd to ui
cd appui
// install app ui dependencies
np install

Once you install all the dependencies, you can start both Angular app and node js server on 4200 and 3070 respectively.

You can start the Angular app with these commands npm start or ng serve and here is the Angular app running on 4200.

The angular app runs on 4200

Let’s start the server with this command npm start and test this API on port 3070.

API running on port 3070

proxy.config.json options

target: This is where we need to define the backend URL.

pathRewrite: We need to use this option to edit or rewrite the path

changeOrigin: If your backend API is not running on localhost, we need to make this flag true.

**logLevel: **If you want to check whether proxy configuration working properly or not, this flag should be debug.

bypass: Sometimes we have to bypass the proxy, we can define a function with this. But it should define in proxy.config.js instead of proxy.config.json.

Proxy Setup with Angular CLI

Now app and server running on different ports. Let’s set up a proxy for communication between these.

First thing you need is this proxy.config.json. We are defining the target for all the URLs starts with /api here.

{
"/api/*": {
"target": "http://localhost:3070",
"secure": false,
"logLevel": "debug",
"changeOrigin": true
}
}

Second thing is to let Angular know we have this proxy.config.json in place. We can do that by adding the proxy-config flag while starting the app like below. Once started, we can see the message indicating all the URLs starting with /api will be redirecting to nodejs server running on port 3070.

npm start script

Now we can test the app and see the settings from the server

settings coming from the server

Different Ways to configure

Another way to configure proxy config in the Angular project is defining in angular.json.

proxyConfig in angular.json

Let’s summarize the ways here

  • Add this ng serve — proxy-config proxy.conf.json to the start script in package.json
  • Define in angular.json under serve section like above.

Rewrite the Path URL

Whenever there is a change in the URLs, we often rewrite the path of the backend servers endpoints. We can do that with the pathRewrite.

Let’s understand the pathRewrite option. For instance, our backend URL /api/settings is changed to /api/app/settings and we want to test in development before it goes to production. We can achieve this with the option **pathRewrite **like below.

{
"/api/*": {
"target": "http://localhost:3070",
"secure": false,
"logLevel": "debug",
"changeOrigin": true,
"pathRewrite": {
"^/api/settings": "/api/app/settings",
"^/api": ""
}
}
}

app.get('/api/app/settings', (req,res) => {
console.log('--settings---');
res.json(settings)
})
app.get('/users', (req,res) => {
console.log('--users---');
res.json({users:[]})
})

So, we are rewriting /api/setting to /api/app/settings and /api/users to /users.

Here is the console output while starting the app.

angular proxy rewriting URL paths

Multiple app entries to one API endpoint

Sometimes we have multiple modules with services in our app. We might have a scenario where multiple entries or services will call the same API endpoint.

In that case, we need to define proxy.config.js instead of proxy.config.json. Don’t forget to add that to angular.json.

const PROXY_CONFIG = [
{
context: [
"/userapi",
"/settingsapi",
"/productapi",
],
target: "http://localhost:3070",
secure: false
}
]
module.exports = PROXY_CONFIG;

proxy.config.js

angular.json

Multiple app entries with multiple endpoints

We have seen how to define multiple entries to the same endpoint. Let’s look at multiple entries to multiple endpoints scenario.

proxy for multiple APIs

For instance, we have three APIs running on ports 3700, 3800 and 3900 and your APP should talk to these APIs.

All we need to add multiple entries to the proxy.config.json. Here is the configuration for that setup and we have to make sure all APIs are running on these ports for successful communication.

proxy.config.js

angular.json
Multiple app entries with multiple endpoints
We have seen how to define multiple entries to the same endpoint. Let’s look at multiple entries to multiple endpoints scenario.


proxy for multiple APIs
For instance, we have three APIs running on ports 3700, 3800 and 3900 and your APP should talk to these APIs.

All we need to add multiple entries to the proxy.config.json. Here is the configuration for that setup and we have to make sure all APIs are running on these ports for successful communication.

Summary

  • In Angular, the proxy is used mostly in the development environment to facilitate the communication between server and UI.
  • We need to have a backend server and UI running on different ports.
  • Proxy.config.json is the file which holds all the information regarding backend API URLs.
  • We need to make sure the Angular App and Backends are running on different ports for successful communication.
  • There are two ways to configure one is to add in the angular.json and another is adding a proxy-config flag to the start script.
  • We can rewrite the path with the option pathRewrite.
  • We can proxy multiple entries to one backend API with the proxy.config.js.
  • We can proxy multiple entries to multiple backends as well.