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.
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.
Joaquin is a full-stack and hybrid mobile app developer with over 12 years of experience working for companies like WebMD and Getty Images.
Expertise
Previously At
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 runningng 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
.
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.)
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 projects
→ my-memories
→ architect
→ serve
→ configurations
:
"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 theangular.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 theindex.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 ourAppModule
-
angular.json
has the indigo-pink theme already included for us
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
.
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:
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:
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:
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:
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.
Second, we’ll install the firebase
and angularfire2
packages:
npm install firebase angularfire2 --save
And then in each of these three files:
/src/environments/environment.ts
/src/environments/environment.hmr.ts
/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')
})
}
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:
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).
What are Angular components?
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.
What is Angular and TypeScript?
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.
What can you do with AngularJS?
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.
What does Angular CLI stand for?
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.
What is hot module replacement?
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.
What is a Firebase database for an application?
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.
Joaquin Cid
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.
Expertise
PREVIOUSLY AT