1

Okay, I'm working with a bit of a challenging grid here and need some help. I need to find a way to populate the data by ng-repeat. I'll attach an image of the grid layout of what I'm trying to achieve and the order of the data should populate. I have done the layout in HTML in the following method. But can't find a way to do it by ng-repeat.

ROW 1 - normal bootstrap col-md-4 and col-md-8

ROW 2 - I've done this row using CSS3 Flexbox. You can have al look at the code below.

ROW 3 - normal bootstrap col-md-4 and col-md-8

enter image description here

This is a plunker of what I've been trying to do. [https://plnkr.co/edit/NtXEnQcKpM0JxnLsxc4m?p=preview]2

I'll also try to convey the logic behind this. So at the bottom of the grid there will be a load more button to repeat this entire layout again and populate the data. Hence I've planned to put and ng-repeat on the container. Following this I've divided the layout in 3 rows. - the first one consist of columns 1 and 2 - the second row consist of columns 3 to 7 - the third row consist of columns 8 and 9

The array returning as well is divided the same way.

I hope I get some clarity if my approach to this is wrong and maybe a work-around to achieve this layout.

  • 1
    have you looked at `ng-repeat-start` and `ng-repeat-end`? – haxxxton Mar 04 '17 at 07:41
  • I'm just getting started with AngularJS. I'll have a look at this. Thanks for your input. – Joystan Fernandes Mar 04 '17 at 07:47
  • Is this duplicate of http://stackoverflow.com/questions/19839743/nested-ng-repeat? – Smit Mar 04 '17 at 07:49
  • 1
    Here's a working fiddle rather than your broken plunkr to show the repeats working: http://jsfiddle.net/93sozhkf/ – haxxxton Mar 04 '17 at 08:11
  • are you set on your data json in the format you've outlined? or would it be preferrable to not have to "pre-group" your data into rows? and instead be able to just provide a list to generate the design for each set of 9 elements? – haxxxton Mar 04 '17 at 08:12
  • @haxxxton How would you suggest the best way to go about this? Modifications can be made.. – Joystan Fernandes Mar 04 '17 at 09:12
  • @haxxxton As per the layout the middle block needs to be given double the height. So keeping this in mind is there a better approach to this? BTW thanks for the jsfiddle. works perfectly! – Joystan Fernandes Mar 04 '17 at 09:21
  • Let me see what i can come up with – haxxxton Mar 04 '17 at 10:36
  • Thank you for your time and help @haxxxton. Really appreciate this. Looking forward to your approach on this :) – Joystan Fernandes Mar 04 '17 at 10:47
  • "I have done the layout in HTML". Can you post a fiddle of this? – Carol Skelly Mar 04 '17 at 11:00
  • @ZimSystem here you go.. [https://jsfiddle.net/58q105u7/3/show/](https://jsfiddle.net/58q105u7/3/show/) For some reason the directive wasn't working in jsfiddle but it works on my local. Just to save on time I've defined the height in CSS for demo purpose. – Joystan Fernandes Mar 04 '17 at 11:43
  • @JoystanFernandes, is the height of your design fixed? or is the ratio of each cell fixed? ie. cells 1,4, and 8 should always be squares? 2, 3, 5, 6, 7, and 9 are always 2:1? – haxxxton Mar 04 '17 at 12:51
  • @haxxxton the height of the divs are equal to the first div. It's just like this layout here https://jsfiddle.net/58q105u7/3/show/. I was doing it with a directive but as the it's ng-class now it doesn't load the height. You could have a look at the directive code too in the above jsfiddle. – Joystan Fernandes Mar 04 '17 at 12:56
  • @haxxxton just to clarify... In Eow 1- the height of both the div's is equal to the first sqaure. In Row 2 all are squares except the one in the middle which is double the height of the other one's. Row 3 is just like Row 1. I know it's a bit confusing but the jsfiddle should give you an idea of what I'm trying to achieve. This has to be responsive too.. I'm trying to figure out something on my end too... I'll update it here if I succeed. Cheers! – Joystan Fernandes Mar 04 '17 at 12:57
  • So the first div doesnt have to be a square/maintain a 1:1 ratio of height to width? – haxxxton Mar 04 '17 at 12:59
  • @haxxxton yes your right and the col-md-8 gets the height of the col-md-4 which is 1:1 only on desktop screen size though. later it can take the size it needs to fit the content – Joystan Fernandes Mar 04 '17 at 13:04

2 Answers2

2

To make the design you've outlined, I would suggest doing the following:

  1. Use a filter to "chunk" your articles into groups of 9 (similar to this memoized chunking solution)

  2. Use a custom directive to create your "template"

  3. Leverage a responsive fixed ratio grid system (like cough my library Perfect Bootstrap cough)

Example JSFiddle


Step1

Let's put your JSON back into a standard array, and then "chunk" it into batches of 9 at run time. We create an angular filter that we then apply using | chunk. To avoid an infinite digest loop, we'll need to memoize the result of the function using a variable, in the below code we use the string 'newsGroups'. If you intend to load more items into your array, say for infinite scroll, you'll need to update this variable in order to refresh the memoized result. Think of it like a cachebreak.

For brevity, i have stripped back your article array to be just an id and title item for each element. I would assume this is coming from an AJAX call in your development environment anyways.

HTML

<div class="container-fluid" ng-controller="MyCtrl">
  <div class="template-container" ng-repeat="newsGroup in NewsListing | chunk:'newsGroups'">
    <!-- content here -->
  </div>
</div>

JS Chunk filter and basic array of articles

var myApp = angular.module('myApp', []);
myApp.filter('chunk', function() {
  function cacheIt(func) {
    cache = {};
    return function(arg) {
      // if the function has been called with the argument
      // short circuit and use cached value, otherwise call the
      // cached function with the argument and save it to the cache as well then return
      return cache[arg] ? cache[arg] : cache[arg] = func(arg);
    };
  }

  // unchanged from your example apart from we are no longer directly returning this
  function chunk(items, chunk_size) {
    var chunks = [];
    if (angular.isArray(items)) {
      if (isNaN(chunk_size))
        chunk_size = 9;
      for (var i = 0; i < items.length; i += chunk_size) {
        chunks.push(items.slice(i, i + chunk_size));
      }
    } else {
      console.log("items is not an array: " + angular.toJson(items));
    }
    return chunks;
  }
  // now we return the cached or memoized version of our chunk function
  // if you want to use lodash this is really easy since there is already a chunk and memoize function all above code would be removed
  // this return would simply be: return _.memoize(_.chunk);

  return cacheIt(chunk);
});
myApp.controller('MyCtrl', function($scope) {
  $scope.NewsListing = [{
    "id": "71b85130-ffe4-11e6-81a4-c1bd97df0d2d",
    "title": "Exercitation ullamco laboris"
  }, {
    "id": "58847180-ffe4-11e6-81a4-c1bd97df0d2d",
    "title": "Duis aute irure dolor in reprehenderit"
  }, {
    "id": "35d2c290-ffe4-11e6-81a4-c1bd97df0d2d",
    "title": "Ut enim ad minim"
  }, {
    "id": "fdbdeb00-ffe3-11e6-81a4-c1bd97df0d2d",
    "title": "Ut enim ad minim"
  }, {
    "id": "df858f30-ffe3-11e6-81a4-c1bd97df0d2d",
    "title": "Dolore magna aliqua"
  }, {
    "id": "bbc619c0-ffe3-11e6-81a4-c1bd97df0d2d",
    "title": "Qui officia deserunt mollit anim"
  }, {
    "id": "8467e800-ffe3-11e6-81a4-c1bd97df0d2d",
    "title": "Consectetur adipisicing elit, sed do eiusm"
  }, {
    "id": "5aea9180-ffe3-11e6-81a4-c1bd97df0d2d",
    "title": "Lorem elit, sed do eiusm"
  }, {
    "id": "418d09c0-ffe3-11e6-81a4-c1bd97df0d2d",
    "title": "ALL YOU NEED TO KNOW ABOUT WAREHOUSE"
  }, {
    "id": "71b85130-ffe4-11e6-81a4-c1bd97df0d2e",
    "title": "Exercitation ullamco laboris2"
  }, {
    "id": "58847180-ffe4-11e6-81a4-c1bd97df0d2e",
    "title": "Duis aute irure dolor in reprehenderit2"
  }, {
    "id": "35d2c290-ffe4-11e6-81a4-c1bd97df0d2e",
    "title": "Ut enim ad minim2"
  }, {
    "id": "fdbdeb00-ffe3-11e6-81a4-c1bd97df0d2e",
    "title": "Ut enim ad minim2"
  }, {
    "id": "df858f30-ffe3-11e6-81a4-c1bd97df0d2e",
    "title": "Dolore magna aliqua2"
  }, {
    "id": "bbc619c0-ffe3-11e6-81a4-c1bd97df0d2e",
    "title": "Qui officia deserunt mollit anim2"
  }, {
    "id": "8467e800-ffe3-11e6-81a4-c1bd97df0d2e",
    "title": "Consectetur adipisicing elit, sed do eiusm2"
  }, {
    "id": "5aea9180-ffe3-11e6-81a4-c1bd97df0d2e",
    "title": "Lorem elit, sed do eiusm2"
  }, {
    "id": "418d09c0-ffe3-11e6-81a4-c1bd97df0d2e",
    "title": "ALL YOU NEED TO KNOW ABOUT WAREHOUSE2"
  }];
})

Step 2

In the content here comment section of the HTML in Step 1 we would apply a custom directive. In real-life development i would probably use a separate file to hold the template, however, for the constraints of the js fiddle i needed to define it as an text/ng-template script tag.

You can see that the newsGroup from the ng-repeat in Step 1 will be handed to our custom directive using the scope value items. This will contain at most 9 news articles.

HTML Directive call

<div nine-item-news items="newsGroup"></div>

JS directive definition

myApp.directive('nineItemNews', function() {
  return {
    restrict: 'A',
    replace: true,
    templateUrl: '/nine-item-news-template.html',
    scope: {
      items: '='
    }
  }
});

HTML TEMPLATE

<script type="text/ng-template" id="/nine-item-news-template.html">
<div class="template">
    <!-- template content here -->
</div>
</script>

Step 3

A while ago i developed a CSS library designed to provide responsive fixed ratio grid elements. This question has provoked me to finally make it public. Think of it like bootstrap's horizontal col- functionality, but vertically.

We replace the template content here comment above with the following html that will load in the items passed to it by the custom directive from Step 2. I would recommend you expand on this template to add ng-if statements to load/unload blocks, similarly, i would recommend using ng-class to adapt the design for the cases you arent handed a full 9 items (ie. what would it look like if only 6 articles were passed to it). Alternatively, using an ng-switch based on items.length and loading an entirely different template could similarly work.

The additional CSS is to add background colors and remove the default padding/margin for the bootstrap columns.

NB: Please note that in order to make the elements maintain their ratio, their overflow is set to hidden. This works particularly well when items have background images, but can have unintended consequences for text. My example includes a mobile responsivised template design that creates sections with a ration of 2:1. I encourage you to test this for your specific build carefully and a number of screen resolutions.

HTML template content

<div class="row" ng-if="items.length">
    <div class="col-xs-12 col-md-4 col-no-padding">
        <div class="row-xs-6 row-md-12">
            <div class="abs-inner bg-darkgrey">
                <p ng-bind="items[0].title"></p>
            </div>
        </div>
    </div>
    <div class="col-xs-12 col-md-8 col-no-padding">
        <div class="row-xs-6">
            <div class="abs-inner bg-grey">
                <p ng-bind="items[1].title"></p>
            </div>
        </div>
    </div>
</div>
<div class="row">
    <div class="col-xs-12 col-md-4 col-no-padding">
        <div class="row">
            <div class="col-xs-12 col-no-padding">
                <div class="row-xs-6">
                    <div class="abs-inner bg-grey">
                        <p ng-bind="items[2].title"></p>
                    </div>
                </div>
            </div>
            <div class="col-xs-12 col-no-padding">
                <div class="row-xs-6">
                    <div class="abs-inner bg-midgrey">
                        <p ng-bind="items[5].title"></p>
                    </div>
                </div>
            </div>
        </div>
    </div>
    <div class="col-xs-12 col-md-4 col-no-padding">
        <div class="row-xs-6 row-md-12">
            <div class="abs-inner bg-white">
                <p ng-bind="items[3].title"></p>
            </div>
        </div>
    </div>
    <div class="col-xs-12 col-md-4 col-no-padding">
        <div class="row">
            <div class="col-xs-12 col-no-padding">
                <div class="row-xs-6">
                    <div class="abs-inner bg-grey">
                        <p ng-bind="items[4].title"></p>
                    </div>
                </div>
            </div>
            <div class="col-xs-12 col-no-padding">
                <div class="row-xs-6">
                    <div class="abs-inner bg-midgrey">
                        <p ng-bind="items[6].title"></p>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>
<div class="row">
    <div class="col-xs-12 col-md-4 col-no-padding">
        <div class="row-xs-6 row-md-12">
            <div class="abs-inner bg-darkgrey">
                <p ng-bind="items[7].title"></p>
            </div>
        </div>
    </div>
    <div class="col-xs-12 col-md-8 col-no-padding">
        <div class="row-xs-6">
            <div class="abs-inner bg-grey">
                <p ng-bind="items[8].title"></p>
            </div>
        </div>
    </div>
</div>

CSS

.col-no-padding{
  padding-left:0;
  padding-right:0;
}
.col-no-padding > .row{
  margin-left:0;
  margin-right:0;
}
.bg-darkgrey{
  background-color:#535353;
}
.bg-midgrey{
  background-color:#808080;
}
.bg-grey{
  background-color:#959595;
}
.bg-lightgrey{
  background-color:#d0d0d0;
}
.bg-white{
  background-color:#FFFFFF;
}
Community
  • 1
  • 1
haxxxton
  • 6,422
  • 3
  • 27
  • 57
0

I think the right way to do this in Angular should be a custom directive.

When the user loas more, there should be an entier layout again, so the directive should display the entire layout.

You just have to give the data in argument to the directive, display it as you want (like give the data in an array or something to get the index).

Let's call that directive layoutDirective.

Then you put this in yout html :

<div ng-repeat="array in arrays" ng-if="$index < limitDisplay">
  <layout-directive data=array> </layout-directive>
</div>

The only trick here is the ng-if directive

Where limitDisplay is an int incremented when someone press loadMore I'm not sure about the data binding written like this, I'll try to improve my answer later to help you create a good directive

Alburkerk
  • 1,564
  • 13
  • 19