Tutorial on Angular 6: Discover the Latest Features and Enhancements

Angular 6 has arrived, bringing notable changes to its CLI and service injection methods. This tutorial guides you through building your first Angular 6 application, or even an Angular/Firebase app, covering initial setup and the creation of a simple diary application.

Angular 6: A Primer

For those new to Angular, here’s a concise overview of its functionality.

Angular is a JavaScript framework designed for building single-page applications (SPAs) that work seamlessly on both desktop and mobile platforms.

The framework offers a comprehensive set of directives and modules, simplifying common web app features like navigation, authorization, forms, and reporting. It also includes packages for testing with the Jasmine framework and running those tests using Karma or Protractor.

Angular’s architecture revolves around components, templates, directives, and services. It has a built-in dependency injection mechanism for services and two-way data binding to link views with component code.

Angular utilizes TypeScript, a typed superset of JavaScript, which can be particularly helpful for developers familiar with typed languages.

Angular 6: What’s New?

Here’s a quick look at the new features in Angular 6:

  • Synchronized major version numbers across framework packages (@angular/core, @angular/common, @angular/compiler, etc.), CLI, Material, and CDK for clearer cross-compatibility. Now, you can easily determine package compatibility at a glance.
  • New ng CLI commands:
    • ng update for intelligent package upgrades, synchronizing dependencies (e.g., running ng update @angular/core updates all frameworks and RxJS). It also executes schematics if included in the package, automatically updating code for breaking changes.
    • ng add for adding new packages and running associated scripts.
  • Services now reference their providing modules, a reversal from the previous approach where modules referenced services.

To illustrate this last change, consider this previous code structure:

1
2
3
4
@NgModule({
  // ...
  providers: [MyService]
})

…which now becomes:

1
2
3
@Injectabe({
  providedIn: 'root',
})

These are known as tree-shakeable providers, enabling the compiler to eliminate unused services for smaller bundle sizes.

Angular 6 CLI

The ng command-line interface is a vital part of the Angular ecosystem, streamlining the development process.

The CLI simplifies initial app scaffolding, generates new components and directives, and facilitates building and running your app locally.

Setting Up an Angular 6 Project

Let’s dive into some practical coding.

First, ensure you have Node.js and npm installed.

Now, install the CLI:

1
npm install -g @angular/cli

This installs the ng CLI command globally.

Next, generate the initial structure for your app using ng new:

1
ng new my-memories --style=scss

This creates a my-memories folder containing all the necessary files for your initial setup and installs the required packages. The optional --style=scss switch configures the compiler to process SCSS files into CSS, which we’ll need later.

After installation, navigate to cd my-memories and run ng serve. This initiates the build process and launches a local web server hosting your app at http://localhost:4200.

An Angular 6 app immediately after scaffolding

Behind the scenes, the CLI transpiles .ts (TypeScript) files into standard JavaScript, gathers dependencies from the node_modules folder, and delivers the output as a set of files served by a local web server on port 4200.

Project Files

If you’re unfamiliar with Angular’s project folder structure, the key takeaway is that all app-related code resides within the src folder. You’ll typically create modules and directives within this folder, following your app architecture (e.g., user, cart, product.)

The Angular 6 project folder structure

Initial Configuration

With the initial setup in place, let’s make some adjustments.

Let’s explore the src folder. The starting point is index.html:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<!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 basic HTML and the <app-root> tag. This tag represents an Angular component and serves as the insertion point for your component code.

The corresponding app/app.component.ts file defines the component with the selector app-root, matching the tag in index.html.

This component is a decorated TypeScript class, in this case, holding a title property. The @Component decorator instructs Angular to apply the component’s behavior to the class. Besides the selector, it specifies the HTML template and stylesheets to use.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import { Component } from '@angular/core';

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

Examining app.component.html, we find the {{title}} interpolation binding. This is where the magic of data binding happens. Angular renders the value of the title property from the component class and dynamically updates it whenever the value changes.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
<!--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 modify the class’s title to 'My Memories!'.

1
2
3
4
5
...
export class AppComponent {
  title = 'My Memories!';
}
...

Observe how the compiler processes the change, and the browser refreshes to display the updated title.

This demonstrates that Angular 6’s ng serve monitors file changes and re-renders the application whenever modifications are detected.

To make coding smoother and avoid full page reloads for every change, we can leverage webpack’s Hot Module Replacement (HMR). HMR updates only the modified chunks of JavaScript or CSS, eliminating the need for complete refreshes.

Setting Up HMR

First, we need to configure the environment.

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

1
2
3
4
export const environment = {
  production: false,
  hmr: true
};

In src/environments/environment.prod.ts, add the hmr: false flag to the environment:

1
2
3
4
export const environment = {
  production: true,
  hmr: false
};

Do the same in src/environments/environment.ts, adding the hmr: false flag:

1
2
3
4
export const environment = {
  production: false,
  hmr: false
};

Next, modify the angular.json file:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
 "projects": {
   "my-memories": {
     // ...
     "architect": {
       "build": {
	  // ...
         "configurations": {
           "hmr":{
             "fileReplacements":[
               {
                 "replace": "src/environments/environment.ts",
                 "with": "src/environments/environment.hmr.ts"
               }
             ]
           },
        // ...

And under projectsmy-memoriesarchitectserveconfigurations:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
 "projects": {
   "my-memories": {
     "architect": {
     // ...       
       "serve": {
	  // ...
         "configurations": {
           "hmr": {
             "browserTarget": "my-memories:build:hmr"
           },
         // ...

Now, update tsconfig.app.json to include the required type by adding this under compilerOptions:

1
2
3
4
5
 "compilerOptions": {
   // ...
   "types": [
     "node"
   ]

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

1
npm install --save-dev @angularclass/hmr

Configure it by creating a file src/hmr.ts:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
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();
  });
};

Finally, update src/main.ts to utilize the above function:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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));
}

Here, we wrap the bootstrap call in an anonymous function and check the environment.hmr flag. If true, we call the function defined in hmr.ts to enable hot module replacement; otherwise, we bootstrap as usual.

Now, running ng serve --hmr --configuration=hmr invokes the hmr configuration. Changes to files will be reflected without a full page refresh. The first --hmr is for webpack, while --configuration=hmr instructs Angular to use the hmr environment.

Progressive Web App (PWA)

To incorporate Angular 6 PWA support and enable offline loading, use the new CLI command ng add:

1
ng add @angular/pwa@0.6.8

Note that I’m including the version, as the latest at the time of writing was throwing an error. (You can try omitting it and see if ng add @angular/pwa works for you.)

Running this command triggers several changes in your project. The most important are:

  • A reference to manifest.json is added to the angular.json assets array, ensuring it’s included in the build output. Additionally, "serviceWorker": true is enabled for production builds.
  • The ngsw-config.json file is generated with the initial configuration to cache essential files for the app.
  • A manifest.json meta tag is added to index.html.
  • The manifest.json file itself is created with basic app configuration.
  • Service worker loading is implemented in the app module: ServiceWorkerModule.register('/ngsw-worker.js', { enabled: environment.production }) (note that the service worker is active only in production).

Now, when a user first visits the URL, the files are downloaded and cached. Subsequent attempts to access the URL without an internet connection will load the app from the cache.

Integrating the Material Angular 6 UI Library

With the initial setup complete, we can start building our app. To leverage pre-built components, let’s utilize the Angular 6 version of Material.

Install the material package using ng add:

1
ng add @angular/material

This adds new packages and some basic style configurations:

  • index.html now includes the Roboto font and Material icons.
  • BrowserAnimationsModule is added to your AppModule.
  • The indigo-pink theme is included in angular.json.
Indicating your choice of a prebuilt Angular 6 theme

You’ll need to restart ng serve or choose another prebuilt theme for the theme to take effect.

Basic Layout

To create the initial sidenav layout, we’ll use Material’s schematics. However, you’re welcome to use a different layout.

(In essence, schematics apply transformations to your project, allowing you to create, modify, or delete files as needed. In this scenario, we’ll use a schematic to generate a sidenav layout.)

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

This generates a sidenav component with a basic setup, ready for customization.

It also includes the necessary modules in your app.module.ts.

A newly created "my-nav" Angular 6 component

Since we’re using SCSS, rename my-nav.component.css to my-nav.component.scss and update the styleUrls reference accordingly in my-nav.component.ts.

Now, let’s use the new component. In app.component.html, remove all the initial code, leaving only:

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

Navigating back to the browser, you should now 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 customize the links to display only the two options we need.

First, create two new components:

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

(The second command uses a Material schematic to generate a table.)

Next, update my-nav to configure the links and include the <router-outlet> for displaying content components:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<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>

In app.component.html, update the content to have only the main <router-outlet> (remove <my-nav>):

1
<router-outlet></router-outlet>

Next, define the routes in your AppModule:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
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 MyNavComponent is set as the parent, with the two newly created components as children. This is because the <router-outlet> is placed within MyNavComponent. When navigating to those routes, the corresponding child component will be rendered in place of the <router-outlet>.

After these changes, running the app should display:

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.

Building the App (A Memories Diary)

Let’s create the form for adding memories to our diary.

Import some Material modules and the forms module into app.module.ts:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import { FormsModule } from '@angular/forms';
import { MatCardModule, MatFormFieldModule, MatInputModule, MatDatepickerModule, MatNativeDateModule } from '@angular/material';

// ...

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

Then, in add-memory.component.html, add the form:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
<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>

We’re using a mat-card and adding two fields: a date input and a textarea.

Note the use of [(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 bindingfor two-way data binding between the class and the view. This synchronizes changes made in the input fields with thememory.dateandmemory.text` properties in the component class.

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
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 initialize the memory property bound via ngModel. When AddMemoryComponent is instantiated, memory starts as an empty object. The ngModel directive then assigns input values to memory.date and memory.text, preventing potential “Cannot set property of undefined” errors.

Add the following styles to add-memory.component.scss:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
.memory-card {
   min-width: 150px;
   max-width: 400px;
   width: 100%;
   margin: auto;
}

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

The line <pre> {{ memory | json }} </pre> displays the current state of the memory object in the view. In the browser, the current result should look like this:

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

The form is bound to the onSubmit function in the component class using (ngSubmit)="onSubmit()".

1
2
3
 onSubmit() {
   console.log(this.memory);
 }

Clicking the “Save me!” button logs the internal representation of the user input to the console:

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

Angular 6 Tutorial: Integrating Firebase

Next, let’s connect our project to Firebase to store our memories.

First, head over to the Firebase console and create a new project.

Adding a Firebase project.

Next, install the firebase and angularfire2 packages:

1
npm install firebase angularfire2 --save

Add your Firebase configuration to each of these files:

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

The configuration should look like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
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 find the necessary configuration details by clicking “Add Firebase to your web app” on your project’s overview page in the Firebase console.

Include the Firebase modules in your app.module.ts:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import { AngularFireModule } from 'angularfire2';
import { AngularFireDatabaseModule } from 'angularfire2/database';
import { environment } from '../environments/environment';

// ...

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

In add-memory.component.ts, inject the database service into the constructor and save the form data to the database. Upon successful data push to Firebase, log a success message and reset the form model:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
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 configure your database rules to allow public access for this example, enabling anonymous users to read and write data. Caution: This setup allows any user to modify your app data. Be sure to set up your rules accordingly before you go to production in a production environment.

Restart ng serve to apply the environment changes.

Now, when you click the save button in the browser, the memory is added to the database:

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

Let’s retrieve and display our memories in the Material table.

Recall that when we created the table using ng generate @angular/material:material-table --name=view-memories, a file named view-memories/view-memories-datasource.ts was automatically generated. This file contains placeholder data, so we’ll modify it to fetch data from Firebase.

In view-memories-datasource.ts, remove the EXAMPLE_DATA and initialize an empty array:

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

Update the field names in getSortedData:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
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, change the column names to date and text to match our memory model. Since the date is stored in milliseconds, use the date pipe to format it for display. Remove [length]="dataSource.data.length" from the paginator, as data will be loaded asynchronously from Firebase:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<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>

Rename view-memories.component.css to view-memories.component.scss and add the table style:

1
2
3
table{
   width: 100%;
}

In view-memories.component.ts, update the styleUrls to reflect the renaming and set displayedColumns to ['date', 'text']. Configure the table’s data source to fetch data from Firebase:

This code subscribes to the memories list and, upon data retrieval, instantiates the ViewMemoriesDataSource and populates its data property with the Firebase data.

1
2
3
4
5
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 an Observable array in the style of ReactiveX.

Note the casting of this.db.list<ViewMemoriesItem>('memories')—the values from the 'memories' path—to ViewMemoriesItem, handled by the angularfire2 library.

We’ve also added an unsubscribe call within the onDestroy lifecycle hook of the Angular component for cleanup.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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

Let’s deploy our app to Firebase Hosting to make it live. Install the Firebase CLI, which provides the firebase command:

1
npm install -g firebase-tools

Log in using the Firebase CLI:

1
firebase login

You’ll be prompted to select your Google account.

Initialize the project and configure Firebase Hosting:

1
firebase init

Choose the “Hosting” option.

When asked for the public directory path, enter dist/my-memories. Respond “yes” to configure it as a single-page app (rewriting all URLs to /index.html).

Finally, when asked about overwriting dist/my-memories/index.html, choose “no.”

This creates the Firebase configuration files .firebaserc and firebase.json.

The final step is to run:

1
2
ng build --prod
firebase deploy

This deploys your app to Firebase Hosting, providing you with a URL to access it. You can find my published demo at https://my-memories-b4c52.firebaseapp.com/view-memories.


Congratulations on completing the tutorial! I hope you found it insightful. You can explore more in-depth examples at the full code for it on GitHub.

Taking it One Step at a Time

Angular is a robust framework for building web applications, with a proven track record for projects of all sizes. Angular 6 continues this tradition.

Looking ahead, Angular aims to continuously improve and embrace new web paradigms like web components (Angular Elements). If hybrid app development piques your interest, check out Ionic, which leverages Angular as its foundation.

This tutorial covered the fundamentals of Angular, Material, and Firebase. For real-world applications, you’ll need to incorporate validation and adhere to best practices like utilizing services, creating reusable components, and more. Those topics will have to wait for another article. For now, I hope this has sparked your enthusiasm for Angular development!

Licensed under CC BY-NC-SA 4.0