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/[email protected]

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).

About the author

Joaquin Cid, Argentina
member since December 21, 2015
Joaquin is a full-stack developer with over 12 years of experience working for companies like WebMD and Getty Images. He specializes in web application development and has also experience building hybrid mobile apps. He has an entrepreneurial mindset and approach to projects which means he always tries to help a business get the most value in the least amount of time possible. [click to continue...]
Hiring? Meet the Top 10 Freelance Angular Developers for Hire in November 2018

Comments

quijoT
In app.module.ts will be necessary to import environment in order to do this... Imports:[ // ... AngularFireModule.initializeApp(environment.firebase), // ... ]
Kevin Bloch
@quijoT:disqus Thanks for your feedback, but the code you cited is already reflected in the original article: Search for "After that, we’ll include the Firebase modules in our app.module.ts:" and you'll see it in the code listing just below.
quijoT
Hi @kevinbloch:disqus ! Thanks for the answer. I mean this line: \\ ... AngularFireModule.initializeApp(environment.firebase), \\ ... Would need this import: import { environment } from '../environments/environment'; I am doing it right?
Joaquin Cid
Hi! @quijoT:disqus yes, you are doing it right, you need that import to run the app, On the full code, the import is there, but i forgot to add it in the tutorial, i'll update that, thanks!
Kevin Bloch
It's been updated. Thanks!
quijoT
Great! Thank you!
rhumbus
Nice and easy-to-follow writing on firebase integration. Thank you!
Raymond A. Dise, Jr.
@joaquincid:disqus thank you for the well written article. It helped me learn some of this very quickly. One question, and it is similar to @rhumbus:disqus 's question, and that is if I add an actions column to the table and within that column incorporate a delete button, how do I trigger a refresh of the table data once the delete has completed? I have read up on how to delete list items from Firebase's Realtime Database so I do have the delete function working, but I would be curious to see how you would approach this in the context of your tutorial. I kind of thought the point of Observables was to listen for changes and refresh things automatically, but obviously, I am missing something. Thanks in advance!
Joaquin Cid
Hi @raymondadisejr:disqus Yup, once you are subscribed to 'valueChanges()' you would receive an emission every time there's a change (this counts for deletes too), i didnt implement the delete button, but did a quick test deleting some items from firebase console and got the count updated on the 'memories' table if your button is deleting the item, and the subscription on the table is alive you should automatically see the updates, can you expand on how you did it so i can understand why is not working?
Joaquin Cid
Hi @rhumbus:disqus sorry for late reply, The refresh should happen automatically, even though the subscription happened at 'ngOnInit'. When you subscribe to 'valueChanges' the subscription will be alive until you actually unsubscribe from it, which in this case happens on 'ngOnDestroy'. While the subscription is alive you will receive the events of added items to the collection, and angular will take care of updating the view. This means that if you add the form component '<app-add-memory></app-add-memory>' in the 'view-memories.component.html' you will see the memories added to the table when you save them. Hope this helps you!
Raymond A. Dise, Jr.
Hi @joaquincid:disqus, Thanks for the quick reply and the offer to help out. Instead of memories, my app deals with upgrades, so when I refer to the view-upgrades.component.ts, etc., there is a direct correlation to the view-memories.* files. My firebase realtime database list is called upgrades. So, to delete a row I added the following to my view-upgrades.component.html file: https://uploads.disquscdn.com/images/08152d664089b3f4d64bd16f689bfdaf3386751f20cdb12004dad188e992cb9f.png And then added to following to my view-upgrades.component.ts file: https://uploads.disquscdn.com/images/0212b81ad050942e8694c6351413d43d808e87dccf8aa38f7332e38f7af0e0d8.png And the deleteUpgrade function works, but the material table is not refreshed. Thanks for the help.
Raymond A. Dise, Jr.
I figured it out. In the example above, you have .valueChanges().pipe(first()).subscribe( ... ) but in your GitHub repository, you have .valueChanges().subscribe( ... ) so, when I removed the .pipe(first()) it works as you describe and as one would expect.
Joaquin Cid
good catch! @rhumbus:disqus maybe this is the same issue you were having
Raymond A. Dise, Jr.
Thanks @joaquincid:disqus ! If you have a moment, would you explain the purpose of the .pipe(first()) part? I am still learning how to wrap my head around Observables and much of this looks like magic to me at this point. Thanks again, Ray
Joaquin Cid
sure, observables are a complete separate topic themselves, basically, observables are pipeable, meaning you can chain operations and modify the items as they go through the "pipe", in the case of "first()" operator, it just returns the first emission and stops listening, you can also pass a condition as a parameter, and in that case it will return the first item that passes that condition this is a great resource you can read more about https://www.learnrxjs.io/operators/filtering/first.html
Kevin Bloch
The article's been updated to match the repo. Thanks! :)
Raymond A. Dise, Jr.
Thanks @kevinbloch:disqus !
Balaji hope tutors
Good article. Excited to see the Angular 7 Does anyone know when it will be released ? <a href="https://www.hopetutors.com/course/angular-js-training-in-chennai/">Angular JS Course in Chennai</a>
comments powered by Disqus
Subscribe
Free email updates
Get the latest content first.
No spam. Just great articles & insights.
Free email updates
Get the latest content first.
Thank you for subscribing!
Check your inbox to confirm subscription. You'll start receiving posts after you confirm.
Trending articles
Relevant Technologies
About the author
Joaquin Cid
Full-stack Developer
Joaquin is a full-stack developer with over 12 years of experience working for companies like WebMD and Getty Images. He specializes in web application development and has also experience building hybrid mobile apps. He has an entrepreneurial mindset and approach to projects which means he always tries to help a business get the most value in the least amount of time possible.