Reputation: 8691
I am developing the toggling radio button functionality in angular 7 using reactive forms. At the moment radio button is rendering for every form array but not toggling across the arrays. The user needs an ability to set one of the addresses as primary address. I have tried to assign new ids and value for the radio button but that has not made any difference
UI
[![enter image description here][1]][1]
Code - Update the code based on José Gazzano comments
<style>
.desc-header {
background-color: #FAE7D6;
color: black
}
.header {
width: 8%;
}
.panel-heading {
color: white;
background-color: #F59850;
border-color: #ddd;
}
.col-form-label {
padding: 0%;
}
.scrollClass {
height: 500px;
overflow-y: scroll;
overflow-x: hidden;
}
</style>
<div class="card">
<div class="card-header panel-heading">
<span style="font-size: 18px; font-weight: bold; ">Firm Details</span>
<div class="pull-right" style="padding-right:10px;">
<label class="btn btn-primary" [ngClass]="{'btn-primary': EditMode, 'btn-default': !EditMode }"><input
type="checkbox" [(ngModel)]="EditMode" class="hidden">Edit Mode</label>
</div>
</div>
<div class="card-body">
<form [formGroup]="frmFirm" (ngSubmit)="saveManager()">
<div *ngIf="FirmDetails && FirmDetails.Firm" class="card-body scrollClass">
<div class="form-group row">
<label for="inputName" class="col-sm-2 col-form-label modal-label header">Name</label>
<div class="col-md-9">
<div *ngIf="!EditMode">{{FirmDetails.Firm.NAME}}</div>
<input *ngIf="EditMode" kendoTextBox [readonly]="false" class="form-control"
formControlName="NAME" />
</div>
</div>
<!--
<div class="form-group row">
<label for="inputTitle" class="col-md-1 col-form-label header">Short Name</label>
<div class="col-md-3">
<div *ngIf="!EditMode">{{FirmDetails.Firm.SHORT_NAME}}</div>
<input *ngIf="EditMode" kendoTextBox [readonly]="false" class="form-control"
formControlName="SHORT_NAME" />
</div>
</div>
<div class="form-group row">
<label for="inputEmail" class="col-md-1 col-form-label header">Alternate Name</label>
<div class="col-md-3">
<div *ngIf="!EditMode">{{FirmDetails.Firm.ALTERNATE_NAME}}</div>
<input *ngIf="EditMode" kendoTextBox [readonly]="false" class="form-control"
formControlName="ALTERNATE_NAME" />
</div>
</div> -->
<div class="form-group row">
<label for="inputEmail" class="col-sm-2 col-form-label modal-label header">Date Founded</label>
<div class="col-md-4">
<div *ngIf="!EditMode">{{dateFoundedDate}}</div>
<kendo-datepicker *ngIf="EditMode" [format]="'MMMM yyyy'"
(valueChange)="dateFoundedChanged($event)" formControlName="DATE_FOUNDED"
class="form-control">
</kendo-datepicker>
</div>
</div>
<div class="form-group row">
<label for="inputEmail" class="col-md-2 col-form-label modal-label header">Websites</label>
<div class="col-md-9">
<div class="form-group row">
<div class="col-md-4">
<label for="inputEmail">Website URL</label>
</div>
<div class="col-md-4">
<label for="inputEmail">User Name</label>
</div>
<div class="col-md-4">
<label for="inputEmail">Password</label>
</div>
</div>
</div>
<div class="col-md-9 offset-md-2">
<div formArrayName="Websites"
*ngFor="let item of frmFirm.get('Websites').controls; let i = index; let last = last">
<div [formGroupName]="i">
<div class="form-group row">
<div class="col-md-4">
<input formControlName="WEBSITE_URL" class="form-control">
</div>
<div class="col-md-4">
<input formControlName="USERNAME" class="form-control">
</div>
<div class="col-md-3">
<input formControlName="PASSWORD" class="form-control">
</div>
<div class="col-md-1" *ngIf="EditMode">
<button (click)="removeWebsite(i)">RM</button>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="form-group row" *ngIf="EditMode">
<div class="col-md-2 offset-md-2">
<button type="button" (click)="addWebsite()">Add Website</button>
</div>
</div>
</div>
<div *ngIf="EditMode">
<button type="button" (click)="addAddress()">Add Address</button>
</div> -->
<div formArrayName="Addresses" *ngFor="let item of frmFirm.get('Addresses').controls; let i = index;">
<div [formGroupName]="test">
<div class="form-group row">
<label class="col-md-2 col-form-label"> Primary Address
</label>
<div class="col-md-2">
<div class="form-check">
<label class="form-check-label">
<input class="form-check-input" id="primary1Id{{i}}" type="radio" value="home{{i}}"
formControlName="IS_HEAD_OFFICE"> Home
</label>
</div>
</div>
</div>
<div class="form-group row">
<label class="col-md-2 col-form-label" attr.for="{{'street1Id' + i}}">Line 1
</label>
<div class="col-md-9">
<input class="form-control" id="{{'street1Id' + i}}" type="text"
formControlName="LINE1">
</div>
</div>
<div class="form-group row">
<label class="col-md-2 col-form-label" attr.for="{{'street2Id' + i}}">Line 2
</label>
<div class="col-md-9">
<input class="form-control" id="{{'street2Id' + i}}" type="text"
formControlName="LINE2">
</div>
</div>
<div class="form-group row">
<label class="col-md-2 col-form-label" attr.for="{{'street3Id' + i}}">Line 3
</label>
<div class="col-md-9">
<input class="form-control" id="{{'street3Id' + i}}" type="text"
formControlName="LINE3">
</div>
</div>
<div class="form-group row">
<label class="col-md-2 col-form-label" attr.for="{{'phoneId' + i}}">Phone
</label>
<div class="col-md-9">
<input class="form-control" id="{{'phoneId' + i}}" type="text"
formControlName="SWITCHBOARD_INT">
</div>
</div>
<div class="form-group row" *ngIf="EditMode">
<div class="col-md-9 offset-md-2">
<button type="button" (click)="removeAddress(i)">Remove Address</button>
</div>
</div>
</div>
</div>
<div class="form-group row " *ngIf="EditMode">
<div class="col-md-2 offset-md-2">
<button type="button" (click)="addAddress()">Add Address</button>
</div>
</div>
</div>
<div class="btn-toolbar" style="padding-top:40px;">
<span *ngIf="EditMode"><button type="submit" class="btn btn-primary btn-view-all btn mr-3">Save</button>
</span>
<span><button type="button" class="btn btn-primary btn-view-all btn mr-3"
(click)="cancelManager">Cancel</button>
</span>
<span><button type="button" style="float: right;" class="btn btn-primary btn-view-all"
(click)="deleteManager()">Delete</button>
</span>
</div>
</form>
</div>
</div>
Component
import { Component, Injectable, NgZone, ViewEncapsulation, ViewChild, Input } from '@angular/core';
import { OnInit } from '@angular/core';
import { FirmService } from '../services/firm.service';
import * as ClassicEditor from '@ckeditor/ckeditor5-build-classic';
import { CommonDataService } from '../services/common.data.service';
import { FormGroup, FormControl, FormBuilder, FormArray } from '@angular/forms';
import { ListItem } from '../models/listItem';
@Component({
selector: 'mgr-firm',
templateUrl: './firm.component.html'
})
export class FirmComponent implements OnInit {
private Error: string;
public FirmDetails: any;
public EditMode: boolean;
public Editor = ClassicEditor;
public EditorConfig: string;
public originalContactList: any;
public originalCityList: any;
public events: string[] = [];
@Input() FirmId: number;
DateFoundedDate: Date;
public frmFirm: FormGroup;
public value: Date = new Date();
constructor(private _fb: FormBuilder, private firmService: FirmService, private commonDataService: CommonDataService) {
}
ngOnInit() {
this.initializeFormModel();
this.getFirmDetails();
}
initializeFormModel() {
this.frmFirm = this._fb.group({
NAME: [''],
SHORT_NAME: [''],
ALTERNATE_NAME: [''],
DATE_FOUNDED: [''],
HISTORY_HTML: [''],
People: [''],
Websites: this._fb.array([
this.createWebsite()
]),
Addresses: this._fb.array([
this.createAddress()
])
});
}
public addWebsite(): void {
this.Websites.push(this.createWebsite());
}
public removeWebsite(index: number): void {
const websites = this.frmFirm.get('Websites') as FormArray;
if (websites.length === 1)
{
websites.reset();
return;
}
websites.removeAt(index);
}
private createWebsite(): FormGroup {
return this._fb.group({
WEBSITE_URL: [''],
USERNAME: [''],
PASSWORD: ['']
});
}
public addAddress(): void {
this.Addresses.push(this.createAddress());
}
public removeAddress(index: number): void {
const addressess = this.frmFirm.get('Addresses') as FormArray;
if (addressess.length === 1)
{
addressess.reset();
return;
}
addressess.removeAt(index);
}
private createAddress(): FormGroup {
return this._fb.group({
// city: [''],
LINE1: [''],
LINE2: [''],
LINE3: [''],
// zipCode: [''],
SWITCHBOARD_INT: ['']
});
}
get Websites(): FormArray {
return <FormArray>this.frmFirm.get('Websites');
}
get Addresses(): FormArray {
return <FormArray>this.frmFirm.get('Addresses');
}
get cities(): ListItem[] {
return JSON.parse(this.FirmDetails.LongCitiesJson).map(x => new ListItem(x.CITY_ID, x.CITY_NAME, null));
}
setFormValues(FirmDetails: any) {
this.frmFirm.patchValue({
NAME: FirmDetails.Firm.NAME,
SHORT_NAME: FirmDetails.Firm.SHORT_NAME,
ALTERNATE_NAME: FirmDetails.Firm.ALTERNATE_NAME,
DATE_FOUNDED: this.getDate(FirmDetails.Firm.DATE_FOUNDED),
HISTORY_HTML: FirmDetails.Firm.HISTORY_HTML,
People: FirmDetails.People
});
this.websites.patchValue(this.modifyFormGroupValues(FirmDetails.Websites, this.websitesMap))
this.websites.patchValue(this.modifyFormGroupValues(FirmDetails.Websites, this.websitesMap));
this.addressess.patchValue(this.modifyFormGroupValues(FirmDetails.Addresses,this.addressesMap))
const addressGroup = FirmDetails.Firm.Addresses.map(address => {
return this._fb.group({
ID: [address.ID],
LINE1: [address.LINE1],
LINE2: [address.LINE2],
LINE3: [address.LINE3],
SWITCHBOARD_INT: [address.SWITCHBOARD_INT],
IS_HEAD_OFFICE: [address.IS_HEAD_OFFICE],
CITY: [address.CITY],
CITY_ID: [address.CITY_ID],
POSTAL_CODE: [address.POSTAL_CODE],
FIRM_ID: [address.FIRM_ID]
});
});
const addressFormArray: FormArray = this._fb.array(addressGroup);
this.frmFirm.setControl('Addresses', addressFormArray);
const websiteGroup = FirmDetails.Firm.Websites.map(website => {
return this._fb.group({
ID: [website.ID],
WEBSITE_URL: [website.WEBSITE_URL],
USERNAME: [website.USERNAME],
PASSWORD: [website.PASSWORD],
FIRM_ID: [website.FIRM_ID]
});
});
const wesbiteFormArray: FormArray = this._fb.array(websiteGroup);
this.frmFirm.setControl('Websites', wesbiteFormArray);
}
getFirmDetails() {
if (this.FirmId != null) {
this.firmService.getFirmDetails(this.FirmId)
.subscribe(data => {
this.FirmDetails = data;
this.originalContactList = this.FirmDetails.People;
this.originalCityList = JSON.parse(this.FirmDetails.LongCitiesJson);
this.setFormValues(this.FirmDetails);
},
err => {
this.Error = 'An error has occurred. Please contact BSG';
},
() => {
});
}
}
get dateFoundedDate(): string {
if (this.FirmDetails.Firm.DATE_FOUNDED != null) {
const dateString = this.FirmDetails.Firm.DATE_FOUNDED;
const results = parseInt(dateString.replace(/\/Date\(([0-9]+)[^+]\//i, "$1"));
const date = new Date(results);
const month = date.toLocaleString('en-us', { month: 'long' });
return (month + '-' + date.getFullYear());
}
}
private getDate(dateFounded: string): Date {
if (dateFounded != null) {
const results = parseInt(dateFounded.replace(/\/Date\(([0-9]+)[^+]\//i, "$1"));
const date = new Date(results);
return new Date(date.getFullYear() + '-' + (date.getMonth() + 1) + '-' + date.getDate());
}
}
saveManager() {
const result = Object.assign(this.FirmDetails.Firm, this.frmFirm.value);
//this.FirmDetails.Firm = Object.assign({}, result.Firm);
//const firm = {...this.FirmDetails.Firm, ...this.frmFirm.value};
this.firmService.createFirm(this.FirmDetails)
.subscribe(data => {
this.getFirmDetails();
this.EditMode = !this.EditMode;
},
err => {
this.Error = 'An error has occurred. Please contact BSG';
},
() => {
});
}
public keyContactChange(value: any): void {
this.FirmDetails.Firm.KEY_CONTACT_ID = value;
}
public dateFoundedChanged(value: Date): void {
this.FirmDetails.Firm.DATE_FOUNDED = value;
}
handleFilter(value) {
if (value.length >= 1) {
this.FirmDetails.People = this.originalContactList.filter((s) => s.FIRST_NAME.toLowerCase().indexOf(value.toLowerCase()) !== -1);
} else {
this.FirmDetails.People = this.originalContactList;
}
}
}
Upvotes: 4
Views: 5891
Reputation: 552
My understanding of your requirements is when a user sets an address as "Home" all of the other addresses should be unset as "Home." If my understanding is correct, You were on the right track with your original code but there are a couple of things that you will need to correct.
With the ngFor you are creating an address for each control that is in the FormArray. Each control in the FormArray is a FormGroup whos name is its index in the FormArray. So you were correct when you had i has the formGroupName
So change back to this
<div [formGroupName]="i">
Add a method to the component that will set IS_HEAD_OFFICE to false for every other control in the array
onPrimaryToggled(newValue: any, changedItem: FormControl) {
if (newValue.returnValue === true) {
const toChange = this.frmFirm.get('Addresses').controls.filter(el => el !== changedItem);
toChange.forEach(el => el.patchValue({
IS_HEAD_OFFICE: false
}));
}
}
Add a change event binding to the the radio button to call the method
(change)="onPrimaryToggled($event, item)"
Now it should look like the below code (btw, you don't need the value attribute)
<div formArrayName="Addresses" *ngFor="let item of frmFirm.get('Addresses').controls; let i = index;">
<div [formGroupName]="i">
<div class="form-group row">
<label class="col-md-2 col-form-label"> Primary Address </label>
<div class="col-md-2">
<div class="form-check">
<label class="form-check-label">
<input
class="form-check-input"
id="primary1Id{{i}}"
type="radio"
formControlName="IS_HEAD_OFFICE"
(change)="OnPrimaryToggled($event, item)"
> Home
</label>
</div>
</div>
</div>
</div>
</div>
Upvotes: 0
Reputation: 114
the problem here is that your radio buttons have the same value assigned, so they are treated as the same input. That's why you can't toggle between them.
Try setting a different value for each of them and it should be working as expected and this will also help with your form logic and validations ;)
As a reminder, the only thing you need in order to make a group of radio buttons to toggle between them is setting the same name (or formControlName in your case) to all of them, and of course also different values. Do not set the same Id as it was previously suggested because that's not semantically correct from an HTML perspective
Here's a minimal StackBlitz example where you can try it out https://stackblitz.com/edit/angular-lvahzg?file=src%2Fapp%2Fapp.component.ts
It has all the radio buttons with the same formContrlName and the same value, try setting different values to each of them and see how it works ;)
Edit: Adding Angular documentation regarding radio buttons withing reactive forms https://angular.io/api/forms/RadioControlValueAccessor
Edit 2: Here's the issue I was talking on my comment
<div formArrayName="Addresses" *ngFor="let item of frmFirm.get('Addresses').controls; let i = index;">
<div [formGroupName]="i"> --> This formGroupName should be the same on each loop in order to make the radio buttons to toggle between them
<div class="form-group row">
<label class="col-md-2 col-form-label"> Primary Address
</label>
<div class="col-md-2">
<div class="form-check">
<label class="form-check-label">
<input class="form-check-input" id="primary1Id{{i}}" type="radio" value="home{{i}}"
formControlName="IS_HEAD_OFFICE"> Home
</label>
</div>
</div>
</div>
Upvotes: 3
Reputation: 3900
you set formControlName in the correct way, now just give a unic ID to each one of the radio buttons.
Edit:
plus you didn't closed your input tag.
I changed from <input type....> to <input type.../>
One more big tip: When you are working with Angular, you should divide your html to much smaller components. your html file is way too long then it should be. for example your four group should be a single component that made from smaller one's as the input+label for example. the smaller component must receive all of the input that it's need for working with forms. you can read more about how to do it in here
<div class="form-group row">
<label class="col-md-2 col-form-label"> Primary Address </label>
<div class="col-md-2">
<div class="form-check">
<label class="form-check-label">
<input class="form-check-input" id="primary1Id{{i}}" type="radio" value="home{{i}}"
formControlName="IS_HEAD_OFFICE"/> <span>Home</span>
</label>
</div>
</div>
</div>
Upvotes: 3