A fullstack epic part II— displaying contents on a webpage using Angular

Developing the frontend side of a dummy app

Mauricio M. Ribeiro
7 min readJan 30, 2017

Introduction

In the Part I of this series we developed a REST API that retrieves data from a DB and delivers it. In this Part II we are going to develop a first blank lightweight version of a webpage used to display and create new data.

This webpage will evolve in another series of posts. But the goal here is only to get initiated in a JS framework — Angular 2 — and make it communicate with the backend side we developed in Part I.

Enough talk. Let’s go directly into action.

The extremely simple dummy musicstore website

The first version of this website, as already mentioned, will only communicate with the API in order to display the inventory of music albums and create new ones. In next versions we are going to focus on updates/deletions; as well as styling and/or server-side rendering. Let’s keep it simple for now.

Why Angular?

I love frontend world and its dynamic way of work. I have already discovered React (and Inferno). Now I want to discover another major actor of this side. Just it. No polemics for now.

Setup

Even if server-side rendering is extremely important, I will not focus on it right now (I have already even written a story about it). In order to develop/run it, I used Angular-CLI.

Be sure you have Node and NPM installed in your machine.

In order to install it, just follow the instructions on the link. Tha name of our project is musicstore.

# install Angular CLI
$ npm install -g angular-cli
# create the project and initialize it
$ ng new musicstore

Your schema should look like:

musicstore
|-- e2e
|-- node_modules
|-- src
| |-- app
| |-- assets
| |-- environments
| |-- main.ts
...

We are just going to add one component — our albums inventory — and one service to it.

The component will be in charge of the interface with the user, and the service will handle the requests to the API.

Let’s create them now.

The musicstore website in Angular

I really like Angular CLI because, with simple commands, we can create web components ready to use, in a structured way that is really handy for code organization.

Creating the component

So, let’s use it to create our Album component. Browse the app directory of the musicstore project and type the following command:

# browse musicstore/app
$ cd <path>/musicstore/app
# I like to place my components in a separate subdirectory
$ mkdir components && cd components
# generate the new component
$ ng g component album

This will create the following structure:

musicstore
|-- e2e
|-- node_modules
|-- src
| |-- app
| | |-- components
| | | |-- album
| | | | |-- album.component.css (for styling)
| | | | |-- album.component.html (for templating)
| | | | |-- album.component.spec.ts (for testing)
| | | | |-- album.component.ts (for coding)
...

For now, our focus will be in the templating and in the code/bootstraping.

First, the template (i.e. how our page will look like): open album.component.html, and paste the following content into it:

<div *ngIf="albums && albums.length">
<h3>Your music store is here!</h3>
<ul>
<li *ngFor="let album of albums">{{album.title}} - {{album.artist}} ({{album.year}})</li>
</ul>
</div>
<div>
You can also create discs here : <br />
<input type="text" placeholder="Title" [(ngModel)]='model.title' />
<input type="text" placeholder="Artist" [(ngModel)]='model.artist' />
<input type="number" placeholder="Year" [(ngModel)]='model.year' />
<button (click)='submitAlbum()'>Add</button>
</div>

What we are doing:

  • checking if there is a list of albums. If so, we iterate into it displaying the contents inside an <ul> element
  • displaying a form where the user will be able to insert new albums to the list. The <input> fields are doing a two-way binding to the model to be sent to the API. This special binding is done via the operator popularly called “banana-in-the-box” — [(ngModel)]. The submit button triggers a function called “submitAlbum

Second, the component logic (i.e. how our component behaves/interacts): open album.component.ts, and paste the following content into it:

import { Component, OnInit } from '@angular/core';
import {AlbumService} from '../../services/album-service.service';
import {Album} from './model/album';
import {Observable} from 'rxjs/Rx';@Component({
selector: 'app-album',
templateUrl: './album.component.html',
styleUrls: ['./album.component.css']
})
export class AlbumComponent implements OnInit {
albums: Album[];
private model = new Album('', '', '');
constructor(private albumService: AlbumService) { }ngOnInit() {
// Load albums
this.loadAlbums();
}
loadAlbums() {
// Get all albums from API
this.albumService.getAlbums()
.subscribe(
albums => this.albums = albums, //Bind to view
err => {
// Log errors if any
console.log(err);
});
}
submitAlbum(){
// Variable to hold a reference of addAlbum
let commentOperation:Observable<Album[]>;
console.log(this.model);
// Subscribe to observable
this.albumService.addAlbum(this.model).subscribe(
() => {
// empty object
this.model = new Album('', '', '');
// reload album list
this.loadAlbums();
},
err => {
// Log errors if any
console.log(err);
});
}
}

What we are doing here is:

  • defining our component, telling which selector makes it display on the page, its template and style urls
  • creating a new Album model that will bind to the view and have the content of any newalbum to create (via the form of our template)
  • initializiation, loading the albums list: we do an async call to the service (AlbumService, the next thing we are going to create here), which will contact the API in order to retrieve the list of albums. Then, we subscribe an Observable to it in order to attach the service response — when it arrives — to the list of albums to be bound in the template.
  • declaring the submitAlbum function, to be triggered by the submit of the insertion form. It calls the AlbumService in order to submit the new Album to the API (which then will insert it into the DB). Then it subscribes an observable to the response, cleaning the form model and reloading the updated list of albums.

Third, we have to create an Album model, which will represent the structure to our music albums:

# browse to album component folder
$ cd <path>/musicstore/src/app/components/album
# create a subdirectory 'model'
$ mkdir model

Inside the model subdirectory, create a file album.ts and paste the following contents:

export class Album {
public id: string;
constructor(
public title: string,
public artist:string,
public year:string
){}
}

This is all for the component. Now, as we have just seen, we are going to need a service in order to retrieve/create albums, via communications with the API.

Let’s do it!

Creating the service

As we put the components into a dedicated folder, we are going to do the same for the service:

# browse musicstore/app
$ cd <path>/musicstore/app
# I like to place my services in a separate subdirectory
$ mkdir services && cd services
# generate the new services
$ ng g service album-service

Jump into the newly created services directory, and put the following contents into album-service.service.ts

// album-service.service.ts
import { Injectable } from '@angular/core';
import { Http, Response, Headers, RequestOptions } from '@angular/http';
import {Observable} from 'rxjs/Rx';
import {Album} from '../components/album/model/album';
@Injectable()
export class AlbumService {
constructor(private http: Http) { }// this is the endpoint of our REST API service in Go
private albumsUrl = 'http://localhost:9000/';
// Fetch all existing albums
getAlbums() : Observable<Album[]> {
// ...using get request
return this.http.get(this.albumsUrl)
// ...and calling .json() on the response to return data
.map((res:Response) => res.json())
//...errors if any
.catch((error:any) => Observable.throw(error.json().error || 'Server error'));
}
// Add a new album
addAlbum (body: Object): Observable<Object> {
let bodyString = JSON.stringify(body); // Stringify payload
let headers = new Headers({ 'Content-Type': 'text/plain' }); // ... Set content type as text in order not to trigger preflight OPTIONS request
let options = new RequestOptions({ headers: headers }); // Create a request option
return this.http.post(this.albumsUrl, body, options) // ...using post request
.map((res:Response) => res) // ...and returning data
.catch((error:any) => Observable.throw(error || 'Server error')); //...errors if any
}
}

As already explained before, we are using Observables in order to notify all possible observers when we have responses from the server. The calls to the Go API are done through the angular/http library.

Note: when POSTing to the web server, we do not set Content-Type header to application/json in order not to send an OPTIONS preflight request. This would return an error from the server. Instead, we set text/plain

And that’s it! Now we just have to bootstrap our component and our service to the main app, and we are going to be able to test it!

Bootstraping everything

We can integrate our new stuff inside the application with two steps:

First: open app.module.ts inside the app folder and add the lines in bold:

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';
import { AppComponent } from './app.component';
import { AlbumComponent } from './components/album/album.component';
import { AlbumService } from './services/album-service.service';
@NgModule({
declarations: [
AppComponent,
AlbumComponent
],
imports: [
BrowserModule,
FormsModule,
HttpModule
],
providers: [AlbumService],
bootstrap: [AppComponent]
})
export class AppModule { }

Great! Our component and our service provider are declared in our application.

Second: open app/app.component.html and insert the Album Component selector in order to display it in the web page:

<h1>
{{title}}
</h1>
<app-album></app-album>

Now we are ready to run it.

Run it

Be sure that you still have your Mongo and REST API up and running.

Then, open a terminal and browse to the root folder of this application

# browse musicstore
$ cd <path>/musicstore
# serve the app
$ ng serve

Then, open your browser at http://localhost:4200/ and check the contents.

Should you have any data in the DB, this data will be displayed on the page.

If you insert any new album, this one will be inserted into the DB and the page properly updated.

What now?

Our backend and frontend are ready, now we can dockerize them and play with replication inside Kubernetes. This will be done in Part III.

Useful links

Angular 2 Tutorial Guide

Angular quickstart

RxJS Observables / ReactiveX

Angular 2 HTTP Requests with Observables

Observables vs. Promises

--

--