AngularJS: Série de Bolso (parte 2)

Que tal ver o primeiro post da série também?

Neste segundo post, é hora de colocar o AngularJS 1.x na prática, com apresentação de alguns conceitos conforme necessidade.

Vamos utilizar muito Javascript e um pouco de HTML neste artigo. Se você não se sente confortável com eles, respire fundo, tentarei ser claro e destacar os conceitos para que você possa estudar o que sentir dúvidas.

AngularJS é puramente um jogo de magia envolvendo Javascript, mas não adianta tentar lançar uma Avada Kedavra se você não consegue embaralhar as cartas direito.

snape-meme

Primeiro, certifique-se que você tem o AngularJS 1.x em mãos. Caso não tenha, baixe direto do site oficial. Cuidado para não baixar a versão 2, este tutorial não se aplica a ela:

img1

Configure o download conforme quiser. Eu prefiro sempre ter a versão não minificada (nesse caso, Uncompressed) para xeretar o que bem entender:

img2

Monte a sua estrutura de pastas para fazer os apontamentos e links de scripts. A minha ficou assim inicialmente:

  • <Pasta Principal>
    • <scripts>
      • angular.js
    • index.html

Você pode modificar essa estrutura a sua vontade, ou jogar tudo em qualquer lugar, eu não vou te julgar.

Preencha o index.html com o seguinte:

<!doctype html>
<html ng-app="minhaApp">
    <head>
        <title>AngularJS: Série de Bolso (parte 2)</title>
    </head>
    <body>

        <h1>Hello {{"AngularJS"}} {{5-4}}</h1>

        <script src="scripts/angular.js"></script>
        <script>
            var app = angular.module('minhaApp', []);
        </script>
    </body>
</html>

Na linha 2, nós indicamos ao angular qual o nome da aplicação (“minhaApp”). Na linha 12 criamos o módulo app.

Com isso podemos usar algumas funcionalidades do angular e imprimir “Hello AngularJS 1” na tela. Notou que fizemos uma subtração para exibir o 1?

O Angular usa as “{{}}” para indicar onde teremos um conteúdo a ser substituído, mas não se limita a trabalhar nas chaves.

Vamos melhorar o exemplo. Digamos que eu queria exibir o nome de uma pessoa no conteúdo HTML. Podemos fazer isso com o seguinte:

<!doctype html>
<html ng-app="app">
    <head>
        <title>AngularJS: Série de Bolso (parte 2)</title>
    </head>
    <body ng-controller="MeuController">

        <h1>Hello {{Amigo}}</h1>

        <script src="scripts/angular.js"></script>
        <script>
            var app = angular.module('app', []);
            app.controller('MeuController', ['$scope', function($scope){
                $scope.Amigo = "Diego Doná";
            }])
        </script>
    </body>
</html>

Na linha 2, nada de novo, indicamos que a aplicação se chama “app” dessa vez.

Na linha 6 estamos indicando o controller responsável pela tag body, no caso “MeuController”.

Na linha 8 estamos pedindo que ele imprima o que diabos for “Amigo”.

Na linha 12, nada de novo, estamos declarando o módulo, desta vez com nome app.

Na linha 13 estamos declarando o controllador “MeuController” e na linha 14, definimos quem/o que é “Amigo”.

Espera… ng-controller? Controller? ‘$scope’? Uma function dentro de um array?!

Hora de respirar fundo. No AngularJS nós precisamos de controllers para atuar sobre nossas views (basicamente, HTML). Eles definem, em essência, variáveis e métodos de forma separada da visualização.

Por isso usamos um controller, para termos onde definir os dados.

Já o $scope representa a coleção de dados que o framework está monitorando. De acordo com a documentação, ele é o objeto que referencia o modelo da aplicação.

Pense nele como uma coleção de dados e métodos presentes no escopo atual da aplicação. Por isso usamos o $scope para definir qual o valor será transportado para a tela.

O AngularJS se encarrega de levar o “Amigo”, presente no $scope “MeuController” até a view que você indicou ser de responsabilidade dele – a tag <body>.

Sobre a function dentro do array, eu me defendo, é uma medida protetora que o framework AngularJS adota pelo seu modelo de Dependency Injection.

$scope é injetado dentro do controller no momento que o criamos. Se você não passar a string antes, irá funcionar, até você minificar o código. Aí suas variáveis mudarão de nome e $scope se torna xyz.

Usando o modelo com strings, nos escapamos disso. O AngularJS não se importa com o nome dentro da function, ele vai assumir que é um objeto do tipo que você passou anteriormente. Esta é uma boa prática, não uma regra.

Por enquanto injetamos apenas o $scope, mas existem diversas outras possibilidades. Fique atento à ordem dos parâmetros.

Faça testes!

Crie outras variáveis no $scope dentro do controller e as exiba no html usando o {{}}. Tente criar objetos e exibir suas propriedade caso sinta-se confiante. Lembrando, para ser levado a view, deve estar presente no $scope!

Quando se sentir confiante, vamos complicar um pouco mais e abalar sua estabilidade emocional recém adquirida.

Evite o uso direto de $scope

Sim, eu acabei de dizer que o $scope é a representação dos dados da aplicação e que sem ele os dados não vão para a view. Agora estou te dizendo para não usá-lo diretamente em seus controllers: pode ser prejudicial à longo prazo.

Jogar e retirar os dados diretamente do $scope leva a um código possivelmente confuso. Não em nossos pequenos exemplos, mas em aplicações completas.

Por exemplo, se usarmos um controller geral para a tag <body> e outro, mais específico, para o rodapé. Neste cenário, se você exibir algo como {{Data}} dentro do rodapé, não está claro se você quer algo do rodapé ou do body.

Vence o $scope mais específico. Se você redefiniu Data em algum momento, dentro do controller do rodapé, ele terá o último valor, caso contrário, terá o valor “padrão” do pai.

scopes

A saída será “Ciclano e Zé” – confundiu, né? Trocamos o pai, mas não o filho. A alternativa é o uso de controllerAs:

<!doctype html>
<html ng-app="app">
    <head>
        <title>AngularJS: Série de Bolso (parte 2)</title>
    </head>
    <body ng-controller="MeuController as meuCtrl">

        <h1>Hello {{meuCtrl.Amigo}}</h1>

        <script src="scripts/angular.js"></script>
        <script>
            var app = angular.module('app', []);
            app.controller('MeuController', [function(){
                var vm = this;
                vm.Amigo = "William"
            }])
        </script>
    </body>
</html>

Nesse modelo, literalmente damos um “apelido” ao controller e usamos ele na view para chamar as propriedades.

Se você trabalhar com herança de controllers, vai ser grato em usar essa técnica e saber exatamente com o quê está lidando.

Fica por sua conta usá-la ou não. Eu já escrevi aplicações inteiras sem o uso de controllerAs e não tive (muitos) problemas, antes de aprender as vantagens desta técnica. É apenas uma boa prática.

Dentro do controller eu usei a variável “vm” para anexar as propriedades (linha 14). Você pode escolher qualquer nome, inclusive, pode usar o mesmo apelido do controller. É uma questão de gosto.

scopes-2

Agora não tem erro: Estamos pedindo as propriedades do bodyCtrl (apelido de BodyController) explicitamente, não importa termos uma propriedade em outro controlador com o mesmo nome.

Quando se sentir confiante de novo, se prepare pra mais uma rasteira.

Essa estrutura está longe de ser real!

Fizemos tudo até agora usando apenas o index.html. Na vida, não vamos trabalhar dentro de um único .html e escrever todos os nossos javascripts lá. Pelo menos você não deveria.

Vamos melhorar um pouco nossa estrutura:

  • <Pasta Principal>
    • <scripts>
      • angular.js
      • app.js
      • controller.js
    • index.html

Nosso index:

<!doctype html>
<html ng-app="app">
    <head>
        <title>AngularJS: Série de Bolso (parte 2)</title>
    </head>
    <body ng-controller="MeuController as meuCtrl">

        <script src="scripts/angular.js"></script>
        <script src="scripts/app.js"></script>
        <script src="scripts/controller.js"></script>
    </body>
</html>

Nosso app.js, responsável por iniciar o módulo “app”:

(function(){
    'use strict';
    angular
        .module('app', []); //estamos CRIANDO o módulo app
}());

Nosso controller.js, que define o “MeuController” indicado na view index.html:

(function(){
    'use strict';
    angular
        .module('app') //estamos ACESSANDO o módulo app
        .controller('MeuController', meuController);

    meuController.$inject = [];
    function meuController(){
        var vm = this;       
    }

}());

Agora sim. Separamos nosso html e javascript, de bônus usamos IIFE (“funções invocadas imediatamente”) para garantir o escopo de todas as nossas variáveis e ‘use strict’ para melhorar a captura de erros.

Também estamos injetando as dependências de uma maneira mais declarativa, para evitar o uso de arrays em funções (aquilo realmente pode ficar confuso).

Para declarar o controller, declaramos a função como referência (linha 5), declaramos as injeções (linha 7) e declaramos a implementação (linha 8).

Esse modelo de desenvolvimento é baseado no guideline do John Papa para AngularJS 1.x. O ideal é que você crie um arquivo para cada controller, todos nesse mesmo modelo, com IIFE e use strict.

É importante notar que quando fazemos “angular.module(‘app’)” nós estamos *acessando* um módulo que já deve existir. Se passarmos o array ao lado, significa que estamos *criando* um módulo.

Nós criamos o módulo principal no app.js e o acessamos no controller.js.

Vamos fazer uma brincadeira usando a diretiva ng-model. Mude seu html para o seguinte:

<!doctype html>
<html ng-app="app">
    <head>
        <title>AngularJS: Série de Bolso (parte 2)</title>
    </head>
    <body ng-controller="MeuController as meuCtrl">

        <h1>Olá {{meuCtrl.Amigo}}!</h1>

        <input type="text" ng-model="meuCtrl.Amigo" />

        <script src="scripts/angular.js"></script>
        <script src="scripts/app.js"></script>
        <script src="scripts/controller.js"></script>
    </body>
</html>

Adicionamos nosso velho conhecido {{}} e um input. A graça está na diretiva ng-model, que garante a passagem dos dados contidos no input para o controller e vice e versa (palavra chave: two way data binding)

Escreva algo no input e se divirta com a atualização instantânea com um esforço mínimo.

Para provar o two way, atualize o html para o seguinte, desta vez com a diretiva ng-click e um button:

<!doctype html>
<html ng-app="app">
    <head>
        <title>AngularJS: Série de Bolso (parte 2)</title>
    </head>
    <body ng-controller="MeuController as meuCtrl">

        <h1>Olá {{meuCtrl.Amigo}}!</h1>

        <input type="text" ng-model="meuCtrl.Amigo" />

        <br/>

        <button type="button" ng-click="meuCtrl.setAmigo()">Clique</button>

        <script src="scripts/angular.js"></script>
        <script src="scripts/app.js"></script>
        <script src="scripts/controller.js"></script>
    </body>
</html>

E seu controller para:

(function(){
    'use strict';
    angular
        .module('app') //estamos ACESSANDO o módulo app
        .controller('MeuController', meuController);

    meuController.$inject = [];
    function meuController(){
        var vm = this;
        vm.Amigo = undefined;   

        vm.setAmigo = setAmigo;

        function setAmigo() {
            vm.Amigo = "Nayara";
        }    
    }

}());

A diretiva ng-click faz com que, ao clicar, algo seja executado. Como mexemos na propriedade Amigo, os dados são atualizados diretamente na tela.

Considerações finais

Neste post vimos alguns conceitos e suas colocações em prática. Estendi um pouco as explicações sobre os conceitos que achei essenciais e fui chato com os padrões que sigo no git do John Papa, pois acho esse estilo um dos melhores para manter aplicações organizadas.

A produtividade é desacelerada um pouco, mas a manutenabilidade é mais importante. A troca vale a pena.

Como sempre: Faça testes. Crie outro arquivo de controller (meu-segundo-controller.js, por exemplo) e faça outras brincadeiras pelo index. Faça um botão que ao ser clicado dispare um alert (banca colocar dentro da função no controller).

O céu é o limite.

Todos os arquivos que usei no artigo estão no meu github.com/diedona, passe por lá se quiser olhar os arquivos em sua versão original, inclusive mostrando a confusão de $scopes aninhados.

Se quiser, olhe uma brincadeira com o angular que fiz no Plunker.

No próximo artigo vamos conhecer mais diretivas e fazer alguma mini aplicação – ainda sem envolver base de dados.

Editado 19/11/2016:

Visite o próximo artigo da nossa série de bolso sobre AngularJS!

Autor: Doná

Um piracicabano médio, amante do desenvolvimento web.

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *