HelloBeer goes Angular – Part I

You can never have enough clients for your beers, right? In our last post we served our Spring server-side clients mostly interested in Java flavored beers. Lets see how difficult it is to generate stub code for our Swagger API geared towards a more client-side oriented beer drinker. In this post we’ll generate some Typescript stubs on which we’ll try to build an Angular front-end application. Now, I’m not an expert in Angular so I obviously ran into a couple of rookie mistakes. In this blogpost I’m gonna show you the way I build the application and I’ll also point out some of the pitfalls I ran into.

Needless to say but the code will be available on GitHub. Since this blogpost is gonna be pretty big, I’ve decided to split it up into two parts. In this first part we’re gonna generate the stub and consume the GET part of the REST service. The link to the final Github code will be posted in part II.

New project

So let’s start brewing. First things first. For starters create our new project and call it hb-angular-client

ng new hb-angular-client

You can fire up your first incarnation by hitting “ng serve”, check http://localhost:4200 and at this time you should see a working application. Next, we’re gonna recreate the front-end we build with Spring MVC in our previous blogpost and make our Angular app look similar. The final version will look like this:

Screenshot-2018-3-3 HelloRoger

Generating the Typescript stub

This is probably the most important part. The rest is just building Angular. To generate the stub code for our Swagger API, I’m gonna use the angular4-swagger-client-generator. Since I’ve got a feeling I’m gonna use this generator more than just for this project, I’ll install it globally (hence the -g):

npm install -g angular4-swagger-client-generator

Now let’s generate our stub code. Make sure the output directory already exists. For our source we’re pointing at our Swagger api available on SwaggerHub. Note that opposed to the Maven generation in our previous blogpost where we took the yaml file as the source, we’re now pointing at the json file:

a4apigen -u https://app.swaggerhub.com/apiproxy/schema/file/rphgoossens/hello-beer/1.0/swagger.json -o ./src/app/services/beer

The generator will create a model file containing the Beer and Type interfaces and the index.ts file that will contain the methods with which we can communicate with our REST service:

  /**
  * Method addToBeerRepositoryUsingPOST
  * @param beer beer
  * @return Full HTTP response as Observable
  */
  public addToBeerRepositoryUsingPOST(beer: Beer): Observable {
    let uri = '/hello-beer/1.0/beer';
    let headers = new HttpHeaders();
    let params = new HttpParams();
    return this.sendRequest('post', uri, headers, params, JSON.stringify(beer));
  }

  /**
  * Method getAllBeersUsingGET
  * @param type type
  * @return Full HTTP response as Observable
  */
  public getAllBeersUsingGET(type: string): Observable {
    let uri = '/hello-beer/1.0/beers';
    let headers = new HttpHeaders();
    let params = new HttpParams();
    if (type !== undefined && type !== null) {
      params = params.set('type', type + '');
    }
    return this.sendRequest('get', uri, headers, params, null);
  }

Consuming the GET method

Let us first build the top part of the app, i.e. the table that will show us the beers currently served by the REST service. After putting the style sheets in place, we need to build the app.component.html and the app.component.ts code.
The typescript file will contain a call to the generated REST service stub. To inject the generated service in an Angular component, we first need to declare it as a Provider. Since we’ll be needing this service throughout the entire application, let’s declare it at the module level. Alter the app.module.ts like this:

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

import {AppComponent} from './app.component';

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

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

Note that the REST service is dependent on the HttpClientModule. I’ve added that one as an import as well.

Next, let’s build the component code. We need to import the model and service classes, inject the service and call it from the ngOnInit method to populate the beers array that will be exposed via our html page on startup.
This is the app.component.ts code so far. The important parts are highlighted.

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

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

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
  public beers: Beer[];

  constructor(private apiClientService: ApiClientService) {
  }

  ngOnInit() {
    this.apiClientService.getAllBeersUsingGET(null)
      .subscribe(resp => {
        this.beers = resp.body;
      }, error => {
        console.log(error);
      });
  }
}

Last piece of the puzzle is the app.component.html page. It’s pretty straightforward. The most important part is the part for exposing the beers

<tr *ngFor="let beer of beers">
  <td><span>{{beer.name}}</span></td>
  <td><span>{{beer.type}}</span></td>
  <td><span>{{beer.brewery}}</span></td>
  <td>
    <a href="" class="btn btn-success custom-width">edit</a>
  </td>
  <td>
    <a href="" class="btn btn-danger custom-width">delete</a>
  </td>
</tr>

Taking a look at the app, we can see that our beers are being displayed quite nicely (of course after our hello-beer-server Spring Boot REST service has been spun up):

Screenshot-2018-3-3 HbAngularClient

CORS

One of the pitfalls – one that every REST developer will run into at least once in his lifetime – is the infamous Cross-Origin Request Block error. In my first attempt the GET request data wasn’t showing in my application.

Checking the Firefox Developer Tools (Network Tab) I could see that the REST service was being called, responding with a couple of Beers.

The console was showing me this error though:

Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://localhost:8080/hello-beer/1.0/beers. (Reason: CORS header ‘Access-Control-Allow-Origin’ missing).

Basically what this boils down to, is that you can’t make a service request from a localhost app when that service is also running on localhost albeit on a different port. The browser simply will not accept the response.

The easiest solution is to change the REST service to let it generate responses that the browser will accept. So that’s what I did. I added the following configuration to my Spring Boot Rest Service:

@Configuration
public class HelloBeerConfiguration {

    @Bean
    public WebMvcConfigurer corsConfigurer() {
        return new WebMvcConfigurerAdapter() {
            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/**");
            }
        };
    }
}

Summary

In this first part of a blog post series of 2 we’ve generated a Typescript client for our Swagger API and build a small Angular app that consumes the REST service implementing the Swagger API, on top of it. So far we’ve only consumed the GET method of the REST service.

In our 2nd part we’re gonna consume the POST part, add some error handling and as a bonus some i18n and finally we’re gonna wrap it all up. So stay tuned!!

References

Advertisements

2 thoughts on “HelloBeer goes Angular – Part I

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