Search code examples
javascripthtmlangulardata-bindingbootstrap-4

How to two way data bind to dynamically set slide toggle in Bootstrap?


I am very new to programming and would appreciate any help with SlideToggle data-bindings

I have a JSFiddle link here: https://jsfiddle.net/haLxj1uy/ (code also provided down below)


HTML:

<label class="switch">
    <input type="checkbox" id="togBtn">
    <div class="slider round">
        <span class="on">ON</span>
        <span class="off">OFF</span>
    </div>
</label>

CSS:

.switch {
    position: relative;
    display: inline-block;
    width: 90px;
    height: 34px;
}

.switch input {display:none;}

.slider {
    position: absolute;
    cursor: pointer;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background-color: #ca2222;
    -webkit-transition: .4s;
    transition: .4s;
 }

.slider:before {
  position: absolute;
  content: "";
  height: 26px;
  width: 26px;
  left: 4px;
  bottom: 4px;
  background-color: white;
  -webkit-transition: .4s;
  transition: .4s;
}

input:checked + .slider {
  background-color: #2ab934;
}

input:focus + .slider {
  box-shadow: 0 0 1px #2196F3;
}

input:checked + .slider:before {
  -webkit-transform: translateX(55px);
  -ms-transform: translateX(55px);
  transform: translateX(55px);
}

.on {
  display: none;
}

.on, .off {
  color: white;
  position: absolute;
  transform: translate(-50%,-50%);
  top: 50%;
  left: 50%;
  font-size: 10px;
  font-family: Verdana, sans-serif;
}

input:checked+ .slider .on {
    display: block;
}

input:checked + .slider .off {
    display: none;
}

/* Rounded sliders */
.slider.round {
  border-radius: 34px;
}

.slider.round:before {
  border-radius: 50%;
}

And in my JavaScript, I made a simple function:

test(event) { console.log(event.target.value); }

However, when I slide the toggle on/off, both the console.log's are on (the test function would be fired twice because I would make it go from "Off"-> "On" and "On" -> "Off"). So a couple of question I have are:

  • How to dynamically set a slidetoggle based off a variable(condition)?
  • How to properly get true/false (or on/off) from a slide toggle in JavaScript?

Solution

  • in angular, suggested way of enabling two-way data binding with a native DOM element is to implement ControlValueAccessor interface. by implementing this interface, your native DOM element becomes compatible with angular forms (both template-driven and reactive forms) and can be used with both FormControl as well as ngModel directives.

    here is a good article that shows how ControlValueAccessor fits into the bigger picture.

    and this is the ControlValueAccessor implementation for your slider;

    custom-slide.component.ts

    import { Component, Renderer2, forwardRef, ViewChild, ElementRef } from '@angular/core';
    import { ControlValueAccessor, NG_VALUE_ACCESSOR } from "@angular/forms"
    
    export const CUSTOM_SLIDE_VALUE_ACCESSOR: any = {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => CustomSlideComponent),
      multi: true,
    };
    
    @Component({
      selector: 'my-custom-slide',
      templateUrl: './custom-slide.component.html',
      styleUrls: ['./custom-slide.component.css'],
      providers: [CUSTOM_SLIDE_VALUE_ACCESSOR]
    })
    export class CustomSlideComponent implements ControlValueAccessor {
      @ViewChild("inp", { static: true }) input: ElementRef<HTMLInputElement>;
    
      onChange = (_: any) => { };
    
      onTouched = () => { };
    
      constructor(private _renderer: Renderer2) { }
    
      writeValue(value: any): void {
        this._renderer.setProperty(this.input.nativeElement, 'checked', value);
      }
    
      registerOnChange(fn: (_: any) => {}): void { this.onChange = fn; }
    
      registerOnTouched(fn: () => {}): void { this.onTouched = fn; }
    
      setDisabledState(isDisabled: boolean): void {
        this._renderer.setProperty(this.input.nativeElement, 'disabled', isDisabled);
      }
    }
    

    custom-slide.component.html

    <label class="switch">
      <input #inp type="checkbox" id="togBtn" (change)="onChange($event.target.checked)" (blur)="onTouched()">
      <div class="slider round">
        <span class="on">ON</span>
        <span class="off">OFF</span>
      </div>
    </label>
    

    custom-slide.component.css (same css you provided. nothing changed.)

    .switch {
      position: relative;
      display: inline-block;
      width: 90px;
      height: 34px;
    }
    
    .switch input {display:none;}
    
    .slider {
      position: absolute;
      cursor: pointer;
      top: 0;
      left: 0;
      right: 0;
      bottom: 0;
      background-color: #ca2222;
      -webkit-transition: .4s;
      transition: .4s;
    }
    
    .slider:before {
      position: absolute;
      content: "";
      height: 26px;
      width: 26px;
      left: 4px;
      bottom: 4px;
      background-color: white;
      -webkit-transition: .4s;
      transition: .4s;
    }
    
    input:checked + .slider {
      background-color: #2ab934;
    }
    
    input:focus + .slider {
      box-shadow: 0 0 1px #2196F3;
    }
    
    input:checked + .slider:before {
      -webkit-transform: translateX(55px);
      -ms-transform: translateX(55px);
      transform: translateX(55px);
    }
    
    /*------ ADDED CSS ---------*/
    .on
    {
      display: none;
    }
    
    .on, .off
    {
      color: white;
      position: absolute;
      transform: translate(-50%,-50%);
      top: 50%;
      left: 50%;
      font-size: 10px;
      font-family: Verdana, sans-serif;
    }
    
    input:checked+ .slider .on
    {display: block;}
    
    input:checked + .slider .off
    {display: none;}
    
    /*--------- END --------*/
    
    /* Rounded sliders */
    .slider.round {
      border-radius: 34px;
    }
    
    .slider.round:before {
      border-radius: 50%;}
    

    and you can use it anywhere like this;

    <my-custom-slide [(ngModel)]="slideValue"></my-custom-slide>
    

    finally, here is a working demo >>> https://stackblitz.com/edit/angular-w7n6er