Lets create a social media app (Part 1 - The Forms)

In this tutorial series we’ll try to build a social media app with Angular 2. This application will be like a stripped down Facebook. So there will be a register-/login form, profiles and so on. The great thing about this app is that we can use all of the core features of Angular2. So lets dive right into it. If you want to have the same styles as I have, you need to use: bootstrap-paper-css.

Note: Since there are already a lot of really good basic tutorials I may not explain everything. If you have trouble to follow, feel free to checkout the Angular2 form and Angular2 template syntax guide.

Goal Of This Part

In this part we want to build a form that will look like this: example form At first we’ll program a little example with Control, Controlgroups and Validators to see how they work. After that we’ll write our own custom controls and validators which will be more efficient for our use case.

The First Field

Before we get to the coding part make sure you have a folder structure like this: folder-structure The app.component.ts file is pretty straight forward:

import { RegisterComponent } from "./form/register.component";
import { Component } from 'angular2/core';
@Component({
  selector: 'app',
  directives: [RegisterComponent],
  template: `
  <register-form></register-form>
  `
})
export class AppComponent {
  constructor() { }
}

If you don’t know what the bootstrap.ts is, read my Angular2 setup tutorial.
Don’t be scared if you get an error at the moment. Its because we haven’t created the Registercomponent. <br> <br>

As you may have experienced, forms can get really big and intimidating. So lets take a look at a basic register form with only 1 field that behaves similarly to the example above:

import { Component, OnInit } from 'angular2/core';
import { FormBuilder, Validators, ControlGroup } from 'angular2/common';

@Component({
  selector: 'register-form',
  template: `
  <div class="container">
    <form [ngFormModel]="registerForm"
          (ngSubmit)="onSubmit()"
          #regForm="ngForm">

      <div class="form-group" [class.has-error]="!regForm.form.find('username').valid
                                                  && regForm.form.find('username').dirty">
        <label for="username">Username</label>
        <input type=text class="form-control"
              placeholder="username"
              [ngFormControl]="regForm.form.find('username')">

      </div>
      <div [hidden]="regForm.form.find('username').untouched ||
                     regForm.form.find('username').valid" class="alert alert-danger">

        Username is required
      </div>

      <button type="submit" class="btn btn-primary"
              [disabled]="!regForm.form.find('username').valid">Submit</button>

    </form>
  </div>
  `
})
export class RegisterComponent implements OnInit {
  private registerForm: ControlGroup;

  constructor(private _fb: FormBuilder) { }

  ngOnInit(): void {
    this.registerForm = this._fb.group({
      username: ['', Validators.required]
    });
  }

  onSubmit(): void {
    console.log('Username: ' + this.registerForm.find('username').value);
  }
}

Note: This example only shows 1 field.
HOLY COW that’s a lot of code/template for just 1 field. I’ll promise you that at the end of this tutorial this will look way better, but for now we leave it as it is and talk a little bit about controls.

How Controls Work

Controls are pretty straight forward in Angular2. You could create a Controlgroup like this:

this.username = new Control("", Validator.required);
this.email = new Control("", Validator.required);
this.password = new Control("", Validator.required);
this.telephone = new Control("", Validator.required);

this.formControl = new ControlGroup({
  username: this.username,
  email: this.email,
  password: this.password,
  telephone: this.telephone
});

So what’s happening here? At first we’ve created our Control objects with an empty string and a validator. The empty string is the initial value of that control. The second parameter tells us that the field on which the control is applied is required. For example if you apply the username control on a username input field it would be invalid as long as no username has been entered. But we don’t want to watch only a single input field, instead we are interested in the whole form. To achieve that we just wrap the form control around all controls that are important.
Since this is a lot of code we can shorten this a little bit by injecting the formbuilder and use him like this:

constructor(private _fb: FormBuilder) {
  this.registerForm = this._fb.group({
    username: ['', Validators.required],
    email: ['', Validators.required],
    telephone: ['', Validators.required],
    password: ['', Validators.required]
  });
}

This is the equivalent to the code given above.
You want to know a little bit more about this topic? Then go to: Angular2 - Forms

Build Form Field Components

To shorten our register component we want to encapsulate this code:

<div class="form-group" [class.has-error]="!regForm.form.find('username').valid
                                                  && regForm.form.find('username').dirty">
        <label for="username">Username</label>
        <input type=text class="form-control"
              placeholder="username"
              [ngFormControl]="regForm.form.find('username')">

      </div>
      <div [hidden]="regForm.form.find('username').untouched ||
                     regForm.form.find('username').valid" class="alert alert-danger">

        Username is required
      </div>

And to use that component we want to have code that looks something like this:

<formField name="Username" type="text" required="true" [control]="username"></formField>

That looks nice, does it? Well let’s see how we can achieve that. At first we create a new typescript file under the folder form/field and we call that file Formfield.ts (not the best name but ok for this tutorial). The file will look like this:

import { Component, Input, OnInit } from 'angular2/core';
import { Control } from 'angular2/common';

@Component({
  selector: 'formField',
  template: `
  <div class="form-group" [class.has-error]="!control.valid && control.dirty">
    <label [attr.for]="name">{{name}}</label>
    <input type={{type}}  class="form-control"
            placeholder="{{name}}"
            [ngFormControl]="control">
  </div>
  <div [hidden]="control.untouched || control.valid" class="alert alert-danger">
    This field is required.
  </div>
  `
})
export class FormFieldComponent implements OnInit{
  @Input() name: string;
  @Input() type: string = "text";
  @Input() required: boolean;
  @Input() control: Control;

  ngOnInit(): void {
    if(this.required) {
      this.name += " *";
    }
  }

}

Let’s take a little deeper look what each part actually does.

<div class="form-group" [class.has-error]="!control.valid && control.dirty">

This part will color our input field red as soon as the given control is not valid and the value of the input has changed.

<label [attr.for]="name">{{name}}</label>
    <input type="{{type}}"  class="form-control"
            placeholder="{{name}}"
            [ngFormControl]="control">

Here we’re applying the type(text/email…), which came as an input variable, and the control. The control is used to validate the field. More on this in the next chapter.

Last but not least we got this snippet:

<div [hidden]="control.untouched || control.valid" class="alert alert-danger">
  This field is required.
</div>

This will show the red bar right under the input as soon as the user has touched the input field and it is invalid. Wow that was actually pretty easy. Now we can reuse this form field everywhere we want… BUT wait. The error message will always be the same no matter what the actual error actually was. We have to change that. Technically we could provide the error message via an input variable to this component, but this wouldn’t be a good approach, since we have to declare the same error message every time we want to use the same formfield. Furthermore the best approach would be to define our error message within the validator, because he is responsible for the given error. So lets see how we can do this.

Build Custom Validations and Controls

As we’ve seen before, our error message is fix. To get around that we’ll first build a custom validator. This validator will check if our username starts with a letter and has at least 3 characters. We place our formfieldvalidator.service.ts file right under the validation folder.

import { Control } from 'angular2/common';
import { Injectable } from 'angular2/core';

export interface ValidationResult {
  message: string;
}

@Injectable()
export class FormFieldValidationService {
  constructor() {}

  validateUserName(control: Control): ValidationResult {
    const startsWithLetter_regex = /^[a-zA-Z]/;

    if(!control.value.match(startsWithLetter_regex)) {
      return {message: "Username has to start with a letter"};
    } else if(control.value.length < 3) {
      return {message: "Username must have more than 3 letters"};
    } else{
      return null;
   }
  }
}

Ok now I’m pretty sure you have a couple of questions. Why do we need that interface? Firstly we are a little bit more expressive with our expected return. But this doesn’t explain why we are returning an object anyway. Why not a string? Well for that we have to look in the AbstractControl class (the Control class inherits from AbstractControl):

export declare abstract class AbstractControl {
...
errors: {
    [key: string]: any;
};
...

As you can see the error field is just a map where each key is a string and the value can be anything. This is important because we can actually override that field and add the desired behavior. So lets do this by creating our custom Control within the form/control folder and call it FormControl.ts :

import { Control } from 'angular2/common';
import { ValidationResult } from '../validation/formvalidator.service';
export class FormControl extends Control {
  errors: ValidationResult;

  get errorMessage(): string {
    if(this.errors != null && this.errors.message != null) {
      return this.errors.message;
    }
    return null;
  }

}

Ok, cool. Now our errors will contain of ValidationResults(which are currently only strings). These Results have a message that we access via our getter. Wow that was a lot of stuff and not that easy to understand. If you want you can check my Github repo and take your 10minutes to grasp everything. I’m not sure either if that is best practice but I’m actually pretty satisfied with the result. If we put everything together our final register form looks like this:

import { FormControl } from "./contol/FormControl";
import { FormFieldComponent } from "./field/formfield.component";
import { Component, OnInit } from 'angular2/core';
import { ControlGroup } from 'angular2/common';
import { FormFieldValidationService } from './validation/formvalidator.service';

@Component({
  selector: 'register-form',
  directives: [FormFieldComponent],
  providers: [FormFieldValidationService],
  template: `
  <div class="container">
  <form [ngFormModel]="registerForm"
        (ngSubmit)="onSubmit()"
        #regForm="ngForm">

    <formField name="Username" type="text" required="true"
      [control]="regForm.form.find('username')"></formField>

    <formField name="Email" type="email" required="true"
      [control]="regForm.form.find('email')"> </formField>

    <formField name="Telephone" type="text"
      [control]="regForm.form.find('telephone')"> </formField>

    <formField name="Password" type="password" required="true"
      [control]="regForm.form.find('password')"> </formField>

    <button type="submit" class="btn btn-primary"
            [disabled]="!regForm.form.valid">Submit</button>

  </form>
  </div>
  `
})
export class RegisterComponent implements OnInit {
  private registerForm: ControlGroup;

  constructor(private _validationService: FormFieldValidationService) {}

  ngOnInit(): void {
    this.registerForm = new ControlGroup({
      username: new FormControl('', this._validationService.validateUserName),
      email: new FormControl('', this._validationService.validateEmail),
      telephone: new FormControl('', this._validationService.validateTelephoneNumber),
      password: new FormControl('', this._validationService.validatePassword),
    });
  }

  onSubmit(): void {
    console.log('Username: ' + this.registerForm.find('username').value);
    console.log('Email: ' + this.registerForm.find('email').value);
    console.log('Telephone: ' + this.registerForm.find('telephone').value);
  }
}

Lastly you have to change the control within the FormFieldComponent to:

@Input() control: FormControl;

Wow this looks way better than our first register component :).
Note: We can’t use the formbuilder anymore. I’m not entirely sure why it’s not working, but this code isn’t more complex than the code with the formbuilder.

Since I haven’t shown all validators you may want to visit my Github page I hope you liked this tutorial. If you have any questions feel free to ask me.


Copyright © 2016 - Mario Brendel Blog content licensed under the Creative Commons CC BY 2.5 | Unless otherwise stated or granted, code samples licensed under the MIT license. | Site design based on the hyde theme under the MIT license