Stack Overflow
Stack Overflow

Reputation: 2774

angular material mat radio button unchecked on multiple click

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

Answers (5)

Bullsized
Bullsized

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

Cole Perry
Cole Perry

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

Marshal
Marshal

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

Sara Bianchi
Sara Bianchi

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

pontusv
pontusv

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

Related Questions