Routing and navigation with Angular 9

07/03/20 dannyVersion Française

What are we going to do ?

Our goal is to integrate Routing into a Web Application.
We will use the Angular version 9.0.5 javascript framework.

This is Step 2 of our Angular guide which will allow us to obtain a PWA type Web Application.
We will use a basic project generated simply with Angular CLI, the tool advocated by angular.

All created sources are indicated at the end of the tutorial.

The application is at the following address


How to do it ?

The summary of what we are going to do

  • Before starting
    What is routing.
     
  • Project structure
    Before integrating our modifications, we must choose a structure.
     
  • Initialization of the project
    Using angular Cli
     
  • Perform the tests
    Unit tests and end tot end tests.
     
  • Source code
    The complete code of the project on github.

Before you start

A website is made up of a multitude of pages
These pages are written with HTML language.
HTML means HyperText Markup Language.

Hypertext is the technology that will link a page to other pages via hyperlinks.

Routing is the mechanism for navigating from one page to another on a website.

For example, if you type these two urls in your browser.


Depending on the movie name indicated in the url, the Wikipedia web application will determine the processing to be performed.
This treatment will display the web page corresponding to the requested movie (here Braveheart or
Dances_with_Wolves).

This is called Routing.

This operating principle is omnipresent in the use of a website, we need to apply as soon as possible in a project.

We will implement this mechanism in our Angular project.


Creation of the Angular project

To be able to continue this tutorial we must have some elements

  • Node.js : Javascript plateform
  • Git : Le logiciel de gestion de versions. 
  • Angular CLI : L'outil fourni par Angular.
  • Visual Studio code : Un éditeur de code.

You can consult the following tutorial that explains in detail how to do


Nous allons utiliser un projet existant dont les caractéristiques sont

  • Genéré avec Angular CLI
# Créez un répertoire demo (le nom est ici arbitraire)
mkdir demo

# Allez dans ce répertoire
cd demo

# Récupérez le code source sur votre poste de travail
git clone https://github.com/ganatan/angular-example-starter

# Allez dans le répertoire qui a été créé
cd angular-example-starter

# Exécutez l'installation des dépendances (ou librairies)
npm install

# Exécutez le programme
npm run start

# Vérifiez son fonctionnement en lançant dans votre navigateur la commande
http://localhost:4200/

Structure

The question of the structure of our project arises very quickly.
This structure will condition the modifications that we will make as the project develops.

Angular's advice is as follows
https://angular.io/guide/architecture

https://angular.io/guide/file-structure

When creating the project with Angular CLI the default structure is as follows

|-- e2e/
|-- src/
    |-- app
    |-- assets
    |-- environnement
package.json

We will follow this way of proceeding by imagining the elements which could constitute our project.

|-- e2e/
|-- src/
    |-- app
        |-- components
        |-- directives
        |-- mocks
        |-- modules
        |-- pipes
        |-- services
    |-- assets
    |-- environnement
package.json

As we will see in the lazy loading tutorial, an Angular project is based on the modules.
This important part will be constructed as follows.

The choice we made is arbitrary, your structure can take any other form.
This choice will nevertheless be followed in the following tutorials.

  • modules/general will bring together the elements found in all projects.
  • modules/application will group the application-specific elements.

To create a new project it will be enough to copy the basic project and to adapt modules / application to our needs.

|-- app
    |-- components
    |-- directives
    |-- mocks
    |-- modules
        |-- general   (contains the elements found in all project types)
            |-- not-found
            |-- home
            |-- contact
            |-- login
            |-- signin
        |-- application  (contains the specific elements to our project)
            |-- item01
            |-- item02
            |-- ....
            |-- itemXX
     |-- pipes
     |-- services
|-- assets
|-- environnement

Initialization

Angular offers an angular@router library that allows you to manage the routing.

Throughout this tutorial we will try to use the commands offered by angular-cli to automatically generate the necessary source code.


First we will create four components that will be used in the routing.

  • Home
  • Contact
  • About
  • Signin
  • NotFound

Just run the following commands.

ng g correspond to ng generate

# Creating new components
ng generate component modules/general/home --module=app
ng generate component modules/general/contact --module=app
ng generate component modules/general/about --module=app
ng generate component modules/general/signin --module=app
ng generate component modules/general/not-found --module=app

# Creating new components (method 2)
ng g c modules/general/home --module=app
ng g c modules/general/contact --module=app
ng g c modules/general/about --module=app
ng g c modules/general/signin --module=app
ng g c modules/general/not-found --module=app

The modules and modules/general directories are created by the executed command.
All files related to each component are automatically created by angular CLI.

For example for the component Home 4 files are created

  • home.component.css    (CSS code dedicated to design)
  • home.component.html   (HTML code )
  • home.component.spec.ts  (component unit test code)
  • home.component.ts    (logical part in typescript of our component)


The app.module.ts file is automatically modified as follows.

The 5 components are imported into the module app.module.ts
Then are used in the decorator @ngModule at the object level metadata declarations.
 

src/app/app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppComponent } from './app.component';
import { HomeComponent } from './modules/general/home/home.component';
import { ContactComponent } from './modules/general/contact/contact.component';
import { AboutComponent } from './modules/general/about/about.component';
import { SigninComponent } from './modules/general/signin/signin.component';
import { NotFoundComponent } from './modules/general/not-found/not-found.component';

@NgModule({
  declarations: [
    AppComponent,
    HomeComponent,
    ContactComponent,
    AboutComponent,
    SigninComponent,
    NotFoundComponent
  ],
  imports: [
    BrowserModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

We will now follow the following tips from Angular https://angular.io/tutorial/toh-pt5

The best practice is to load and configure the router in a routing module.
This module will be imported into the App module (appModule).
By convention the module will be named AppRoutingModule in the file app-routing.module.ts and located in the directory src/app.

We will create

  • app-routing.module.ts

For that we will use the following command angular-cli

# Creation of the routing
ng generate module app-routing --flat --module=app

# Creation of the routing (method 2)
ng g m app-routing --flat --module=app

Then you must modify the following files

  • app-routing.module.ts
  • styles.css
  • app.component.html
  • app.component.css
  • app.component.ts
  • app.component.spec.ts
  • app.e2e-spec.ts
  • app.po.ts
     

This will handle the desired routing and called components.

In the app-routing.module.ts file

  • Import the Routes and RouterModule modules
  • Configure Routes
  • call the forRoot method of RouterModule
src/app/app-routing.module.ts
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

import { AboutComponent } from './modules/general/about/about.component';
import { ContactComponent } from './modules/general/contact/contact.component';
import { HomeComponent } from './modules/general/home/home.component';
import { SigninComponent } from './modules/general/signin/signin.component';
import { NotFoundComponent } from './modules/general/not-found/not-found.component';

const routes: Routes = [
  { path: '', component: HomeComponent, },
  { path: 'contact', component: ContactComponent },
  { path: 'about', component: AboutComponent },
  { path: 'signin', component: SigninComponent },
  { path: '**', component: NotFoundComponent }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule],
  declarations: []
})
export class AppRoutingModule { }
src/styles.css
body {
  color: black;
  font-weight: 400;
}

In the AppComponent component we will take care to add the <router-outlet> element to the file app.component.html.
It will tell the router where to display the routed graphics.

The routerLink element will create the link to the desired pages.

src/app/app.component.html
<div class="app">
  <header>
    <section>
      <h1>{{ title }}</h1>
      <p> {{ version }}</p>
    </section>
    <nav>
      <ul>
        <li><a routerLink="/">Home</a></li>
        <li><a routerLink="/about">About</a></li>
        <li><a routerLink="/contact">Contact</a></li>
        <li><a routerLink="/signin">Signin</a></li>
      </ul>
    </nav>
  </header>

  <main>
    <router-outlet></router-outlet>
  </main>

  <footer>
    <a href="https://www.ganatan.com/">&copy; 2020 - www.ganatan.com</a>
  </footer>

</div>
src/app.component.css
h1 {
  color: blue;
}

.app {
  font-family: Arial, Helvetica, sans-serif;
  max-width: 500px;
  margin: auto;
}

src/app.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'angular-starter';
  version = 'Angular version 9.0.5';
}

We need to add the RouterTestingModule module in the unit tests of the App component.
This addition is done at the corresponding test file app.component.spec.ts.

We will modify the end to end tests to adapt to changes in the app.component.html file
Changes that will be made in the app.e2e-spec.ts and  app.po.ts files.

src/app/app.component.spec.ts
import { TestBed, async } from '@angular/core/testing';
import { AppComponent } from './app.component';
import { RouterTestingModule } from '@angular/router/testing';

describe('AppComponent', () => {
  beforeEach(async(() => {
    TestBed.configureTestingModule({
      imports: [
        RouterTestingModule
      ],
      declarations: [
        AppComponent
      ],
    }).compileComponents();
  }));

  it('should create the app', () => {
    const fixture = TestBed.createComponent(AppComponent);
    const app = fixture.componentInstance;
    expect(app).toBeTruthy();
  });

  it(`should have as title 'angular-starter'`, () => {
    const fixture = TestBed.createComponent(AppComponent);
    const app = fixture.componentInstance;
    expect(app.title).toEqual('angular-starter');
  });

  it('should render title', () => {
    const fixture = TestBed.createComponent(AppComponent);
    fixture.detectChanges();
    const compiled = fixture.nativeElement;
    expect(compiled.querySelector('h1').textContent).toContain('angular-starter');
  });
});
e2e/src/app.e2e-spec.ts
import { AppPage } from './app.po';
import { browser, logging } from 'protractor';

describe('workspace-project App', () => {
  let page: AppPage;

  beforeEach(() => {
    page = new AppPage();
  });

  it('should display welcome message', () => {
    page.navigateTo();
    expect(page.getTitleText()).toEqual('angular-starter');
  });

  afterEach(async () => {
    // Assert that there are no errors emitted from the browser
    const logs = await browser.manage().logs().get(logging.Type.BROWSER);
    expect(logs).not.toContain(jasmine.objectContaining({
      level: logging.Level.SEVERE,
    } as logging.Entry));
  });
});
e2e/src/app.po.ts
import { browser, by, element } from 'protractor';

export class AppPage {
  navigateTo(): Promise<unknown> {
    return browser.get(browser.baseUrl) as Promise<unknown>;
  }

  getTitleText(): Promise<string> {
    return element(by.css('app-root h1')).getText() as Promise<string>;
  }
}

Tests

It remains only to test the following scripts.

Note
Source code tests (npm run lint) can sometimes indicate errors.
Just click on the error link and modify the file and start the test again.

# Development
npm run start
http://localhost:4200/

# Test of the not-found page
http://localhost:4200/link-unknown

# Tests
npm run lint
npm run test
npm run e2e

# Production
npm run build