Creating an Angular Component Library - Toggle Switch Component


A common recurring component that is used quite often are toggle switches. A toggle switch functions similar to a checkbox, where we essentially have two state, on/of, checked/unchecked, etc. This will be one of many form controls we will create for our library.

We are also going to learn about Angular’s ControlValueAccessor interface and see how we can create custom form controls to work with both template driven and reactive forms.

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

 

Prerequisites

 

Generating Our Toggle Switch

Our toggle switch will consist of a module, component, and several enum values for configuration.

Lets generate our module and component….

ng generate module components/toggle-switch --project=foo
ng generate component components/toggle-switch --project=foo

As always, we will need to update our module file to export our component in order to make it accessible to importing modules.

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ToggleSwitchComponent } from './toggle-switch.component';

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

Aside from our module and component, we will continue using enum values for our configurable inputs.

We will create enums for color, shape and size.

ng generate enum components/toggle-switch/toggle-switch-color --project=foo
ng generate enum components/toggle-switch/toggle-switch-shape --project=foo
ng generate enum components/toggle-switch/toggle-switch-size --project=foo

And we will also break out our SCSS into smaller files to keeps things more readable.

touch projects/foo/src/lib/components/toggle-switch/_toggle-switch-colors.scss \
    projects/foo/src/lib/components/toggle-switch/_toggle-switch-shapes.scss \
    projects/foo/src/lib/components/toggle-switch/_toggle-switch-sizes.scss

And finally, we need to update our components directory’s public_api.ts file…

export * from './toggle-switch/toggle-switch-color.enum';
export * from './toggle-switch/toggle-switch-shape.enum';
export * from './toggle-switch/toggle-switch-size.enum';
export * from './toggle-switch/toggle-switch.component'
export * from './toggle-switch/toggle-switch.module';

 

Creating Our Enum Values

First thing we should do is create our enum values that map to our CSS classes. Our colors enum will look something like the following…

export enum ToggleSwitchColor {
  PRIMARY = 'toggle-switch-color-primary',
  SECONDARY = 'toggle-switch-color-secondary',
  SUCCESS = 'toggle-switch-color-success',
  INFO = 'toggle-switch-color-info',
  WARNING = 'toggle-switch-color-warning',
  DANGER = 'toggle-switch-color-danger'
}

Our shapes enum…

export enum ToggleSwitchShape {
  SQUARED = 'toggle-switch-shape-squared',
  ROUNDED = 'toggle-switch-shape-rounded',
  PILLED = 'toggle-switch-shape-pilled'
}

And our sizes enum…

export enum ToggleSwitchSize {
  SMALL = 'toggle-switch-small',
  MEDIUM = 'toggle-switch-medium',
  LARGE = 'toggle-switch-large'
}

 

Creating Our Toggle Switch Component

Our component will be a little different from our previous components we’ve created. We want our component to work as if it was a native form control. Angular provides us an interface, ControlValueAccessor, that our component will need to implement in order for it to act as a form control in the Angular framework.

If you unfamiliar with interfaces, you can think of an interface as a contract. By implementing an interface you’re essentially signing a contract saying that the implementing class will provide all required attributes/methods of the interface. This will guarentee that our component class has the necessary attributes/methods to work with Angular’s forms API. The ControlValueAccessor interface requires us to implement the following….

  1. writeValue()

    • When a value changes in a form programatically, Angular’s forms API will call this method to write a new value to our customer form control.
  2. registerOnChange()

    • Registers a callback function that we call to notify the forms API that a change occured.
  3. registerOnTouch()

    • Registers a callback function that we call to notify the forms API that our control has been touched.
  4. setDisabledState() Optional

    • A method called by the forms API to notify our control component that the control should be disabled.

Angular documentation for ControlValueAcessor

 

First we will start with a basic HTML template…

<div class="toggle-switch" [ngClass]="classes">
  <label 
    (click)="toggleChecked()" 
    [ngClass]="{ 'checked' : isChecked, 'disabled': disabled }">
  </label>
</div>

And our component class file…

import { Component, Input, forwardRef } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';

import { ToggleSwitchColor } from './toggle-switch-color.enum';
import { ToggleSwitchShape } from './toggle-switch-shape.enum';
import { ToggleSwitchSize } from './toggle-switch-size.enum';


@Component({
  selector: 'foo-toggle-switch',
  templateUrl: './toggle-switch.component.html',
  styleUrls: ['./toggle-switch.component.scss'],
  providers: [     
    {       
      provide: NG_VALUE_ACCESSOR, 
      useExisting: forwardRef(() => ToggleSwitchComponent),
      multi: true     
    }   
  ]
})
export class ToggleSwitchComponent implements ControlValueAccessor {
  @Input()
  public color: ToggleSwitchColor;

  @Input()
  public shape: ToggleSwitchShape;

  @Input()
  public size: ToggleSwitchSize;

  @Input()
  public disabled: boolean;

  @Input()
  public set value(isChecked: boolean) {
    if (!this.disabled){
      this.isChecked = isChecked
      this.onChange(isChecked)
    }
  }
  
  protected isChecked: boolean;

  constructor() {
    this.disabled = false;
    this.color = ToggleSwitchColor.PRIMARY;
    this.shape = ToggleSwitchShape.PILLED;
    this.size = ToggleSwitchSize.MEDIUM;
  }

  public onChange: any = () => {}
  public onTouch: any = () => {}

  public writeValue(value: any): void {
    this.value = value
  }

  public registerOnChange(fn: any): void {
    this.onChange = fn;
  }
  
  public registerOnTouched(fn: any): void {
    this.onTouch = fn;
  }

  public setDisabledState(isDisabled: boolean): void {
    this.disabled = isDisabled;
  }

  public toggleChecked(): void {
    if (!this.disabled) {
      this.isChecked = !this.isChecked;
      this.onChange(this.isChecked);
    }
  }

  public get classes(): string {
    return this.color + ' ' + this.size + ' ' + this.shape;
  }
}

It looks like there is a lot going on in our component class but its mostly just assigning values. Our first five @Input decorators are simply configuration inputs for our toggle switch and our initial state. We also have our ControlValueAccessor interface methods.

Towards the bottom we have our method that toggles the state of our control, toggleChecked, which changes our control’s state and calls the callback function Angular’s form API registered, onChange().

Another thing you will notice with our component class file is that we added a providers array to our @Component decorator. So what does this do?

Well, we implemented the ControlValueAccessor interface but Angular doesn’t know about our custom control yet. Aside from implementing the ControlValueAccessor interface, we still need to register our component with Angular’s DI (Dependency Injection) so Angular knows our component should be treated as a form control. We do this by providing the NG_VALUE_ACCESSOR token with our class as a provider.

You can learn more about providers with this excellent blog post

 

Styling Our Toggle Switch

Now that the functionality is done for our toggle switch, we need to style it to make it look like and actual switch. We’re going to use our _variables.scss file to create multiple color options for our control. We are also going to create three differet sizes (small, mediuam, large) along with three different shapes (squared, rounded, and pilled).

Our components main scss file (`toggle-switch.component.scss) will be fairly minimal…

@import '../_shared/scss/variables';
@import '../_shared/scss/mixins';
@import './toggle-switch-shapes';
@import './toggle-switch-sizes';
@import './toggle-switch-colors';

.toggle-switch {
  display: inline-block;
  @include generate-toggle-switch-colors;
  @include generate-toggle-switch-shapes;
  @include generate-toggle-switch-sizes;

  label {
    display: inline-block;
    background: $default;
    position: relative;
    transition: background 0.2s;
    cursor: pointer;

    &::before {
      display: inline-block;
      position: absolute;
      content: ' ';
      background: $card-background;
      vertical-align: middles;
      top: 3px;
      left: 3px;
      transition: left 0.2s;
      @include box-shadow;
    }

    &.disabled {
      background: lighten($default, 20%);
      &::before {
        box-shadow: none;
      }
    }
  }
}

And we will create mixins to pull in the additional styles into our main scss file, mainly to keep the files short and easy to read.

Our color styles…

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

@mixin create-toggle-switch-color($color) {
  label {
    &.checked {
      &:not(.disabled) {
        background: $color
      }
    }
  }
}

@mixin generate-toggle-switch-colors {
  &.toggle-switch-color-primary {
    @include create-toggle-switch-color($primary);
  }

  &.toggle-switch-color-secondary {
    @include create-toggle-switch-color($secondary);
  }

  &.toggle-switch-color-success {
    @include create-toggle-switch-color($success)
  }

  &.toggle-switch-color-info {
    @include create-toggle-switch-color($info)
  }

  &.toggle-switch-color-warning {
    @include create-toggle-switch-color($warning)
  }

  &.toggle-switch-color-danger {
    @include create-toggle-switch-color($danger)
  }
}

Our shape styles…

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

@mixin create-toggle-switch-shape($radius) {
  label {
    border-radius: $radius;
    &::before {
      border-radius: $radius;
    }
  }
}

@mixin generate-toggle-switch-shapes {
  &.toggle-switch-shape-squared {
    @include create-toggle-switch-shape(0);
  }

  &.toggle-switch-shape-rounded {
    @include create-toggle-switch-shape(4px);
  }

  &.toggle-switch-shape-pilled {
    @include create-toggle-switch-shape(3rem);
  }
}

And finally our size styles…

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

@mixin generate-toggle-switch-sizes {
  &.toggle-switch-size-small {
    label {
      width: 42px;
      height: 22px;
      &::before {
        width: 16px;
        height: 16px;
        line-height: 32px;
      }
      &.checked {
        &::before {
          left: 23px;
        }
      }
    }
  }

  &.toggle-switch-size-medium {
    label {
      width: 62px;
      height: 32px;
      &::before {
        width: 26px;
        height: 26px;
        line-height: 32px;  
      }
      &.checked {
        &::before {
          left: 33px;
        }
      }
    }
  }

  &.toggle-switch-size-large {
    label {
      width: 82px;
      height: 42px;
      &::before {
        width: 36px;
        height: 36px;
        line-height: 32px;  
      }
      &.checked {
        &::before {
          left: 43px;
        }
      }
    }
  }
}

 

Stackblitz Result

Below is an embeded Stackblitz showing our toggle switch being used with our [dashabord layout][14] we create a few posts back. I’ve created two examples, one with our toggle switch being used in a reactive form and the other being used with a template driven form.

A full screen demo can also be viewed here

 

 

The completed github repository can be found here

 


comments powered by Disqus