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