December 2017

Volume 32 Number 12

[The Working Programmer]

How to be MEAN: Angular Forms, Too

By Ted Neward | December 2017

Ted NewardWelcome back again, MEANers.

In the last column (msdn.com/magazine/mt845619), I talked about how to create forms with Angular, but candor compels me to admit a rather severe fact: I’ve really only begun to scratch the surface of the capabilities of Angular regarding forms, input, validation and more. For example, in the previous version of Angular (the not-at-­all-confusing version called AngularJS), it was possible to set up “two-way binding” so that form input modified an ECMAScript object held in memory within the browser, which meant code could worry about model objects and not the input fields containing the data.

Angular hasn’t abandoned that concept, but it was necessary to show you the syntax for event binding against input controls before we could get there. Now that you’ve seen it, you can begin to explore Angular form capabilities in greater depth.

SpeakerUI

In the last column, I showed you how to build a component called SpeakerEdit that captured user input. Bah. How entirely un-component-oriented of me. What you really should want, if this is going to take a truly component-oriented approach, is to encapsulate the details of displaying or editing a Speaker. That is, you want a component that can be handed a Speaker model object (for edit/update or even delete), a Speaker ID (meaning the Speaker model object needs to be fetched from the server), or “undefined” (in which case you’re asking the user to populate a new Speaker model object), and “do the right thing” in each case. In short, the component should know already what to display and how to display it, based solely on the contents (or lack thereof) of a corresponding Speaker object.

Let’s start by generating a SpeakerUI component. (I tend to use “UI” as a suffix, to indicate that this is intended as a standalone UI component around the Speaker model type. Some readers may prefer “SpeakerDetail,” because this is allowing for the view or edit of the different details of a Speaker model. As always, whichever convention you choose, stick with it.) This is comfortable territory for you by now: “ng generate component SpeakerUI.”

However, I’m going to make a slight addition to Speaker, to help show off some of the power of the TypeScript language when it comes to UI. To demonstrate a few different things, I’m going to mention that in between the last column and this, the application’s client has said that Speakers have one or more Subjects on which they speak, like so:

import { Upvote } from './upvote/upvote';
import { Subject } from './subject';
export class Speaker {
  id: number;
  firstName: string;
  lastName: string;
  votes: Upvote;
  subjects: /*something*/[];
}

The Subject type will be defined in subject.ts, but you have a couple of options here. If subject makes sense to model as a string—meaning the actual value will be a raw, unadorned TypeScript string—but can only be of a few values, then you can use a TypeScript “string literal type” in TypeScript, like so:

export type Languages = "C#" | "Visual Basic" |
  "F#" | "ECMAScript" | "TypeScript"

Essentially, it’s an enumerated string—instances of Subject can only be populated with the values specified in the “or”-style assignment expression, in this case, one of five different Microsoft languages. That said, however, as of TypeScript 2.4, it’s also possible to write a full-blown enumerated type using string backing for storage, like so:

export enum Subject {
  CSHARP = 'C#', FSHARP = 'F#',
  VB = 'Visual Basic', ES = 'ECMAScript',
  TS = 'TypeScript'
}

This will have a few benefits when you get to modeling the checkboxes that will hold these five values for selection; the drawback to using the enumerated type, of course, is that it cannot hold anything other than one of the five possible Subject values. (Languages is, at heart, a string, and so any one of its five values can be stored as a string, which could of course be in a dropdown, or you could have an open-ended edit field allow users to type in other values beyond these.)

Let’s assume the enum is the preferred choice for now, and that the Speaker has an array of Subject instances, called “subjects.” That’s all to be done with that for now.

Editing? Or Just Viewing?

From a UI perspective, it’s usually preferential if the UI component can know whether the instance being displayed is being edited or just examined; sometimes you’ll merely want to display the details of the Speaker, whereas at other times, you’ll want to be able to edit them. The UI will often want to display the details differently when being edited (using edit field controls) than when being viewed (just leaving it as plain text) so as to provide a clear hint to the user when something is editable. Angular supports this, in a slightly roundabout way.

To begin, you need the component to know whether it’s readonly or not; the easiest way to model that is to have a private Boolean field in the component by that exact name:

@Component({
  selector: 'app-speaker-ui',
  templateUrl: './speaker-ui.component.html',
  styleUrls: ['./speaker-ui.component.css']
})
export class SpeakerUIComponent implements OnInit {
  @Input() model: Speaker | number | undefined;
  public readonly = true;

Defaulting readonly to true is a matter of personal taste or application context.

Once that field is established, the template can differentiate between the two states, and choose between one of two different div sections in the template by setting the div hidden attribute to true or false accordingly:

<form>
  <div [hidden]="readonly">
    ...
  </div>
  <div [hidden]="!readonly">
    ...
  </div>
</form>

Here, the first section is hidden if the readonly flag is set; that makes it the editable section displayed; the second section, therefore, is the read-only section (that’s to say, it’s not hidden if the component isn’t read-only). Notice how the property-binding syntax (the square brackets) is used to bind to the component’s readonly field, inverting it when necessary using the Angular expression syntax.

Viewing

The second readonly section is pretty straightforward: Simply use the Angular double-bracket syntax to display the values of the model’s fields, and display a button labeled Edit to kick the component over into editing mode when the user wants to edit the Speaker instance:

<form>
  <div [hidden]="readonly">
    ...
  </div>
  <div [hidden]="!readonly">
    FirstName: {{model.firstName}}<br>
    LastName: {{model.lastName}}<br>
    Subjects: <span>{{model.subjects}}</span><br>
    <button (click)="edit()">Edit</button>
  </div>
</form>

The edit function on the component will flip the readonly field to false, but this brings up an interesting question of functionality. Normally, when some kind of editing is presented to the user, there’s also an opportunity to undo that editing and go back to the original state via a Cancel button. If that functionality is wanted or needed here, then you need to cache off the original values for later retrieval if the user selects Cancel. Therefore, a new field, cached (of Speaker type), should be added and the current model values copied over into it:

edit() {
  this.readonly = false;
  this.cached = new Speaker();
  this.cached.id = (<Speaker>this.model).id;
  this.cached.firstName = (<Speaker>this.model).firstName;
  this.cached.lastName = (<Speaker>this.model).lastName;
  this.cached.votes = (<Speaker>this.model).votes;
  this.cached.subjects = (<Speaker>this.model).subjects;
}

This will bring the UI into editing mode.

Editing

Editing, then, uses input fields instead of the double-bracketed syntax, but Angular holds another surprise: You can use the ngModel directive to help Angular provide some additional form-relevant behavior, such as automatically double-binding the model (the Speaker object) to the various form fields, and giving some predefined behavior for when fields get edited.

The first step is to tell Angular that this is a form for which model support is wanted; this is done by creating a template variable reference for the form object on the form tag in the template:

<form #speakerForm="ngForm">
  ...
</form>

This tells Angular that you want Angular to “do its form thing.” From there, you can use the ngModel attribute on the different input fields to provide clear binding from field to model without requiring any additional work, as shown in Figure 1.

Figure 1 Using the ngModel Attribute

<form #speakerForm="ngForm">
  <div [hidden]="readonly">
    FirstName: <input name="firstName" type="text"
                      [(ngModel)]="model.firstName"><br>
    LastName:  <input name="lastName" type="text"
                      [(ngModel)]="model.lastName"><br>
    Subjects: <span>{{model.subjects}}</span><br>
    <button (click)="save()"
            [disabled]="!speakerForm.form.valid">Save</button>
    <button [disabled]="speakerForm.form.pristine"
            (click)="cancel()">Cancel</button>
    <br><span>{{diagnostic}}</span>
  </div>
  <div [hidden]="!readonly">
    . . .
  </div>
</form>

There’s several interesting things going on in Figure 1.

First, notice the “{{diagnostic}} at the bottom of the form; this is a useful trick to see what’s going on inside the component as you’re editing/viewing the component. To do this on your own components, define a property-get method called diagnostic that dumps interesting elements:

get diagnostic() {
  return 'readonly:' + this.readonly + ';'
    + 'cached:' + JSON.stringify(this.cached) + ';'
    + 'model:' + JSON.stringify(this.model);
}

This is useful to see that Angular literally changes the model object in memory in response to each and every keystroke that appears in the input forms. (It’s also useful to help make sure that the caching behavior—for cancellation support—is behaving correctly, though realistically that should be unit-tested before accepting it as genuine and bug-free.)

Second, notice how the firstName and lastName input fields have a dual-mode binding attribute on them called ngModel, referencing the property of the model object to which these fields should be bound. The dual-mode binding (the [(…)] syntax) suggests that changes to either the underlying model or the UI will be automatically reflected in the other, leaving little to do programmatically here. This is almost identical to the two-way binding from AngularJS, which was always one of the strengths of that framework.

Finally, notice how the Save button is only enabled if speaker­Form.form (the form object defined via the # syntax in the form element) has a valid property that’s false. Valid is one of several properties the form exposes to indicate whether the form would require a save. Several other properties are present, including the pristine property referenced in the Cancel button, but that discussion has to be deferred until the next column, when I talk about form validation.

The save and cancel methods, then, are pretty easy to imagine:

save() {
  this.cached = undefined;
  this.readonly = true;
}
cancel() {
  this.readonly = true;
  // Bring back cached values
  this.model = this.cached;
  // Clear out cache
  this.cached = undefined;
}

Again, all of this is simply manipulating the two-state state machine to go back and forth between editing and viewing mode with cancel support.

Wrapping Up

With a quick dip of the pen (so to speak), I’ve opened up a fairly large subject, to be sure. Angular’s support for form-based input is extensive, to say the least, particularly when combined with some of the component lifecycle methods, such as ngOnInit, where some initialization will usually take place to establish the initial contents of the UI. In the SpeakerUI, for example, ngOnInit will look at the model input property, and if it’s undefined, assume a new Speaker is being created; if it’s a number, assume that ID from the SpeakerService needs to be fetched; and if it’s a Speaker object, assume that’s the object wanted to use as the model. It would be reasonable to assume that other lifecycle methods could play a part in the component’s interaction with the surrounding system, as well, depending on circumstances.

In the next column, I’ll look at the form validation capabilities of Angular, so that both a first and a last name must be required of the speakers, among other things. I’ll also examine how new subjects on Speakers can be captured, and toss in the Upvote component that was constructed oh-so-long ago. In the meantime, 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 a ton of articles, authored and co-authored a dozen books, and speaks 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: Garvice Eakins


Discuss this article in the MSDN Magazine forum