I am developing an "art gallery" app.
Feel free to pull down the source on github and play around with it.
Plunker with full source.
The current work around for getting Masonry to play nice with Angular:
.directive("masonry", function($parse) { return { restrict: 'AC', link: function (scope, elem, attrs) { elem.masonry({ itemSelector: '.masonry-brick'}); } }; }) .directive('masonryBrick', function ($compile) { return { restrict: 'AC', link: function (scope, elem, attrs) { scope.$watch('$index',function(v){ elem.imagesLoaded(function () { elem.parents('.masonry').masonry('reload'); }); }); } }; });
This doesn't work well because:
The reload
function:
In context with the app I've given links to above, this problem becomes very easy to replicate.
I am looking for a solution that will use directives to leverage:
.masonry('appended', elem)
and .masonry('prepended', elem)
Rather than executing .masonry('reload')
every time.
.masonry('reload')
for when elements are removed from result set.
The project has been updated to use the working solution below.
Grab the source on GitHub
See a working version on Plunker
I've been playing around with this a bit more and @ganaraj's answer is pretty neat. If you stick a $element.masonry('resize');
in his controller's appendBrick
method and account for the images loading then it looks like it works.
Here's a plunker fork with it in: http://plnkr.co/edit/8t41rRnLYfhOF9oAfSUA
The reason this is necessary is because the number of columns is only calculated when masonry is initialized on the element or the container is resized and at this point we haven't got any bricks so it defaults to a single column.
If you don't want to use the 'resize' method (I don't think it's documented) then you could just call $element.masonry() but that causes a re-layout so you'd want to only call it when the first brick is added.
Edit: I've updated the plunker above to only call resize
when the list grows above 0 length and to do only one "reload" when multiple bricks are removed in the same $digest cycle.
Directive code is:
angular.module('myApp.directives', []) .directive("masonry", function($parse, $timeout) { return { restrict: 'AC', link: function (scope, elem, attrs) { elem.masonry({ itemSelector: '.masonry-brick'}); // Opitonal Params, delimited in class name like: // class="masonry:70;" //elem.masonry({ itemSelector: '.masonry-item', columnWidth: 140, gutterWidth: $parse(attrs.masonry)(scope) }); }, controller : function($scope,$element){ var bricks = []; this.appendBrick = function(child, brickId, waitForImage){ function addBrick() { $element.masonry('appended', child, true); // If we don't have any bricks then we're going to want to // resize when we add one. if (bricks.length === 0) { // Timeout here to allow for a potential // masonary timeout when appending (when animating // from the bottom) $timeout(function(){ $element.masonry('resize'); }, 2); } // Store the brick id var index = bricks.indexOf(brickId); if (index === -1) { bricks.push(brickId); } } if (waitForImage) { child.imagesLoaded(addBrick); } else { addBrick(); } }; // Removed bricks - we only want to call masonry.reload() once // if a whole batch of bricks have been removed though so push this // async. var willReload = false; function hasRemovedBrick() { if (!willReload) { willReload = true; $scope.$evalAsync(function(){ willReload = false; $element.masonry("reload"); }); } } this.removeBrick = function(brickId){ hasRemovedBrick(); var index = bricks.indexOf(brickId); if (index != -1) { bricks.splice(index,1); } }; } }; }) .directive('masonryBrick', function ($compile) { return { restrict: 'AC', require : '^masonry', link: function (scope, elem, attrs, MasonryCtrl) { elem.imagesLoaded(function () { MasonryCtrl.appendBrick(elem, scope.$id, true); }); scope.$on("$destroy",function(){ MasonryCtrl.removeBrick(scope.$id); }); } }; });
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With