class: center, middle
-
cada slide é um passo na construção do app
-
execute cada passo em um terminal linux (via bash)
-
você pode copiar e colar os comandos
(só tenha cuidado para não copiar o$que é o prompt)
Para seguir este roteiro, você precisará algumas ferramentas. Certifique-se de que estão devidamente instaladas antes de continuar.
certifique-se de que seu ambiente dispõe das seguintes ferramentas
- um terminal linux com bash ou equivalente
- algum editor de textos (vim, atom, etc)
nodeenpm(opcionalmente, você pode usarpython)curl(opcionalmente, você pode usar owget)
você pode verificar se estão instalados, digitando:
$ node --version
v10.2.1
$ npm --version
5.6.0
$ curl --version
curl 7.38.0 (x86_64-pc-linux-gnu) …class: middle, center, inverse
Há várias opções de servidores de desenvolvimento. Você pode até usar o servidor simples que vem com
python, mas é aconselhável usar um servidor um pouco mais sofisticado. Pra casos simples, uso olive-serverabaixo (e é o que usarei nos exemplos do roteiro). Mas pode ser de seu interesse instalar uma opção mais sofisticada, como obrowser-syncque é um dos mais usados. Em compensação, requer mais argumentos na linha de comando.
escolha uma das opções abaixo (ou instale ambas)
opção 1: live-server
$ npm install -g live-serveropção 2: browser-sync
$ npm install -g browser-syncComo ocorre com qualquer projeto, é importante manter todos os arquivos que compõem o app em um diretório próprio para isso. Tipicamente, será um subdiretório para o frontend contido no diretório geral do projeto. Aqui, contudo, faremos apenas o frontend do app. Para isso, usaremos um diretório no seu diretório home.
crie um diretório para o app.footnote[Observe que se você já
tiver um diretório com o nome mural, será necessário usar outro
nome ou renomear o diretório original, antes de prosseguir.]
$ mkdir ~/muralAqui optei por baixar o
angular.min.jse servi-lo diretamente do nosso servidor. Na prática, contudo, é muito comum que o script do angular (e de outros frameworks) seja lido diretamente de algum CDN. Isso poupa banda passante do servidor e permite tirar melhor proveito de cache http.
entre no diretório do projeto e baixe o angular
$ cd ~/mural
$ curl https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.7.2/angular.min.js \
-o angular.min.jsverifique que o arquivo foi corretamente gravado
$ ls
total 344
-rw-r--r-- 1 dalton staff 172105 Jul 10 13:12 angular.min.jsO arquivo
index.htmlé o ponto de entrada do app. Ele contém o html básico e instruções sobre os demais arquivos que formam o app (neste caso:app.css,angular.min.jseapp.js). Observe ainda o atributong-appno elementobodyusado para vincular o módulo angular chamado"mural"à DOM.
usando seu editor de textos, crie o arquivo index.html contendo:
<!DOCTYPE html>
<html lang="pt-br">
<head>
<meta charset="UTF-8">
<title>Mural</title>
<link rel="stylesheet" type="text/css" href="app.css">
</head>
<body ng-app="mural">
<!-- Angular JS -->
<script src="angular.min.js"></script>
<!-- Aplicação -->
<script src="app.js"></script>
</body>
</html>O arquivo
app.cssconterá os estilos de nossa aplicação. Por ora, vamos colocar apenas uma única regra, que nos permitirá verificar que está funcionando devidamente. Mais adiante, ajustaremos a regra.
usando seu editor de textos, crie o arquivo app.css contendo:
body {
background: lightgreen;
padding: 20px;
border: 1px solid blue;
}O arquivo
app.jsconterá o código de nossa aplicação. Por ora, criaremos apenas o módulo angular que representa nossa aplicação angular. Para isso, usaremos o métodomodulede angular. Ele recebe como parâmetros o nome do módulo e um array de dependências (nenhuma dependência por ora). O objeto retornado pelo método é um módulo angular que usaremos mais adiante. Para isso, o armazenamos na varávelapp. Observe ainda que o nome registrado é o nome que devemos usar no atributong-appnoindex.html. É esse nome que vincula a aplicação ao DOM.
usando seu editor de textos, crie o arquivo app.js contendo:
const app = angular.module('mural', []);Antes de prosseguir, assegure-se de que o diretório do projeto contém os 4 arquivos criados. Se faltar algum, reveja os passos anteriores.
$ ls -l
total 368
-rw-r--r-- 1 dalton staff 172105 Jul 11 07:09 angular.min.js
-rw-r--r-- 1 dalton staff 84 Jul 11 07:09 app.css
-rw-r--r-- 1 dalton staff 42 Jul 11 08:19 app.js
-rw-r--r-- 1 dalton staff 368 Jul 11 08:17 index.htmlQuando desenvolvemos para frontend, é importante podermos verificar rapidamente cada pequena mudança feita. Para isso, usamos um servidor de desenvolvimento que serve localmente os arquivos da aplicação por http, de forma que possamos testar a aplicação em um browser, sem a necessidade de fazer deploy (enviar uma cópia para um servidor de produção).
no diretório do projeto, inicie o servidor de desenvolvimento.ref[1] .footnote[.ref[1] Se você já estiver usando a porta 9900, escolha outra porta.]
$ cd ~/mural
$ live-server --port=9900em um browser, visite o app na url: http://localhost:9900
Ao visitar o app pela primeira vez, você deve ver apenas uma página verde claro com uma caixa com borda azul que corresponde ao elemento
bodycujo estilo é especificado pela única regra no arquivoapp.css. Obviamente, este é apenas um stub a partir do qual vamos desenvolver nossa aplicação. Mas por ora serve ao propósito de demonstrar que todas as partes estão devidamente conectadas e em funcionamento.
class: middle, center, inverse
Antes de prosseguir, relembre o padrão MVVM que discutimos em sala de aula. Relembre que chamamos de view aos elementos visuais propriamente ditos que são renderizados pelo browser. Na prática, portanto, a view é concretizada pelo código html.
O view-model, por sua vez, é um objeto javascript que servirá de intermediário entre a view propriamente dita e o model (o dado propriamente dito, provavelmente trazido do backend).
Finalmente, o controller é o nome dado à lógica que atua sobre o view-model e, possivelmente, sobre o model. O controller é o conjunto de handlers que tratam os eventos produzidos pela view em resposta ao usuário. Na prática, o controller será o código que reúne esses handlers.
Em angular, controllers e view-models são definidos juntos através de funções construtoras chamadas controllers.ref[2].footnote[.ref[2] Relembre das aulas teóricas que funções construtoras são invocadas com o operador
new.]. A ideia é que “instâncias” da função controller atuem como view-model. A função construtora que definimos abaixo especifica view-models contendo uma única propriedade:msg. Observe que definimos a variávelvmpara substituirthis, tal como no padrãoconst that = this;que discutimos nas aulas teóricas.
function AppCtrl() {
const vm = this;
vm.msg = "oi (vindo do controller)";
}Além de definirmos a função, também é preciso registrá-la em nossa aplicação, para que seja reconhecida por angular como controller. Para isso, adicione a linha abaixo ao código javascript.
app.controller('AppCtrl', AppCtrl);Finalmente, temos tudo para conectarmos o controller (e o respectivo view-model) à view. Para isso, precisamos editar o arquivo
index.html. Adicionaremos umdivaobodyque irá atuar como view. Para vinculá-lo corretamente ao controller que criamos, usamos a diretiva atributong-controllercom o valor"AppCtrl as vm". Dessa forma, informamos a angular: 1) que uma instância do controllerAppCtrldeve ser usada para aquele trecho; e 2) que usaremos o nomevmnas expressões angular para nos referirmos ao view-model correspondente.
adicione um div ao html no início do body
<div ng-controller="AppCtrl as vm">
<h1>mural</h1>
<p>{{vm.msg}}</p>
</div>Observe que não é uma simples página html. É efetivamente uma aplicação funcional. A mensagem que apresentada não é parte do html original lido pelo browser. É parte do código da função que define o controller. Como isso funciona? Ao iniciar o módulo angular, ele procura no DOM pela diretiva
ng-app. Em seguida, dentro desse nó, procura pelas diretivasng-controllere instancia um controller (ou um view-model) para cada diretiva encontrada. É desse objeto que vem a mensagem lida na view.
Modifique a aplicação para compreender melhor o que está ocorrendo.
Adicione outras properties à view-model e mostre esses dados na view.
Replique o
dive veja que a mensagem é mostrada uma vez para cadang-controllerque aparecer no html.
- Crie outro controller diferente de
AppCtrle faça um novodivque o use. Use properties de tipo numérico.
class: middle, center, inverse
Para exibir dados armazenados no view-model, usamos expressões angular, tais como .blue[
{{vm.msg}}]. Com expressões, contudo, podemos manipular os dados antes de exibi-los, de forma que podemos derivar dados a partir do view-model. A expressão .blue[{{vm.msg.length}}], por exemplo, exibe o comprimento da string. Já a expressão ternária .blue[{{vm.condicao ? vm.msg1 : vm.msg2}}] exibiria o conteúdo de .green[vm.msg1] ou de .green[vm.msg2] a depender do valor de .green[vm.condicao].Expressões também podem invocar métodos no controller. Por exemplo, um controller que defina as properties .green[
vm.peso] e .green[vm.altura] pode ter também definir um método .green[vm.imc()] que calcula o IMC. Assim, se a view incluir a expressão .blue[{{vm.imc()}}] o imc será dinamicamente calculado e exibido sempre que o valor das properties .green[vm.peso] e .green[vm.altura] mudar.
Expressões complexas levam para a view, parte da lógica que poderia ser colocada no controller. Embora seja recurso conveniente e útil, abusar de seu uso é um bad smell que indica quebra de arquitetura.
A condição ternária do slide anterior, por exemplo, poderia ser substituída pela expressão .blue[
{{vm.msg()}}] que invoca o método .green[vm.msg()] no view-model/controller que implementa a lógica. O pseudo-código abaixo demonstra a implementação do controller para esse caso.
function MeuController() {
const vm = this;
vm.condicao = …
vm.msg = function () {
return vm.condicao ? 'msg1' : 'msg2';
};
}Observe que esta abordagem garante que a lógica que determina a mensagem fica contida no controller e não espalhada pela view (no código html).
A view não é mera cópia dos dados no view-model. Trata-se de uma vinculação permanente chamada de two-way data binding. A ideia é que se o dado for modificado, a view será automaticamente atualizada por angular. Podemos ver isso em funcionamento, modificando a property .blue[
vm.msg] após alguns segundos. Observe que devemos usar o serviço$timeoutde angular ao invés dosetTimeoutde javascript. Mas a ideia é a mesma. Atualize o controller de acordo com o código abaixo. Observe que o controller recebe$timeoutcomo argumento. Mais adiante entenderemos isso melhor.
function AppCtrl($timeout) {
const vm = this;
vm.msg = "oi (vindo do controller)";
$timeout(function () {
vm.msg = 'OI!!! (mudado pelo controller)';
}, 3000)
}Releia a aplicação no browser e observe que em 3 segundos, a mensagem será automaticamente mudada.
O nome two-way data binding é usado para expressar que a vinculação é bidirecional. A ideia é que quando a view for diretamente atualizada pelo usuário, o dado no view-model também seja automaticamente modificado. Para experimentar isso, atualize o
divde nosso controller como indicado abaixo.
<div ng-controller="AppCtrl as vm">
<h1>mural</h1>
<p>{{vm.msg}}</p>
<p>comprimento msg: {{vm.msg.length}}</p>
<p>msg: <input type="text" ng-model="vm.msg"></p>
</div>Elementos html do tipo .blue[
input] são usados para permitir que o usuário digite dados na aplicação. Poderíamos ler esse dado manualmente com javascript, mas usando two-way data binding, isso é feito automaticamente. A diretiva .blue[ng-model] é usada como atributo do .blue[input] para e permite declarar a property do view-model à qual o valor digitado deve ser vinculado (.green[vm.msg] em nosso exemplo).
Uma forma recorrente de uso do
two-way data bindingé na exibição de dados contidos em arrays. Para isso, usamos a diretivang-repeat. Para entender melhor, adicione odivabaixo aindex.html.
<div ng-controller="MensagensCtrl as vm">
<h1>mensagens</h1>
<p>número de mensagens: {{vm.mensagens.length}}</p>
<div class="msg" ng-repeat="msg in vm.mensagens">
<div class="msg-texto">{{msg.texto}}</div>
<div class="msg-autor">{{msg.autor}}</div>
</div>
</div>A
divacima não é um nó simples. É uma sub-árvore completa. Além dos elementosh1ep, o nó contém umdivque, por sua vez, contém dois outrosdivs. Odivexterno é um container de vinculação ao controller e respectivo view-model. Odivinterno.ref[3].footnote[.ref[3] Osdivs internos são usados apenas para estruturar os dados para aplicação de estilos.] representa uma mensagem. Contudo, observe que ele tem a diretivang-repeatusada para indicar que, quando a view for renderizada, uma cópia dodivdeve ser criada para cada um dos valores em .green[vm.mensagens].
adicione o código do novo controller ao arquivo app.js
app.controller('MensagensCtrl', MensagensCtrl);
function MensagensCtrl() {
const vm = this;
vm.mensagens = [
{autor: "fulano", texto: "mensagem 1"},
{autor: "beltrano", texto: "outra mensagem"},
{autor: "sicrano", texto: "terceira mensagem"},
{autor: "beltrano", texto: "quarta mensagem"}
];
}confirme que as mensagens são exibidas, fazendo o reload da aplicação
Para melhorar a visualização, adicione as três regras abaixo ao arquivo
app.css.
.msg {
margin: 10px 0 10px 0;
border: 1px solid #aaa;
background: #cfc;
border-radius: 8px;
padding: 10px;
}
.msg-texto {
color: #088;
}
.msg-autor {
color: gray;
font-size: 85%;
}class: middle, center, inverse
Projetos maiores requerem que o código possa ser dividido em diferentes arquivos. Por um lado, isso é fácil em JavaScript, dado que os scripts são executados no mesmo namespace global. Dividir o script em arquivos não é problema. Contudo, isso obriga a poluir o namespace global. Para resolver isso, angular suporta o conceito de módulos. Isso permite que: o código de cada script seja executado em uma IIFE, protegendo o namespace global; e 2) que as partes tenham acesso aos componentes criados nos diferentes scripts.
Para isso, usaremos o método
moduledeangular. No início usamos o método para criar um módulo. Veremos agora que o método permite também recuperar um módulo existente. A diferença de comportamento é controlada pela sintaxe. Se dois parâmetros forem usados, trata-se de criação de um módulo. Se houver um único parâmetro, trata-se da recuperação de um módulo.
use a sintaxe abaixo .green[para declarar/criar] novos módulos…
(function () {
// sintaxe para criação de um módulo
const mod1 = angular.module('modulo1', []);
}());…e esta .green[para recuperar] um módulo previamente declarado
(function () {
// sintaxe para recuperação de um módulo
const mod1 = angular.module('modulo1');
}());.green[app.js]: declara o módulo da aplicação
(function () {
const app = angular.module('mural', []);
}());.green[app-controller.js]: cria AppCtrl
(function () {
const app = angular.module('mural');
app.controller('AppCtrl', function AppCtrl() {
const vm = this;
vm.msg = "oi (vindo do controller)";
});
}());.green[mensagens-controller.js]: cria MensagensCtrl
(function () {
const app = angular.module('mural');
app.controller('MensagensCtrl', function MensagensCtrl() {
const vm = this;
vm.mensagens = [
{autor: "fulano", texto: "mensagem 1"},
{autor: "beltrano", texto: "outra mensagem"},
{autor: "sicrano", texto: "terceira mensagem"},
{autor: "beltrano", texto: "quarta mensagem"}
];
});
}());atualize os scripts que devem ser lidos pela aplicação
<!-- Aplicação -->
<script src="app.js"></script>
<script src="app-controller.js"></script>
<script src="mensagens-controller.js"></script>Observe que os scripts devem ser lidos nessa ordem, por conta da ordem de declaração e uso do módulo
mural.
class: middle, center, inverse
Services (ou providers) em AngularJS são objetos cujo tempo de vida persiste ao longo de toda uma sessão de uso da aplicação. Compare isso com controllers (ou view-models) que são destruídos sempre que o nó correspondente é eliminado do DOM. Isso torna os services o melhor lugar para armazenar models (no sentido MVC) da aplicação.
Além disso, uma vez registrados, services podem ser facilmente injetados em controllers, de forma que permitem compartilhar os models ou outros dados entre controllers e outros serviços.
Infelizmente, Angular provê 5 “receitas” para registrar serviços. Essas receitas são: value, factory, service, provder e constant. Todas elas compartilham algo em comum: apenas um único objeto será criado, no primeiro uso do service. A partir daí, o mesmo objeto será compartilhado sempre que for requisitado.
O método
factoryde um módulo angular permite registrar um serviço de forma extremamente simples. Consiste em passar para o método o nome do serviço e uma função que constrói (“fabrica”) o objeto service propriamente dito. A receita típica é semelhante a esta.
(function () {
const app = angular.module('nome_app');
app.factory('NomeService', function () {
const service = {};
service.dados1 = ["x", "y", …, "z"];
service.dados2 = "msg";
service.metodo = function () {
…
};
return service;
});
}());O código consiste simplesmente em criar o objeto
service, equipá-lo com os properties necessários (possivelmente, dados do servidor) e retornar o objeto. Mas, lembre, só um objeto será criado.
As mensagens do mural são o exemplo típico de dado (um model) que precisa ser compartilhado por vários controllers. Vamos, portanto, refatorar nosso código, levando os dados para um service. Para isso, crie o service abaixo.
(function () {
const app = angular.module('mural');
app.service('Mensagens', function MensagensCtrl() {
const service = {};
service.mensagens = [
{autor: "fulano", texto: "mensagem 1"},
{autor: "beltrano", texto: "outra mensagem"},
{autor: "sicrano", texto: "terceira mensagem"},
{autor: "beltrano", texto: "quarta mensagem"}
];
return service;
});
}());Lembre-se de complementar isso, atualizando o index.html com o arquivo desse service. Sugestão: nomeie o arquivo .green[
mensagens-service.js].
Uma vez criado o service, podemos usá-lo nos controllers ou até em outros serviços. Para isso, precisamos usar injeção de dependência. Felizmente, é só um nome sofisticado para algo super simples. A ideia é que você “peça” a angular que o serviço seja “injetado” via passagem de parâmetros. Assim, quando angular instanciar seu controller ele criará (ou recuperará) o serviço nomeado e o passará como parâmetro para a função construtora do controller. Isso é feito simplesmente adicionando o parâmetro com o nome do serviço a seu controller. Relembre, como exemplo, o exercício em que usamos
$timeout. Faremos da mesma forma com os serviços que criarmos.
Assim, para adicionar o serviço
Mensagensque criamos no slide anterior ao controller, basta reescrevermos o controller da seguinte forma.
(function () {
const app = angular.module('mural');
app.controller('MensagensCtrl', function MensagensCtrl(Mensagens) {
vm = this;
vm.mensagens = Mensagens.mensagens;
});
}());Observe que nesta versão do código, optei por colocar a versão em estilo inlined no registro do controller.

