July 2017

Volume 32 Number 7

[The Working Programmer]

How To Be MEAN: Angular Ins and Outs

By Ted Neward | July 2017

Ted NewardWelcome back, MEANers.

Last month I talked briefly about how to create components that can synthesize data (such as the current year) and display it as part of the component’s view. I also briefly experimented with the Angular Command-Line Interface (CLI) tool, “ng,” and used that to generate a new application. In this month’s column, I’ll go further down the Web component path and talk about how to pass information into—and out of—a given Angular component.

In this particular case, as part of my ongoing loose example around tracking speakers and their talks, I’m going to build a small component to track the “upvotes” from users/attendees so that people can rate speakers’ talks and offer feedback. I’ll call this component the UpvoteComponent; you want the component to obey the following (highly simplified) set of specs:

  • The Upvote should initialize the “Upvote” property from the “votes” attribute. Default to 0 if no votes attribute is provided.
  • The Upvote should let text between the tags act as the label.
  • The Upvote should display an up-arrow character and when the user clicks on the up-arrow, it should increment the vote count.
  • The Upvote should, when clicked, also notify interested parties (such as other Angular components) of the new vote count.

In addition, you’re going to deliberately structure this Upvote­Component to use a simplified, miniaturized Model-View-­Controller (MVC) approach because this will help structure the application as a whole later.

UpvoteComponent

The first step in any component is to generate the basic scaffolding of the component and the Angular CLI gives you that with a single command-line command: “ng generate component upvote.” This will create a series of four files (the same four-part .ts/.cs”/.html/.spec.ts split you got before) in an upvote subdirectory.

Upvote

If the UpvoteComponent is actually going to use a full MVC approach, then you also need a “model” class, which we’ll call Upvote. This can be scaffolded out using the Angular CLI, as well, by running “ng generate class upvote --spec true.” (The parameter at the end asks the CLI to generate a unit-test file for the Upvote class, and we all like to write unit tests, right?) This will create src/app/upvote.ts and src/app/upvote.spec.ts files. The first will be effectively an empty class, waiting for a more intelligent/feature-­driven class definition, which you’ll promptly provide:

export class Upvote {
  constructor(private votes : number) { }
  public get count() { return this.votes; }
  public increment() { this.votes++; }
}

(Note: We could call this a “Vote” model, because that’s what it’s really tracking, but it’s common in Angular components for the model to directly reflect the component itself, so if we’re calling this an “UpvoteComponent,” consistency suggests we call it an “Upvote.” This is, of course, entirely aesthetic, and teams can—and should—come up with their own naming conventions that make sense to them, at least until the Angular community as a whole has locked some in stone.)

And again, because you’re a good, unit-test-loving developer, you’ll also set up a quick set of unit tests in the upvote.spec.ts file to verify that the Upvote class behaves the way it’s supposed to, as shown in Figure 1.

Figure 1 Verifying the Upvote Class

import {Upvote} from './upvote';
describe('Upvote', () => {
  it('should create an instance', () => {
    expect(new Upvote(0)).toBeTruthy();
  });
  it('should remember the votes passed in via the constructor', () => {
    let v = new Upvote(12);
    expect(v.count).toEqual(12);
  });
  it('should increment the vote count by one when incremented', () => {
    let v = new Upvote(12);
    v.increment();
    expect(v.count).toBe(13);
  });
});

There’s obviously more you can do to test this (Can an Upvote have negative values? Can an Upvote be initialized with non-integer values?), but this covers the basics.

By the way, if you’re not already, you can run the Angular test-runner by (again) using the CLI to kick it off in a long-running process by running “ng test.” This will fire up a browser window, execute the tests and provide interactive feedback by running the tests every time the source code files change. In many respects, it’s even better than a compiler, so long as you’re good about writing tests.

Now that you have a model to work with, let’s work on the UpvoteComponent itself.

UpvoteComponent Use

It often helps to start from the perspective of how you want to use the component, so let’s take a moment and rewrite the AppComponent’s view to think about how we’ll use the UpvoteComponent:

<upvote votes="10" (onIncrement)="upvoted($event)">Current upvotes:</upvote>

You have a little bit of everything here: some content between the tags you want to use as part of the UpvoteComponent rendering, an attribute (votes) to initialize the UpvoteComponent internal state and an event that you want to expose to the caller/user of the UpvoteComponent so that they can bind in a local (to the calling component) function that will receive an update every time the user clicks on the UpvoteComponent up-arrow.

It’s easiest first to provide the votes attribute syntax; this simply requires that you provide a field in the UpvoteComponent and decorate it using the @Input decorator, so that Angular can discover it and wire up the necessary code to pull the value of votes and store it in that field. However, you’re going to do something simultaneously tricky and cool with TypeScript along the way, as shown in Figure 2.

Figure 2 Initializing UpvoteComponent Using TypeScript “Intersection Type” Syntax

export class UpvoteComponent implements OnInit {
  @Input() votes : number | Upvote;
  ngOnInit() {
    if (this.votes instanceof Upvote) {   
      // It's already an Upvote
    }
    else if (this.votes == undefined) {
      this.votes = new Upvote(0);
    }
    else {
      this.votes = new Upvote(this.votes);
    }
  }
}

Notice that “model” is declared as a private field that uses the TypeScript “intersection type” syntax; this means that the field model can be either a number or an Upvote object. You then test to see if it’s an Upvote. If it isn’t, then you test whether it’s a number, so that regardless of what was passed in, you ultimately end up with an Upvote object holding the data in question. The truly mind-bending part of this code is in the “else” block—we assign “votes” a new Upvote object, initialized with votes. Because TypeScript is doing some deep code analysis on this block, it knows—thanks to the earlier “if” test that determines that votes is not an Upvote type—that it must be a number and, therefore, “this.votes” (temporarily) satisfies the condition that the Upvote constructor requires a number.

Next, notice how we would like to provide a label to the content of the UpvoteComponent, so that it can display the text along with a number (and an up-arrow). To do that, Angular lets you project content using the ng-content tag, essentially picking up the content inside the app-upvote tag and dropping it somewhere in the view (defined, remember, in upvote.component.html), like so:

<p><ng-content></ng-content> {{votes.count}}  &#x25B2;</p>

The Unicode character at the end is the Unicode code point for the up-arrow glyph, and recall that the double-curly-bracket syntax is string interpolation of the enclosed expression. The ng-content tag will then be replaced by whatever the user of the UpvoteComponent specified in the body of the tag, which in this case will be the text Current upvotes.

Last, you want to provide a “push” sort of notification system, so that when the user clicks on the up-arrow, the count will increment and then notify interested parties. This is going to require three steps: knowing when the user clicks on the up-arrow (which will require trapping the event in the UpvoteComponent’s view), doing the Upvote increment and then pushing that out to the world.

In the first step, you need to trap when the user clicks on the up-arrow, so in the UpvoteComponent view, surround the up-arrow character in a span tag and attach a local method, clicked, to the click event using the Angular event-binding syntax, like so:

<p><!-- as before -->  <span (click)="clicked()">&#x25B2;</span></p>

This will call a method, clicked, that needs to be defined on the UpvoteComponent class, like so:

clicked() {
  (this.votes as Upvote).increment();
  this.onIncrement.emit((this.votes as Upvote).count);
}

Notice that because the “votes” field is an intersection type of Upvote or number, you need to explicitly cast it as an Upvote object in order to call the Upvote object’s increment method.

The third part is already wired in the clicked method—you use an EventEmitter object to send out a message to any interested parties. You pass the current vote count as the sole event parameter to any parties that subscribe to this event. The EventEmitter is also declared as a field of the UpvoteComponent, but marked with the @Output decorator, like so:

export class UpvoteComponent implements OnInit {
  @Output() onIncrement = new EventEmitter<number>();
  // ... as before
}

Marking it with @Output means that it’s now accessible to interested parties, just as @Input does, but as the names imply, one is for incoming values declared as attributes (“property-binding”) and the other as events (“event-binding”). Notice that the field name (“onIncrement”) is the name used for the event in the <app-upvote> tag when used. Assuming the AppComponent (which is the root component, the one using the <app-upvote> component) has a method on its class called “upvoted,” every time the user clicks the up-arrow, the vote count will increment, and the AppComponent will have its upvoted method called with the parameter value set to the current vote count (after incrementing).

Wrapping Up

Now, permit yourself a moment of congratulations; I’ve covered a fair amount of ground in this one, so it might take a while to settle in. Take a moment to experiment with the code and make sure the property-binding and event-binding syntaxes make sense, both from the “outside” as well as from the “inside.”

It might seem odd that you didn’t just use a number for the votes field in the UpvoteComponent; it certainly would be simpler to just track a number, but then a crucial part of the “model” concept gets lost, because now data (the vote count) would be duplicated, once in the UpvoteComponent and once wherever the vote count is tracked outside of the UpvoteComponent. Using the slightly more complicated syntax you saw previously, a user can provide an Upvote object as the parameter to the votes attribute, and the UpvoteComponent can increment it directly without requiring any additional work. In exchange for that bit of complexity buried away inside the component, clients will get a better degree of simplicity (which you’ll see as you build out the example further).

There’s still some more to do, but hopefully the component concept is starting to take clearer shape now. You still need to define a SpeakerComponent (and a Speaker model that holds the data for a given speaker), but you can well imagine what it’ll look like already, given what’s shown here. (I encourage you to take a shot at defining one before my next column comes out, to test yourself.) For now, however, happy coding!


Ted Neward is a Seattle-based polytechnology consultant, speaker and mentor, currently working as the director of developer relations at Smartsheet.com. He has written more than 100 articles, authored and coauthored a dozen books, and works all over the world. Reach him at ted@tedneward.com or read his blog at blogs.tedneward.com.

Thanks to the following technical expert for reviewing this article: Ward Bell


Discuss this article in the MSDN Magazine forum