Paginação no AngularJs Artigo

Conheça os cursos gratuitos do WebDevBr! - Inscreva-se!


Este artigo foi publicado a 1 ano, 4 meses, 4 semanas, 2 dias atrás.

Neste artigo quero ilustrar como criar um sistema de paginação com AngularJs, mas mais que isso, quero ilustrar como você pode chegar no resultado final de forma organizada.

Uma coisa que aprendi com orientação a objetos organizar minhas aplicações de forma a facilitar a expansão e a manutenção do código, isso quer dizer que eu gasto um tempo a mais durante a~s etapas iniciais para que possa economizar mais no futuro, e isso fez muito sentido pra mim até agora.

Neste artigo vou mostrar uma forma de paginar os dados, mas com o máximo de lógica movida para um service do AngularJs.

Gostou deste artigo?

Receba atualizações semanais com novos artigos do WebDevBr e outras dicas!

Estrutura

Para este exemplo vou usar o AngularJs 1.5.5 com o seguinte HTML:

<!DOCTYPE html>
<html lang="pt-br">
<head>
    <meta charset="UTF-8">
    <title>Angular Pagination</title>
    <script src="libs/angularjs/angular.min.js"></script>
    <script src="js/pagination.js"></script>
    <script src="js/app.js"></script>
</head>
<body ng-app="app">
    <main>
        <section id="content" ng-controller="PaginationCtrl">
            <h3>Paginação</h3>
            <hr>
            <ul>
                <li ng-repeat="item in paginate.result.data">
                    {{item.id}} - {{item.name}}
                </li>
            </ul>

            <ul>
                <li>
                    <a href="" ng-click="paginate.prev()"><span aria-hidden="true">Anterior</span></a>
                </li>
                <li>
                    <a href="" ng-click="paginate.next()"><span aria-hidden="true">Próximo</span></a>
                </li>
            </ul>

            <p>Mostrando registros de {{paginate.result.from}} até {{paginate.result.to}} de um total de {{paginate.total}}</p>
        </section>
    </main>

</body>
</html>

Nada de novo aqui, você pode observar que eu adicionei o Angular e 2 arquivos, um pagination.js e um app.js. O pagination.js vai guardar o service que fará a mágica acontecer e o app.js será o controller que dirá o que o service vai fazer, desta forma fica mais fácil para você aplicar a páginação em várias partes do seu projeto, que é o ideal.

Também vou deixar aqui 3 arquivos json que vamos usar como dados de exemplo, salve-os dentro de um diretório resources na raiz do projeto.

https://gist.github.com/erikfig/25c8d3abbadbdc719039c4fd9e72c26e.

Estes arquivos vão simular os dados vindos de um servidor, eles estão no padrão usado pela api do Facebook e os dados que queremos paginar estão no nó data. Estas informações são da minha lista de amigos, eu alterei a quantidade total, nome e id para garantir a privacidade dos meus contatos e, claro, a minha.

Existem duas formas de paginar estes dados, requisitar tudo e paginar no cliente com a vantagem de já ter os dados e o usuário final não precisar esperar outra requisição, ou paginar via demanda, ou seja, o servidor vai paginar e você vai mostrar isso, ideal para uma quantidade de items que vá atrapalhar o desempenho da sua aplicação de alguma forma.

Paginar dados no AngularJs

Neste exemplo vamos paginar os dados já disponíveis, ou seja, você trouxe tudo do servidor e agora vai paginar diretamente no seu módulo do AngularJs, teremos dois arquivos dentro de um diretório js:

No arquivo app.js adicione o seguinte código:

(function(){
    'use strict';

    var module = angular.module('app', ['Pagination']);

    module.controller('SimplePaginationCtrl', function($scope, $http, SimplePaginate) {
        var success = function(response) {
            //aqui vamos configurar a paginação
            //o SimplePaginate que injetei será o service responsável por isso
        }

        $http.get('resource/data.json').then(success);
    });
})()

E no pagination.js:

(function(){
    'use strict';

    var module = angular.module('Pagination', []);

    module.service('SimplePaginate', function() {
        //variáveis de configuração
        var data = {}; //os dados a paginar
        var current = 0; //página atual
        var perPage = 10; //quantidade de itens por página

        //Seta a configuração da paginação
        var configure = function(config) {

        }

        //Informa o total de registros com base em um array
        var itemsTotal = function () {

        }

        //Calcula o total de páginas da paginação
        var pagesTotal =  function () {

        }

        //vai para uma página específica
        var goToPage = function (indice) {

        }

        //avança paginação se possível
        var next = function() {

        }

        //volta paginação se possível
        var prev = function() {

        }

        //passo os métodos para o service
        this.configure = configure;
        this.itemsTotal  = itemsTotal;
        this.pagesTotal = pagesTotal;
        this.goToPage = goToPage;
        this.next = next;
        this.prev = prev;
    });
})()

É claro que é uma "estrutura base" do que vamos fazer, vou começar pelo pagination.js.

Temos algumas variáveis e alguns métodos, comentei bem as variáveis e acredito que dispensa maiores explicações, o método configure() vai permitir alterar o valor das variáveis de forma a configurar os dados a serem exibidos (óbvio, não?), ele deve se parecer com:

if (config.perPage !== undefined) {
    perPage = config.perPage;
}
if (config.data !== undefined) {
    data = config.data;
}

Claro que este código vai dentro do configure() (só pra reforçar), eu não altero o current aqui, isso será feito pelos métodos de paginação.

O método itemsTotal() será responsável por calcular e retornar o total de itens a serem paginados, ele deve receber o seguinte conteúdo:

return data.length;

Simples, não?

O pagesTotal() vai calcular a quantidade de páginas que teremos com base na quantidade de itens totais e itens por página, por exemplo, se eu tiver 30 itens e passar um perPage = 5 teremos 6 páginas, se eu informar perPage = 20, teremos 2 páginas, e por ai vai. O código dele é:

return Math.ceil(itemsTotal() / perPage);

E finalmente o cara que faz a mágica, goToPage() traz os dados da paginação informada, lembrando que as páginas começam em 0, então se eu passar 2, na verdade estou indo para a 3ª página, você já deve ser familiarizado com isso. O método se parecerá com:

current = indice;

if (indice > 0) {
    indice = indice*perPage;
}
var final = indice+perPage;

if (final > itemsTotal()) {
    final = itemsTotal();
}

return {
    data: data.slice(indice, final),
    from: indice+1,
    to: final
};

Note que eu retorno um objeto com três chaves, dentro da chave data estão os dados que irão para o ng-repeat, dentro de fromestá o número do primeiro item listado, e em to o último, estes dois últimossão para você poder incluir um texto tipo: "Mostrando resultados de {{from}} até {{to}}".

Pra facilitar um pouco nossa vida, coloquei dois métodos que usam o goToPage(), é o next() para avançar para a próxima página e o prev() que volta, também inclui em ambos uma verificação para garantir que não vá pra frente caso esteja na última página e nem volte caso esteja na primeira.

O método next receberá:

if (current+1 == pagesTotal()) {
    return goToPage(current);
}
current++;
return goToPage(current);

E o prev():

if (current == 0) {
    return goToPage(current);
}
current--;
return goToPage(current);

O arquivo completo ficou assim:

(function(){
    'use strict';

    var module = angular.module('Pagination', []);

    module.service('SimplePaginate', function() {
        //variáveis de configuração
        var data = {}; //os dados a paginar
        var current = 0; //página atual
        var perPage = 10; //quantidade de itens por página

        //Seta a configuração da paginação
        var configure = function(config) {
            if (config.perPage !== undefined) {
                perPage = config.perPage;
            }
            if (config.data !== undefined) {
                data = config.data;
            }
        }

        //Informa o total de registros com base em um array
        var itemsTotal = function () {
            return data.length;
        }

        //Calcula o total de páginas da paginação
        var pagesTotal =  function () {
            return Math.ceil(itemsTotal() / perPage);
        }

        //vai para uma página específica
        var goToPage = function (indice) {
            current = indice;

            if (indice > 0) {
                indice = indice*perPage;
            }
            var final = indice+perPage;

            if (final > itemsTotal()) {
                final = itemsTotal();
            }

            return {
                data: data.slice(indice, final),
                from: indice+1,
                to: final
            };
        }

        //avança paginação se possível
        var next = function() {
            if (current+1 == pagesTotal()) {
                return goToPage(current);
            }
            current++;
            return goToPage(current);
        }

        //volta paginação se possível
        var prev = function() {
            if (current == 0) {
                return goToPage(current);
            }
            current--;
            return goToPage(current);
        }

        //passo os métodos para o service
        this.configure = configure;
        this.itemsTotal  = itemsTotal;
        this.pagesTotal = pagesTotal;
        this.goToPage = goToPage;
        this.next = next;
        this.prev = prev;
    });
})()

Legal, e como usso isso? Simples, no controller dentro do arquivo app.js, inclua isto dentro do método success:

    SimplePaginate.configure({
        data:response.data.data,
        perPage: 10
    });

    $scope.paginate = {
        result : SimplePaginate.goToPage(0),
        total : SimplePaginate.itemsTotal(),
        next : function() {
            $scope.paginate.result = SimplePaginate.next();
        },
        prev : function() {
            $scope.paginate.result = SimplePaginate.prev();
        }
    };

Eu tenho dois blocos de código, no primeiro eu configuro os dados passando para o data a listagem de itens que vou paginar e informando uma limite de 10 itens por página, quero lembrar que eu só inclui o perPage: 10 para ficar explicito para você, na verdade ele não é necessário já que esse é o valor padrão.

No segundo bloco eu simplesmente criei um objeto chamado paginate que recebe os dados que eu preciso pra paginação:

  • result: Os dados já paginados e o itens from e to
  • total: O total de itens que eu tenho
  • next: um método para avançar para a próxima página na view
  • prev: um método para voltar para a página anterior na view

O app.js final deve estar assim:

(function(){
    'use strict';

    var module = angular.module('app', ['Pagination']);

    module.controller('SimplePaginationCtrl', function($scope, $http, SimplePaginate) {
        var success = function(response) {
            SimplePaginate.configure({
                data:response.data.data,
                perPage: 10
            });

            $scope.paginate = {
                result : SimplePaginate.goToPage(0),
                total : SimplePaginate.itemsTotal(),
                next : function() {
                    $scope.paginate.result = SimplePaginate.next();
                },
                prev : function() {
                    $scope.paginate.result = SimplePaginate.prev();
                }
            };
        }

        $http.get('resource/data.json').then(success);
    });
})()

Paginação sob demanda (no servidor)

Em vários casos este formato se torna inviável já que podemos ter muitos registros (1.000, 10.000 ou mais) que podem prejudicar a performance da aplicação. Neste ponto, vale a pena trazer os dados já paginados do servidor, pode ser que a API tenha um formato sequencial de paginação (http://server/api/users?page=1, http://server/api/users?page=2 ...) ou não, neste caso vou usar um retorno que já traz a url da próxima página e da página anterior, minha espectativa é que você consiga adaptar para a sua situação o melhor possível.

Novamente vamos voltar a estrutura base dos arquivos app.js e pagination.js.

app.js

(function(){
    'use strict';

    var module = angular.module('app', ['Pagination']);

    module.controller('ServerPaginationCtrl', function($scope, $http, SeverPaginate) {
        var success = function(response) {
            //aqui vamos configurar a paginação
            //o SeverPaginate que injetei será o service responsável por isso
        }

        var goToPage = function(url) {
            $http.get(url).then(success);
        }

        goToPage('resource/data.json');
    });
})()

pagination.js

(function(){
    'use strict';

    var module = angular.module('Pagination', []);

    module.service('SeverPaginate', function($http) {
        //variáveis de configuração
        var data = {};
        var current = 0;
        var perPage = 0;
        var total = 0;

        var next = null;
        var prev = null;

        var goToPage = function(url) {

        }

        //Seta a configuração da paginação
        var configure = function(config) {

        }

        //retorna os dados
        var getData = function(response) {

        }

        this.configure = configure;
        this.getData = getData;

    });
})()

Note que eu encapsulei a primeira requisição (no arquivo app.js) em um método goToPage(), isso porque eu vou usar ele mais de uma vez e quero guardar a referência, a ideia é executar o código novamente quando fizermos uma nova requisição. A estrutura do service e a ideia geral é a mesma do exemplo anterior, vou usar um configure() e jogar tudo em um model paginate que vai pra view. Mas vamos por partes.

No service (pagination.js), o método goToPage será recebido do via configure() junto com as demais informações desta forma:

if (config.data !== undefined) {
    data = config.data;
    if (perPage == 0) {
        perPage = config.data.length;
    }
}

next = null;
if (config.next !== undefined) {
    next = config.next;
}

prev = null;
if (config.prev !== undefined) {
    prev = config.prev;
}
if (config.total !== undefined) {
    total = config.total;
}

//aqui onde recebo o goToPage()
if (config.goToPage !== undefined) {
    goToPage = config.goToPage;
}

Acredito que não tenha nenhuma novidade aqui.

O método getData() ficará assim:

var from = current*perPage+1;
var to = (from+perPage)-1;

if (to > total) {
    to = total;
}
return getData = {
    result : {
        data: data,
        from: from,
        to: to
    },
    total : total,
    next : function() {
        console.log(next);
        if (next !== null) {
            current++;
            goToPage(next);
        }
    },
    prev : function() {
        if (prev !== null) {
            current--;
            goToPage(prev);
        }
    }
};

Também não temos muitas novidades aqui, é bem parecido com o que fizemos antes, só que eu estou passando o objeto que vai pra view daqui do service, já que ele vai acabar ficando muito grande no controller pra eu repetir de novo em outro (controller), um detalhe interessante é o goToPage(next); do next e o goToPage(prev); do prev, que é o que eu expliquei antes, ele vai rodar o método criado no controller (o goToPage()).

dentro do success() do app.js vou fazer o seguinte:

SeverPaginate.configure({
    data: response.data.data,
    next: response.data.paging.next,
    prev: response.data.paging.previous,
    total: response.data.summary.total_count,
    goToPage: goToPage
});

$scope.paginate = SeverPaginate.getData();

Neste ponto você deve se perguntar, porque você não enviou logo o request.data e usou internamente no service em vez de ficar informando item por item? É lógica a sua dúvida, já que isso vai te ajudar a digitar menos m outros controllers, você pode fazer assim se quiser, eu mantenho desta forma caso eu precise fazer uma requisição paginada que não segue esta estrutura exata, ai eu posso manipular em cada controller da forma que eu quiser, veja o que é melhor pra você!

O arquivo app.js final:

(function(){
    'use strict';

    var module = angular.module('app', ['Pagination']);

    module.controller('ServerPaginationCtrl', function($scope, $http, SeverPaginate) {
        var success = function(response) {
            SeverPaginate.configure({
                data: response.data.data,
                next: response.data.paging.next,
                prev: response.data.paging.previous,
                total: response.data.summary.total_count,
                goToPage: goToPage
            });

            $scope.paginate = SeverPaginate.getData();
        }

        var goToPage = function(url) {
            $http.get(url).then(success);
        }

        goToPage('resource/data.json');
    });
})()

O arquivo pagination.js final:

(function(){
    'use strict';

    var module = angular.module('Pagination', []);

    module.service('SeverPaginate', function($http) {
        //variáveis de configuração
        var data = {};
        var current = 0;
        var perPage = 0;
        var total = 0;

        var next = null;
        var prev = null;

        var goToPage = function(url) {

        }

        //Seta a configuração da paginação
        var configure = function(config) {
            if (config.data !== undefined) {
                data = config.data;
                if (perPage == 0) {
                    perPage = config.data.length;
                }
            }

            next = null;
            if (config.next !== undefined) {
                next = config.next;
            }

            prev = null;
            if (config.prev !== undefined) {
                prev = config.prev;
            }
            if (config.total !== undefined) {
                total = config.total;
            }
            if (config.goToPage !== undefined) {
                goToPage = config.goToPage;
            }
        }

        //retorna os dados
        var getData = function(response) {
            var from = current*perPage+1;
            var to = (from+perPage)-1;

            if (to > total) {
                to = total;
            }
            return getData = {
                result : {
                    data: data,
                    from: from,
                    to: to
                },
                total : total,
                next : function() {
                    console.log(next);
                    if (next !== null) {
                        current++;
                        goToPage(next);
                    }
                },
                prev : function() {
                    if (prev !== null) {
                        current--;
                        goToPage(prev);
                    }
                }
            };
        }

        this.configure = configure;
        this.getData = getData;

    });
})()

Note que você vai usar o mesmo HTML para ambos os exemplos, da até pra criar uma directiva a ser incluida e facilitar ainda mais esta etapa também.

Conclusão

Paginar dados no AngularJs, seja sob demanda ou não, é uma tarefa simples, organizar o código para ser reutilizado é aonde as coisas começam a complicar para muita gente, já que pode ser custosa a manutençãos e for feita de outra forma, espero que tenha ajudado um pouco alguns devs/alunos que sempre me perguntam isso.

Em uma outra oportunidade quero somar as ideias aqui e criar uma paginação "hibrida", ou seja, que vai buscar uma certa quantidade de itens e listar em várias páginas antes de buscar mais, dimuindo o tempo de espera do usuário e melhorando a interação com a aplicação.

Ate a próxima.


Cursos relacionados


* Parcelamento apenas cartão de crédito! Pode haver uma pequena variação no parcelamento em relação a simulações apresentadas!