In my angular 2 app, I have an edit assignment component which loads when user clicks on edit button. It displays a form and fills all form controls with already existing values from firebase database which user can then edit. It loads all the values in the form controls when I click the edit button for the first time. Then if I go back or navigate to any other link and then click on the edit button again the form controls do not patch values from firebase database.
The images below will help you understand the problem better:
I click on the edit button first time everything works fine
Then if I go back to some other link and then return by clicking the edit button again the form does not patch form controls with values from firebase database.
I am validating my form like this
constructor(
private _routeParams: ActivatedRoute,
private _db: AngularFireDatabase,
private _fb: FormBuilder,
private _uploadService: UploadService,
private _getAsnService: GetAssignmentService,
private _editAsnSvc: EditAssignmentService,
) {
console.log("in constructor");
this.validate();
}
ngOnInit() {
console.log("in init");
this.getRouteParams();
this.assignment = this._getAsnService.getAssignment(this.asnDetailKey); // this.assignment is FirebaseObjectObservable retrieved from firebase DB
this.setInputValues();
this.getCourses();
this.validate();
}
validate() {
this.form = this._fb.group({
course: ['', Validators.compose([Validators.required])],
batch: ['', Validators.compose([Validators.required])],
subject: ['', Validators.compose([Validators.required])],
name: ['', Validators.compose([Validators.required])],
description: ['', Validators.compose([Validators.required])],
dueDate: ['', Validators.compose([Validators.required])]
});
this.today = new Date().toJSON().split('T')[0];
}
setInputValues() {
this.assignment.subscribe(asnDetails => {
console.log("setting values"); // <----- These are the values you see in console
this._assignment = asnDetails;
console.log(this._assignment); // <----- _assignment contains all values but form controls are unable to patch them
this.form.get('course').patchValue(this._assignment.course);
this.form.get('batch').patchValue(this._assignment.batch);
this.form.get('subject').patchValue(this._assignment.subject);
this.form.get('name').patchValue(this._assignment.AsnName);
this.form.get('description').patchValue(this._assignment.AsnDesc);
this.form.get('dueDate').patchValue(this._assignment.dueDate);
this.batches = this._db.list('/batches/' + this._assignment.course);
});
}
Thanks
* Edited *
I removed the setInputValues method and did the validation inside the validateForm method (i changed name from validate to validateForm) by passing asnDetails to it as told by #developer033
ngOnInit() {
this.getRouteParams();
this.assignment = this._getAsnService.getAssignment(this.asnDetailKey);
this.assignment.subscribe(asnDetails => this.validateForm(asnDetails));
this.getCourses();
}
validateForm(asnDetails) {
this.form = this._fb.group({
course: [asnDetails.course, Validators.compose([Validators.required])],
batch: [asnDetails.batch, Validators.compose([Validators.required])],
subject: [asnDetails.subject, Validators.compose([Validators.required])],
name: [asnDetails.AsnName, Validators.compose([Validators.required])],
description: [asnDetails.AsnDesc, Validators.compose([Validators.required])],
dueDate: [asnDetails.dueDate, Validators.compose([Validators.required])]
});
this.today = new Date().toJSON().split('T')[0];
this.batches = this._db.list('/batches/' + asnDetails.course);
}
I also created a initForm method and call it from constructor to initialize form.
initForm() {
this.form = this._fb.group({
course: ['', Validators.compose([Validators.required])],
batch: ['', Validators.compose([Validators.required])],
subject: ['', Validators.compose([Validators.required])],
name: ['', Validators.compose([Validators.required])],
description: ['', Validators.compose([Validators.required])],
dueDate: ['', Validators.compose([Validators.required])]
});
}
I think thats the cleaner way to validate model driven forms dynamically.
Your validate() method actually is wrong. It is initialising the form with default values and with validators which get executed automatically. The name validate() is misleading. In your case you create a new form with this._fb.group() everytime and you loose all the former values. The first time its just working because the request takes some time and in the meantime your validate() aka initializeForm() method did run. I guess in all other cases the request may is cached and so the setInputValues is called before your validate method. Just move the call to validate() before calling the asnService and you should be good. Greetings Chris
Related
I am using the ionic 4 ion-datetime picker to try to select a date in a form, though when a new date is selected, it does not update the variable.
I've tried using all of the different combinations I know, but I've included all of them here in this example.
<form [formGroup]="upsert" (ngSubmit)="submitUpsert()">
<ion-datetime
display-format="MM DD, YYYY"
picker-format="MM DD YYYY"
min="2010"
max="2030"
class="form__group__item--input has-value"
formControlName="date"
placeholder="Select Date"
[value]=date
[(ngModel)]="event.today"
(ngModelChange)="event.today"
></ion-datetime>
</form>
and in my form controller I have:
constructor(
private db: DatabaseService,
private modalController: ModalController,
private formBuilder: FormBuilder
) {
this.upsert = this.formBuilder.group({
title: ['', Validators.compose([Validators.required])],
date: ['', Validators.compose([Validators.required])],
notes: [''],
location: [''],
timezone: [''],
});
this.event.today = moment().toISOString(true);
this.event.timezone = moment().format('ZZ');
}
And eventually, I use the form submit action
submitUpsert() {
console.log("new date", this.event.today);
if(this.upsert.invalid) {
const error = {
code: "001",
message: this.upsert.status
};
this.showError(error);
} else {
this.db.upsert(this.upsert.value).then(eid => {
console.log("Success", eid);
this.hideUpsert();
}, error => {
this.showError(error);
console.error(error);
});
}
}
In the latest beta API, it does not mention to use [(ngModel)] or (ngModelChange), but it was the only way I could get a default date (of today) pre-selected.
Unfortunately, it's not updating the model nor the form object. Any thoughts?
You are using both ngModel and FormControlName. Use either one. you can update the form control value using setValue() method.
this.event.today = moment().toISOString(true);
this.upsert.get('date').setValue(this.event.today);
Try bind the model like this <ion-datetime [(ngModel)]="newsItem.validTo" name="validTo"></ion-datetime> The name attribute is required if the ion-datetime is within an ngForm.
This worked for me in Ionic 4.
Let's say I have a checkout form that contains a child address component as follows
<form [formGroup]="checkoutForm">
<input formControlName="fullName">
/** more inputs **/
<address-form></address-form>
<button type="submit">Submit</button>
</form>
At the moment I have the checkoutForm built like:
this.checkoutForm = this.formBuilder.group({
fullName: ['', Validators.required]
});
The addressForm template is like:
<form [formGroup]="addressForm">
<input formControlName="street">
<input formControlName="town"
</form>
And built like:
this.addressForm = this.formBuilder.group({
street: ['', [Validators.required]],
town: ['', Validators.required]
});
Now the issue I have is I dont know how to
1 - Validate the parent form only when the child form is valid.
2 - Validate the child form when the parent form is submitted.
The only way I could think about it is to have an #Output() addressResponse that will emit on this.addressForm.valueChanges with the validity and data. Something like:
this.addressForm.valueChanges.subscribe(data => {
let form = this.addressForm.valid ?
{ valid: true, value: data }:
{ valid: false, value: data };
this.addressResponse.emit(form);
});
And the parent form component can use this emitted data.
And also have an #Input() parentFormSubmitted that I can use to display the errors in the template of AddressForm
<input formControlName="town"
<div *ngIf="town.hasError('required') && (parentFormSubmitted || town.dirty">Town is a required field</div>
While this would work, I am not sure it is the optimal solution. I was wondering if there a more Reactive Form way of doing things. (Maybe include the AddressForm group in the definition of the CheckoutForm group?...)
You are completely correct with your comment:
I was wondering if there a more Reactive Form way of doing things. (Maybe include the AddressForm group in the definition of the CheckoutForm group?...)
You can build your form in your parent, and pass the nested group address to your child as an #Input. As objects in JS are mutable, your don't need an EventEmitter to pass any changes to your parent from child, changes will be caught "automatically" and you can therefore do all validation from the parent.
So in parent build your form like:
this.checkoutForm = this.formBuilder.group({
fullName: ['', [...]],
address: this.formBuilder.group({
street: ['', [...]],
town: ['', [...]]
})
})
then in your child tag pass the address to child as #Input:
<address-form [address]="checkoutForm.controls.address"></address-form>
In your child you mark the #Input:
#Input() address: FormGroup;
and the template in child would look like:
<div [formGroup]="address">
<input formControlName="street"><br>
<!-- Validation messages -->
<input formControlName="town">
<!-- Validation messages -->
</div>
and as mentioned, you can now handle all validation from the parent, as the parent is aware of what values the child has :)
I have this inside a component:
private formBuilder: FormBuilder
...
signupForm: FormGroup;
...
this.signupForm = this.formBuilder.group({
'name': [null, Validators.required],
'account': this.formBuilder.group({
'email': [null, [Validators.required, ...]],
'confirm_email': [null, Validators.required],
}, {validator: ValidationService.emailMatcher}),
'password': [null, [Validators.required,...]]
});
And I want to set the value for the email field. I tried this, but no luck:
this.signupForm.patchValue({'email': 'myvalue#asd.com'});
But the value is nested, so whats the sintax in this case? I also tried:
this.signupForm.patchValue({'account.email': 'myvalue#asd.com'});
Also searched here:
https://angular.io/docs/ts/latest/api/forms/index/FormGroup-class.html#!#patchValue-anchor
Thanks
Try this:
this.signupForm.patchValue({account:{email: 'myvalue#asd.com'}});
Another solution is:
(<FormGroup>this.signupForm.controls['account']).controls['email'].patchValue('myvalue#asd.com');
Sorry for bad indentation.
I have the following form in Angular created with FormBuilder:
constructor(private fb: FormBuilder) {
this.myForm = fb.group({
'name': ['', [Validators.required],
'surname': ['', [Validators.required],
'email': ['', [validateEmail]],
'address': fb.group({
'street': [''],
'housenumber': [''],
'postcode': ['']
}, { validator: fullAddressValidator })
});
}
Does exist a way to programmatically append new fields such as FormControl or new FormGroup to myForm ?
I mean if I want to add new fields on demand or on some conditions, how to add items to the same form that is created the first time in the constructor?
You can use addControl method of FormGroup class as per documentation
So you can do as below :
this.myForm.addControl('newcontrol',[]);
To add upon what #ranakrunal9 said.
If you would like to use validators with addControl do the following:
this.myForm.addControl('newControl', new FormControl('', Validators.required));
Just remember to add the following import
import {FormControl} from "#angular/forms";
Reference to addControl: https://angular.io/api/forms/FormGroup#addControl
Reference to FormControl: https://angular.io/api/forms/FormControl
In my opinion, you could just use an intermediate variable for this purpose. Take a look at the next example:
constructor(private fb: FormBuilder) {
let group = {
'name': ['', [Validators.required]],
'surname': ['', [Validators.required]],
'email': ['', [Validators.required]]
};
let middlename = true;
if(middlename) {
group['middlename'] = ['', [Validators.required]];
}
this.myForm = fb.group(group);
}
Also, it would a better idea to transfer a form initiation in ngOnInit hook, instead of component constructor.
I had the same issue, but since I already have a empty FormGroup {} inside the main FormGroup I was able to append FormControls like this:
(this.myForm.get("form_group_name") as FormGroup).addControl("item1", new FormControl("default val"));
The submit button for my formPanel works by using scope:this as it is an extended formPanela nd ext.getCmp(formid) doesn't work.
In my submit function, it successfully works using this.getForm().submit(....). However when trying to get the field values, this.getForm().getFields() doesn't work, flagging that it is not a function.
the buttons & handler function is nested within the formPanel setup.
Can anyone shed light on how to get the values in this manner?
Submit function:
{
text: 'Submit',
id: "submitBtn",
handler: this.submit,
scope: this
}
....
,submit : function(url, waitMsg) {
//this.getForm.getFields()
this.getForm().submit({
url: url
,scope: this
,waitMsg: 'Please wait'
,success: this.onSuccess
//,failure: this.onFailure
});
}
submit : function(url, waitMsg) {
//this.getForm.getFields() -- should work now
this.getForm().submit({
url: url
,scope: this
,waitMsg: 'Please wait'
,success: this.onSuccess
//,failure: this.onFailure
});
}.createDelegate(this)
I've resolved this by not using the submit action but by using a simple Ext.Ajax.request:
,submit : function() {
var data = this.getForm().getValues();
Ext.Ajax.request({
url: '...',
method: 'GET',
timeout:180000,
params: {
param1: Ext.encode(this.getForm().getValues())
}
.
})