Back-End

19 jan, 2016

AngularJS do jeito certo: pensando em Angular 2

Publicidade

A próxima versão do AngularJS, que vem sendo chamada de Angular 2 pela comunidade, está bem próxima do lançamento e deve trazer grandes mudanças: a morte dos controllers e do $scope e maior foco nas diretivas e nos Web Components.

AngularJS

Porém, o Angular 1 ainda tem muita energia pra queimar e muitas empresas ainda estão começando projetos do zero usando as versões 1.x. Neste artigo, vou mostrar uma abordagem de como fazer uma aplicação completa (porém simples) utilizando Angular 1, mas já pensando nas adaptações necessárias para o Angular 2.

Obs.: Este texto requer um conhecimento mínimo do framework AngularJS. Se você ainda não possui esse conhecimento, sugiro primeiro aprender um pouco mais antes de continuar. Este tutorial completo pode te ajudar.

A aplicação que vamos construir é um SPA bem simples: uma busca por usuários do GitHub e, após a busca, devemos mostrar os repositórios do usuário. Desta forma:

angular-1É possível ver uma demonstração da versão final da aplicação neste link.

Lembrando que seguiremos uma abordagem baseada no Angular 2, ou seja, nada de controllers. A estrutura será baseada em 4 diretivas, da seguinte forma:

angular-2

  1. github: diretiva “pai”, controla todas as outras internas;
  2. search-user: representa a busca por usuários;
  3. user-info: mostra as informações do usuário;
  4. user-repo: mostra os repositórios do usuário.

Fazendo uma abordagem Top-Down, vamos começar pela estrutura básica da diretiva “pai”, a github (marcada em vermelho na imagem):

(function() {
    'use strict';

    angular
        .module('app')
        .directive('github', Github);

    function Github() {
        return {
            restrict: 'E',
            template: [
                '<search-user></search-user>',
                '<user-info></user-info>'
            ].join(''),
            scope: {},
            controller: controller
        };

        function controller($scope) {
            // TO-DO
        }
    }
})();

Analisando essa estrutura, percebemos que bate exatamente com a descrição dada pela imagem, ou seja, a diretiva github contém 2 diretivas filhas: search-user e user-info. Vamos voltar depois nessa diretiva para complementá-la com o código que resta.

Focando agora na diretiva search-user (marcada em azul na imagem e responsável pela busca de usuários), temos a seguinte estrutura:

(function() {
    'use strict';

    angular
        .module('app')
        .directive('searchUser', SearchUser);

    function SearchUser() {
        return {
            restrict: 'E',
            template: [
                '<div class="jumbotron">',
                    '<div class="row">',
                        '<div class="col-md-4">',
                            '<form>',
                                '<div class="form-group">',
                                    '<label>Username</label>',
                                    '<input ng-model="username" type="text">',
                                '</div>',
                            '</form>',
                        '</div>',
                    '</div>',
                '</div>'
            ].join(''),
            scope: {},
            link: linker
        };

        function linker($scope, $element, $attrs) {
            $scope.$watch('username', function(username) {
                if (username) {
                    // BUSCA PELO USUÁRIO
                }
                else {
                    // LIMPA INFORMAÇÕES
                }
            });
        }
})();

A estrutura é bem simples: um template com um form de busca atribuindo o ngModel para username. Além disso, criamos um $scope.$watch para ler continuamente as mudanças nessa variável digitada pelo usuário.

Caso a variável seja undefined, vamos limpar as informações. Caso contrário, fazemos uma busca com esse valor, usando a API do GitHub.

O método linker deverá ficar com esta cara:

function linker($scope, $element, $attrs, githubCtrl) {
    $scope.$watch('username', function(username) {
        if (username) {
            UsersService.getByUsername(username).then(function(response) {
                githubCtrl.setUser(response.data);
            }).catch(function() {
                githubCtrl.clearUser();
            });
        }
        else {
            githubCtrl.clearUser();
        }
    });
}

Essa parte é complicada, mas não se assuste ainda. Nós precisamos injetar a diretiva pai para que ela possa transmitir os dados obtidos pela busca para as outras diretivas. Ou seja, se o resultado da busca for bem sucedida, vamos invocar setUser da diretiva pai para que possamos atribuir os dados obtidos pela API do GitHub; caso contrário, vamos invocar clearUser para limpar os dados.

Voltando, então, na diretiva github, precisamos implementar estes dois métodos:

(function() {
    'use strict';

    angular
        .module('app')
        .directive('github', Github);

    function Github() {
        return {
            restrict: 'E',
            template: [
                '<search-user></search-user>',
                '<user-info user="user"></user-info>'
            ].join(''),
            scope: {},
            controller: controller
        };

        function controller($scope) {
            this.setUser = function(data) {
                $scope.user = data;
            }

            this.clearUser = function() {
                $scope.user = null;
            };
        }
    }
})();

Repare que agora a variável user é passada para a diretiva (que ainda não criamos) user-info. Isso será útil mais para a frente.

Além disso, com as atualizações necessárias, a diretiva search-user ficará assim:

(function() {
    'use strict';

    angular
        .module('app')
        .directive('searchUser', SearchUser);

    SearchUser.$inject = ['UsersService'];

    function SearchUser(UsersService) {
        return {
            restrict: 'E',
             template: [
                '<div class="jumbotron">',
                    '<div class="row">',
                        '<div class="col-md-4">',
                            '<form>',
                                '<div class="form-group">',
                                    '<label>Username</label>',
                                    '<input ng-model="username" type="text">',
                                '</div>',
                            '</form>',
                        '</div>',
                    '</div>',
                '</div>'
            ].join(''),
            require: '^github',
            scope: {},
            link: linker
        };

        function linker($scope, $element, $attrs, githubCtrl) {
            $scope.$watch('username', function(username) {
                if (username) {
                    UsersService.getByUsername(username).then(function(response) {
                        githubCtrl.setUser(response.data);
                    }).catch(function() {
                        githubCtrl.clearUser();
                    });
                }
                else {
                    githubCtrl.clearUser();
                }
            });
        }
    }
})();

Vamos criar, então, a terceira diretiva (das quatro que precisamos): a user-info, que será responsável pelo quadrado verde da imagem e representa os dados obtidos pela busca na API do GitHub.

(function() {
    'use strict';

    angular
        .module('app')
        .directive('userInfo', UserInfo);

    function UserInfo() {
        return {
            restrict: 'E',
            template: [
                '<div id="user-info" ng-if="user">',
                    '<div class="row">',
                        '<div class="col-lg-4">',
                            '<img class="img-circle" ng-src="{{user.avatar_url}}" alt="avatar" width="140" height="140">',
                            '<h2>{{user.login}}</h2>',
                            '<p>{{user.name}}</p>',
                            '<p>Followers: {{user.followers}} / Following: {{user.following}}</p>',
                            '<p><a class="btn btn-default" ng-href="{{user.html_url}}" role="button">View details</a></p>',
                        '</div>',
                        '<div class="col-lg-8">',
                            '<user-repos repos="repos"></user-repos>',
                        '</div>',
                    '</div>',
                '</div>'
            ].join(''),
            scope: {
                user: '=',
                repos: '='
            }
        };
    }

})();

Essa diretiva é bem simples, não tem nenhuma lógica, apenas templates. Repare que ela invoca a quarta (e última) diretiva a ser criada, a user-repos, que será responsável por mostrar a lista de repositórios do usuário e representa o quadrado laranja da imagem.

Precisamos, então, atualizar as duas outras diretivas para que elas saibam lidar com os repositórios de um usuário, que estarão na variável repos.

Primeiramente adicionamos dois novos métodos: setRepos e clearRepos na diretiva github. A versão final ficará assim:

(function() {
    'use strict';

    angular
        .module('app')
        .directive('github', Github);

    function Github() {
        return {
            restrict: 'E',
            template: [
                '<search-user></search-user>',
                '<user-info user="user" repos="repos"></user-info>'
            ].join(''),
            scope: {},
            controller: controller
        };

        function controller($scope) {
            this.setUser = function(data) {
                $scope.user = data;
            }

            this.clearUser = function() {
                $scope.user = null;
            };
            
            this.setRepos = function(data) {
                $scope.repos = data;
            };

            this.clearRepos = function() {
                $scope.repos = [];
            };
        }
    }
})();

E também na diretiva search-user, precisamos adicionar um método para buscar os repositórios de um usuário usando a API do GitHub. Esta é a versão final dela:

(function() {
    'use strict';

    angular
        .module('app')
        .directive('searchUser', SearchUser);

    SearchUser.$inject = ['UsersService'];

    function SearchUser(UsersService) {
        return {
            restrict: 'E',
             template: [
                '<div class="jumbotron">',
                    '<div class="row">',
                        '<div class="col-md-4">',
                            '<form>',
                                '<div class="form-group">',
                                    '<label>Username</label>',
                                    '<input ng-model="username" type="text">',
                                '</div>',
                            '</form>',
                        '</div>',
                    '</div>',
                '</div>'
            ].join(''),
            require: '^github',
            scope: {},
            link: linker
        };

        function linker($scope, $element, $attrs, githubCtrl) {
            $scope.$watch('username', function(username) {
                if (username) {
                    getUserInfo(username);
                }
                else {
                    githubCtrl.clearUser();
                }
            });

            function getUserInfo(username) {
                UsersService.getByUsername(username).then(function(response) {
                    getUserRepos(username);
                    githubCtrl.setUser(response.data);
                }).catch(function() {
                    githubCtrl.clearUser();
                });
            }

            function getUserRepos(username) {
                UsersService.getReposByUsername(username).then(function(response) {
                    githubCtrl.setRepos(response.data);
                }).catch(function() {
                    githubCtrl.clearRepos();
                });
            }
        }
    }
})();

Para fechar, basta criarmos a única diretiva que falta, referente à listagem dos repositórios de um usuário, a user-repos:

(function() {
    'use strict';

    angular
        .module('app')
        .directive('userRepos', UserRepos);

    function UserRepos() {
        return {
            restrict: 'E',
            template: [
                '<div ng-if="repos">',
                    '<h2>Repositories</h2>',
                    '<div ng-repeat="repo in repos">',
                        '<div class="thumbnail">',
                            '<div class="caption">',
                                '<h3>{{repo.name}}',
                                    '<span class="badge">{{repo.stargazers_count}} STARS</span>',
                                '</h3>',
                                '<p>{{repo.description}}</p>',
                                '<p>',
                                    '<a ng-href="{{repo.html_url}}" class="btn btn-primary" role="button">Repository</a>',
                                    '<a ng-href="{{repo.html_url + '/issues'}}" class="btn btn-default" role="button">Issues ({{repo.open_issues}})</a>',
                                '</p>',
                            '</div>',
                        '</div>',
                    '</div>',
                '</div>',
            ].join(''),
            scope: {
                repos: '='
            }
        };
    }

})();

Com isso, fechamos a aplicação! Para quem quiser ir mais a fundo, eu disponibilizei esta aplicação no GitHub para verificar a versão final completa, o processo de build usado e mais.

Em resumo, nesse artigo aprendemos:

  1. Fazer uma aplicação completa sem controllers, usando apenas diretivas;
  2. Como transmitir dados de uma diretiva para outra;
  3. Começar a pensar com a mentalidade Angular 2.

Gostou? Aprendeu? Se tiver qualquer dúvida ou comentário, deixe sua contribuição abaixo. Até a próxima!