HelloBeer goes Angular – part II

Alright, let’s keep going. Our Angular clients are getting thirsty! In our previous blog post we’ve built the first part of our Angular app consuming a REST service based on a Swagger API. Let’s finish the app.

Consuming the POST method

To POST beers we’re gonna add a new Form component to our Angular app. We could have put the code in the main app component, but it’s always a good idea to keep your components small. So, to create the new component – we’ll call it edit-beer – just hit:

ng generate component edit-beer

First we’ll fill in the edit-beer.component.ts component code. We need a beer model variable, a list of beerTypes to serve as a drop-down list and a method for posting a beer. Here you go:

import {Component, OnInit} from '@angular/core';

import {ApiClientService} from "../services/beer";
import {Beer, Type} from '../services/beer/models';

@Component({
  selector: 'app-edit-beer',
  templateUrl: './edit-beer.component.html',
  styleUrls: ['./edit-beer.component.css']
})
export class EditBeerComponent implements OnInit {

  public beer: Beer = {} as Beer;
  public beerTypes: string[];

  constructor(private apiClientService: ApiClientService) {
    this.beerTypes = (Object.keys(Type));
  }

  /**
   * Call REST service to POST a new beer
   */
  public postBeer(): void {
    this.apiClientService.addToBeerRepositoryUsingPOST(this.beer)
      .subscribe(resp => {
        this.reset();
      }, (error => {
        console.log(error);
      }));
  }

  private reset(): void {
    this.beer = {} as Beer;
  }

  ngOnInit() {
  }

}

The first time I was testing with the generated code and used the Type interface in my typescript code, I got these messages from IntelliJ:

Screenshot from 2018-02-20 21-09-10

Apparently I was using an older version of typescript, that couldn’t handle string enums. Updating to the newest version of typescript fixed this issue.

Let’s put the form on the html component – edit-beer.component.html – that will enable us to post new beers to our inventory:

<div>
  <div class="well lead">Add a beer</div>
  <form method="POST" class="form-horizontal">
    <div class="row">
      <div class="form-group col-md-12">
        <label class="col-md-3 control-lable" for="input.name">Name</label>
        <div class="col-md-7">
          <input [(ngModel)]="beer.name" type="text" id="input.name" name="name" class="form-control input-sm"/>
        </div>
      </div>
    </div>
    <div class="row">
      <div class="form-group col-md-12">
        <label class="col-md-3 control-lable" for="input.type">Beer type</label>
        <div class="col-md-7">
          <select [(ngModel)]="beer.type" id="input.type" name="type" class="form-control input-sm">
            <option *ngFor="let beerType of beerTypes" [value]="beerType">{{beerType}}</option>
          </select>
        </div>
      </div>
    </div>
    <div class="row">
      <div class="form-group col-md-12">
        <label class="col-md-3 control-lable" for="input.brewery">Brewery</label>
        <div class="col-md-7">
          <input [(ngModel)]="beer.brewery" type="text" id="input.brewery" name="brewery" class="form-control input-sm"/>
        </div>
      </div>
    </div>
    <div>
      <input (click)="postBeer()" title="OK" class="btn btn-primary custom-width float-right"/>
    </div>
  </form>
</div>

Note the postBeer() method coupled to the button click event binding.

Add the edit-beer component at the bottom of the app component and fire up the app to see my next rookie mistake appearing in the console:

Can't bind to 'ngModel' since it isn't a known property of 'input'.

This took me while to figure out. The error – though a bit vague – is all about forgetting to import the FormsModule in the app module, so let’s fix that:

import {NgModule} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import {FormsModule} from "@angular/forms";
import {HttpClientModule} from '@angular/common/http';

import {AppComponent} from './app.component';
import {EditBeerComponent} from './edit-beer/edit-beer.component';

import {ApiClientService} from './services/beer/index';

@NgModule({
  declarations: [
    AppComponent,
    EditBeerComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
    HttpClientModule
  ],
  providers: [ApiClientService],
  bootstrap: [AppComponent]
})
export class AppModule {
}

Now after running the app again, we’ll see a familiar screen appearing:
Screenshot-2018-3-4 HbAngularClient

You can enter a new beer and after hitting the OK button, the beer is posted. When you refresh the screen (don’t worry we’re gonna fix that), you can see that the list is updated with the beers you just added.

Updating the beer inventory instantly

Hitting refresh to update the beer inventory after adding a new beer every time, is bound to piss some clients off, especially when one of ’em is an angry drunk. So let’s fix that asap!

Now how do we do that? We obviously need a way to communicate between the edit-beer component and the app component: events to the rescue!

The application will require a few changes. Let start with the edit-component that will fire the event. This is the new code:

import {Component, EventEmitter, OnInit, Output} from '@angular/core';

import {ApiClientService} from "../services/beer";
import {Beer, Type} from '../services/beer/models';

@Component({
  selector: 'app-edit-beer',
  templateUrl: './edit-beer.component.html',
  styleUrls: ['./edit-beer.component.css']
})
export class EditBeerComponent implements OnInit {

  @Output() onBeerPosted = new EventEmitter<Beer>();

  public beer: Beer = {} as Beer;
  public beerTypes: string[];

  constructor(private apiClientService: ApiClientService) {
    this.beerTypes = (Object.keys(Type));
  }

  /**
   * Call REST service to POST a new beer
   */
  public postBeer(): void {
    this.apiClientService.addToBeerRepositoryUsingPOST(this.beer)
      .subscribe(resp => {
        this.pushBeer(resp.body)
      }, (error => {
        console.log(error);
      }));
  }

  private pushBeer(beer: Beer): void {
    // add beer to app
    this.onBeerPosted.emit(beer);
    this.reset();
  }

  private reset(): void {
    this.beer = {} as Beer;
  }

  ngOnInit() {
  }

}

The REST service will return a beer after it’s been posted. So we’ll gonna send that new beer on the event to the app component. That guy will use it to update its beers list.

The event will be emitted from the edit-beer.component, so reflect that in the app.component.html, here you’ll glue the methods from the app and edit-beer components together:

<app-edit-beer (onBeerPosted)="onBeerPosted($event)"></app-edit-beer>

Last piece of the puzzle is catching the event in the app component. Just add this code snippet to the app.component.ts and you’re done.

public onNewBeer(beer: Beer): void {
  this.beers.push(beer);
}

Test the app again and see the magic happening when you add a new beer. Flashy is it not?

Summary

We’re almost there. I was planning to finish this blog in 2 parts, but like with every great blockbuster movie of the 80s, we need a third installment.

In the last part of our HelloBeer goes Angular trilogy we’ll wrap it all up with some user-friendly error handling. We’ll also add i18n to our app to improve upon those ugly beer types. And then we’re really done!

Advertisements

One thought on “HelloBeer goes Angular – part II

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s