Creating an Angular Component Library - Toggle Switch Component
Published on
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.
Prerequisites
- Install Node
- Install Angular CLI
- Creating an Angular Component Library - Workspace Setup
- OR Clone this github repository
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….
-
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.
-
registerOnChange()
- Registers a callback function that we call to notify the forms API that a change occured.
-
registerOnTouch()
- Registers a callback function that we call to notify the forms API that our control has been touched.
-
setDisabledState()
Optional- A method called by the forms API to notify our control component that the control should be disabled.
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.
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