Angular 2 Click + ngClass, how to apply ONLY to SELF but not ALL elements inside ngFor
Asked Answered
C

2

5

In Angular 1.x this following code works as I want to to click and flip a card inside an ng-repeat

<div class="card" ng-repeat="let card of cards">
<div class="flipcard" ng-class="{'flipped':isflipped}" ng-click="isflipped = !isflipped">
</div>
</div>

However in Angular 2 when clicked, it flipped every "card" inside the ngFor loop... how do I bind the ngClass condition to the element itself only?

<div class="card" *ngFor="let card of cards">
<div class="flipcard"  [ngClass]="{'flipped': isflipped }" (click)="isflipped = !isflipped;">
</div>
</div>
Cutthroat answered 25/7, 2016 at 10:12 Comment(0)
P
5

Change it to:

<div class="card" *ngFor="let card of cards">
    <div class="flipcard"  [ngClass]="{'flipped': card.isflipped }" (click)="card.isflipped = !card.isflipped;">
    </div>
</div>
Pirogue answered 25/7, 2016 at 10:16 Comment(1)
it worked!! I guess Angular 2 is less forgiving as Angular 1.x so the behaviour is different? it's a good thing for me to learn better practice in coding.Cutthroat
D
4

Why does it work with AngularJS (1.x) ?

ng-repeat create a new scope for each iterated element, so the isFlipped property is set on the iterated element scope (unique for each element):

ngRepeat

The ngRepeat directive instantiates a template once per item from a collection. Each template instance gets its own scope, where the given loop variable is set to the current collection item, and $index is set to the item index or key.

Why is it not working with Angular (2+) ?

Angular has no scope anymore, so when you set the isFlipped property, it is on the current component, not anything related to the iterated element.

If card elements are Objects :

For your particular case, it seems that every card is an Object, so you can just add a a property isFlipped to every card element like @Harry Ninh suggests it. Think to declare this property inside the class or interface that defines card elements otherwise AOT compilation might fail.

If you don't want to add a contextual property onto your class/interface, see the "If there can be more than one flipped card(...)" part.

If there can be only one flipped card :

If you can only have one single flipped card, you can add a property currentCard to your component and compare the iterated card to the current one inside your template :

component :

export class MyComponent{
    // (...)
    currentCard:Card;
    // (...)
}

template :

<div class="card" *ngFor="let card of cards">
    <div class="flipcard"  [ngClass]="{'flipped': card===currentCard }" (click)="currentCard = card">
    </div>
</div>

If there can be more than one flipped card and card elements are not objects or can be null.

Well in this case you need to maintain the state flipped/ not flipped of each item, like using an array of boolean or an Object with card value as key and boolean values

component :

export class MyComponent{

    // (...)

    cards: Card=[];
    private flipped: boolean[];

    flip(index:number){
      this.flipped[index]=!this.flipped[index]
    }

    // (...)

}

template :

<div class="card" *ngFor="let card of cards: let i= index">
    <div class="flipcard"  [ngClass]="{'flipped': flipped[i] }" (click)="flip(i)">
    </div>
</div>
Dufy answered 18/9, 2017 at 7:59 Comment(2)
Thanks for a great answer and explanationIvey
In my case, in the function that flips I add an extra property to the to the object and then check what is its value. I got that inspiration from your Angular 2 part, thanks.Cramoisy

© 2022 - 2024 — McMap. All rights reserved.