<!DOCTYPE html>
<html ng-app="demoApp">

<head>
  <title>Lesson</title>
  <link href="https://netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css" rel="stylesheet" />
  <link rel="stylesheet" href="https://npmcdn.com/lightbox2@^2.8.2/dist/css/lightbox.min.css" data-require="lightbox2@^2.8.2" data-semver="2.8.2">
</head>

<body ng-controller="demoController">

  <div class="alert alert-info">
    <a href="#" class="close" data-dismiss="alert">&times;</a>
    <p>
      Masonary, Isotope, and Packery are very powerful layout tools that work with the modern "card" UI found in many modern applications.
    </p>
    <p>Here we show the use of Masonry working with Bootstrap and Angular to do some really nice layout.</p>
  </div>
  
  <div class="alert alert-warning">
      <a href="#" class="close" data-dismiss="alert">&times;</a>
    <p>
      I added lightbox too. Click a person's "Photo" link to view example usage.
      Click "Name" and "Age" buttons to sort by those fields and see masonry layout respond.
    </p>
  </div>
  
  <div class="well" ng-hide="showDetail">
    Sort
    <button class="btn btn-default" ng-click="sortButtonClick('name')">
      Name
      <span ng-show="sortField=='name'">{{(sortAscending)?'+':'-'}}</span>
    </button>
    <button class="btn btn-default" ng-click="sortButtonClick('age')">
      Age
      <span ng-show="sortField=='age'">{{(sortAscending)?'+':'-'}}</span>
    </button>
    Search
    <input type="text" class="btn" ng-model="filterText" placeholder="" /> Add
    <button class="btn btn-default" ng-click="addButtonClick()">New</button>
  </div>

  <div masonry ng-hide="showDetail">
    <div class="masonry-brick col-xs-12 col-sm-6 col-md-4 col-lg-3 container-fluid" ng-repeat="person in people | filter:filterText | orderBy:sort">
      <!--<div class="panel panel-default" ng-click="editPerson(person)">-->
      <div class="panel panel-default">
        <div class="panel-heading" ng-click="editPerson(person)">{{ person.name }}</div>
        <div class="panel-body">Age: {{ person.age }}</div>
        <a ng-href="{{ person.photo  }}" data-lightbox="{{ person.name }}">Photo</a>
      </div>
    </div>
  </div>

  <div class="container" ng-show="showDetail">
    <input type="text" class="form-control" ng-model="detailPerson.name" placeholder="Name" />
    <input type="text" class="form-control" ng-model="detailPerson.age" placeholder="Age" />
    <button class="btn" ng-show="detailPerson.id == null" ng-click="addPerson(detailPerson)">Add</button>
    <button class="btn" ng-show="detailPerson.id != null" ng-click="updatePerson(detailPerson)">Update</button>
    <button class="btn" ng-show="detailPerson.id != null" ng-click="deletePerson(detailPerson)">Delete</button>
    <button class="btn" ng-click="cancelDetail()">Cancel</button>
  </div>

  <script src="https://code.jquery.com/jquery-2.0.3.min.js"></script>
  <script src="https://netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js"></script>
  <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.17/angular.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/masonry/3.1.5/masonry.pkgd.min.js"></script>
  <script src="https://passy.github.io/angular-masonry/bower_components/imagesloaded/imagesloaded.js"></script>
  <script src="https://passy.github.io/angular-masonry/angular-masonry.js"></script>
  <script type="text/javascript" src="https://npmcdn.com/lightbox2@^2.8.2/dist/js/lightbox.min.js" data-require="lightbox2@^2.8.2" data-semver="2.8.2"></script>

  <script>
    var app = angular.module('demoApp', ['wu.masonry']);
    app.controller('demoController', function($scope) {
      $scope.people = [{
        'name': 'Chris',
        'age': 21,
        'id': 0,
        'photo': 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/82/submerged.jpg'
      }, {
        'name': 'Alex',
        'age': 25,
        'id': 1,
        'photo': 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/82/look-out.jpg'
      }, {
        'name': 'Ryan',
        'age': 31,
        'id': 2,
         'photo': 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/82/contrail.jpg'
     }, {
        'name': 'Sam',
        'age': 19,
        'id': 3,
        'photo': 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/82/drizzle.jpg'
      }, {
        'name': 'Kelly',
        'age': 45,
        'id': 4,
        'photo': 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/82/golden-hour.jpg'
      }];

      $scope.people._idGenerator = $scope.people.length;

      // sort, sortField, sortAscending, sortButtonClick(field)
      $scope.$watch('sortField', function() {
        $scope.sortAscending = true;
      });
      $scope.$watch('sortField+sortAscending', function() {
        $scope.sort = (($scope.sortAscending) ? "+" : "-") + $scope.sortField;
      });

      $scope.sortField = "name";

      $scope.sortButtonClick = function(field) {
        $scope.sortField = field;
        $scope.sortAscending = !$scope.sortAscending;
      };

      // showDetail, detailPerson, editPerson(person)
      $scope.addButtonClick = function() {
        $scope.detailPerson = {};
        $scope.showDetail = true;
      };
      $scope.editPerson = function(person) {
        $scope.detailPerson = JSON.parse(JSON.stringify(person));
        $scope.showDetail = true;
      };
      $scope.addPerson = function(person) {
        person.id = $scope.people._idGenerator++;
        $scope.people.push(person);
        $scope.cancelDetail();
      };
      $scope.deletePerson = function(person) {
        for (var i = 0; i < $scope.people.length; i++) {
          if ($scope.people[i].id == person.id) {
            $scope.people.splice(i, 1);
            break;
          }
        }
        $scope.cancelDetail();
      };
      $scope.updatePerson = function(person) {
        for (var i = 0; i < $scope.people.length; i++) {
          if ($scope.people[i].id == person.id) {
            $scope.people[i] = person;
            break;
          }
        }
        $scope.cancelDetail();
      };
      $scope.cancelDetail = function() {
        $scope.detailPerson = {};
        $scope.showDetail = null;
      };
    });
  </script>
</body>

</html>
Bootstrap and Angular are powerful tools for building modern responsive single page applications.  In turn these modern web applications can be transformed into high quality mobile applications.

In these lessons we introduce new concepts from Bootstrap and Angular one step at a time...  and we keep the number of new concepts to a minumum so it is easy to see how all the pieces fit together. 

The lessons are hosted on GitHub at http://johnshew.github.io/WebAppTutorial/Lessons/

The individual lessons are available as easily editable examples on http://plnkr.co/users/johnshew

Hope you find this tutorial easy to follow and useful.

@johnshew


I forked this plunker to add lightbox for a demo.

@dad700