Angular 2 with an ASP.NET Core API

This week’s post is going to take the Angular 2 application from a couple of weeks ago and add the same functionality currently present in the Aurelia application found the ASP.NET Core Basic repo. This release is the starting point for the solution used in this post.

Starting point overview

When you download a copy of the repo you will find an ASP.NET Core solution that contains three projects. The Angular project is where this post will be focused.

The Contacts project has a set of razor views and a controller to go with them that support standard CRUD operations, which at the moment is the best way to get contact information in the database. It also contains the ContactsApiController which will be the controller used to feed contacts to the Angular 2 and Aurelia applications.

Multiple startup projects in Visual Studio

In order to properly test the functionality that will be covered here both the Contacts project and the Angular project will need to be running at the same time. Visual Studio provides a way to handle this. The Multiple startup projects in Visual Studio section of this post walks through the steps of setting up multiple startup project. The walk through is for the Aurelia project, but the same steps can be applied to the Angular project.

Model

Create a contacts directory inside of ClientApp/app/components/ of the Angular project. Next create a contact.ts file to the contacts directory. This file will be the model of a contact in the system. If you read the Aurelia version of this post you will noticed that this model is more fully defined since this project is using TypeScript the more fully defiled model provides more type safety. The following is the contests file.

export class Contact {
    id: number;
    name: string;
    address: string;
    city: string;
    state: string;
    postalCode: string;
    phone: string;
    email: string;

    constructor(data) {
        Object.assign(this, data);
    }

    getAddress() {
        return `${this.address} ${this.city}, ${this.state} ${this.postalCode}`;
    }

}

Service

To isolate HTTP access the application will use a service to encapsulate access to the ASP.NET Core API. For the service create a contact.service.ts file in the contacts directory of the Angular project. The following is the code for the service.

import { Injectable } from '@angular/core';
import { Http } from '@angular/http';
import 'rxjs/add/operator/toPromise';
import { Contact } from './contact';

@Injectable()
export class ContactService {

    constructor(private http: Http) {
    } 

    getAll(): Promise<Contact[]> {
        return this.http.get('http://localhost:13322/api/contactsApi/')
            .toPromise()
            .then(response => response.json())
            .then(contacts => Array.from(contacts, c => new Contact(c)))
            .catch(error => console.log(error));
    }
}

This class uses Angular’s HTTP client to access the API and download a list of contacts. Angular’s HTTP client uses reactive extensions and returns an observable. In this case we don’t need an observable so for this service the observable is being converted to a promise. Then from there the response from the API is being converted an array of type contact.

Also make note of the Injectable decorator which tells Angular 2 the class should be available for dependency injection.

View Model

The next step is to create a view model to support the view that will be used to display the contacts download from the API. Add a file named contactlist.component.ts to the contacts directory of the Angular project. The following is the full contents of the view model file. This will be followed by a breakdown of the file in order to highlight some parts of the file.

import { Component, OnInit } from '@angular/core';
import { Contact } from './contact';
import { ContactService } from './contact.service';

@Component({
    selector: 'contactlist',
    template: require('./contactlist.component.html'),
    providers: [ContactService]
})
export class ContactListComponent implements OnInit {
    contacts: Contact[];

    constructor(private contactService: ContactService) { }

    ngOnInit(): void {
        this.contactService.getAll()
            .then(contacts => this.contacts = contacts);
    }
}

The import statements are pulling in a couple parts of the Angular 2 framework in addition to the contact model and contact service created above.

Next is a component decorator which marks the class as an Angular component and provides a method to set metadata about the class.

@Component({
    selector: 'contactlist',
    template: require('./contactlist.component.html'),
    providers: [ContactService]
})

The selector property sets the identifier for the class to be used in templates. The template property sets the view that should be used with the view model. In this case it is requiring in another file, but it could also contain the actual template that should be used to render the component. An alternate is to use templateUrl to point to an external file containing a template. The final property used in this example is the providers  property which is a list of providers that the framework needs to be made available to the component, in this case the ContractService. For more information on the component decorator check out the Angular docs.

The next thing of note on this class is that it implements OnInit.

export class ContactListComponent implements OnInit

OnInit is one of Angular’s lifecycle hooks, see the docs for the rest of the available hooks. OnInit is called once after component creation and runs the ngOnInit function which in the case of this class is being used to get a list of contacts from the ContactService.

View

For the view create a contactlist.component.html in the contacts directory of the Angular project. This is the file that the veiw model created above is bound with to display the contact data retrieved from the API. The following is the complete contents of the view file.

<ul>
    <li *ngFor="let contact of contacts">
        <h4>{{contact.name}}</h4>
        <p>{{contact.getAddress()}}</p>
    </li>
</ul>

The second line repeats the li tag for each contact in the contacts array of the view model class. {{expression}} is Angular’s syntax for one way data binding. {{contact.name}} does a one way binding to the name property of the current contact in the *ngFor loop. For more details on the different options available for data binding see the docs.

Add menu

The final piece is to add an item to the menu from with the contact list can be accessed. Open app.module.ts in the ClientApp/app/components/ directory. Add an imports for the ContactListComponent.

import { ContactListComponent } from './components/contacts/contactlist.component';

Next add a new path to the RouterModule. The third from the bottom is the line that was added for the contact list.

RouterModule.forRoot([
    { path: '', redirectTo: 'home', pathMatch: 'full' },
    { path: 'home', component: HomeComponent },
    { path: 'counter', component: CounterComponent },
    { path: 'fetch-data', component: FetchDataComponent },
    { path: 'contact-list', component: ContactListComponent },
    { path: '**', redirectTo: 'home' }
])

Finally open the navmenu.component.html file in the ClientApp/app/components/navmenu/  directory. Add a new li for the contact list matching the following.

<li [routerLinkActive]="['link-active']">
    <a [routerLink]="['/contact-list']">
        <span class='glyphicon glyphicon-list-alt'></span> Contact List
    </a>
</li>

Wrapping up

That is all it takes to consume some data from an ASP.NET Core API and use it in an Angular 2 application. I can’t stress enough how easy working with in the structure provided by JavaScriptServices helped in getting this project up and going quickly.

The completed code that goes along with this post can be found here. Also note that the Aurelia project has be redone as well also based on JavaScriptServices and TypeScript so that the applications will be easier to compare.

Leave a Comment

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

This site uses Akismet to reduce spam. Learn how your comment data is processed.