AndreaM16 AndreaM16 - 4 years ago 1016
Javascript Question

jQuery Swiper Script to Run after Ng-Repeat elements are loaded - AngularJS

I have a problem that I wasn't able to solve even looking to other posts on StackOverflow.

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 an Image to another, it seems to work as it should. It's a jQuery plugin, 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 either in my JSON (validated with JSONLint). Below, I add 2 screenshots of my outuput in both situations.

Working with 3 separated divs:

Working with 3 separated divs

Not working with ng-repeat:

Not working with ng-repeat


Below, I paste some 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 can't also 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 loading for full before triggering my script.


What Am I doing wrong? Are there better solutions?

Thanks in Advice, I hope I've explained my problem as best as possible.

Answer Source

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.

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download