Tracking control interaction and validating input

Although we are already tracking changes in our input forms through the two-way data binding, we need a better way to watch the overall state of our form. In order to do so, we can take advantage of the NgForm directive. The NgForm directive keeps track of the state of all input controls found within it. The good news is that such a directive has been present in our example right from the beginning. How? Basically, the NgForm directive is configured in its selector to be attached to any <form> tag present in our template, providing additional features to our form as real-time tracking of the state of the input fields in respect of validity and user interaction. In other words, if you have a form in your template, you have a ngForm directive already.

Tip

You can cancel this automatic binding by appending the ngNoForm attribute to any <form> tag you do not want to be intervened by Angular.

Let's see all this through a simple example. First, mark all our fields with the HTML5 required attribute:

app/tasks/task-editor.component.html

<form class="container" (submit)="saveTask()">
  <h3>Task Editor:</h3>
  <div class="form-group">
    <input type="text" 
      class="form-control"
      placeholder="Task name" 
      [(ngModel)]="task.name"
      required>
  </div>

  <div class="form-group">
    <input type="date" 
      class="form-control"
      [(ngModel)]="task.deadline"
      required>
  </div>

  <div class="form-group">
    <input type="number" 
      class="form-control"
      placeholder="Pomodoros required"
      min="1"
      max="4"
      [(ngModel)]="task.pomodorosRequired"
      required>
  </div>

  <div class="form-group">
    <input type="checkbox" 
      name="queued"
      [(ngModel)]="task.queued">
    <label for="queued"> this task by default?</label>
  </div>

  <p>
    <input type="submit" class="btn btn-success" value="Save">
    <a [routerLink]="['TaskList']" class="btn btn-danger">
      Cancel
    </a>
  </p>
</form>

If we attempt to submit the form, automatic alerts will be triggered by the browser applying the validation policies enforced by the HTML5 form validation API. But there is a lot more happening under the hood. If we inspect the code of our form with the browser's developer tools, we will see a myriad of class names decorating the input controls now. Where did all these come from? The answer is simple! The NgForm directive, actioning our <form> tag, put all these there. Let's inspect the first input field through our browser's dev tools as an example:

<input type="text"
  class="form-control ng-untouched ng-pristine ng-invalid"
  placeholder="Task name"
  required="">

These classnames (easily identifiable by the ng- prefix) give us a very good hint to the different states any given input control can take when wrapped inside the NgForm directive. These class bindings are fully reactive to state changes in our input fields. With your dev tools pane open, select any input field, update its value and then empty it again and see how the classnames change, assuming any of the following states:

  • Untouched: When true, the control has not been interacted with the user
  • Touched: When true, the control has been interacted with the user
  • Pristine: The control and its underlying model has not been changed
  • Dirty: The control and its underlying model has been changed
  • Valid: The inner model is valid
  • Invalid: The inner model is not valid

When interacting with our form controls, we will see generated classnames matching these states represented with the ng- prefix here and there: ng-untouched, ng-pristine, ng-invalid, and so on.

Tracking changes with local references

Now that we know that our input controls can react to user interactions and model validation, we can take a step further and render more information on screen. For instance, we can style our form in a reactive fashion:

app/tasks/task-editor.component.ts

@Component({
  selector: 'pomodoro-tasks-editor',
  directives: [ROUTER_DIRECTIVES],
  providers: [Title],
  templateUrl: 'app/tasks/task-editor.component.html',
  styles: [`
    .ng-valid { border-color: #3c763d; }
    .ng-invalid { border-color: #a94442; }
    .ng-untouched { border-color: #999999; }
  `]
})
...

Our form will provide now visual hints of the overall state of each input through its visual layout, but perhaps rendering some messages on screen will compound up the user experience we want to deliver in our app. In order to do so, we need to go into each input control state, and template local references become quite handy for this. They will provide a valuable accessor to the general state handler of our form which is our ngForm directive. Let's insert a state message in our form and turn it into a watcher of the state of the first input field, using ngForm as a proxy:

app/tasks/task-editor.component.html

<form class="container" (submit)="saveTask()">
  <h3>Task Editor:</h3>
  <div class="form-group">
    <p class="text-muted" *ngIf="name.untouched">
      Start here by entering the task name.
    </p>
    <p class="text-success" *ngIf="name.valid && name.touched">
      Well done! That's a good name for a task!
    </p>
    <p class="text-danger" *ngIf="!name.valid && name.touched">
      Oops! You cannot leave the name blank...
    </p>
    <input type="text"
      class="form-control"
      placeholder="Task name"
      [(ngModel)]="task.name"
      #name="ngForm"
      required>
  </div>
...

Let's take a closer look at the preceding code. The first block is pretty straightforward: we will render different messages (using the styling provided by Bootstrap, as we did already with the rest of the form) depending of the overall state of the control. In order to refer to the input control, we need to create a local template variable so we can address it from a different element, and so we do by appending the #name local template reference in our control. Surprisingly, it is populated with a value though. Local template variables in Angular 2 can be populated with other values, or pointers to other objects and that's exactly what we are doing by pointing #name to the ngForm string. The NgForm directive exports itself under the ngForm name, so if we refer to its name from any local template reference, we will gain access to its API and, thus, its state.

Let's wrap up our journey into form building based on classical two-way data-binding. Nevertheless, there is another powerful way to build forms in Angular 2 that has nothing to do with our beloved [(ngModel)] directive, although it also provides support for the same functionalities and even extends support for some more features, becoming the preferred way for building forms in Angular 2.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.141.152.123