1

I inherited some code off someone and I'm having a weird problem with it and can't figure it out. I have a screen that displays a grid of 9 flashcards - randomly selected - which should flip round and reveal a word when clicked (it's for teaching kids words).

It works fine until the Refresh button is pressed, which should select a different random set of nine cards. It does that OK, but the cards then no longer flip when clicked.

It seems that the flashcard component is 'emitting' the click event but after the 'refresh' operation the newly minted 'flashcards' appear to not be listening.

Anyone got any ideas what is going wrong and how I can fix it? Thanks!

The code is made of the page (flashcards.component.html) :

    <ion-toolbar>
        <ion-button (click)="refreshCards()" color="primary" expand="block" fill="solid" id="refresh">Refresh
        </ion-button>
    </ion-toolbar>

  <div class="app-flash-card-grid">
        <!-- flashCardsSelection has the random selection of cards -->
        <!-- generate each flippable card -->
        <flashCard class="app-flash-card" *ngFor="let flashCards of flashCardsSelection; let i = index">

            <div class="flash-card-front">
                <img alt="{{flashCards.name}}" src="{{flashCards.filename}}">
            </div>

            <div class="flash-card-back">
                <h1>{{flashCards.name}}</h1>
            </div>

        </flashCard>
    </div>

in flashcards.component.ts :

 refreshCards() {
        let fcardindex = 0;
        let x = 0;

        // clear selection
        for (fcardindex = 0; fcardindex < 9; fcardindex++) {
            this.flashCardsSelection.pop();
        }

        // populate selection
        for (fcardindex = 0; fcardindex < 9; fcardindex++) {
            const wordlistsize = this.flashCardsArray.length;

            x = Math.round((Math.random() * 100) * wordlistsize / 100);
            console.log('x=' + x);
            if (this.categoryfilter.length > 0) {
                // check the category filter
                if (this.categoryfilter.indexOf(this.flashCardsArray[x].category) == -1) {
                    // not filtered
                    console.log('not filtered');

                    // check for duplicates
                    // is this one already in the selection array?

                    if (this.selectionContains(this.flashCardsSelection, this.flashCardsArray[x].name)) {
                        console.log('dupe found');
                        // if so, decrement the index and go again
                        fcardindex = fcardindex - 1;
                    } else {
                        // if not, add it
                        console.log('no dupe, adding');
                        this.flashCardsSelection.push((this.flashCardsArray)[x]);
                    }
                } else {
                    // filtered out, so decrement index and go again
                    // console.log('category filtered!');
                    fcardindex = fcardindex - 1;
                }
            } else {
                // no filter defined
                // console.log('no filter defined, adding');
                this.flashCardsSelection.push((this.flashCardsArray)[x]);
            }
        }
    }

and the flashcards.component.scss file:

.flip-grid {
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;

  .flip-item {
    display: flex;
    width: 50%;
    justify-content: center;
    align-items: center;

    .panel {
      float: left;
      width: 45vmin;
      height: 45vmin;
      margin: 8px;
      position: relative;
      font-size: .8em;
      -webkit-perspective: 600px;
      perspective: 600px;
      display: block;
      @media(min-width: 768px) {
        width: 32vmin;
        height: 32vmin;
      }

      @media(min-width: 1024px) {
        width: 24.5vmin;
        height: 24.5vmin;
      }

      .front {
        float: none;
        position: absolute;
        top: 0;
        left: 0;
        z-index: 900;
        width: inherit;
        height: inherit;
        border: 1px solid #ccc;
        background: #6b7077;
        text-align: center;
        -webkit-transform: rotateX(0) rotateY(0);
        transform: rotateX(0) rotateY(0);
        -webkit-transform-style: preserve-3d;
        transform-style: preserve-3d;
        -webkit-backface-visibility: hidden;
        backface-visibility: hidden;
        -webkit-transition: all .4s ease-in-out;
        transition: all .4s ease-in-out;
      }

      &.flip {

        .front {
          z-index: 900;
          //    border-color: #eee;
          background: #333;
          box-shadow: 0 15px 50px rgba(0, 0, 0, 0.2);
          -webkit-transform: rotateY(179deg);
          transform: rotateY(179deg);
        }

        .back {
          z-index: 1000;
          background: #4c8dff;
          -webkit-transform: rotateX(0) rotateY(0);
          transform: rotateX(0) rotateY(0);
        }

      }

      .back {
        float: none;
        position: absolute;
        top: 0;
        left: 0;
        z-index: 800;
        //width: inherit;
        //height: inherit;
        // border: 1px solid #ccc;
        background: #4c8dff;
        color: #fff;
        -webkit-transform: rotateY(-179deg);
        transform: rotateY(-179deg);
        -webkit-transform-style: preserve-3d;
        transform-style: preserve-3d;
        -webkit-backface-visibility: hidden;
        backface-visibility: hidden;
        -webkit-transition: all .4s ease-in-out;
        transition: all .4s ease-in-out;
        display: flex;
        width: 100%;
        height: 100%;
        align-items: center;
        justify-content: center;

        h1 {
          margin: 0;
          font-size: 18px;
          text-transform: capitalize;
        }
      }
    }
  }
}

.app-flash-card-grid {
  display: flex;
  flex-wrap: wrap;

  .app-flash-card {
    @media screen and (min-width: "768px") {
      max-width: 33.33%;
    }
    max-width: 50%;
    width: 100%;
    padding: 8px;

    .flash-card-back {
      h1 {
        margin: 0;
        font-size: 18px;
        text-transform: capitalize;
        color: #fff;
      }
    }
  }
}

.pos-center {
  margin: auto;
  display: block;
}

.sc-ion-buttons-md-h {
  width: 85px;
  justify-content: flex-end;
}


.flipper {
  transition: 0.6s;
  transform-style: preserve-3d;
  position: relative;
  //border: 1px solid #000;
  &:after {
    content: "";
    display: block;
    padding-bottom: 100%;
  }

  .front, .back {
    display: flex;
    align-items: center;
    justify-content: center;
    backface-visibility: hidden;
    margin: 0;
    position: absolute;
    top: 0;
    left: 0;
    height: calc(100% + 3px);
  }

  .front {
    z-index: 2;
    transform: rotateY(0deg);
  }

  .back {
    transform: rotateY(180deg);
    background: #4c8dff;

    h1 {
      margin: 0;
      font-size: 18px;
      text-transform: capitalize;
      color: #fff;
    }
  }

}

flash-card-grid.component.html:

<ng-content></ng-content>

flash-card-grid.component.ts:

import {AfterContentInit, AfterViewInit, Component, ContentChildren, OnChanges, QueryList} from '@angular/core';
import {FlashCardComponent} from '../flash-card/flash-card.component';


@Component({
    selector: '.app-flash-card-grid',
    templateUrl: './flash-card-grid.component.html',
    styleUrls: ['./flash-card-grid.component.scss'],
})
export class FlashCardGridComponent implements AfterViewInit, OnChanges, AfterContentInit {

    @ContentChildren(FlashCardComponent)
    groups: QueryList<FlashCardComponent>;

    ngAfterViewInit() {
        console.log('Inside FlashCardGridComponent:ngAfterViewInit');
        // this.groups.toArray()[0].flipped = true;
        this.groups.toArray().forEach((t) => {
            t.flip.subscribe(() => {
                this.openGroup(t);
            });
        });
    }

    ngAfterContentInit() {

    }

    openGroup(flashCard) {
        // toggle flipped status
        if (flashCard.flipped == true) {
            flashCard.flipped = false;
        } else {
            this.groups.toArray().forEach((t) => t.flipped = false);
            flashCard.flipped = true;
        }
    }
}

and the flashcard component itself (flash-card.component.html)

<div class="flip-container" (click)="emitflip()" [class.flipped]="flipped">
    <div class="flipper">
        <div class="front">
            <ng-content select=".flash-card-front"></ng-content>
        </div>
        <div class="back">
            <ng-content select=".flash-card-back"></ng-content>
        </div>
    </div>
</div>

and flash-card-component.ts:

import {Component, Input, Output, EventEmitter} from '@angular/core';

@Component({
  selector: 'flashCard',
  templateUrl: './flash-card.component.html',
  styleUrls: ['./flash-card.component.scss'],
})

export class FlashCardComponent  implements OnInit {

  @Input() flipped = false;
  @Output() flip: EventEmitter<any> = new EventEmitter<any>();

  emitflip() {
    console.log('emit');
    this.flip.emit();
  }

}

and finally the flash-card-component.scss:

.flip-container {
  perspective: 1000px;
}

.flip-container.flipped .flipper {
  transform: rotateY(180deg);
}

.flip-container, .front, .back {
  width: 100%;
}

.flipper {
  transition: 0.6s;
  transform-style: preserve-3d;
  position: relative;
  border: 1px solid #dee2e3;
    &:after{
        content: "";
        display: block;
        padding-bottom: 100%;
    }

  .front, .back {
    display: flex;
    align-items: center;
    justify-content: center;
    backface-visibility: hidden;
    margin: 0;
    position: absolute;
    top: 0;
    left: 0;
    height: calc(100% + 4px);
  }

  .front {
    z-index: 2;
    transform: rotateY(0deg);
  }

  .back {
    transform: rotateY(180deg);
    background: #4c8dff;
    h1{
        margin: 0;
        font-size: 18px;
        text-transform: capitalize;
        color: #fff;
    }
  }
}
Andy Rox
  • 13
  • 3

1 Answers1

0

Here you might find what you are looking for: Mat-Button click inside a *ngFor with let index = index does not react/fire action

Basically you need to inform Angular how to track your objects in the *ngFor. Since your refresh() method will modify the dataset you are iterating the in the template, Angular will lose it's reference.

It could look like this:

<div *ngFor="let value of values; trackBy: index">
  • index is predefined by Angular and is the current iteration number

You need to use trackBy in the *ngFor operation and provide a id or the index of the element. I have an example for you how to do it:

https://stackblitz.com/edit/angular-form-array-example-test123-2jzdij

Any feedback would be very apprechiated!

Ling Vu
  • 4,740
  • 5
  • 24
  • 45