2

Suppose I have a list of objects and I want to change a certain object styling. I am utilizing ngClass and click event to toggle CSS class.

<ul class="container">
  <li class="item" [ngClass]="{'active': isClassVisible }" (click)="isClassVisible = !isClassVisible">1</li>
  <li class="item" [ngClass]="{'active': isClassVisible }" (click)="isClassVisible = !isClassVisible;">2</li>
  <li class="item" [ngClass]="{'active': isClassVisible }" (click)="isClassVisible = !isClassVisible;">3</li>
  <li class="item" [ngClass]="{'active': isClassVisible }" (click)="isClassVisible = !isClassVisible;">4</li>
</ul>

then in component I have

export class MyComponent {
  isClassVisible: false;
}

and CSS

.active {
  background: black;
}

However with this approach, when I click on an element inside list, the CSS class is applied to all of them, but not the element I clicked.

4 Answers 4

7

Why not make a directive to handle it

import { Directive, ElementRef, Renderer2, HostListener } from '@angular/core';
@Directive({ selector: '[myActive]' })
export class ActiveDirective {

    private _isActive = false;

    constructor(private el: ElementRef, private renderer: Renderer2) {

    }

    @HostListener('click', ['$event'])
    onClick(e) {
        e.preventDefault();
        this._isActive = !this._isActive;
        if (this._isActive) {
          this.renderer.addClass(this.el.nativeElement, 'active');
        } else {
          this.renderer.removeClass(this.el.nativeElement, 'active');
        }
    }
}

Then use it like this

<ul class="container">
  <li class="item" myActive>1</li>
  <li class="item" myActive>2</li>
</ul>
  • The Renderer class has been marked as deprecated since Angular version 4 and been completely removed since Angular version 9. You can use Renderer2 in lower angular versions too. - ref
  • Renderer2 Docs
Sign up to request clarification or add additional context in comments.

2 Comments

Thank you, mate. If I get it rright, usually directives are meant to be used to add behaviour to an DOM-element, whereas components require their own templates.
more cleaner way to do the same , +1
0

because you are using same variable in all the li's and when you click one of them style is applying to all of them.

Try using diff diff variable on each li

or use like this

<ul class="container">
  <li *ngFor = 'let n of num' class="item" [ngClass]="{'active': selectedLi == n }" (click)="selectedLi = n">{{n}}</li>
</ul>

export class MyComponent {
  isClassVisible: false;
  num = [1,2,3,4,5]
}

2 Comments

But what if I have hundreds of elements? It would be tedious to use different variable for each of them. Isn't there a more neat solution?
yeah exactly please see my Updated answer for that case
0

You can achieve this by simply implementing a separate directive. To handle this click event use @HostListener and for binding the property use @HostBinding as:

import { Directive, HostBinding, HostListener } from '@angular/core';

@Directive({
  selector : '[appActive]'
})
export class ActivelinkDirective{
  @HostBinding('class.active') isActive=false;

  @HostListener('click') toActive(){
    this.isActive = !this.isActive;
  }
}

And then for the view use this directive as:

<ul class="container">
  <li class="item" appActive>1</li>
  <li class="item" appActive>2</li>
  <li class="item" appActive>3</li>
</ul>

Comments

0

It suffices to have a function and only a property to set some index up to achive your goal. Your html would look like this:

<ul class="container">
  <li class="item" [ngClass]="{'active': selectedItemIndex == 1 }" (click)="onItemClicked(1)">1</li>
  <li class="item" [ngClass]="{'active': selectedItemIndex == 2  }" (click)="onItemClicked(2)">2</li>
  <li class="item" [ngClass]="{'active': selectedItemIndex == 3  }" (click)="onItemClicked(3)">3</li>
  <li class="item" [ngClass]="{'active': selectedItemIndex == 4  }" (click)="onItemClicked(4)">4</li>
</ul>

And in your ts file:

public selectedItemIndex: number = 0;
onItemClicked(val: number){
  this.selectedItemIndex = val;
}

A plus would be to be able set the index up to its original default vale, in other words, to unselect it. One opcion would be to do it at a second click. To pull that off you would need to create a property that stores the number of clicks by index, and a timer. The timer would reset the counter to 0 after a certain amount of time, so to trigger the double click you should click it faster than the reset timer. That should be a good exercise.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.