Creating an Angular Component Library - Button Component

Published on

The next component we going to look at building is a button component. Our button component will be an attribute component that provides styling to an existing HTML element.

A full screen demo of what we’re going to build.

 

Prerequisites

 

Generate Our Button

For our button we will need to generate a module, a component, a few different enums for configurations, and a few additional SCSS files for our stylings.

Lets generate our module and component…

ng generate module components/button --project=foo
ng generate component components/button --project=foo --inlineTemplate=true

We need to update our module file to export our button component to make it accessible to the importing modules.

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ButtonComponent } from './button.component';

@NgModule({
  declarations: [
    ButtonComponent
  ],
  imports: [
    CommonModule
  ],
  exports: [
    ButtonComponent
  ]
})
export class ButtonModule { }

We going to make our button configurable with several different styles. Our buttons will be configurable by size, color, shape, and style. We will create enums to model these stylings.

touch projects/foo/src/lib/components/button/button-color.enum.ts \
    projects/foo/src/lib/components/button/button-shape.enum.ts \
    projects/foo/src/lib/components/button/button-size.enum.ts \
    projects/foo/src/lib/components/button/button-style.enum.ts

Aside from the Angular module, component, and enums, we also going to create multiple SCSS files too keep our stylings organized.

touch projects/foo/src/lib/components/button/_button-style-outlined.scss \
    projects/foo/src/lib/components/button/_button-style-solid.scss \
    projects/foo/src/lib/components/button/_button-shape.scss \
    projects/foo/src/lib/components/button/_button-size.scss

Creating all these SCSS files might be overkill but I find SCSS easier to read when the files are small and concise.

Now we need to update our components directory’s public_api.ts file…

export * from './button/button.module';
export * from './button/button.component';
export * from './button/button-shape.enum';
export * from './button/button-size.enum';
export * from './button/button-style.enum';
export * from './button/button-color.enum';

 

Creating Our Enum Values

Our enum values are going to act as mappings to CSS classes. Our button color enum values…

export enum ButtonColor {
  DEFAULT = 'button-color-default',
  PRIMARY = 'button-color-primary',
  SECONDARY = 'button-color-secondary',
  SUCCESS = 'button-color-success',
  INFO = 'button-color-info',
  WARNING = 'button-color-warning',
  DANGER = 'button-color-danger'
}

Our button shape enum values…

export enum ButtonShape {
  SQUARED = 'button-shape-squared',
  ROUNDED = 'button-shape-rounded',
  PILLED = 'button-shape-pilled'
}

Our button size enum values…

export enum ButtonSize {
  SMALL = 'button-size-small',
  MEDIUM = 'button-size-medium',
  LARGE = 'button-size-large'
}

And finally, our buttons style enum values…

export enum ButtonStyle {
  SOLID = "button-style-solid",
  OUTLINED = "button-style-outlined",
}

 

Creating Our Stylings

Since this component essentially acts as an attribute directive, there is quite a lot of styling involved.

Our button shape styles…

@mixin generate-shaped-buttons {
  &.button-shape-squared {
    border-radius: 0;
  }

  &.button-shape-rounded {
    border-radius: 4px;
  }

  &.button-shape-pilled {
    border-radius: 3rem;
  }
}

Our button size styles…

@mixin generate-sized-buttons {
  &.button-size-small {
    font-size: 0.8rem;
    padding: 0.6rem 1rem;
  }

  &.button-size-medium {
    font-size: 1rem;
    padding: 0.8rem 1.3rem;
  }

  &.button-size-large {
    font-size: 1.2rem;
    padding: 1rem 1.5rem;
  }
}

Our outlined button styles…

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

@mixin generate-button-bordered($color, $border) {
  border: 2px $border $color;
  background: $card-background;
  color: $color;
  transition: background 0.3s, color 0.3s, border 0.3s;

  &:hover {
    color: $card-background;
    background: $color;
  }

  &:active {
    border-color: darken($color, 10%);
    background: darken($color, 10%);
    box-shadow: none;
  }

  &:disabled {
    border-color: lighten($color, 15%);
    color: lighten($color, 15%);
    box-shadow: none;
    cursor: not-allowed;
  }
}

@mixin generate-outlined-buttons {
  &.button-style-outlined {
    &.button-color-default {
      @include generate-button-bordered($default, solid)
    }
    &.button-color-primary {
      @include generate-button-bordered($primary, solid)
    }
    &.button-color-secondary {
      @include generate-button-bordered($secondary, solid)
    }
    &.button-color-success {
      @include generate-button-bordered($success, solid)
    }
    &.button-color-info {
      @include generate-button-bordered($info, solid)
    }
    &.button-color-warning {
      @include generate-button-bordered($warning, solid)
    }
    &.button-color-danger {
      @include generate-button-bordered($danger, solid)
    }
  }
}

And our solid button styles…

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

@mixin generate-button-solid($color) {
  background: $color;
  border: 2px solid $color;

  &:hover {
    background: darken($color, 10%);
    border: 2px solid darken($color, 10%);
  }

  &:active {
    background: darken($color, 12%);
    border: 2px solid darken($color, 12%);
    box-shadow: none;
  }

  &:disabled {
    background: lighten($color, 15%);
    border: 2px solid lighten($color, 10%);
    box-shadow: none;
    cursor: not-allowed;
  }
}

@mixin generate-solid-buttons {
  &.button-style-solid {
    &.button-color-default {
      @include generate-button-solid($default);
    }

    &.button-color-primary {
      @include generate-button-solid($primary);
    }
    &.button-color-secondary {
      @include generate-button-solid($secondary);
    }

    &.button-color-success {
      @include generate-button-solid($success);
    }

    &.button-color-info {
      @include generate-button-solid($info);
    }

    &.button-color-warning {
      @include generate-button-solid($warning);
    }

    &.button-color-danger {
      @include generate-button-solid($danger);
    }
  }
}

 

Creating Our Button Component

Our button template will simply be an <ng-content> element which will be replaced by the element that is hosting our button attribute selector.

Since our template is so minimal, we generated our button component with the --inlineTempalte=true option to avoid creating a separate template file.

Our component class file…

import { Component, Input, HostBinding } from '@angular/core';
import { ButtonStyle } from './button-style.enum';
import { ButtonSize } from './button-size.enum';
import { ButtonShape } from './button-shape.enum';
import { ButtonColor } from './button-color.enum';

@Component({
  selector: 'div[fooButton],span[fooButton],a[fooButton],input[fooButton],button[fooButton]',
  template: '<ng-content></ng-content>',
  styleUrls: ['./button.component.scss']
})
export class ButtonComponent {
  @Input()
  public style: ButtonStyle;

  @Input()
  public size: ButtonSize;

  @Input()
  public shape: ButtonShape;

  @Input()
  public color: ButtonColor;

  constructor() {
    this.style = ButtonStyle.SOLID;
    this.size = ButtonSize.MEDIUM;
    this.shape = ButtonShape.ROUNDED;
    this.color = ButtonColor.DEFAULT;
  }

  @HostBinding('class')
  get classes(): string {
    return `${this.style} ${this.size} ${this.shape} ${this.color}`
  }
}

The component class is mostly made up of @Input decorators to configure our button styles. One thing that is new that we haven’t use before is the @HostBinding decorator. This will allow us to dynamically set the class attribute of our host element from our typescript backing file. We put the @HostBinding on a getter that will generate and return a string of our configured values.

And finally our component class SCSS file…

@import '../_shared/scss/variables';
@import '../_shared/scss/mixins';
@import './button-style-solid';
@import './button-style-outlined';
@import './button-size';
@import './button-shape';

:host {
  box-sizing: border-box;
  font-weight: bold;
  margin: 0 1rem 1rem 0;
  display: inline-block;
  color: #FFF;
  outline: 0;
  border: 0;
  cursor: pointer;
  transition: background 0.2s, border 0.2s;
  @include box-shadow;

  @include generate-solid-buttons;
  @include generate-outlined-buttons;
  @include generate-sized-buttons;
  @include generate-shaped-buttons;
}

You will noticed we import all of our separate button styling file where we created mixins. We then include those mixins in our button component SCSS. I think this keeps our SCSS very readable.

Now we can build our library

npm run build

 

Using Our Button Component

To use our button component we simply import our button module into an existing module. We then can use our button attribute selector on existing HTML elements.

<!-- On a div element -->
<div fooButton
  [color]="ButtonColor.PRIMARY"
  [size]="ButtonSize.MEDIUM"
  [style]="ButtonStyle.OUTLINED"
  [shape]="ButtonShape.ROUNDED">Button</div>

<!-- On an input element -->
<input fooButton
    [color]="ButtonColor.DANGER"
    [size]="ButtonSize.SMALL"
    [style]="ButtonSize.SOLID"
    [shape]="ButtonShape.PILLED"
    type="reset"
    value="Clear" />

 

Stackblitz Result

I’ve also embeded a Stackblitz showing our button being used with our [dashabord layout][14] we create a few posts back. Clicking “Show More” on the dashboard page will show you all configuration values. The values can be combined to create any number of look and feels for buttons.

A full screen demo can also be viewed here

 

 

The completed github repository can be found here

 

comments powered by Disqus