I have an Angular form with textarea. I am using a string array for the formControl. I am checking for a number of the email addresses entered upon ngModelChange and assigning it to the array. Somehow When I submit the form the array is converted to a string value. Am I doing it the right way?
public emailList: string[] = [];
public form: FormGroup = new FormGroup({});
public ngOnInit(): void {
this.form = new FormGroup({
sendMeACopy: new FormControl(false, []),
emailAddressesGroup: new FormControl(this.emailList, [
Validators.pattern(/^([\w -.%] @[\w-.] \.[A-Za-z]{2,4},?\s?) $/),
]),
});
}
public extractEmailList(e: any): void {
this.emailList = [];
if (this.generateMonthlyProductionReportForm.valid) {
let emails = e.replace(/\s/g, "").split(",");
emails.forEach((email: any) => {
if (email && email.length > 0) {
this.emailList.push(email);
}
});
}
}
public report() {
if (this.form.invalid) {
for (const control of Object.keys(
this.form.controls
)) {
this.form.controls[
control
].markAsTouched();
}
return;
}
console.log(this.form.value);
}
<form [formGroup]="form">
<div >
<div>
<div>Recipient</div>
<div>
<ejs-checkbox
formControlName="sendMeACopy"
name="send me a copy"
label="Send me a copy"
>
</ejs-checkbox>
</div>
<div>
<textarea
(ngModelChange)="extractEmailList($event)"
formControlName="emailAddressesGroup"
id="emailAddresses"
name="Email Address Input"
placeholder="Use a comma to separate email addresses."
></textarea>
<div
*ngIf="
form.controls[
'emailAddressesGroup'
].hasError('pattern') &&
form.controls[
'emailAddressesGroup'
].dirty
"
>
Please enter comma separated list of valid email addresses.
</div>
</div>
</div>
</div>
<button
(click)="report()"
[disabled]="!form.valid"
type="button"
>
Submit
</button>
</form>
Initially upon form submit I get the output as follows:
emailAddressesGroup: [],
sendMeACopy: false
Upon entering the values, I get this:
emailAddressesGroup: "[email protected], [email protected]",
sendMeACopy: false
CodePudding user response:
One thing to keep in mind is that a <textarea>
is just a much more convenient way of inputting large amounts of text in an expandable input. That said, the input is still just a string.
Instead of trying to have to parse out a bunch of emails, why not just allow the user to add as many email inputs as they need.
This solution leverages the ReactiveFormsModule
instead of the traditional FormsModule
.
ex:
working stackblitz: https://stackblitz.com/edit/angular-hhitaa?file=src/app/app.component.ts
In a nutshell, this approach allows us to be more dynamic and explicit in how we create additional fields for our form. Each input has its own validator, so this way you don't have to try and make your own validators and parse out the email list. Another good thing is that you can also more easily prevent a user from say adding more than X emails by just seeing how many controls are defined in the emails FormArray
.
your form would look like so:
constructor(private fb : FormBuilder) {}
ngOnInit() {
this.form = this.fb.group({
//First field is the default value, second field is the Validators
sendMeACopy: [false, []],
emails: this.fb.array(
[
this.fb.group({
'email' : ['[email protected]', [Validators.pattern(/^([\w -.%] @[\w-.] \.[A-Za-z]{2,4},?\s?) $/)]]
})
]
)
})
}
In the same TS file create a method to add the field dynamically as so:
addInput() {
const emailsControl = <FormArray>this.form.controls['emails'];
let newEmailGroup = this.fb.group({
'email' : ['', [Validators.email, Validators.required]]
});
emailsControl.push(newEmailGroup);
this.form.updateValueAndValidity();
}
So, with the above we have successfully pushed our new control. Now we need to modify the HTML such that it updates itself accordingly.
Your final HTML will look like so. A note about the index in the for loop. Since we are iterating over individual groups the index allows us to make sure that whatever changes happen to a particular element in that formgroup it is tied to that specific group only. Furthermore, if we ever wanted to delete that form group, we can do so easily by just leveraging the index for that specific group as well.
<div *ngIf="form" [formGroup]="form">
<div >
<div>
<pre>{{ form.value | json }}</pre>
</div>
<div>Recipient</div>
<div>
<input
type="checkbox"
formControlName="sendMeACopy"
name="Send Me A Copy"
label="Send Me A Copy"
/>
<label>Send Me A Copy</label>
</div>
<div>Emails</div>
<div formArrayName="emails">
<div *ngFor="let e of emailsArray.controls; let i = index" [formGroupName]="i">
<input type="email" formControlName='email'>
</div>
</div>
<button type='button' (click)="addInput()">Add New Email</button>
{{form.valid}}
<button type="submit" [disabled]="!form.valid">Save</button>
</div>
</div>
CodePudding user response:
if you use or the value becomes always an string. You can use [ngModel] and (ngModelChange) to mannage a FormControl or to mannage a FormArray. Imagine you has some like
this.form = new FormGroup({
sendMeACopy: new FormControl(false, []),
emailAddressesGroup: new FormArray(
this.emailList.map(x=>new FormControl(x,Validators.email))
)
});
You can use
<texarea (blur)="form.get('emailAddressesGroup').markAsTouched()"
[ngModel]="(form.get('emailAddressesGroup').value || []).join(,)"
(ngModelChange)="setEmailAddressesGroup($event)"
[ngModelOptions]="{standalone:true}">
</textarea>
setEmailAddressesGroup(emailList:string)
{
const control=this.form.get('emailAddressesGroup');
if (!emailList)
control.setValue(null)
else
control.setValue(
emailList.split(',').map(x=>new FormControl(x,Validators.email))
)
}
See that in the textarea you always are working with string ans how you use setValue to give value to the formArray and how you need "markAsTouched" manually (in blur of textArea)
NOTE: The use of a FormArray of FormGroup is because I want to mannage the errors using Validators.email. If you use a custom FormControl or another way, you can simply use control.setValue(emailList.split(','))