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 in order to render those images in my main page. I would also like to swipe them like in a carousel. To make that work, I'm trying to use iDangero.us Swiper.
When I pick my images with 3 separated divs, I have no problems. I get my images and then I can normally swipe them as I want. I do it like shown below:
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>
I use Swiper to swipe from one image to another, and it seems to work as it should. It's a jQuery plugin, and you can see some demos at this link.
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;
})
}
}]);
The problem is that when I try to use ng-repeat
instead of 3 separated divs, I can't see them anymore and my Swiper script triggers before they are fully loaded. I have no errors in my console or in my JSON (validated with JSONLint). Below, I added 2 screenshots of my output in both situations.
Working with 3 separated divs:
Not working with ng-repeat:
This is the code where I try to make ng-repeat
work keeping the same controller and the same Swiper script as before:
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')"}
],
In order to get my images loaded before triggering the script, I'm trying to use 2 custom directives.
isLoaded
tells me when the last ng-repeat
element is loaded and sets 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
waits for pageIsLoaded = true;
and loads the 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}}'
}
})
They do not seem to fix my problem. I also can't see console.log('page Is Ready!');
when making the first one.
I'm just having some troubles when I have to trigger a script like Swiper after the page is loaded in order to avoid these kind of problems. My images seem to have no height. I think that the problem is caused by ng-repeat
not fully loading before triggering my script.
What am I doing wrong? Are there better solutions?
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.