Search code examples
javascriptangularjsangularjs-slider

Why does AngularJS Slider (rzslider) not update correctly when the slider options are updated inside a watch expression?


I have an rzslider that is set to default values.

HTML:

<div id="slider">
    <rzslider class="with-legend"
              rz-slider-model="slider.value"
              rz-slider-options="slider.options"> 
    </rzslider>
</div>

Javascript:

$scope.slider = {
    this.value = 0;
    this.options = {
        floor: 0,
        ceil: 0
}

Inside a Watch Expression, there is code that updates the slider's options:

$scope.$watch('foo', function () {
    $scope.slider.value = 0;
    $scope.slider.options.floor = 0;
    $scope.slider.options.ceil = foo.length;
}

After the code inside the watch expression is run, I expect the slider's ceiling option to change. However, even though the values of $scope.slider have been updated correctly, the change does not appear on the slider itself.

I have tried to re-render the slider as documented on the AngularJS Slider Github page, but it doesn't fix the issue.

$scope.refreshSlider = function() {
  $timeout(function() {
    $scope.$broadcast('rzSliderForceRender')
  })
}

Solution

  • After quite some digging, we found that this was a timing issue. Note that this is only a problem in the first angular digest cycle when the rzslider is set to the default value and updated by the watch expression in the same digest cycle.

    The combination of the rzslider update options code and the angular watch expression code causes the rzslider to miss the update in the first digest cycle.

    This is the rzslider update options code:

    this.scope.$watch('rzSliderOptions()', function(newValue, oldValue) {
        if (newValue === oldValue)
            return;
        self.applyOptions(); // need to be called before synchronizing the values
        self.syncLowValue();
        if (self.range)
            self.syncHighValue();
        self.resetSlider();
    }, true);
    

    This is the angular watch expression code that calls the rzslider update options code:

     watch.fn(value, ((last === initWatchVal) ? value : last), current);
    

    In the first digest loop last equals initWatchVal, because (according to angular) there has been no change. Determining oldValue

    This means that both the newValue and the oldValue in the rzslider code contain the updated version of slider options. When these values are compared, the function returns before ever calling self.applyOptions();, which would have updated the slider.

    There are two possible solutions:

    1. Update the slider options after the first digest loop has finished running. This can be done using a timeout or adding the update code to a button-click event.

    2. Render the slider after the slider options have already been updated. We achieved this by putting the rzslider inside an ng-if. The rzslider is created with the updated values from the start when we show the element.