Page 19 - MSDN Magazine, June 2018
P. 19
Figure 2 HTML Template
approach doesn’t do away with the template, but instead removes most of the logic away from the template and into the component. This is where things start to take a left turn from the techniques examined in the previous pair of columns. The component code will actually construct a tree of control objects, and most (if not all) interaction with the controls in these two forms will happen entirely inside the code base. I won’t put event-handlers in the template. Instead, I’ll hook them up inside the component code.
But first, I need to construct the controls themselves. These will all be FormControl instances, imported from the @angular/ forms module, but it can get a little tedious to construct them each (along with any required validations) by hand in code. Fortunately, Angular provides a FormBuilder class that’s designed to make things a bit more succinct and compact to construct a whole form’s worth of controls, particularly for longer (or nested) forms.
In the case of my speaker form—where I want to have a drop- down serve as a selection mechanism to choose which speaker in the list to work on—I need to do something a little different. I want to have two forms: one around the dropdown speaker-selection control, and the other containing the detail for the individual speaker. (Normally this would be two separate components in a master-detail kind of arrangement, but I haven’t covered routing yet.) Thus, I need two forms, and in Figure 3 I show how to con- struct them, both with the FormBuilder and without.
This is, of course, only an excerpt of the class—there’s a few more things I need to add before it’s ready to ship, but this code neatly demonstrates two of the ways to construct a form. The “selectGroup” is the FormGroup that contains the one FormControl for the HTML <select> control, and I use the SpeakerService to populate a local array of Speaker instances so the <select> can populate itself. (This is actually on the template, not in the component code. If there’s a way to populate the dropdown from the component code, I’ve not found it yet in Angular 5.)
The second form, called speakerForm, is populated using the Form- Builder, which is a tiny little DSL-like language for constructing controls. Notice how I call “group” to indicate that I’m constructing a group of controls, which are effectively name-value pairs. The name is the name of the control (which must match the formControlName property in the template for each control) and the value is either an initial value, or an initial value followed by an array of validation functions (two arrays, if you want to include validation functions that run asynchronously) to run to validate the value users type in to the control.
This constructs the two forms, but selecting something in the dropdown doesn’t do anything. I need to respond to the event the dropdown will broadcast, and I can do that by hooking a func- tion on to the “valueChanges” method of FormControl, like so:
export class SpeakerDetailComponent implements OnInit { // ...
ngOnInit() {
const speakerSelect = this.selectGroup.get('selectSpeaker'); speakerSelect.valueChanges.forEach(
(value:string) => {
const speaker = this.speakers.find( (s) => s.lastName === value); this.populate(speaker);
} );
}
// ... }
<h2>Select Speaker</h2>
<form [formGroup]="selectGroup"> <label>Speaker:
<select formControlName="selectSpeaker"> <option *ngFor="let speaker of speakers"
[value]="speaker.lastName">{{speaker.lastName}}</option> </select>
</label> </form>
<h2>Speaker Detail</h2>
<form [formGroup]="speakerForm" novalidate>
<div>
<label>First Name:
<input formControlName="firstName"> </label>
<label>Last Name:
<input formControlName="lastName"> </label>
<label>Age:
<input formControlName="age"> </label>
</div> </form>
easy way to mock out the service without having to build a Promise object in the longer fashion using the Promise constructor.
Next, the SpeakerDetail component is just a standard Angular component (“ng new component speaker-detail”), and the construc- tor should inject a SpeakerService instance as a private constructor parameter. (This was detailed in my August 2017 column, “How To Be MEAN: Angular Plays Fetch” [msdn.com/magazine/mt826349].) While you’re at it, use the constructor to call the SpeakerService’s getSpeakers method to get back the array, and store that locally into the component as a property called “speakers.” So far, this sounds a lot like the template-based component described earlier. The HTML template for this component will display a dropdown of all the speakers in the system (as obtained by getSpeakers), and then as each is selected, display the details in another set of controls underneath that dropdown. Thus, the template looks like Figure 2.
It may seem strange that the alternative to “template-based” forms uses HTML templates. That’s largely because the reactive-forms
Figure 3 Constructing Forms
export class SpeakerDetailComponent implements OnInit { selectGroup : FormGroup
speakerForm : FormGroup
speakers : Speaker[]
constructor(private formBuilder : FormBuilder, private speakerService : SpeakerService) {
speakerService.getSpeakers().then( (res) => { this.speakers = res; });
this.createForm(); }
createForm() {
this.selectGroup = new FormGroup({
selectSpeaker : new FormControl() });
this.speakerForm = this.formBuilder.group({ firstName: ['', Validators.required], lastName: ['', Validators.required], age: [0]
}); }
// ... }
msdnmagazine.com
June 2018 13