Matt RaibleMatt Raible is a Web Developer and Java Champion. Connect with him on LinkedIn.

The Angular Mini-Book The Angular Mini-Book is a guide to getting started with Angular. You'll learn how to develop a bare-bones application, test it, and deploy it. Then you'll move on to adding Bootstrap, Angular Material, continuous integration, and authentication.

Spring Boot is a popular framework for building REST APIs. You'll learn how to integrate Angular with Spring Boot and use security best practices like HTTPS and a content security policy.

For book updates, follow @angular_book on Twitter.

The JHipster Mini-Book The JHipster Mini-Book is a guide to getting started with hip technologies today: Angular, Bootstrap, and Spring Boot. All of these frameworks are wrapped up in an easy-to-use project called JHipster.

This book shows you how to build an app with JHipster, and guides you through the plethora of tools, techniques and options you can use. Furthermore, it explains the UI and API building blocks so you understand the underpinnings of your great application.

For book updates, follow @jhipster-book on Twitter.

10+ YEARS


Over 10 years ago, I wrote my first blog post. Since then, I've authored books, had kids, traveled the world, found Trish and blogged about it all.

How to Implement a Smart Chunking Bootstrap Carousel with AngularJS

I've been helping a client develop a project management application for the last several months. One of the features I implemented uses UI Bootstrap's carousel directive to display a list of project templates to choose from when creating a new project. Rather than displaying one at a time, we wanted to display as many as the user's screen would allow. That is, if they were on a large monitor, we wanted to display five templates, a medium size monitor would display three and so on. This is a story of how I implemented a smart chunking carousel.

To begin, I made it possible to show groups of items in the carousel using array chunking.

  function chunk(arr, size) {
    var newArr = [];
    var arrayLength = arr.length;
    for (var i = 0; i < arrayLength; i += size) {
      newArr.push(arr.slice(i, i + size));
    }
    return newArr;
  }

Using UI Bootstrap's example code, I created $scope.chunkedSlides from $scope.slides.

$scope.chunkSize = 5;

// chunk slides so there's two per chunk by default
$scope.chunkedSlides = chunk($scope.slides, $scope.chunkSize);

Next, I changed the HTML template to read the grouped slides, and show each one.

<uib-carousel active="active" interval="0" no-wrap="true">
  <uib-slide ng-repeat="row in chunkedSlides">
    <div class="row">
      <div ng-repeat="slide in row track by $index" class="slide">
        <img ng-src="{{slide.image}}">
        <div class="carousel-caption">
          <h4>Slide {{slide.id}}</h4>
          <p>{{slide.text}}</p>
        </div>
      </div>
    </div>
  </uib-slide>
</uib-carousel>

This was enough to get five squares on a large monitor.

Carousel Diagram

However, I wanted to go further and reduce the number per group on smaller monitors. I created a SmartChunking service that defined how many per group for each possible width.

angular.module('app').service('SmartChunking', function() {
  var large = 1600;
  var medium = 1200;
  var small = 1024;
  var xsmall = 800;

  this.getChunkSize = function(width) {
    var chunkSize;
    if (width >= large) {
      chunkSize = 5;
    } else if (width >= medium) {
      chunkSize = 4;
    } else if (width >= small) {
      chunkSize = 3;
    } else if (width >= xsmall) {
      chunkSize = 2;
    } else {
      chunkSize = 1;
    }
    return chunkSize;
  }
});

I wrote a smart-chunking directive to fire an event with the chunk size.

angular.module('app').directive('smartChunking', function($window, SmartChunking) {
  return {
    restrict: 'A',
    link: function($scope) {
      var w = angular.element($window);

      // window.outerWidth works on desktop, screen.height on iPad (width returns 768)
      var width = ($window.outerWidth > 0) ? $window.outerWidth : screen.height;
      var chunkSize = SmartChunking.getChunkSize(width);
      if (chunkSize !== 5) {
        $scope.$emit('change-chunk-size', chunkSize);
      }

      $scope.getWidth = function() {
        return ($window.outerWidth > 0) ? $window.outerWidth : screen.width;
      };

      $scope.$watch($scope.getWidth, function(newValue, oldValue) {
        if (newValue !== oldValue) {
          var chunkSize = SmartChunking.getChunkSize(newValue);
          $scope.$emit('change-chunk-size', chunkSize);
        }
      });

      w.bind('resize', function() {
        $scope.$apply();
      });
    }
  }
});

Then I added a listener for this in the controller that populated the carousel.

$scope.$on('change-chunk-size', function(event, data) {
  if (data !== $scope.chunkSize) {
    $scope.chunkedSlides = chunk($scope.slides, data);
    $scope.chunkSize = data;
  }
});

The final step was adding the smark-chunking directive to each slide and dynamically determining its col-sm-* class.

<div ng-repeat="slide in row track by $index" class="slide" ng-class="getSlideClass(chunkSize)" smart-chunking>

The controller contains a map of classes that map to chunk sizes:

var classMap = {
  5: 'col-sm-2',
  4: 'col-sm-3',
  3: 'col-sm-4',
  2: 'col-sm-5',
};

$scope.getSlideClass = function(chunkSize) {
  if (classMap[chunkSize]) {
    return classMap[chunkSize];
  } else {
    return 'col-sm-10';
  }
}

I did find that adding some CSS made things look quite a bit better.

.carousel-caption {
  padding-bottom: 0;
}

.carousel-control.left,
.carousel-control.right {
  background-image: none;
}

.carousel-indicators {
  display: none;
}

.carousel-inner {
  padding-left: 10%;
  overflow: visible;
}

.carousel-control .glyphicon-chevron-left,
.carousel-control .glyphicon-chevron-right {
  font-size: 100px;
  margin-top: -60px;
  font-style: normal;
  font-weight: 100;
}

.carousel-control .glyphicon-chevron-left {
  margin-left: -100px;
}

.carousel-control .glyphicon-chevron-right {
  margin-right: -40px;
}

/* make slide widths more responsive */
@media only screen and (min-width: 1600px) {
  .col-sm-2 {
    width: 18%;
  }
}

@media only screen and (min-width: 1200px) {
  .col-sm-3 {
    width: 22%;
  }
  .carousel-control .glyphicon-chevron-left {
    margin-left: -70px;
  }
  .carousel-control .glyphicon-chevron-right {
    margin-right: -20px;
  }
}

@media only screen and (max-width: 1200px) {
  .col-sm-4 {
    width: 29%;
  }
  .carousel-control .glyphicon-chevron-left {
    margin-left: -70px;
  }
  .carousel-control .glyphicon-chevron-right {
    margin-right: -20px;
  }
}

@media only screen and (max-width: 800px) {
  .col-sm-10 {
    width: 90%;
  }
}

I hope this tip helps you if you need to implement a similar feature. I've published a demo on Plunkr (best experienced in embedded view).

Smark Chunking Carousel

Posted in The Web at Mar 15 2016, 09:47:30 AM MDT 2 Comments
Comments:

What if the same directive has to support lesser width carousel elements? In this case, for larger desktops carousel should show 6 chunks instead of 5. And the component should support this dynamic chunking based on the width of elements. How do we achieve this through the above directive/service?

Posted by SmartAsh on January 22, 2018 at 07:03 PM MST #

Hello SmartAsh: I'm not sure, but please let me know if you figure out a solution! You might try asking on Stack Overflow.

Posted by Matt Raible on February 11, 2018 at 01:18 PM MST #

Post a Comment:
  • HTML Syntax: Allowed