> Click here to view Reactive Approach
> Angular 6 Samples
Environment: MS VS Code, bootstrap 3 and Angular 6
Download Project (zip): SampleFormTemplateDriven
We have a very simple form with a
<form>
<label for="username">Username</label>
<input type="text" id="username" class="form-control">
<button class="btn btn-default" type="button">Suggestion</button>
<label for="email">Mail</label>
<input type="email" id="email" class="form-control">
<label for="secret">Secret Questions</label>
<select id="secret" class="form-control">
<option value="pet">Your first Pet?</option>
<option value="teacher">Your first teacher?</option>
</select>
<button class="btn btn-primary" type="submit">Submit</button>
</form>
With the above in, angular will create a javascript representation of the
element. Angular will not detect your inputs in this form. We need to register controls manually like input etc.
ngModel on Form Controls and name attribute
Add ngModel without [()] (two way data binding, for now), this will tell angular that this is control of my form. Also we need to give name to the controls. Without these two pieces angular will not detect it as a form control.
For putting in default values, use it as [ngModel]="SomeProperty" without () around ngModel. We are not doing two way databinding at this time.
[ngModel] is one way data binding
[(ngModel)] is two way data binding
Also in the component change the method to onSubmit(form: NgForm).
onSubmit(form: NgForm){
console.log(form);
}
After submit we can then take a look at the console to see form and control properties.
We can also use @ViewChild to access the local reference. For this change the form element to
and then create parameter and change the onSubmit method to
@ViewChild('f') signUpForm: NgForm;
onSubmit(){
console.log(this.signUpForm);
}
Best practice is to use first approach and not the second one.
Add required attribute to user name.
Add required and email attributes to the email field.
Submit the form and take a look at the console. Valid should be true if proper values have been provided for these controls.
We can disable the submit button when the form is not valid by adding [disabled]="!f.valid" to submit button
<button class="btn btn-primary" type="submit" [disabled]="!f.valid">Submit</button>
For the template-driven approach, we need the directives for validation. We can find out their names, by searching for "validator" in the official docs: https://angular.io/api?type=directive - everything marked with "D" is a directive and can be added to our template.
Additionally, we might also want to enable HTML5 validation (by default, Angular disables it). You can do so by adding the ngNativeValidate to a control in your template.
input.ng-invalid.ng-touched, select.ng-invalid.ng-touched {
border: 1px solid red;
}
To display error messages, first put a reference on the email element as #email="ngModel" and then provide an error message in a span.
<input type="email" id="email" name="email" class="form-control" ngModel required email #email="ngModel">
<span class="help-block" *ngIf="!email.valid && email.touched">Please enter a valid email!</span>
<div class="form-group" ngModelGroup="userData" #userData="ngModelGroup">
If you take a look at the console and then value or controls, the controls placed inside the above userData will be group together.
genders = ['male', 'female'];
and then in the template add
<div class="radio" *ngFor="let g of genders">
<label>
<input type="radio" name="gender" ngModel [value]="g" required>
{{ g }}
</label>
</div>
We can do two databind and input the suggestion but here we'll use alternate way to do it.
One of the way is by using the local reference used on the form which we pick in the component via @ViewChild
@ViewChild('f') signUpForm: NgForm;
and the writing the event code where we will take advantage of setValue method of the local reference and pass to it a javascript code. Make sure that the javascript code is full representation of the html.
//first approach using the reference
onSuggestion(){
const suggestedUserName = "Superuser";
this.signUpForm.setValue({
userData: {
username: suggestedUserName
},
email: '',
secret: 'teacher',
gender: 'female'
});
}
However, above is not a better approach. The better approach would be to use the "form" object on signUpForm local reference and then use it patchValue method and pass the javascript as before to it. We also have a setValue method available. Use patchValue when setting some fields otherwise use setValue.
onSuggestion(){
const suggestedUserName = "Superuser";
this.signUpForm.form.patchValue({
userData: {
username: suggestedUserName
},
email: '',
secret: 'teacher',
gender: 'female'
});
}
Lets say we want to display the entered data after submit towards the bottom of the template. For this add following html
<div *ngIf="isFormSubmitted">
<hr>
<div class="row">
<div class="col-xs-12">
<h3>Form Data</h3>
<p>Username: {{ user.username }}</p>
<p>Email: {{ user.email }}</p>
<p>Secret question: {{ user.secret }}</p>
<p>Gender: {{ user. secretQuestion}}</p>
</div>
</div>
</div>
and then on the component side add
isFormSubmitted = false;
//javascript object not related to the form
user: {
username: '',
email: '',
secretQuestion: '',
gender: ''
}
and then update the onSubmit event
onSubmit(form: NgForm){
//console.log(form);
this.isFormSubmitted = true;
this.user.secretQuestion = form.value.secret;
this.user.gender = form.value.gender;
this.user.email = form.value.email;
this.user.username = form.value.userData.username;
form.reset();
}
or via local reference as
onSubmit(){
//console.log(this.signUpForm);
this.isFormSubmitted = true;
this.user.secretQuestion = this.signUpForm.value.secret;
this.user.gender = this.signUpForm.value.gender;
this.user.email = this.signUpForm.value.email;
this.user.username = this.signUpForm.value.userData.username
this.signUpForm.reset();
}
<button class="btn btn-primary" type="submit" [disabled]="!f.valid">Submit</button>
When the form is invalid, the message will display above the submit button.
> Angular 6 Samples
Environment: MS VS Code, bootstrap 3 and Angular 6
Download Project (zip): SampleFormTemplateDriven
Topics Covered
- Sample Form
- FormsModule
- ngSubmit on Form
- Access to Form
- Adding Validation
- Error Message and Applying Different Color
- Grouping the controls
- Working with radio buttons
- Handling the Suggestions Button -- Using setValue and patchValue
- Using Form Data and re-setting the form after displaying data
We have a very simple form with a
- A user name
- A button for Suggestion
- A email
- A secret question
- A submit button
Sample Form
On the form element we don't have a method and action attributes. This form will not get posted to the server, so no http post. Check app.component.html for more details<form>
<label for="username">Username</label>
<input type="text" id="username" class="form-control">
<button class="btn btn-default" type="button">Suggestion</button>
<label for="email">Mail</label>
<input type="email" id="email" class="form-control">
<label for="secret">Secret Questions</label>
<select id="secret" class="form-control">
<option value="pet">Your first Pet?</option>
<option value="teacher">Your first teacher?</option>
</select>
<button class="btn btn-primary" type="submit">Submit</button>
</form>
FormsModule
In app modules import import { FormsModule } from '@angular/forms'; and then add FormsModule to imports array.With the above in, angular will create a javascript representation of the
element. Angular will not detect your inputs in this form. We need to register controls manually like input etc.
ngModel on Form Controls and name attribute
Add ngModel without [()] (two way data binding, for now), this will tell angular that this is control of my form. Also we need to give name to the controls. Without these two pieces angular will not detect it as a form control.
For putting in default values, use it as [ngModel]="SomeProperty" without () around ngModel. We are not doing two way databinding at this time.
[ngModel] is one way data binding
[(ngModel)] is two way data binding
ngSubmit on Form
To make the form submitable, go to app.component.ts file and add onSubmit() method. To call this method, add directive (ngSubmit)="onSubmit()" on form elementAccess to Form
To get access to the form created by angular in javascript, put a local reference like #f and pass an f as an argument to onSubmit likeAlso in the component change the method to onSubmit(form: NgForm).
onSubmit(form: NgForm){
console.log(form);
}
After submit we can then take a look at the console to see form and control properties.
We can also use @ViewChild to access the local reference. For this change the form element to
and then create parameter and change the onSubmit method to
@ViewChild('f') signUpForm: NgForm;
onSubmit(){
console.log(this.signUpForm);
}
Best practice is to use first approach and not the second one.
Adding Validation
For validation, since we are using here the template driven approach, we only add the validators to the template.Add required attribute to user name.
Add required and email attributes to the email field.
Submit the form and take a look at the console. Valid should be true if proper values have been provided for these controls.
We can disable the submit button when the form is not valid by adding [disabled]="!f.valid" to submit button
<button class="btn btn-primary" type="submit" [disabled]="!f.valid">Submit</button>
For the template-driven approach, we need the directives for validation. We can find out their names, by searching for "validator" in the official docs: https://angular.io/api?type=directive - everything marked with "D" is a directive and can be added to our template.
Additionally, we might also want to enable HTML5 validation (by default, Angular disables it). You can do so by adding the ngNativeValidate to a control in your template.
Error Message and Applying Different Color
We can add a css class to put border around invalid controls. Now this will show border around form loads as well. We only want to show it when submit is clicked so use ng-touched as well.input.ng-invalid.ng-touched, select.ng-invalid.ng-touched {
border: 1px solid red;
}
To display error messages, first put a reference on the email element as #email="ngModel" and then provide an error message in a span.
<input type="email" id="email" name="email" class="form-control" ngModel required email #email="ngModel">
<span class="help-block" *ngIf="!email.valid && email.touched">Please enter a valid email!</span>
Grouping the controls
Angular provided a very straight way of providing grouping for form controls. Just put the controls inside a div tag and apply ngModelGroup="userData" to it. We can even put a local reference on it to get access to the database object.<div class="form-group" ngModelGroup="userData" #userData="ngModelGroup">
If you take a look at the console and then value or controls, the controls placed inside the above userData will be group together.
Working with radio buttons
Create a genders array in the componentgenders = ['male', 'female'];
and then in the template add
<div class="radio" *ngFor="let g of genders">
<label>
<input type="radio" name="gender" ngModel [value]="g" required>
{{ g }}
</label>
</div>
Handling the Suggestions Button -- Using setValue and patchValue
We have a suggestion button. Clicking this will give us some suggestion and will put it in the form control. First attach an event to it as (click)="onSuggestion()"We can do two databind and input the suggestion but here we'll use alternate way to do it.
One of the way is by using the local reference used on the form which we pick in the component via @ViewChild
@ViewChild('f') signUpForm: NgForm;
and the writing the event code where we will take advantage of setValue method of the local reference and pass to it a javascript code. Make sure that the javascript code is full representation of the html.
//first approach using the reference
onSuggestion(){
const suggestedUserName = "Superuser";
this.signUpForm.setValue({
userData: {
username: suggestedUserName
},
email: '',
secret: 'teacher',
gender: 'female'
});
}
However, above is not a better approach. The better approach would be to use the "form" object on signUpForm local reference and then use it patchValue method and pass the javascript as before to it. We also have a setValue method available. Use patchValue when setting some fields otherwise use setValue.
onSuggestion(){
const suggestedUserName = "Superuser";
this.signUpForm.form.patchValue({
userData: {
username: suggestedUserName
},
email: '',
secret: 'teacher',
gender: 'female'
});
}
Using Form Data and re-setting the form after displaying data
Lets say we want to display the entered data after submit towards the bottom of the template. For this add following html
<div *ngIf="isFormSubmitted">
<hr>
<div class="row">
<div class="col-xs-12">
<h3>Form Data</h3>
<p>Username: {{ user.username }}</p>
<p>Email: {{ user.email }}</p>
<p>Secret question: {{ user.secret }}</p>
<p>Gender: {{ user. secretQuestion}}</p>
</div>
</div>
</div>
and then on the component side add
isFormSubmitted = false;
//javascript object not related to the form
user: {
username: '',
email: '',
secretQuestion: '',
gender: ''
}
and then update the onSubmit event
onSubmit(form: NgForm){
//console.log(form);
this.isFormSubmitted = true;
this.user.secretQuestion = form.value.secret;
this.user.gender = form.value.gender;
this.user.email = form.value.email;
this.user.username = form.value.userData.username;
form.reset();
}
or via local reference as
onSubmit(){
//console.log(this.signUpForm);
this.isFormSubmitted = true;
this.user.secretQuestion = this.signUpForm.value.secret;
this.user.gender = this.signUpForm.value.gender;
this.user.email = this.signUpForm.value.email;
this.user.username = this.signUpForm.value.userData.username
this.signUpForm.reset();
}
Validation Check on Form
<p *ngIf="!f.valid && f.touched">The form is invalid</p><button class="btn btn-primary" type="submit" [disabled]="!f.valid">Submit</button>
When the form is invalid, the message will display above the submit button.