Creating an Angular Component Library - Card Component
Published on
A card component is something that you might use quite a bit in a project. We are going to build a very basic card component that doesn’t really do anything fancy to start. It will be made up of a header, body, and footer. In subsequent post will expand on our card component and create a couple fancier forms of cards using animations.
Prerequisites
- Install Node
- Install Angular CLI
- Creating an Angular Component Library - Workspace Setup
- OR Clone this github repository
Generating Our Card Component
For our card we are going to need a module and a component.
ng generate module components/card --project=foo
ng generate component components/card --project=foo
We are also going to generate three additional components.  These components are simply wrappers around an <ng-content> tag.  There won’t be any logic or styling so we will generate them with an inline style and template.
ng generate component components/card/card-body --project=foo --inlineStyle=true --inlineTemplate=true --flat=true
ng generate component components/card/card-footer --project=foo --inlineStyle=true --inlineTemplate=true --flat=true
ng generate component components/card/card-header --project=foo --inlineStyle=true --inlineTemplate=true --flat=true
Now we can update our module to export our components to make them accessible to other modules importing our card 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';
@NgModule({
  declarations: [
    CardComponent,
    CardBodyComponent,
    CardFooterComponent,
    CardHeaderComponent,
  ],
  imports: [
    CommonModule
  ],
  exports: [
    CardComponent,
    CardBodyComponent,
    CardFooterComponent,
    CardHeaderComponent
  ]
})
export class CardModule { }
We are also going to need an enum value to model our different card types.
touch projects/foo/src/lib/components/card/card-type.enum.ts
And enum will look something like this…
export enum CardType {
  BLANK = 'card-blank',
  DEFAULT = 'card-default',
  PRIMARY = 'card-primary',
  SECONDARY = 'card-secondary',
  SUCCESS = 'card-success',
  INFO = 'card-info',
  WARNING = 'card-warning',
  DANGER = 'card-danger'
}
We will have quite a few different card styles.  A BLANK card will simple be a blank white card, while the others will follow our color scheme we already setup.  We will need to update our _variables.scss file with a couple addtional values as well, the default color and card background color.
// COLORS
$default: #AAAAAC;
$primary: #3B4D63;
$secondary: #B676BC;
$success: #5aaa95;
$info: #61adb5;
$warning: #d3c160;
$danger: #db9c7c;
// CONTAINERS
$border-radius: 4px;
$card-background: #FFF;
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';
Creating Our Card Child Components
In the previous step, I had us generate three components that essentially act as a wrapper around an <ng-content> element.  We generated these with inline styles and inline templates.  We are going to update our template to render an <ng-content> element and thats its.  There will be no styles or logic.  We could have chosen to use a directive for these as well and would have had a similar effect, but I find using components with element selectors easier to read.
Lets update our card-body component…
import { Component } from '@angular/core';
@Component({
  selector: 'foo-card-body',
  template: `<ng-content></ng-content>`
})
export class CardBodyComponent {}
Our card-footer component…
import { Component } from '@angular/core';
@Component({
  selector: 'foo-card-footer',
  template: `<ng-content></ng-content>`
})
export class CardFooterComponent {}
And finally our card-header component…
import { Component } from '@angular/core';
@Component({
  selector: 'foo-card-header',
  template: `<ng-content></ng-content>`
})
export class CardHeaderComponent {}
Creating Our Card Component
Our root card component is very simple.  Our card is essentially a dummy component with no logic and a single @Input to configure the card type.  By default going to set the type to blank.  The major part of the card component is the styling.
For our HTML will use a single div with nested ng-content elements. The content elements will select our card-header, card-body, and card-footer components we created.
Lets update our card component’s HTML file…
<div class="card" [ngClass]="type">
  <ng-content select="foo-card-header"></ng-content>
  <ng-content select="foo-card-body"></ng-content>
  <ng-content select="foo-card-footer"></ng-content>
</div>
Our card styles…
@import '../_shared/scss/variables';
@import '../_shared/scss/mixins';
:host ::ng-deep .card {
  box-sizing: border-box;
  background: $card-background;
  display: flex;
  flex-direction: column;
  height: 100%;
  @include box-shadow;
  
  foo-card-header,
  foo-card-body,
  foo-card-footer {
    padding: 1rem;
  }
  foo-card-body {
    flex-grow: 1;
  }
  &.card-default {
    foo-card-header {
      color: $card-background;
      background: $default;
    }
  }
  &.card-primary {
    foo-card-header {
      color: $card-background;
      background: $primary;
    }
  }
  &.card-secondary {
    foo-card-header {
      color: $card-background;
      background: $secondary;
    }
  }
  &.card-success {
    foo-card-header {
      color: $card-background;
      background: $success;
    }
  }
  &.card-info {
    foo-card-header {
      color: $card-background;
      background: $info;
    }
  }
  &.card-warning {
    foo-card-header {
      color: $card-background;
      background: $warning;
    }
  }
  &.card-danger {
    foo-card-header {
      color: $card-background;
      background: $danger;
    }
  }
}
We need to create one additional file for our card component.  If you look at our SCSS you will see an additional @import at the top and an @include under the .card class for a box-shadow.  We are going to create a mixin scss file so we can reuse some of these mixin values.
We will create our _mixins.scss file in the same directory as our _variables.scss file…
touch projects/foo/src/lib/components/_shared/scss/_mixins.scss
And our box-shadow 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);
}
Finally, our card component class file…
import { Component, OnInit, Input } from '@angular/core';
import { CardType } from './card-type.enum';
@Component({
  selector: 'foo-card',
  templateUrl: './card.component.html',
  styleUrls: ['./card.component.scss']
})
export class CardComponent implements OnInit {
  @Input()
  public type: CardType;
  constructor() {
    this.type = CardType.BLANK;
  }
  ngOnInit(): void {
  }
}
Using Our Card Component
Lets update our demo application to use our card component.
<div style="width: 500px; margin: 2rem;">
  <foo-card [type]="CardType.BLANK">
    <foo-card-header>
      <h1>Hello</h1>
    </foo-card-header>
    <foo-card-body>
      <p>Testing</p>
    </foo-card-body>
    <foo-card-footer>
      <p>Footer</p>
    </foo-card-footer>
  </foo-card>
</div>
<div style="width: 500px; margin: 2rem;">
  <foo-card [type]="CardType.DEFAULT">
    <foo-card-header>
      <h1>Hello</h1>
    </foo-card-header>
    <foo-card-body>
      <p>Testing</p>
    </foo-card-body>
    <foo-card-footer>
      <p>Footer</p>
    </foo-card-footer>
  </foo-card>
</div>
<div style="width: 500px; margin: 2rem;">
  <foo-card [type]="CardType.PRIMARY">
    <foo-card-header>
      <h1>Hello</h1>
    </foo-card-header>
    <foo-card-body>
      <p>Testing</p>
    </foo-card-body>
    <foo-card-footer>
      <p>Footer</p>
    </foo-card-footer>
  </foo-card>
</div>
Our app component class file…
import { Component, OnInit } from '@angular/core';
import { CardType } from 'foo';
@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {
  public CardType = CardType;
  constructor() {}
  ngOnInit() {
  }
}
And update our app module to import our card module from our library…
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 simply build our library and start our demo application
npm run build
npm start
Stackblitz Result
I’ve also embeded a Stackblitz showing our cards with our dashabord layout we create a few posts back. I’ve added a card for each card type we created.
The completed github repository can be found here
More Posts
- Creating an Angular Component Library - Workspace Setup
- Creating an Angular Component Library - Alert Component
- Creating an Angular Component Library - Progress Bar Component
- Creating an Angular Component Library - Toaster Component
- Creating an Angular Component Library - Card Component
- Creating an Angular Component Library - Flip Card Component
- Creating an Angular Component Library - Overlay Loader Component
- Creating an Angular Component Library - Overlay Side Panel Component
- Creating an Angular Component Library - Button Component
- Creating an Angular Component Library - Toggle Switch Component
- Creating an Angular Component Library - Checkbox Component