From Idea to Reality in under 50 minutes (mostly) with Angular and Firebase - Part II
This is the second part of the super series on Angular and Firebase. If you missed the first one then go take a look!
Welcome to part two of this series of articles that aim to help you bring your awesome ideas to life. In this installment of the series, I bring you lots more of Angular and Firebase goodness while we build the rest of baby-gotchi, a baby simulator to help you become a better dad.
Enjoy!
Quick Recap
Let’s start by making a quick recap of what we have achieved thus far so we all are on the same page before we continue.
In the last part of the series we:
- Created a new Angular app using the Angular cli
- Devised a way to model babys in code with the
Baby
class - Built the
BabiesComponent
as a way to show a list of babies and give birth to new ones - Setup Firebase using the AngularFire2 library
- Used Firebase Realtime Database to store our babies
- Added Angular Material and Angular Flex Layout to our app and made it look nicer
And so we arrived to this point:
Just after adding those two buttons to Take Care and Control our babies with this corresponding template:
<h1>Born Babies!</h1>
<section fxLayout="column" fxLayoutGap="12px">
<md-card *ngFor="let baby of babies | async">
<md-card-title>{{baby.name}}</md-card-title>
<md-card-actions>
<a md-button color="primary" [routerLink]="[baby.$key, 'care']">Take care</a>
<a md-button color="accent" [routerLink]="[baby.$key, 'control']">Control!!!</a>
</md-card-actions>
</md-card>
<button md-raised-button color="accent" (click)="giveBirth()">Give Birth!</button>
</section>
Excellent! The next feature in our MVP will be taking care of our baby.
Taking Care of Your Baby
I don’t have a lot of parenting experience yet, but I think that in order to be able to take care of a baby it can be helpful to see the actual baby.
So, we’ll start by creating a new BabyComponent
where we can both see our baby and take actions to care for her or him like cuddling, feeding or rocking the baby to sleep.
Again, just like we did in the previous article, we’ll take advantage of the Angular cli to generate our components for us. And since you have become an expert with the cli by this point, we’ll start using short-hand notation:
PS> ng g c baby-component
# generate -> g
# component -> c
The short-hand notation is surprisingly easy to guess. Just pick the first letter of a word (like g
for generate
) and it’ll work 99% of the times. When in doubt, use the Angular cli help. You can reach it via ng help
or ng <command> --help
(or ng <command> -h
).
For instance, the following command :
PS> ng g -h
Will show you all the available generators and the different flags that you can use with them.
Back to the baby! My idea with the baby component is to have a UI sort of like this:
<!--
Baby status here:
Different indicators for sleepiness, hunger, etc
-->
<!-- take care actions -->
Since they are two separate concerns: showing the status of the baby and performing actions on him or her we should create two separate components, a BabyStatusComponent
and a BabyCareComponent
.
The Baby Status Component
Let’s start with the component for the baby status. Again, Angular cli for the win:
PS> ng g c baby-status
The baby status should have a template where we can see how the baby is feeling at a glance. We update the template of this component to reflect all of the babies stats:
<h1>{{baby.name}} is {{status}}</h1>
<div class="baby" [class.baby-sad]="baby.life < 50" [class.baby-super-sad]="baby.life < 25"></div>
<div fxLayout="row" fxLayoutAlign="start center" class="status-indicator">
<span fxFlex="60%">Sleepiness</span>
<md-progress-bar fxFlex="25%" mode="determinate" [value]="baby.sleepiness" color="primary"></md-progress-bar>
<span fxFlex="15%" class="status-indicator-counter" >{{baby.sleepiness}}</span>
</div>
<div fxLayout="row" fxLayoutAlign="start center" class="status-indicator">
<span fxFlex="60%">Hunger </span>
<md-progress-bar fxFlex="25%" mode="determinate" [value]="baby.hunger" color="primary"></md-progress-bar>
<span fxFlex="15%" class="status-indicator-counter">{{baby.hunger}}</span>
</div>
<div class="status-indicator">
<span fxFlex="60%">Shittiness</span>
<md-progress-bar fxFlex="25%" mode="determinate" [value]="baby.shittiness" color="accent"></md-progress-bar>
<span fxFlex="15%" class="status-indicator-counter">{{baby.shittiness}}</span>
</div>
<div class="life status-indicator">
<span fxFlex="60%">Life</span>
<md-progress-bar fxFlex="25%" mode="determinate" [value]="baby.life" color="warn"></md-progress-bar>
<strong fxFlex="15%" class="status-indicator-counter">{{baby.life}}</strong>
</div>
A couple of things happen in this template:
- We use the
[class]
property binding to set a class on the.baby
element in order to display different baby images depending on how the baby is feeling - We use the
[value]
property binding to set the value of different progress bars for the baby’s different stats
The template binds to a baby
property that is exposed in the BabyStatusComponent
class. But how does that baby get there? How does this component get hold of a Baby
instance? With an input! And what is an input you may be asking yourself?
Well, Angular allows you to define inputs in your own components so that you can pass data from parent to child components using HTML attributes (just like it is so natural when writing HTML).
For instance, a consumer of the BabyStatusComponent
could type the following:
<app-baby-status [baby]="myBaby"></app-baby-status>
And send the myBaby
variable with a Baby
to the BabyStatusComponent
(also notice how this is again a property binding).
In order to allow this type of behavior we need to define an input in the BabyStatusComponent
using the @Input()
decorator:
import { Component, OnInit, Input } from '@angular/core'
import { Baby } from 'app/baby'
@Component({
selector: 'app-baby-status',
templateUrl: './baby-status.component.html',
styleUrls: ['./baby-status.component.scss'],
})
export class BabyStatusComponent implements OnInit {
// Here is where we define the input
@Input() baby: Baby
get status(): string {
if (this.baby.life > 50) {
return 'happy'
} else if (this.baby.life > 25) {
return 'sad'
} else {
return 'very sad'
}
}
}
We also need to update the look and feel of the component inside baby-status.component.scss
with some basic styles and some images for the baby:
h1 {
text-align: center;
}
.baby {
width: 150px;
height: 150px;
background-image: url('~/assets/baby-happy.png');
background-size: contain;
background-repeat: no-repeat;
margin: 0 auto;
}
.baby-sad {
background-image: url('~/assets/baby-sad.png');
}
.baby-super-sad {
background-image: url('~/assets/baby-super-sad.png');
}
.status-indicator {
margin: 12px 0;
.status-indicator-counter{
text-align: right;
}
}
.life {
color: red;
}
After writing this component you realize that there’s some opportunities for improvement. Particularly, you see that there’s a lot of code duplication and verbosity in what concerns the status indicators. This is a great signal that the indicator is a perfect candidate for becoming its own component.
Let’s create it and do some refactoring.
Refactoring!!! Extracting the Status Indicator Component
Once more we take advantage of the Angular cli and type:
PS> ng g c status-indicator
Which results in a new StatusIndicatorComponent
. We update its template status-indicator.component.html
by copying and pasting the original template and making it a little bit more configurable. Namely we should be able to set the label
and the value
for every status indicator:
<div fxLayout="row" fxLayoutAlign="start center" class="status-indicator">
<span fxFlex="60%">{{label}}</span>
<md-progress-bar fxFlex="25%" mode="determinate" [value]="value" [color]="color"></md-progress-bar>
<span fxFlex="15%" class="counter" >{{value}}</span>
</div>
We also update its styles status-indicator.component.scss
:
// simpler style now that we no longer need
// to append any "status-indicator" to it
// The style is encapsulated to this component
// which simplifies naming immensely
.counter {
text-align: right;
}
And, of course, its underlying component class status-indicator.component.ts
:
import { Component, OnInit, Input } from '@angular/core'
@Component({
selector: 'app-status-indicator',
templateUrl: './status-indicator.component.html',
styleUrls: ['./status-indicator.component.scss'],
})
export class StatusIndicatorComponent {
@Input() label: string
@Input() value: number
@Input() color: string = 'primary'
}
Using this new component will make our original BabyStatusComponent
less wordy and simpler:
<section *ngIf="baby">
<h1>{{baby.name}} is {{status}}</h1>
<div class="baby" [class.baby-sad]="baby.life < 50" [class.baby-super-sad]="baby.life < 25"></div>
<section fxLayout="column" fxLayoutGap="12px">
<app-status-indicator [label]="'Sleepiness'" [value]="baby.sleepiness"></app-status-indicator>
<app-status-indicator [label]="'Hunger'" [value]="baby.hunger" [color]="'accent'"></app-status-indicator>
<app-status-indicator [label]="'Shittiness'" [value]="baby.shittiness"></app-status-indicator>
<app-status-indicator class="life" [label]="'Life'" [value]="baby.life" [color]="'warn'"></app-status-indicator>
</section>
</section>
Ooooh… *sigh* like a breath of fresh air.
Ok, so now that we have a way to display the status of a baby we can use it within our baby component. If you remember from the beginning of the article, we had used some pseudocode to represent the template of the BabyComponent
in baby.component.html
which looked like this:
<!-- baby status here with the different indicators -->
<!-- take care actions -->
We can continue by substituting that with this:
<app-baby-status [baby]="baby | async"></app-baby-status>
<!-- take care actions -->
Now the BabyComponent
needs to be loaded in our application somehow.
At this point, when a user uses baby-gotchi they are confronted with an initial view where they can see a list of babies, give birth and click on a link to either take care or control the baby. However, those links won’t work. That’s because we haven’t updated our routing configuration to tell Angular what to do with those URLs.
We are going to update the routing configuration to connect these URL paths with the BabyComponent
which will be the component in charge of providing this service to the user.
Updating Our Routing Configuration To Care For The Baby
In our routing configuration in app-routing.component.ts
we add the following route:
import { NgModule } from '@angular/core'
import { Routes, RouterModule } from '@angular/router'
import { BabiesComponent } from 'app/babies/babies.component'
import { BabyComponent } from 'app/baby/baby.component'
const routes: Routes = [
{
path: 'babies',
children: [
{
path: '',
component: BabiesComponent,
},
// This is the new route!!!
{
path: ':id',
component: BabyComponent,
pathMatch: 'prefix',
},
],
},
{
path: '**',
redirectTo: 'babies',
},
]
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule],
})
export class AppRoutingModule {}
The new route tells Angular that whenever he sees a path that matches the babies/
URL and then starts with :id
it should load the BabyComponent
. The special :id
token represents a route parameter and will store the value it matches within the URL. For instance:
/babies/12
// => The 'id' route parameter is equal to 12
/babies/f324afasd324/care
// => The 'id' route parameter is equal to f324afasd324
Later on, we will be able to refer to this route parameter via its name id
when interacting with the router.
If you run the application now (remember ng serve --open
) you’ll be able to see how when you click on either Take Care or Control Baby you are able to navigate to the BabyComponent
view where… nothing is displayed!
The reason for that is that we haven’t taught the BabyComponent
how to get hold of a baby. In our current implementation, we click on the link, load the BabyComponent
, attempt to bind a baby
to the BabyStatus
component and there’s no baby to be found.
We can get hold of that missing baby by using the id
route parameter when loading the component. The Angular router offers a service called ActivatedRoute
that let’s you get access to the current route parameters in an asynchronous fashion.
Let’s update the BabyComponent
class to use ActivatedRoute
. Within baby.component.ts
we write the following:
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Baby } from 'app/baby';
@Component({
selector: 'app-baby',
templateUrl: './baby.component.html',
styleUrls: ['./baby.component.css']
})
export class BabyComponent implements OnInit {
constructor(private activatedRoute: ActivatedRoute) { }
ngOnInit() {
this.activatedRoute
.params
.subscribe(
p => {
const key = p['id'];
// TODO: get baby from Firebase using key
}
);
}
}
So using the params
observable property of the ActivatedRoute
service we can get hold of the :id
route parameter that is the baby’s key in Firebase Realtime database.
Now that we have that key in our hand we can use it to retrieve our baby and expose it through the public interface of our component class.
The next step is to update our baby component class to take that key and retrieve our baby from Firebase. Again on baby.component.ts
write:
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { AngularFireDatabase, FirebaseObjectObservable} from 'angularfire2/database';
import { Baby } from 'app/baby';
@Component({
selector: 'app-baby',
templateUrl: './baby.component.html',
styleUrls: ['./baby.component.css']
})
export class BabyComponent implements OnInit {
baby: FirebaseObjectObservable<Baby>;
constructor(private activatedRoute: ActivatedRoute,
private db: AngularFireDatabase) { }
ngOnInit() {
this.activatedRoute
.params
.subscribe(
p => {
const key = p['id'];
this.baby = this.db.object(`/babies/${key}`);
}
);
}
}
In this case we’ll use the FirebaseObjectObservable
type and the db.object
method because we have a single object that we want to retrieve: our Baby
. Since we expose a baby
property in our component class the template can now bind to that property and show the baby status in the screen.
If you take a look at your app using ng serve -o
you should see this beautiful masterpiece:
Now We can Take Care of The Baby
So we’ve got ourselves the baby status where we can see how well our baby and parenting is faring. The next thing that we want to do is add some controls so as a dad I can take care of my baby. Since this feels like a complete separate concern from displaying the baby status we create a new component:
PS> ng g c baby-care
This creates a brand new and shiny BabyCareComponent
. But before we dive into implementing the component there’s one detail that we still need to contend with: How are we going to load this component inside the BabyComponent
template?
Since I had some foresight in my initial prototyping I know that I want to have very similar views for my baby caring and my baby controlling. When going to this route baby/key/care
I want to get a view like this one:
<!-- baby status -->
<!-- baby caring lounge -->
And when going to this other route baby/key/control
I want to get a view like this other one:
<!-- baby status -->
<!-- baby control room -->
These two views are very similar and a perfect use case for nested routes. In this scenario we can take advantage of the Angular router and define a new <router-outlet>
inside the BabyComponent
where we can inject either the BabyCareComponent
or the future BabyControlRoomComponent
based on the url selected.
So if we update our BabyComponent
template as follows:
<section fxLayout="column" fxLayoutAlign="start center" fxLayoutGap="12px">
<md-card *ngIf="baby | async; let baby; else loading">
<md-card-title>Baby Status</md-card-title>
<md-card-content>
<app-baby-status [baby]="baby"></app-baby-status>
</md-card-content>
</md-card>
<ng-template #loading>Loading Baby Data...</ng-template>
<!-- inject Baby Care or Baby Control Room components -->
<router-outlet></router-outlet>
</section>
And our routing configuration in app-routing.module.ts
:
import { NgModule } from '@angular/core'
import { Routes, RouterModule } from '@angular/router'
import { BabiesComponent } from 'app/babies/babies.component'
import { BabyComponent } from 'app/baby/baby.component'
import { BabyControlRoomComponent } from 'app/baby-control-room/baby-control-room.component'
import { BabyCareComponent } from 'app/baby-care/baby-care.component'
const routes: Routes = [
{
path: 'babies',
children: [
{
path: '',
component: BabiesComponent,
},
{
path: ':id',
component: BabyComponent,
children: [
{
path: 'care',
component: BabyCareComponent,
},
/* We will uncomment this soon
{
path: 'control',
component: BabyControlRoomComponent
}
*/
],
},
],
},
{
path: '',
pathMatch: 'full',
redirectTo: 'babies',
},
{
path: '**',
redirectTo: 'babies',
},
]
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule],
})
export class AppRoutingModule {}
Now when we click on the Take care button in the BabiesComponent
view we will be magically transported to a view where we can see the status of our baby and below, the magical words: baby care works!.
Implementing the Baby Care Component
Let’s add some caring into our app. The dad will be able to use the following techniques to improve the stats of the baby:
- Feed: To remove hunger. Effect: reduce Hunger indicator
- Clean: To clean the baby’s outputs. Effect: reduce Shittiness indicator
- Sleep: To put the baby to sleep and give him the rest he or she needs. Effect: reduce sleepiness indicator
- Cuddle: The secret weapon in every parent arsenal that will not only affect all stats but will also increase a baby’s life indicator. Effect: reduce all indicators and increase life.
These actions will be represented by buttons in the BabyCareComponent
template within baby-care.component.html
:
<md-card>
<md-card-content>
<button md-raised-button color="primary" (click)="feedBaby()">Feed</button>
<button md-raised-button color="primary" (click)="cleanBaby()">Clean</button>
<button md-raised-button color="primary" (click)="sleepBaby()">Sleep</button>
<button md-raised-button color="accent" (click)="cuddleBaby()">Cuddle</button>
</md-card-content>
</md-card>
These buttons are bound to different methods in the underlying component class baby-care.component.ts
. We now need to implement them to update the baby’s stats. Let’s focus on one single of these methods for the sake of simplicity:
import { Component, OnInit } from '@angular/core';
import { FirebaseApp } from 'angularfire2';
import { ActivatedRoute } from '@angular/router';
@Component({
selector: 'app-baby-care',
templateUrl: './baby-care.component.html',
styleUrls: ['./baby-care.component.scss']
})
export class BabyCareComponent implements OnInit {
babyId;
constructor(private activatedRoute: ActivatedRoute,
private firebaseApp: FirebaseApp) { }
ngOnInit() {
this.activatedRoute
.parent
.params
.subscribe(
p => this.babyId = p['id']
);
}
feedBaby() {
const statRef = this.firebaseApp.database().ref(`/babies/${this.babyId}/hunger`);
statRef.transaction(stat => stat - 10 < 0 ? 0 : stat - 10);
}
// etc...
}
In the code above we do a couple of things:
- On component initialization we get a hold of the baby’s id by using the
ActivatedRoute
service. Since this is a nested route we use theactivantedRoute.parent
property to access the parent route parameters. - We take a direct reference to the babies
hunger
property within Firebase realtime database and we execute a transaction to update it so that the effect of a caring action decreases hunger in the baby by a factor of 10. We need to do a transaction because we plan to have several clients increasing and decreasing thathunger
value at the same time.
We now can extend the same logic to all of our caring methods:
import { Component, OnInit } from '@angular/core';
import { FirebaseApp } from 'angularfire2';
import { ActivatedRoute } from '@angular/router';
@Component({
selector: 'app-baby-care',
templateUrl: './baby-care.component.html',
styleUrls: ['./baby-care.component.scss']
})
export class BabyCareComponent implements OnInit {
babyId;
constructor(private activatedRoute: ActivatedRoute,
private firebaseApp: FirebaseApp) { }
ngOnInit() {
this.activatedRoute
.parent
.params
.subscribe(
p => this.babyId = p['id']
);
}
feedBaby() {
const statRef = this.firebaseApp.database().ref(`/babies/${this.babyId}/hunger`);
statRef.transaction(stat => stat - 10 < 0 ? 0 : stat - 10);
}
cleanBaby() {
const statRef = this.firebaseApp.database().ref(`/babies/${this.babyId}/shittiness`);
statRef.transaction(stat => stat - 10 < 0 ? 0 : stat - 10);
}
sleepBaby() {
const statRef = this.firebaseApp.database().ref(`/babies/${this.babyId}/sleepiness`);
statRef.transaction(stat => stat - 10 < 0 ? 0 : stat - 10);
}
cuddleBaby() {
const statRef = this.firebaseApp.database().ref(`/babies/${this.babyId}`);
statRef.transaction(baby => {
if (baby) {
baby.hunger = this.getDecreasedStat({statGetter: () => baby.hunger, modifier: 10});
baby.shittiness = this.getDecreasedStat({statGetter: () => baby.shittiness, modifier: 10});
baby.sleepiness = this.getDecreasedStat({statGetter: () => baby.sleepiness, modifier: 10});
baby.life = this.getIncreasedStat({statGetter: () => baby.life, modifier: 1});
}
return baby;
});
}
getDecreasedStat({statGetter, modifier}) {
return statGetter() > modifier ? statGetter() - modifier : 0;
}
getIncreasedStat({statGetter, modifier}) {
return statGetter() + modifier > 100 ? 100 : statGetter() + modifier;
}
}
If you go back to the browser you’ll now be able to see the following:
In order to do some testing, you can go into your Firebase Realtime Database Dashboard, modify the stats of the baby and then interact with the baby caring buttons to verify that indeed they are working. Yay! Victory!
Controlling Your Baby
The other side of things, the baby control room, will let you affect the needs, wants and desires of the baby and make him or her be hungry or sleepy.
Implementing the baby control room will be very similar to what we have just done in the previous section. We start by creating a new component:
PS> ng g c baby-control-room
We update the BabyControlRoomComponent
template within baby-control-room.component.html
to display a bunch of buttons:
<md-card>
<md-card-title>Baby Secret Control Room</md-card-title>
<md-card-content>
<button md-raised-button (click)="babyHungry()">I'm Hungry!!</button>
<button md-raised-button (click)="babyShitty()">I'm Dirty!!</button>
<button md-raised-button (click)="babySleepy()">I'm Sleepy!!</button>
</md-card-content>
</md-card>
And we implement these babyHungry
, babyShitty
and babySleepy
methods in the component class baby-control-room.component.ts
:
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { FirebaseApp } from 'angularfire2';
@Component({
selector: 'app-baby-control-room',
templateUrl: './baby-control-room.component.html',
styleUrls: ['./baby-control-room.component.scss']
})
export class BabyControlRoomComponent implements OnInit {
babyId: string;
constructor(private activatedRoute: ActivatedRoute,
private firebaseApp: FirebaseApp) { }
ngOnInit() {
this.activatedRoute
.parent
.params
.subscribe(
p => this.babyId = p['id']
);
}
babyHungry() {
const statRef = this.firebaseApp.database().ref(`/babies/${this.babyId}/hunger`);
statRef.transaction(stat => stat + 10);
}
babyShitty() {
const statRef = this.firebaseApp.database().ref(`/babies/${this.babyId}/shittiness`);
statRef.transaction(stat => stat + 10);
}
babySleepy() {
const statRef = this.firebaseApp.database().ref(`/babies/${this.babyId}/sleepiness`);
statRef.transaction(stat => stat + 10);
}
}
And that’s it. Remember to uncomment the small section that referred to this component in your routing configuration in app-routing.module.ts
and now you should be able to take care, and control your baby.
Now you can do some proper old school manual testing by opening a couple of browser windows, one with the caring, the other with the controlling and seeing how interacting with the buttons in either window affects the other. Awesome!
Adding More User Feedback With Snack Bars
Before we wrap up this part, we’re going do to a couple of things more.
First we’ll add some more feedback for our dads to give them some encouragement when they take care of the baby. Luckily for us, Angular Material provides a service that allows us to display messages to our users: the MdSnackBar.
The MdSnackBar
is very simple to use. You just need to inject the MdSnackBar
service into your component and then use the following method to display a message:
this.mdSnackBar.open('Hi you!')
Let’s update our BabyCareComponent
class to show a message to the dad every time they do a caring action:
import { Component, OnInit } from '@angular/core';
import { FirebaseApp } from 'angularfire2';
import { ActivatedRoute } from '@angular/router';
import { MdSnackBar } from '@angular/material';
import { RandomPickerService } from "app/random-picker.service";
const messages = ['Awesome Dad!', 'You rock Buddy!', 'Hell yeah!', 'Good job!!', 'Saaaavyyy', 'Dad of the Year!',
'Go get a beer, you deserve it!', 'Sweeeet!'];
@Component({
selector: 'app-baby-care',
templateUrl: './baby-care.component.html',
styleUrls: ['./baby-care.component.scss']
})
export class BabyCareComponent implements OnInit {
babyId;
constructor(private activatedRoute: ActivatedRoute,
private firebaseApp: FirebaseApp,
private mdSnackBar: MdSnackBar,
private randomPicker: RandomPickerService) { }
ngOnInit() {
this.activatedRoute
.parent
.params
.subscribe(
p => this.babyId = p['id']
);
}
feedBaby() {
this.showMessage();
const statRef = this.firebaseApp.database().ref(`/babies/${this.babyId}/hunger`);
statRef.transaction(stat => stat - 10 < 0 ? 0 : stat - 10);
}
cleanBaby() {
this.showMessage();
const statRef = this.firebaseApp.database().ref(`/babies/${this.babyId}/shittiness`);
statRef.transaction(stat => stat - 10 < 0 ? 0 : stat - 10);
}
sleepBaby() {
this.showMessage();
const statRef = this.firebaseApp.database().ref(`/babies/${this.babyId}/sleepiness`);
statRef.transaction(stat => stat - 10 < 0 ? 0 : stat - 10);
}
cuddleBaby() {
this.showMessage();
const statRef = this.firebaseApp.database().ref(`/babies/${this.babyId}`);
statRef.transaction(baby => {
if (baby) {
baby.hunger = this.getDecreasedStat({statGetter: () => baby.hunger, modifier: 10});
baby.shittiness = this.getDecreasedStat({statGetter: () => baby.shittiness, modifier: 10});
baby.sleepiness = this.getDecreasedStat({statGetter: () => baby.sleepiness, modifier: 10});
baby.life = this.getIncreasedStat({statGetter: () => baby.life, modifier: 1});
}
return baby;
});
}
getDecreasedStat({statGetter, modifier}) {
return statGetter() > modifier ? statGetter() - modifier : 0;
}
getIncreasedStat({statGetter, modifier}) {
return statGetter() + modifier > 100 ? 100 : statGetter() + modifier;
}
showMessage() {
this.mdSnackBar.open(this.randomPicker.pickAtRandom(messages), null, {duration: 3000});
}
}
Good! Now everytime the dad clicks on a care button he’ll get some nice encouragement like “You rock Buddy!” or “Sweeeet!”.
We can extend the same functionality to our BabyControlRoomComponent
:
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { AngularFireDatabase } from 'angularfire2/database';
import { FirebaseApp } from 'angularfire2';
import { MdSnackBar } from "@angular/material";
import { RandomPickerService } from "app/random-picker.service";
const messages = ['You are evil!', 'I bow before you Dr Evil!', 'Satanaaaas!!', "Keepin' it real!", "Wow that was mean!",
"You have no shame!", "OMG reaaaallllly?" ];
@Component({
selector: 'app-baby-control-room',
templateUrl: './baby-control-room.component.html',
styleUrls: ['./baby-control-room.component.scss']
})
export class BabyControlRoomComponent implements OnInit {
babyId: string;
constructor(private activatedRoute: ActivatedRoute,
private firebaseApp: FirebaseApp,
private mdSnackBar: MdSnackBar,
private randomPicker: RandomPickerService) { }
ngOnInit() {
this.activatedRoute
.parent
.params
.subscribe(
p => this.babyId = p['id']
);
}
babyHungry() {
this.showMessage();
const statRef = this.firebaseApp.database().ref(`/babies/${this.babyId}/hunger`);
statRef.transaction(stat => stat + 10);
}
babyShitty() {
this.showMessage();
const statRef = this.firebaseApp.database().ref(`/babies/${this.babyId}/shittiness`);
statRef.transaction(stat => stat + 10);
}
babySleepy() {
this.showMessage();
const statRef = this.firebaseApp.database().ref(`/babies/${this.babyId}/sleepiness`);
statRef.transaction(stat => stat + 10);
}
showMessage() {
this.mdSnackBar.open(this.randomPicker.pickAtRandom(messages), null, {duration: 3000});
}
}
The final step is to deploy the first version of our app to the internets and make it available to the world with the aid of the Angular cli and Firebase Hosting.
Deploying Your App With the Angular CLI and Firebase Hosting
In addition to offering a helping hand during development and testing, the Angular cli also allows you to create a production optimized build with a single command:
PS> ng build -prod -aot
The -prod
flag will tell angular to create a production build and the -aot
flag will enable Ahead of Time compilation which will result in a smaller package and faster Angular loading times. The process will take a short while and at the end you’ll have a production-ready version of your application under the dist
folder in your application root directory.
So now that we have a production-ready version of our app sitting cozy inside the dist
folder, the next thing is to use Firebase hosting to bring the baby-gotchi goodness to the world.
Using the Firebase Tools To Deploy Your App
The best way to learn how to deploy your app to Firebase is to go to the Firebase Console, select your project, then go to Hosting and click on the Get Started button.
A beautifully looking dialog will open and prompt you to install the firebase-tools
command line interface via npm. You install it globally:
PS> npm install -g firebase-tools
And after that you can use it through the firebase
command.
First, we’ll need to sign up:
PS> firebase login
And setup your app as a Firebase project using the init
command:
PS> firebase init
The init
command will trigger a super duper awesome (look at those flames) step-by-step menu that will guide you through the setup process:
You basically need to enable Firebase Hosting and select the dist
folder as the one that will hold your application files. As a result, the setup process will create a Firebase config file firebase.json
that should look like this:
{
"hosting": {
"public": "dist",
"rewrites": [
{
"source": "**",
"destination": "/index.html"
}
]
}
}
Where the rewrites
section redirects any url to the source of your app which ensures that the Angular router will take care of every possible route.
The next step is to use this configuration to deploy your site. Type the following command:
PS> firebase deploy
And magic! Your site is now deployed on Firebase. When the deployment finishes take a look at your site by following the URL that the Firebase tools cli displays on your console.
Congratulations! You have deployed your baby-gotchi app into Firebase! Rejoice!!
Up Next! Cloud Functions and PWAs
Let’s make a quick summary of what you have achieved. In this part of the series you continued developing the baby-gotchi baby simulator and created new components to see the baby status, take care and control the baby.
Along the way you learnt a lot of new concepts like:
- Using Angular route parameters and
ActivatedRoute
to load a baby detail view and get hold of the baby id - How to use the
AngularFireDatabase
andFirebaseObjectObservable
to get a single baby object and make it available to the template - How to refactor your Angular application to create smaller components with a single responsibility
- How to use lots of Angular Material components
- How to setup and use nested routes
- How to do transactional updates in Firebase to take care or control your baby
- Building a production ready Angular application with the help of the Angular cli
- The Benefits of Ahead of Time compilation
- Deploying your app to Firebase Hosting using the
firebase-tools
That was an awful lot of things! Congrats! Pat yourself in the back.
For our next feature, we will develop a baby heartbeat or baby lifecycle that updates the baby status over time using Firebase Cloud Functions. And in the last part of the series, we will give the baby-gotchi an awesome mobile experience by turning it into a Progressive Web App.
Until next time, take care and be awesome and kind!
Written by Jaime González García , dad, husband, software engineer, ux designer, amateur pixel artist, tinkerer and master of the arcane arts. You can also find him on Twitter jabbering about random stuff.