Model complexo
Os exemplos apresentados até o momento lidaram com estruturas mais básicas para o model. O Angular também permite trabalhar com model com uma estrutura mais complexa, como será visto nesta seção. Por "estrutura mais complexa" e por "model complexo" quero dizer que o aplicativo é um CRUD completo e que o model possui uma formulação com duas entidades relacionadas entre si.
Estrutura do aplicativo
O aplicativo realiza o CRUD de cidades. As funcionalidades são:
- listar cidades (com filtro e ordenação)
- cadastrar cidade
- excluir cidade
- editar cidade
Há dois arquivos no aplicativo: app.js
e index.html
. O primeiro contém a definição do módulo e do controller, respectivamente: cidades
e CidadesController
. O app.js
contém, ainda, a definição da estrutura de dados usada no model e o CidadesController
contém a lógica para o CRUD e definir dados iniciais (estados e cidades).
O arquivo index.html
fica com a responsabilidade de representar a view e o comportamento do aplicativo. Ele contém, essencialmente, código HTML e utiliza as diretivas do angular para que seja possível a comunicação com o módulo cidades
e seu conteúdo.
Model
Há duas entidades no modelo de dados: Cidade
e Estado
. A figura a seguir apresenta um diagrama de classes UML.
A classe Estado
possui uf
e nome
, enquanto a classe Cidade
possui estado
e nome
. Assim, há um relacionamento entre as duas entidades. Estas entidades estão definidas no arquivo app.js
.
function Cidade(estado, nome) {
this.estado = estado;
this.nome = nome;
}
function Estado(uf, nome) {
this.uf = uf;
this.nome = nome;
}
Entretanto, é bem verdade que o JavaScript não requer que estas entidades estejam definidas como "classes", portanto, este tipo de procedimento não é estritamente necessário.
Lógica de negócio (CRUD)
Como já disse, o CRUD está definido no CidadesController
(arquivo app.js
) no qual está definida a lógica de negócio para a manipulação do model e a comunicação com a view. As seções a seguir fazem referência ao CidadesController
e explicam seu funcionamento.
Definição dos dados
O aplicativo começa com alguns dados pré-definidos. Esta etapa poderia ser substituída por uma comunicação com um banco de dados (se estiver disponível).
$scope.estados = [
new Estado('TO', 'Tocantins'),
new Estado('SP', 'São Paulo'),
new Estado('MG', 'Minas Gerais')
];
$scope.cidades = [
new Cidade(encontrarEstado('TO'), 'Araguaína'),
new Cidade(encontrarEstado('TO'), 'Gurupi'),
new Cidade(encontrarEstado('TO'), 'Palmas'),
new Cidade(encontrarEstado('TO'), 'Porto Nacional'),
new Cidade(encontrarEstado('TO'), 'Paraíso do Tocantins'),
new Cidade(encontrarEstado('SP'), 'São Paulo'),
new Cidade(encontrarEstado('MG'), 'Belo Horizonte')
];
Em resumo, o código cria dois elementos no $scope
: estados
, que representa um vetor de Estados, e cidades
, que representa um vetor de Cidades. A função encontrarEstado()
recebe como parâmetro uma UF, pesquisa a lista de Estados e retorna o objeto (Estado) correspondente. Importante lembrar que é necessário fazer com que estes objetos estejam no $scope
para que a view consiga acessar estes dados.
Uma parte importante do código que não está explícita é que o controller também trata de uma "cidade atual", que representa a cidade que está sendo cadastrada ou editada no momento. Isso ficará mais visível a seguir.
Excluir
A funcionalidade de exclusão é representada pela função $scope.excluir()
:
$scope.excluir = function(index) {
$scope.cidades.splice(index, 1);
};
A função recebe como parâmetro o índice da cidade a ser excluída e usa a função splice()
para remover um elemento do vetor $scope.cidades
.
Salvar
A funcionalidade de salvar dados representa dois aspectos: o cadastro de uma [nova] cidade e a atualização (edição) dos dados de uma cidade. Esta funcionalidade é representada pela função $scope.salvar()
:
$scope.salvar = function(cidade) {
if (!$scope.cidade.edit) {
var c = angular.copy(cidade);
$scope.cidades.push(c);
cidade.nome = "";
cidade.estado = null;
} else {
$scope.cidade = null;
}
};
A função recebe como parâmetro um objeto Cidade
e, como já disse, realiza duas tarefas. O código verifica se o objeto $scope.cidade
(a cidade atual -- e seria o mesmo que usar o parâmetro cidade
) não possui um atributo edit
. Se for este o caso, o modo é o de cadastro. Neste caso, é feita uma cópia do objeto, a qual é inserida no vetor $scope.cidades
por meio da função push()
. Na sequência, os dados do objeto são limpos, para garantir que o formulário de cadastro apresente os valores padrões. O segundo caso (se o $scope.cidade
possuir o atributo edit
) é o de edição. Neste caso, o código não realiza uma tarefa mais específica, senão redefinir a referência de $scope.cidade
. Por que isso acontece?
O controller CidadesController
expõe para a view a função $scope.editar()
:
$scope.editar = function(cidade) {
$scope.cidade = cidade;
$scope.cidade.edit = true;
}
Esta função recebe um objeto Cidade
como parâmetro e o atribui a $scope.cidade
(a cidade atual). Neste caso, o código faz com que a cidade atual fique em modo de edição, por assim dizer. Como já visto, a função $scope.salvar()
leva em consideração a existência de um atributo edit
no objeto que representa a cidade que está sendo salva. Assim, a última linha da função $scope.editar()
cria o atributo edit
no objeto cidade
atual e define seu valor como true
.
Uma vez definida a lógica de negócio do aplicativo, a seção a seguir apresenta a definição da view.
View
A view, representada pelo arquivo index.html
, contém a interface gráfica (o visual e o comportamento) do aplicativo. A estrutura da interface gráfica é representada por duas seções:
- à esquerda, a lista de cidades, com opção de filtro; e
- à direita, o formulário de cadastro e edição.
Para estruturar a interface desta maneira (em colunas) foram utilizados recursos do Bootstrap.
Lista de cidades
Para a lista de cidades, a parte principal está no trecho de código a seguir:
<tr ng-repeat="cidade in cidades | filter:q | orderBy:estado.uf">
<td>{{$index}}</td>
<td>{{cidade.estado.uf}}</td>
<td>{{cidade.nome}}</td>
<td>
<button type="button" class="btn btn-danger btn-xs" ng-click="excluir($index)">
<span class="glyphicon glyphicon-remove" aria-hidden="true"></span>
</button>
<button type="button" class="btn btn-default btn-xs" ng-click="editar(cidade)">
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span>
</button>
</td>
</tr>
O trecho de código em questão representa a criação das linhas da tabela que apresenta a lista de cidades. Primeiro, perceba a utilização da diretiva ng-repeat
. Seu valor indica que o elemento tr
será repetido conforme o vetor cidades
(definido no CidadesController
), que cada elemento do vetor será representado, na repetição, por um objeto chamado cidade
, que o vetor é filtrado pelo elemento q
(vinculado a um elemento input
) e ordenado por estado.uf
. Perceba que a ordenação leva em consideração a estrutura do conteúdo do vetor, ou seja, cada elemento é do tipo Cidade
, que possui um atributo estado
, que é do tipo Estado
, que possui um atributo uf
.
A primeira coluna apresenta o índice da lista (por meio de $index
, uma das propriedades automáticas da diretiva ng-repeat
). A segunda coluna apresenta a UF do Estado da Cidade (cidade.estado.uf
) e a terceira coluna apresenta o nome da cidade (cidade.nome
).
A última coluna apresenta dois botões de ação: excluir e editar. O clique no botão excluir chama a função excluir()
(definda no CidadesController
) passando como parâmetro o índice atual da iteração ($index
). O clique no botão editar chama a função editar()
(também definida no CidadesController
) passando como parâmetro a cidade atual da iteração (o objeto, em si). Perceba que, no caso do botão editar, o comportamento esperado é o que acontece, ou seja, os dados da cidade em questão são apresentados no formulário, para edição. Além disso, quando você editar os dados (nome e estado) a lista de cidades já será atualizada, demonstrando a edição.
Formulário de cadastro e edição
O formulário à direita realiza a função de cadastrar e editar os dados de uma cidade. O trecho de código a seguir apresenta a parte principal desta seção do aplicativo:
<form class="form">
<div class="form-group">
<label for="nomeDaCidade">Nome
</label>
<input type="text" id="nomeDaCidade" class="form-control"
ng-model="cidade.nome" placeholder="Nome da cidade" required>
</div>
<div class="form-group">
<label for="estado">Estado</label>
<select class="form-control" ng-model="cidade.estado.uf">
<option ng-repeat="estado in estados" value="{{estado.uf}}">
{{estado.nome}}
</option>
</select>
</div>
<div>
<button class="btn btn-default" type="button"
ng-click="salvar(cidade)">Salvar</button>
</div>
</form>
Utilizando Bootstrap, o formulário possui dois campos: nome da cidade e Estado. O primeiro campo é representado por um elemento input
, vinculado a cidade.nome
. O segundo campo é um elemento select
, vinculado a cidade.estado.uf
. Importante notar que os elementos option
do select
são criados por meio da diretiva ng-repeat
, com base no vetor de Estados definido no CidadesController
. Ainda, o atributo value
de cada elemento option
está vinculado a estado.uf
.
A última parte do formulário é o botão "Salvar". No clique do botão salvar, é chamada a função salvar()
, definida no CidadesController
.
Você pode ver o exemplo em funcionamento aqui.