barbarian meets coding

WebDev, UX & a Pinch of Fantasy

From Idea to Reality in Under 50 Minutes (Mostly) With Angular and Firebase - Part II

| Comments

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.

From Idea To Reality wit Angular And Firebase

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:

  1. Created a new Angular app using the Angular cli
  2. Devised a way to model babys in code with the Baby class
  3. Built the BabiesComponent as a way to show a list of babies and give birth to new ones
  4. Setup Firebase using the AngularFire2 library
  5. Used Firebase Realtime Database to store our babies
  6. Added Angular Material and Angular Flex Layout to our app and made it look nicer

And so we arrived to this point:

From Idea To Reality wit Angular And Firebase - Babies Component

Just after adding those two buttons to Take Care and Control our babies with this corresponding template:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<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:

1
2
3
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 :

1
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:

1
2
3
4
5
<!-- 
  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:

1
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:

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

1
<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:

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

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

1
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:

1
2
3
4
5
  <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:

1
2
3
4
5
6
7
// 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:

1
2
3
4
5
6
7
8
9
10
11
12
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:

1
2
3
4
5
6
7
8
9
10
11
12
<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:

1
2
<!-- baby status here with the different indicators -->
<!-- take care actions -->

We can continue by substituting that with this:

1
2
<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:

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

1
2
3
4
5
/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:

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

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

From Idea To Reality wit Angular And Firebase - Baby Component with Status

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:

1
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:

1
2
<!-- 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:

1
2
<!-- 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:

1
2
3
4
5
6
7
8
9
10
11
12
<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:

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

1
2
3
4
5
6
7
8
 <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:

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

  1. 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 the activantedRoute.parent property to access the parent route parameters.
  2. 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 that hunger value at the same time.

We now can extend the same logic to all of our caring methods:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
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:

From Idea To Reality with Angular And Firebase - Baby Component Complete

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:

1
PS> ng g c baby-control-room

We update the BabyControlRoomComponent template within baby-control-room.component.html to display a bunch of buttons:

1
2
3
4
5
6
7
8
<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:

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

1
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
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:

1
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:

1
PS> npm install -g firebase-tools

And after that you can use it through the firebase command.

First, we’ll need to sign up:

1
PS> firebase login

And setup your app as a Firebase project using the init command:

1
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:

From Idea To Reality with Angular And Firebase -  Firebase Hosting

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:

1
2
3
4
5
6
7
8
9
10
11
{
  "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:

1
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 and FirebaseObjectObservable 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!

The next part in the series is ready!

Comments