Page 16 - MSDN Magazine, November 2017
P. 16
The Working Programmer TED NEWARD How To Be MEAN: Angular Forms
Welcome back again, MEANers.
All throughout this series, the focus has been about Angular;
specifically, using Angular to display data and route through multiple pages. As much as the Web browser is built to be able to display data, however, it’s also intended to be able to gather data and pass it back to the server, and thus far, that’s been missing from the discussion.
Angular can certainly capture data through forms, but doing so is a touch tricky—not so much in the syntax of creating and defining the browser form, but in one particular aspect of the underlying behavior, which I’ll go over later. But let’s not put the cart before the horse. Time to talk about some simple form definition and capture.
Formed Components
In previous columns, I’ve talked about how you can bind “template statements” (to use the Angular terminology) to the events offered up by an Angular component. The most common example of that is capturing the click event on a <button> element to invoke a method on an Angular component:
<button (click)="console.log('Clicked')">Push me!</button>
In and of itself, this is good, but it doesn’t provide any facility to capture input—in order for this to be useful for data entry, one of two things has to happen: Either the code in the component has the ability to reference the controls on the page from inside the component (which is what ASP.NET Web Forms, among other frameworks, will do), or the code being invoked has to be able to receive the input data as a parameter. However, this can take a couple of different forms.
First, and most generic, the Angular template statement can reference the $event object, which is essentially the DOM’s event object generated during the user’s session. This simply requires referencing the parameter as part of the statement, such as:
<button (click)="capture($event)" >Save</button>
The drawback, however, is that the object passed is the DOM event representing the user’s action; in this case, a MouseEvent tracking the location on the screen where the mouse was clicked, and it doesn’t really capture the state of the other elements on the page. While it would certainly be possible to navigate the DOM hierarchy to find the control elements and extract the values from them, it’s also not really the Angular Way. Components should be isolated away from the DOM, and the template statement should be able to obtain the data it needs and pass it in to a method on the component for use.
This approach suggests that Angular needs some way to identify the input fields on the page, so that the template statement can pull values and pass them in. The need to be able to identify the form element takes the form of an “identifier” on the <input> element itself, what Angular calls a “template reference variable.” Like some of the other Angular syntax, it deliberately uses syntax that doesn’t look like HTML:
<input #firstName>
This will create an Input field in HTML, as per the normal HTML tag of the same name, but then introduce a new variable into the template’s scope, called firstName, which can be referenced from the template statement bound to an event from a field, like so:
<button (click)="addSpeaker(firstName, lastName)" >Save</button>
This is pretty self-explanatory: On a button click, invoke the addSpeaker method of the component, passing in the firstName and lastName variables, accordingly, as shown in Figure 1.
However, written like this, what shows up in the browser’s console isn’t the expected strings from the input; instead, values such as <input _ngcontent-crf-2> appear in place of each of those values. The reason for this is simple: The browser console returns the actual Angular representations of the DOM element, rather than the input data that was typed in. The solution to that is equally simple: Make use of the “value” property on each side of the tem- plate statement to get to the data the user typed in.
Thus, if I need to build a component for creating new speakers, I can create a component that displays two <input> fields, a <button> that has a (click) that calls addSpeaker, passing in firstName.value and lastName.value, and use that method to invoke the Speaker- Service (from an earlier article) to save it to the database. But this idea of “creating a new Speaker” is conceptually very close to “editing an existing Speaker,” so much so that some modern databases have
Figure 1 Invoking the addSpeaker Method
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-speaker-edit',
templateUrl: './speaker-edit.component.html', styleUrls: ['./speaker-edit.component.css']
})
export class SpeakerEditComponent implements OnInit {
constructor() { }
ngOnInit() { }
addSpeaker(fname: string, lname: string) {
console.log("addSpeaker(", fname, ",", lname, ")") }
}
12 msdn magazine