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 и веб-разработки в целом, поэтому принял неправильные решения.
Надеюсь, я был полезен.