Angular: Toggle active class on only button the current clicked button (not using *ngFor)

Viewed 26795

I'm trying to figure out how can I toggle the active class on only the clicked button:

This is my current code...

<div class="btn-group">
  <button class="btn btn-secondary" [ngClass]="{ 'active' : isActive }" (click)="isActive = !isActive" type="button">Premier bouton</button>
  <button class="btn btn-secondary" [ngClass]="{ 'active' : isActive }" (click)="isActive = !isActive" type="button">Second bouton</button>
  <button class="btn btn-secondary" [ngClass]="{ 'active' : isActive }" (click)="isActive = !isActive" type="button">Troisième bouton</button>
  <button class="btn btn-secondary" [ngClass]="{ 'active' : isActive }" (click)="isActive = !isActive" type="button">Quatrième bouton</button>
</div>

...and this is the result:

enter image description here

I DO NOT USE *ngFor or ng-repeat principle. I know I need to isolate each button. How can I do that?

Adding this. to isActive doesn't work: this.isActive.

6 Answers

What you do using [ngClass] is good but the problem is with your (click) event. The expression isActive = !isActive gives the error, when you click on the same button twice.

So, you can use different set of methods on each click event of the buttons.

In your .ts file, write as below

isValue: number = 0;  // This is a class property. By initializing with a value, you can set the default button highlighted

toggle1() { this.isValue = 1; }
toggle2() { this.isValue = 2; }
toggle3() { this.isValue = 3; }
toggle4() { this.isValue = 4; }

And in your html file, change as follows,

<button class="btn btn-secondary" [ngClass]="{ 'active' : isValue == 1 }" (click)="toggle1()" type="button">Premier bouton</button>
<button class="btn btn-secondary" [ngClass]="{ 'active' : isValue == 2 }" (click)="toggle2()" type="button">Second bouton</button>
<button class="btn btn-secondary" [ngClass]="{ 'active' : isValue == 3 }" (click)="toggle3()" type="button">Troisième bouton</button>
<button class="btn btn-secondary" [ngClass]="{ 'active' : isValue == 4 }" (click)="toggle4()" type="button">Quatrième bouton</button>

Here, I have change only the (click) events and the [ngClass] .

you also can do it:

in your .ts file

isValue: number = 0;

toggle(num) { this.isValue = num; }

and in your html file change as follow

<button class="btn btn-secondary" [ngClass]="{ 'active' : isValue == 1 }" (click)="toggle(1)" type="button">Premier bouton</button>
<button class="btn btn-secondary" [ngClass]="{ 'active' : isValue == 2 }" (click)="toggle(2)" type="button">Premier bouton</button>
<button class="btn btn-secondary" [ngClass]="{ 'active' : isValue == 3 }" (click)="toggle(3)" type="button">Premier bouton</button>
<button class="btn btn-secondary" [ngClass]="{ 'active' : isValue == 4 }" (click)="toggle(4)" type="button">Premier bouton</button>

and in your css file change as follows:

.active { background-color: gray; }

As you are looking for a more 'Angular Way' to proceed, here is another way, using Service coupled with RxJs. The idea is to have a MenuService which will store the active button reference as Subject. Your buttons will be able to subscribe to that Subject, receiving the new button reference when it changes. Testing that value in your button template will allow you to apply or not the active class.

This example can be elaborated with richer functions, but for explanation purposes, I will keep it as simple as it should be to answer the OP question.

First, create a new Interface (or Class, as you prefer) to define your button model. In this case, I will call it MenuItem. In this example, MenuItem.ts is stored here /src/app/_models/

MenuItem.ts

export interface MenuItem {
   label: string;
   routerLink: string;
}

Then, create the MenuService service. Here, you will need to declare a BehaviourSubject to store the reference of the clicked button. Using this method, you will be able to call .next() each time a new button is clicked and becomes the active one.

menu.service.ts

import { MenuItem } from 'src/app/_models/MenuItem'
import { BehaviorSubject } from 'rxjs';

@Injectable({
    providedIn: 'root',
})
export class MenuService {
    $activeButton: BehaviorSubject<MenuItem> = new BehaviorSubject<MenuItem>({} as MenuItem);

    constructor() {}
}

Next, create a component that will use a MenuItem as Input() property. Don't forget to inject MenuService. Therefore, you will be able to subscribe to the current active button stored in the service.

menu.button.component.ts

@Component({
    selector: 'app-menu-button',
    templateUrl: './menu-button.component.html',
    styleUrls: ['./menu-button.component.scss']
})
    
export class MenuButton implements OnInit {
    @Input() menuItem: MenuItem = {} as MenuItem;
    isActive: boolean = false;

    constructor(private menuService: MenuService) {}

    ngOnInit(): void {
        this.menuService.$activeButton.subscribe((activeButton) => this.isActive = this.menuItem === activeButton);
    }

    setActive() {
       this.menuService.$activeButton.next(this.menuItem);
    }

}

You are now able to test the isActive property of your button and apply, or not, the desired class to it. Don't forget to capture the click event and update the $activeButton value by calling next() and passing the new menuItem value using the setActive() method written above.

menu.button.component.html

<button [routerLink]="this.menuItem.routerLink" [ngClass]="{ active: this.isActive }" (click)="this.setActive()">{{ this.menuItem.label }}</button>
Related