Consulta dinâmica com paginação utilizando AngularJS e PHP

Neste artigo pretendo mostrar como fazer uma consulta de registros completamente dinâmica, ou seja, sem a necessidade de recarregar a página para filtrar ou paginar os registros. Utilizaremos como exemplo uma simples consulta de clientes onde o usuário poderá filtrá-los e navegar entre as páginas.

No lado cliente iremos utilizar o AngularJS para facilitar a comunicação com o servidor, além de manter um código mais organizado e reutilizável. Atualmente já temos o Angular 2, porém ainda gosto da simplicidade do AngularJS e acredito que se encaixa melhor no propósito deste artigo.

No lado servidor iremos apenas utilizar uma biblioteca de Query Builder para montarmos nossa query de consulta com o banco de dados. Hoje em dia é muito comum a utilização de um Query Builder em frameworks e no nosso caso vai facilitar muito a paginação dos registros.

Confira o exemplo em funcionamento, clicando aqui.

O banco de dados

Vamos criar nossa tabela no banco de dados e inserir alguns registros para teste. No nosso exemplo, iremos utilizar MySQL.

CREATE TABLE `clientes` (
 `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
 `nome` varchar(60) NOT NULL,
 `telefone` varchar(15),
 `email` varchar(80),
 PRIMARY KEY (`id`)
) ENGINE=InnoDB;

INSERT INTO `clientes` (`id`, `nome`, `telefone`, `email`) VALUES (1, 'Kaua Ribeiro Cardoso', '(74) 8414-6200', 'KauaRibeiroCardoso@armyspy.com');
INSERT INTO `clientes` (`id`, `nome`, `telefone`, `email`) VALUES (2, 'Guilherme Ferreira Barros', '(12) 8865-4022', 'GuilhermeFerreiraBarros@teleworm.us');
INSERT INTO `clientes` (`id`, `nome`, `telefone`, `email`) VALUES (3, 'Murilo Azevedo Martins', '(96) 9572-6402', 'MuriloAzevedoMartins@jourrapide.com');
INSERT INTO `clientes` (`id`, `nome`, `telefone`, `email`) VALUES (4, 'Gabriel Melo Castro', '(11) 6048-4953', 'GabrielMeloCastro@rhyta.com');
INSERT INTO `clientes` (`id`, `nome`, `telefone`, `email`) VALUES (5, 'Luan Castro Goncalves', '(21) 8790-5998', 'LuanCastroGoncalves@rhyta.com');
INSERT INTO `clientes` (`id`, `nome`, `telefone`, `email`) VALUES (6, 'Sophia Sousa Almeida', '(62) 4566-3895', 'SophiaSousaAlmeida@teleworm.us');
INSERT INTO `clientes` (`id`, `nome`, `telefone`, `email`) VALUES (7, 'Fernanda Carvalho Pinto', '(44) 5505-7193', 'FernandaCarvalhoPinto@dayrep.com');
INSERT INTO `clientes` (`id`, `nome`, `telefone`, `email`) VALUES (8, 'Rebeca Sousa Cunha', '(14) 5928-5119', 'RebecaSousaCunha@rhyta.com');

O lado cliente

Javascript

Primeiramente, vamos criar nosso module.

angular.module('appModule', ['bw.paging']);

No segundo parâmetro, definimos que que nosso module depende de um outro module chamado bw.paging. Esse module será útil para fazermos a paginação mais adiante.

Vamos então criar o nosso controller responsável por gerenciar a página de consulta.

angular.module('appModule')
    .controller('consultaController', function($http) {

        // Armazenando o objeto do controller para evitar conflitos
        var self = this;

        // Filtros da consulta
        self.filtros = [];

        // Lista de clientes
        self.clientes = [];

        // Controla a exibição da barra de progresso da pesquisa
        self.consultando = false;

        // Método responsável pela consulta dos registros
        self.consultar = function(pagina) {

            // Se nenhuma página definida, define como primeira página
            if (!pagina) pagina = 1;

            // Habilita a barra de progresso
            self.consultando = true;

            // Fazendo requisição AJAX com o servidor e passando os filtros por parâmetro
            $http.get('api/consulta.php?pagina=' + pagina, {
                params: self.filtros
            }).then(function(response) {

                // Armazena os clientes retornados
                self.clientes = response.data.registros;

                // Definindo dados para a paginação
                self.paginacao = response.data.paginacao;

            }).finally(function() {

                // Quando terminar a requisição, desabilita a barra de progresso
                self.consultando = false;

            });
        };

    });

  • Linha 2: Declaramos o nosso controller e injetamos o $http que é um serviço do AngularJS para fazer requisições AJAX.
  • Linha 5: O controller é nada mais que uma função em Javascript, portanto o this é referente a esta função. Porém, se declaramos uma função dentro do controller, o this dentro desta função é referente à ela. Portanto, apenas armazenamos o objeto do controller na variável self pois assim podemos acessar suas propriedades independente do escopo.
  • Linha 26: Através do serviço $http fazemos uma requisição AJAX com o servidor utilizando o método GET. Enviamos a página atual e os filtros para recuperarmos no servidor. Se você utiliza jQuery, isso é equivalente ao $.get.

HTML

Vamos incluir em nossa página as dependências.

<!DOCTYPE html>
<html lang="pt-br">
<head>
    <meta charset="UTF-8">
    <title>Consulta de clientes</title>

    <link rel="stylesheet" href="css/materialize.min.css">
    <link rel="stylesheet" href="css/icon.css">

    <script type="text/javascript" src="js/lib/angular.min.js"></script>
    <script type="text/javascript" src="js/lib/paging.min.js"></script>

    <script type="text/javascript" src="js/app/app.js"></script>
    <script type="text/javascript" src="js/app/controllers/consulta.js"></script>

</head>

Para nossa página ter um visual mais moderno, incluímos o Materialize. Além disso, incluímos a biblioteca do AngularJS, o componente de paginação e os arquivos que criamos anteriormente.
<!-- Definindo aplicativo que será utilizado -->
<div class="container" ng-app="appModule">

    <h3>Consulta de clientes</h3>

    <!-- Definindo controller que será utilizado e fazendo pesquisa inicial -->
    <div ng-controller="consultaController as ctrl" ng-init="ctrl.consultar()">

        <!-- Formulário para pesquisar com os filtros -->
        <form ng-submit="ctrl.consultar()">

            <button class="btn" type="submit" name="action">Pesquisar
                <i class="material-icons left">search</i>
            </button>

            <div class="row">

                <div class="input-field col s4">
                    <input placeholder="Nome" type="text" ng-model="ctrl.filtros.nome">
                </div>
                <div class="input-field col s4">
                    <input placeholder="Telefone" type="text" ng-model="ctrl.filtros.telefone">
                </div>
                <div class="input-field col s4">
                    <input placeholder="Email" type="text" ng-model="ctrl.filtros.email">
                </div>

            </div>

        </form>

        <!-- Se barra de progresso habilitada, exibe ela -->
        <div class="progress" ng-show="ctrl.consultando">
            <div class="indeterminate"></div>
        </div>

        <!-- Se barra de progresso desabilitada, exibe a tabela com os registros -->
        <div ng-show="!ctrl.consultando">

            <table class="bordered striped">

                <thead>
                <tr>
                    <th>Nome</th>
                    <th>Telefone</th>
                    <th>Email</th>
                </tr>
                </thead>

                <tbody>

                <!-- Construindo linhas e colunas de acordo com a lista de clientes -->
                <tr ng-repeat="cliente in ctrl.clientes">
                    <td ng-bind="cliente.nome"></td>
                    <td ng-bind="cliente.telefone"></td>
                    <td ng-bind="cliente.email"></td>
                </tr>

                <!-- Se não houver nenhum cliente na lista -->
                <tr ng-if="ctrl.clientes.length == 0">
                    <td colspan="3" class="center-align">Nenhum cliente encontrado</td>
                </tr>

                </tbody>

            </table>

            <!-- Definindo informações de paginação do componente -->
            <paging class="center-align" page="ctrl.paginacao.pagina_atual"
                    page-size="ctrl.paginacao.registros_por_pagina" total="ctrl.paginacao.total_registros"
                    paging-action="ctrl.consultar(page)" show-prev-next="true" ></paging>

        </div>

    </div>

</div>

Um dos recursos que eu mais gosto no AngularJS é o Data Binding. Esse recurso nos permite a separação da lógica (controller) com a página (view), ou seja, nós definimos uma propriedade no controller, vinculamos na view e o framework se encarrega de atualizar a view quando a propriedade for alterada e vice-versa.

  • Linha 2: Através do ng-app definimos que esta div
    é a raiz do nosso module e dentro dela poderemos fazer a chamada dos controllers.
  • Linha 7: Através do ng-controller definimos que esta div
    é a raiz do nosso controller e dentro dela poderemos acessar suas propriedades e métodos. O ng-init é sempre chamado na criação do elemento, ou seja, quando a div for criada chamaremos o método consultar definido no controller, fazendo assim a consulta inicial.
  • Linha 10: Criamos um formulário onde colocaremos os filtros e através do ng-submit definimos que quando esse formulário for enviado, seja clicando no botão de submit ou através do enter, chamaremos método consultar definido no controller.
  • Linha 19, 22, 25: O ng-model nos permite vincular um campo de formulário com uma propriedade no controller, sendo assim sempre que o campo for atualizado, a propriedade será atualizada e vice-versa.
  • Linha 33: O ng-show nos permite definir se o elemento será mostrado de acordo com a condição. No caso, se a propriedade consultando for true será exibido a barra de progresso.
  • Linha 38: Aqui utilizamos o ng-show, porém negando a condição, ou seja, se a propriedade consultando for false será exibido tabela de clientes.
  • Linha 53 até 57: O ng-repeat nos permite repetir um elemento de acordo com uma collection. No caso, iremos repetir o elemento tr (que cria uma linha na tabela) para cada item da propriedade clientes. Para cada linha que for criada criaremos as colunas e através do ng-bind vinculamos a coluna com sua respectiva propriedade do objeto cliente.
  • Linha 60: O ng-if nos permite definir se um elemento será criado ou não através de uma condição. Neste caso, se não houver nenhum item em nossa collection de clientes, essa linha será exibida.
  • Linha 69 até 71: Utilizamos a directive de paginação que adicionamos ao nosso module no inicio. Nos atributos definimos as informações necessárias para a geração do elemento. Você pode ver a documentação completa da directive aqui.

O lado servidor

Precisamos agora desenvolver o código para consultar os registros no banco de dados. Porém, antes de qualquer coisa vamos instalar a biblioteca de Query Builder utilizando o Composer. Para isto vamos criar nosso composer.json.

{
  "require": {
    "usmanhalalit/pixie": "2.*@dev"
  }
}

Agora basta rodar composer install no terminal e podemos começar. Primeiramente, vamos criar um arquivo para configuração e conexão com o banco de dados.
<?php

// Definindo dados para conexão com o banco de dados
$config = [
    'driver'   => 'mysql',
    'host'     => 'localhost',
    'database' => 'rafaelcouto',
    'username' => 'root',
    'password' => '',
];

// Iniciando objeto de conexão da biblioteca
// A partir de agora podemos instanciar uma query utilizando QB
new \Pixie\Connection('mysql', $config, 'QB');

Simples assim. A biblioteca já faz a conexão com base nas informações que passamos e disponibiliza o Query Builder que pode ser acessado através do alias QB, definido no terceiro parâmetro.
<?php

// Incluindo biblioteca e configuração do banco de dados
require 'vendor/autoload.php';
require 'config/database.php';

// Quantidade de registros que serão exibidos por página
$registrosPorPagina = 3;

// Recupera a página atual, caso não tenha sida definida, define como a primeira página
$paginaAtual = (isset($_GET['pagina']) ? intval($_GET['pagina']) : 1);

// Inicia a query na tabela de clientes
$query = QB::table('clientes');

// Definindo filtros
// Nome
if (!empty($_GET['nome'])) {
    $query->where('nome', 'LIKE', '%' . $_GET['nome'] . '%');
}

// Telefone
if (!empty($_GET['telefone'])) {
    $query->where('telefone', 'LIKE', '%' . $_GET['telefone'] . '%');
}

// Email
if (!empty($_GET['email'])) {
    $query->where('email', 'LIKE', '%' . $_GET['email'] . '%');
}

// Ordenando
$query->orderBy('nome');

// Aplicando paginação
$query->limit($registrosPorPagina)->offset(($paginaAtual - 1) * $registrosPorPagina);

// Executando query e armazenando os registros retornados
$registros = $query->get();

// Definindo quantidade total de registros
$totalRegistros = $query->count();

// Retornando os dados no formato JSON
echo json_encode([
    'registros' => $registros,
    'paginacao' => [
        'registros_por_pagina' => $registrosPorPagina,
        'pagina_atual'         => $paginaAtual,
        'total_registros'      => $totalRegistros,
    ],
]);

  • Linha 18 até 20: No controller do lado cliente passamos os filtros por parâmetro. Portanto, verificamos se o filtro existe e aplicamos um where em nossa query. A vantagem do Query Builder neste caso é que a query é um objeto, portanto apenas chamamos o método where, muito mais simples e organizado do que fazer o SQL manualmente.
  • Linha 36: É aqui que a paginação acontece. Através do limit definimos a quantidade de registros que queremos, no caso 3. E através do offset a partir de qual registro vamos começar. Por exemplo:
    • Página 1: (1 – 1 * 3 = 0) – Começando do primeiro registro;
    • Página 2: (2 – 1 * 3 = 3) – Começando depois do terceiro registro;
  • Linha 39: O Query Builder irá construir e executar o comando SQL. Por exemplo, se tivermos na primeira página sem nenhum filtro, ele irá executar o SQL abaixo.
    SELECT * FROM `clientes` ORDER BY `nome` ASC LIMIT 3 OFFSET 0
    
  • Linha 42: Para saber a quantidade de páginas, precisamos saber a quantidade total de registros, portanto precisamos fazer duas consultas no banco de dados; uma para buscar os dados e a outra para contar os registros. Com o Query Builder isso fica muito simples, pois basta reutilizar o objeto da query para contar a quantidade de registros.

Conclusão

Neste artigo, vimos como fazer uma consulta com a possibilidade de filtrar e paginar os registros, sem a necessidade de recarregar toda a página.

No lado cliente, utilizamos AngularJS para manter o código mais simples e organizado. Você poderia fazer a mesma coisa utilizando jQuery ou Javascript puro, porém teria que escrever muito mais linhas de código e provavelmente não teria a separação das camadas de lógica e visualização.

No lado servidor, utilizamos uma biblioteca de Query Builder para evitar duplicação e manter um código mais limpo do que utilizar SQL puro. Você poderia muito bem utilizar as funções do próprio PHP para fazer a consulta, porém seria um pouco mais trabalhoso.

Enfim, espero que este artigo tenha sido útil de alguma forma para você. Até a próxima.

Código fonte disponível em: https://github.com/rafaelcouto/consulta-dinamica-com-paginacao-utilizando-angularjs-e-php/

Referências