Technology16-minute read

Angular 6 Tutorial: New Features with New Power

Angular 6 is out! The most outstanding changes are in its CLI and how services get injected. In this tutorial, Toptal Freelance Angular Developer Joaquin Cid goes over the basic steps of initial setup and then creates a small diary app using Material Design for the front end and Firebase for the back end.


Toptalauthors are vetted experts in their fields and write on topics in which they have demonstrated experience. All of our content is peer reviewed and validated by Toptal experts in the same field.

Angular 6 is out! The most outstanding changes are in its CLI and how services get injected. In this tutorial, Toptal Freelance Angular Developer Joaquin Cid goes over the basic steps of initial setup and then creates a small diary app using Material Design for the front end and Firebase for the back end.


Toptalauthors are vetted experts in their fields and write on topics in which they have demonstrated experience. All of our content is peer reviewed and validated by Toptal experts in the same field.
Joaquin Cid
Verified Expert in Engineering

Joaquin is a full-stack and hybrid mobile app developer with over 12 years of experience working for companies like WebMD and Getty Images.

Previously At

Velocity Partners
Share

Angular 6 is out! The most outstanding changes are in its CLI and how services get injected. If you are looking to write your very first Angular 6 app—or Angular/Firebase app—in this tutorial, we’ll go over the basic steps of initial setup and create a small diary app.

Angular 6: The Basics

If you’ve never used Angular before, let me give you a short description of it and how it works.

Angular is a JavaScript framework designed to support the building of single-page applications (SPAs) for both desktop and mobile.

The framework includes a full suite of directives and modules that allow you to easily implement some of the most common scenarios for a web app, like navigation, authorization, forms, and reporting. It also comes with all the necessary packages to add tests using the Jasmine framework and run them using the Karma or Protractor test runners.

Angular architecture is based on components, templates, directives, and services. It provides a built-in dependency injection mechanism for your services, as well as two-way data binding to connect your views with your component code.

Angular uses TypeScript, a typed superset of JS, and will make some things easier, especially if you come from a typed language background.

Angular 6: New Features

A brief summary of new features in Angular 6:

  • A policy of synchronizing major version numbers for the framework packages (@angular/core, @angular/common, @angular/compiler, etc.), CLI, Material, and CDK. This will help to make cross-compatibility clearer going forward: You can tell from a quick glance at the version number whether key packages are compatible with each other.
  • New ng CLI commands:
    • ng update to upgrade package versions smartly, updating dependencies versions and keeping them in sync. (E.g. when running ng update @angular/core all frameworks will be updated as well as RxJS.) It will also run schematics if the package includes them. (If a newer version includes breaking changes that require changes in code, the schematic will update your code for you.)
    • ng add to add new packages (and run scripts, if applicable)
  • Services now reference the modules that will provide them, instead of modules referencing services, as they used to have it.

As an example of what this last change means, where your code used to look like:

@NgModule({
  // ...
  providers: [MyService]
})

…with this particular change in Angular 6, it will look like:

@Injectabe({
  providedIn: 'root',
})

These are called tree-shakeable providers and allow the compiler to removed unreferenced services resulting in smaller size bundles.

Angular 6 CLI

The ng command-line interface is a very important piece of Angular and allows you to move faster when coding your app.

With the CLI you can scaffold your initial app setup very easily, generate new components, directives, etc, and build and run your app in your local environment.

Creating an Angular 6 Project

Okay, enough talk. Let’s get our hands dirty and start coding.

To start, you’ll need Node.js and npm installed on your machine.

Now, let’s go ahead and install the CLI:

npm install -g @angular/cli

This will install the ng CLI command globally, due to the -g switch.

Once we have that, we can get the initial scaffold for our app with ng new:

ng new my-memories --style=scss

This will create a my-memories folder, create all the necessary files to get your initial setup ready to start, and install all necessary packages. The --style=scss switch is optional and will set up the compiler to compile SCSS files to CSS, which we’ll need later.

Once the install is complete, you can cd my-memories and run ng serve. This will start the build process and a local web server that serves your app at http://localhost:4200.

An Angular 6 app immediately after scaffolding

What’s happening behind the scenes is that the CLI transpiles all the .ts (TypeScript files) to vanilla JS, gathers all the required dependencies from the packages folder node_modules, and outputs the result in a set of files that get served via a local web server that runs on port 4200.

Project Files

If you are not familiar with the project folder structure of Angular, the most important thing you need to know is that all the code related to the app goes inside the src folder. You will usually create all your modules and directives in that folder following your app architecture (e.g. user, cart, product.)

The Angular 6 project folder structure

Initial Setup

Okay, so far we have the initial setup for our app. Let’s start making some changes.

Before we start, let’s dig into the src folder a bit. The initial page is index.html:

<!doctype html>
<html lang="en">
<head>
 <meta charset="utf-8">
 <title>MyMemories</title>
 <base href="/">

 <meta name="viewport" content="width=device-width, initial-scale=1">
 <link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
 <app-root></app-root>
</body>
</html>

Here we see some basic HTML and the <app-root> tag. This is an Angular component, and where Angular 6 inserts our component code.

We’ll find the app/app.component.ts file, which has the selector app-root to match what’s in the index.html file.

The component is a decorated TypeScript class, and in this case, contains the title property. The @Component decorator tells Angular to include the component behavior in the class. In addition to the selector, it specifies which HTML file to render and which stylesheets to use.

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

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent {
  title = 'app';
}

If we look at app.component.html we’ll see the {{title}} interpolation binding. Here’s where all the magic binding happens, and Angular will render the value of the class title property and update it any time it changes.

<!--The content below is only a placeholder and can be replaced.-->
<div style="text-align:center">
 <h1>
   Welcome to {{ title }}!
 </h1>
 <img width="300" alt="Angular Logo" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTAgMjUwIj4KICAgIDxwYXRoIGZpbGw9IiNERDAwMzEiIGQ9Ik0xMjUgMzBMMzEuOSA2My4ybDE0LjIgMTIzLjFMMTI1IDIzMGw3OC45LTQzLjcgMTQuMi0xMjMuMXoiIC8+CiAgICA8cGF0aCBmaWxsPSIjQzMwMDJGIiBkPSJNMTI1IDMwdjIyLjItLjFWMjMwbDc4LjktNDMuNyAxNC4yLTEyMy4xTDEyNSAzMHoiIC8+CiAgICA8cGF0aCAgZmlsbD0iI0ZGRkZGRiIgZD0iTTEyNSA1Mi4xTDY2LjggMTgyLjZoMjEuN2wxMS43LTI5LjJoNDkuNGwxMS43IDI5LjJIMTgzTDEyNSA1Mi4xem0xNyA4My4zaC0zNGwxNy00MC45IDE3IDQwLjl6IiAvPgogIDwvc3ZnPg==">
</div>
<h2>Here are some links to help you start: </h2>
<ul>
 <li>
   <h2><a target="_blank" rel="noopener" href="https://angular.io/tutorial">Tour of Heroes</a></h2>
 </li>
 <li>
   <h2><a target="_blank" rel="noopener" href="https://github.com/angular/angular-cli/wiki">CLI Documentation</a></h2>
 </li>
 <li>
   <h2><a target="_blank" rel="noopener" href="https://blog.angular.io/">Angular blog</a></h2>
 </li>
</ul>

Let’s go ahead and update the title from the class to 'My Memories!'.

...
export class AppComponent {
  title = 'My Memories!';
}
...

We’ll see the compiler process our change and the browser refresh to show our updated title.

This means Angular 6’s ng serve watches for our file changes and renders every time a change is introduced into any file.

In order to make coding more friendly and avoid full page refresh every time we make changes, we can take advantage of webpack Hot Module Replacement (HMR), which just updates the chunk of JS/CSS that was changed instead of producing a full refresh to show your changes.

Configuring HMR

First, we need to set up the environment.

Create a file src/environments/environment.hmr.ts with the following contents:

export const environment = {
  production: false,
  hmr: true
};

Update src/environments/environment.prod.ts and add the hmr: false flag to the environment:

export const environment = {
  production: true,
  hmr: false
};

Then update src/environments/environment.ts and add the hmr: false flag to the environment there as well:

export const environment = {
  production: false,
  hmr: false
};

Next in the angular.json file, update this part:

 "projects": {
   "my-memories": {
     // ...
     "architect": {
       "build": {
	  // ...
         "configurations": {
           "hmr":{
             "fileReplacements":[
               {
                 "replace": "src/environments/environment.ts",
                 "with": "src/environments/environment.hmr.ts"
               }
             ]
           },
        // ...

And under projectsmy-memoriesarchitectserveconfigurations:

 "projects": {
   "my-memories": {
     "architect": {
     // ...       
       "serve": {
	  // ...
         "configurations": {
           "hmr": {
             "browserTarget": "my-memories:build:hmr"
           },
         // ...

Now update tsconfig.app.json to include the necessary types (well, type) by adding this under compilerOptions:

 "compilerOptions": {
   // ...
   "types": [
     "node"
   ]

Next, we’ll install the @angularclass/hmr module as a development dependency:

npm install --save-dev @angularclass/hmr

Then configure it by creating a file src/hmr.ts:

import { NgModuleRef, ApplicationRef } from '@angular/core';
import { createNewHosts } from '@angularclass/hmr';

export const hmrBootstrap = (module: any, bootstrap: () => Promise<NgModuleRef<any>>) => {
  let ngModule: NgModuleRef<any>;
  module.hot.accept();
  bootstrap().then(mod => ngModule = mod);
  module.hot.dispose(() => {
    const appRef: ApplicationRef = ngModule.injector.get(ApplicationRef);
    const elements = appRef.components.map(c => c.location.nativeElement);
    const makeVisible = createNewHosts(elements);
    ngModule.destroy();
    makeVisible();
  });
};

Next, update src/main.ts to use the above function:

import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

import { AppModule } from './app/app.module';
import { environment } from './environments/environment';

import { hmrBootstrap } from './hmr';

if (environment.production) {
  enableProdMode();
}

const bootstrap = () => platformBrowserDynamic().bootstrapModule(AppModule);

if (environment.hmr) {
  if (module[ 'hot' ]) {
    hmrBootstrap(module, bootstrap);
  } else {
    console.error('HMR is not enabled for webpack-dev-server!');
    console.log('Are you using the --hmr flag for ng serve?');
  }
} else {
  bootstrap().catch(err => console.log(err));
}

What we are doing here is making bootstrap call an anonymous function, and next asking whether the environment.hmr flag is true. If it is, we call the previously defined function from hmr.ts which enabled hot module replacement; otherwise, we bootstrap it as we used to.

Now, when we run ng serve --hmr --configuration=hmr, we’ll be invoking the hmr configuration, and when we make changes to files, we’ll get updates without a full refresh. The first --hmr is for webpack, and --configuration=hmr is for Angular to use the hmr environment.

Progressive Web App (PWA)

In order to add Angular 6 PWA support and enable offline loading for the app, we can make use of one of the new CLI commands, ng add:

ng add @angular/pwa@0.6.8

Note that I’m adding the version, since the latest version when I was writing this tutorial was throwing an error. (You can try without it and check if it works for you using simply ng add @angular/pwa.)

Okay, so after we’ve run the command we’ll see a lot of changes on our project. The most important changes are that it added:

  • A reference to manifest.json in the angular.json assets file, so that it’s included in the build output, as well as "serviceWorker": true in production builds
  • The ngsw-config.json file with the initial setup to cache all files necessary for the app to run
  • A manifest.json meta tag in the index.html file
  • The manifest.json file itself, with a basic configuration for the app
  • The service worker load in the app module ServiceWorkerModule.register('/ngsw-worker.js', { enabled: environment.production }) (note that the service worker will be only enabled in production environments)

So, this now means when the user first accesses the URL, the files will be downloaded. After that, if the user tries to access the URL without network service, the app will still work by pulling those cached files.

Adding the Material Angular 6 UI library

So far we have the initial setup, and we are ready to start building our app. To make use of already built components, we can use the Angular 6 version of Material.

In order to install the material package on our app, we’ll again make use of ng add:

ng add @angular/material

After we run that, we’ll see some new packages added and some basic style configuration:

  • index.html includes the Roboto font and Material icons
  • BrowserAnimationsModule is added to our AppModule
  • angular.json has the indigo-pink theme already included for us

Indicating your choice of a prebuilt Angular 6 theme

You’ll need to restart ng serve to pick up the theme, or you can choose another prebuilt theme.

Basic Layout

To have the initial sidenav layout, we’ll use the schematics that come with Material. But it’s OK if you want to use a different layout.

(In a nutshell, schematics let you apply transformations to a project: You can create, modify, or delete files as needed. In this case, it scaffolds a sidenav layout for our app.)

ng generate @angular/material:material-nav --name=my-nav

This will create a sidenav component with the minimum setup ready to start. Isn’t that great?

It has also included all the necessary modules in our app.module.ts.

A newly created "my-nav" Angular 6 component

Since we are using SCSS, we need to rename the my-nav.component.css file to my-nav.component.scss, and in my-nav.component.ts update the corresponding reference styleUrls to use the new name.

Now to make use of the new component, let’s go to app.component.html and remove all the initial code, leaving just:

<app-my-nav></app-my-nav>

When we go back to the browser, here’s what we’ll see:

A four-pane layout with Menu in the upper-left corner, my-memories beside it, and three numbered links under Menu; the fourth pane is empty

Let’s update the links to have just the two options we want.

First, let’s create two new components:

ng g c AddMemory
ng generate @angular/material:material-table --name=view-memories

(The second one is a Material schematic used to create a table.)

Next, on my-nav we’ll update to set up the links and include the <router-outlet> to display our content components:

<mat-sidenav-container class="sidenav-container">
 <mat-sidenav
   #drawer
   class="sidenav"
   fixedInViewport="true"
   [attr.role]="(isHandset$ | async) ? 'dialog' : 'navigation'"
   [mode]="(isHandset$ | async) ? 'over' : 'side'"
   [opened]="!(isHandset$ | async)">
   <mat-toolbar color="primary">Menu</mat-toolbar>
   <mat-nav-list>
     <a mat-list-item [routerLink]="['/add-memory']">Add Memory</a>
     <a mat-list-item [routerLink]="['/view-memories']">View My Memories</a>
   </mat-nav-list>
 </mat-sidenav>
 <mat-sidenav-content>
   <mat-toolbar color="primary">
     <button
       type="button"
       aria-label="Toggle sidenav"
       mat-icon-button
       (click)="drawer.toggle()"
       *ngIf="isHandset$ | async">
       <mat-icon aria-label="Side nav toggle icon">menu</mat-icon>
     </button>
     <span>my-memories</span>
   </mat-toolbar>
   <router-outlet></router-outlet>
 </mat-sidenav-content>
</mat-sidenav-container>

Also, in app.component.html we need to update it to just have the main <router-outlet> (i.e., remove <my-nav>):

<router-outlet></router-outlet>

Next, on the AppModule we’ll include the routes:

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

// ...

imports: [
  // ...
  RouterModule.forRoot([
    {
      path: '', component: MyNavComponent, children: [
        { path: 'add-memory', component: AddMemoryComponent },
        { path: 'view-memories', component: ViewMemoriesComponent }
      ]
    },
  ]),
]

Note that we are setting MyNavComponent as the parent and the two components we created as children. This is because we included the <router-outlet> in MyNavComponent, and whenever we hit one of those two routes, we’ll render the child component where the <router-outlet> was placed.

After this, when we serve the app we should see:

The left-hand links have been replaced with Add Memory and View My Memories, with the latter selected. The empty pane now has a table with id numbers and names.

Build the App (A Memories Diary)

Okay, now let’s create the form to save new memories to our diary.

We’ll need to import some material modules and the forms module to our app.module.ts:

import { FormsModule } from '@angular/forms';
import { MatCardModule, MatFormFieldModule, MatInputModule, MatDatepickerModule, MatNativeDateModule } from '@angular/material';

// ...

Imports:[
  // ...
  MatCardModule,
  MatFormFieldModule,
  MatInputModule,
  MatDatepickerModule,
  FormsModule,
  MatNativeDateModule,
  // ...
]

And then in add-memory.component.html, we’ll add the form:

<form #form="ngForm" (ngSubmit)="onSubmit()">
 <mat-card class="memory-card">
   <mat-card-title>
     Add a memory
   </mat-card-title>
   <mat-card-content>
     <mat-form-field>
       <input disabled matInput placeholder="Select the date..." [(ngModel)]="memory.date" name="date" [matDatepicker]="date">
       <mat-datepicker-toggle matSuffix [for]="date"></mat-datepicker-toggle>
       <mat-datepicker disabled="false" #date></mat-datepicker>
     </mat-form-field>
     <mat-form-field>
       <textarea placeholder="Enter your memory..." rows="3" maxlength="300" matInput [(ngModel)]="memory.text" name="memory"></textarea>
     </mat-form-field>
   </mat-card-content>
   <mat-card-actions>
     <button mat-button type="submit">Save me!</button>
   </mat-card-actions>
 </mat-card>
</form>
<pre> {{ memory | json }} </pre>

Here we are using a mat-card and adding two fields, a date and a textarea.

Note that we are using [(ngModel)]. This Angular directive will bind the memory.date expression and the memory property in the class to each other, as we’ll see later. ([(ngModel)] is syntactic sugar—a shortcut to perform two-way data binding from the class to the view and from the view to the class. This means when you input text in the view, memory.date will reflect those changes in the class instance, and if you make changes to memory.date in the class instance, the view will reflect the changes.)

In the add-memory.component.ts, the code will look like this:

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

@Component({
 selector: 'app-add-memory',
 templateUrl: './add-memory.component.html',
 styleUrls: ['./add-memory.component.scss']
})
export class AddMemoryComponent implements OnInit {
 
  memory: any = {};
 
  constructor() { }
 
  ngOnInit() {
 
  }
 
  onSubmit() {
    console.log(this.memory);
  }
}

Here, we are initializing the memory property bound via ngModel. When the AddMemoryComponent component gets instantiated, memory will be an empty object. Then when the ngModel directive runs, it will be able to assign the input value to memory.date and memory.text. If we wouldn’t do this, we would get an error of Cannot set property 'date/text' of undefined.

Meanwhile, add-memory.component.scss needs to have:

.memory-card {
   min-width: 150px;
   max-width: 400px;
   width: 100%;
   margin: auto;
}

.mat-form-field {
   width: 100%;
}

Since we have <pre> {{ memory | json }} </pre> we can see the current state of memory in the view. If we go to the browser, here’s the result so far:

The "my-memories" diary app prototype, showing the internal representation of user input (date and text.)

In the view, we bound the form via (ngSubmit)="onSubmit()" to the onSubmit function in the class.

 onSubmit() {
   console.log(this.memory);
 }

So when you click the “Save me!” button, you’ll get the internal representation of the user input sent to the console log:

The internal representation of the user input in the console log.

Angular 6 Tutorial: Connecting with Firebase

What we’ll do next is connect our project to Firebase in order to save our memories.

First, we’ll go to the Firebase console and create a project there.

Adding a Firebase project.

Second, we’ll install the firebase and angularfire2 packages:

npm install firebase angularfire2 --save

And then in each of these three files:

  1. /src/environments/environment.ts
  2. /src/environments/environment.hmr.ts
  3. /src/environments/environment.prod.ts

…we’ll add our Firebase config:

export const environment = {
// ...
  firebase: {
    apiKey: '<your-key>',
    authDomain: '<your-project-authdomain>',
    databaseURL: '<your-database-URL>',
    projectId: '<your-project-id>',
    storageBucket: '<your-storage-bucket>',
    messagingSenderId: '<your-messaging-sender-id>'
  }
};

You can get the required configuration details for the above files by clicking “Add Firebase to your web app” on the project overview page.

After that, we’ll include the Firebase modules in our app.module.ts:

import { AngularFireModule } from 'angularfire2';
import { AngularFireDatabaseModule } from 'angularfire2/database';
import { environment } from '../environments/environment';

// ...

Imports:[
// ...
   AngularFireModule.initializeApp(environment.firebase),
   AngularFireDatabaseModule,
// ...
]

And in add-memory.component.ts, we inject the database in the constructor and save the values from the form to the database. When the push promise from Firebase is successful, we log success in the console and reset the model:

import { AngularFireDatabase } from 'angularfire2/database';
// ...
constructor(private db: AngularFireDatabase) { }
// ...
 onSubmit() {
   this.memory.date = new Date(this.memory.date).valueOf();
   this.db.list('memories').push(this.memory)
     .then(_ => {
       this.memory = {}
       console.log('success')
     })     
 }

Allowing read and write access to your Firebase database.

You’ll need to allow public access on the database rules, so anonymous users can read from and write to it. Please note that with this setup, any user would be able to read/change/delete your app data. Make sure you set up your rules accordingly before you go to production.

Also, to pick up the environment changes you will need to restart the ng serve process.

Now, when we go back to the browser and click our save button, we’ll see the memory was added to the database:

Our test memory added to our diary app's Firebase database.

Let’s take a look at how we can retrieve our memories and display them in the Material table.

Back when we created the table using ng generate @angular/material:material-table --name=view-memories', we automatically got a file view-memories/view-memories-datasource.ts`. This file contains fake data, so we’ll need to change it to start pulling from Firebase.

In view-memories-datasource.ts, we’ll remove the EXAMPLE_DATA and set an empty array:

export class ViewMemoriesDataSource extends DataSource<ViewMemoriesItem> {
  data: ViewMemoriesItem[] = [];
// ...

And in getSortedData we’ll update the field names:

private getSortedData(data: ViewMemoriesItem[]) {
  if (!this.sort.active || this.sort.direction === '') {
    return data;
  }
  return data.sort((a, b) => {
    const isAsc = this.sort.direction === 'asc';
    switch (this.sort.active) {
      case 'text': return compare(a.name, b.name, isAsc);
      case 'date': return compare(+a.id, +b.id, isAsc);
      default: return 0;
    }
  });
}

In view-memories.component.html we’ll update the columns’ names to date and text from our memory model. Note that since we saved the date in milliseconds format, here we are using a date pipe to transform the value for display in a more human-friendly date format. Lastly we are removing the [length]="dataSource.data.length" from the paginator, since we’ll load data asyncronously from Firebase:

<div class="mat-elevation-z8">
 <table mat-table #table [dataSource]="dataSource" matSort aria-label="Elements">
   <!-- Id Column -->
   <ng-container matColumnDef="date">
     <th mat-header-cell *matHeaderCellDef mat-sort-header>Date</th>
     <td mat-cell *matCellDef="let row">{{row.date | date:'short'}}</td>
   </ng-container>

   <!-- Name Column -->
   <ng-container matColumnDef="text">
     <th mat-header-cell *matHeaderCellDef mat-sort-header>Text</th>
     <td mat-cell *matCellDef="let row">{{row.text}}</td>
   </ng-container>

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

 <mat-paginator #paginator
   [pageIndex]="0"
   [pageSize]="50"
   [pageSizeOptions]="[25, 50, 100, 250]">
 </mat-paginator>
</div>

Change the view-memories.component.css to view-memories.component.scss and set the table style:

table{
   width: 100%;
}

In view-memories.component.ts, we’ll change the styleUrls to reflect the above renaming to ./view-memories.component.scss. We’ll also update the displayedColumns array to be ['date', 'text'] and setup the table datasource to get data from Firebase.

What’s happening here is we are subscribing to the memories list and when we receive the data we instantiate the ViewMemoriesDataSource and set its data property with the data from Firebase.

this.subscription = this.db.list<ViewMemoriesItem>('memories').valueChanges().subscribe(d=>{
  console.log('data streaming');
  this.dataSource = new ViewMemoriesDataSource(this.paginator, this.sort);   
  this.dataSource.data = d;
});

Firebase returns a ReactiveX-style Observable array.

Note we are casting this.db.list<ViewMemoriesItem>('memories')—the values pulled from the 'memories' path—to ViewMemoriesItem. This is taken care by the angularfire2 library.

We also included the unsubscribe call within the onDestroy hook of the Angular component lifecycle.

import { Component, OnInit, ViewChild, OnDestroy } from '@angular/core';
import { MatPaginator, MatSort } from '@angular/material';
import { ViewMemoriesDataSource, ViewMemoriesItem } from './view-memories-datasource';
import { AngularFireDatabase } from 'angularfire2/database';
import { Subscription } from 'rxjs';
import { map, first } from 'rxjs/operators';

@Component({
  selector: 'app-view-memories',
  templateUrl: './view-memories.component.html',
  styleUrls: ['./view-memories.component.scss']
})
export class ViewMemoriesComponent implements OnInit, OnDestroy{
  @ViewChild(MatPaginator) paginator: MatPaginator;
  @ViewChild(MatSort) sort: MatSort;
  dataSource: ViewMemoriesDataSource;
 
  /** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */
  displayedColumns = ['date', 'text'];
  subscription: Subscription;
 
 
  constructor(private db: AngularFireDatabase) {
 
  }
 
  ngOnInit() {
    this.subscription = this.db.list<ViewMemoriesItem>('memories').valueChanges().subscribe(d=>{
      console.log('data streaming');
      this.dataSource = new ViewMemoriesDataSource(this.paginator, this.sort);   
      this.dataSource.data = d;
    });   
  }
 
  ngOnDestroy(): void {
    this.subscription.unsubscribe();
  }
}

Deploying to Firebase Hosting

Now, to make our app live, let’s deploy it to Firebase Hosting. For that, we’ll install the Firebase CLI, which makes the firebase command available:

npm install -g firebase-tools

Now we can use Firebase CLI to log in:

firebase login

This will prompt you to select your Google account.

Next, we’ll initialize the project and configure Firebase Hosting:

firebase init

We’ll just select the Hosting option.

Next, when we are asked for the path, we’ll set it to dist/my-memories. When we’re asked whether to configure it as a single-page app (i.e. rewrite all URLs to /index.html), we’ll respond “yes.”

Finally, we’ll hit, “File dist/my-memories/index.html already exists. Overwrite?” Here we’ll say “no.”

This will create the Firebase config files .firebaserc and firebase.json with the provided configuration.

The last step is to run:

ng build --prod
firebase deploy

And with that, we will have published the app to the Firebase, which provides a URL for us to navigate to, like https://my-memories-b4c52.firebaseapp.com/view-memories, where you can view my own published demo.


Wow, you’ve been through the tutorial! I hope you enjoyed it. You can also check out the full code for it on GitHub.

One Step at a Time

Angular is a very powerful framework for building web apps. It’s been out there for a long time and has proven itself for small, simple apps and large, complex ones alike—Angular 6 is no exception here.

Going forward, Angular plans to keep improving and following new web paradigms such as web components (Angular Elements). If you are interested in building hybrid apps, you can check out Ionic, which uses Angular as its underlying framework.

This tutorial covered very basic steps to start using Angular, Material, and Firebase. But you should take into account that for real-world applications, you’ll need to add validation, and to make your application easier to maintain and scale, you’d probably want to follow best practices such as using Services, reusable components, etc. That will have to be the subject of another article—hopefully, this one was enough to whet your appetite for Angular development!

Understanding the basics

  • What is AngularJS used for?

    AngularJS—simply “Angular” nowadays, since version 2—is used to build web apps, progressive web apps (PWAs), and hybrid mobile apps. The Angular framework ships with all the necessary functionality (such as routing, security, forms, and testing) to quickly build a single-page application (SPA).

  • Angular components are a special kind of directive. They bind a view with class via a decorator. The view is an HTML template, and from the class, you can control and react to any interactions with the view.

  • Angular is a JavaScript (JS) framework, with a full suite of built-in functionality for building web apps. TypeScript is a typed superset of JS. It compiles to vanilla JS and is supported by Angular.

  • With Angular you can build scalable, performant web apps. Angular allows you to build reusable, testable components which will allow you to make changes and maintain your app easily.

  • Angular CLI is its command-line interface and provides commands for most common actions. Examples include generating components, pipes, and directives; adding dependencies; and creating new projects.

  • Hot module replacement (HMR) is a webpack feature that allows you to replace modules without a full browser refresh. Avoiding the extra step and resulting delay means you can code and test your changes a lot faster.

  • The Firebase real-time database is Google’s cloud-based NoSQL database. You can use it to save data and auto-sync it to users’ devices. Firebase databases allow you to quickly build your application without the need of a back end of your own. Firebase has a lot of products that help build serverless apps.

Hire a Toptal expert on this topic.
Hire Now
Joaquin Cid

Joaquin Cid

Verified Expert in Engineering

Rosario, Santa Fe Province, Argentina

Member since May 2, 2018

About the author

Joaquin is a full-stack and hybrid mobile app developer with over 12 years of experience working for companies like WebMD and Getty Images.

authors are vetted experts in their fields and write on topics in which they have demonstrated experience. All of our content is peer reviewed and validated by Toptal experts in the same field.

PREVIOUSLY AT

Velocity Partners

World-class articles, delivered weekly.

By entering your email, you are agreeing to our privacy policy.

World-class articles, delivered weekly.

By entering your email, you are agreeing to our privacy policy.

Join the Toptal® community.