In the first part of this article, we deal with foundations of the form support. We then describe in a second part more advanced features to make form creation and processing more concise and robust. It’s now time to tackle the form submission.
Handling form submission
Angular 2 automatically applies the NgForm
directive to the HTML form element. The main consequence is that we need to disable the default behavior of browsers and apply the one from Angular 2. That way a specific event is fired and processing remains in the same page.
Submitting data
To submit data, we need to leverage the submit event of the form with the Angular 2 syntax. This event is triggered when clicking on an HTML submit button.
Simply register processing against this event using the bracket expression. In the following sample, we call the onSubmit
method of the component when the submit
event occurs.
<form [ngFormModel]="companyForm" (submit)="onSubmit()">
(...)
<button type="submit">Save</button>
(...)
</form>
This onSubmit
method will leverage the CompanyService
service injected in the component to actually update the company data in the RESTful service. To give the user some feedback, we can use a submitPending
property to display a spinning icon during the execution of the HTTP request. When response is back, you can hide it and display a message using the Toastr library.
onSubmit() {
this.submitPending = true;
this.service.updateCompany(this.companyId, this.company).subscribe(
company => {
toastr.info('Company successfully updated');
this.submitPending = false;
}, error => {
(...)
}
);
return false;
}
We will see in the next section how to handle errors.
This submitPending
property also allows us to disable the submit button to prevent users from clicking several times. For this we can simply use interpolation for the disabled
attribute of the button. This way the value of this attribute will be linked to the provided expression. This can be combined with the pending
property of the form control to disable the button as well during asynchronous validation.
<button type="submit" [disabled]="submitPending || companyForm.pending">
Save
<span *ngIf="submitPending"
class="glyphicon glyphicon-refresh glyphicon-refresh-animate"></span>
</button>
Here is the result.
Form submission error handling
In the previous section, we intentionally didn’t handle errors that could occur when executing the HTTP request to save the company. To make this processing more robust, we need to handle them. This must be done at two different levels.
First in the service. Since the map operator isn’t called in the case of failures, we need to extract the JSON error content from the payload using the catch operator, as described below:
updateCompany(companyId:string,company:Company) {
var headers = new Headers();
this.createAuthorizationHeader(headers);
headers.append('Content-Type', 'application/json');
return this.http.put(
`https://Angular 2.apispark.net/v1/companies/${companyId}`,
JSON.stringify(company), {
headers: headers
}).map(res => res.json()).catch(res => {
return Observable.throw(res.json());
});
}
Observables provide operators to configure asynchronous data streams. This aspect is part of Reactive Programming that will be discussed in a following article. We used the map operator in a previous Angular2 article in the section “Interacting with the Web API”.
This way we will receive the error as JSON object when defining a second callback at the level of the subscribe method in the component. We can then directly handle it.
onSubmit() {
this.service.updateCompany(this.companyId, this.company).subscribe(
company => {
(...)
}, error => {
this.displayErrors(error);
}
);
return false;
}
Now we receive the error payload within the error callback registered in the subscribe method, we can distinguish two kinds of errors to handle within the displayErrors method:
- Global errors that notify that the update fails
- Server validation errors that are linked to the fields of the form
The method has the responsibility to handle both cases. In the first one, an error property is set with the message. A dedicated component will then use this property to display the message.
For field errors, the method will look for fields with errors and set them within their corresponding controls. All these hints are present in the error content as we can see within the DHC HTTP client.
Here is its complete implementation.
displayErrors(error) {
if (error.messages) {
var messages = error.messages;
messages.forEach((message) => {
this.companyForm.controls[message.property].setErrors({ remote: message.message });
});
} else {
this.error = `${error.reasonPhrase} (${error.code})`;
}
}
Server validation errors for fields are displayed the same way as the local ones at the level of field inputs.
To display global errors as Bootstrap does, we need to go further by creating a simple component, as described below. It will leverage the alert* classes of the library.
@Component({
selector: 'error',
template: `
<div *ngIf="error" class="form-group form-group-sm">
<div class="alert alert-warning alert-dismissible col-md-10 col-sm-10" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
{{error}}
</div>
</div>
`
})
export class FormErrorComponent {
@Input()
error:string;
}
Simply add an tag in your form to specify where to display the error after having adding the FormErrorComponent component into the directives attribute of the component containing the form:
@Component({
directives: [ (...), FormErrorComponent ],
template: `
<form [ngFormModel]="companyForm" (submit)="onSubmit()">
(...)
<error [error]="error"></error>
</form>
`
})
export class DetailsComponent {
(...)
}
Here is how such messages will be displayed in the form.
Conclusion
In this article, we described how to implement forms with Angular 2. We went beyond the basics to show how to leverage the power of Angular 2. We saw how the standard form support of Angular 2 can fit into its component-based approach to implement concise and powerful forms.
In this article, we made an effort to split form building and processing into several small components that interact together to provide the company editing feature. All this work contributes to improve code maintainability and reusability, since it’s possible to reuse most of these components into other forms of the application.
As stated form controls can also leverage observables and reactive programming to make other parts of the application react following user inputs. We will focus on the support of such approaches within Angular 2 in a following article, so stay tuned!
The source code is available in the following Github repository: https://github.com/restlet/restlet-sample-angular2-forms.