Masonry with AngularJS Masonry with AngularJS jquery jquery

Masonry with AngularJS


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);      });     }  };});


This is not exacly what you are looking for (prepend and append), but should be just what you are looking for:

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

Your version of the directive triggers reload for every brick. This version triggers only reload only once for the whole list change.

The approach is very simple:

  1. Register new bricks in parent masonry controller
  2. $watch for changes in the registered bricks and fire masonry('reload')
  3. Remove brick from bricks registry when you are removing the element - $on('$destroy')
  4. ?
  5. Profit

You can extend this approach to do what you wanted (use prepend and append) but I don't see any reason why you would want to do that. This also would be much more complicated, since you would have to manually track the order of the elements. I also don't belive it would be any faster - on the contrary it may be slower, since you would have to trigger multiple append/prepend if your changes a lot of bricks.

I am not quite sure, but I guess you could use ng-animate for this (the JavaScript animation version)

We have implemented something similar for tiling events in our calendar app. This solution turned out to be the fastest. If anyone has better solution, I'd love to see that.

For those who want to se the code:

angular.module('myApp.directives', [])  .directive("masonry", function($parse) {    return {      restrict: 'AC',      controller:function($scope,$element){        // register and unregister bricks        var bricks = [];        this.addBrick = function(brick){          bricks.push(brick)        }        this.removeBrick = function(brick){          var index = bricks.indexOf(brick);          if(index!=-1)bricks.splice(index,1);        }        $scope.$watch(function(){          return bricks        },function(){          // triggers only once per list change (not for each brick)          console.log('reload');          $element.masonry('reload');        },true);      },      link: function (scope, elem, attrs) {        elem.masonry({ itemSelector: '.masonry-brick'});      }    };       })  .directive('masonryBrick', function ($compile) {    return {      restrict: 'AC',      require:'^masonry',      link: function (scope, elem, attrs,ctrl) {        ctrl.addBrick(scope.$id);        scope.$on('$destroy',function(){          ctrl.removeBrick(scope.$id);        });      }    };  });

Edit:there is one thing I forgot about (loading images) - just call 'reload' when all images were loaded. Ill try to edit the code later.


Hey I just made masonry directive for AngularJS that is far more simpler than most of the implementations I've seen. Check the gist out here: https://gist.github.com/CMCDragonkai/6191419

It's compatible with AMD. Requires jQuery, imagesLoaded and lodash. Works with dynamic amount of items, AJAX loaded items (even with initial items), window resizing, and custom options. Prepended items, appended items, reloaded items... etc. 73 lines!

Here's a plunkr showing it work: http://plnkr.co/edit/ZuSrSh?p=preview (without AMD, but the same code).