Pagination

WireDrupal offers the ability to paginate results within a component.

Unfortunately, Drupal's existing pagination implementation is not flexible enough to be extended but a helper trait \Drupal\wire\WithPagination is available to help with the implementation.

Assuming you have a articles component, but you want to limit the results to 10 articles per page.

Here is an example of a simple prev/next pager implementation.

use Drupal\wire\Plugin\Attribute\WireComponent;
use Drupal\wire\WireComponentBase;
use Drupal\wire\View;
use Drupal\wire\WithPagination;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;

#[WireComponent(id: 'articles')]
class Articles extends WireComponentBase {

  use WithPagination;

  const ITEMS_PER_PAGE = 10;

  public string $search = '';

  protected string $pagerId = 'page';

  protected array $queryString = [
    'search' => ['except' => '', 'as' => 's'],
    'page' => ['except' => 0, 'as' => 'p'],
  ];

  protected ?EntityTypeManagerInterface $entityTypeManager;

  public static function create(
    ContainerInterface $container,
    array              $configuration,
                       $plugin_id,
                       $plugin_definition
  ) {
    $instance = new static($configuration, $plugin_id, $plugin_definition);
    $instance->entityTypeManager = $container->get('entity_type.manager');
    return $instance;
  }

  public function render(): ?View {

    $nodeStorage = $this->entityTypeManager->getStorage('node');
    $nodeQuery = $nodeStorage->getQuery()->accessCheck(TRUE);

    $getBaseQuery = function () use ($nodeQuery) {
      $thisQuery = clone $nodeQuery;
      $query = $thisQuery
        ->condition('type', 'article')
        ->condition('status', 1);
      if (!empty($this->search)) {
        $query->condition('title', '%' . addcslashes($this->search, '\%_') . '%', 'LIKE');
      }
      return $query;
    };

    $articleEntities = $nodeStorage->loadMultiple(
      $getBaseQuery()
        ->range(floor($this->page * self::ITEMS_PER_PAGE), self::ITEMS_PER_PAGE)
        ->execute()
    );

    $totalPages = ceil($getBaseQuery()->count()->execute() / self::ITEMS_PER_PAGE);

    return View::fromTpl('articles', [
      'items' => array_map(function ($article) {
        return $article->label();
      }, $articleEntities),
      'paginator' => $this->getPaginationData($totalPages),
    ]);
  }

  private function getPaginationData($totalPages): ?array {
    $items = [];

    if ($this->page > 0) {
      $items['previous']['hasItems'] = TRUE;
    }

    if ($this->page < ($totalPages - 1)) {
      $items['next']['hasItems'] = TRUE;
    }

    return ['items' => $items];
  }

}
<div>

  <input
    wire:model="search"
    type="search"
    placeholder="Search article by title..."
  >

  <h1>Articles</h1>

  <ul>

    {% for item in items %}

      <li>{{ item }}</li>

    {% endfor %}

  </ul>

  {% if paginator.items %}

    <nav role="navigation" aria-labelledby="pager-articles">

      {% if paginator.items.previous.hasItems %}
        <button
          wire:click="previousPage()"
          wire:loading.attr="disabled"
          type="button"
          title="{{ 'Go to previous page'|t }}"
        >
          {{ 'Prev'|t }}
        </button>
      {% endif %}

      {% if paginator.items.next.hasItems %}
        <button
          wire:click="nextPage()"
          wire:loading.attr="disabled"
          type="button"
          title="{{ 'Go to next page'|t }}"
        >
          {{ 'Next'|t }}
        </button>
      {% endif %}
    </nav>

  {% endif %}

</div>

 

* Note that the $pagerId property must be defined in order to identify the pager in case there are multiple ones on the same page.

* The previousPage() and nextPage() methods are provided by WithPagination trait which calls setPage() method. You should call this method if you're implementing a Full pagination to go to a specific page.

You might need to reset pagination to it's first page when performing any filtering of data as it would return a different set of results.

E.g: If a user is on the page "2" and types into a search field to narrow the results you might want to reset the page as first one to make sense of any pagination if any.

The WithPagination trait exposes a resetPage() method to accomplish this.

This method can be used in combination with the updating/updated lifecycle hooks to reset the page when certain component data is updated.

public function updatingSearch(): void {

  $this->resetPage();
  
}

If you implemented the search when a specific action, add $this->resetPage(); in the action method accordingly.

********************************** ************************* ************************ **************** ****************** *********** ************** ************* ************ *************