Angular Routing Part 1: Setting up our application

Even though an Angular application is a Single Page Application (SPA), and all your application’s functions exist in a single HTML page, we still need routing to navigate through our application. In the next series of posts, I will build a book tracking application to show the basics and some more advanced features of Angular routing. In this first post, I’m building the different components and services the application needs.

Creating the application

To create a new Angular application with the Angular CLI, we use the following command.

ng new angular-routing

You will get prompted for a few things. For now, don’t add Angular routing because we will add it manually later to see what is going on. Make sure you select scss when asked for the stylesheet format you would like to use.

After the application is created, we add Angular Material to our project. You can choose whatever prebuilt theme you like when asked about it.

ng add @angular/material

We also add bootstrap to the application.

npm install bootstrap

To use the bootstrap css classes, you need to add them to the styles-section in angular.json.

"styles": [
   "./node_modules/bootstrap/dist/css/bootstrap.min.css",
   "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css",
   "src/styles.scss"
],

Now we are ready to start building our components.

Home and page not found component

We first create a new folder “pages” in the app-folder. In here we will group our different pages that don’t need data from a server or other component. In this folder, we create a new component with the Angular CLI.

ng generate component home

Change the home.component.html to the following and add an image of books in a folder “images” in the assets-folder.

<h1>Welcome to Book Tracker.</h1>
<img src="assets/images/library.jpg" width="100%"/> 

Next, generate the page not found component in the pages-folder.

ng generate component pageNotFound

And change the page-not-found.component.html to some text that says that the page doesn’t exist.

<div class="container pt-5">
    <div class="row justify-content-center">
        <div class="col-md-6">
            <div class="clearfix">
                <h1>404</h1>
                <h4 class="pt-3">Not found</h4>
                <p class="text-muted">The page you are looking for is not found.</p>
            </div>
            <button mat-raised-button color="primary">Go back to home page</button>
        </div>
    </div>
</div>

For both components, you can leave the typescript files like they are.

For now, change the app.component.html to the following. We will change this later when we add routing to the application. What we are doing here, is displaying the home component in the root component of the application.

<div class="container">
  <app-home></app-home>
</div>

When you now run the application, you will see the home page.

The books components

In the app-folder, we create a new folder ‘books’. For our application, we need a component to get an overview of all our books, a component to add a new book to our list and a component to edit a book.

In-memory database

Before we will create these components, we will create a service and an in-memory database to get the data in our views. In a real-world application, you will get the data from a backend, but because this series of posts will be about routing in Angular, I created an in-memory DB instead of setting up a backend service with database connection.

We create a shared folder in the app folder. In this folder, I put all the models we need in the application and components that we shared across all modules. Here, I will also create the in-memory DB.

For our in-memory database, we need a npm-package ‘angular-in-memory-web-api’. So let’s install this first:

npm install angular-in-memory-web-api

In the shared folder, first, create a new folder ‘models’ and in here we create a file ‘book.model.ts’.

export interface Book {
    id: number;
    title: string;
    authors: string[];
    description: string;
    publishDate: Date;
    publisher: string;
    startReadingDate?: Date;
    readDate?: Date;
    rating?: number;
}

Next, create a folder ‘book-data’ in the shared folder. In this folder, we create a file books.ts. In this file, we create our in-memory database and add some books to the database.

import { InMemoryDbService } from 'angular-in-memory-web-api';
import { Book } from '../models/book.model';

export class InMemBooksService implements InMemoryDbService {
    createDb() {
        let books: Book[] = [
            {
                id: 1,
                title: 'Invisible women. Exposing data bias in a world designed for men',
                authors: ['Caroline Criado Perez'],
                description: 'Imagine a world where your phone is too big for your hand, where your doctor prescribes a drug that is wrong for your body, where in a car accident you are 47% more likely to be seriously injured, where every week the countless hours of work you do are not recognised or valued. If any of this sounds familiar, chances are that you\'re a woman. Invisible Women shows us how, in a world largely built for and by men, we are systematically ignoring half the population. It exposes the gender data gap – a gap in our knowledge that is at the root of perpetual, systemic discrimination against women, and that has created a pervasive but invisible bias with a profound effect on women’s lives. Award-winning campaigner and writer Caroline Criado Perez brings together for the first time an impressive range of case studies, stories and new research from across the world that illustrate the hidden ways in which women are forgotten, and the impact this has on their health and well-being. From government policy and medical research, to technology, workplaces, urban planning and the media, Invisible Women reveals the biased data that excludes women. In making the case for change, this powerful and provocative book will make you see the world anew.',
                publishDate: new Date(2019, 3, 5),
                publisher: 'Vintage',
                startReadingDate: new Date(2020, 4, 27),
                readDate: new Date(2020, 4, 29),
                rating: 5
            },
            {
                id: 2,
                title: 'Atomic Habits: An Easy & Proven Way to Build Good Habits & Break Bad Ones',
                authors: ['James Clear'],
                description: 'No matter your goals, Atomic Habits offers a proven framework for improving--every day. James Clear, one of the world\'s leading experts on habit formation, reveals practical strategies that will teach you exactly how to form good habits, break bad ones, and master the tiny behaviors that lead to remarkable results. If you\'re having trouble changing your habits, the problem isn\'t you. The problem is your system. Bad habits repeat themselves again and again not because you don\'t want to change, but because you have the wrong system for change. You do not rise to the level of your goals. You fall to the level of your systems. Here, you\'ll get a proven system that can take you to new heights. Clear is known for his ability to distill complex topics into simple behaviors that can be easily applied to daily life and work. Here, he draws on the most proven ideas from biology, psychology, and neuroscience to create an easy-to-understand guide for making good habits inevitable and bad habits impossible. Along the way, readers will be inspired and entertained with true stories from Olympic gold medalists, award-winning artists, business leaders, life-saving physicians, and star comedians who have used the science of small habits to master their craft and vault to the top of their field. Learn how to make time for new habits (even when life gets crazy), overcome a lack of motivation and willpower, design your environment to make success easier, get back on track when you fall off course and much more. Atomic Habits will reshape the way you think about progress and success, and give you the tools and strategies you need to transform your habits--whether you are a team looking to win a championship, an organization hoping to redefine an industry, or simply an individual who wishes to quit smoking, lose weight, reduce stress, or achieve any other goal.',
                publishDate: new Date(2018, 10, 16),
                publisher: 'Avery',
                startReadingDate: new Date(2020, 3, 27),
                readDate: new Date(2020, 3, 29),
                rating: 4
            },
            {
                id: 3,
                title: 'Why We Sleep: Unlocking the Power of Sleep and Dreams',
                authors: ['Matthew Walker'],
                description: 'The first sleep book by a leading scientific expert—Professor Matthew Walker, Director of UC Berkeley’s Sleep and Neuroimaging Lab—reveals his groundbreaking exploration of sleep, explaining how we can harness its transformative power to change our lives for the better. Sleep is one of the most important but least understood aspects of our life, wellness, and longevity. Until very recently, science had no answer to the question of why we sleep, or what good it served, or why we suffer such devastating health consequences when we don\'t sleep. Compared to the other basic drives in life—eating, drinking, and reproducing—the purpose of sleep remained elusive. An explosion of scientific discoveries in the last twenty years has shed new light on this fundamental aspect of our lives. Now, preeminent neuroscientist and sleep expert Matthew Walker gives us a new understanding of the vital importance of sleep and dreaming. Within the brain, sleep enriches our ability to learn, memorize, and make logical decisions. It recalibrates our emotions, restocks our immune system, fine-tunes our metabolism, and regulates our appetite. Dreaming mollifies painful memories and creates a virtual reality space in which the brain melds past and present knowledge to inspire creativity. Walker answers important questions about sleep: how do caffeine and alcohol affect sleep? What really happens during REM sleep? Why do our sleep patterns change across a lifetime? How do common sleep aids affect us and can they do long-term damage? Charting cutting-edge scientific breakthroughs, and synthesizing decades of research and clinical practice, Walker explains how we can harness sleep to improve learning, mood, and energy levels; regulate hormones; prevent cancer, Alzheimer’s, and diabetes; slow the effects of aging; increase longevity; enhance the education and lifespan of our children, and boost the efficiency, success, and productivity of our businesses. Clear-eyed, fascinating, and accessible, Why We Sleep is a crucial and illuminating book.',
                publishDate: new Date(2017, 10, 3),
                publisher: 'Scribner',
                startReadingDate: new Date(2020, 4, 10),
                readDate: new Date(2020, 4, 27),
                rating: 3
            },
            {
                id: 4,
                title: 'Sapiens: A Brief History of Humankind',
                authors: ['Yuval Noah Harari'],
                description: '100,000 years ago, at least six human species inhabited the earth. Today there is just one. Us. Homo sapiens. How did our species succeed in the battle for dominance? Why did our foraging ancestors come together to create cities and kingdoms? How did we come to believe in gods, nations and human rights; to trust money, books and laws; and to be enslaved by bureaucracy, timetables and consumerism? And what will our world be like in the millennia to come? In Sapiens, Dr Yuval Noah Harari spans the whole of human history, from the very first humans to walk the earth to the radical – and sometimes devastating – breakthroughs of the Cognitive, Agricultural and Scientific Revolutions. Drawing on insights from biology, anthropology, paleontology and economics, he explores how the currents of history have shaped our human societies, the animals and plants around us, and even our personalities. Have we become happier as history has unfolded? Can we ever free our behaviour from the heritage of our ancestors? And what, if anything, can we do to influence the course of the centuries to come? Bold, wide-ranging and provocative, Sapiens challenges everything we thought we knew about being human: our thoughts, our actions, our power ... and our future. ',
                publishDate: new Date(2011, 1, 1),
                publisher: 'Harvill Secker',
                startReadingDate: new Date(2019, 10, 1),
                readDate: new Date(2019, 10, 29),
                rating: 3
            },
            {
                id: 5,
                title: 'Homo Deus: A History of Tomorrow',
                authors: ['Yuval Noah Harari'],
                description: 'Yuval Noah Harari, author of the critically-acclaimed New York Times bestseller and international phenomenon Sapiens, returns with an equally original, compelling, and provocative book, turning his focus toward humanity’s future, and our quest to upgrade humans into gods. Over the past century humankind has managed to do the impossible and rein in famine, plague, and war. This may seem hard to accept, but, as Harari explains in his trademark style—thorough, yet riveting—famine, plague and war have been transformed from incomprehensible and uncontrollable forces of nature into manageable challenges. For the first time ever, more people die from eating too much than from eating too little; more people die from old age than from infectious diseases; and more people commit suicide than are killed by soldiers, terrorists and criminals put together. The average American is a thousand times more likely to die from binging at McDonalds than from being blown up by Al Qaeda. What then will replace famine, plague, and war at the top of the human agenda? As the self-made gods of planet earth, what destinies will we set ourselves, and which quests will we undertake? Homo Deus explores the projects, dreams and nightmares that will shape the twenty-first century—from overcoming death to creating artificial life. It asks the fundamental questions: Where do we go from here? And how will we protect this fragile world from our own destructive powers? This is the next stage of evolution. This is Homo Deus. With the same insight and clarity that made Sapiens an international hit and a New York Times bestseller, Harari maps out our future.',
                publishDate: new Date(2017, 2, 21),
                publisher: 'Harper',
                startReadingDate: new Date(2019, 11, 21),
                readDate: new Date(2019, 12, 14),
                rating: 4
            },
            {
                id: 6,
                title: 'So You Want to Talk About Race',
                authors: ['Ijeoma Oluo'],
                description: 'In this breakout book, Ijeoma Oluo explores the complex reality of today\'s racial landscape--from white privilege and police brutality to systemic discrimination and the Black Lives Matter movement--offering straightforward clarity that readers need to contribute to the dismantling of the racial divide. In So You Want to Talk About Race, Editor at Large of The Establishment Ijeoma Oluo offers a contemporary, accessible take on the racial landscape in America, addressing head-on such issues as privilege, police brutality, intersectionality, micro-aggressions, the Black Lives Matter movement, and the "N" word. Perfectly positioned to bridge the gap between people of color and white Americans struggling with race complexities, Oluo answers the questions readers don\'t dare ask, and explains the concepts that continue to elude everyday Americans. Oluo is an exceptional writer with a rare ability to be straightforward, funny, and effective in her coverage of sensitive, hyper-charged issues in America. Her messages are passionate but finely tuned, and crystalize ideas that would otherwise be vague by empowering them with aha-moment clarity. Her writing brings to mind voices like Ta-Nehisi Coates and Roxane Gay, and Jessica Valenti in Full Frontal Feminism, and a young Gloria Naylor, particularly in Naylor\'s seminal essay "The Meaning of a Word."',
                publishDate: new Date(2018, 1, 16),
                publisher: 'Seal Press',
                startReadingDate: null,
                readDate: null,
                rating: null
            }
        ];

        return { books };
    }
}

To use this in-memory database, we need to register it in app.module.ts. We add a delay to simulate the request to a backend service.

...
 imports: [
   ...
    HttpClientInMemoryWebApiModule.forRoot(InMemBooksService, { delay: 1000 })
  ],
...

Now that we have our database, we can start creating our components.

Books components

In the folder books under app, we first create the component for the overview of the books.

ng generate component books

This component is automatically added to the declarations of the AppModule.

Change the books.component.ts to the following:

import { Component, OnInit, ViewChild } from '@angular/core';
import { MatTableDataSource } from '@angular/material/table';
import { MatPaginator } from '@angular/material/paginator';
import { MatSort } from '@angular/material/sort';
import { BookService } from './services/book.service';
import { Book } from '../shared/models/book.model';

@Component({
  selector: 'app-books',
  templateUrl: './books.component.html',
  styleUrls: ['./books.component.scss']
})
export class BooksComponent implements OnInit {
  public books: Book[];
  public displayedColumns: string[] = ['title', 'author', 'rating', 'actions'];
  public dataSource: MatTableDataSource<Book>;

  @ViewChild(MatPaginator, { static: true }) paginator: MatPaginator;
  @ViewChild(MatSort, { static: true }) sort: MatSort;

  constructor(private bookService: BookService) { }

  public ngOnInit(): void {
    this.bookService.getBooks()
      .subscribe(books => {
        this.books = books;
        this.dataSource = new MatTableDataSource(this.books);
        this.dataSource.paginator = this.paginator;
        this.dataSource.sort = this.sort;
      });
  }

  public applyFilter(event: Event) {
    const filterValue = (event.target as HTMLInputElement).value;
    this.dataSource.filter = filterValue.trim().toLowerCase();

    if (this.dataSource.paginator) {
      this.dataSource.paginator.firstPage();
    }
  }
}

As you can see in this code, we need a service to get the books from the in-memory database. We create this service in a folder services inside the books folder. In this service, we also already add the code to create, update and delete a book.

ng generate service book
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { HttpClient } from '@angular/common/http';
import { environment } from 'src/environments/environment';
import { Book } from 'src/app/shared/models/book.model';

@Injectable({
  providedIn: 'root'
})
export class BookService {
  public apiUrl = environment.booksApiUrl;

  constructor(private http: HttpClient) { }

  public getBooks(): Observable<Book[]> {
    return this.http.get<Book[]>(this.apiUrl);
  }
  
  public getBook(id: number): Observable<Book> {
    return this.http.get<Book>(`${this.apiUrl}/${id}`);
  }

  public createBook(book: Book): Observable<Book> {
    return this.http.post<Book>(`${this.apiUrl}`, book);
  }

  public updateBook(book: Book): Observable<Book> {
    return this.http.put<Book>(`${this.apiUrl}`, book);
  }

  public deleteBook(id: number): Observable<{}> {
    return this.http.delete<Book>(`${this.apiUrl}/${id}`);
  }
}

Next, we change the code from books.component.html to show the data.

<h1>Your Book List</h1>
<div class="row">
    <mat-form-field class="col-md-6">
        <mat-label>Filter books</mat-label>
        <input matInput (keyup)="applyFilter($event)">
    </mat-form-field>
</div>

<div class="mat-elevation-z8">
    <table mat-table [dataSource]="dataSource" matSort>
        <ng-container matColumnDef="title">
            <th mat-header-cell *matHeaderCellDef mat-sort-header>Title</th>
            <td mat-cell *matCellDef="let book">
                <a>{{book.title}}</a>
            </td>
        </ng-container>

        <ng-container matColumnDef="author">
            <th mat-header-cell *matHeaderCellDef mat-sort-header>Author(s)</th>
            <td mat-cell *matCellDef="let book">
                <span *ngFor="let author of book.authors; let isLast=last">
                    {{author}}{{isLast ? '' :', '}}&nbsp;
                </span>
            </td>
        </ng-container>

        <ng-container matColumnDef="rating">
            <th mat-header-cell *matHeaderCellDef mat-sort-header>Your rating</th>
            <td mat-cell *matCellDef="let book">
                <app-star-rating [rating]="book.rating"></app-star-rating>
                </td>
        </ng-container>

        <ng-container matColumnDef="actions">
            <th mat-header-cell *matHeaderCellDef mat-sort-header></th>
            <td mat-cell *matCellDef="let book">
                <button mat-icon-button class="editIcon">
                    <mat-icon>edit</mat-icon>
                </button>
            </td>
        </ng-container>

        <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
        <tr mat-row *matRowDef="let row; columns: displayedColumns;">
        </tr>
    </table>

    <mat-paginator [pageSizeOptions]="[5, 10, 15, 25]"></mat-paginator>
</div>

Book-detail and book-edit components

Just, like the books component, we add 2 new components in the books folder for the book and book-edit components.

ng generate component book-detail
ng generate component book-edit

And we change the code of the typescript and HTML-files to the following:

book-detail.component.ts

import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { Book } from 'src/app/shared/models/book.model';
import { BookService } from '../services/book.service';

@Component({
  selector: 'app-book-detail',
  templateUrl: './book-detail.component.html',
  styleUrls: ['./book-detail.component.scss']
})
export class BookDetailComponent implements OnInit {
  public book$: Observable<Book>;

  constructor(
    private bookService: BookService) { }

  ngOnInit(): void {
    this.book$ = this.bookService.getBook(1);
  }
}
book-detail.component.html

<mat-card *ngIf="book$ | async as book">
    <mat-card-header>
        <mat-card-title>{{book.title}}</mat-card-title>
    </mat-card-header>
    <mat-card-content>
            <div class="row">
                <div class="mat-subheading-2 col-md-2">Author(s)</div>
                <div class="col-md-10">
                    <span *ngFor="let author of book.authors; let isLast=last">
                        {{author}}{{isLast ? '' :', '}}&nbsp;
                    </span>
                </div> 
            </div>
            <div class="row">
                <div class="mat-subheading-2 col-md-2">Description</div>
                <div class='col-md-10'>{{book.description}}</div>
            </div>

            <mat-divider class="my-3" [inset]="true"></mat-divider>

            <div class="row">
                <div class="mat-subheading-2 col-md-2">Publication details</div>
                <div class="col-md-10">
                    <div> Publish date: {{book.publishDate | date: "dd/MM/yyyy"}}</div>
                    <div> Publisher: {{book.publisher}}</div>
                </div>
            </div>

            <mat-divider class="my-3" [inset]="true"></mat-divider>

            <div class="row">
                <div class="mat-subheading-2 col-md-2">Reading details</div>
                <div class="col-md-10">
                    <div> Start Reading Date: {{book.startReadingDate | date: "dd/MM/yyyy"}}</div>
                    <div> Read date: {{book.readDate | date: "dd/MM/yyyy" }}</div>
                    <div> Rating: <app-star-rating [rating]="book.rating"></app-star-rating> </div>
                </div>
            </div>
    </mat-card-content>
    <mat-card-actions>
        <button color="primary" mat-button >Edit</button>
        <button color="accent" mat-button>Back to book list</button>
    </mat-card-actions>
</mat-card>
book-edit.component.ts

import { Component, OnInit } from '@angular/core';
import { Book } from 'src/app/shared/models/book.model';
import { BookService } from '../services/book.service';

@Component({
  selector: 'app-book-edit',
  templateUrl: './book-edit.component.html',
  styleUrls: ['./book-edit.component.scss']
})
export class BookEditComponent implements OnInit {
  public book: Book;
  public author: string;
  public authorRequired = true;
  public pageTitle: string = 'Edit book';

  public errorMessage: string = '';

  constructor(
    private bookService: BookService) { }

  public ngOnInit(): void {
    this.bookService.getBook(1)
    .subscribe(book => {
      this.book = book;
      this.book.authors.length !== 0 ? this.authorRequired = false : this.authorRequired = true;
    });
  }

  public addAuthor(): void {
    if (this.author) {
      this.book.authors.push(this.author);
      this.authorRequired = false;
    }
    this.author = null;
  }

  public removeAuthor(author: string): void {
    const index = this.book.authors.indexOf(author);

    if (index >= 0) {
      this.book.authors.splice(index, 1);
      this.book.authors.length !== 0 ? this.authorRequired = false : this.authorRequired = true;
    }
  }

  public onRatingChanged($event: number): void {
    this.book.rating = $event;
  }
}
book-edit.component.html

<mat-card>
    <mat-card-header>
        <mat-card-title>{{pageTitle}}</mat-card-title>
    </mat-card-header>
    <mat-card-content>
        <form>
            <div class="row">
                <mat-form-field class="col">
                    <mat-label>Title</mat-label>
                    <input matInput type="text" required [(ngModel)]="book.title" name="title">
                </mat-form-field>
            </div>

            <div class="row">
                <mat-form-field class="col-8">
                    <mat-label>Add author</mat-label>
                    <input matInput type="text" [required]="authorRequired" name="author" [(ngModel)]="author">
                </mat-form-field>
                <button mat-button class="col-2 bnt-small" color="primary" (click)="addAuthor()">Add author</button>
                <mat-chip-list #chipList aria-label="Authors" class="col-12 mb-2">
                    <mat-chip *ngFor="let author of book.authors" removable (removed)="removeAuthor(author)">
                        {{author}}
                        <mat-icon matChipRemove>cancel</mat-icon>
                    </mat-chip>
                </mat-chip-list>
            </div>

            <div class="row">
                <mat-form-field class="col">
                    <mat-label>Description</mat-label>
                    <textarea matInput type="text" matTextareaAutosize required [(ngModel)]="book.description"
                        name="description"></textarea>
                </mat-form-field>
            </div>

            <div class="row">
                <mat-form-field class="col-4">
                    <mat-label>Publish Date</mat-label>
                    <input matInput [matDatepicker]="publishDate" required [(ngModel)]="book.publishDate" name="publishDate">
                    <mat-datepicker-toggle matSuffix [for]="publishDate"></mat-datepicker-toggle>
                    <mat-datepicker #publishDate></mat-datepicker>
                </mat-form-field>
                <mat-form-field class="col-8">
                    <mat-label>Publisher</mat-label>
                    <input matInput type="text" required [(ngModel)]="book.publisher" name="publisher">
                </mat-form-field>
            </div>

            <div class="row">
                <mat-form-field class="col-4">
                    <mat-label>Start Reading Date</mat-label>
                    <input matInput [matDatepicker]="startReadingDate" [(ngModel)]="book.startReadingDate" name="startReadingDate">
                    <mat-datepicker-toggle matSuffix [for]="startReadingDate"></mat-datepicker-toggle>
                    <mat-datepicker #startReadingDate></mat-datepicker>
                </mat-form-field>
                <mat-form-field class="col-4">
                    <mat-label>Read Date</mat-label>
                    <input matInput [matDatepicker]="readDate" [(ngModel)]="book.readDate" name="readDate">
                    <mat-datepicker-toggle matSuffix [for]="readDate"></mat-datepicker-toggle>
                    <mat-datepicker #readDate></mat-datepicker>
                </mat-form-field>
                <div class="col-4">
                    <mat-label>Rating   </mat-label>
                    <app-star-rating [rating]="book.rating" (ratingUpdated)="onRatingChanged($event)"></app-star-rating>
                </div>
            </div>

        </form>
    </mat-card-content>
    <mat-card-actions>
        <button color="primary" mat-raised-button>Save</button>
        <button color="warn" mat-button>Delete</button>
        <button color="accent" mat-button>Cancel</button>
    </mat-card-actions>
</mat-card>

In this code, you can see that I used an app-star-rating selector. Because I used the code for the star rating in multiple components, I created a custom component for the star rating in the shared folder.

star-rating.component.ts

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

@Component({
  selector: 'app-star-rating',
  templateUrl: './star-rating.component.html',
  styleUrls: ['./star-rating.component.scss']
})
export class StarRatingComponent implements OnInit {
  @Input('rating') private rating: number;
  @Output() private ratingUpdated: EventEmitter<number> = new EventEmitter<number>();

  public ratingStars = [];

  constructor() { }

  public ngOnInit(): void {
    for (let index = 0; index < 5; index++) {
      this.ratingStars.push(index);
    }
  }

  public onClick(rating:number): void {
    this.ratingUpdated.emit(rating);
  }

  public showIcon(index:number): string {
    if (this.rating >= index + 1) {
      return 'star';
    } else {
      return 'star_border';
    }
  }
}
star-rating.component.html

<button mat-icon-button color="accent" *ngFor="let ratingId of ratingStars;index as i" [id]="'star_'+i" (click)="onClick(i+1)" [matTooltip]="ratingId+1" matTooltipPosition="above">
  <mat-icon>
    {{showIcon(i)}}
  </mat-icon>
</button>

Navigation toolbar

In our components folder in the shared folder, we create the navigation toolbar.

ng generate component navbar
navbar.component.ts

import { Component, OnInit } from '@angular/core';
import { RouteInfo } from '../../models/route-info.model';

export const HOME_ROUTE: RouteInfo = { path: '/home', title: 'Home'};
export const BOOKS_ROUTE: RouteInfo = { path: '/books', title: 'Your book list'};
export const NEW_BOOK_ROUTE: RouteInfo = { path: '/books/new/edit', title: 'Add new book'};

@Component({
  selector: 'app-navbar',
  templateUrl: './navbar.component.html',
  styleUrls: ['./navbar.component.scss']
})
export class NavbarComponent implements OnInit {
  public title = 'Book Tracker';
  public menuItems: RouteInfo[];

  constructor() { }

  public ngOnInit(): void {
    this.setMenuItems();
  }

  private setMenuItems(): void {
    this.menuItems = new Array<RouteInfo>();
    this.menuItems.push(HOME_ROUTE);
    this.menuItems.push(BOOKS_ROUTE);
    this.menuItems.push(NEW_BOOK_ROUTE);
  }
}
navbar.component.html

<mat-toolbar color="primary">
    <mat-toolbar-row>
        <h1 class="title">{{title | uppercase }}</h1>
        <a *ngFor="let item of menuItems">{{item.title}}</a>
    </mat-toolbar-row>
</mat-toolbar>

And we add some styling to our navbar.

mat-toolbar-row {
    padding: 0px 25px;
}

a {
    padding: 0 10px;
    font-size: large;
    cursor: pointer;
    text-decoration: none;
    color: #fff;
}

We create a model RouteInfo in our models folder.

export interface RouteInfo {
    path: string;
    title: string;
}

To display the navigation bar, we change the code in app.component.html

<app-navbar></app-navbar>
<div class="container">
  <app-home></app-home>
</div>

Okay, now we have all our components, and when we start our application we see our home-page with the navbar.

But… clicking in the navigation bar does … nothing. So how do we navigate from one component to another? Let’s explore that in the next post.

You can find this code on my GitHub.

Lindsey is a .NET Consultant at eMenKa NV where she is focusing on web development. She is a crew-member of Techorama Belgium and the Netherlands and she runs VISUG, The Visual Studio User Group in Belgium.
Leave a Reply

Your email address will not be published. Required fields are marked *