airbnb / infinity

UITableViews for the web (DEPRECATED)
http://airbnb.io/infinity/
Other
2.8k stars 281 forks source link

AngularJS integration #21

Open hkdobrev opened 11 years ago

hkdobrev commented 11 years ago

AngularJS is a great MVC framework for web apps created and supported by Google.

http://angularjs.org

It has some awesome features, but the one I am using for a list with infinite scroll allows me to append more elements into the DOM by just updating a JavaScript array with a JSON response from the server.

infinity.js is requiring to use HTML snippets when appending items.

I am trying to port infinity.js to be used with AngularJS with a custom directive. Similar to angular-ui which ports all jQuery UI plugins to be used easily in AngularJS.

How should I approach this? How could I append items using infinity.js when I don't have the HTML source which should be appended?

arush commented 11 years ago

Don't know if it is worth the effort buddy. Have you seent this? http://binarymuse.github.io/ngInfiniteScroll/demos.html

hkdobrev commented 11 years ago

Thanks for your effort, but this directive is a simple infinite scroll for AngularJS which I've implemented myself several times. My problem is the slow scrolling after a few hundred items. These problems are well handled by inifinity.

Perhaps there is a better way implementing infinity features in AngularJS by rewriting it entirely having in mind the two-way data bindings and the models.

Cheers!

pareeohnos commented 11 years ago

Not sure if you've figured anything out but I've just been trying to figure out the same thing and came up with the following solution, consisting of 2 directives:

The infinity-scroll initialises the ListView, and the infinity-item adds each item into the list (I'm using it in conjunction with an ng-repeat directive). It looks a little like this :

  myApp.directive('infinityScroll", function() {
    'use strict';

    return {
      restrict "A",
      transclude: true,

      template: '<div class="scrollable"><div ng-transclude></div></div>',
      link: function(scope, elm, $attrs) {

        var listView = new infinity.ListView(elm.find("[ng-transclude]:first"), {
          lazy: function() {
            this.find("[infinity-item]").each(function() {
              var $ref = this;
              $ref.attr('src', $ref.attr('data-original'));
            })
          }
        });
        elm.data("listView", listView);
      },

      replace: true
    }
});

So this will initialise the ListView. Then, the items are controlled by :

myApp.directive("infinityItem", function() {
  'use strict';
  return {
    restrict: "A",
    link: function(scope, element, attrs) {
      var listView = element.parents("[infinity]").data("listView");
      if (listView) {
        listView.append(element);
      }
    }
  }
});

This basically just moves each element into the list via the append method of ListView. Then to use it, I just have

<div infinity-scroll>
    <div infinity-item ng-repeat"item in items">
        ..item content here...
    </div>
</div>

And in theory you should get something like what you're after. At least it's working for me :)

Hope this helps somewhat

pareeohnos commented 11 years ago

Actually that needs some work. I found the way infinity loads the items, a lot of them don't have a parent element above the infinity wrappers so that code fails. What I've done so far is to change the infinity-item to infinity-item="id-of-list" instead and then the directive looks for the list with that ID.

This is working well, but I've now run into the issue that the scrollable area is long enough, however infinity isn't swapping the pages. I just get a big blank area under the initial pages :(

hkdobrev commented 11 years ago

@pareeohnos :+1: You did some good work there! Do you have a jsfiddle or a repo where I could play with it?

pareeohnos commented 11 years ago

Yeah give this a go :) I've just put it together now. It's slightly different from the code I posted due to it having an error, but I've sorted that out. You also have to make use of the infinity.js version that is on github and NOT on the main website as it makes uses of an update that hasn't been made available on the main site yet (NO idea why!)

http://jsfiddle.net/6Xz46/1/

See if that's what you're after :) If you've got any questions on it let me know

stvhanna commented 10 years ago

@pareeohnos awesome code! I tried your demo at http://jsfiddle.net/6Xz46/4/ and it works nicely for a fixed number of items in an array, but I'm trying to get it to work with an array that has dynamically added content. Please check out my demo here: http://jsfiddle.net/Q5uA6/1/. I would greatly appreciate it if you have any suggestions. :)

@hkdobrev did you find a working integration of infinity.js & angular.js? Like you, I'm also familiar with http://binarymuse.github.io/ngInfiniteScroll/index.html but want an efficient way of dealing with large lists, especially on mobile. I've found https://github.com/stackfull/angular-virtual-scroll (demo: http://demo.stackfull.com/virtual-scroll), which should do something similar to infinity.js, but I haven't tried it yet.

pareeohnos commented 10 years ago

Hey @thestevehanna sorry for the delay in replying. I think that in this case, the ng-repeat is causing the list to mess up, most likely in the way that angular is performing the render to add the new items which is a pain. I'd say that you'd need to modify the directive on the list container, so that it was watching the list property itself. Then when the list changed it could re-initialise the list view. It's a pretty horrible solution though, and inefficient as it would be creating a new list every time, so there's got to be a nicer way of doing it.

I think what is essentially happening, is angular is rendering the items once which works fine. Then when the list changes, angular re-renders the list, but because we moved the element into a different container, it has to render it again, which causes the item directive to fire again. Not entirely sure why infinity then messes up though, as in theory it's just being given new elements to add to its list.

To be perfectly honest, I wrote this directive when I first started learning angular so it's probably not the best way of doing things, but I've not tried working with infinity JS again since, so I'm sure there's a much better solution to this :)

Whilst quite complex, I think that the best approach for this be to create a custom ng-repeat directive which instead of rendering a new element, would simply add the element to the list view instead. This way there wouldn't be the need for the individual item directives which is great for performance, and you also wouldn't be fighting with the ng-repeat directive as you wouldn't be using it. This directive could also quite simply be a copy and paste of the existing ng-repeat code, and simply changing the code that inserts the new element into the DOM, with code that appends it to the list view, and the same for removing elements. I'd say this will most likely be the best approach