Passo 2 - Templates do Angular
Uma vez que o Passo 1 definiu a estrutura da interface gráfica, o template estático, o objetivo do Passo 2 é utilizar os recursos de template do Angular para que o aplicativo se torne dinâmico, carregando dados dos telefones de uma fonte de dados.
O resultado final deste passo é ilustrado pela figura a seguir.
O aplicativo apresenta uma lista de telefones, com foto, nome e um botão para visualizar detalhes de cada telefone (que ainda não está funcionando).
Uma das maneiras preferidas de criar aplicativos Angular é utilizar o padrão de projeto arquitetural MVC (Model-View-Controller). Ao utilizar este padrão, a estrutura do aplicativo permitirá separar código e responsabilidades, trazendo maior organização e facilidade de manutenção. Em resumo, cada um dos componentes é assim definido:
- Controller: representa a lógica de negócio (onde ficará o código JavaScript)
- Model: é uma abstração dos dados (a lista de telefones, por exemplo)
- View: é a interface gráfica; sob domínio do controller, apresenta o model (os dados) e efetua a interação com o usuário.
View e Template
No Angular, uma view é uma projeção do model por meio do template HTML, ou seja, é a apresentação dos dados. Na verdade, além de apresentar os dados, a view serve como receptora da interação com o usuário e repassa dados dessa interação para o controller. Quando o model for alterado, por exemplo, o Angular irá procurar pelas vinculações que usam o model e fará as atualizações necessárias.
No Passo 1, o arquivo index-1.html
apresenta a lista de telefones utilizando um template estático. Entretanto, não é interessante que o aplicativo seja criado desta forma, pois quaisquer alterações nos dados implicariam em alterações manuais na página (no template estático). A melhor abordagem, portanto, é separar conteúdo (dados) da apresentação (interface gráfica).
O código a seguir apresenta parte do arquivo index.html
:
<!doctype html>
<html lang="pt-br" ng-app="phonecat">
<head>
...
<link rel="stylesheet" href="app.css">
...
<script src="app.js"></script>
</head>
<body>
<div class="container" ng-controller="Home">
<h1>Telefones</h1>
<ul id="listaDeTelefones">
<li ng-repeat="telefone in telefones">
<div class="panel panel-default">
<div class="panel-body">
<img class="img-responsive" ng-src="{{telefone.images[0]}}">
<div class="caption text-center">
<h4>{{telefone.name}}</h4>
</div>
</div>
<div class="panel-footer text-center">
<a href="#" class="btn btn-default" role="button">
<i class="glyphicon glyphicon-zoom-in"></i> Detalhes
</a>
</div>
</div>
</li>
</ul>
</div> <!--/container-->
</body>
</html>
Há várias coisas interessantes acontecendo aqui. As seções a seguir apresentam detalhes.
Identificando a raiz do aplicativo com a diretiva ngApp
Anteriormente, o código indicava que o elemento html
era marcado com a diretiva ngApp
(atributo ng-app
). Entretanto, não identificava qual módulo do Angular era responsável por servir como ponto de partida do aplicativo. Um módulo é uma das formas principais de organização de código no Angular. Além disso, é também uma das maneiras de modularização de código, permitindo que usuários criem seus próprios módulos e os distribuam.
O código, agora, indica que o módulo se chama phonecat
:
<html lang="pt-br" ng-app="phonecat">
Arquivo JavaScript do aplicativo
Parte importante do aplicativo está separada no arquivo app.js
. O conteúdo deste arquivo será visto posteriormente, mas importante é considerar que ele contém a lógica de negócio da definição do módulo phonecat
.
Diretiva ngRepeat
O elemento li
do arquivo index.html
tem o atributo ng-repeat
. Este atributo representa a diretiva ngRepeater
, que faz com que o elemento seja repetido conforme uma determinada situação.
O valor do atributo ng-repeat
é telefone in telefones
. Assim, a diretiva ngRepeater
indica ao Angular para criar um elemento li
para cada telefone
da lista telefones
utilizando o conteúdo do li
como um template. Assim, as expressões contidas no li
serão devidamente interpretadas pelo Angular, como em:
<h4>{{telefone.name}}</h4>
Neste caso, a expressão indica que será substituída pelo valor do atributo name
de telefone
(sim, valem todos os princípios da Programação Orientada a Objetos aqui também).
Diretiva ngController
O elemento body
tem o atributo ng-controller
, que representa a diretiva ngController
. Seu valor é Home
, o que indica que o Angular irá procurar, no módulo phonecat
, um controller com este nome. O conteúdo do elemento body
serve como template
para o controller Home
.
A diretiva ng-repeat
está utilizando um objeto telefones
(um array
) que está definido no controller Home
. A imagem a seguir ajuda a ilustrar esse comportamento.
O escopo raiz é criado para o aplicativo. O escopo de "Home" pertence ao controller Home
, que herda do escopo raiz. No ng-repeat
são criados escopos locais, que herdam do escopo de "Home". A herança de escopo é um conceito que permite a um escopo "filho" acessar elementos (como objetos e funções) do escopo "pai".
Diretiva ngSrc
O código do template para apresentar a foto do produto é:
<img class="img-responsive" ng-src="{{telefone.images[0]}}">
No elemento img
é utilizado o atributo ng-src
, que representa a diretiva ngSrc
. Esta diretiva é utilizada como substituta da diretiva src
. O motivo de utilizá-la é que utilizar simplesmente src
não fará com o que o Angular interprete o valor da expressão. Neste caso, o valor {{telefone.images[0]}}
é interpretado pelo Angular como o primeiro elemento de telefone.images
(um array
).
Módulo phonecat
, Model e Controller
O módulo phonecat
está definido no arquivo app.js
.
'use strict';
angular.module('phonecat', []);
Na função angular.module()
o primeiro parâmetro é o nome do módulo, enquanto o segundo é a lista de dependências do módulo (mais sobre isso depois).
Exercício #1 Qual a finalidade de utilizar
'use strict';
na primeira linha do arquivo JavaScript?
A partir da definição do módulo, são criados os controllers. Na prática, a função angular.module()
retorna um objeto que pode ser atribuído a uma variável.
var phonecat = angular.module('phonecat', []);
O objeto fornece a função controller()
que aceita dois parâmetros:
- o nome do controller
- uma função que define o código do controller
O trecho de código:
angular.module('phonecat', []).
controller('Home', function() {
...
});
é similar a este:
var phonecat = angular.module('phonecat', []);
phonecat.controller('Home', function() {
...
});
Fica a critério do programador a maneira preferida.
Os argumentos da função (anônima ou não) que é passada como segundo argumento da função controller()
são tratados pelo injetor de dependência do Angular. O trabalho do injetor de dependência é instanciar classes de modo que o código do controller funcione adequadamente.
angular.module('phonecat', [])
.controller('Home', function($scope) {
...
});
Neste caso, a função do controller possui um parâmetro $scope
.
Importante: Certos objetos cujos nomes começam com
$
são particulares do Angular. Por isso, não use$
nos nomes de suas variáveis ou seus objetos.
O injetor de dependência identifica o parâmetro $scope
e trata de instanciá-lo para que ele esteja disponível para uso.
Objeto $scope
O objeto $scope
é utilizado para fornecer acesso ao escopo do controller. Voltando à figura anterior, por meio de $scope
é que acontecerá uma interação entre os três componentes do modelo MVC. Esta interação é chamada two-way data-binding (ou vinculação de via dupla). Isso significa que uma alteração no model realizada na view será sincronizada no controller, e vice-versa. Além disso, o objeto $scope
também pode conter funções. Por exemplo:
angular.module('phonecat', []).
controller('Home', function($scope) {
$scope.detalhes = function(telefone) {
...
};
});
Esta função poderá ser chamada na view, por exemplo, no clique de um botão. Mais sobre isso será visto posteriormente.
O modelo de dados (model) utilizado no aplicativo contém um array
de objetos (telefones). Sua definição ocorre no $scope
do controller Home
e é isso que permite que telefones
esteja disponível na view (template dinâmico, como visto antes).
angular.module('phonecat', []).
controller('Home', function($scope) {
$scope.telefones = [
...
];
});
O conteúdo do array
$scope.telefones
é composto de objetos que representam dados de telefones. Por enquanto, a view precisa (espera) cada um destes objetos tenha os atributos:
name
: o nome do telefoneimages
: umarray
de imagens (array
destring
cujos elementos representam nomes de arquivos de fotos do telefone)
O trecho de código a seguir ilustra a definição de $scope.telefones
.
'use strict';
angular.module('phonecat', [])
.controller('Home', function($scope) {
$scope.telefones = [
{
"id": "dell-streak-7",
"images": [
"img/phones/dell-streak-7.0.jpg",
"img/phones/dell-streak-7.1.jpg",
"img/phones/dell-streak-7.2.jpg",
"img/phones/dell-streak-7.3.jpg",
"img/phones/dell-streak-7.4.jpg"
],
"name": "Dell Streak 7"
}
];
});
Experimento #1
- Apresente a quantidade total de telefones na view.
- Adicione a propriedade
brand
(marca) nos telefones e apresente-a na view abaixo do nome do telefone