Idrees Khan
Idrees Khan

Reputation: 7752

jQuery Plugins Doesn't Work Well with Angularjs

i have used jQuery slider in my project in which i am loading images using angular. My current view looks like this;

        <div id="slides">
            <div class="slides_container">
                <a href="" data-ng-repeat="image in gallery">
                    <img data-ng-src="{{image.imageUrl}}" width="919" height="326" alt="" />
                </a>
            </div>
        </div>

the controller;

blogApp.controller("HomeController", ['$scope', '$resource', function ($scope, $resource) {
    var basePath = '/api/',
        FrontGallery = $resource(basePath + 'gallery?culture=en-US'),

    $scope.gallery = FrontGallery.query();
}]);

And the jQuery slider code (to make it a slider. I'm using this plugin http://archive.slidesjs.com/)

$(document).ready(function () {
    $('#slides').slides({
        preload: true,
        preloadImage: '/content/images/theme/loading.gif',
        play: 5000,
        pause: 2500,
        hoverPause: true
    });
});

When, i try this code, my all images are loaded from database (debugged it through firebug) but jQuery slider isn't applying the animation or sliding effect to it
And when i remove the data-ng-repeat="image in gallery and use Some static content i.e images, i get the sliding effect back.
What's the problem here. I think Angular is manipulating some how my DOM. That's why i'm getting this problem whenever i put i.e use some angular attributes on my slider.
Note: i have the same issue with jQuery news i.e jQuery InnerFade plugin http://medienfreunde.com/lab/innerfade/
Here is how the innerFade is used;

<h1>
    <img src="/content/images/theme/hotnews.png" alt="" />Hot News</h1>
<ul id="news">
    <li class="ng-repeat:headline in news">
        <span class="ng-bind:headline.description"></span>
        <a href="#" title="Read More">» more</a>
    </li>
</ul>

and the controller;

var HotNews = $resource(basePath + 'article/hotnews?culture=en-US');
$scope.news = HotNews.query();

How do i fix these problems?
Update 2: Here is my routes;

// declare a module
var blogApp = angular
    .module('blogApp', ['ngResource', 'ngSanitize'])
    .config(['$routeProvider', '$locationProvider', function($routeProvider, $locationProvider) {
        //$locationProvider.html5Mode(true);
        //configure the routes
        $routeProvider
            .when('/', {
                // list the home page
                templateUrl: "tmpl/home.html",
                controller: "HomeController"
            })
            .when('/article/:id', {
                // access custom param
                foo: "hello world",
                // list the home page
                templateUrl: "tmpl/article.html",
                controller: "ArticleController"
            })
            .when('/404', {
                template: '<h1>404 Not Found!</h1>'
            })
            .otherwise({ redirectTo: '/404' });
    }]);

Solution #1: Temporary, (As @Jason Goemaat answered) works for me. So, here is what i have done. I didn't create any directive but straightly i injected the $timeout to my controller and then i did the following;

$timeout(function () {
    jQuery('#news').innerfade({
        animationtype: 'slide',
        speed: 750,
        timeout: 2000,
        type: 'random',
        containerheight: '1em'
    });
    jQuery('#slides').slides({
        preload: true,
        preloadImage: 'image/theme/loading.gif',
        play: 5000,
        pause: 2500,
        hoverPause: true
    });

}, 100);

And it did work for both i.e for Slider and for InnerFade

Upvotes: 8

Views: 13609

Answers (5)

Marian Turchyn
Marian Turchyn

Reputation: 559

as it was said: "The jQuery slider script should only be activated after angular has loaded the content" So you can wait before it will be loaded, and then your jQuery code will start running.

$(document).ready(function () {
setTimeout(function(){
      $('#slides').slides({
        preload: true,
        preloadImage: '/content/images/theme/loading.gif',
        play: 5000,
        pause: 2500,
        hoverPause: true
     });
}, 10);
});

Upvotes: 1

Jason Goemaat
Jason Goemaat

Reputation: 29234

Angular IS manipulating your dom, the ng-repeat directive is creating the elements when it has a list to work from. Because of that you need to call .slides after angular is done creating the elements because that plugin is altering the DOM itself.

Here's a directive you can use to call $.slides on an element based on when an array has length > 0. It shows using extra attributes like 'start' to modify the call to slides too. Here it checks an array on your $scope called 'images' and calls $timeout() during the link phase to call .slidesjs() when angular is done creating the DOM. I didn't know what plugin you used so I found one called 'slidesjs that looked similar. 'Here's a plunker: http://plnkr.co/edit/4qdUctYMIpd8WqEg0OiO?p=preview

Html:

<div my-slides="images" start="4">
  <img ng-repeat="image in images" ng-src="{{image.url}}" />
</div>

Directive:

app.directive('mySlides', function() {
  var directive = {
    restrict: 'A',
    link: function(scope, element, attrs, ctrl) {
      scope.$watch(attrs.mySlides, function(value) {
        setTimeout(function() {
          // only if we have images since .slidesjs() can't
          // be called more than once
          if (value.length > 0) {
            $(element[0]).slidesjs({
              preload: true,
              preloadImage: '/content/images/theme/loading.gif',
              play: attrs.play || 5000,
              pause: attrs.pause || 2500,
              start: attrs.start || 1,
              hoverPause: attrs.hoverPause || true,
              navigation: { active: true, effect: "slide" }
            });
          }
        }, 1);
      });
    }
  };
  return directive;
});

One thing to note is that it still can only be called once, so if your image list gets updated it won't work. To do that you could create the content in the directive...

Upvotes: 6

Fresheyeball
Fresheyeball

Reputation: 30015

 $(document).ready(function () {

is firing before angular has loaded the content onto the page. The jQuery slider script should only be activated after angular has loaded the content. Part of the problem here is that angular does not provide a callback for $digest so you don't have an event to listen for.

The content load -> event callback -> ui build, while common in jQuery and standard javascript thinking, its not part of the angular mentality. Luckily angular has an insanely powerful method for handling this problem.

What you need is to turn the jQuery slideshow script into an angular directive. Then it will become part of the angular process space and get instantiated as a part of the angular digest cycle.

Something like

   <div id="slides" my-slideshow >
        <div class="slides_container">
            <a href="" data-ng-repeat="image in gallery">
                <img data-ng-src="{{image.imageUrl}}" width="919" height="326" alt="" />
            </a>
        </div>
    </div>

Good luck!


Happy example time using innerfade (I have not tested this yet, will do ASAP). First your application must declare an angular module if it doesn't already. ng-app="myModule". http://docs.angularjs.org/api/angular.module

(make sure your controller is attached as well!)

Now you can declare a directive on that module. Which is super cool.

angular.module('myModule').directive("innerFade", function () {
   var res = {
     restrict : 'C',
     link     : function (scope, element, attrs) {
           scope.$watch(attrs.innerFade, function(){             
               element.innerfade({
                  speed: 'slow',
                  timeout: 1000,
                  type: 'sequence',
                  containerheight: '1.5em'
               });
           });
        }
     };
  return res;
});

The way this works is pretty simple. The $watch function will monitor the part of scope containing your data. When that changes (including initialization), it will fire the innerfade jQuery function on the element with the directive. We are also passing 'C' to the restrict argument so this custom directive will be a class only directive (I noticed you seem to like that).

 <ul class="inner-fade:innerData" >
      <li class="ng-repeat:fadeItem in innerData">{{fadeItem}}</li>
 </ul>

Now all you have to do is bind the data to $scope.innerData in your controller (bind it any way you like including ajax), and it will just work. You can even change the data on $scope.innerData and it will update with full jQuery glory.

Upvotes: 9

David Nguyen
David Nguyen

Reputation: 8508

Add a directive to #slides

app.directive("slideDirective", function () {
     return function (scope, element, attrs) {
         // Dom
         element.slides({
             preload: true,
             preloadImage: '/content/images/theme/loading.gif',
             play: 5000,
             pause: 2500,
             hoverPause: true
         });
     }
}

Upvotes: 1

ddeloy
ddeloy

Reputation: 27

A few thoughts
1) My guess is that there is something sequentially wrong with your main app page. Do you have jQuery include after angularjs? I assume you are using ng-repeat directive?

2) change $ to jQuery

3) place document.ready script in main page ... also consider replacing it with

 window.onload=function(){} 

I am sure I can debug if you can show code for main page as well as the view.

Upvotes: 1

Related Questions