Esse capítulo aborda a criação de um módulo AngularJS completo, contendo controlador, serviço CRUD e interface para listagem, criação, modificação e deleção de objetos de uma entidade.
A implementação abordada é continuação do plugin criado no capítulo anterior. O CRUD será em cima da entidade Item.
Antes da abordagem da implementação do módulo em si, vamos criar uma base para testes. Uma rota /meu-plugin/itens, um arquivo de visão e um template part com o template angular da listagem.
// arquivo Controllers/MeuPlugin.php
function GET_itens () {
$this->requireAuthentication();
$this->render('items');
}
Primeiro, deve-se criar um arquivo JS com o esqueleto do módulo contendo controller e service vazios. É necessário modificar o conteúdo das requisições para que o Angular não envie um JSON no corpo da requisição, pois o Slim não vai parsear corretamente.
// arquivo assets/js/meu-plugin/ng.module.item.js
(function (angular) {
var module = angular.module('module.item', ['ngSanitize']);
// modifica as requisições POST para que sejam lidas corretamente pelo Slim
module.config(['$httpProvider', function ($httpProvider) {
$httpProvider.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=utf-8';
$httpProvider.defaults.headers.put['Content-Type'] = 'application/x-www-form-urlencoded;charset=utf-8';
$httpProvider.defaults.headers.common['X_REQUESTED_WITH'] = 'XMLHttpRequest';
$httpProvider.defaults.transformRequest = function (data) {
var result = angular.isObject(data) && String(data) !== '[object File]' ? $.param(data) : data;
return result;
};
}]);
// Seriço que executa no servidor as requisições HTTP
module.factory('ItemService', ['$http', function ($http) {
return {};
}]);
// Controlador da interface
module.controller('ItemController', ['$scope', 'ItemService', function ($scope, ItemService) {
$scope.data = {};
}]);
})(angular);
Enfileirando Arquivo JS
Com o arquivo JS do módulo criado, é necessário que ele seja incluído na visão. Isto pode ser feito de diferentes maneiras, mas para este exemplo, faremos o enfileiramento no próprio arquivo do template part:
// layouts/parts/meu-plugin/items-list.php
<?php
$this->enqueueScript(
'app', // grupo de scripts
'ng-module-item', // nome do script
'js/meu-plugin/ng.module.item.js', // arquivo do script
[] // dependências do script
);
?>
<div>
<h1>Lista de itens</h1>
<ul>
<li>Item 1 - <a>remover</a></li>
<li>Item 2 - <a>remover</a></li>
</ul>
</div>
Chamando o Módulo no Template
O AngulaJS precisa de um elemento com a diretiva ng-app que indica qual o módulo principal da página. Como utilizaremos o módulo numa nova rota que ainda não tem nenhum ng-app definido, colocaremos a propriedade na tag com a classe main-content:
// arquivo views/meu-plugin/items.php
<div ng-app="module.item" class="main-content">
<h1> Lista de itens </h1>
<?php $this->part('meu-plugin/items-list'); ?>
</div>
Definido o ng-app, podemos chamar o controlador no arquivo do template part colocando a diretiva ng-controller na div que envolve o ul:
Em seguida, fazemos a iteração no template utilizando a diretiva ng-repeat na tag li:
// layouts/parts/meu-plugin/items-list.php
<div ng-controller="ItemController">
<h1>Lista de itens</h1>
<ul>
<li ng-repeat="item in data.items">{{item.title}} - <a>remover</a></li>
</ul>
</div>
Para testar a listagem é necessário, nesse ponto, inserir algumas entradas na tabela item.
Recuperando Itens do Banco de Dados
Vamos implementar uma função no serviço que consulta a API de busca da entidade item:
// arquivo assets/js/meu-plugin/ng.module.item.js
// Seriço que executa no servidor as requisições HTTP
module.factory('ItemService', ['$http', function ($http) {
return {
find: function(keyword) {
var endpoint = MapasCulturais.baseURL + 'api/item/find';
var params = {'@select': '*'};
// para possibilitar filtro
if (keyword) {
params.title = 'ILIKE(*' + keyword + '*)';
}
return $http.get(endpoint, {params});
}
};
}]);
Em seguida, removemos os itens fakes do array de itens, implementamos função no controlador que chama a função find do serviço e chamamos essa função no controller para trazer os itens da API:
// arquivo assets/js/meu-plugin/ng.module.item.js
// Controlador da interface
module.controller('ItemController', ['$scope', 'ItemService', function ($scope, ItemService) {
$scope.data = {
items: []
};
// busca itens na api
$scope.findItems = function (keyword) {
ItemService.find(keyword).then(function(result) {
$scope.data.items = result.data;
});
}
// faz a busca assim que carrega a página
$scope.findItems();
Inserindo Itens
No serviço, criamos uma função insert que faz uma requisição POST em /item:
// arquivo assets/js/meu-plugin/ng.module.item.js
// Serviço que executa no servidor as requisições HTTP
module.factory('ItemService', ['$http', function ($http) {
.
.
.
// faz uma requisição do tipo POST
insert: function(item) {
var endpoint = MapasCulturais.createUrl('item', 'index');
return $http.post(endpoint, item);
},
No controlador, adicionamos uma propriedade para guardar o título do novo item, criamos uma função que utiliza essa variável para criar um item e enviar para o serviço:
Antes da listagem, adicionamos um formulário para criação de novo item:
// layouts/parts/meu-plugin/items-list.php
<div ng-controller="ItemController">
<h1>Lista de itens</h1>
<form>
<input type="text" placeholder="Título do novo item" ng-model="data.newItemTitle">
<button ng-click="insertItem()">criar novo item</button>
</form>
Deletando Itens
No serviço, criamos uma função remove que faz uma requisição http do tipo DELETE para /item/single/{id}.
// arquivo assets/js/meu-plugin/ng.module.item.js
// Serviço que executa no servidor as requisições HTTP
module.factory('ItemService', ['$http', function ($http) {
return {
.
.
.
// faz uma requisição DELETE para deletar um item
remove: function (item) {
var endpoint = MapasCulturais.createUrl('item', 'single', [item.id]);
return $http.delete(endpoint);
}
No controlador, criamos uma função $scope.removeItem que chama a função remove do serviço:
// arquivo assets/js/meu-plugin/ng.module.item.js
module.controller('ItemController', ['$scope', 'ItemService', function ($scope, ItemService) {
.
.
.
// deleta um item
$scope.removeItem = function(item) {
ItemService.remove(item).then(function () {
var index = $scope.data.items.indexOf(item);
if (index > -1) {
$scope.data.items.splice(index, 1);
}
});
}
E, chamamos ela no click do link remover:
// layouts/parts/meu-plugin/items-list.php
<div ng-controller="ItemController">
<h1>Lista de itens</h1>
<ul>
<li ng-repeat="item in data.items">{{item.title}} - <a ng-click="removeItem(item)">remover</a></li>
</ul>
</div>
Modificando Itens
No serviço, criamos uma função update que faz uma requisição http do tipo PUT para /item/single/{id}:
// arquivo assets/js/meu-plugin/ng.module.item.js
module.controller('ItemController', ['$scope', 'ItemService', function ($scope, ItemService) {
.
.
.
// atualiza um item
update: function(item) {
var endpoint = MapasCulturais.createUrl('item', 'single', [item.id]);
return $http.put(endpoint, item);
},
No controlador, adicionamos uma nova chave no $scope.data para guardar uma cópia do item que será editado.
E, adicionamos duas funções no $scope, a primeira que faz a cópia do objeto e a segunda que salva o objeto copiado.
// arquivo assets/js/meu-plugin/ng.module.item.js
module.controller('ItemController', ['$scope', 'ItemService', function ($scope, ItemService) {
$scope.data = {
newItemTitle: '',
editItem: null,
items: []
};
// copia o item para edição
$scope.editItem = function (item) {
$scope.data.editItem = angular.copy(item);
}
// modifica um item
$scope.updateItem = function(item) {
ItemService.update($scope.data.editItem).then(function (result) {
var index = $scope.data.items.indexOf(item);
if (index > -1) {
$scope.data.items[index] = result.data;
$scope.data.editItem = null;
}
});
}
No template, modificamos o conteúdo da tag li adicionando um formulário e um link para edição que chama a função $scope.editItem. Esta, faz uma cópia do item para o $data.editItem fazendo com que a condição para exibição do formulário seja verdadeira e que assim, o formulário é exibido: