In my last article (CODE Magazine Jan/Feb 2017), I covered creating a full-featured ASP.NET Core API back end for an AlbumViewer sample application. In that article, I focused entirely on the server side. In this article, I want to dive into the client side and discuss how you can create an AngularJS application to interface with your Web API. In this article, I start from scratch with Angular project creation, move on to the build process, and then creating your first Angular pages that load content from your Web and let you edit that data. This article is meant as a getting-started guide that provides all the pieces you need to create your first Angular application. In my next article, I'll delve into more detail and the little things you need to deal with beyond the basics.

To get an idea of what I'm talking about, you can check out the live AlbumViewer application and the source code on GitHub:

If you want to follow along with this article, make sure to first clone or copy the GitHub repo and take a look at the Readme.md for installation instructions and how to get the ASP.NET Core server-side application running. The step-by-step code described in this article is somewhat simplified to cover the core concepts and keep it short enough to present here.

Angular 2 is a drastic departure from Angular 1. The concepts are similar but the execution is very different.

Angular

The client-side front end in the AlbumViewer application uses Angular 2. It's now been out for about a half a year, and has been widely adopted. It's far-and-away the most popular client-side JavaScript framework today and is integrated into major sites as well as a host of third-party frameworks, such as Ionic, Telerik's Web/Mobile platform and many more. Angular is a heavy front-end framework, so it's not really a tool you want to “just drop into” a webpage for some add-on functionality. Rather, Angular is meant for full-scale client-side application development of applications that consist of many pages and components that need to interact with each other. The key point about Angular is that it's really more of a platform than a framework in that it provides just about everything you need to build Web applications without having to add a bunch of additional tools. Although it's a heavy framework, you also get a ton of well-integrated functionality for all that weight.

Angular recently dropped the 2.0 moniker and the product is now just the artist formerly known as Angular - again. First there was Angular 1, then there was Angular2, and now there's just Angular because the Angular team has decided that versions rev too frequently to keep up the numbering scheme in the name. It revs on a regular schedule with major releases every half a year or so. The next forthcoming version of Angular will be version 4, which might be out by the time this article is released. There is no version 3 due to issues with sub-components. Angular 2 was a major breaking change from Angular 1 and although major version releases will bring breaking changes, they are going to be much less drastic than the v1 to v2 upgrade.

I've built a ton of Angular 1 applications and getting started with Angular 2 was tough, mainly due to the incomplete tooling that initially made it hard to get a project set up. The process of setting up a new project continues to be tedious even though the tooling has improved. Angular has a lot of moving parts to bootstrap an application and as a new user, it can be quite overwhelming. My advice is: Don't get bogged down in those details as you start out. This stuff makes much more sense once you understand how the more commonly used features of Angular work. Even though initial setup can be tedious, once you're ready to create components, add routes, build your templates, and build the meat of your application, Angular is surprisingly easy, logical, and efficient to work with.

Angular is more of a platform than a framework in that it provides most of what you need to build a Web Application.

Angular's component model maps components and templates together in an easily understandable manner. It uses optionally strongly typed TypeScript, which makes it easy to discover functionality that tools like WebStore, Visual Studio, and Visual Studio Code can expose due to the structured nature of the code This makes short work of cranking out pages and forms quickly and efficiently. Angular's platform mentality is certainly complex if you need to dig deeper (as you will occasionally), but for the most part, the concepts you deal with day-in and day-out are easy and logical to work with. I've really enjoyed building applications with Angular, as it matches the way I like to build applications.

We're Not in Kansas Anymore

JavaScript development sure has changed in the last few years. We've gone from slapping a couple of libraries like jQuery or even Angular 1 into an HTML page and just writing JavaScript code, to having hard requirements for a build pipeline that effectively compiles TypeScript or ECMAScript 6 or later code into JavaScript that can run on most browsers. Although ECMAScript 2015 has been a validated standard for nearly two years now, ES 6 browser support is still very spotty. TypeScript, of course, always requires transpilation, so a build process is always needed.

Angular works best with TypeScript and firmly lives in the transpiler camp. It requires a full build process to get anything running. You can use ES5 and ES6 with Angular, but you won't want to. The process of transpilation converts TypeScript into plain ECMAScript 5 code that can run in all but the oldest browsers.

Because a TypeScript (or ES6) project now needs to be built in order to run anyway, the build pipeline typically includes additional functionality, like bundling, minification, and optimization of the transpilation. Angular's build pipeline uses WebPack and a project setup that can take all of the resources associated with your application and package them, ready for deployment in fully bundled and minified packages.

This build process and initially setting up a project structure is perhaps the biggest hurdle to adopting Angular for many developers who aren't deeply embedded in the JavaScript world. The good news is that although the build pipelines are very complex behind the scenes, using the provided tooling makes them as easy as running a command from the command line to build an optimized output package.

Modern JavaScript libraries like Angular require a build pipeline in order to compile TypeScript or ECMAScript 6+ to the JavaScript that can execute in most browsers.

You definitely don't want to set up an Angular project by hand. Use a tool or starter template. There are many starter projects you can use, and the Angular team also provides an Angular-CLI tool that automates the project creation and build process by setting up a fully functional build and development environment. A few command line commands can be used to create a new project, start a development server with live reload, and build a production build that's ready to deploy on your Web site. Let's get started.

Creating a New Angular Project

The first step is to create a new project. I'll use the Angular CLI to create a separate new project from the ASP.NET Core Web API project in the same Solution. Although it's possible to generate a new Angular project in an ASP.NET Web project, the structures of the Angular CLI tool and ASP.NET Core layout don't mix well. I recommend that you set up your Angular project in a completely separate folder and use the build process to move files over if you want to run them in the same Web as your ASP.NET Web API. If you use full Visual Studio, you can use a Web Site project for the Angular project. During development, you'll be running the Angular development server anyway, so you're already running Angular and ASP.NET on separate Web sites.

To get started, you'll need to install the Angular CLI using the directions shown here: https://github.com/angular/angular-cli.

Once the CLI is installed, change to .NET Core project's src folder (the root folder to all the subprojects) and then use the ng new command to create a new project.

cd <project root>\src
ng new AlbumViewer --routing

This creates a new project called AlbumViewer in a folder called AlbumViewer. The –routing flag tells the CLI to generate and hook up a main routing module that allows you to navigate multiple pages. Without it, the project is created as a single page application with no routing. Unfortunately, the CLI doesn't actually hook up any routes and there's no support for using ng generate to add routes either, so configuration of routes is a manual process. I'll come back to this later.

You can use the Angular CLI to create a new project, run the development server, and create a production build of your application.

Running the Application

Running ng new installs a ton of Node modules and takes a few minutes to run. When it's done, you'll have a functioning starter project that can run and serve a test page. Try this:

cd .\AlbumViewer
ng serve

The ng serve command builds the Angular project and then starts a local development server, file watcher, and live-reload behavior. Navigate to localhost:4200 in your browser to run the application and see a simple static message that says app works. That's not very impressive or even dynamic, but it means that the app runs as the message comes from within an Angular component. Figure 1 shows what the command line and your browser look like at this point.

Figure 1: Running the Angular CLI to create a new project and a development server
Figure 1: Running the Angular CLI to create a new project and a development server

Opening the Project: Angular 101

Next, I'm going to open the new project using the WebStorm editor (https://www.jetbrains.com/webstorm/). There are a number of good editor choices (see the sidebar Angular Project Editors) including the very popular Visual Studio Code, but I prefer WebStorm, as it provides a ton of useful features beyond basic editing and code completion. I'll show WebStorm here, but any other project editor will do just as well.

I can open the AlbumViewer folder as a project to see all files. Find the src/app/app.component.html and app.component.ts files, open them in the editor, and make the changes shown in Figure 2 to display the current time.

Figure 2: Making a dynamic change to app.component to verify the app works.
Figure 2: Making a dynamic change to app.component to verify the app works.

App Component is typically the entry point component of an Angular application that contains this top-level component. This component is referenced in the Index.html page, which effectively links and bootstraps the Angular application into the single page of the application.

In index.html, you have:

<app-root>Loading...</app-root>

That's the place-holder for the AppComponent Typescript class that defines an app-root selector defined in app.component.ts:

import {Component} from '@angular/core';
@Component({
    selector: 'app-root',
    templateUrl: './app.component.html',
})
export class AppComponent {
    title = 'app works!';
}

This component can then either embed a template as string directly into the @Component header, or, as this example does, links to an external html file that contains the HTML template to render. A template can contain Angular expressions that map to the component's property model.

AppComponent has a title property that's set to the string displayed in Figure 2. The component's class interface is the base model for the HTML template, so the template can access the property simply with:

<h1>
    {{title}}
</h1>

This causes the title property from the component to render. Let's make a small addition by adding a time value so you can see the value change as you load the page.

Angular's databinding makes data display incredibly easy. Any model update is reflected immediately in any template or bound value.

Add a time property to the component and set it to the current time:

export class AppComponent {
    title = 'app works!';
    time = new Date();
}

Let's display that value in the template:

<p>
    Time is {{time | date:"MMM dd - hh:mm:ss a"}}
</p>

If your server and browser window are still running, you should see the updated content in the browser, as shown in Figure 2. The content has automatically reloaded after you made a change to either the HTML or TypeScript code, which is a result of the live server that watches for changes and auto-refreshes the page. Cool, eh?

Let's make the example a little more dynamic by automatically updating the time every second. Add the following code to the constructor function:

constructor() {
    setInterval( ()=> this.time = new Date(), 1000);
}

This code updates the time every second with a new value using an arrow function. A TypeScript/JavaScript arrow function is like a C# lambda or a shorthand syntax an anonymous function in JavaScript. The code in the expression is evaluated once a second as setInterval() is triggered every 1000 milliseconds.

Unlike Angular 1, Angular 2 is smart enough to detect model binding changes without any explicit hints - it just works!

When you look at the HTML form in the browser now, you should see the time update every second. There's no databinding code and Angular automatically detects the update of the time property and updates the {{ time }} expression on the template. Also note the handy date formatting that's provided through Angular's built in date: filter.

Data binding also works quite easily with input controls using two-way model binding. To demonstrate, let's add another property called name to the AppComponent class:

name = 'Rick';

Then let's add the following input control display area to the HTML:

<label>What's your name</label>
<input id="name" [(ngModel)]="name" class="form-control" />

<div class="alert alert-info" *ngIf="name">
    Your name is: {{name}}
</div>

The key is the [(ngModel)] (referred to as banana-in-a-box binding. See the sidebar Banana in a Box for more on that), which is an attribute binding [ ] and an event binding ( ) rolled into a single composite directive. The model is bound for display and the model is updated as the value is changed when keystrokes are entered.

As you can probably guess, when you type into the text box, the message text updates immediately in the alert box below. Notice also the *ngIf="name" directive on the alert box, which hides the message display when the name is empty. Figure 3 demonstrates what this looks like.

Figure 3: Textbox binding via [(ngModel)] is easy and powerful
Figure 3: Textbox binding via [(ngModel)] is easy and powerful

The two-way banana-in-the-box model binding is a special case, but Angular has a few standard ways to bind common things:

  • [disabled]=“disabledState”: Any attribute can be bound with square brackets.
  • (click)="buttonClick(album)": Events are bound with parenthesis.
  • #form1=“ngForm”: Element name bindings
  • *ngXX: Directives like *ngIf and *ngFor
  • {{album.title}}: Inline expression bindings

Angular's databinding is very fast, even for large models and requires minimal effort on your part to keep synced. Unlike Angular 1, there are no explicit rebinding triggers (good riddance to $apply()) required to get bindings to work. Angular monitors model values directly and can efficiently detect changes and update the display in each JavaScript context switch cycle. I'm always surprised to see how well this mechanism works, even on very large pages with tons of model data that updates frequently.

Angular monitors model values directly and can efficiently detect changes and update the display in each JavaScript context switch cycle.

Adding Assets and Building the Project

You'll notice in Figure 3 that I've added some basic Bootstrap styling to the page. Angular applications are packaged using a build tool called WebPack. When you're using the Angular CLI, it wraps WebPack with a wrapper configuration layer that closely mimics WebPack's. The advantage of using the CLI over WebPack directly is that if, in the future, something better comes along, the CLI can continue to work with the existing syntax but wrap around a different build provider.

When you create your single-page Index.html file, Angular wants to inject all of the dependencies you might have into this file itself. This includes the JavaScript libraries, CSS, HTML templates, images, and everything else the application references. If you want things to work smoothly, it's recommended that you set up your .angular-cli.json with all the dependencies you might have.

Specifically, you'll add:

  • NPM script and asset dependencies (Bootstrap or Font Awesome for example)
  • Global Script dependencies (jQuery, Toastr, Bootstrap extension components)
  • CSS dependencies
  • Images and other non-manipulated resources

In the AlbumViewer application, I have dependencies to Bootstrap, Font Awesome, jQuery, Toastr and Bootstrap-3-Typeahead. I also have a bunch of images in an images folder.

The first step is to install the external components via NPM and mark them as runtime dependencies (--save):

npm install --save bootstrap toastr
jquery font-awesome
bootstrap-3-typeahead

Next, I need to include any external components into the Angular CLI configuration using the scripts and styles sections. Any loose assets, like images, are copied along with the build.

Listing 1 shows the AlbumViewer project's .angular-cli.json file.

Listing 1: The .angular-cli.json holds dependencies and build settings

{
  "project": {
    "name": "album-viewer"
  },
  "apps": [
    {
      "root": "src",
      "outDir": "dist",
      "index": "index.html",
      "main": "main.ts",
      "polyfills": "polyfills.ts",
      "tsconfig": "tsconfig.app.json",
      "prefix": "app",
      "assets": [
        "images",
        "favicon.ico",
        "touch-icon.png"
      ],
      "styles": [
        "../node_modules/bootstrap/dist/css/bootstrap.css",
        "../node_modules/font-awesome/css/font-awesome.css",
        "../node_modules/toastr/build/toastr.css",
        "./css/albumviewer.css"
      ],
      "scripts": [
        "../node_modules/jquery/dist/jquery.js",
        "../node_modules/toastr/toastr.js",
        "../node_modules/bootstrap/dist/js/bootstrap.js",
      ],
      "environmentSource": "environments/environment.ts",
      "environments": {
        "dev": "environments/environment.ts",
        "prod": "environments/environment.prod.ts"
      }
    }
  ], ... // dev time more stuff below
}

The root determines where the application lives and dest is the output folder where the resulting packed resources are dumped. By default, this is the /dist folder in the project. You can also point this at your ASP.NET Web application's wwwroot folder if you want to run the application inside of the existing Web project when it starts up.

The scripts and styles entries translate into <script> and CSS <link> tags and they're injected into index.html and behave just as if they were manually added there. Scripts load into global scope the same way they always do, so if you have dependencies like Toastr or Bootstrap that also depend on jQuery in global scope, they still work as you'd expect. By adding scripts and styles in this file rather than in index.html, these resources are minimized and bundled as part of the rest of the application.

The end result is that you specify all your resources and the Angular CLI, WebPack manages packaging, minimizing, and creating the final output in a single folder, producing a few big bundles that contain all application files. To create a production build use:

ng build --prod

Figure 4 shows what the packaged output looks like.

Figure 4: Distribution folder output packages all scripts and css into a few largish files
Figure 4: Distribution folder output packages all scripts and css into a few largish files

The main and inline packages are your application code; the rest are included scripts and NPM vendor dependencies.

This output is self-contained and contains the entire runnable single-page application. If you want to run the application as part of the Web project, you can point the dist folder at the project's wwwroot folder:

"outDir": "../AlbumViewerNetCore/wwwroot",

This works great, but be aware that this clears the folder first, so if you have other resources in wwwroot, this approach won't work and you'll have to manually create a build step as an NPM script or Gulp task.

Getting ASP.NET API Data

With most of the configuration out of the way, let's do something a little more useful and grab some data from the ASP.NET Core AlbumViewer API application, and display it on a page. In this step, you'll set up routing and pull album view data from the ASP.NET Core application (or from the Web).

To do this you need to:

  • Make sure that there's a <router-outlet /> to receive the route.
  • Create a route that fires a new Component.
  • Create a new Component.
  • Create a new HTML Template.

Setting Up the App Component Page for Routing

The first thing I'm going to do is set up the app.component.html page and create a simple page structure that includes header, footer and the <router-outlet> that receives the routed page content:

<header id="TitleBar">
    <a href="#/albums">
        <img src="images/headphone-head.png"/>
    </a>
...
</header>
<div id="MainView">
    <!-- page content goes here -->
    <router-outlet></router-outlet>

    <footer>
        &copy; West Wind Technologies
    </footer>
</div>

Hooking Up the Route

Next, I'm hooking up a new route to the component I'm going to create. Yeah, it's a chicken and egg thing as the component doesn't exist yet, but to keep the routing in context, I'll hook it up now. To create a route, I add it to the app-routing.module.ts and associate the albums URL with an albumListComponent that I'll create in the next step.

const routes: Routes = [
    { path: '', redirectTo: "albums",
        pathMatch: 'full' },
    { path: "albums",
        component: albumListComponent }
];

If you have the watcher running, you'll probably see some errors right about now, because the component doesn't exist yet. Once it does (next steps) you'll also need to import it:

import {albumListComponent} from "./albums/albumList";

Tip: In WebStorm, press Alt-Enter on the albumListComponent to automatically create the import statement. In VS Code, use the Auto Import extension by right-clicking the red underline to import.

Angular's Router supports nested Routes. Any Component can have a child Router Outlet and manage its own Routes.

Creating an AlbumList Component

To complete the route logic and fix my code error, I create the albumListComponent and add a related HTML template that renders the album list. To do this, I:

  • Add a new folder to src/app called albums.
  • Add albumList.ts and albumList.html.

The component file is a TypeScript class that acts as a model and logic container for the component. In this component, I want to get a list of albums and render it into the page, so I need an array of albums and an error message to display error info. The initial class skeleton with these properties looks like this:

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

@Component({
    selector: 'album-list',
    templateUrl: 'albumList.html'
})
export class albumListComponent {
    constructor() {
        getAblums();
    }

    albumList[] = [];
    errorMessage:string = null;

    getAlbums() { }
}

I recommend that you use your favorite editor's Angular Snippet pack create a new component skeleton (see the sidebar Angular Snippet Packs), which automates this repetitive step.

At the top of the document, there are always module import statements. You need to import any library and components that your component depends on. This is needed so that the TypeScript compiler can figure out what components to eventually pull into your application. References typically point at NPM modules or project internal source files without an extension.

The @Component() header is Angular metadata that describes the behavior of a component (or other type of class). This data is used by the Angular compiler/parser to uniquely identify your component and resolve any dependencies, both in terms of physical references, like the templateUrl above, as well as other components.

The class then provides the model and behavior methods for you to write your code in. The properties of this class are accessed in the template, so in the template, you can use the model with handlebar expression syntax using {{ errorMessage }} or directives like *ngFor="let album for albumList".

Once you've created your component, you need to make it available to Angular. To do this, add the following in app.module.ts:

@NgModule({
    declarations: [
        AppComponent,
        albumListComponent,
    ],
    ...
}

Adding a Dependency

In order to use this component, you need HTTP access, which requires adding a constructor dependency to the Angular Http component. This requires two steps:

  • Add an import statement for the Http object:
import {Http} from "@angular/http";
  • And then inject an Http instance into the constructor:
export class albumListComponent {
    constructor(private http:Http) { }

By specifying private or public on a constructor parameter in TypeScript, a new property is created, so after this constructor runs, there's a this.http property available on albumListComponent. If you leave off private or public, the parameter is local in scope.

To retrieve data, the getAlbums() method makes an Http call to the ASP.NET Core application:

getAlbums() {
    var url = "http://localhost:5000/api/";

      this.albumList = [];
      this.http.get(url)
        .subscribe( (response)=> {
            this.albumList = response.json();
            this.busy = false;
        },(response)=> {
        this.errorMessage = "Request failed.";
        });
}

Note that if you don't have the ASP.NET API app running locally, you can also use a public Web URL (https://albumviewer.west-wind.com/api/) to get this data.

This code uses the http.get() method, which returns an Observable. Observables are event listeners with a subscribe() method and success and failure functions that are called when an event is fired. With HTTP requests, an event occurs exactly once when the request completes or fails. You can think of Observables as Promises on steroids. Observables have many cool features that aren't really used on HTTP requests, but Angular uses Observables for all event-based interactions for consistency.

The http.get().subscribe() function receives a response object as a result for both success and error handlers. This is the HTTP response represented as an object that contains the content, result codes etc. It also has a json() helper method that can conveniently turn JSON content into an object. It's a simple matter to assign the JSON to the model:

this.albumList = response.json();

When an error occurs, the second function in .subscribe() is fired and there you can capture any errors and display a message accordingly.

Angular uses Observables for all event-based operations including HTTP Requests.

Displaying the Album List

With the model loaded up, I can now display the data in my HTML template. The template is specified in the templateUrl metadata of the @Component tag, so let's create this page, as shown in Listing 2.

Listing 2: Displaying the Album List in an Angular HTML Template

<div class="alert alert-warning" *ngIf="errorMessage">
    <strong>{{ errorMessage }}</strong>
</div>

<div class="page-header-text">
    <i class="fa fa-list">
    </i> Albums <span class="badge">{{albumList.length}}</span>
</div>

<a class="album" role="button"
    [routerLink]="['/album/edit',album.Id]"
      *ngFor="let album of albumList" >
      <img [src]="album.ImageUrl" class="album-image" />
      <div style="padding-left: 80px;">
          <div class="album-title">{{album.Title}}</div>
          <div class="album-artist">
              by {{album.Artist.ArtistName}}
              {{(album.Year ? 'in ' + album.Year : '')}}
          </div>
          <div class="album-descript">{{album.Description}}</div>
      </div>
</a>

As you can see, I'm embedding {{ }} expressions into the page for displaying model values in the HTML. I'm using the *ngFor directive to loop through all items and generate a bunch of <a> links into the page.

*ngFor="let album of albumList"

This directive makes an album object available to the inner scope of the attribute that it's applied to, so I can use {{album.Title}}, for example.

Notice the alert box at the very top of the HTML template that's used to display error messages. If an error occurs, I want to display the error box; otherwise I don't want to see it. This is easy to do with the *ngIf directive, which selectively determines whether an element is rendered in the DOM based on the truthy expression provided.

When it's all said and done, you end up with an album list, as shown in Figure 5.

Figure 5: A rendered album list component/page with data from the ASP.NET Core Service
Figure 5: A rendered album list component/page with data from the ASP.NET Core Service

It sure seems like it took a lot of effort to get here. But this was the first page hooked up; now that this is done, adding new pages and content to the page gets a lot easier because all of the ground work is done.

Adding an Edit Page

To demonstrate a few more key features of Angular, I'm going to create an album editor page. This gives you a chance to review a few steps and also pick up a few new features related to entering data and pushing it back to the service.

Creating the Edit Component

I'll start with creating the component class this time, as shown in Listing 3.

Listing 3: The AlbumEditorComponent lets you edit and save an album

import {Component, OnInit} from '@angular/core';
import {ActivatedRoute} from "@angular/router";
import {Http} from "@angular/http";
import {Album} from "../business/entities";

@Component({
    selector: 'album-editor',
    templateUrl: 'albumEditor.html'
})
export class albumEditorComponent implements OnInit {
    constructor(private route:ActivatedRoute,
    private http:Http) { }

    album:Album = new Album();
    errorMessage = "";
    baseUrl = "http://localhost:5000/api/";

    ngOnInit() {
        var id = this.route.snapshot.params["id"];
        if (id < 1)
        return;

        this.loadAlbum(id);
    }

    loadAlbum(id) {
        this.errorMessage = "";
        this.http
            .get(`${this.baseUrl}album/${id}`)
                .subscribe(response => {
                this.album = response.json();
                }, response => {
                this.errorMessage = "Unable to load album.";
                });
    }

    saveAlbum(album) {
        return this.http
        .post(`${this.baseUrl}album`,album)
        .subscribe(response => {
            this.album = response.json();
            this.errorMessage = album.Title + " has been saved."
         },
         response => {
             this.errorMessage = "Unable to save album.";
         });
    }
}

To create the class:

  • Create a new albumEditor.ts file in the Albums folder.
  • Use ng2-Component to expand the default template (in Webstorm and VSCode).
  • Name the component, as shown.

This time around, I'll need some extra imports for the Http object and the ActivatedRoute to access information from the route, and both of these are injected into the constructor. Retrieving route values is a pain in Angular because routes are actually Observables that have to be captured and because of this, the syntax is just plain unintuitive:

var id = this.route.snapshot.params["id"];

The loadAlbum() method retrieves a JSON instance of an album and assigns it to an album property on the page. The loadAlbum() method is called when the page loads and that provides the initial album display.

Saving an album is pretty easy too, thanks to the API back end that already knows how to save and validate an album. All I have to do is pick up the album and post it to the server, which is done by using the http.post() (or the .put()) method to post an object - in this case an album.

Typed Entities

I also created strongly typed entities for the Album, Artist, and Track class, which I stored in a separate entity.ts file, as shown in Listing 4.

Listing 4: Typed entities provide Auto-completion in Code and Templates

export class Album {
    Id:number = 0;
    ArtistId:number = 0;
    Title:string = null;
    Description:string = null;
    Year:number = 0;
    ImageUrl:string = null;
    AmazonUrl:string = null;
    SpotifyUrl:string = null;

    Artist:Artist = new Artist();
    Tracks:Track[] = [];
}

export class Artist {
    Id:number = 0;
    ArtistName:string = null;
    Description:string = null;
    ImageUrl:string = null;
    AmazonUrl:string = null;
    AlbumCount:number = 0;
    Albums:Album[] = [];
}

export class Track {
    Id:number = 0;
    AlbumId:number = 0;
    SongName:string = null;
    Length:string = null;
    Bytes:number = 0;
    UnitPrice:number = 0;
}

Using them provides type checking in the TypeScript compiler as well as Auto-complete support for code and template editing. I also had problems with Angular not binding to sub-objects unless I used a strongly typed object (i.e., using album = {} vs. album = new Album()). Although you can get away without creating typed objects (or interfaces), it's usually worthwhile to create classes and get the strong typing and type checking.

It's recommended that you use typed entities with TypeScript to take full advantage of type checking and Auto Completion.

Creating the Editor HTML Template

The HTML template is lengthy, mostly due to a bunch of Bootstrap-related HTML. However, data-binding is very easy to hook up with binding expressions, like [(ngModel)]="album.Title", that two-way bind the album and the associated artist to the class model. Listing 5 shows a subset of some of the relevant input fields and the Save button.

Listing 5: Editing Album Model Values in an Input Form

<form name="form1" action="javascript:{}"
 #form1="ngForm" novalidate>

<div class="form-group">
    <label for="AlbumName">Album Name:</label>
    <input id="AlbumName" type="text" class="form-control input-sm"
           placeholder="Album Name"
           name="Title" required
           [(ngModel)]="album.Title" autofocus />
</div>
<div class="form-group">
    <label for="BandName">Band Name:</label>
    <input type="text" class="form-control input-sm typeahead"
           id="BandName" required
           autocomplete="off"
           data-provide="typeahead"
           placeholder="Band Name"
           name="ArtistName"
           [(ngModel)]="album.Artist.ArtistName" />

</div>
<div class="form-group">
    <label for="Description">Album Description:</label>
    <textarea class="form-control input-sm" id="Description"
              placeholder="Album description or review"
              required
              [(ngModel)]="album.Description"
              name="Description"
              style="height: 115px"></textarea>
</div>

... more fields

<div class="well well-sm">
    <button type="submit" (click)="saveAlbum(album)"
            class="btn btn-success"
            [disabled]="form1.invalid" accesskey="S">
        <i class="fa fa-check"></i> Save
    </button>
    <a [routerLink]="['/albums']" class="btn btn-default">
      <i class="fa fa-remove"></i> Cancel
    </a>
</div>

</form>

<!-- Live Preview as you type -->
<div class="col-sm-7">
    <h3>Preview</h3>
    <img [src]="album.ImageUrl class="album-image-big"/>
    <div style="margin-top: 10px;">
        <h2 class="album-title-big">{{album.Title}}</h2>
        <div class="album-descript line-breaks"
            [innerHTML]="album.Description"></div>
    </div>
</div>

There's really very little to this, other than the [(ng-model)] inputs. The end result is shown in Figure 6.

Figure 6: Creating input forms with Angular is easy.
Figure 6: Creating input forms with Angular is easy.

There's also a live content preview as you type, which is easy to do with model binding simply by echoing the model values modified with {{ }} expressions.

Basic Input Validation

Note that you don't need an HTML <form> to do model binding unless you want to do input validation. To enable form field access for validation features, add a pseudo ID to the form like this: #form1="ngForm" as shown in Listing 5. Once you do this, you can access form1.fieldname (which keys off the name= attribute) to check for validation and check for things like form1.invalid, form1.fieldname.valid, or form1.pristine, etc.

Notice that the two input fields have a required attribute which, when not filled, causes the form to be invalid. The submit button on the form then has an attribute binding for [disabled]="form1.invalid" to disable form submission unless the form is valid. Angular comes with a number of built-in form validations and automatically assigns a number of styles to invalid elements so that error display is relatively easy to accomplish. In Figure 6, the Year is invalid and the error displays a red field via CSS styling of the ng-invalid CSS class.

Form Submission

In Angular applications, you don't really submit a form - what happens instead is that you fire a click event handler on your component and pass some relevant state. The handler then makes HTTP calls to submit data by sending a model object to the server. In this case, the state is the active album and the (click)="saveAlbum(album)" event binding fires the saveAlbum() method on the component. Angular uses parenthesis around an event name to hook up a handler, so (click) binds the element.click event. What's cool about Angular is that it works with any event, not merely events it knows about. So any DOM event, including component triggered events, can be bound. Here, I'm doing the simplest but also most common thing possible, which is binding a click handler on a button.

The component's saveItem() method takes the captured album and pushes it to the server in an HTTP POST operation.

Note: The full ASP.NET Core API sample uses authentication by default, so updates require authentication. To get these initial samples against the API server, you need to comment out the authentication code in the server's SaveAlbum() method. I'll cover authentication in a future article.

And that's really all there's to it! Because the server code already knows how to validate and save the entity, the server simply saves the pushed entity (or rejects it with errors) and gives you back the updated entity.

Hooking Up the Route

The last thing left to do is to hook up the route. Add the following in app-routing.module.ts:

{
    path: 'album/edit/:id',
    component: albumEditorComponent
},

This is a parameterized route where the last route segment is the album ID. This is the value that gets retrieved with the funky this.route.snapshot.params["id"] in the component. In the album list, I have to then reference this route in each album like this:

[routerLink]="['/album/edit',album.Id]"

Although you can also use a direct URL like href="#/album/edit/{{album.Id}}", it's generally a good idea to use routerLink because it works with any of the routing schemes available (hashbang or HTML 5 routes). I generally prefer hashbang (#/) routing because it works regardless of what the server does, even though it's not quite as nice looking as HTML5 routing.

Finally, add the albumEditorComponent to the module declarations:

declarations: [
    AppComponent, albumListComponent,
    albumEditorComponent,
]

And that should be all you need to get the list and edit forms to work.

Build It

While developing, you're typically running the development server and just leaving it running. Any changes automatically update and recompile the code and reload your pages.

When you're ready to create a production build and deploy it, you need a separate production build step. From the command line, use:

ng build --prod --aot

This creates a production-ready build that's compressed and packaged and ready to go.

Summary

I've covered a lot of ground for an introduction to Angular. There's a lot more to cover and I'll come back to some topics, like dealing with configuration, authentication, breaking up page components into smaller reusable components, and more, in a future issue. But for now, I hope this article has given you a useful introduction and overall feel of what Angular is all about.

Angular is a big framework and there's definitely some complexity in setting up a new project at first. But I think you'll find that after initial set up, Angular rewards you with a simple, logical, and consistent model for building sophisticated client-side interactions quite easily. I've found myself crazy productive once I get rolling in the component workflow. It all feels quite natural and fits in well with how I work.

Still, starting out can be daunting. If you find it off-putting, I have this advice: Don't try to figure out how it all works when you get started, but rather just get things working and start building some components and forms to get a feel of building an application. You can absorb the infrastructure pieces gradually and it'll make a lot more sense once you've used Angular for a bit.

I'll have more in my next article. In the meantime, happy Angularing.