angular/material

virtualRepeat: with layout-wrap

Closed this issue ยท 49 comments

I have an <md-content layout="row" layout-wrap> which contains many <md-card> elements with fixed width and height. On the initial page load I load X number of them. This is all fine. What I would like to do is on scroll (infinite scroll) load more card data and display them. Is this possible with virtual-repeat now and if so, how? If not, how could I achieve the same effect without virtual-repeat?

Do you solve this issue or find a workaround ? I'm facing the same issue

If there a way to expose NUM_EXTRA property can sove the problem using a high number of extra components in the screen (i think it's ugly, but it's a way to solve this problem).

You should be able to do this with the md-on-demand feature. Have a look at the infinite scroll demo to see how you'd load in the additional data.

@kseamon I tried something like this, but the problem is, the card is only drawn when it gets near the top, leaving the bottom of the page blank. Maybe i made something wrong, here a codepen with the problem: http://codepen.io/eloyleonardo/pen/epjVBJ

Thanks

Ah, the problem is that the repeater is designed to only allow one item per row, if that makes sense. It computes its height by * which does not work when multiple items occupy each row.

We could maybe call this a feature request, but I've got a lot of higher priority items to work on for now.

Have the same issue so this feature would be great if it got implemented ๐Ÿ‘

Perhaps there is someone who can point me in the direction how to solve this?

Since we're now post 1.0, does anyone knows the state on this? I know that there is so much we can expect from a limited size team(which is doing a great job) but it seems to be a common UX gallery pattern for which frameworks like React is, unfortunately, eating Angular's lunch.
@kseamon, it was mentioned in issue #5312 that it would be handled around 1.08rc. Is there an ETA to add this feature or should folks finding other workarounds?

+1

+1

+1

+1

sarod commented

+1

+1

+1. md-virtual-repeat is a great directive but too limited for responsive design.

Is there a reason why this issue has been closed?

Can somebody reopen the issue as it is still unanswered?

Yeah I'd also love this reopened. Unless there is some other way that I can display a grid of dynamic content with high performance?

Folks,
In our case we had to display tile view, all of uniform dimensions. Each tile has an image and few text data bindings. At this point we were pressed for time and used angular tile-view. https://github.com/tinydesk/angular-tileview
Beware to remove any one-time-binding ('::binding') you might have in your tile. It worked pretty well for us on collections north of 20,000 elements.

@gkohen sorry, the demo not working.

@dpmontero , It's something that got broken in the demo 13 days ago. A new library dependency is pointing to the wrong place. I've opened an issue with the author:
tinydesk/angular-tileview#2
You can still download it and run it locally.

+1

+1

troig commented

+1

+1

@gkohen Thank your for the hint to the angular-tileviewproject. I have however my difficulties to embed this tile view into a angular material app since the scroll and container size handling seems off

I have the usual material design setup of a sticky md-toolbar and side-nav, and want to show the tile-view as main content (loaded in a ui-view).
When I try to use the tileview the whole page gets scrollable and it does not really load new content.

Did you have to tweak some css to get this to work? If you could provide an example how you integrated it with material that would be awesome.

Looking forward to the native material design support for this feature, which seems to be underway ๐Ÿ‘

I like how this issue is being ignored and postponed for more than a year now. Oh, did I mention it's in top 5 most commented ones?

As @kseamon mentioned a while ago, the issue here is that the virtual repeat is meant to be used for rendering single rows with similar heights. We'd have to a lot of refactoring to make it work with thumbnails.

You should be able to work around this limitation by grouping your repeated items into groups of 4 and using virtual repeat to iterate over the groups. The Material datepicker uses this to render a calendar and it works pretty well.

@crisbeto You realize that people use layout-wrap to dynamically create groups of X elements for each row depending on width of the element? Hardcoding the row size to be exactly 4 elements as you proposed is not a solution.

I'm saying that presumably people know how many items each row will have (I used 4 since one of the examples above used that). E.g. imagine that you get a flat array from your backend that looks like [1, 2, 3, 4, 5, 6, 7], you can split it into [[1, 2, 3], [4, 5, 6], [7]] and iterate over each of those sub-arrays with a virtual repeat:

<md-virtual-repeat-container>
  <div md-virtual-repeat="group in groups">
    <md-card style="width: 33%" ng-repeat="item in group"></md-card>
  </div>
</md-virtual-repeat-container>

Also I'm not saying that this isn't an issue, but it is something that can be worked around.

The part of "flat-array from the backend and splitting it" is what worries me the most. I assume that if you use virtual repeat, you deal with a lot of data. This is usually exposed as paged data.

This can be easily mapped by an implementation which dynamically loads a page if an item is accessed by getItemAtIndex(index). To make this work with the groups, we need an adapter implementation with the same interface, but which returns an array when getItemAtIndex(index) is called and an adjusted length.

Im going ahead and try to implement this as performance effective as possible, maybe such an adapter could be shipped as a workaround until this is resolved.

This basically means, that the data access interface the user has to provide, must not be aware at all that grouping is going on.

@crisbeto I understood what you proposed, but used wrong terms. Sorry.

After all, I guess I'll do a small directive that will split arrays into groups. Thanks.

By that adapter, do you mean something within Material @IsNull? That sounds like something that shouldn't be too much trouble to support.

Yes, the idea is that the user has to provide the data-source with the following interface (like he already has to do it)

// list-datasource interface
getItemAtIndex: function(index) [object]
getLength: function() [number]

Now, if multiple items have to be grouped, this can be done with an adapter, on top of the users list-datasource. This adapter provides the same interface, thus allowing composition. Assume GroupingAdapter is such an adapter, and the second argument would be the column count:

Pseudo code:

virtual-repeat.data = new GroupingAdapter(usersDataSource, 4);

The GroupingAdapter would just call the underlying usersDataSource. getItemAtIndex() for each column, and build an array dynamically. If the number of columns change, you would throw away the adapter and create a new instance, assuming the actual data is managed in the users datasource implementation.

I hope you can follow my wild thoughts ๐Ÿ˜„

Not sure I do the pseudo code, but the proposal definitely makes sense. I'll see what I can do.

@crisbeto What I meant was something like

.factory('RowDataSourceAdapter', function () {

        var RowDataSourceAdapter = function(dataSource, columns) {
            this.dataSource = dataSource;
            this.columns = columns;
        };

        /**
         * Calc the number of rows with the given number of columns
         * @returns {number}
         */
        RowDataSourceAdapter.prototype.getLength = function () {
            var numberOfItems = this.dataSource.getLength();
            return numberOfItems / this.columns;
        };

        /**
         * Gets the row at the given index
         * @param rowIndex
         * @returns {Array} The row items as array
         */
        RowDataSourceAdapter.prototype.getItemAtIndex = function (rowIndex) {

            // Returns the given rows data as array
            var realStartIndex = rowIndex * this.columns;
            var total = this.dataSource.getLength();

            var row = [];

            for (var i = 0; i < this.columns; i++) {
                var realIndex = realStartIndex + i;

                if(realIndex >= total){
                    break;
                }
                var item = this.dataSource.getItemAtIndex(realIndex);

                if(item){
                    row.push(item);
                }else{
                    // If a item from the row did not finish loading, we have to return null
                    // to indicate that this row item has to be retried later on.
                    row = null; 
                    break
                }
            }

            return row;
        };

        return RowDataSourceAdapter;
    });
kabb5 commented

+1

There are unfortunately no plans to add this feature. It is recommended that you look outside of AngularJS Material for a custom repeater component that can handle wrap layouts and dynamically sized items.