Подгрузка новых данных при скролле в AngularJS

Нужно показывать достаточно длинный (но не бесконечный) список фотографий, о котором известно, что в зависимости от разрешения фото могут показываться по одной или по две в ряд.
Делается это только на одном экране, а не во всем приложении (т.е. надо снимать обработчики событий и навешивать их снова при заходе на нужный экран).
Да, и еще — у нас нет jQuery, только AngularJS 1.0.8.
Сначала было решено делать все через сервис навешивания обработчиков событий, который снимает все обработчики при смене урла.
Но т.к. AngularJS-ное подобие не поддерживает нэймспэйсы для событий, то будут сниматься все обработчики на элементе, что неправильно.
Поэтому было решено сделать так:

app.js

app.run(['$rootScope', function($rootScope) {
	/**
	 * To proxy global window events to angular's
	 */
	(function ()
	{
		"use strict";
		var rotateEvent = 'onorientationchange' in window ? 'orientationchange' : 'resize';

		angular.element($window).
			bind('scroll', function () {
				$rootScope.$broadcast('scrolled.window');
			}).
			bind(rotateEvent, function () {
				$rootScope.$broadcast('rotated.window');
			});
	}());
}]);

Когда уходим на другой контроллер, $scope уничтожится, и обработчик вместе с ним.

Шаблон:

<div>
<ul>
<li ng-repeat="user in users">
<a ng-href="#/users/[[user.id]]"><img ng-src-preload="[[ user | avatar ]]" alt="" class="js-top100-photo" /></a>
</li>
</ul>
</div>

Сервисы:

.factory('Throttle', ['$rootScope', function throttle($rootScope) {/*будет показан отдельно*/}])
.factory('HelpersService', ['$rootScope', function HelpersService($rootScope) {
	var docEl = document.documentElement, docBody = document.body;
	return {
		/**
		 * Window height
		 */
		getDocumentHeight: function getDocumentHeight()
		{
			return window.innerHeight || docEl.clientHeight || docBody.clientHeight || docEl.offsetHeight || docBody.offsetHeight;
		},
		/**
		 * Scrolled distance
		 */
		getDocumentScrollTop: function getDocumentScrollTop()
		{
			return window.pageYOffset || window.scrollY || docEl.scrollTop || docBody.scrollTop;
		}
	};
}]);

Контроллер

controller('Top100ListCtrl', ['$scope', '$routeParams', 'HelpersService', 'Throttle',
	function Top100ListCtrl($scope, $routeParams, HelpersService, throttle) {
		...

		function loadMore()
		{
			// ...
		}

		/**
		 * Everything below is for infinite scroll-like behaviour.
		 * When user scrolls close to the bottom, more data loaded from server.
		 * Js needs to know about DOM:
		 *      the size of one picture (that's why we need selector 'js-top100-photo');
		 *      the size of the window;
		 *      that there is only one or two pictures in a row.
		 */

		/**
		 * Getting all photos from DOM by selectors.
		 * Made only once.
		 */
		var photo1,
			photo2,
			photo3;
		function getPhotos()
		{
			if (! photo1)
			{
				var rawPhotos = document.getElementsByClassName('js-top100-photo');
				photo1 = rawPhotos[0] ? angular.element(rawPhotos[0]) : null;
				photo2 = rawPhotos[0] ? angular.element(rawPhotos[1]) : null;
				photo3 = rawPhotos[0] ? angular.element(rawPhotos[2]) : null;
			}
			return {photo1: photo1, photo2: photo2, photo3: photo3};
		}

		/**
		 * Calculating and storing all heights.
		 * Made on screen load and on screen rotation.
		 */
		var twoInARow, rowHeight, winHeight;
		function calcHeights(photos)
		{
			var rect1 = photos.photo1[0].getBoundingClientRect(),
				rect2 = photos.photo2[0].getBoundingClientRect(),
				rect3 = photos.photo3[0].getBoundingClientRect(),
				photoHeight = rect1.height;
			twoInARow = (rect1.top + photoHeight) > rect2.top;
			rowHeight = twoInARow ? rect3.top - rect1.top : rect2.top - rect1.top;
			winHeight = HelpersService.getDocumentHeight();
		}

		/**
		 * Invoked as a callback on scroll (throttled) and on device rotation.
		 * @param {Boolean} isWindowChanged if device rotated (need recalc heights)
		 */
		function onScroll(isWindowChanged) {
			var photos = getPhotos();
			if (photos.photo1 && photos.photo2)
			{
				if (isWindowChanged || ! rowHeight) {
					calcHeights(photos);
				}
				if (HelpersService.getDocumentScrollTop() > (($scope.users.length - (twoInARow ? 4 : 2)) * rowHeight - winHeight)) {
					loadMore();
				}
			}
		}

		// throttling invoсation of callback for scrolling events
		var throttledOnScroll = throttle(200, $scope, function () { onScroll(); });
		$scope.$on('scrolled.window', throttledOnScroll);

		$scope.$on('rotated.window', function () { onScroll(true); });
}]).

2 комментария so far.

  1. А почему бы не использовать какой-нибудь плагин для этого, например https://github.com/BinaryMuse/ngInfiniteScroll

LEAVE A COMMENT