30

Take a look at this Plunker: https://plnkr.co/edit/yu95hUrKlUh4Ttc5SwYD?p=preview

When I'm using <mat-slide-toggle>, I am able to modify the values in my component:

<mat-slide-toggle [(ngModel)]="myFlagForSlideToggle">Toggle me!</mat-slide-toggle>

myFlagForSlideToggle is updated as expected.

But when I'm using <mat-button-toggle>, the values are not updated. I had to add ngDefaultControl to even make this example work, but I'm not sure how it matters.

<mat-button-toggle [(ngModel)]="myFlagForButtonToggle" ngDefaultControl>Toggle me!</mat-button-toggle>

What is the correct way to bind a button state to the component?

Mateusz Stefek
  • 3,478
  • 2
  • 23
  • 28

8 Answers8

40

MatButtonToggle component doesn't implement ControlValueAccessor therefore you can't use ngModel on it. ngDefaultControl was introduced for other purposes.

MatButtonToggle is supposed to be a part of mat-button-toggle-group. But if you want to use it as a standalone component and bind model to it here is some example of how you can do it:

<mat-button-toggle 
  [checked]="myFlagForButtonToggle" 
  (change)="myFlagForButtonToggle = $event.source.checked">
    Toggle me!
</mat-button-toggle>

Plunker Example

yurzui
  • 205,937
  • 32
  • 433
  • 399
  • thank you! was trying to do things with observable on an object from parent component but didn´t get it working. This made the trick (BUT, was not working for the observable array, only a regular one.. – JimiSweden Jun 03 '21 at 11:57
27

If you are trying to use mat-button-toggle to switch between enabling / disabling something, you will have to use a binding on mat-button-toggle-group, and make sure that the mat-button-toggle's themselves have the boolean values of true and false, not the string values. That is to say:

<mat-button-toggle-group [(ngModel)]="isEnabled">
    <mat-button-toggle [value]="true"> Enable </mat-button-toggle>
    <mat-button-toggle [value]="false"> Disable </mat-button-toggle>
</mat-button-toggle-group>
Andrew Favaloro
  • 291
  • 3
  • 7
  • 1
    This should be marked as correct answer, because this works and is cleanest way to do it without click or change handlers. – Janne Harju Nov 22 '21 at 09:39
  • @JanneHarju No, this is perfectly fine as a second answer. It would result in 2 buttons - a.k.a. not what the question asked for. And it cannot _easily_ be adapted to fit the requirement, either. A single button like `Toggle me!` would not work. – Dávid Leblay Jul 17 '22 at 18:43
  • Binding the value of the actual toggle buttons was the 'trick' – utd1878 Aug 01 '23 at 00:01
18

mat-button-toggle dont have a boolean value and [(ngModel)] won't work. See doc.

These toggles can be configured to behave as either radio-buttons or checkboxes.

a use case may be like this

<mat-button-toggle-group  [(ngModel)]="myFlagForButtonToggle">
  <mat-button-toggle value="button1"  ngDefaultControl>Toggle me!</mat-button-toggle>
  <mat-button-toggle value="button2"  ngDefaultControl>Toggle me!</mat-button-toggle>
  <mat-button-toggle value="button3"  ngDefaultControl>Toggle me!</mat-button-toggle>
</mat-button-toggle-group>

and change your boolean to myFlagForButtonToggle :string;

ttugates
  • 5,818
  • 3
  • 44
  • 54
Hareesh
  • 6,770
  • 4
  • 33
  • 60
  • 2
    Can't bind to 'ngModel' since it isn't a known property of 'mat-button-toggle-group' – jenson-button-event Jun 08 '18 at 20:41
  • Everything works like it should... just import CommonModule for NgModel to work... – Playnox Jun 22 '18 at 01:16
  • Brilliant. Other solutions work, but this is by far the easiest and most "normal" for data binding. – Scott Byers Dec 12 '18 at 19:28
  • 4
    This should be the best answer, which is exactly the design and the feature mentioned in the official document. But `ngModel` lives in `FormModule`, which means that `import { FormsModule } from '@angular/forms';` is required to be imported into AppModule – 千木郷 Mar 13 '19 at 02:40
  • This contains something good and something bad. Code itself works. It was good part. But that part where you say "toggle button won't work with boolean" is wrong. Check Andrew Favaloro answer above. surrounfing value with [] you can set boolean value. otherwise it is string. Maybe in 2017 situation was different. – Janne Harju Nov 22 '21 at 09:35
6
public selectedVal: string;
constructor() { }

ngOnInit(){
  this.selectedVal ='option1';
} 

public onValChange(val: string) {
  this.selectedVal = val;
}

 <mat-button-toggle-group #group="matButtonToggleGroup" [value]="selectedVal" (change)="onValChange(group.value)" >
  <mat-button-toggle value="option1">
    Option 1
  </mat-button-toggle>
  <mat-button-toggle value="option2">
    Option 2
  </mat-button-toggle>
</mat-button-toggle-group>
Uliana Pavelko
  • 2,824
  • 28
  • 32
2

I have created a Custom Form Control for the Angular Material Button Toggle Group. Here is how I implemented it:

custom-button-toggle-group.component.html

<mat-button-toggle-group
  (change)="onToggleGroupChange($event)"
  [disabled]=disabled
  [value]=value
>
  <mat-button-toggle
    *ngFor="let toggle of toggles"
    [value]="toggle.value"
  >{{ toggle.label }}</mat-button-toggle>
</mat-button-toggle-group>

custom-button-toggle-group.component.ts

import { Component, forwardRef, HostListener, Input } from '@angular/core';
import { NG_VALUE_ACCESSOR, ControlValueAccessor } from '@angular/forms';
import { MatButtonToggleChange } from '@angular/material/button-toggle';

export interface ButtonToggle {
  value: string;
  label: string;
}

@Component({
  selector: 'custom-button-toggle-group',
  templateUrl: './button-toggle-group.component.html',
  styleUrls: ['./button-toggle-group.component.scss'],
  providers: [
    {
      provide: NG_VALUE_ACCESSOR, multi: true,
      useExisting: forwardRef(() => CustomButtonToggleGroupComponent),
    }
  ]
})
export class CustomButtonToggleGroupComponent implements ControlValueAccessor {

  @Input() public toggles: ButtonToggle[];

  public value: string;
  public disabled: boolean;

  private onChange: (value: string) => void;
  private onTouched: () => void;


  public onToggleGroupChange({ value }: MatButtonToggleChange): void {
    this.doChange(value);
  }

  writeValue(obj: any): void {
    this.value = obj;
  }

  registerOnChange(fn: any): void {
    this.onChange = fn;
  }

  registerOnTouched(fn: any): void {
    this.onTouched = fn;
  }

  setDisabledState?(isDisabled: boolean): void {
    this.disabled = isDisabled;
  }

  private doChange(selectedValue: string): void {
    this.onChange(selectedValue);
  }

  @HostListener('blur')
  private doBlur(): void {
    this.onTouched();
  }

}

This way you will be able to use FormControl, FormControlName, etc. on the custom-button-toggle-group.

webpreneur
  • 795
  • 9
  • 15
1
<mat-button-toggle-group formControlName="flags" style="width: 100%" (change)="onChangeFlag($event)">
    <mat-button-toggle *ngFor="let flag of allFlags" value="{{flag.flag}}" style="width: 100%;" 
    matTooltip="{{flag.name}}" [checked]="flagSelected == flag.flag"
    > <img [alt]="flag" src="assets/images/flags/{{flag.flag}}.png" width="20px"/> 
    </mat-button-toggle>

  </mat-button-toggle-group>
0

I had the same issue with Angular Material. I provided name and id attributes along with ngModel attribute (for template driven forms) for to bind to model values. It worked for me.

Please see code below:

<mat-button-toggle-group name="fontStyle" name="fontStyle" id="fontStyle">
    <mat-button-toggle value="bold">Bold</mat-button-toggle>
    <mat-button-toggle value="italic">Italic</mat-button-toggle>
    <mat-button-toggle value="underline">Underline</mat-button-toggle>
Samira
  • 1
  • 1
0

This was what worked for me.

My typescript:

myFlagForButtonToggle: boolean = false

My HTML:

   <mat-button-toggle 
      [checked]="myFlagForButtonToggle" 
      (change)="myFlagForButtonToggle = !myFlagForButtonToggle">
        Toggle me!
    </mat-button-toggle>
joseph chikeme
  • 184
  • 1
  • 12