jQuery Swiper script to run after Ng-Repeat elements are loaded

后端 未结 3 1344
甜味超标
甜味超标 2021-02-19 04:36

I\'m making a web app using AngularJS, jQuery, HTML, CSS and Bootstrap, and I would like to pick some image links from my JSON that is located in an Apache2 server and use them

相关标签:
3条回答
  • 2021-02-19 05:27

    I'm answering to this old question because some guys asked me to do it and because I want to help other developers with this "funny" problem.

    After more than one year, I managed to solve this issue using Promises and Factories.

    Essentially, I made a Factory, ItemFactory ensuring to get all the items needed (in this case slides), then, I made another Factory, SwiperFactory, which uses a Promise to trigger that funny guy, swipers(). I called all those factory methods into a MainController, which, after getting the slides and triggering the script, does $scope.apply(); to reflect the changes in the main view.

    Main.html:

    //MainController known as mainCtrl
    <div ng-repeat="slide in mainCtrl.slides">
      <div class="swiper-slide" ng-click="mainCtrl.swipers()"></div>
    </div>
    

    MainController.js:

    angular
    .module('myApp')
    .controller('MainController', MainController);
    
    function MainController($scope, ItemFactory, SwiperFactory) {
    
       var vm    = this;
       //Initializing slides as an empty array
       vm.slides = [];
    
       /** Gets called as soon as the Controller is called **/
       getData('main.json');
    
       function getData(path){
          ItemFactory.getItems(path).then( function(response){
    
            //If response is not empty
            if (response) {
               //Assigning fetched items to vm.items
               vm.slides = response.data;
            }
    
            //Calling triggerSwiper after we ensured fetching item data
            SwiperFactory.triggerSwiper();
    
            //Callig $scope.$apply() to reflect correctly the changes in the view
            $scope.$apply();
          })
       };
    
    }
    

    ItemFactory.js:

    angular
    .module('myApp')
    .factory('ItemFactory', function ($http) {
    
        /** Using this referring as ItemFactory Controller **/
        var vm     = this;
        vm.API_URL = 'http://localhost/';
    
        /** Gets items from API based on jsonPath **/
        function getItems(jsonPath) {
            return $http.get(vm.API_URL + jsonPath
            ).then(function (response) {
                return (response);
            });
        }
    
        //Exposing getItems method
        return {
            getItems : getItems
        }
    });
    

    SwiperFactory.js:

    angular
    .module('myApp')
    .factory('SwiperFactory', function () {
    
        /** Using this referring as SwiperFactory Controller **/
        var vm     = this;
    
        /** Triggers Swiper Script 
         *  Resolving only after swiper has been triggered **/
        function triggerSwiper(){
          return new Promise( function(resolve, reject){
            resolve({
                $(document).ready(function (){
                    var swiper = new Swiper('.swiper-container',{
                    direction           : 'horizontal',
                    pagination          : '.swiper-pagination',
                    paginationClickable : true
                })
            });
    
          }, function(error){
             console.log('Error while triggering Swiper');
          })
    
        }
    
        //Exposing triggerSwiper method
        return {
            triggerSwiper : triggerSwiper
        }
    });
    
    • $scope.$apply(); can be removed, it is not really essential
    • In general, this kind of approach works well with asynchronous tasks

    The code above does the work, but, please, pay attention to what I'm saying below

    Try avoiding using jQuery libraries such as iDangero.us Swiper in an AngularJS project. Instead, look for an Angular library which makes the same work. If back then I was skilled like I'm right now, I would have never used it, expecially for the $(document).ready stuff. Instead, study Promises or AngularJS way of doing that. I was totally new to AngularJS and Web Development in general, so I took wrong decisions.

    I hope I've been helpful.

    0 讨论(0)
  • 2021-02-19 05:37

    There's an even easier way you can use that's baked into Swiper.js. When you're initializing the swiper, just set the observer property to true. This will make Swiper watch for changes to your slides, so you won't have to worry about whether it runs before all the slides are added through ng-repeat.

    Example:

    var galleryTop = new Swiper('.gallery-top', { observer: true });

    And from the Swiper documentation on the observer property:

    Set to true to enable Mutation Observer on Swiper and its elements. In this case Swiper will be updated (reinitialized) each time if you change its style (like hide/show) or modify its child elements (like adding/removing slides)

    0 讨论(0)
  • 2021-02-19 05:41

    Before getting into how to make this work, most of these types of jQuery plugins don't work well with angular since they make modifications to the DOM that angular doesn't know about.

    That being said, the trick is deferring the invocation of the jQuery plugin until after Angular has rendered the DOM.

    First, put your swiper plugin into a directive:

    .directive('swiper', function($timeout) {
        return {
            link: function(scope, element, attr) {
                //Option 1 - on ng-repeat change notification
                scope.$on('content-changed', function() {
                    new Swiper(element , {
                        direction: 'horizontal',
                        pagination: '.swiper-pagination',
                        paginationClickable: true);
                }
                //Option 2 - using $timeout
                $timeout(function(){
                    new Swiper(element , {
                        direction: 'horizontal',
                        pagination: '.swiper-pagination',
                        paginationClickable: true);
                });
    
            }
        };
    })
    

    For Option 1, you need a modified isLoaded directive

    myApp.directive('isLoaded', function (){
    
       return{
         scope:false, //don't need a new scope
         restrict: 'A', //Attribute type
         link: function (scope, elements, arguments){ 
    
            if (scope.$last) {
                scope.$emit('content-changed');
                console.log('page Is Ready!');
            }
         }   
       }
    })
    

    Finally, your markup (note the change from isLoaded to is-loaded....this is probably the reason you weren't seeing the notification).

    <div ng-init="initGetRequestMain()" swiper>
    
       <div ng-repeat="slide in data.slides" is-loaded>
           <div class="swiper-slide" style="{{slide.background}}" 
                       ng-click="swipers()"></div>
       </div>
    </div>
    

    As mentioned, most jQuery plugins that do carousel functionality don't handle changes to the DOM (i.e new elements) very well. Even with both options, you may see unexpected things if you change your model after the ng-repeat is first rendered. However, if your model is static, this should work for you. If your model changes, then you might want to search for a more "angular" carousel directive.

    0 讨论(0)
提交回复
热议问题