JQuery Swiper script для запуска после загрузки элементов Ng-Repeat

Я делаю веб-приложение с помощью AngularJS, jQuery, HTML, CSS и Bootstrap, и я хотел бы выбрать некоторые ссылки на изображение из моего JSON, расположенного на сервере Apache2, и использовать их для рендеринга этих изображений в моя главная страница. Я также хотел бы провести их как в карусели. Чтобы сделать эту работу, я пытаюсь использовать iDangero.us Swiper.

Когда я выбираю свои изображения с тремя разделенными div, у меня нет проблем. Я получаю свои изображения, а затем я могу нормально их пронести, как хочу. Я делаю это, как показано ниже:

main.html:

<div ng-init="initGetRequestMain()">

  <div class="swiper-slide" ng-click="swipers()" 
              style="{{data.background1}}"></div>
  <div class="swiper-slide" ng-click="swipers()" 
              style="{{data.background2}}"></div>
  <div class="swiper-slide" ng-click="swipers()" 
              style="{{data.background3}}"></div>

   <script src="scripts/custom/main/swipers.js"></script>
</div>

Я использую Swiper для перехода от одного изображения к другому и, похоже, работает так, как должно. Это плагин jQuery, и вы можете увидеть некоторые демо в эту ссылку.

Swipers.js:

angular.module('swipers', [])
       .controller('',[ 
        $(document).ready(function (){
           var swiper = new Swiper('.swiper-container',{
           direction: 'horizontal',
           pagination: '.swiper-pagination',
           paginationClickable: true
       })
})]);

Json:

"background1":"background-image: url(images/img1.jpg)",
"background2":"background-image: url(images/img2.jpg)",
"background3":"background-image: url(images/img3.jpg)"

mainController.js:

  myApp.controller('MainController', ["$scope","$http",                
                  function($scope,$http){

      $scope.initGetRequestMain = function(){

       $http.get('http://localhost/main.json').success(function(data){

         $scope.data=data;
       })
      }
  }]);

Проблема заключается в том, что когда я пытаюсь использовать ng-repeat вместо 3 разделенных div, я больше не вижу их, а мои Swiper script запускаются до их полной загрузки. У меня нет ошибок в моей консоли или в моем JSON (проверяется с помощью JSONLint). Ниже я добавил 2 скриншота моего вывода в обеих ситуациях.

Работа с тремя разделенными div:

Работа с 3 разделенными divs

Не работает с ng-repeat:

Не работает с ng-repeat

Это код, в котором я пытаюсь сделать работу ng-repeat, сохраняя один и тот же контроллер и тот же Swiper script, как и раньше:

main.html:

  <div ng-init="initGetRequestMain()">

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

    <script type="text/javascript-lazy" src="scripts/custom/main/swipers.js"></script>
  </div>

mainJson.json:

"slides":[
            {"background":"background-image:url('images/img1.jpg')"},
            {"background":"background-image:url('images/img2.jpg')"},
            {"background":"background-image: url('images/img3.jpg')"}
],

Чтобы загрузить мои изображения перед запуском script, я пытаюсь использовать 2 пользовательских директивы.

isLoaded сообщает мне, когда загружается последний элемент ng-repeat и устанавливает pageIsLoaded = true;:

myApp.directive('isLoaded', function (){

   return{
     scope:true,
     restrict: 'A', //Attribute type
     link: function (scope, elements, arguments){ 

        if (scope.$last === true) {
            scope.pageIsReady = true;
            console.log('page Is Ready!');
         }
     }   
   }
})

gettingTheScript ждет pageIsLoaded = true; и загружает script:

myApp.directive('src', function (){

     return{
       scope:true,
       restrict: 'A', //Attribute type
       link: function (scope, elements, arguments){

            scope.$on(pageIsReady===true, function(){
                   if (attr.type === 'text/javascript-lazy'){

                       scope.scriptLink = arguments.src;
                    }
             })
         },
         replace: true, //replaces our element 
         template: '{{scriptLink}}'
       }
  })   

Они, похоже, не исправляют мою проблему. Я также не вижу console.log('page Is Ready!'); при создании первого.

У меня просто проблемы, когда я должен запускать script как Swiper после загрузки страницы, чтобы избежать подобных проблем. У моих изображений нет высоты. Я думаю, что проблема вызвана ng-repeat не полной загрузкой, прежде чем запускать мой script.

Что я делаю неправильно? Есть ли лучшие решения?

Ответы

Ответ 1

Прежде чем приступить к выполнению этой работы, большинство этих типов плагинов jQuery не работают с angular, так как они вносят изменения в DOM, о которых angular не знает.

Как говорится, трюк откладывает вызов плагина jQuery до тех пор, пока angular не отобразит DOM.

Сначала поставьте плагин swiper в директиву:

.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);
            });

        }
    };
})

Для варианта 1 вам нужна измененная директива isLoaded

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!');
        }
     }   
   }
})

Наконец, ваша разметка (обратите внимание, что изменение от isLoaded до загружено... это, вероятно, причина, по которой вы не видели уведомление).

<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>

Как уже упоминалось, большинство плагинов jQuery, которые выполняют функции карусели, не очень хорошо меняют изменения в DOM (например, новые элементы). Даже с обоими параметрами вы можете увидеть неожиданные вещи, если вы измените свою модель после первого рендеринга ng-repeat. Однако, если ваша модель статична, это должно сработать для вас. Если ваша модель изменится, вы можете захотеть найти более "angular" карусельную директиву.

Ответ 2

Есть еще более простой способ использования этого, запеченного в Swiper.js. Когда вы инициализируете swiper, просто установите для свойства observer значение true. Это заставит Swiper следить за изменениями ваших слайдов, поэтому вам не придется беспокоиться о том, будет ли он работать до того, как все слайды будут добавлены через ng-repeat.

Пример:

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

И из документации Swiper относительно свойства observer:

Установите значение true, чтобы включить Mutation Observer на Swiper и его элементы. В этом случае Swiper будет обновляться (повторно инициализироваться) каждый раз, если вы измените его стиль (например, hide/show) или измените его дочерние элементы (например, добавление/удаление слайдов)

Ответ 3

Я отвечаю на этот старый вопрос, потому что некоторые ребята просили меня сделать это и потому, что я хочу помочь другим разработчикам с этой "смешной" проблемой.

Спустя более года мне удалось решить эту проблему, используя Promises и Factories.

По существу, я сделал Factory, ItemFactory, чтобы получить все необходимые элементы (в данном случае слайды), затем я сделал еще один Factory, SwiperFactory, который использует Promise для запуска этот смешной парень, swipers(). Я назвал все эти методы factory в MainController, который после получения слайдов и запуска script сделал $scope.apply();, чтобы отразить изменения в главном представлении.

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(); можно удалить, это не имеет особого значения.
  • В общем, такой подход хорошо работает с асинхронными задачами

Приведенный выше код выполняет работу, но, пожалуйста, обратите внимание на то, что я говорю ниже

Попробуйте избежать использования jQuery-библиотек, таких как iDangero.us Swiper в проекте AngularJS. Вместо этого найдите библиотеку Angular, которая выполняет ту же работу. Если тогда я был опытным, как сейчас, я бы никогда не использовал его, особенно для вещей $(document).ready. Вместо этого изучите способ Promises или AngularJS. Я был совершенно новым для AngularJS и веб-разработки в целом, поэтому принял неправильные решения.

Надеюсь, я был полезен.