first of all apologize for my English.
I have a problem when trying to add an array field to a formGroup.
I'm trying to add a formArray using the push method to my rate formGroup, and the error I have I think is due to the formControlName.
As I searched and read the problem it is there, but I can not solve it, can someone help me please?
I created this stackblitz so you can see the error it gives me.
https://stackblitz.com/edit/angular-mlk2mh
To work with FormArray you need to understand this:
Need form group
\/\/\/
<form [formGroup]="rates">
<input type="text" placeholder="credit_card" formControlName="credit_card" />
Need form array name
\/\/\/
<div formArrayName="servicesRates" *ngFor="let item of rates.get('servicesRates').controls; let i = index;">
<div [formGroupName]="i"> <-- this is important
<input type="text" placeholder="id" formControlName="id" />
<input type="text" placeholder="Servicio" formControlName="service" />
<input type="text" placeholder="Price" formControlName="price" />
</div>
</div>
</form>
To work above HTML your TS must be:
rates: FormGroup;
servicesRates: FormArray;
this.rates = this._formBuilder.group({
credit_card: [null, Validators.compose([Validators.required, Validators.minLength(8)])],
servicesRates: this._formBuilder.array([
this._formBuilder.group({
id: 0,
service: '',
price: 0,
})
])
});
and addNew function:
addField() {
this.servicesRates = this.rates.get('servicesRates') as FormArray;
this.servicesRates.push(this.servicesRates);
}
Related
I am trying to make a blogsite comment system. Where a blog post has a comment form field and each comment has a reply form field. But the reply form and the comment form have the same input fields so I am trying to initialize the comment form and reply form with same reactive form. Look like these:
ngOnInit(){
this.commentForm = this.formBuilder.group({
comment: ['', [Validators.required]]
});
}
My Html code looks like these:
<div *ngFor="let item of allBlogPost">
<p>item.blogPostText</p>
<div class='comment'>
<div *ngFor="let comment of allBlogPost.comments">
<p>{{comment.comment_text}}</p>
<div *ngFor="let reply of comment.replies">
<p>{{reply.reply_text}}</p>
</div>
<form [formGroup]="commentForm" (ngSubmit)="onReplySubmit()">
<input type="text" formControlName="comment"/>
<button type="submit" [disabled]="commentForm.invalid">Reply</button>
</form>
</div>
<form [formGroup]="commentForm" (ngSubmit)="onCommentSubmit()">
<input type="text" formControlName="comment"/>
<button type="submit" [disabled]="commentForm.invalid">Comment</button>
</form>
</div>
The code is working fine whenever I am posting comments and replies. But the problem is when I write comment inside comment form or reply form the submit button is activated as the form is now valid. But all the other form submit button also became activated (as my assumption it is happening for one of the form being validate) though they remain untouched. But now I don't know how to solve the problem.
I need to activate the particular submit button when a particular form is valid. How can I get the funtionality in angular way?
There's a few approaches. One way is to make a comment component for your app. This will ensure that you have more instances of formGroup
<div *ngFor="let item of allBlogPost">
<p>item.blogPostText</p>
<div class='comment'>
<app-comment *ngFor="let comment of allBlogPost.comments" [comment]="comment">
</app-comment>
</div>
Use the #Input decorator in your new Comment Component like this
comment.component.ts
#Component({
selector: 'app-comment',
templateUrl: './comment.component.html'
})
export class CommentComponent implements OnInit {
#Input() comment;
ngOnInit(){
this.commentForm = this.formBuilder.group({
comment: ['', [Validators.required]]
});
}
// Remember to move the rest of the comment logic from your bloglist...
}
comment.component.html
<p>{{comment.comment_text}}</p>
<div *ngFor="let reply of comment.replies">
<p>{{reply.reply_text}}</p>
</div>
<form [formGroup]="commentForm" (ngSubmit)="onReplySubmit()">
<input type="text" formControlName="comment"/>
<button type="submit" [disabled]="commentForm.invalid">Reply</button>
</form>
</div>
<form [formGroup]="commentForm" (ngSubmit)="onCommentSubmit()">
<input type="text" formControlName="comment"/>
<button type="submit" [disabled]="commentForm.invalid">Comment</button>
</form>
I have a component with a reactive form in it like so...
form component.html
<form [formGroup]="form" class="do-form">
<div formGroupName="dot">
<div class="do-form__container">
<div class="do-form__group">
<label for="day">Day</label>
<input id="day" type="number" placeholder="XX" class="do-form__group__control" formControlName="day" />
</div>
<div class="do-form__group">
<label for="month">Month</label>
<input id="month" type="number" placeholder="XX" class="do-form__group__control" formControlName="month" />
</div>
<div class="do-form__group">
<label for="year">Year</label>
<input id="year" type="number" placeholder="XXXX" class="do-form__group__control" formControlName="year" />
</div>
</div>
<div class="do-form__errors" *ngIf="isValid()">
<p>Please enter a valid date</p>
</div>
</div>
</form>
and in my form.component.ts
form = this.fb.group({
dot: this.fb.group({
day: ['',
[
Validators.required,
Validators.min(1),
Validators.max(31)
]],
month: ['',
[
Validators.required,
Validators.min(1),
Validators.max(12)
]],
year: ['2018',
[
Validators.required,
Validators.min(1918),
Validators.max(2018)
]]
})
});
isValid() {
return (
!this.form.valid &&
this.form.get('dot.day').touched &&
this.form.get('dot.month').touched &&
this.form.get('dot.year').touched
);
}
Now I have a separate page (app.component.html) like so
<app-form #formTool></app-form>
<button class="full-width-btn" (click)="next(); form.sendResult();">SEND</button>
app.component.ts
import { formComponent } from '../form-component/form-component.ts'
export ...
#ViewChild(formComponent) form;
Now basically I want to disable the send button until the form in the app form component is valid.
I'm not entirely sure how to do this. I thought about storing a valid event on a shared server but I'm not sure how I can store a valid event in a service. I saw that with non-reactive forms you can just have a button that uses the same ngModel but once again not sure if that would work in this case.
Any help would be appreciated!
EDIT
I have tried [disabled]="form.invalid" and [disabled]="!isValid()" but I am still able to click the button
I have also tried using [disabled]=!form.isValid() and [disabled]=!form.form.isValid()
Thanks
You are really close. Here is the only thing you need to add:
<app-form #formTool></app-form>
<button class="full-width-btn" [disabled]="!isValid()" (click)="next(); form.sendResult();">SEND</button>
In form component you could define an event #Output formValid = new EventEmitter().
Then you can listen to changes in input fields (on keypress or so) and on any change check the validity and if the form is valid, emit an event: formValid.emit().
In the app component define formIsValid = false, and on the app-form element you can listen to the formValid event:
< app-form (formValid)="formIsValid = true">
(or some function in app.component.ts instead of the inline code).
And finally on button
< button [disabled]="!formIsValid">
I'm curious if it's possible to use the myForm.controls property in an Angular Reactive form instead of defining an AbstractControl for every instance?
This method is suggested in NG-Book but it's not used with nested forms and FormGroupName.
Here is my code.
HTML:
<form [formGroup]="myForm"
(ngSubmit)="onSubmit(myForm.value)">
<div>
<input type="text"
id="nameInput"
placeholder="Product"
[formControl]="myForm.controls['name']">
<div *ngIf="myForm.controls['name'].hasError('required')">Name is
required</div>
</div>
<div formGroupName="address">
<input type="text"
id="streetInput"
placeholder="Enter Street"
[formControl]="myForm.controls['street']">
<div *ngIf="myForm.controls['street'].hasError('required')">Street is required</div>
</div>
<button type="submit" class="ui button">Submit</button>
JS:
export class App {
myForm: FormGroup;
constructor(fb: FormBuilder) {
this.myForm = fb.group({
'name': ['', Validators.required],
'address': fb.group({
'street': ['', Validators.required],
});
}
}
}
I realize I can set formControlName="street" on the input but then I think I would have to use AbstractControl to determine it's validation state. Where as in my non-nested formGroup, I can just use myForm.controls['name'].hasError('required').
I don't know, i understand your question well.
Do you want to get validation about nested from control?
Then, you can get myForm.get('address').control['street'].hasError('required') or myForm.get('address').get('street').hasError('required')
And you can change hasError('required') to errors.required.
These link is description about form validation.
https://angular.io/api/forms/FormGroup#example-4
https://angular.io/guide/form-validation#reactive-form-validation
If I misunderstand your question, comment.
Please see the following examples. I have loaded jquery and jquery-steps into the project already and it is working. However after rendering the view, changing the data in the input boxes doesn't update the values in the form group mainForm. I believe the reason is that jquery-steps dynamically removed and reconstructed the html for the form, and so the form group doesn't link to the DOMs anymore.
Is there any way to re-bind FormGroup mainForm to the DOMs after jquery-steps reconstructed the html?
I read about ComponentResolver and ViewContainerRef, is it where it should use those? Could you give me an example how to use those in this situation?
Thank you!
<pre>{{ mainForm.value | json }}</pre>
<form [formGroup]="mainForm" id="mainForm" action="#">
<h1>Account</h1>
<div>
<label for="userName">User name *</label>
<input formControlName="userName" type="text" class="required">
<label for="password">Password *</label>
<input formControlName="password" type="text" class="required">
<label for="confirm">Confirm Password *</label>
<input formControlName="confirm" type="text" class="required">
<p>(*) Mandatory</p>
</div>
<h1>Finish</h1>
< div>
<input formControlName="acceptTerms" type="checkbox" class="required">
<label for="acceptTerms">I agree with the Terms and Conditions.</label>
</div>
</form>
import {Component, AfterContentInit} from "#angular/core";
import {FormBuilder, Validators, FormGroup} from "#angular/forms";
#Component({
templateUrl: 'main-view.template.html'
})
export class MainViewComponent implements AfterContentInit {
private mainForm: FormGroup;
constructor(private formBuilder: FormBuilder) {
this.mainForm = this.formBuilder.group({
userName: ['', Validators.required],
password: ['', Validators.required],
confirm: ['', Validators.required],
acceptTerms: ['', Validators.required],
});
}
ngAfterContentInit() {
$("#mainForm").steps();
}
}
The main reason why that is not working is that jquery-steps plugin removes your html markup.
Using jquery in angular2 is bad idea but if you want to get it working i can offer you slightly modify the library
jquery.steps.js
function render(wizard, options, state) {
+ var contentWrapper = $('<{0} class=\"{1}\"></{0}>'.format(options.contentContainerTag, "content " + options.clearFixCssClass));
+ contentWrapper.append(wizard.children());
// Create a content wrapper and copy HTML from the intial wizard structure
var wrapperTemplate = "<{0} class=\"{1}\">{2}</{0}>",
orientation = getValidEnumValue(stepsOrientation, options.stepsOrientation),
verticalCssClass = (orientation === stepsOrientation.vertical) ? " vertical" : "",
- //contentWrapper = $(wrapperTemplate.format(options.contentContainerTag, "content " + options.clearFixCssClass, wizard.html())),
See also Plunker Example
I'm trying to validate that the password and ConfirmPassword fields are the same in my form, but when I add the validation provided by other posts on SO (aside from changing ControlGroup to FormGroup) I keep getting
ERROR: Cannot find control with unspecified name attribute
BUT, when I don't validate using the "matchingPassword" group below and just use the Validators.required syntax, it works just fine.
I don't know why it's throwing that error. Has anyone else worked through this? I'm currently building on Angular 4 Distro.
constructor(
private accountService: accountService,
fb: FormBuilder,
) {
this.changePWForm = fb.group({
'CurrentPassword' : [null, Validators.required],
'SecurityQuestions' : [null, Validators.required],
'SecurityQuestionAnswer' : [null, Validators.required],
'matchingPassword': fb.group({
'NewPassword' : [null, Validators.compose([Validators.pattern(this.strongPW), Validators.required])],
'ConfirmPassword' : [{disabled: true}, Validators.required],
}, {validator: this.equalPasswords})
})
}
equalPasswords(group: FormGroup){
//When I use the syntax above, it never makes it here.
var valid = false;
for (var name in group.controls) {
var val = group.controls[name].value
}
if (valid) {
return null;
}
return {
areEqual: true
};
}
Here's My HTML Template
<form [formGroup]="changePWForm" (ngSubmit)="updatePW(changePWForm.value)" *ngIf="securityQuestions">
<div class="form-group">
<label>Current Password:</label>
<input type="text" [(ngModel)]="changePWData.CurrentPassword" [formControl]="changePWForm.controls['CurrentPassword']">
</div>
<div class="form-group">
<label>New Password:</label>
<input type="text" [(ngModel)]="changePWData.NewPassword" [formControl]="changePWForm.controls['NewPassword']">
<small *ngIf="!changePWForm.controls.NewPassword.valid && !changePWForm.controls.NewPassword.pristine">You need a secure password.</small>
</div>
<div class="form-group" >
<label>Confirm New Password:</label>
<input type="text" [(ngModel)]="changePWData.ConfirmPassword" [formControl]="changePWForm.controls['ConfirmPassword']">
</div>
<div class="form-group">
<label>Security Question:</label>
<select #select type="text" [(ngModel)]="selectedSecurityQuestion" [formControl]="changePWForm.controls['SecurityQuestions']" class="select">
<option *ngFor="let question of securityQuestions" [ngValue]="question">{{question.SecurityQuestionText}}</option>
</select>
</div>
<div class="form-group">
<label>Security Question Answer: </label>
<input type="text" [(ngModel)]="securityQuestionAnswer" [formControl]="changePWForm.controls['SecurityQuestionAnswer']">
</div>
<div *ngIf="processing">
<div class="spinner">
<div class="rect1"></div>
<div class="rect2"></div>
<div class="rect3"></div>
<div class="rect4"></div>
<div class="rect5"></div>
</div>
</div>
<button *ngIf="!processing" type="submit" [disabled]="!changePWForm.valid">Change Address</button>
</form>
The problem is that you've a nested FormGroup (matchingPassword).
So, you must wrap the controls of this nested group using a <div>, for example.
Wrap your password controls (NewPassword and ConfirmPassword) in a element, like this:
<form [formGroup]="changePWForm" ...>
...
<div formGroupName="matchingPassword">
<!-- you can also use [formGroup] if you want -->
<!-- Put the content of password controls here -->
</div>
...
</form>
Besides having the problem stated by developer033, that you are missing the formGroupName:
<div formGroupName="matchingPassword">
<!-- you can also use [formGroup] if you want -->
<!-- Put the content of password controls here -->
</div>
.. I also didn't really understand the custom validator and it didn't function correctly for me. Also noticed for some reason the validator don't even fire when marking form controls like:
[formControl]="changePWForm.controls['NewPassword']"
I can't really say why, but I also prefer the more "cleaner" version:
formControlName="NewPassword"
So changing those would make the custom validator fire.
Then to the custom validator, this is the way I do it. Also notice I've changed areEqual: true to notEqual:true to better describe what is actually going on, since when we return null it means that the passwords match, and if we return something else than null, it means that we want to mark that the passwords do not match.
equalPasswords = (control: AbstractControl): {[key: string]: boolean} =>{
const newPassword = control.get('NewPassword');
const confirmPassword = control.get('ConfirmPassword');
if (!newPassword || !confirmPassword) {
return null;
}
return newPassword.value === confirmPassword.value ? null : { notEqual: true };
}
Here's a DEMO with a shortened version of your code.