Creating an Angular Component Library - Flip Card Component

Published on

In the last post we created a very basic card component. Now we’re going to expand on that card component and create a more interactive flip card. You can think of a flip card as a index/flash card where you might have some content on the front side, then can be toggled/flipped over with additional content on the back side of the card. We will make our flip card have a configuration where we can set the axis we want the card to flip around, X or Y.

A full screen demo of what we’re going to build.
In the demo, any card with a lower right corner caret icon is flippable.

 

Prerequisites

 

Generating Our Flip Card Component

Since we are expanding on our previous card component, we will put our flip card in the same module. We will need one root level component along with two child components, one for either side of the card (front and back).

Our child components will act just like our original card header, body and footer components where the component is essentially a wrapper around an <ng-content> element.

Lets generate our components…

mkdir projects/foo/src/lib/components/card/shared
ng generate component components/card/flip-card --project=foo
ng generate component components/card/shared/front-card --project=foo --inlineStyle=true --inlineTemplate=true 
ng generate component components/card/shared/back-card --project=foo --inlineStyle=true --inlineTemplate=true 

We are also going to create a shared directory in our card directory to hold our front-card and back-card components. We will use both of these components again when we create our reveal card.

Now we need to update our card module to make our new component available to the importing module.

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';

import { CardComponent } from './card.component';
import { CardBodyComponent } from './card-body.component';
import { CardFooterComponent } from './card-footer.component';
import { CardHeaderComponent } from './card-header.component';
import { FlipCardComponent } from './flip-card/flip-card.component';
import { BackCardComponent } from './shared/back-card/back-card.component';
import { FrontCardComponent } from './shared/front-card/front-card.component';

@NgModule({
  declarations: [
    CardComponent,
    CardBodyComponent,
    CardFooterComponent,
    CardHeaderComponent,
    FlipCardComponent,
    BackCardComponent,
    FrontCardComponent,
  ],
  imports: [
    CommonModule
  ],
  exports: [
    CardComponent,
    CardBodyComponent,
    CardFooterComponent,
    CardHeaderComponent,
    FlipCardComponent,
    BackCardComponent,
    FrontCardComponent,
  ]
})
export class CardModule { }

We are going to use an enum for out flip axis value. This will allow us to set which axis we want our card to flip around.

touch projects/foo/src/lib/components/card/flip-card/flip-axis.enum.ts
export enum FlipAxis {
  X = 'flip-axis-x',
  Y = 'flip-axis-y'
}

And finally update our components directory’s public_api.ts file…

// Card
export * from './card/card.module';
export * from './card/card.component';
export * from './card/card-body.component';
export * from './card/card-footer.component';
export * from './card/card-header.component';
export * from './card/card-type.enum';
export * from './card/flip-card/flip-axis.enum.ts';
export * from './card/flip-card/flip-card.component';
export * from './card/shared/back-card/back-card.component';
export * from './card/shared/front-card/front-card.component';

Create Our Card Side Components

We will add our ng-content tags to our back card and front card components. There will be no logic or styling for these two as previously mentioned.

Our back card component class file…

import { Component } from '@angular/core';

@Component({
  selector: 'foo-back-card',
  template: `<ng-content></ng-content>`
})
export class BackCardComponent {}

Our front card component class file…

import { Component } from '@angular/core';

@Component({
  selector: 'foo-front-card',
  template: `<ng-content></ng-content>`
})
export class FrontCardComponent {}

Creating Our Flip Card Component

And now our flip card component. Our flip card compnonent will make use of our two side componet, front and back, and ng-content elements which will be uses as placesholders for the two cards we use between our flip card component element tag.

We are also going to add a small caret svg icon for each side of our flip card in the lower right corner to act as a toggle to switch between the two sides. Feel free replace the svg with whatever you want. I didn’t want to pull in a font icon library for one icon so I just grabbed a raw svg.

Our HTML template will look something like this…

<div class="flip-card" [ngClass]="flipAxis" [class.flipped]="isFlipped">
  <div class="flip-card-inner">
    <div class="flip-card-front">
      <ng-content select="foo-front-card"></ng-content>
      <div class="flip-button" (click)="flipCard()">
        <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16pt" height="16pt" viewBox="0 0 16 16" version="1.1">
          <g id="surface1">
            <path style=" stroke:none;fill-rule:nonzero;fill: #AAA;fill-opacity:1;" d="M 10.855469 4 L 10.855469 12 C 10.855469 12.15625 10.800781 12.289062 10.6875 12.402344 C 10.574219 12.515625 10.441406 12.570312 10.285156 12.570312 C 10.132812 12.570312 9.996094 12.515625 9.882812 12.402344 L 5.882812 8.402344 C 5.769531 8.289062 5.714844 8.15625 5.714844 8 C 5.714844 7.84375 5.769531 7.710938 5.882812 7.597656 L 9.882812 3.597656 C 9.996094 3.484375 10.132812 3.429688 10.285156 3.429688 C 10.441406 3.429688 10.574219 3.484375 10.6875 3.597656 C 10.800781 3.710938 10.855469 3.84375 10.855469 4 Z M 10.855469 4 "/>
          </g>
        </svg>
      </div>
    </div>
    <div class="flip-card-back">
      <ng-content select="foo-back-card"></ng-content>
      <div class="flip-button" (click)="flipCard()">
        <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16pt" height="16pt" viewBox="0 0 16 16" version="1.1">
          <g id="surface1">
            <path style=" stroke:none;fill-rule:nonzero;fill: #AAA;fill-opacity:1;" d="M 10.855469 4 L 10.855469 12 C 10.855469 12.15625 10.800781 12.289062 10.6875 12.402344 C 10.574219 12.515625 10.441406 12.570312 10.285156 12.570312 C 10.132812 12.570312 9.996094 12.515625 9.882812 12.402344 L 5.882812 8.402344 C 5.769531 8.289062 5.714844 8.15625 5.714844 8 C 5.714844 7.84375 5.769531 7.710938 5.882812 7.597656 L 9.882812 3.597656 C 9.996094 3.484375 10.132812 3.429688 10.285156 3.429688 C 10.441406 3.429688 10.574219 3.484375 10.6875 3.597656 C 10.800781 3.710938 10.855469 3.84375 10.855469 4 Z M 10.855469 4 "/>
          </g>
        </svg>
      </div>
    </div>
  </div>
</div>

Our SCSS which will handle our flip aniamtion for both the X and Y axis….

@import '../../_shared/scss/mixins';

:host ::ng-deep .flip-card {
  display: block;
  position: relative;
  perspective: 1000px;
  background: transparent;
  height: 100%;
  
  .flip-card-inner {
    display: flex;
    height: 100%;
    transition: transform 0.6s;
    transform-style: preserve-3d;
    @include box-shadow;
    
    .flip-card-front,
    .flip-card-back {
      flex: 1;
      
      .flip-button {
        cursor: pointer;
        position: absolute;
        right: 0rem;
        bottom: 0rem;
        transform: rotate(225deg);
        z-index: 10 !important;
      }

      foo-card {
        height: 100%;
        flex-grow: 1;
      }
    }
    
    .flip-card-front {
      @include backface-visibility(visible);
      transition: opacity 0s 0.2s;
      margin-right: -50%;
    }
    
    .flip-card-back {
      @include backface-visibility(hidden);
      margin-left: -50%;
    }
  }

  &.flipped {
    .flip-card-inner {
      .flip-card-front {
        opacity: 0;
        transition: opacity 0s 0.2s;
        @include backface-visibility(hidden);

        .flip-button {
          opacity: 0;
          z-index: -1;
        }
      }

      .flip-card-back {
        @include backface-visibility(visible);
      }
    }
  }
  
  &.flipped {
    &.flip-axis-x {
      .flip-card-inner {
        transform: rotateX(180deg);
      }
    }
  }

  &.flipped {
    &.flip-axis-y {
      .flip-card-inner {
        transform: rotateY(180deg);
      }
    }
  }

  &.flip-axis-x {
    .flip-card-inner {
      .flip-card-back {
        transform: rotateX(180deg);
      }
    }
  }

  &.flip-axis-y {
    .flip-card-inner {
      .flip-card-back {
        transform: rotateY(180deg);
      }
    }
  }
}

We are also going to need to update our mixins file, components/_shared/scss/_mixins.scss, to include our backface-visibility mixin…

@mixin box-shadow {
  box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2);
  -webkit-box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2);
  -moz-box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2);
}

@mixin backface-visibility($value) {
  backface-visibility: $value;
  -webkit-backface-visibility: $value;
}

And finally our flip card component class file…

import { Component, OnInit, Input } from '@angular/core';
import { FlipAxis } from './flip-axis.enum';

@Component({
  selector: 'foo-flip-card',
  templateUrl: './flip-card.component.html',
  styleUrls: ['./flip-card.component.scss']
})
export class FlipCardComponent implements OnInit {
  @Input()
  public flipAxis: FlipAxis;
  
  public isFlipped: boolean;

  constructor() {
    this.isFlipped = false;
    this.flipAxis = FlipAxis.Y;
  }

  ngOnInit(): void {
  }

  public flipCard(): void {
    this.isFlipped = !this.isFlipped;
  }
}

Our class file has a single method that flips our boolean, isFlipped. We use isFlipped in our template to apply our flipped class to our component which will trigger our animation. We also allow for out flip axis value as an @Input value. We set our default flip axis to the Y axis.

Using Our Flip Card Component

Using our flip card is pretty easy. We create an HTML structure around two card components. Each card component will be wrapped in either <foo-front-card> or <foo-back-card> element tag. Our two wrapped card components will then be wrapped together in a <foo-flip-card> element tag. For demo app we will configure out flip card to flip around the X axis as well.

Lets update our demo project’s app component HTML file…

<div style="width: 500px; display: block;">
  <foo-flip-card [flipAxis]="FlipAxis.X">
    <foo-front-card>
      <foo-card [type]="CardType.DANGER">
        <foo-card-header>
          <h1>Front</h1>
        </foo-card-header>
        <foo-card-body>
          <p>Testing</p>
        </foo-card-body>
        <foo-card-footer>
          <p>Footer</p>
        </foo-card-footer>
      </foo-card>
    </foo-front-card>
    <foo-back-card>
      <foo-card [type]="CardType.DEFAULT">
        <foo-card-header>
          <h1>Back</h1>
        </foo-card-header>
        <foo-card-body>
          <p>Testing</p>
        </foo-card-body>
        <foo-card-footer>
          <p>Footer</p>
        </foo-card-footer>
      </foo-card>
    </foo-back-card>
  </foo-flip-card>
</div>

In our app component class we need to import and declare our FlipAxis enum…

import { Component, OnInit } from '@angular/core';

import { CardType, FlipAxis } from 'foo';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {
  public CardType = CardType;
  public FlipAxis = FlipAxis;

  constructor() {}

  ngOnInit() {
  }
}

And since we made our flip card component a part of our card module, our app module will remain the same as well…

import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { NgModule } from '@angular/core';

import { CardModule } from 'foo';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    BrowserAnimationsModule,
    AppRoutingModule,
    CardModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Now we can build our library and run our demo project.

npm run build
npm start

Stackblitz Result

I’ve also embeded a Stackblitz showing our flip card being used with our dashabord layout we create a few posts back. I’ve added a card for each card type we created, some are wrapped in flip cards so any card with a lower right corner caret icon is flippable. Also some are configured to flip around the X axis and other the Y axis.

A full screen demo can also be viewed here

 

 

The completed github repository can be found here

 

References

 

comments powered by Disqus