Single Page Apps com AngularJS

Single Page Apps com AngularJS

Definiçã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.

  1. Como um elemento HTML: <date-picker></date-picker>.
  2. Um atributo de um elemento: <input type="text" date-picker>.
  3. Uma classe: <input type="text" class="date-picker">.
  4. 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 de setTimeout
  • $interval no lugar de setInterval
  • $window no lugar de window
  • $document no lugar de document
  • $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.