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
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 essentialThe 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.
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)
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.