Blog

In the previous part of the article, we introduced the new features that the stable version of Angular 2 provides. We focused on providing a starting project with everything configured for our needs using Angular CLI.

It’s time to implement processing specific to our use case. Let’s start by configuring the application routes.

Organizing application code with services

As described in the previously quoted article, leveraging services is a good practice to externalize business processing outside components. In our case, we implement processing to execute and handle HTTP requests based on the Http instance of Angular 2.

A service simply consists of a class decorated with the Injectable decorator if we need to inject dependencies. That’s our case with the instance of the Http class.

import { Injectable } from ‘@angular/core';
import { Http } from ‘@angular/http';
import { Observable } from ‘rxjs/Observable';

@Injectable()
export class ContactService {
  constructor(private http: Http) {
  }

  getContacts(): Observable<Contact[]> {
    return this.http.get(‘/contacts/').map(res => res.json());
  }

  getContact(id: string): Observable<Contact> {
    return this.http.get(`/contacts/${id}`).map(res => res.json());
  }

  addContact(id: string, contact: Contact): Observable<Contact> {
    return this.http.post(‘/contacts/', contact).map(res => <Contact>res.json());
  }

  updateContact(id: string, contact: Contact): Observable<Contact> {
    return this.http.put(`/contacts/${id}`, contact).map(res => <Contact>res.json());
  }

  deleteContact(id: string): Observable<any> {
    return this.http.delete(`/contacts/${id}`);
  }
}

As we can see, method signatures rely on a Contact class describing the contact attributes. This approach allows us to enforce type check in code. Here is the content of the Contact class:

export class Contact {
  id: string;
  firstName: string;
  lastName: string;
  (...)
}

Be careful when using transtyping in TypeScript. It doesn’t mean that we have an instance of the type, but an instance that should have the same structure. In this case, methods of the transtyped type can’t be used. For example, if the Contact has a method, we won’t be able to use on a transtyped instance.

The same service can be implemented for companies.

To be able to use this service within the contact module, we need to add to the providers attribute of class decorated with NgModule for contacts.

(...)
import { ContactService } from './contacts.service;

@NgModule({
  (...)
  providers: [
    ContactService
  ]
})
export class ContactsModule {}

Let’s continue by configuring the application routes.

Configuring routing

Routing is an external module that allows applications to display components according to paths. This is a good for helping to organize applications. Angular 2 goes a bit further by linking routes to modules. This means that, in our case, we will have additional modules regarding routes: one for the company module and one for the contact module.

modules-and-routing

Within our application we will have the following routes:

  • Route with path /companies (or empty string): displays the list of companies based on the CompanyList component
  • Route with path /companies/{id}: displays the details of a particular company for editing based on the CompanyDetails component
  • Route with path /contacts: displays the list of contacts based on the ContactList component
  • Route with path /contacts/{id}: displays the details of a particular contact for editing based on the ContactDetails component

Configuring these routes is straightforward at the level of a module using the RouterModule.forChild method and an array.

import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';

import { ContactsListComponent }    from './contacts.list.component';
import { ContactsDetailsComponent }  from './contacts.detail.component';

@NgModule({
  imports: [
    RouterModule.forChild([
      { path: ‘contacts',  component: ContactsListComponent },
      { path: ‘contacts/:id', component: ContactsDetailComponent }
    ])
  ],
  exports: [
    RouterModule
  ]
})
export class ContactsRoutingModule { }

This routing module can be simply added to the module for contacts within the imports attribute:

(...)
import { ContactsRoutingModule } from './heroes-routing.module';

@NgModule({
  imports: [
    CommonModule,
    FormsModule,
    ContactsRoutingModule
  ],
  declarations: [
    AppComponent,
    ContactsListComponent,
    ContactsDetailsComponent
  ],
  providers: [
    ContactsService
  ]
})
export class ContactsModule {}

The last step is to define where to display the view for the route. Angular 2 provides the router-outlet directive. It’s available because the èRouterModule` type is exported in our routing module. Here is a sample of its use in our main component:

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

@Component({
  selector: 'app-root',
  template: `
    <router-outlet></router-outlet>
  `
})
export class AppComponent {
}

We stop at this level for the moment but the new router comes with a set of cool features:

The support of resolve to wait for asynchronous processing to have ended before activating a route The guards to control the access to routes in Angular 2 applications

Let’s display now the list of companies.

Displaying companies

The first thing consists of loading the contacts leveraging the contacts service. To do that, inject first the service into the component and call its getContacts method within the component constructor. We can set the return of this method into a property named contacts.

import { Component } from ‘@angular/core';
import { Observable } from ‘rxjs/Observable';

@Component({
  (...)
})
export class ContactsListComponent {
  private contacts: Observable<Contact[]>;

  constructor(private contactsService: ContactsService) {
    this.contacts = this.contactsService.getContacts();
  }
}

To display the list of contacts, we can leverage the ngFor directive. Since our contacts property is an observable, we also need to use the async pipe. This way, when the data is there, they will be automatically displayed. The async pipe is responsible for correctly subscribing and unsubscribing under the hood.

import { Component } from ‘@angular/core';
(...)

@Component({
  (...)
  template: `
    Contacts:
    <ul>
      <li *ngFor="let contact of contacts | async">
        {{contact.lastName}} {{contact.firstName}}
      </li>
    </ul>
  `
})
export class ContactsListComponent {
  (...)
}

We can note the use of the let keyword in the ngFor expression instead of “#” in the beta version.

We can now improve this using the previously quoted resolve feature. In this case, the component will be displayed only when the list of contacts is received.

The first step consists of defining a dedicated class to resolve the data. This class needs to implement the Resolve interface and implement the resolve method. The latter is responsible for returning the observable. Angular 2 uses it to get data asynchronously.

import { Injectable } from '@angular/core';
import { Resolve, ActivatedRouteSnapshot } from '@angular/router';
import { Contact, ContactsService } from './contacts.service';

@Injectable()
export class ContactsResolve implements Resolve<Contact[]> {

  constructor(private contactsService: ContactsService) {}

  resolve(route: ActivatedRouteSnapshot) {
    return this.contactsService.getContacts();
  }
}

This class must be defined within the providers attribute of our main module.

(...)
import { ContactsResolve } from './contacts/contacts.resolve';

@NgModule({
  (...)
  providers: [
    ContactsService,
    ContactsResolve
  ],
  bootstrap: [AppComponent]
})
export class AppModule {
}

The next step consists of adding a resolve block in the route that displays the list of contacts. This way the route will be only activated when the data is received through the previous observable.

import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';

import { ContactsListComponent }    from './contacts.list.component';
import { ContactsDetailsComponent }  from './contacts.details.component';
import { ContactsResolve } from './contacts.resolve';

@NgModule({
  imports: [
    RouterModule.forRoot([
      {
        path: 'contacts',
        component: ContactsListComponent,
        resolve: {
          contacts: ContactsResolve
        }
      },
      (...)
    ])
  ],
  exports: [
    RouterModule
  ]
})
export class ContactsRoutingModule {
}

Let’s now implement the route to display company details.

Displaying company details

Implementing the support for company details is similar to the list of companies. There is one difference. We need to get a parameter from the route address at the level of the resolve class. This parameter will be used when calling the getContact method of the service.

import { Injectable } from '@angular/core';
import { Resolve, ActivatedRouteSnapshot } from '@angular/router';
import { Contact, ContactsService } from './contacts.service';

@Injectable()
export class ContactResolve implements Resolve<Contact> {

  constructor(private contactsService: ContactsService) {}

  resolve(route: ActivatedRouteSnapshot) {
    return this.contactsService.getContact(route.params['id']);
  }
}

The id parameter is specified when defining the route for contact details.

(...)

@NgModule({
  imports: [
    RouterModule.forRoot([
      {
        path: 'contacts',
        component: ContactsListComponent,
        resolve: {
          contacts: ContactsResolve
        }
      },
      {
        path: 'contacts/:id',
        component: ContactsDetailsComponent,
        resolve: {
          contact: ContactResolve
        }
      }
    ])
  ],
  exports: [
    RouterModule
  ]
})
export class ContactsRoutingModule { }

The remaining point consists of defining the navigation within the application. We need to use the routerLink directive. The specified value will correspond to the address of the route.

(...)

@Component({
  (...)
  template: `
    Contacts:
    <ul>
      <li *ngFor="let contact of contacts | async">
        <a [routerLink]=”'/contacts/' + contact.id”>
          {{contact.lastName}} {{contact.firstName}}
        </a>
      </li>
    </ul>
  `
})
export class ContactsListComponent {
  (...)
}

Conclusion

As we can see, we’ve come a long way since the beta version of Angular 2 was released. It’s mainly due to the reintroduction of modules and the new router. After having introduced the differences in the first part of this article, we went more into detail in this second part. We described how to configure and use modules and routing, the main features that changed or were introduced.

CTA_Free Trial_2

Categories: APISpark, Products, REST

Leave a Reply

Your email address will not be published. Required fields are marked *