I know this has been asked a thousand time, but I think I've tried every solution I've read and I can't seem to get it to work.
I have an API from which I'm getting my images, after pulling the images into the view I want them to use photoswipe for displaying on mobile.
I have a directive:
'use strict';
App.directive('photoswipe', function () {
return {
replace: false,
restrict: 'A',
link: function photoSwipeLink(scope, element, attr) {
scope.$watch(attr.photoswipe, function(value){
angular.element('#gallery a').photoSwipe({
enableMouseWheel: false,
enableKeyboard: false
});
});
}
};
});
I get this error in the console:
Code.PhotoSwipe.createInstance: No images to passed.
I guess this is because the directive is running before the view has rendered.
If I add a $timeout instead of watch then the code does work. Unfortunately this solution is no good, as there could be a delay in getting the data from API. Also, the code doesn't work within the timeout.
I have tried, the above code, I've tried using $viewcontentloaded, have tried firing function after last element in ng-repeat, but neither work.
Any help would be much appreciated.
EDIT:
Here's the HTML.
<h1>{{ gallery.headline }}</h1>
<ul id="gallery" photoswipe>
<li class="gallery" ng-repeat="image in gallery.images">
<a href="{{ image.url }}" alt="image.caption">
<img ng-src="{{ image.thumb_url }}" class="thumb" alt="{{ image.caption }}" style="display: block;">
<h3 class="headline">{{ image.caption }}</h3>
</a>
</li>
</ul>
I think this is happening because Angular is processing your directive before the results are bound to the list. It is probably a race condition. One way to solve this is to do
$scope.$broadcast("picsDownloaded" ...
event in your controller after you get the results. On the directive, instead of
scope.$watch(attr.photoswipe ....
do this
scope.$on("picsDownloaded" ....
and in that handler you apply the Jquery Plugin.
Yes, you can use events to signal when data are there. Though you maybe have to use $rootScope, depends on use case.
But I think the most probable reason for your error is, that all $watchers are called by AngularJS the first time when initializing them with an undefined value.
You should check that in your $watch function, could solve your problem immediately.
As others pointed out you need to wait until all that stuff is rendered which happens only after your api call returns with result. The signal for your directive would be the change of gallery.images
array. So, make your directive watch for it like the following.
Please pay attention to the newly introduced isolated scope - to me it sounds like it should be isolated, but I can't really say it for sure because I don't know the rest of your app code.
Anyway, it tells angular that whatever value is used in photoswipe
attribute in the template (see below) should be bound to the images
property of directive's scope. Using such a construct ensures that this will happen on each and every change of gallery.images
regardless of when and how it happens. All you need to do outside is just to change the collection when api call finishes and the rest will just work. No events (to me they are pretty cumbersome in angular 1.x), no connections between different components, no mixing of concerns. Pretty clean solution. However, you need to understand how $watchCollection() works, it's a bit different from regular $watch
in that it performs shallow collection scan and does not compare objects in collection themselves but only their references.
Another important moment is a cleanup. You must make sure that when something is getting removed from the collection then corresponding events and whatever else that plugin binds to elements is properly destroyed, otherwise you'll end up having memory leaks, unexpected events and poor performance.
App.directive('photoswipe', function ($timeout) {
return {
replace: false,
restrict: 'A',
scope: {
"images": "=photoswipe"
},
link: function photoSwipeLink(scope, element, attr) {
scope.$watchCollection('images', function(value){
// here $timeout() is necessary because this event will be fired
// immediately after collection change
// thus not giving angular time to render it
// so, jquery plugin will be fired on the next angular digest
// and everything will be rendered by then.
$timeout(function () {
angular.element('#gallery a').photoSwipe({
enableMouseWheel: false,
enableKeyboard: false
});
// and now figure out how to clean it up!
});
});
}
};
});
Now to the template. Please note that first time I use photoswipe="gallery.images"
attribute. This is what tells angular to bind gallery.images
to the scope of photoswipe
directive to images
property.
Since directive now introduces its own isolated scope everything inside it should account to this fact. That's why ng-repeat="image in images"
.
<h1>{{ gallery.headline }}</h1>
<ul id="gallery" photoswipe="gallery.images">
<li class="gallery" ng-repeat="image in images">
<a href="{{ image.url }}" alt="image.caption">
<img ng-src="{{ image.thumb_url }}" class="thumb" alt="{{ image.caption }}" style="display: block;">
<h3 class="headline">{{ image.caption }}</h3>
</a>
</li>
</ul>
I honestly did not test this code but apart from possible syntax errors it should work. Also, I want to stress one more time that I don't know the structure of the rest of your application, so introducing isolated scope can break it if something inside your directive is bound to some higher scope - but to me it is bad idea by itself because it introduces hidden interconnections between components.
来源:https://stackoverflow.com/questions/16528482/how-can-i-get-directive-to-fire-after-view-loaded