Reputation: 2774
How can I clear <mat-radio-button>
selected on second click
(after it is already selected)
I know I can implement this behavior with checkbox but i need to allow select only one item.
Any help?
My code look like :
<mat-radio-group name="WasFamilyMemberPresent">
<mat-radio-button *ngFor="let item of lookupNoYes" [value]="item.Code" >
{{item.Desc}}
</mat-radio-button>
</mat-radio-group>
Upvotes: 7
Views: 21020
Reputation: 577
All of the answers above are for Template driven forms that are foreached, not typed and without unit tests, so here's my take on this.
In my template I have 3 radio buttons (pay no mind to the i18n
translations or the CSS classes):
<form [formGroup]="setForm">
<mat-label class="equal-labels mb-sm">{{ t('package-type') }}:</mat-label>
<mat-radio-group class="d-flex flex-column" formControlName="packageType">
<mat-radio-button #containerBtn class="ml-s" [value]="packageType.Container" (click)="checkPackaging(containerBtn)">{{
t('container')
}}</mat-radio-button>
<mat-radio-button #sheetWrapBtn class="ml-s" [value]="packageType.SheetWrap" (click)="checkPackaging(sheetWrapBtn)">{{
t('sheet-wrap')
}}</mat-radio-button>
<mat-radio-button #peelPackageBtn class="ml-s" [value]="packageType.PeelPackage" (click)="checkPackaging(peelPackageBtn)">{{
t('peel-package')
}}</mat-radio-button>
</mat-radio-group>
</form>
then in my script I have:
// form generation
setForm: FormGroup = this.formBuilder.group({
packageType: [''],
});
// function that handles the click (with typed properties)
checkPackaging(btn: MatRadioButton): void {
const packageTypeValue: SetPackageType | undefined = this.setForm.get('packageType')?.value;
if (packageTypeValue && packageTypeValue === btn.value) {
btn.checked = false;
this.setForm.get('packageType')?.setValue('');
} else {
btn.checked = true;
this.setForm.get('packageType')?.setValue(btn.value);
}
}
There is no need to prevent the default event, as we're triggering our own activation of the reactive form control.
Here are my unit tests (with Karma/Jasmine) for this as well:
describe('checkPackaging', () => {
let radioButton: MatRadioButton;
it('should select the mat button if it has not been selected', () => {
radioButton = { value: SetPackageType.Container, checked: false } as MatRadioButton;
component.setForm.get('packageType')?.setValue('');
component.checkPackaging(radioButton);
expect(radioButton.checked).toBeTrue();
expect(component.setForm.get('packageType')?.value).toBe(SetPackageType.Container);
});
it('should deselect the mat button if it has already been selected', () => {
radioButton = { value: SetPackageType.Container, checked: true } as MatRadioButton;
component.setForm.get('packageType')?.setValue(SetPackageType.Container);
component.checkPackaging(radioButton);
expect(radioButton.checked).toBeFalse();
expect(component.setForm.get('packageType')?.value).toBe('');
});
});
I hope that this helps someone. It's done with and works with Angular/Angular Material 17.
Upvotes: 0
Reputation: 160
I had the same problem as OP and I tried both the most popular and second most popular answers but neither of them worked for me. I'm using Angular 10 and this post is 4 years old so I hope this answer will help someone else who has my problem.
I noticed that the mat-radio-button was being assigned a class 'mat-radio-checked'
when you checked it. The reason I believe that the above 2 answers no longer work is that the value of the button is set before you receive it in the ts file so no matter how you try to assign the _checked
value to false
it isn't going to work. But changing that class is doable. This method also allows you to deselect the button without having to keep track of any kind of global variable. Here's a generic example:
In the html assign each button a reference variable using the #
and an on click event passing the event and button reference.
<mat-radio-group class="radio-group">
<mat-radio-button #option1 class="radio-item" value="New User" (click)="onRadioClick($event, option1)”>New User</mat-radio-button>
<mat-radio-button #option2 class="radio-item" value=“Existing User" (click)="onRadioClick($event, option2)”>Existing User</mat-radio-button>
</mat-radio-group>
Then in the onRadioClick
function check to make sure that the button the user clicked has a class 'mat-radio-checked'
; if it does then remove the check class and add our own flag marked-unchecked
. This way if you uncheck an option then check it again we will know that it has been checked again and can go ahead and add the 'mat-radio-checked'
class again.
onRadioClick(event: any, button: any): void {
let targetButton = event.target;
while (targetButton && targetButton.parentNode && targetButton.parentNode.classList) {
targetButton = targetButton.parentNode;
if (targetButton.classList.contains('mat-radio-checked') && !targetButton.classList.contains('marked-unchecked')) {
targetButton.classList.remove('mat-radio-checked');
targetButton.classList.add('marked-unchecked');
break;
}
if (targetButton.classList.contains('marked-unchecked')) {
targetButton.classList.remove('marked-unchecked');
targetButton.classList.add('mat-radio-checked');
}
}
}
For further explanation on this just inspect your mat-radio-button element in your browser and you'll be able to see the mat-radio-checked
css class I was talking about and it should become apparent.
Upvotes: 0
Reputation: 11081
** UPDATED ** 10/11/2022
reference Sara's stackblitz answer below.
You can do the following to accomplish this.
Assign a template reference to the button #button
and pass it to the component method (click)="checkState(button)"
<mat-radio-button #button class="example-radio-button" *ngFor="let season of seasons" [value]="season" (click)="checkState(button)">
Create a local variable in the component to store the button value for comparison in checkState()
currentCheckedValue = null;
DI Renderer2 to remove focus from the button
constructor(private ren: Renderer2) { }
Wrap logic in setTimeout
to put it on the digest cycle, check if local variable exist and if current value equals argument value... if true deselect, remove focus and null local variable... else set local variable for future comparison.
checkState(el) {
setTimeout(() => {
if (this.currentCheckedValue && this.currentCheckedValue === el.value) {
el.checked = false;
this.ren.removeClass(el['_elementRef'].nativeElement, 'cdk-focused');
this.ren.removeClass(el['_elementRef'].nativeElement, 'cdk-program-focused');
this.currentCheckedValue = null;
this.favoriteSeason = null;
} else {
this.currentCheckedValue = el.value
}
})
}
Stackblitz
https://stackblitz.com/edit/angular-c37tsw?embed=1&file=app/radio-ng-model-example.ts
Upvotes: 10
Reputation: 191
I have simplified Marshal's code here
https://stackblitz.com/edit/angular-c37tsw-zho1oe
I removed setTimeout
and Renderer2
import and it would seem to work even without these but with an event.preventDefault()
.
Upvotes: 11
Reputation: 291
I'm not sure what answer you are looking for since the angular material radio buttons default behaviour is to only allow one selected item. (This is what i assume you are asking based off of your question)
Are you trying to create behaviour where the user can deselect all radio buttons?
Upvotes: -1