Using slick caroussel inside a template
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.
David Laberge
Javascript, jQuery, AngularJs, KnockoutJs, PHP, MySQL @nonelinkedin
Updated on June 27, 2022Comments
-
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
- The
-
David Laberge almost 10 yearsI 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 almost 10 yearsThanks 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 almost 10 yearsI 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 almost 10 yearsCould 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 almost 10 yearsThis 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 almost 10 yearsThis reinitialize the entire directive and requires yous to switch a magic flag trough
$timeout
, I would not prefer this approach -
miron almost 10 yearsIf 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 almost 10 yearsThis 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 almost 10 yearsThanks @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 almost 10 yearsThe
slickApply
is function I added toangular-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 almost 10 yearsThanks @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 almost 10 yearsInside the
slickApply()
line 45 in angular-slick.js to what doesapply()
makes reference? Hop it is more clear. -
Willem D'Haeseleer almost 10 yearsIt references the function in
script.js
on line54
, that's the function resetting the collection. You can see theslickApply
being added to the$scope
inindex.html
on line16
-
David Laberge almost 10 yearsThanks @WillemD'haeseleer
-
Willem D'Haeseleer almost 10 years@DavidLaberge I added some more explanation on the changes in my post for your convenience.
-
Seeker over 7 yearsThanks this really helped me in understanding the behavior of the slick directive.
-
localhost almost 7 yearsI came across your answer searching for kind of same problem but for angular 4. How Can I use the same concept on Angular 4.