September 06, 2016

Angular 2 and Typescript Conference Browser Application

by Jscrambler

Angular 2 and TypeScript logos

Introduction

Angular 1 has been a phenomenal success in the world of Single-Page Applications (SPA).

This was in part due to its simplicity and easy to understand syntax. It was also aided by the fact that it is a Google backed product which instills some level of confidence in developers. After the team at Google decided to come up with version two of the popular framework, they decided to change things drastically.

Angular 2 takes a very different approach in building single-page applications. Let this not stop you from diving into it because Angular 2 is geared towards using some of the best tools out there in the industry from some of the most brilliant companies.

For example, Angular 2 fully supports typescript as a first class citizen. Even though typescript is not required to use the framework, I must admit that the framework feels easier and more intuitive to use when used with typescript. In addition to that, you tend to write less code.

Also, a lot of the examples and blog posts out there are mostly written in typescript, so that alone is a reason to stick to using it when just starting out. Another adoption is the new angular-cli which is based on the already brilliant ember-cli. The command line helps with rapid development and shields you from having to make many small decisions upfront.

In light of that, we will be using typescript and the angular-cli to build a conference browsing application. The data for the conference will come from an Express.js backend application which I have prepared upfront with some mocked data. We will connect to it using Angular 2′s http module to the Express.js backend to get a list of speakers so we can browse each of them in details and see their respective talks and time.

Protect Your Angular App

A simple application but it will touch on many aspects of Angular 2 like components, the router, component scoped styles, data-binding, helpers or pipes and much more, so let’s dive in now and get started.

Bootstrap

Install angular-cli

First of all, make sure you have node.js and npm installed.

Head over to your terminal and install angular-cli using the following command:

npm install - g angular - cli@ latest  

Now we should have a new ng command available to us. Just type ng and a whole bunch of commands should show up. If so, then you have successfully installed the tool.

Setup new angular conference application

As of this article the latest version of angular is 2.0.0-rc.4. Create a new application using the command:

ng new angular2 - conference  

You should see that ng created a new project for you and installed the node dependencies.

The backend

Next, let’s setup our backend. Clone the basic setup I have created here. Install the dependencies using npm install, and start the backend server using node server. You should now have a service running at localhost:3000.

Navigate to the url localhost:3000/speakers. You should see a list of speakers data in json format in your browser. If that is the case, our backend setup is done. Leave the service running in the background as it will be needed soon.

Application structure

Let’s quickly go through the important parts of our newly bootstrapped application.
The folder we are concerned with in this article is the src folder as it will contain all our components, services, styles, templates and typescript class files.

The angular-cli know to look for files in there. Speaking of the cli, lets start the development server by navigating to our application folder (angular2-conference) and using:

ng server  

This should start a server which watches the files in our application and automatically refreshes the browser when there are any changes. If all goes well, you should see some text on your browser when you visit localhost:4200 which is the cli’s default development URL.
In terms of our application, we will have three components. One is the main component which was automatically created for us when we bootstrapped a new application. The second one is the speakers-component, which will list out the speaker names along with how many talks they have. The third one will be a speaker-detail-component which will show more details for a particular speaker and show his list of talks along with further details as well. Now that we understand our app structure a bit more, let’s create our first component.

Speakers component

Creates the speakers-component using:

ng generate component speakers  

This should create a folder called speakers in the src/app folder. It will have a bunch of files there but the ones to be concerned with are speakers.component.ts, speakers.component.css, speakers.component.html. Let’s see how we can query for speakers from the backend and list them.

In the project root directory create a service folder for speakers using

mkdir src / app / speakers / services  

Next create the actual service class using

ng generate class speakers / services / speaker - service  

In the new file speakers/services/speaker-service.ts put there the following content:

import {  
    Injectable
}
from '@angular/core';  
import {  
    Http, Response
}
from '@angular/http';  
import {  
    Speaker
}
from '../models/speaker';  
import {  
    Talk
}
from '../models/talk';

@
Injectable()  
export class SpeakerService {  
    speakers: Speaker[];
    talks: Talk[];
    constructor(private http: Http) {}

    public getAll() {
        return this.http.get("http://localhost:3000/speakers")
            .toPromise().then((response) => {
                return this.mapGetAll(response);
            });
    }

    public getOne(id) {
        return this.http.get("http://localhost:3000/speakers/" + id)
            .toPromise().then((response) => {
                return this.mapGetOne(response);
            });
    }

    public mapGetAll(resp) {
        let speakers = resp.json().speakers.map((speaker) => {
            let talks = this.prepareSpeakerTalks(speaker, 
                 resp.json().talks);
            return new Speaker(speaker.id, speaker.firstName, 
                      speaker.lastName, talks, speaker.summary);
        });

        return speakers;
    }

    public mapGetOne(resp) {
        let speaker = resp.json().speaker;
        let talks = this.prepareSpeakerTalks(speaker, resp.json().talks);

        speaker = new Speaker(speaker.id, speaker.firstName, 
                       speaker.lastName, talks, speaker.summary);

        return speaker;
    }

    public prepareSpeakerTalks(speaker, talks) {
        let speakerTalks = [];

        talks.forEach((talk) => {
            if (speaker.id === talk.speaker) {
                speakerTalks.push(new Talk(talk.id, talk.title, 
                  talk.date, talk.summary));
            }
        });

        return speakerTalks;
    }
}

We have two methods called getAll and getOne. The first one gets a list of speakers while the second retrieves a single speaker by id. The other functions are helper functions for mapping the data.
Inspecting these two methods, we see that we are calling toPromise function on the HTTP method return.
This is because in Angular 2, HTTP calls return observables instead of promises so we need to make that call to convert them into promises. Observables are useful but using promises will do just fine in our application.

We are also using the Speaker and Talk models which we imported above but are yet to create. Let’s do that now.

Create the Speaker model using the following commands:

mkdir src / app / speakers / models  

and

ng generate class speakers / models / speaker  

Generate the talk model using:

ng generate class speakers / models / talk  

In the speaker model class speakers/models/speaker.ts paste there:

export class Speaker {  
    id: number;
    firstName: string;
    lastName: string;
    talks: number[];
    summary: string;

    constructor(id, firstName, lastName, talks, summary) {
        this.id = id;
        this.firstName = firstName;
        this.lastName = lastName;
        this.talks = talks;
        this.summary = summary;
    }
}

and paste in the talk model:

import {  
    Speaker
}
from './speaker';  
export class Talk {  
    id: number;
    title: string;
    date: string;
    summary: string;

    constructor(id, title, date, summary) {
        this.id = id;
        this.title = title;
        this.date = date;
        this.summary = summary;
    }
}

These two models are simply plain typescript classes with constructors
that accept properties.

Modify the speakers class file speakers.component.ts with the following content:

import {  
    Component, OnInit
}
from '@angular/core';  
import {  
    Speaker
}
from './models/speaker';  
import {  
    SpeakerService
}
from './services/speaker-service';  
import 'rxjs/add/observable/throw';  
import 'rxjs/add/operator/catch';  
import 'rxjs/add/operator/map';  
import 'rxjs/add/operator/toPromise';

@
Component({  
    moduleId: module.id,
    selector: 'speakers',
    templateUrl: 'speakers.component.html',
    styleUrls: ['speakers.component.css']
})
export class SpeakersComponent implements OnInit {  
    public speakers: Speaker[];
    constructor(private speakerService: SpeakerService) {}

    ngOnInit() {
        this.speakerService.getAll().then(speakers => {
            this.speakers = speakers
        });
    }

}

This calls the speaker-service and gets the list of speakers which have been prepared as instances of speaker model classes and assigns it to a speakers property of the component. The ngOnInit is a way of running code when the component has initiliazed.

Modify the template file speakers.component.html with the following content:

<div>  
    <h2>Speakers</h2>
    <div>
        <p *ngFor="let speaker of speakers">
         <a class="speaker">{{ speaker.firstName + " " 
                   + speaker.lastName }}</a>
         <span class="talks-counter">{{speaker.talks.length}} talk(s)
         </span>
        </p>
    </div>
</div>  

This loops over the speakers array and show their firstName, lastName and number of talks they have. We plan to convert these into links which we can click to see a more detailed information of that particular speaker.

Finally to use our speakers-component, we need to tell the main app-component about it and do some other setup work. Let’s make sure the HTTP service and speaker services are available throughout our application and easily injectable.

Do that by modifying main.ts as so

import {  
    bootstrap
}
from '@angular/platform-browser-dynamic';  
import {  
    enableProdMode
}
from '@angular/core';  
import {  
    AppComponent, environment
}
from './app/';  
import {  
    HTTP_PROVIDERS
}
from '@angular/http';  
import {  
    SpeakerService
}
from './app/speakers/services/speaker-service';

if (environment.production) {  
    enableProdMode();
}

bootstrap(AppComponent, [  
    HTTP_PROVIDERS, SpeakerService
]);

Now let’s tell the app-component about our new component. Modify
app.component.ts to

import {  
    Component
}
from '@angular/core';  
import {  
    SpeakersComponent
}
from './speakers';

@
Component({  
    moduleId: module.id,
    selector: 'app-root',
    templateUrl: 'app.component.html',
    styleUrls: ['app.component.css'],
    directives: [SpeakersComponent]
})
export class AppComponent {  
    title = 'test-angular2-conference works!';
}

The only change we’ve made is to add a directives property to the @component declaration and adding there the imported SpeakersComponent class.

Now let’s use the component in app.component.html. Modify the template file to

<div class="page">  
    <speakers></speakers>
</div>  

Looking at your browser window, you should now see a list of users along with their talk count, which by default is 2. If that is not the case and you are getting errors make sure to remove all the code on .spec.ts files as these are used for unit testing and we will not cover them in this article.

Now let’s move onto the speaker-details components.

Speaker Details Component

Create the component using:

ng generate component speaker - details  

In the new class file speaker-details/speaker-details.component.ts, let’s make a call to the server for a single speaker, so modify it like so:

import {  
    Component, OnInit
}
from '@angular/core';  
import {  
    ActivatedRoute
}
from '@angular/router';  
import {  
    SpeakerService
}
from '../speakers/services/speaker-service';  
import {  
    Speaker
}
from '../speakers/models/speaker';

@
Component({  
    moduleId: module.id,
    selector: 'speaker-details',
    templateUrl: 'speaker-details.component.html',
    styleUrls: ['speaker-details.component.css']
})
export class SpeakerDetailsComponent implements OnInit {  
    speaker: Speaker;
    constructor(
        private speakerService: SpeakerService,
        private route: ActivatedRoute
    ) {}

    ngOnInit() {
        this.route.params.subscribe(params => {
            let speakerId = params['id'];
            this.speakerService.getOne(speakerId).then(speaker => 
               this.speaker = speaker);
        });
    }

}

Here we are extracting the speaker id from the url parameters and making a call to the server for that speaker using the speaker-service.

Next, in the template file for the component speaker-details.component.html, modify it to look like this:

<div>  
    <h2>Speaker Details</h2>
    <div *ngIf="speaker" class="details">
        <p>Name: {{ speaker.firstName }} {{ speaker.lastName }}</p>
        <p>{{ speaker.summary }}</p>
    </div>
    <div *ngIf="speaker" class="talks">
        <h3>Talks</h3>
        <div *ngFor="let talk of speaker.talks" class="talk">
            <p><span>Title:</span> <span>{{talk.title}}</span>
            </p>
            <p>
              <span>Date:</span>
              <span>{{talk.date | date:"yMMMdjms"}}</span>
            </p>
            <p>{{ talk.summary }}</p>
        </div>
    </div>
</div>  

This displays the details for the current speaker and lists out more details about each of their talks. In this template, we are making use of the *ngIf directives which is used to show or remove dom elements based on a property value in the templates component class.

Setup the angular 2 router

Next let’s setup the router so we can easily navigate between pages. This will allow us to click on a speaker name on the homepage and we’ll be taken to the details page for that speaker.

Manually create a router file called routes.ts directly inside the app folder.

In there paste the following code:

import {  
    provideRouter, RouterConfig
}
from '@angular/router';  
import {  
    SpeakersComponent
}
from './speakers/index';  
import {  
    SpeakerDetailsComponent
}
from './speaker-details/index';

const routes: RouterConfig = [{  
    path: 'speakers',
    component: SpeakersComponent
}, {
    path: 'speakers/:id',
    component: SpeakerDetailsComponent
}, {
    path: '',
    redirectTo: '/speakers',
    pathMatch: 'full'
}];

export const APP_ROUTE_PROVIDERS = [  
    provideRouter(routes)
];

Here we are setting up three pages. One for the homepage, one for the speakers page and the last one being the speaker-details page.

When we hit the homepage, we have instructed the router to redirect us to the speakers list page and that will automatically load the speakers-component.
When we click a specific speaker, we’ll be taken to the speaker-details page and the speaker-details-component will be automatically loaded to show the information for that speaker.

That’s all the setup we need for the router, however we need to modify the speakers-component so that it links to the speaker-details page.

Modify its class file by adding a directive property to its component declaration like so:

...More code
    ...
import 'rxjs/add/operator/toPromise';  
import {  
    ROUTER_DIRECTIVES
}
from '@angular/router';

@
Component({  
        moduleId: module.id,
        selector: 'speakers',
        templateUrl: 'speakers.component.html',
        styleUrls: ['speakers.component.css'],
        directives: [ROUTER_DIRECTIVES]
    })
    ...
    ...More code

Don’t forget to import the ROUTER_DIRECTIVES first.

Modify the template file to look like the following:

<div>  
    <h2>Speakers</h2>
    <div>
        <p *ngFor="let speaker of speakers">
          <a class="speaker" href="#" [routerLink]="['/speakers', 
             speaker.id]">
            {{ speaker.firstName + " " + speaker.lastName }}</a> 
           <span class="talks-counter">
            {{speaker.talks.length}} talk(s)</span>
        </p>
    </div>
</div>  

All we have added is the part:

[routerLink] = "['/speakers', speaker.id]"

This will evaluate the speaker model and create the link to the appropriate speaker using the route.

Now import the route.ts and load it in src/main.ts. So modify the main.ts to look like:

import {  
    bootstrap
}
from '@angular/platform-browser-dynamic';  
import {  
    enableProdMode
}
from '@angular/core';  
import {  
    AppComponent, environment
}
from './app/';  
import {  
    HTTP_PROVIDERS
}
from '@angular/http';  
import {  
    SpeakerService
}
from './app/speakers/services/speaker-service';  
import {  
    APP_ROUTE_PROVIDERS
}
from './app/routes';

if (environment.production) {  
    enableProdMode();
}

bootstrap(AppComponent, [  
    HTTP_PROVIDERS, SpeakerService, APP_ROUTE_PROVIDERS
]);

Finally replace app.component.html by the following code:

<div class="page">  
    <router-outlet></router-outlet>
</div>  

and modify the app.component.ts file to look like this:

import {  
    Component
}
from '@angular/core';  
import {  
    ROUTER_DIRECTIVES
}
from '@angular/router';

@
Component({  
    moduleId: module.id,
    selector: 'app-root',
    templateUrl: 'app.component.html',
    styleUrls: ['app.component.css'],
    directives: [ROUTER_DIRECTIVES]
})
export class AppComponent {  
    title = 'test-angular2-conference works!';
}

Now we have a list of speaker links on the homepage which when clicked will take you to the speaker-details page for that speaker.

Component Scoped Styling

Even though we have a functional application, it does not look pretty. Let’s add some styling to both the speaker list page and the speaker details page.
Angular 2 has component-level styles which only apply to elements in the component. You can still have global styles but we do not need them in this application. So with that in mind, let’s style the app component which has a class of page in its template.

In the css file app.component.css add there

.page {
    max - width: 600 px;
    margin - left: auto;
    margin - right: auto;
}

This will center the page of our application. Next style the links for speakers.
In the file speakers.component.css, add there:

.speaker {
    margin - bottom: 40 px;
    padding: 5 px 20 px;
    border - radius: 7 px;
    background - color: #0093ff;
    color: white;
}

The speaker links look much prettier now. Finally, navigate to any speakers page by clicking on their name. In the file speaker-details.component.css add this:

.details {
    margin - bottom: 40 px;
    padding: 5 px 20 px;
    border - radius: 7 px;
    margin - bottom: 15 px;
    background - color: #0093ff;
    color: white;
}

.talk {
    padding: 5px 20px;
    border-radius: 7px;
    margin-bottom: 15px;
    background-color: # 0093 ff;
    color: white;
}

The details page should look a little better now.

Conclusion

That brings us to the end of this article where we touched on some important Angular 2 concepts like using angular-cli, data-binding, components, dependency injection, scoped-styling and directives.

Protect Your Angular App

Have you used Angular 2 before or are planning to do so, please do let us know what your thoughts are in the comments below.

The code sample for this tutorial can be downloaded here.