Learn Angular 4.0 In 10 Days - ngForms And ngContent - Day Six

Let us start day six of Learning Angular 4.0 in 10 Days Series. In the previous articles, we already discussed Pipes and ViewChild in Angular 4.0. If you want to read the previous articles of this series, do visit the below link.
Angular 4.0 Forms – What it IS?

In today's web applications, a large category of front-end applications is very much form dependent, especially in the case of large enterprise type development. Most of these applications contains simply a huge or large form which contains multiple tabs, dialogs and with non – trivial business validation logic. Forms are a very important of part of the applications. In a component-based application, we always want to split a form into small and reusable pieces of code which are stored within the Smart and Dumb components. These components are normally spread over the entire application which provides several architectural benefits, including flexibility and design changes.

In Angular 4.0 Framework, we have two different mechanisms related to form-binding.

  1. Template Driven Form
  2. Reactive or Model-Driven Forms

In this article, we will discuss the Template-Driven Forms.

Angular 4 tackles forms via the famous ngModel. The instantaneous two-way data binding of ng-model in Angular 1 is really a life-saver as it allows to transparently keep in sync a form with a view model. Forms built with this directive can only be tested in an end to end test because this requires the presence of a DOM, but still, this mechanism is very useful and simple to understand. Unlike the case of AngularJs 1.0, ngModel and other form-related directives are not available by default, we need to explicitly import them in our application module. For include the form module in our application, we need to inject the FormModule in our application and bootstrapped it.

Template Driven Forms Features
  • Easy to use
  • Suitable for simple scenarios and fails for complex scenarios
  • Similar to angular 1.0
  • Two-way data binding (using [(NgModel)] syntax)
  • Minimal component code
  • Automatic track of the form and its data
  • Unit testing is another challenge
Advantages and Disadvantages of Template Driven Forms

In this simple example we cannot really see it, but keeping the template as the source of all form validation truth is something that can become pretty hairy rather quickly.

As we add more and more validator tags to a field or when we start adding complex cross-field validations the readability of the form decreases, to the point where it will be harder to hand it off to a web designer.

The upside of this way of handling forms is its simplicity, and it is probably more than enough to build a very large range of forms.

On the downside, the form validation logic cannot be unit tested. The only way to test this logic is to run an end to end test with a browser, for example using a headless browser like PhantomJs.

While using of directives in our templates gives us the power of rapid prototyping without too much boilerplate, we are restricted in what we can do. Reactive forms, on the other hand, let us define our form through the code and give us much more flexibility and control over data validation.

Advantages of Model Driven Forms
  • UNIT TESTABLE
    Since we have the form model defined in our code, we can unit test it.
  • LISTEN TO FORM AND CONTROLS CHANGES
    With reactive forms, we can listen to form or control changes easily. Each form group or form control exposes a few events which we can subscribe to (e.g. statusChanges, valuesChanges, etc).

To begin, we must first ensure we are working with the right directives and the right classes in order to take advantage of procedural forms. For this, we need to ensure that the ReactiveFormsModule was imported in the bootstrap phase of the application module. This will give us access to components, directives, and providers like FormBuilder, FormGroup, and FormControl.

Sample Code of app.component.formdemo1.ts
  1. import { Component, OnInit, ViewChild } from '@angular/core';  
  2. import { NgForm } from '@angular/forms';  
  3.   
  4. @Component({  
  5.     moduleId: module.id,  
  6.     selector: 'form-demo-1',  
  7.     templateUrl: 'app.component.formdemo1.html'  
  8. })  
  9.   
  10. export class FormDemo1Component implements OnInit {  
  11.   
  12.     private formData: any = {};  
  13.     private showMessage: boolean = false;  
  14.   
  15.     constructor() {  
  16.     }  
  17.   
  18.     ngOnInit(): void {  
  19.     }  
  20.   
  21.     registerUser(formdata: NgForm) {  
  22.         this.formData = formdata.value;  
  23.         this.showMessage = true;  
  24.     }  
  25. }  
Sampse Code of app.component.formdemo1.html
  1. <h2>Template Driven Form</h2>  
  2. <div>  
  3.     <form #signupForm="ngForm" (ngSubmit)="registerUser(signupForm)">  
  4.         <table style="width:60%;" cellpadding="5" cellspacing="5">  
  5.             <tr>  
  6.                 <td style="width :40%;">  
  7.                     <label for="username">User Name</label>  
  8.                 </td>  
  9.                 <td style="width :60%;">  
  10.                     <input type="text" name="username" id="username" [(ngModel)]="username" required>  
  11.                 </td>  
  12.             </tr>  
  13.             <tr>  
  14.                 <td style="width :40%;">  
  15.                     <label for="email">Email</label>  
  16.                 </td>  
  17.                 <td style="width :60%;">  
  18.                     <input type="text" name="email" id="email" [(ngModel)]="email" required>  
  19.                 </td>  
  20.             </tr>  
  21.             <tr>  
  22.                 <td style="width :40%;">  
  23.                     <label for="password">Password</label>  
  24.                 </td>  
  25.                 <td style="width :60%;">  
  26.                     <input type="password" name="password" id="password" [(ngModel)]="password" required>  
  27.                 </td>  
  28.             </tr>  
  29.             <tr>  
  30.                 <td style="width :40%;"></td>  
  31.                 <td style="width :60%;">  
  32.                     <button type="submit">Sign Up</button>  
  33.                 </td>  
  34.             </tr>  
  35.         </table>  
  36.     </form>  
  37.     <div *ngIf="showMessage">  
  38.         <strong>Thanks You {{formData.username}} for registration</strong>  
  39.     </div>  
  40. </div>  
Output

 
Form Control

Note that the FormControl class is assigned to similarly named fields, both on this and in the FormBuilder#group({ }) method. This is mostly for ease of access. By saving references to the FormControl instances on this, you can access the inputs in the template without having to reference the form itself. The form fields can otherwise be reached in the template by using loginForm.controls.username and loginForm.controls.password. Likewise, any instance of FormControl in this situation can access its parent group by using its .root property (e.g. username.root.controls.password). A FormControl requires two properties: an initial value and a list of validators. Right now, we have no validation.

Validating Reactive Forms

Building from the previous login form, we can quickly and easily add validation. Angular provides many validators out of the box. They can be imported along with the rest of dependencies for procedural forms. We are using .valid and .untouched to determine if we need to show errors - while the field is required, there is no reason to tell the user that the value is wrong if the field hasn't been visited yet. For built-in validation, we are calling .hasError() on the form element, and we are passing a string which represents the validator function we included. The error message only displays if this test returns true.

Reactive Forms Custom Validation

As useful as the built-in validators are, it is very useful to be able to include your own. Angular allows you to do just that, with minimal effort. A simple function takes the FormControl instance and returns null if everything is fine. If the test fails, it returns an object with an arbitrarily named property. The property name is what will be used for the .hasError() test.

  1. <div [hidden]="!password.hasError('needsExclamation')">  
  2.   Your password must have an exclamation mark!  
  3. </div>  
Sample  code of app.component.formdemo2.ts
  1. import { Component, OnInit, ViewChild } from '@angular/core';  
  2. import { Validators, FormBuilder, FormControl, FormGroup  } from '@angular/forms';  
  3.   
  4. @Component({  
  5.     moduleId: module.id,  
  6.     selector: 'form-demo-2',  
  7.     templateUrl: 'app.component.formdemo2.html'  
  8. })  
  9.   
  10. export class FormDemo2Component implements OnInit {  
  11.   
  12.     private formData: any = {};  
  13.   
  14.     username = new FormControl('', [  
  15.         Validators.required,  
  16.         Validators.minLength(5)  
  17.     ]);  
  18.   
  19.     password = new FormControl('', [  
  20.         Validators.required,  
  21.         hasExclamationMark  
  22.     ]);  
  23.   
  24.     loginForm: FormGroup = this.builder.group({  
  25.         username: this.username,  
  26.         password: this.password  
  27.     });  
  28.   
  29.   
  30.     private showMessage: boolean = false;  
  31.   
  32.     constructor(private builder: FormBuilder) {  
  33.     }  
  34.   
  35.     ngOnInit(): void {  
  36.     }  
  37.   
  38.     registerUser() {  
  39.         this.formData = this.loginForm.value;  
  40.         this.showMessage = true;  
  41.     }  
  42. }  
  43.   
  44. function hasExclamationMark(input: FormControl) {  
  45.     const hasExclamation = input.value.indexOf('!') >= 0;  
  46.   
  47.     return hasExclamation ? null : { needsExclamation: true };  
  48. }  
Sample code of app.component.formdemo2.html
  1. <h2>Reactive Form Module</h2>  
  2. <div>  
  3.     <form [formGroup]="loginForm" (ngSubmit)="registerUser()">  
  4.         <table style="width:60%;" cellpadding="5" cellspacing="5">  
  5.             <tr>  
  6.                 <td style="width :40%;">  
  7.                     <label for="username">User Name</label>  
  8.                 </td>  
  9.                 <td style="width :60%;">  
  10.                     <input type="text" name="username" id="username" [formControl]="username">  
  11.                     <div [hidden]="username.valid || username.untouched">  
  12.                         <div>  
  13.                             The following problems have been found with the username:  
  14.                         </div>  
  15.                         <div [hidden]="!username.hasError('minlength')">  
  16.                             Username can not be shorter than 5 characters.  
  17.                         </div>  
  18.                         <div [hidden]="!username.hasError('required')">  
  19.                             Username is required.  
  20.                         </div>  
  21.                     </div>  
  22.                 </td>  
  23.             </tr>             
  24.             <tr>  
  25.                 <td style="width :40%;">  
  26.                     <label for="password">Password</label>  
  27.                 </td>  
  28.                 <td style="width :60%;">  
  29.                     <input type="password" name="password" id="password" [formControl]="password">  
  30.                     <div [hidden]="password.valid || password.untouched">  
  31.                         <div>  
  32.                             The following problems have been found with the password:  
  33.                         </div>  
  34.   
  35.                         <div [hidden]="!password.hasError('required')">  
  36.                             The password is required.  
  37.                         </div>  
  38.                         <div [hidden]="!password.hasError('needsExclamation')">  
  39.                             Your password must have an exclamation mark!  
  40.                         </div>  
  41.                     </div>  
  42.                 </td>  
  43.             </tr>  
  44.             <tr>  
  45.                 <td style="width :40%;"></td>  
  46.                 <td style="width :60%;">  
  47.                     <button type="submit" [disabled]="!loginForm.valid">Log In</button>  
  48.                 </td>  
  49.             </tr>  
  50.         </table>  
  51.     </form>  
  52.     <div *ngIf="showMessage">  
  53.         <strong>Thanks You {{formData.username}} for registration</strong>  
  54.     </div>  
  55. </div>  
Output 

 
Transclusion

In Angular 1.0, there is a concept of Transclusion. Actually, transclusion in an Angular 1.x represents the content replacement such as a text node or HTML and injecting it into a template at a specific entry time. The same thing in Angular 4.0 is totally forbidden. This is now done in Angular 4.0 through modern web APIs such as shadow DOM which is known as content projection.

What is Content Projection?

So now, we know what we are looking from an Angular 1.x perspective so that we can easily migrate the same in Angular 4.0. Actually, the projection is a very important concept in Angular. It enables the developers to develop or build reusable components and make the application more scalable and flexible.

In Web Components, we had the <content> element, which was recently deprecated, which acted as a Shadow DOM insertion point. Angular 4 allows Shadow DOM through the use of ViewEncapsulation. Early beta versions of Angular 4 adopted the <content> element, however, due to the nature of a bunch of Web Component helper elements being deprecated, it was changed to <ng-content>. Actually, View encapsulation defines whether the template and styles defined within the component can affect the whole application or vice versa. Angular provides three encapsulation strategies:

  1. Emulated (default)
    styles from the main HTML propagate to the component. Styles defined in this component's @Component decorator are scoped to this component only.

  2. Native
    styles from the main HTML do not propagate to the component. Styles defined in this component's @Component decorator are scoped to this component only.

  3. None
    styles from the component propagate back to the main HTML and therefore are visible to all components on the page. Be careful with apps that have None and Native components in the application. All components with None encapsulation will have their styles duplicated in all components with Native encapsulation.

To illustrate ng-content that, suppose we have a children component,

  1. @Component({  
  2.     selector: 'child',  
  3.     template: `  
  4.       <div>  
  5.         <h4>Child Component</h4>  
  6.         {{ _childInfo }}  
  7.       </div>  
  8.     `  
  9. })  
  10. export class ChildComponent {  
  11.   _childInfo = "Base Area";  
  12. }  

What should we do if we want to replace {{_childInfo}} to any HTML that provided to ChildComponent? One tempting idea is to define an @Input containing the text, but what if you wanted to provide styled HTML or other components? Trying to handle this with an @Input can get messy quickly, and this is where content projection comes in. Components by default support projection, and you can use the ngContent directive to place the projected content in your template.

So, change ChildComponent to use projection:

  1. import { Component } from '@angular/core';  
  2.   
  3. @Component({  
  4.   selector: 'child',  
  5.   template: `  
  6.     <div style="border: 1px solid blue; padding: 1rem;">  
  7.       <h4>Child Component</h4>  
  8.       <ng-content></ng-content>  
  9.     </div>  
  10.   `  
  11. })  
  12. export class ChildComponent {  
  13. }  

Then, when we use ChildComponent in the template:

  1. <child>  
  2.     <p>My <i>dynamic</i> content.</p>  
  3. </child>  

This is telling Angular, that for any markup that appears between the opening and closing tag of <child>, to place inside of <ng-content></ng-content>. When doing this, we can have other components, markup, etc projected here and the Child Component does not need to know about or care what is being provided.

But what if we have multiple <ng-content></ng-content> and want to specify the position of the projected content to certain ng-content? For example, for the previous ChildComponent, if we want to format the projected content into an extra area1 and area2 section. Then in the template, we can use directives, say, <area1> to specify the position of projected content to the ng-content with select="area1".

Sample Code of app.component.modal.html
  1. <div class="modal" id="myModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true" [ngStyle]="{'display' : display }">  
  2.     <div class="modal-dialog">  
  3.         <div class="modal-content animated bounceInRight">  
  4.             <div class="modal-header">  
  5.                 <button type="button" class="close" (click)="fnClose()">×</button>  
  6.                 <strong class="modal-title">{{header}}</strong>  
  7.             </div>  
  8.             <div class="modal-body">  
  9.                 <ng-content select="content-body"></ng-content>  
  10.             </div>  
  11.             <div class="modal-footer">  
  12.                 <ng-content select="content-footer"></ng-content>  
  13.             </div>  
  14.         </div>  
  15.     </div>  
  16. </div>  
Sample Code of app.component.modal.ts
  1. import { Component, OnInit, ViewChild, Input } from '@angular/core';  
  2.   
  3. @Component({  
  4.     moduleId: module.id,  
  5.     selector: 'modal-window',  
  6.     templateUrl: 'app.component.modal.html'  
  7. })  
  8.   
  9. export class ModalComponent implements OnInit {  
  10.     @Input() private display: string = 'none';  
  11.     @Input('header-caption'private header: string = 'Modal';  
  12.   
  13.     constructor() {  
  14.     }  
  15.   
  16.     ngOnInit(): void {  
  17.     }  
  18.   
  19.     private fnClose(): void {  
  20.         this.display = 'none';  
  21.     }  
  22.   
  23.     showModal(): void {  
  24.         this.display = 'block';  
  25.     }  
  26.   
  27.     close(): void {  
  28.         this.fnClose();  
  29.     }  
  30.   
  31.     setModalTitle(args: string): void {  
  32.         this.header = args;  
  33.     }  
  34. }  
Sample Code of app.component.modaldemo.html
  1. <div>  
  2.     <h2>Demonstrate Modal Window using ngContent</h2>  
  3.     <input type="button" value="Show Modal" class="btn-group" (click)="fnOpenModal()" />  
  4.     <br />  
  5.     <modal-window [header-caption]="caption" #modal>  
  6.         <content-body>  
  7.             <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum tincidunt est vitae ultrices accumsan. Aliquam ornare lacus adipiscing, posuere lectus et, fringilla augue.</p>  
  8.             <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum tincidunt est vitae ultrices accumsan. Aliquam ornare lacus adipiscing, posuere lectus et, fringilla augue.</p>  
  9.             <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum tincidunt est vitae ultrices accumsan. Aliquam ornare lacus adipiscing, posuere lectus et, fringilla augue.</p>  
  10.             <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum tincidunt est vitae ultrices accumsan. Aliquam ornare lacus adipiscing, posuere lectus et, fringilla augue.</p>  
  11.             <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum tincidunt est vitae ultrices accumsan. Aliquam ornare lacus adipiscing, posuere lectus et, fringilla augue.</p>  
  12.         </content-body>  
  13.         <content-footer>  
  14.             <input type="button" class="btn-default active" class="btn btn-primary" value="Modal Close" (click)="fnHideModal();" />   
  15.         </content-footer>  
  16.     </modal-window>  
  17. </div>  
Sample Code of app.component.modaldemo.ts
  1. import { Component, OnInit, ViewChild } from '@angular/core';  
  2. import { ModalComponent } from './app.component.modal';  
  3.   
  4. @Component({  
  5.     moduleId: module.id,  
  6.     selector: 'parent-content',  
  7.     templateUrl: 'app.component.modaldemo.html'  
  8. })  
  9.   
  10. export class ModalDemoComponent implements OnInit {  
  11.   
  12.     private caption: string = 'Custom Modal';  
  13.     @ViewChild('modal'private _ctrlModal: ModalComponent;  
  14.   
  15.     constructor() {  
  16.     }  
  17.   
  18.     ngOnInit(): void {  
  19.     }  
  20.   
  21.     private fnOpenModal(): void {  
  22.         this._ctrlModal.showModal();  
  23.     }  
  24.   
  25.     private fnHideModal(): void {  
  26.         this._ctrlModal.close();  
  27.     }  
  28. }  

Next Recommended Readings