Vamos começar pela definição: AngularJS é um framework JavaScript para construção de aplicações web dinâmicas, comumente referenciado como um framework MVC client side. Essa definição não está errada, mas o fato é que MVC é um padrão para dividir uma aplicação em diferentes partes (modelo, visão e controle), cada uma com suas respectivas responsabilidades. O AngularJS não implementa um MVC na sua forma tradicional, mas algo próximo de um MVVM (Model – View – ViewModel). Logo, seu próprio time de desenvolvimento resolveu batizá-lo, carinhosamente, de um framework MVW – Model View Whatever. Bem, o time de desenvolvimento do Angular dispensa apresentação, ele nasceu e é mantido por uma empresa que entende de web, o Google.
“AngularJS – Superheroic JavaScript MVW Framework”
A principal característica do AngularJS é que ele funciona como uma extensão do HTML, diferente de outros frameworks JavaScript, não é necessário manipular o DOM. Isso possibilita a criação de um front end mais organizado e sem a necessidade de manipular “html + dados”.
Suas principais características são:
- Two-way Data binding
- Injeção de Dependências
- Criação de diretivas (Extensão do HTML)
- Modularização e reuso (Controllers, Services e Filters)
- Testabilidade
Basicão
[code language=\\\”html\\\”]
<html ng-app>
<head>
<title>AngularJS</title>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.2.25/angular.min.js"></script>
</head>
<body>Meu nome é: {{ nome }}
<input type="text" ng-model="nome">
</body>
</html>
[/code]
Este é o exemplo mais básico em AngularJS. A diretiva ng-app na tag <html> inicializa a aplicação e define esta tag como o elemento root da aplicação, tudo a partir dela pode ser gerenciado pelo Angular. Ao digitar algo no input o nome será atualizado em realtime na tela. Essa mágica é chamada de Two-way data binding. Qualquer interação na view será refletida no modelo e vice-versa, isso é feito através do objeto $scope, criado automaticamente pelo angular. A propriedade ng-model é responsável por fazer o canal de comunicação e a sintaxe utilizada é o “bigode-bigode”: {{ atributo }}. Note que até aqui não escrevemos nenhum código JavaScript.
Controllers
Para entender como é feito o vínculo da view com o modelo, e como o angular permite organizamos nossa aplicação com a chamada de Controllers, segue o código a seguir:
cervejas.html
[code language=\\\”html\\\”]
<div ng-controller="CervejaController">
<form name="form">
<input type="text" placeholder="Cerveja" ng-model="cerveja.nome">
<input type="text" placeholder="Cervejaria" ng-model="cerveja.cervejaria">
<button ng-click="adicionar()">Adicionar</button>
</form>
<table>
<thead>
<tr>
<th>Cerveja</th>
<th>Cervejaria</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="cerveja in cervejas">
<td>{{ cerveja.nome }}</td>
<td>{{ cerveja.cervejaria }}</td>
</tr>
</tbody>
</table>
</div>
[/code]
CervejaController.js
[code language=\\\”javascript\\\”]
function CervejaController($scope) {
$scope.cervejas = [ { nome : \\\’Cerpinha\\\’, cervejaria : \\\’Cerpa\\\’},
{ nome : \\\’Tijuca\\\’, cervejaria : \\\’Cerpa\\\’ } ];
$scope.adicionar = function() {
$scope.cervejas.push({nome : $scope.cerveja.nome,
cervejaria : $scope.cerveja.cervejaria});
$scope.limpar();
};
$scope.limpar = function() {
$scope.cerveja = {nome: \\\’\\\’, cervejaria: \\\’\\\’};
};
}
[/code]
A diretiva ng-controller vincula um elemento html com um Controller JavaScript, no caso CervejaController. Tudo a partir desta div pode ser controlado por este Controller. O button chama através da diretiva ng-click a função adicionar(), definida dentro do nosso Controller. Esta função adiciona uma nova cerveja na listagem e limpa os campos do formulário. Ao iniciar o controller, por padrão é adicionada duas cervejas na listagem, também poderíamos usar a diretiva ng-init neste caso. Note que a variável $scope foi recebida como argumento do Controller. Este é um exemplo de injeção de dependência feito automaticamente pelo AngularJS, um dos principais recursos do framework.
Outra diretiva interessante presente neste exemplo é a ng-repeat, que permite iterar em listas de forma bem simples e intuitiva, no formato <elemento> in <coleção>.
Filters
Outro recurso muito interessante e nativo do framework é a aplicação de filters. Os filtros servem para transformar o resultado de uma expressão, aplicando algum tipo de formatação ou restrição nos dados. Segue o exemplo:
[code language=\\\”html\\\”]
<h2>Busca</h2>
Cerveja: <input ng-model="search.nome">
Cervejaria: <input ng-model="search.cervejaria">
<table>
<thead>
<tr>
<th><a href="">Cerveja</a></th>
<th>Cervejaria</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="cerveja in cervejas | filter:search | orderBy:\\\’nome\\\’">
<td>{{ cerveja.nome | uppercase }}</td>
<td>{{ cerveja.cervejaria }}</td>
</tr>
</tbody>
</table>
[/code]
A sintaxe dos filtros funciona com a inclusão de um pipeline “|” dentro da expressão. No exemplo foi adicionado dois inputs, para filtrar por nome e por cervejaria, através do objeto search, incorporado na diretiva ng-repeat: “filter:search”. Os filtros também suportam ordenação através da propriedade orderBy. Adicionalmente existem alguns filtros padrão no Angular, como uppercase, lowercase e date. Há também a possibilidade de criar filtros customizados da seguinte forma:
[code language=\\\”javascript\\\”]
App.filter(\\\’meuFiltro\\\’, function() {
return function(data) {
return data;
};
});
[/code]
Services
O conceito de separação de responsabilidades está presente no AngularJS através da aplicação de Services. Basicamente, os serviços são funções javascript que devem executar funções específicas, bem definidas, de fácil manutenção e testáveis. Dessa forma é possível modularizar e reaproveitar serviços em diferentes controladores, por meio da injeção de dependências, da seguinte forma:
[code language=\\\”javascript\\\”]
//app.js
var app = angular.module("app", []);
app.service(\\\’CervejaService\\\’, function(){
this.remover = function(cervejas, cerveja) {
var index = cervejas.indexOf(cerveja);
cervejas.splice(index, 1);
};
});
//CervejaController.js
function CervejaController($scope, CervejaService) {
//…
$scope.remover = function(cerveja) {
CervejaService.remover($scope.cervejas, cerveja);
};
}
[/code]
[code language=\\\”html\\\”]
//cervejas.html
<table>
<thead>
<tr>
<th><a href="">Cerveja</a></th>
<th>Cervejaria</th>
<th></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="cerveja in cervejas | filter:search | orderBy:\\\’nome\\\’">
<td>{{ cerveja.nome | uppercase }}</td>
<td>{{ cerveja.cervejaria }}</td>
<td><button ng-click="remover(cerveja)">Remover</button></td>
</tr>
</tbody>
</table>
[/code]
Na primeira linha, é iniciado o módulo principal da aplicação, o primeiro argumento é o nome do módulo, o segundo é um lista de dependências externas que podem ser injetadas na aplicação, como plug-ins de controle de rotas, componentes de view, etc. Em seguida é definido o serviço CervejaService, nele implementamos uma função para remover uma cerveja de uma listagem, essa função pode ser usada em qualquer controller que injete este serviço. Para chamar a função de remoção, basta uma chamada via ng-click para a função remover(),definida no controller, para que o serviço seja invocado.
Jasmine
Graças a organização do código que o AngularJS nos proporciona, testá-lo se torna uma tarefa fácil. Para isso vamos usar o framework de teste Jasmine. Aliás, o próprio código do Angular é testado utilizando Jasmine. Precisamos importar apenas jasmine.js e o módulo de mocks do angular.
[code language=\\\”javascript\\\”]
beforeEach(module(\\\’app\\\’));
describe(\\\’CervejaController\\\’, function () {
it(\\\’deve definir uma cerveja padrao com nome e cervejaria vazias\\\’, inject(function ($controller) {
var scope = {};
var ctrl = $controller(\\\’CervejaController\\\’, { $scope: scope });
expect(scope.cerveja.nome).toBe(\\\’\\\’);
expect(scope.cerveja.cervejaria).toBe(\\\’\\\’);
}));
it(\\\’deve iniciar com duas cervejas carregadas\\\’, inject(function ($controller) {
var scope = {};
var ctrl = $controller(\\\’CervejaController\\\’, { $scope: scope });
expect(scope.cervejas.length).toBe(2);
}));
it(\\\’deve limpar uma cerveja\\\’, inject(function ($controller) {
var scope = {};
var ctrl = $controller(\\\’CervejaController\\\’, { $scope: scope });
scope.cerveja = {nome: \\\’Cerpinha\\\’, cervejaria: \\\’Cerpa\\\’};
scope.limpar();
expect(scope.cerveja.nome).toBe(\\\’\\\’);
expect(scope.cerveja.cervejaria).toBe(\\\’\\\’);
}));
});
[/code]
A função beforeEach carrega o módulo antes de iniciarmos o teste do CervejaController. Basicamente foram definidos três testes, os dois primeiros testam o estado inicial do controller, o terceiro testa a função limpar. A função expect é responsável por fazer a asserção de nossos testes.
Daria para escrever um artigo só para os detalhes da implementação de testes com Jasmine e suas diferentes abordagens de cobertura dentro de aplicações com AngularJS.
Conclusões
As possibilidades do AngularJS não se esgotam aqui, existem vários recursos que não foram abordados. Mas o que foi apresentado serve de base pra você considerar utilizar este poderoso framework no seu próximo projeto.
Todo código apresentado aqui está disponível no github.
Deixe um comentário