Using slick caroussel inside a template

13,464

Solution 1

The reason why this is happening is because the plugin itself drastically modifies the html that is generated by the ngRepeat leaving angular unable to update the repeater as it no longer can identify the structure.

In order to prevent angular from getting confused when you update your repeater you could:

  • Uninitialize the plugin
  • Update your repeater
  • Reinitialize the plugin.

I have a working example of that here:

http://plnkr.co/edit/ZQrDZ3Nr33S95Y4uy1AZ?p=preview

It modifies the angular-slick.js wrapper only slightly by adding a slickApply method so that you can do this easily from in your controller.

Changes:

angular-slick.js

    touchThreshold: '@',
    vertical: '@',
    slickApply: '='  // this is the two way binding that changes the interface
  },
  link: function (scope, element, attrs) {
    var initializeSlick, isInitialized;
    var slider; // slider has been moved from the initializeSlick function to here
    /* 
      This function takes another function as an argument 
       and will reset the slick plugin, call the function, and reload it
    */
    scope.slickApply = function(apply){
        if (isInitialized) {
            slider.unslick();
        }
        apply();
        initializeSlick();
    }
    initializeSlick = function () {
      return $timeout(function () {
        var currentIndex;
        slider = $(element);

index.html

The only thing changed here was the addition of the slick-apply attribute, that references the slickApply function in the $scope

<h1>Hello Plunker!</h1>
<slick init-onload="false" slick-apply='slickApply' data="dataLoaded" slides-to-show="3" dots="true">
  <div ng-repeat="item in items">
    <span>

script.js

The only thing changed here was wrapping the assignment of $scope.items into the $scope.slickApply function.

$scope.messItUp = function(){
    $scope.slickApply(function(){
      $scope.items = [
          {imgSrc: 'http://lorempixel.com/325/325/sports/', label: 'label 1'},
          {imgSrc: 'http://lorempixel.com/325/325/sports/', label: 'label 2'},
          {imgSrc: 'http://lorempixel.com/325/325/sports/', label: 'label 3'},
          {imgSrc: 'http://lorempixel.com/325/325/sports/', label: 'label 4'},
          {imgSrc: 'http://lorempixel.com/325/325/sports/', label: 'label 5'},
          {imgSrc: 'http://lorempixel.com/325/325/sports/', label: 'label 6'},
          {imgSrc: 'http://lorempixel.com/325/325/sports/', label: 'label 7'},
          {imgSrc: 'http://lorempixel.com/325/325/sports/', label: 'label 8'},
          {imgSrc: 'http://lorempixel.com/325/325/sports/', label: 'label 9'},
          {imgSrc: 'http://lorempixel.com/325/325/sports/', label: 'label 10'}
      ]  
    });
}

You could also do all this without modifying the wrapper but that would require you to do awkward jQuery / angular stuff inside your controller, as demonstrated by miron here:

http://plnkr.co/edit/WCEWwgNcIEC0rseaZIO6?p=preview

I think you could definitely improve on the former example, especially by modifying the wrapper so that it can be made aware of angular collections, currently it doesn't do that. But that is obviously a lot more work then the hot fix I'm providing here.

If you are planning on using this slick library more throughout your code, I definitely recommend you fork the git repo so that you can introduce any additional changes to it in a more managed way.

However, since this entire wrapper is literally 1 file with not even 100 lines of code in it, I don't really see any issue with adopting it in your code base directly.
I would however recommend to keep it as coffee script, if you want to ever merge in upstream changes. But the compiled output doesn't look to terrible so it doesn't really mater I guess.

Solution 2

I dont know how your data structure looks like but i tried to recreate your problem as far as I could.

I simplified the data, I use only objects which hold any specific data like the image source and the label text:

$scope.items = [
  {
    imgSrc: 'http://placekitten.com/325/325',
    label: 'label 1'
  }
];

In the loop I only used the attributes of the current item which ng-repeat iterates through:

<div ng-repeat="item in items">
  <span>
    <img ng-src="{{ item.imgSrc }}" alt="{{ item.label}}">
   </span>
   <span>{{ item.label }}</span>
</div>

But now to your main problem. I could recreate it by putting an delay on the data which is loaded. I wrapped the data assignment in an timeout block. I extended the slick element with the init-onload and data attribute:

<slick init-onload="false" data="dataLoaded" slides-to-show="3" dots="true"></slick>

Now when the data is completly loaded, dataLoaded gets true and the carousel gets rendered.

I don't know why but it is important that the dataLoaded variable doesn't get initialized with an initial value like $scope.dataLoaded = false; because than it doesn't work again.

EDIT:

So as you can see, the slick carousel doesn't expose the reinit method to the public api so you have to trick it in order to keep the carousel working even if the elements change:

<slick ng-if="dataLoaded" init-onload="false" data="dataLoaded" slides-to-show="3" dots="true"></slick>

ng-if removes the complete dom strucutre of the slick element so this is exactly what we need here. Now when you update the data you simply wrap the $scope.dataLoaded assignment in an timeout function to put it in the event loop. Thereby angular has time to remove the created old slick carousel structure:

$scope.dataLoaded = false;

$timeout(function(){
  $scope.dataLoaded = true;
});

I prepared an working plunk for you. Let me know if it solves your problem.

Share:
13,464
David Laberge
Author by

David Laberge

Javascript, jQuery, AngularJs, KnockoutJs, PHP, MySQL @nonelinkedin

Updated on June 27, 2022

Comments

  • David Laberge
    David Laberge about 2 years

    Our customer requested that we implement the kenWheeler slick caroussel: http://kenwheeler.github.io/slick/

    Since our project is built with AngularJs we found the following directive that works as a wrapper around the plugin. https://github.com/vasyabigi/angular-slick

    The directive is call inside a template as follow :

    <slick dots="true" slides-to-show=3>
        <div ng-repeat="index in question.getProperty().getDynamicData().getIndexes()">
            <span>
                <img ng-src="{{builtPathToImage(question.getProperty().getDynamicData().getImage($index))}}">
            </span>
            <span>{{question.getProperty().getDynamicData().getLabel($index)}}</span>
        </div>
    </slick>
    

    When the template is called inside the code, the div are created on the fly. The directive is run and the div are display on top of one another.

    EDIT :

    • The div inside the carroussel are created after the page is loaded

    From my research the issue might be loacated in the fact that the directive is "empty" when the page is loaded.

    Plunkr : http://plnkr.co/edit/EdKT8W?p=preview clck the "mess it up" button to recreate the issue.

    END OF EDIT

    A bit more about the context : The project uses require.js Angular bootstrap

    Is there someone outthere that used this deirective inside a template? and if so, could you provide some pointer on how you made it to work.

    Thanks

  • David Laberge
    David Laberge almost 10 years
    I edited the question to make it more precise, I hope. I think the directive works when the HTML is populated before the page is loaded.
  • David Laberge
    David Laberge almost 10 years
    Thanks for the plunk. Altough the issue persist if you add a button to change the url in the scope the caroussel breaks. plnkr.co/edit/EdKT8W?p=preview
  • miron
    miron almost 10 years
    I looked at the public api of the slick carousel and it doesn't expose the reinit method to repaint the carousel so we need to trick it to recreate the slick elements. I update my plunk so take a look: plnkr.co/edit/WCEWwgNcIEC0rseaZIO6?p=preview
  • miron
    miron almost 10 years
    Could be an possible approach but keep in mind that you need to modify the wrapper code which is sometimes not the best solution.
  • Willem D'Haeseleer
    Willem D'Haeseleer almost 10 years
    This wrapper code is very limited and doesn't really add any functionality add all. Modifying it seems like the best thing to do here.
  • Willem D'Haeseleer
    Willem D'Haeseleer almost 10 years
    This reinitialize the entire directive and requires yous to switch a magic flag trough $timeout , I would not prefer this approach
  • miron
    miron almost 10 years
    If on production site you depend on any frontend dependency systems like bower then I suggest that you make an pull request :) For completeness sake you should also set the variable isInitialized to false in the unInitializeSlick method.
  • Willem D'Haeseleer
    Willem D'Haeseleer almost 10 years
    This wrapper is extremely limited and having to go trough the red tape of pull requests seems like over kill here, I would just take ownership of the repo or commit it directly to the code base. It doesn't have a license so there is no problem there.
  • David Laberge
    David Laberge almost 10 years
    Thanks @WillemD'haeseleer, I was getting there in my dev. I'm checking your solution and I'll come back to you. Could you tell me to what refer apply() in slick.angular.js. Thanks again
  • Willem D'Haeseleer
    Willem D'Haeseleer almost 10 years
    The slickApply is function I added to angular-slick.js It resets the plugin, applies the angular transformation you want, and then reloads it again. It is being exposed by a two way binding. Does that answer your question ?
  • David Laberge
    David Laberge almost 10 years
    Thanks @miron I'm currently trying your solution, and willem's, I'll come back to you as soon as I get it to work in my context. Thanks for all your effort
  • David Laberge
    David Laberge almost 10 years
    Inside the slickApply() line 45 in angular-slick.js to what does apply() makes reference? Hop it is more clear.
  • Willem D'Haeseleer
    Willem D'Haeseleer almost 10 years
    It references the function in script.js on line 54, that's the function resetting the collection. You can see the slickApply being added to the $scope in index.html on line 16
  • David Laberge
    David Laberge almost 10 years
    Thanks @WillemD'haeseleer
  • Willem D'Haeseleer
    Willem D'Haeseleer almost 10 years
    @DavidLaberge I added some more explanation on the changes in my post for your convenience.
  • Seeker
    Seeker over 7 years
    Thanks this really helped me in understanding the behavior of the slick directive.
  • localhost
    localhost almost 7 years
    I came across your answer searching for kind of same problem but for angular 4. How Can I use the same concept on Angular 4.