How do you Bind to Angular-UI's Carousel Slide

2020-01-29 04:39发布

问题:

I'm using Angular-UI's carousel and I need to tell my google charts to redraw after they have slid into view. In spite of what I've read, I can't seem to hook into the event.

See my attempt: http://plnkr.co/edit/Dt0wdzeimBcDlOONRiJJ?p=preview

HTML:

<carousel id="myC" interval="myInterval">
  <slide ng-repeat="slide in slides" active="slide.active">
    <img ng-src="{{slide.image}}" style="margin:auto;">
    <div class="carousel-caption">
      <h4>Slide {{$index}}</h4>
      <p>{{slide.text}}</p>
    </div>
  </slide>
</carousel>

On document load:

$('#myC').live('slid.bs.carousel', function (event) { console.log("slid"); } );

It should work something like this: http://jsfiddle.net/9fwuq/ - non-angular-ui carousel

Perhaps there is a more Angular way to hook into the fact that my chart has slid into view?

回答1:

There are 3 ways I can think of and that depends of your requirement.

Please see http://plnkr.co/edit/FnI8ZX4UQYS9mDUlrf6o?p=preview for examples.

  1. use $scope.$watch for an individual slide to check if it is become active.

    $scope.$watch('slides[0].active', function (active) {
      if (active) {
        console.log('slide 0 is active');
      }
    });
    
  2. use $scope.$watch with custom function to find an active slide.

    $scope.$watch(function () {
      for (var i = 0; i < slides.length; i++) {
        if (slides[i].active) {
          return slides[i];
        }
      }
    }, function (currentSlide, previousSlide) {
      if (currentSlide !== previousSlide) {
        console.log('currentSlide:', currentSlide);
      }
    });
    
  3. use a custom directive to intercept select() function of the carousel directive.

    .directive('onCarouselChange', function ($parse) {
      return {
        require: 'carousel',
        link: function (scope, element, attrs, carouselCtrl) {
          var fn = $parse(attrs.onCarouselChange);
          var origSelect = carouselCtrl.select;
          carouselCtrl.select = function (nextSlide, direction) {
            if (nextSlide !== this.currentSlide) {
              fn(scope, {
                nextSlide: nextSlide,
                direction: direction,
              });
            }
            return origSelect.apply(this, arguments);
          };
        }
      };
    });
    

    and use it like this:

    $scope.onSlideChanged = function (nextSlide, direction) {
        console.log('onSlideChanged:', direction, nextSlide);
    };
    

    and in html template:

    <carousel interval="myInterval" on-carousel-change="onSlideChanged(nextSlide, direction)">
    ...
    

Hope this help : )



回答2:

AngularUI Bootstrap has changed naming conventions for controllers as thery have prefixed all of their controllers with prefix uib, so below is the updated solution of the original solution provided by runTarm:

Angular:

.directive('onCarouselChange', function($parse) {
    return {
        require: '^uibCarousel',
        link: function(scope, element, attrs, carouselCtrl) {
            var fn = $parse(attrs.onCarouselChange);
            var origSelect = carouselCtrl.select;
            carouselCtrl.select = function(nextSlide, direction, nextIndex) {
                if (nextSlide !== this.currentSlide) {
                    fn(scope, {
                        nextSlide: nextSlide,
                        direction: direction,
                        nextIndex: this.indexOfSlide(nextSlide)
                    });
                }
                return origSelect.apply(this, arguments);
            };
        }
    };
});

Angular with TypeScript:

module App.Directive {

    export class CarouselChange implements ng.IDirective {

        public require: string = '^uibCarousel';

        constructor(private $parse: ng.IParseService) { }

        public link: ng.IDirectiveLinkFn = (scope: ng.IScope, element: ng.IAugmentedJQuery, attributes: any, carouselCtrl: any) => {
            var fn = this.$parse(attributes.carouselChange);
            var origSelect = carouselCtrl.select;
            carouselCtrl.select = function(nextSlide, direction) {
                if (nextSlide !== this.currentSlide) {
                    fn(scope, {
                        nextSlide: nextSlide,
                        direction: direction
                    });
                }
                return origSelect.apply(this, arguments);
            };
        }

        static Factory(): ng.IDirectiveFactory {
            var directive: ng.IDirectiveFactory = ($parse: ng.IParseService) => new CarouselChange($parse);
            directive['$inject'] = ["$parse"];
            return directive;
        }
    }
}

Thanks,



回答3:

Following the answer given by runTarm If you want to know the index of the next slide, you should add something like this:

 .directive('onCarouselChange', function ($parse) {
    return {
      require: 'carousel',
          link: function (scope, element, attrs, carouselCtrl) {
            var fn = $parse(attrs.onCarouselChange);
            var origSelect = carouselCtrl.select;
            carouselCtrl.select = function (nextSlide, direction,nextIndex) {
      if (nextSlide !== this.currentSlide) {
        fn(scope, {
          nextSlide: nextSlide,
          direction: direction,
          nextIndex:this.indexOfSlide(nextSlide)
        });
      }
      return origSelect.apply(this, arguments);
    };
  }
  };
})

Then, in the controller you just need to do this to catch the new index:

$scope.onSlideChanged = function (nextSlide, direction, nextIndex) {
    console.log(nextIndex);
}


回答4:

I managed to modify runTarm's answer so that it calls the callback once the slide has finished sliding into view (i.e. the sliding animation has finished). Here's my code:

.directive('onCarouselChange', function ($animate, $parse) {
    return {
        require: 'carousel',
        link: function (scope, element, attrs, carouselCtrl) {
            var fn = $parse(attrs.onCarouselChange);
            var origSelect = carouselCtrl.select;
            carouselCtrl.select = function (nextSlide, direction) {
                if (nextSlide !== this.currentSlide) {
                    $animate.on('addClass', nextSlide.$element, function (elem, phase) {
                        if (phase === 'close') {
                            fn(scope, {
                                nextSlide: nextSlide,
                                direction: direction,
                            });
                            $animate.off('addClass', elem);
                        }
                    });
                }
                return origSelect.apply(this, arguments);
            };
        }
    };
});

The secret lies in using $animate's event handler to call our function once the animation is finished.



回答5:

here's an alternate method that uses controllers, somewhere between runTarm's #2 and #3.

original HTML + a new div:

<carousel id="myC" interval="myInterval">
  <slide ng-repeat="slide in slides" active="slide.active">
    <div ng-controller="SlideController"> <!-- NEW DIV -->
      <img ng-src="{{slide.image}}" style="margin:auto;">
      <div class="carousel-caption">
        <h4>Slide {{$index}}</h4>
        <p>{{slide.text}}</p>
      </div>
    </div>
  </slide>
</carousel>

the custom controller:

  .controller('SlideController',
    function($log, $scope) {
      var slide = $scope.slide;
      $scope.$watch('slide.active', function(newValue) {
        if (newValue) {
          $log.info("ACTIVE", slide.id);
        }
      });
    });


回答6:

And if you just want to start playing a video when the slide comes into view, and pause when it leaves:

JS

{# Uses angular v1.3.20 & angular-ui-bootstrap v0.13.4 Carousel #}
{% addtoblock "js" %}<script type="text/javascript">
angular.module('videoplay', []).directive('videoAutoCtrl', function() {
  return {
    require: '^carousel',
    link: function(scope, element, attrs) {
      var video = element[0];
      function setstate(visible) {
        if(visible) {
          video.play();
        } else {
          video.pause();
        }
      }
      // Because $watch calls $parse on the 1st arg, the property doesn't need to exist on first load
      scope.$parent.$watch('active', setstate);
    }
  };
});
</script>{% endaddtoblock %}
{% addtoblock "ng-requires" %}videoplay{% endaddtoblock %}

NOTE: Has additional bits for Django

HTML:

<carousel interval="15000">
  <slide>
   <video class="img-responsive-upscale" video-auto-ctrl loop preload="metadata">
    <source src=
...