Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular Directives watching external models

In learning Angular I am creating a simple gallery that can be zoomed. My initial implementation used a simple ng-repeat, but I quickly discovered that based on the zoom of the gallery I would want to change things like the url source (from small to medium thumbs), and probably the css on captions etc.

<div class="photoHolder" ng-repeat="photo in photos | filter:{caption:query} | orderBy:orderProp" ng-attr-style="transition: .3s ease; width: {{thumbWidth}}px; height: {{thumbWidth}}px;">
    <div class="photoClass" data-id="{{photo.id}}" ng-attr-style="background-image:url({{zoomSize < 5 ? photo.thumb_url : photo.medium_url}})"></div>
    <div class="caption">{{photo.caption}}</div>
    <div class="caption">{{photo.date}}</div>
</div>

So, I switched to a much cleaner directive:

<gal-photo class="photoHolder" ng-repeat="photo in photos | filter:{caption:query} | orderBy:orderProp"/>  

but the only way I could get the directive element to respond to the zoom change is to add a watch to the zoom inside the link for the element:

link: function(scope, element, attrs) {
    var image = new Image();
    scope.photo.url = scope.zoomSize < 5 ? scope.photo.thumb_url : scope.photo.medium_url;
    scope.$watch('thumbWidth', function() {
           scope.photo.url = scope.zoomSize < 5 ? scope.photo.thumb_url : scope.photo.medium_url;
        element.attr('style',  'transition: .3s; width: ' + scope.thumbWidth + 'px; height: ' + scope.thumbWidth + 'px;');
    });
}

I know that you aren't supposed to abuse watches, and my fear is that with 500 elements in the gallery you are doing 500 watches. But I can find no guidance on responding to external inputs within repeating directives. The two galleries seem to perform about the same, but I am wondering if I am doing this wrong? Here is a fiddle for the original gallery and a fiddle for the directive version.

like image 841
Kris Erickson Avatar asked Feb 13 '14 20:02

Kris Erickson


2 Answers

Your use case is a good candidate for $scope events. Have one watch inside your controller:

$scope.$watch('thumbWidth', function() {
    $scope.$broadcast('thumbWidthChange');
});

And then in your directive register listeners for this event:

scope.$on('thumbWidthChange', function(){
    scope.photo.url = scope.zoomSize < 5 ? scope.photo.thumb_url : scope.photo.medium_url;
    element.attr('style',  'transition: .3s; width: ' + scope.thumbWidth + 'px; height: ' + scope.thumbWidth + 'px;');
});

Scope event listeners are only called when their events are fired, so this should limit the watch list and optimize performance.

Here is an updated fiddle based on your example using the events method: http://jsfiddle.net/dv7fy/1/

As a side note, performance of Angular watches scales better than what most think, have a look at this example (still on Angular 1.0.3, should be even better on latest version): http://jsfiddle.net/mhevery/cXMPJ/11/

like image 113
Beyers Avatar answered Nov 06 '22 19:11

Beyers


I think Beyers is right that watches are actually pretty efficient.

Still, here's how you could do this with a single watch by using a parent directive for the whole image gallery:

myApp.directive('imageGallery', function() {
return {
    restrict: 'A',
    controller: function($scope, $element, $attrs) {
        function updatePhotos() {
            $element.children().attr('style',  'transition: .3s; width: ' + $scope.thumbWidth + 'px; height: ' + $scope.thumbWidth + 'px;');
            $scope.photos.forEach(function(photo){
                photo.url = $scope.zoomSize < 5 ? photo.thumb_url : photo.medium_url;
            });
        };

        this.updatePhotos = updatePhotos;
    },
    link: function(scope, element, attrs, ctrl) {
        scope.$watch('thumbWidth', function() {   
            ctrl.updatePhotos();
        });
        ctrl.updatePhotos();
    }
}
});

(full fiddle here)

Notice that I've put the central updating logic into the controller rather than the link function. That's not strictly necessary here, but it could be useful in the future, because that controller object would be visible to the child galPhoto directives. (See the two "NOTE" comments in the fiddle.)

like image 33
anandthakker Avatar answered Nov 06 '22 18:11

anandthakker