## Paginators

Pagination is a feature which appears often in Web Client and many modern web apps. In the Twitch community, a user may have thousands of followers, and a channel may have hundreds of feed posts. To provide the best experience to users, we usually want to fetch this data from the server and present it to the user in small chunks, often 10 or 20 at a time.

Pagination is closely related to Ember Data because the data that we want to paginate is often represented in the front-end as Ember Data models and relationships. We can often get useful information about the data we want to paginate from the relationship itself, such as how to query and serialize the data. However, pagination in Web Client does not and should not require Ember Data. Any data type can be paginated.

Web Client has a type of object called a Paginator which encapsulates a mutable array of data with knowledge of how to fetch and serialize pages of data. This feature is in two parts which are described in detail below:
 1. The Paginator Service, which creates instances of Paginator objects.
 2. Paginator Factories, which must be implemented for each type of data being paginated.

### Using the Paginator Service

The Paginator Service creates instances of Paginator objects using the `paginatorFor()` function. For example, given:
 * A model object called `channel`,
 * with a defined relationship `following: hasMany('channel')`,
 * and a paginator defined in `app/paginators/follow`,

the following code will create a paginator: `let page = service.paginatorFor({ model: channel, relationshipName: 'following', pageFactory: 'follow' })`. The resulting `page` object has a property `items` which is initially empty. Calling `fetchNextPage()` will fetch the first page and add it to the array. You can iterate like so:
```hbs
{{#each page.items as |item}}
  {{item-detail item=item}}
{{/each}}
````

Here is the documentation of `paginatorFor()`:

```js
// app/services/paginator
  /**
   * Instantiate a paginator object for a given `model` and `relationshipName`, using the specified `pageFactory`.
   *
   * @method paginatorFor
   * @param {Model} model
   * @param {String} relationshipName
   * @param {String} pageFactory corresponds to a paginator file in `app/paginators/${pageFactory}`
   * @return {Paginator} paginator object with empty array of items.
   */
  paginatorFor({model, relationshipName, pageFactory})
```

Implementing the paginator as a service, instead of paginating the relationship directly, has 2 benefits:
 * It allows us to render multiple paginations of the same relationship on the page. For example you could have 2 columns of followers for a user, sorted into friends and non-friends, by using 2 page objects.
 * It preserves the canonical nature of the relationship. The relationship should always represent the relationship in its entirety. If we make the relationship itself be paginated, we lose the ability to also work with an array of all the IDs for example. We also lose the meaning of adding an subtracting objects from a relationship itself.

### Implementing a Paginator Factory

Paginator Objects encapsulate a mutable array of data with knowledge of how to fetch and serialize pages of data. In most cases, the object will require a model and a relationship name to know how to construct a URL to query for pages of data based on the ID of the model and the datatype of the relationship.

Paginator objects are EmberObject instances which implement the following interface:
```js
// app/paginators/-base.js
export default EmberObject.extend({
  // An array of items which is initially empty, grows as data is fetched, and can be observed for changes.
  items: [],

  // the total number of items available via pagination, which starts at 0 and is (hopefully)
  // determined from the meta data in the first request.
  total: 0,

  // Fetch the next page of data and append to the items array.
  // Returns a promise which resolves itself (ie. the paginator object, not the new items)
  fetchNextPage(),

  // Knows how to actually fetch the data. Called internally by fetchNextPage().
  // This function usually has to be implemented for new data types but should not be explicitly called
  doQuery()
});
```
There are currently 2 examples of paginator factories in the app:

 * `app/paginators/follow` - works for the `following` and `followers` relationships on a `channel`. Gets the URL for the first page directly off the relationship. Subsequent pages use the URL defined in the `meta.links.next` key in the received payload.
 * `app/paginators/feed-post` - the URL is constructed using a cursor which starts and empty and then updates from the meta.
