0

I have an array of 6 objects.

I have a list with an ng-repeat where I want to display 3 unique items from those 6 items.

One each refresh, it might pull 3 different items, but it's ok if it does not, the only thing is that the 3 cannot have duplicates amongst themselves.

As an example, if the array is [red, yellow, blue, green, purple, cyan, fuchsia] then on refresh I could get:

red,blue,green
purple,blue,yellow
fuchsia,green,red

etc. As you can see, I don't care that blue came up twice in a row there, but I must never get red, blue, blue.

I have this code:

<ul class="ch-grid">
  <li ng-repeat="user in home.testimonials|orderBy:random|limitTo: 3">
    <div class="ch-item ch-img-1" style="background-image: url(assets/images/{{user.image}})">
      <div class="ch-info">
        <h3>{{user.quote}}</h3>
      </div>
    </div>
    <h3 class="name">{{user.name}}</h3>
    <p class="title">{{user.title}}</p>
  </li>
</ul>

and them in my controller:

_this.random = function () {
  return 0.5 - Math.random();
};

_this.testimonials = [
  {
    name: 'Sara Conklin',
    title: 'SMB/SendPro UX Architect',
    image: 'sara-conklin.jpg',
    quote: 'Instead of inventing original solutions, we can leverage DS guidelines and components, save time, ensure great UX and promote consistency. '},
  {
    name: 'Jenn Church',
    title: 'User Experience Designer',
    image: 'jenn-church.jpg',
    quote: 'The Design System has been a great tool in rapid prototyping, allowing me to get modern, on-brand interfaces put together quickly without having to start from scratch.'},
  {
    name: 'Peter Leeds',
    title: 'Global Creative and Brand Activation',
    image: 'peter-leeds.jpg',
    quote: 'Design System provides the unified, consistent look needed to preserve and reinforce the integrity of our brand.”'},
  {
    name: 'Marcy Goode',
    title: 'Digital Marketing, Self Service &amp; Content Management Leader',
    image: 'marcy-goode.jpg',
    quote: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Iusto, ipsam, mollitia in vitae nemo aliquam.'},
  {
    name: 'Clarence Hempfield',
    title: 'Spectrum Spatial Analyst Product Manager',
    image: 'clarence.jpg',
    quote: 'Design System isn’t just about the interface. It’s about understanding how people are expecting to interact with your technology.'},
  {
    name: 'Aaron Videtto',
    title: 'SendSuite Tracking Online Product Manager',
    image: 'aaron.jpg',
    quote: 'Customers of SendSuite Tracking Online have been up and running within 10-15 minutes. We were able to do this because of the Design System.'}
];

But it is not limiting to 3, I get dozens.

OK, trying the filter route as suggested by @csschapker:

    (function () {
  'use strict';

  angular.module('pb.ds.home').filter('getThree', function () {
    return function (array) {
      var copy = angular.copy(array);
      var sample = [];
      while (sample.length < 3) {
        var randomIndex = Math.floor(Math.random() * (copy.length));
        sample.push(copy[randomIndex]);
      }
      return sample;
    };
  });
})();

and

    <ul class="ch-grid">
  <li ng-repeat="user in home.testimonials|filter:getThree">
    <div class="ch-item ch-img-1" style="background-image: url(assets/images/{{user.image}})">
      <div class="ch-info">
        <h3>{{user.quote}}</h3>
      </div>
    </div>
    <h3 class="name">{{user.name}}</h3>
    <p class="title">{{user.title}}</p>
  </li>
</ul>

This simply prints out all 6. I must be missing something.

Steve
  • 14,401
  • 35
  • 125
  • 230

3 Answers3

2

User random and limitTo filters.

<p ng-repeat="i in list|orderBy:random|limitTo:3">{{i}}</p>
speedingdeer
  • 1,236
  • 2
  • 16
  • 26
  • yes, it will give you three random items from your list (each one from different index). – speedingdeer Aug 18 '16 at 18:47
  • that's probably because the `orderBy` filter doesn't actually change the array you give it. I'm not 100% sure though. – csschapker Aug 18 '16 at 19:56
  • 1
    I don't think just saying "random" does anything without a filter function to back it up – Steve Aug 18 '16 at 20:19
  • @Steve is right. It looks like for this case it would need to be `orderBy:home.random` because of controllerAs syntax – csschapker Aug 18 '16 at 20:58
  • OK it's about convention then. Just figure out how to use the right filter. It's usually better to use the embedded method than create own ones. – speedingdeer Aug 18 '16 at 21:28
  • I'm not sure random is an embedded method. I can't find any documentation for it. FWIW I wound up creating a loop in my controller: http://plnkr.co/edit/fBPgxWHXSp5UUGzsIFFe?p=preview – Steve Aug 18 '16 at 23:37
  • not random by orderBy, https://docs.angularjs.org/api/ng/filter/orderBy - I'm sure it is an embedded method. – speedingdeer Aug 19 '16 at 09:07
  • 1
    `orderBy`, yes. `orderBy: random`, no. Not in the docs or anywhere else I can find, except in some jsfiddles where a user created a function called `random` – Steve Aug 19 '16 at 12:44
  • @Steve true +1 my fault. I just always have random injected to my root scope and I forgot it isn't core. – speedingdeer Aug 19 '16 at 14:05
0

This answer is a good start for a filter but there are problems with it (see the comments). I would delete the answer but the comments may be useful to someone in the future. My new answer is a better way to solve this problem for now.

You can use a custom filter:

.filter('randomSample', function() {
    return function(array, length) {
        var copy = angular.copy(array);
        var sample = [];
        while(sample.length < length) {
            var randomIndex = Math.floor(Math.random() * (copy.length));
            sample.push(copy.splice(randomIndex, 1)[0]);
        }
        return sample;
    };
})

Use it like:

<li ng-repeat="item in array | randomSample:3">{{ item }}</li>

Here's an example on plnkr: http://plnkr.co/edit/NgsQlvgrCD7vLXnBC7q1?p=preview

csschapker
  • 129
  • 7
  • I tried it, and I must be missing something, as now all 6 items print out. I see your edit now. – Steve Aug 18 '16 at 19:58
  • try taking out the length parameter and hardcoding a 3 instead just to see if it works and changing `randomSample:3` to `randomSample` in html – csschapker Aug 18 '16 at 20:00
  • That works, except it keeps getting applied. It keeps loading another 3 items over and over instead of only on load. – Steve Aug 18 '16 at 20:02
  • Check out my plnkr. It was working perfectly for me there – csschapker Aug 18 '16 at 20:04
  • Also, with this filter you don't need to use `orderBy:random` so take that out if you're still using it – csschapker Aug 18 '16 at 20:06
  • Odd, it works with a simple plunkr: http://plnkr.co/edit/fBPgxWHXSp5UUGzsIFFe?p=preview – Steve Aug 18 '16 at 20:11
  • I see the problem now. Each time angular runs a `$scope.$apply` the filter will run again, changing the values. See my plnk again to see it: http://plnkr.co/edit/NgsQlvgrCD7vLXnBC7q1?p=preview – csschapker Aug 18 '16 at 20:17
  • Also I get this error for every iteration: Error: [ngRepeat:dupes] Duplicates in a repeater are not allowed. Use 'track by' expression to specify unique keys. Repeater: user in home.testimonials track by $index | randomSample:3, Duplicate key: [null,null,null], Duplicate value: {"name":"Jenn Church","title":"User Experience Designer","image":"jenn-church.jpg","quote":"The Design System has been a great tool in rapid prototyping, allowing me to get modern, on-brand interfaces put together quickly without having to start from scratch."} – Steve Aug 18 '16 at 20:17
  • The other thing that I can think to do is to use the code from the filter to make another array of the sample set in your controller and use that one. – csschapker Aug 18 '16 at 20:18
  • as for the dupes error, I think you can add the `track by` to after the filter, something like: `ng-repeat="user in home.testimonials | randomSample:3 track by $index"` – csschapker Aug 18 '16 at 20:26
  • So, how to stop the repeating iterations? It still happens and errors out with `Error: [$rootScope:infdig] 10 $digest() iterations reached. Aborting!` – Steve Aug 18 '16 at 20:30
  • Yeah, I'm looking for a way around that. It's because we are creating a new array in the filter. So far I'm thinking that you'll be better off just creating a sample array in your controller and displaying that. The filter is a fun idea though – csschapker Aug 18 '16 at 20:33
  • Also change `sample.push(copy[randomIndex]);` to this: `sample.push(copy.splice(randomIndex, 1)[0]);` – csschapker Aug 18 '16 at 20:45
  • 1
    I got it to work in the controller: http://plnkr.co/edit/fBPgxWHXSp5UUGzsIFFe?p=preview – Steve Aug 18 '16 at 23:36
0

After many attempts, it looks like it is better to get the random values in your controller after the array is populated. Like so:

_this.sample = [];
var copy = angular.copy(_this.testimonials);
while(_this.sample.length < 3) {
    var randomIndex = Math.floor(Math.random() * (copy.length));
    _this.sample.push(copy.splice(randomIndex, 1)[0]);
}

Then just ng-repeat="user in home.sample"

csschapker
  • 129
  • 7