Single Page Apps com AngularJS
· Leia em 15 minutosDefinição
Single Page Application (SPA) diferente dos websites tradicionais é basicamente uma aplicação completa que trabalha bastante no client-side para melhorar a experiência do usuário, proporcionando uma aplicação bem fluida, carregando recursos dinamicamente conforme necessário. Tenta assemelhar-se a um aplicativo desktop.
Apesar de SPAs estarem na moda nos últimos tempos. A primeira SPA foi criada em 2004 pelo Google, o nosso querido GMail. Hoje temos vários outros produtos de sucesso utilizando essa técnica, como o Facebook, Github, Twitter, Google +, etc.
Normalmente tudo é feito no client-side, deixando para o server-side apenas a comunicação com o banco de dados, trazendo esses dados para o client-side em respostas no formato JSON.
Para essa técnica surgiram muitos frameworks e libraries que podem ser utilizados, abaixo uma pequena comparação entre os mais comuns.
Comunidade
O fator comunidade é importante para qualquer framework, pois é ela quem move o projeto, tira dúvidas e instiga outros desenvolvedores a usar.
Métrica | AngularJS | Backbone.js | Ember.js |
---|---|---|---|
Stars no Github | 29.5k | 19.2k | 11.4k |
Plugins de terceiros | 822 ngmodules | 236 backplugs | 21 emberaddons |
Perguntas no StackOverflow | 49.5k | 15.9k | 11.2k |
Resultados no YouTube | ~75k | ~16k | ~6k |
Contribuídores no GitHub | 939 | 230 | 396 |
Usuários com extensões no Chrome | 150k | 7k | 38.3k |
Tamanho do Framework/Library
O tamanho é calculado com todos os scripts concatenados, compreensados e gzipped.
Framework | Tamanho | Tamanho com dependências obrigatórias |
---|---|---|
AngularJS 1.3.0 | 40.8 KB | 40.8 KB |
Backbone.js 1.1.2 | 6.5 KB | 40.4 KB (jQuery + Underscore) 20.6 KB (Zepto + Underscore) |
Ember.js 1.7.0 | 95 KB | 137.7 KB (jQuery + Handlebars) |
Essas são as 3 opções mais comuns, além delas temos outras como React, Knockout, Spine, etc.
Porém não se atenha a apenas essas informações para fazer sua escolha, cada um tem suas características e peculiaridades. Veja qual se adapta melhor a sua necessidade.
Nesse exemplo vamos utilizar AngularJS.
Sobre o AngularJS
AngularJS é um framework javascript MVC mantido pelo Google, que torna fácil a implementação de aplicações web dinâmicas. Esse é o objetivo do Angular.
Ele nos fornece um conjunto de ferramentas para ajudar no trabalho com SPAs, acredito que a razão de todo seu sucesso é a alta produtividade que pode-se conseguir com ele.
Algumas features que o Angular nos da:
- Two-way Data-binding e templates.
- Criar componentes reutilizáveis e customizáveis.
- Suporte para validação de formulários.
- Módulos e injeção de dependências.
- Controle de eventos e rotas.
- Arquitetura MVC.
Começando com AngularJS
O Angular possui alguns atributos com determinadas funções, todo atributo começa com ng-*
. Para dizer ao Angular que queremos que ele execute algo precisamos adicionar o atriburo ng-app
, geralmente colocamos isso na tag html.
Feito isso, adicionamos mais algum código para visualizar como os models funcionam.
<!doctype html>
<html ng-app> <!-- Adicionamos o atributo ng-app -->
<head>
<!-- Importamos o JS do Angular -->
<script src="assets/angular.min.js"></script>
</head>
<body>
<!-- Criamos um model chamado
'name' com o atributo ng-model -->
<input type="text" ng-model="name"
placeholder="Digite seu nome...">
<h1>Olá {{name}} !</h1>
</body>
</html>
O atributo ng-model
cria um model, o valor armazenado nesse model pode ser impresso em qualquer lugar da página utilizando a sintaxe de templates, que são definidos com {{
e }}
, então se queremos imprimir o valor do model name
basta adicionar {{name}}
onde quer que seja impresso.
Veja o resultado. Não esqueça de digitar um valor no input.
Templates
Como vimos templates são definidos com {{
e }}
, o Angular sempre “processará” o conteúdo das variáveis ou expressões nos templates transformando para HTML, podemos usar para diversos casos, abaixo um exemplo com o que cada expressão retorna.
{{ "Hello " + "World" }} // Hello World
{{ 1 + 1 }} // 2
{{ 1410356671914 | date: 'dd-MM-yyyy' }} // 10-09-2014
O interessante sobre os templates é que quando temos uma váriavel definida no Javascript e usamos ela em um template, é que se o valor dessa variável mudar o Angular vai tomar conhecimento disso e automaticamente refletir essa alteração no template, gerando o novo valor no HTML também.
Isso é o que chamamos de data-binding, a ligação/comunicação automática que o javascript faz com o template.
Podemos combinar o uso dos templates com atributos do Angular.
Introduzimos dois novos atributos ng-controller
e ng-repeat
.
<!doctype html>
<!-- Adicionamos um nome/valor para o App,
que será nosso módulo principal -->
<html ng-app="myApp">
<head>
<script src="assets/angular.min.js"></script>
</head>
<!-- Criamos um controller -->
<body ng-controller="CharactersCtrl">
<table>
<thead>
<tr>
<th>Name</th>
<th>Role</th>
<th>Gender</th>
</tr>
</thead>
<tbody>
<!-- Repetimos a tr para cada item
no array chamado "characters" -->
<tr ng-repeat="char in characters">
<td>{{char.name}}</td>
<td>{{char.role}}</td>
<td>{{char.gender}}</td>
</tr>
</tbody>
</table>
<script src="app.js"></script>
<script src="characters-ctrl.js"></script>
</body>
</html>
Agora no JS criamos o módulo myApp no arquivo app.js, os []
significam que não estamos passando nenhuma dependência para esse módulo.
// app.js
angular
.module('myApp', []);
Abaixo teremos o arquivo characters-ctrl.js, nele criamos o método CharactersCtrl
e a váriavel characters
que recebe um array, a mesma que usamos no HTMl. Portanto, o ng-repeat
vai criar uma tr
para cada item dentro do array.
// characters-ctrl.js
function CharactersCtrl($scope) {
$scope.characters = [
{ name: 'John Snow', role: 'Bastard', gender: 'Male' },
{ name: 'Daenerys Targaryen', role: 'Mother of Dragons', gender: 'Female' },
{ name: 'Tyrion Lannister', role: 'Imp', gender: 'Male' },
{ name: 'Eddard Stark', role: 'Lord of Winterfell', gender: 'Male' },
{ name: 'Robert Baratheon', role: 'King', gender: 'Male' }
];
}
angular
.module('myApp')
.controller('CharactersCtrl', CharactersCtrl);
Por fim, na última linha definimos o controller chamado CharactersCtrl
que utiliza a função acima de mesmo nome e dizemos que ele pertente ao módulo myApp.
Portanto como adcionamos ng-controller="CharactersCtrl"
, o Angular vai procurar no nosso Javascript um controller com esse nome. Por sua vez ele possui um Array chamado characters
, é esse o Array que iteramos na table
anteriormente.
Vamos adicionar um campo de busca, criamos um input com um model e usamos o model criado para buscar characters.
<input type="text" ng-model="searchChar" placeholder="Search">
<tr ng-repeat="char in characters | filter:searchChar">
{ ... }
</tr>
Rotas
Quando você está fazendo uma SPA, normalmente não se endereça rotas no back-end, você apenas cria uma API e cria suas rotas no front-end. Procure sempre trabalhar dessa forma.
Rotas no Angular é algo realmente muito simples.
// routes.js
function config($routeProvider, $locationProvider) {
// Leia a Nota sobre o HTML5Mode posteriormente
$locationProvider.html5Mode(true);
$routeProvider
// Rota para a Home
.when('/', {
templateUrl: 'templates/home.html',
controller: 'HomeCtrl'
})
// Rota para a página About
.when('/about', {
templateUrl: 'templates/about.html',
controller: 'AboutCtrl'
})
// Rota para a página Contact
.when('/contact', {
templateUrl: 'templates/contact.html',
controller: 'ContactCtrl'
})
// Rota para a página Avengers
.when('/avengers', {
templateUrl: 'templates/avengers.html',
controller: 'AvengersCtrl',
controllerAs: 'avengers'
});
// Caso não seja nenhum desses,
// redirecione para a rota '/'
.otherwise ({ redirectTo: '/' });
});
angular
.module('myApp')
.config(config);
Bom, vamos com calma, criamos uma função chamada config
e utilizamos o $routeProvider
para criar nossas rotas no escopo dela.
Dizemos no código que quando alguem acessar a URL /about
o Angular vai executar o código Javascript que estiver AboutCtrl
e usar o HTML about.html
que está na pasta templates
.
// Rota para a página About
.when('/about', {
templateUrl: 'templates/about.html',
controller: 'AboutCtrl'
})
- Nota HTML5Mode: O HTML5Mode irá usar pushState para montar as páginas, como isso não é suportado em navegadores antigos ele irá utilizar um fallback adicionando ‘#’ nas suas URLs. Se você não tem necessidade de dar suporte a esses navegares pode adicionar essa linha
$locationProvider.html5Mode(true);
para ter URLs “mais bonitas”, caso contrário remova a linha.
Controllers
Não podemos falar de controllers sem entender o que é o $scope
. Ele nada mais é do que um objeto que representa a view do seu controller.
O Angular usa o $scope para poder fazer o two-way data binding, imagine o $scope como sendo uma ligação entre sua view (template) e seu controller.
Criando o controller:
// about-ctrl.js
function AboutCtrl($scope) {
$scope.pageTitle = 'About';
$scope.message = 'Look! I am an about page.';
}
angular
.module('myApp')
.controller('AboutCtrl', AboutCtrl);
Essa duas variáveis criadas passam a ser acessíveis na nossa view:
<!-- templates/about.html -->
<h1>{{pageTitle}} Page</h1>
<p>{{message}}</p>
Mas para que o roteamento funcione corretamente, precisamos adicionar no nosso arquivo index.html
a tag ou atributo ng-view
, que na verdade é o que chamados de diretiva.
Mostrarei o que é uma diretiva adiante, no momento atenha-se ao funcionamento do ng-view.
<!doctype html>
<html ng-app="myApp">
<head>
<script src="assets/angular.min.js"></script>
</head>
<body>
<div ng-view></div>
<script src="app.js"></script>
<script src="routes.js"></script>
<script src="about-ctrl.js"></script>
</body>
</html>
Essa diretiva é responsavel por injetar os templates na página, o que vai acontecer é que quando um usuário acessar a url /about
, irá disparar a rota que criamos no arquivo routes.js
.
Como definimos, essa rota usa o template templates/about.html
e o controller AboutCtrl
. Portanto o Angular sabe que o que ele precisa fazer é injetar esse template dentro da diretiva ng-view
executando o controller que a rota pede.
Temos o resultado abaixo:
Directives
Diretivas são usadas para manipular elementos do DOM, não devemos usar os controllers para isso.
Além disso, as directives nos fornecem um $scope
isolado, permitindo a reutilização em outros lugares.
Pode-se encarar diretivas como componentes.
Definindo
As diretivas podem ser criadas de 4 maneiras.
- Como um elemento HTML:
<date-picker></date-picker>
. - Um atributo de um elemento:
<input type="text" date-picker>
. - Uma classe:
<input type="text" class="date-picker">
. - Comentários:
<!-- directive:date-picker -->
.
Sendo a maneira 1 e 2 as mais convencionais.
Veja como utilizá-las: Pratical Guide Directives
Abstrações do Angular
Podemos usar algumas abstrações que o Angular proporciona, essas abstrações tornam o código mais fácil de testar e executar, já que podemos executar de forma síncrona.
$timeout
no lugar desetTimeout
$interval
no lugar desetInterval
$window
no lugar dewindow
$document
no lugar dedocument
$http
no lugar de$.ajax
$q
(promises) no lugar de callbacks
One-time Binding
Sempre quando você utiliza um templace como {{ user.name }}
, o angular cria um watcher para ele. Isso é muito bom, pois quando mudarmos esse valor ele atualizará automaticamente na nossa página.
Porém em alguns casos sabemos que esse valor não vai mudar, vai ser sempre (ou quase sempre) o mesmo valor e não tem necessidade do Angular criar um watcher para isso. Quando estamos nessa situação pode-se usar o One-time Binding, que vai fazer exatamente isso, vai renderizar apenas uma vez.
Isso é muito mais performático. Procure, como uma boa prática, sempre utilizar One-time binding quando possível.
Para usar, basta mudar de {{ user.name }}
para {{ ::user.name }}
Quando for preciso atualizar o valor na página, pode-se executar $scope.user.name.$apply();
manualmente.
Debug/Tools
- Batarang: Adiciona ao Developer Tools funcionalidades que ajudam no debug de AngularJS Apps.
- Postman: Ajuda você a ser mais eficiente ao trabalhar com APIs.
Objetivo
O objetivo era mostrar os recursos do Angular para criação de SPAs, na minha opnião ele é um framework fácil de trabalhar e que já te dá boas features e convenções. Ainda existe muitos recursos não explorados aqui, como Filters, Services, etc.
Isso é apenas o básico.