AngularJs Directive For noUiSlider Plugin

Posted By : Nisheet Sharma | 27-Feb-2018

Slider Directive:

 

1. Define and add the noUiSlider directive.
2. Inject $document and $timeout as dependencies.
We'll use $document to access the input elements, to which we'll bind our slider to.
$timeout will be used to make sure the view is updated accurately in the next digest cycle of angular.

 

        app.directive('noUiSlider', ['$document', '$timeout', function ($document, $timeout) {
    

 

Next, we'll define how to we'll use the directive, ie as an Element or class or Attribute
Here, I am going to define it as an attribute name only.
So, our restrict property will look as follows:

 

            return {
                restrict: 'A', // Restricted to attribute name only
    

 

Then, we'll create an isolated scope, for this directive, using scope property.
Here we specify the variables/values we want to bind to our slider
I have used the shorthand syntax ('=') here.
Like: noUiModel: '=' This is same as doing this: noUiModel: '=noUiModel'
The shorthand syntax can only be used for attributes that have exactly the same name as the variables we specify.
I have used the following attributes:


noUiModel The Model you want to bind to the slider
noUiModelId An optional variable, which can be used to specify the Id of another input element
whose value is to be updated by the slider.
noUiStart The Start value of the slider
noUiMin Minimum Range value
noUiMax Maximum Range values
noUiStep Step value
noUiFormat An optional format object to format the slider values, I have used wNumb plugin for specifying the format

 

                scope: {
                    noUiModel: '=',     // The Model you want to bind to the slider
                    noUiModelId: '=',   // An optional variable, which can be used to specify the Id of another input element whose value is to be updated by the slider.
                    noUiStart: '=',     // The Start value of the slider
                    noUiMin: '=',       // Minimum Range value
                    noUiMax: '=',       // Maximum Range values
                    noUiStep: '=',      // Step value
                    noUiFormat: '='     // An optional format object to format the slider values, I have used wNumb plugin for specifying the format
                },
    

 

Finally, we'll set up our link function, as follows
It should have the following arguments:


scope Scope of element on which our our directive is applied.
element The Element on which our our directive is applied.
attrs List of all the attributes that are defined on the element

 

                link: function (scope, element, attrs) {
        
                    //I am using initialized variable as a flag to know the slider's status
                    var initialized = false;
    

 

First, we'll setup the default options that our slider will have.
Options should be defined as per noUiSlider plugin definition.
Refer, to https://refreshless.com/nouislider/slider-options/ for detailed list of all possible options.


Here, I have used the following:
1. start - To specify the starting value of slider
2. range - To specify the minimum and Maximum limits of the slider
3. step - To specify the step value, by which slider value will increased/decreased based on user interaction.
4. format - To specify a custom format that will be applied to the value, like for currency, '$ 1'
You can use wNumb JavaScript Library for implementing this, as I have done here.

 

                    // Link function continued..
                    //Here we set the default options for the slider
                    var options = {};
                    options.range = { min: 0, max: 1 };
                    options.connect = 'lower';
        
                    function updateOptions() {

                        /*
                            Set the start value, I have prioritized the input as follows:
                            If noUiModel attribute is specified, use that first, else check for noUiStart attribute
                            If neither exists, I have set the default start value to zero.
                        */ 
                        options.start = scope.noUiModel || scope.noUiStart || 0;

                        /* 
                            Similarly, set the range values, min and max only if noUiMin and noUiMax attributes exist.
                            Else set the default to 0 and 1. 
                        */
                        options.range.min = scope.noUiMin || 0;
                        options.range.max = scope.noUiMax || 1;
        
                        /*
                           We need to check noUiSlider options range value, it should satisfy the following condition:
                                Min value should be less than Max value at all times.

                            If they are not I have reset them to zero, alternately you can throw an error/warning to the user instead.
                        */
                        if (options.range.max <= options.range.min) {
                            options.range.min = 0;
                            options.range.max = 1;
                            options.start = 0;
                            if (initialized) element[0].noUiSlider.set(0);
                        }
                        
                        //Set the step value, if noUiStep attribute exists use that, else set it to 1 by default.
                        options.step = scope.noUiStep || 1;

                        //If a format is specified, add that to the options
                        if (attrs.noUiFormat) options.format = scope.noUiFormat;
                    }
        
                    function initialize() { //This Function is used to Initialize the slider.
        
                        //Step 1
                        //Set up noUiSlider options
                        updateOptions();
        
                        //Step 2
                        //Create slider using noUiSlider plugin
                        noUiSlider.create(element[0], options);

                        //Set the initialized flag as true.
                        initialized = true;
        
                        /*
                            Because the value of slider is being updated out of the angular's scope, we have to update the view ourselves.
                            I have used $timeout here, because $timeout makes angular start a new digest cycle, which will then automatically
                            update the view with modified value of slider input.
                            So, add $timeout for updation of view on set event
                        */
                        element[0].noUiSlider.on('set', function (values, handle) {
                            $timeout();
                        });
        
                        //If a model is attached update it on slider value updation
                        if (scope.noUiModel) {
                            element[0].noUiSlider.on('update', function (values, handle) {

                                //If noUiModelId is specified, we'll update that input's value as well, whenever the slider is set.
                                if (attrs.noUiModelId && $document[0].getElementById(attrs.noUiModelId)) {
                                    var input = $document[0].getElementById(attrs.noUiModelId);
                                    input.value = values[0];
                                }
                                //Update the model value, when the slider is modified by user.
                                scope.noUiModel = values[0];
                            });
                        }
                    }
        
                    //This is the destroy function. 
                    //This I have used to destroy the slider, when angular fires $destroy event, to prevent memory leaks.
                    function destroy() {
                        initialized = false;
                        element[0].noUiSlider.destroy();
                    }
        
                    //Call the initialize function to make slider
                    initialize();
        
                    /*
                        If instead of values any scope variables are used to set up the slider, 
                        we have to watch their changes and update the slider too.
                    */

                    //Watch changes in model/object
                    if (scope.noUiModel) {

                        //Because our directive creates an isolated scope, its scope variables are not updated if they are updated in the parent scope.
                        //So, we use the directive's parent scope to watch the changes and update the variables.
                        scope.$parent.$watch(attrs.noUiModel, function (value) {
                            element[0].noUiSlider.set(value);
                        });
                    }
        
                    //This function updates the options object and forwards it to the slider, to show the changes.
                    var update = function (value) {
                        updateOptions();
                        element[0].noUiSlider.updateOptions(options);
                    };
        
                    //Watch changes in step if its a model/object
                    if (scope.noUiStep) {
                        scope.$parent.$watch(attrs.noUiStep, update);
                    }
        
                    //Watch changes in Min if its a model/object
                    if (scope.noUiMin) {
                        scope.$parent.$watch(attrs.noUiMin, update);
                    }
        
                    //Watch changes in Max if its a model/object
                    if (scope.noUiMax) {
                        scope.$parent.$watch(attrs.noUiMax, update);
                    }
        
                    //Watch changes in Format
                    if (scope.noUiFormat) {
                        /*
                            For changes in format we cannot simply update the slider options, 
                            we have to destroy the slider first and then re-initialize it with the updated format.
                        */
                        scope.$parent.$watch(attrs.noUiFormat, function () {
                            destroy(); initialize();
                        });
                    }
        
                    //Destroy element on angular's $destroy event
                    scope.$on('$destroy', destroy);
                }
            }
        }]);
    

 

Sample View:

        Amount Input: <input type="text" name="amount" id="amount" ng-model="amount">
        
        Step: <input id="step" min="1" name="step" ng-model="step" type="number" />

        Slider: 
        <div no-ui-max="20000" no-ui-min="1000" no-ui-model="amount" no-ui-model-id="amount" no-ui-slider="" no-ui-step="step">
        </div>

About Author

Author Image
Nisheet Sharma

Nisheet is a Full Stack Developer (MEAN). He is familiar with C, C++, Java, Html, Css, JavaScript, MySql, MongoDb, AngularJs, NodeJs, ExpressJs.

Request for Proposal

Name is required

Comment is required

Sending message..