How to use formControlName and deal with nested formGroup? - forms

As Angular documentation says we can use formControlName in our forms:
<h2>Hero Detail</h2>
<h3><i>FormControl in a FormGroup</i></h3>
<form [formGroup]="heroForm" novalidate>
<div class="form-group">
<label class="center-block">Name:
<input class="form-control" formControlName="name">
</label>
</div>
</form>
As they say...
Without a parent FormGroup, [formControl]="name" worked earlier because that directive can stand alone, that is, it works without being in a FormGroup. With a parent FormGroup, the name input needs the syntax formControlName=name in order to be associated with the correct FormControl in the class. This syntax tells Angular to look for the parent FormGroup, in this case heroForm, and then inside that group to look for a FormControl called name.
Anyway some months ago I asked this to figure out what is the difference between formControlName and [formControl].
Now my question is: what about use formControlName with nested FormGroups?
For example, if I have the following form structure:
this.myForm = fb.group({
'fullname': ['', Validators.required],
'gender': [],
'address': fb.group({
'street': [''],
'houseNumber': [''],
'postalCode': ['']
})
});
What is the right way to bind "street" (or "houseNumber" or "postalCode") to related HTML elements using formControlName?

you can use Form group which is basically a collection of controls ( controls mean the fields given in your html form) define in your typescript syntax and binded to your HTML elements using the formControlName directive ,for example
this.myForm = fb.group({
'fullname': ['', Validators.required],
'gender': [],
'address': fb.group({
'street': [''],
'houseNumber': [''],
'postalCode': ['']
})
});
Template:
<form [formGroup]="myForm" >
<div class="form-group">
<label for="fullname">Username</label>
<input type="text" id="username" formControlName="fullname" class="form-control">
</div>
<div class="radio" *ngFor="let gender of genders">
<label>
<input type="radio" formControlName="gender" [value]="gender">{{ gender }} </label>
</div>
<div formGroupName="address">
<div class="form-group">
<label for="street">Username</label>
<input type="text" id="username" value="street" formControlName="street" class="form-control">
</div>
<div class="form-group">
<label for="houseNumber">Username</label>
<input type="text" id="username" value="street" formControlName="houseNumber" class="form-control">
</div>
<div class="form-group">
<label for="postalCode">Username</label>
<input type="text" id="username" value="street" formControlName="postalCode" class="form-control">
</div>
</div>
</form>
A formGroup can consist of a nested formGroup and hierarchy can continue on ,but in accessing the value the its fairly simple .

It is true. Look at formGroupName
this.myForm = fb.group({
'fullname': ['', Validators.required],
'gender': [],
'address': fb.group({
'street': [''],
'houseNumber': [''],
'postalCode': ['']
})
});
<form [formGroup]="myForm" >
<div class="form-group">
<label for="fullname">Username</label>
<input type="text" id="username" formControlName="fullname" class="form-control">
</div>
<div class="radio" *ngFor="let gender of genders">
<label>
<input type="radio" formControlName="gender" [value]="gender">{{ gender }} </label>
</div>
<div formGroupName="address">
<div class="form-group">
<label for="street">Username</label>
<input type="text" id="username" value="street" formControlName="street" class="form-control">
</div>
<div class="form-group">
<label for="houseNumber">Username</label>
<input type="text" id="username" value="street" formControlName="houseNumber" class="form-control">
</div>
<div class="form-group">
<label for="postalCode">Username</label>
<input type="text" id="username" value="street" formControlName="postalCode" class="form-control">
</div>
</div>
</form>

tl;dr:
You can break your form up into components that use your nested formgroups, and formcontrolname can be used as normal.
The approach I tend to use, since usually nested Formgroups are used to designate separate parts of a form, is to break it up into components and pass those components the nested formgroup as an input parameter.
So in your case, I would have an address component that takes a formgroup as a parameter:
<app-address [formGroup]="myForm.get('address')"></app-address
And inside of that component, I would just have an #Input() formGroup that would be used in the html:
<div [formGroup]="formGroup">
....
That way you can reference the control name explicitly as you normally would, since it would be part of this formgroup.
Also, keep in mind the form is passed as reference. your changes would be accounted for in the parent component's myForm element, and if you needed access to parts of the form not in you formgroup (validation, change detection, ect ect) you could always pass down the whole form and just define the formgroup to reference the inner group explicitly:
<div [formGroup]="formGroup.get('adress')">
(assuming you pass down the entire form object that is
Happy coding!

I am struggling with a problem in Angular 10.
I have a "parent" form group "forma" who has a few dependent groups: "company", and at the same time, "company" has two
"children" with another groups, msgAccounts and socialMedia.
When I fill the form and I submit it, in the backend everything is correct, I can see how these data is stored in the db
correctly, but when I receive this json I cannot display the data
inside "company.msgAccounts" and "company.socialMedia" in the controls
(inputs). This is what I get from the server:
{
name:'',
active: '',
........
company:{
msgAccounts:{line: "#linedemo", whatsapp: "0325554244"},
socialMedia: {fb: '', tw: '', is: ''}
}
..........
}
this.forma = this.fb.group( {
siteName : [ '', [Validators.required, Validators.minLength(5)]],
siteEmail : [ '', [Validators.required, Validators.minLength(5)]],
// defaultLocation: [ '', [Validators.required, Validators.minLength(10)]],
active : [ true, [Validators.required, Validators.minLength(5)]],
mainLanguage: ['' , [Validators.required, Validators.minLength(2)]],
company: this.fb.group({
name: [''],
address: [''],
phone: [''],
msgAccounts: this.fb.group({
line: [],
whatsapp: []
}),
socialMedia: this.fb.group({
fb: [],
is: [],
tw: []
})
})
});
And the html: (Sorry about the indentation, when it was not easy using this editor, and I just pasted a part of the code in order to do it as shorter as possible).
<mat-tab-group>
<mat-tab label="settings">
<form autocomplete="off" >
<ng-template ng-template matTabContent>
<mat-tab-group [formGroup]="forma">
<mat-tab label="global">
// All this fields have are fine
</mat-tab>
<mat-tab formGroupName="company" label="company">
<div class="card">
<div class="card-header" id="headingTwo">
<h5 class="mb-0">Details</h5>
</div>
<div id="company-details">
<div class="form-group row">
<div class="col-sm-3">
<input
type="text"
class="form-control"
id="name"
name="name"
placeholder="Company name"
formControlName=name>
</div>
</div>
<div class="form-group row" formGroupName="msgAccounts">
<div class="col-sm-6">
<input
type="text"
class="form-control"
id="line"
name="line"
placeholder="line"
formControlName=line>
</div>
<div class="col-sm-6">
<input
type="text"
class="form-control"
id="whatsapp"
name="whatsapp"
placeholder="whatsapp"
formControlName=whatsapp>
</div>
</div>
<div class="form-group row" formGroupName="socialMedia" >
<div class="col-sm-6">
<input
type="text"
class="form-control"
id="is"
name="is"
placeholder="Is"
formControlName=is>
</div>
</div>
</div>
</div>
</mat-tab>
</mat-tab-group>
<div class="form-group row">
<div class="col-sm-10">
<button type="submit"
(click)="saveConfig()">Save</button>
</div>
</div>
</ng-template>
</form>
</mat-tab>
</mat-tab-group>

Related

Angular 5 ngForm prefilled with value="{{variable}}" is undefined on submit

I use the same form to create and update my data. When i select a record to update, it fills the form correctly but if I submit, the values are undefined
<form (ngSubmit)="create(addMountForm)" #addMountForm="ngForm" ngNativeValidate>
<div class="form-row" style="margin: 0 auto;">
<div class="form-group col-md-2">
<label for="name">Name(*)</label>
<input type="text" class="form-control" [(ngModel)]="name" name="name" id="name" placeholder="Countdown-Name" required value="{{current_name}}">
</div>
<div class="form-group col-md-2">
<label for="datepicker">Datum(*)</label>
<input type="datetime-local" class="form-control" [(ngModel)]="datepicker" name="datepicker" id="datepicker" placeholder="Datum" required value="{{current_timestamp * 1000 | date: 'yyyy-MM-ddThh:mm'}}">
</div>
</div>
The variables are defined like this:
current_name:string;
current_timestamp:number;
The submitted form contains the values like this:
value:
name: undefiend
datepicker: undefiend

Angular2 disable submit button if form is invalid

How can I make submit button disabled, when my form is invalid in Angular2?
Now I use code:
<input type="submit" [disabled]="!registrationForm.valid" value="Zarejestruj">
and this is not working and make form always disabled, also when this is valid.
Any suggestion?
UPDATE:
I found error, in form I have file input and this causes form button failed. Here is my whole code (without validation tips)
<div class="form">
<form [formGroup]="registrationForm" (ngSubmit)="RegisterUser(registrationForm.value)" novalidate>
<div class="form-row">
<input type="text" name="login" placeholder="Nazwa użytkownika" [formControl]="registrationForm.controls['login']">
</div>
<div class="form-row">
<input type="password" name="password" placeholder="Hasło" [formControl]="registrationForm.controls['password']" validateEqual="repeatPassword" reverse="true">
</div>
<div class="form-row">
<input type="password" name="repeatPassword" placeholder="Powtórz hasło" [formControl]="registrationForm.controls['repeatPassword']" validateEqual="password">
</div>
<div class="form-row">
<input type="file" name="avatar" file-model="avatar" [formControl]="registrationForm.controls['avatar']" >
</div>
<div class="form-row submit">
<input type="submit" [disabled]="!registrationForm.valid" value="Zarejestruj">
</div>
</form>
</div>
And registration.component.ts:
/ Imports
import { Component, OnInit } from '#angular/core';
import { FormGroup, FormBuilder, Validators } from '#angular/forms';
#Component({
templateUrl: './app/registration/registration.component.html'
})
// Component class implementing OnInit
export class RegistrationComponent {
registrationForm : FormGroup;
constructor(fb: FormBuilder){
this.registrationForm = fb.group({
'login' : [null, Validators.compose([Validators.required, Validators.minLength(4), Validators.maxLength(12)])],
'password' : [null, Validators.compose([Validators.required, Validators.minLength(6), Validators.maxLength(20)])],
'repeatPassword': [null, Validators.required],
'avatar': [null, Validators.required],
})
}
RegisterUser(value: any){
console.log(value);
}
}
Now the question is: why this does not working?

Angular Form Validation error : form is undefined

I am trying to validate my data for a student object with Angular Form Validation, but when I want to use or print for example, the serialNumber of a student it gives me this error: Cannot read property 'serialNumber' of undefined.
Here is the code:
<div *ngIf="student">
<div class=container>
<h2>Student {{student.name}} details</h2>
<form name="studentForm" (ngSubmit)="save()">
<!--<div ng-class="{ 'has-error' : studentForm.serialNumber.$invalid && !studentForm.serialNumber.t">-->
<label>Serial number: {{student.serialNumber}} </label>
<input type="text" name="serialNumber" class="form-control" ng-model="student.serialNumber" required>
<div ng-messages="studentForm.serialNumber.$error">
{{studentForm.serialNumber}}
<p ng-message="required">Your name is required!</p>
</div>
<!--</div>-->
<div>
<label>Name: {{student.name}}</label>
<input ng-model="student.name" placeholder="name">
</div>
<div>
<label>Group number: {{student.groupNumber}}</label>
<input ng-model="student.groupNumber" placeholder="groupNumber">
</div>
<button (click)="goBack()">Back</button>
<button (click)="save()">Save</button>
</form>
</div>
</div>
The ng-messages and ng-model attribute directives do not exist in Angular 2+. I would recommend reading into Angular Forms, ReactiveForms, and Template Syntax.
If you would like to dual data-bind the input values, you can do so with the following syntax: [(ngModel)]="student.serialNumber". However, in Angular 2+, there are usually better ways of getting values other than explicitly data-binding.
Angular Form Validation template Driven Model
onSubmit(form : NgForm) {
console.log(form);
}
<form #form="ngForm" (ngSubmit)="onSubmit(form)"
[ngClass]="{'was-validated': form.invalid && (form.dirty || form.touched)}">
<div class="" ngModelGroup="User">
<h2 class="text-center">Registration page</h2>
<br />
<div class="form-group">
<input type="text" class="form-control" placeholder="First Name" name="firstname" required
ngModel #firstname="ngModel">
<span class="help-bpx" *ngIf="firstname.touched && !firstname.valid ">Please enter the
firstname</span>
</div>
<div class="form-group">
<input type="text" class="form-control" placeholder="Last Name" name="lastname" required ngModel
#lastname="ngModel">
<span class="help-bpx" *ngIf="lastname.touched && !lastname.valid ">Please enter the
lastname</span>
</div>
<div class="form-group">
<input type="email" class="form-control" id="email" placeholder="Email" name="email" email
required ngModel #email="ngModel">
<span class="help-bpx" *ngIf="email.touched && !email.valid ">Please enter the Email
Value</span>
</div>
<div class="form-group">
<div class="custom-file">
<input type="file" class="custom-file-input" id="customFile" required ngModel name="file" #file="ngModel">
<label class="custom-file-label" for="customFile">Choose file</label>
</div>
</div>
<br />
<div class="align-center">
<button type="submit" class="btn btn-primary" [disabled]="!form.valid">Register</button>
</div>
</div>
</form>

Cannot read property of undefined in record edit form in angular 2

I have a problem in my edit form with undefined properties. I have an API that allows me to create and update customers. The problem is that some of the nested attributes are optional, this is fine when creating or editing fields that already exist but i can't figure out what to do when editing one of the nested fields on a customer that wasn't created with those fields and keep getting Cannot read property undefined errors.
This is my customer object:
export class Customer {
CompanyName: string;
DisplayName: string;
FamilyName: string;
GivenName: string;
Id: number;
Title: string;
BillAddr: BillAddress;
ShipAddr: ShipAddress;
PrimaryPhone: PrimaryPhoneNumber;
Mobile: MobileNumber;
PrimaryEmailAddr: PrimaryEmailAddress;
}
export class ShipAddress {
City: string;
Country: string;
CountrySubDivisionCode: string;
Line1: string;
Line2: string;
Line3: string;
Line4: string;
Line5: string;
PostalCode:string;
}
export class BillAddress {
City: string;
Country: string;
CountrySubDivisionCode: string;
Line1: string;
Line2: string;
Line3: string;
Line4: string;
Line5: string;
PostalCode:string;
}
export class PrimaryPhoneNumber {
FreeFormNumber: number;
}
export class MobileNumber {
FreeFormNumber: number;
}
export class PrimaryEmailAddress {
Address: string;
}
This is the html from the edit component:
<h1 class="page-header">Edit Customer</h1>
<div [ngBusy]="busy"></div>
<form (ngSubmit)="onSubmit()">
<div class="row">
<div class="col-md-6">
<h3>Customer information</h3>
<div class="form-group">
<label for="customertitle">Title</label>
<input type="text" class="form-control" id="cutomertitle" placeholder="Title" name="title" [(ngModel)]="customer && customer.Title" >
</div>
<div class="form-group">
<label for="customerGivenName">First Name</label>
<input type="text" class="form-control" id="customerGivenName" placeholder="First Name" name="givenname" [(ngModel)]="customer && customer.GivenName" >
</div>
<div class="form-group">
<label for="customerFamilyName">Last Name</label>
<input type="text" class="form-control" id="customerFamilyName" placeholder="Surname" name="familyname" [(ngModel)]="customer && customer.FamilyName" >
</div>
<div class="form-group">
<label for="customerEmailAddress">Email Address</label>
<input type="text" class="form-control" id="customerEmailAddress" placeholder="Email" name="email" [(ngModel)]="customer && customer.PrimaryEmailAddr.Address" >
</div>
<div class="form-group">
<label for="customerPhone">Phone</label>
<input type="text" class="form-control" id="customerPhone" placeholder="Phone Number" name="primaryphone" [(ngModel)]="customer && customer.PrimaryPhone.FreeFormNumber" >
</div>
<div class="form-group">
<label for="customerMobile">Mobile</label>
<input type="text" class="form-control" id="customerMobile" placeholder="Mobile Number" name="mobile" [(ngModel)]="customer && customer.Mobile.FreeFormNumber" >
</div>
</div>
<div class="col-md-6">
<h3>Address:</h3>
<div class="form-group">
<label for="customerLine1">Line 1</label>
<input type="text" class="form-control" id="cutomerLine1" placeholder="Line 1" name="line1" [(ngModel)]="customer && customer.BillAddr.Line1" >
</div>
<div class="form-group">
<label for="customerLine1">Line 2</label>
<input type="text" class="form-control" id="cutomerLine2" placeholder="Password" name="line2" [(ngModel)]="customer && customer.BillAddr.Line2" >
</div>
<div class="form-group">
<label for="customerLine1">Line 3</label>
<input type="text" class="form-control" id="cutomerLine3" placeholder="Password" name="line3" [(ngModel)]="customer && customer.BillAddr.Line3" >
</div>
<div class="form-group">
<label for="customerCity">City</label>
<input type="text" class="form-control" id="customerCity" placeholder="Password" name="city" [(ngModel)]="customer && customer.BillAddr.City" >
</div><div class="form-group">
<label for="customerLine1">State/Province</label>
<input type="text" class="form-control" id="cutomerLine1" placeholder="Password" name="Province" [(ngModel)]="customer && customer.BillAddr.CountrySubDivisionCode" >
</div>
<div class="form-group">
<label for="customerLine1">Postal Code</label>
<input type="text" class="form-control" id="cutomerLine1" placeholder="Password" name="postcode" [(ngModel)]="customer && customer.BillAddr.PostalCode" >
</div>
</div>
</div>
<button type="submit" class="btn btn-default">Save Changes</button>
</form>
The current details are fetched oninit with this function:
getCustomer(id): void {
this.busy = this.customersService.getCustomer(id)
.then(customer => this.customer = customer);
}
First problem is that data is coming async, so customer will be undefined when the view is rendered on all fields. Second problem is indeed that, since some fields do not have a value, will also throw an error.
Usually this could be solved by just initializing the customer:
customer = {};
But here again the problem is, that you have some deeper property paths that will though throw error despite this, like: customer.BillAddr.Line3. Of course you could initialize aaaall properties you have in your object, but that seems just ugly to me.
All this can be solved with the safe navigation operator. Unfortunately two-way-binding, i.e [(ngModel)] does not allow the safe navigation operator.
But!
[(ngModel)]="something" equals the following:
[ngModel]="something" (ngModelChange)="changeHappened($event)"
and one-way-binding does support the safe navigation operator. So what you could then do, is to use the ternary operator, that catches the input user makes and assigns it to your variable (in this example) with customer.Title = $event. And by using the ternary operator you get rid of the error undefined if there is no value initially with applying an empty string to the field:
[ngModel]="customer?.Title"
(ngModelChange)="customer?.Title ? customer.Title = $event : ''"
(All credit goes to this answer: https://stackoverflow.com/a/36016472/6294072)
Also I could suggest to use a model-driven form here where you could set all fields as empty initially and then just set values to the fields that have value and get rid of the ngModel. Just to throw in a second option.
But all in all, the above solution should get rid of your errors! :)

How to bind form input to object?

I have the following form:
<form role="form" [ngFormModel]="userEditForm" (ngSubmit)="editUser(user)">
<div>
<label for="username">Username</label>
<input type="text" #username id="username" placeholder="Username" [ngFormControl]="userEditForm.controls['username']">
</div>
<div>
<label for="firstName">First Name</label>
<input type="text" #firstName id="firstName" placeholder="First Name" [ngFormControl]="userEditForm.controls['firstName']">
</div>
<div>
<label for="lastName">Last Name</label>
<input type="text" #lastName id="lastName" placeholder="Last Name" [ngFormControl]="userEditForm.controls['lastName']">
</div>
<div>
<label for="email">Email</label>
<input type="text" #email id="email" placeholder="Email" [ngFormControl]="userEditForm.controls['email']">
</div>
<button type="submit">Submit</button>
And in my component I have defined:
constructor(private _routeParams:RouteParams, private _formBuilder:FormBuilder, private _UserInject:UserInject){
this.userEditForm = _formBuilder.group({
'firstName': [this.user.firstName],
'lastName': [this.user.lastName],
'username': [this.user.username],
'email': [this.user.email],
})
}
ngOnInit(){
let id = this._routeParams.get('userId');
this.user = this._UserInject.getUser(id);
}
}
However, this creates an error, because this.user is not yet defined in the constructor. Any ideas?
UPDATE
It works when I use formBuilder in the ngOnInit - however, I am not sure if thats a proper way to do it.
Just move
this.userEditForm = _formBuilder.group({
'firstName': [this.user.firstName],
'lastName': [this.user.lastName],
'username': [this.user.username],
'email': [this.user.email],
})
after the code where you assign this.user (into ngOnInit())