Graças a uma enorme quantidade de fontes valiosas, eu tenho algumas recomendações gerais para implementar componentes nos aplicativos AngularJS:
Controlador
O controlador deve ser apenas um interlayer entre o modelo e a visualização. Tente torná-lo o mais fino possível.
É altamente recomendável evitar a lógica de negócios no controlador. Deve ser movido para o modelo.
O controlador pode se comunicar com outros controladores usando a invocação de método (possível quando os filhos desejam se comunicar com os pais) ou os métodos $ emit , $ broadcast e $ on . As mensagens emitidas e transmitidas devem ser reduzidas ao mínimo.
O controlador não deve se preocupar com a apresentação ou manipulação do DOM.
Tente evitar controladores aninhados . Nesse caso, o controlador pai é interpretado como modelo. Injete modelos como serviços compartilhados.
O escopo no controlador deve ser usado para vincular o modelo com vista e
encapsular o modelo de vista como para o padrão de design do modelo de apresentação .
Escopo
Trate o escopo como somente leitura em modelos e somente gravação em controladores . O objetivo do escopo é se referir ao modelo, não ao modelo.
Ao fazer a ligação bidirecional (modelo ng), certifique-se de não ligar diretamente às propriedades do escopo.
Modelo
O modelo no AngularJS é um singleton definido pelo serviço .
O modelo fornece uma excelente maneira de separar dados e exibição.
Os modelos são candidatos principais para testes de unidade, pois normalmente possuem exatamente uma dependência (alguma forma de emissor de evento, no caso comum o $ rootScope ) e contêm lógica de domínio altamente testável .
O modelo deve ser considerado como uma implementação de determinada unidade. É baseado no princípio de responsabilidade única. Unidade é uma instância que é responsável por seu próprio escopo de lógica relacionada, que pode representar uma entidade única no mundo real e descrevê-la no mundo da programação em termos de dados e estado .
O modelo deve encapsular os dados do aplicativo e fornecer uma API
para acessar e manipular esses dados.
O modelo deve ser portátil para que possa ser facilmente transportado para aplicação semelhante.
Ao isolar a lógica da unidade em seu modelo, você facilitou a localização, atualização e manutenção.
O modelo pode usar métodos de modelos globais mais gerais, comuns a todo o aplicativo.
Tente evitar a composição de outros modelos em seu modelo usando injeção de dependência, se não for realmente dependente de diminuir o acoplamento de componentes e aumentar a testabilidade e a usabilidade da unidade .
Tente evitar o uso de listeners de eventos nos modelos. Isso os torna mais difíceis de testar e geralmente mata modelos em termos de princípio de responsabilidade única.
Implementação do modelo
Como o modelo deve encapsular alguma lógica em termos de dados e estado, ele deve restringir arquitetonicamente o acesso a seus membros, para que possamos garantir um acoplamento flexível.
A maneira de fazer isso no aplicativo AngularJS é defini-lo usando o tipo de serviço de fábrica . Isso nos permitirá definir propriedades e métodos privados com muita facilidade e também retornar os acessíveis publicamente em um único local, o que o tornará realmente legível para o desenvolvedor.
Um exemplo :
angular.module('search')
.factory( 'searchModel', ['searchResource', function (searchResource) {
var itemsPerPage = 10,
currentPage = 1,
totalPages = 0,
allLoaded = false,
searchQuery;
function init(params) {
itemsPerPage = params.itemsPerPage || itemsPerPage;
searchQuery = params.substring || searchQuery;
}
function findItems(page, queryParams) {
searchQuery = queryParams.substring || searchQuery;
return searchResource.fetch(searchQuery, page, itemsPerPage).then( function (results) {
totalPages = results.totalPages;
currentPage = results.currentPage;
allLoaded = totalPages <= currentPage;
return results.list
});
}
function findNext() {
return findItems(currentPage + 1);
}
function isAllLoaded() {
return allLoaded;
}
// return public model API
return {
/**
* @param {Object} params
*/
init: init,
/**
* @param {Number} page
* @param {Object} queryParams
* @return {Object} promise
*/
find: findItems,
/**
* @return {Boolean}
*/
allLoaded: isAllLoaded,
/**
* @return {Object} promise
*/
findNext: findNext
};
});
Criando novas instâncias
Tente evitar ter uma fábrica que retorne uma nova função capaz, pois isso começa a interromper a injeção de dependência e a biblioteca se comportará de maneira desajeitada, principalmente para terceiros.
Uma maneira melhor de realizar a mesma coisa é usar a fábrica como uma API para retornar uma coleção de objetos com métodos getter e setter anexados a eles.
angular.module('car')
.factory( 'carModel', ['carResource', function (carResource) {
function Car(data) {
angular.extend(this, data);
}
Car.prototype = {
save: function () {
// TODO: strip irrelevant fields
var carData = //...
return carResource.save(carData);
}
};
function getCarById ( id ) {
return carResource.getById(id).then(function (data) {
return new Car(data);
});
}
// the public API
return {
// ...
findById: getCarById
// ...
};
});
Modelo Global
Em geral, tente evitar tais situações e projetar seus modelos adequadamente, para que possam ser injetados no controlador e usados em sua visão.
Em particular, alguns métodos requerem acessibilidade global dentro do aplicativo. Para tornar isso possível, você pode definir a propriedade ' common ' em $ rootScope e vinculá-la ao commonModel durante a inicialização do aplicativo:
angular.module('app', ['app.common'])
.config(...)
.run(['$rootScope', 'commonModel', function ($rootScope, commonModel) {
$rootScope.common = 'commonModel';
}]);
Todos os seus métodos globais viverão dentro de propriedades ' comuns '. Este é algum tipo de espaço para nome .
Mas não defina nenhum método diretamente no seu $ rootScope . Isso pode levar a um comportamento inesperado quando usado com a diretiva ngModel dentro do seu escopo de exibição, geralmente desarrumando seu escopo e levando a métodos de escopo a substituir problemas.
Recurso
O recurso permite que você interaja com diferentes fontes de dados .
Deve ser implementado usando o princípio de responsabilidade única .
Em particular, é um proxy reutilizável para terminais HTTP / JSON.
Os recursos são injetados nos modelos e oferecem a possibilidade de enviar / recuperar dados.
Implementação de recursos
Uma fábrica que cria um objeto de recurso que permite interagir com fontes de dados RESTful do lado do servidor.
O objeto de recurso retornado possui métodos de ação que fornecem comportamentos de alto nível sem a necessidade de interagir com o serviço $ http de baixo nível.
Serviços
Modelo e recurso são serviços .
Os serviços não são associados, são fracamente acoplados unidades de funcionalidade que são independentes.
Os serviços são um recurso que o Angular traz para os aplicativos Web do lado do cliente, do lado do servidor, onde os serviços costumam ser usados há muito tempo.
Serviços em aplicativos angulares são objetos substituíveis que são conectados usando injeção de dependência.
Angular vem com diferentes tipos de serviços. Cada um com seus próprios casos de uso. Leia Noções básicas sobre tipos de serviço para obter detalhes.
Tente considerar os principais princípios da arquitetura de serviço em seu aplicativo.
Em geral, de acordo com o Glossário de Serviços da Web :
Um serviço é um recurso abstrato que representa a capacidade de executar tarefas que formam uma funcionalidade coerente do ponto de vista das entidades provedoras e solicitantes. Para ser usado, um serviço deve ser realizado por um agente fornecedor concreto.
Estrutura do lado do cliente
Em geral, o lado do cliente do aplicativo é dividido em módulos . Cada módulo deve ser testável como uma unidade.
Tente definir módulos dependendo do recurso / funcionalidade ou exibição , não por tipo. Veja a apresentação de Misko para detalhes.
Os componentes do módulo podem ser agrupados convencionalmente por tipos como controladores, modelos, visualizações, filtros, diretivas etc.
Mas o próprio módulo permanece reutilizável , transferível e testável .
Também é muito mais fácil para os desenvolvedores encontrarem algumas partes do código e todas as suas dependências.
Por favor, consulte Organização de código em aplicativos AngularJS e JavaScript grandes para obter detalhes.
Um exemplo de estruturação de pastas :
|-- src/
| |-- app/
| | |-- app.js
| | |-- home/
| | | |-- home.js
| | | |-- homeCtrl.js
| | | |-- home.spec.js
| | | |-- home.tpl.html
| | | |-- home.less
| | |-- user/
| | | |-- user.js
| | | |-- userCtrl.js
| | | |-- userModel.js
| | | |-- userResource.js
| | | |-- user.spec.js
| | | |-- user.tpl.html
| | | |-- user.less
| | | |-- create/
| | | | |-- create.js
| | | | |-- createCtrl.js
| | | | |-- create.tpl.html
| |-- common/
| | |-- authentication/
| | | |-- authentication.js
| | | |-- authenticationModel.js
| | | |-- authenticationService.js
| |-- assets/
| | |-- images/
| | | |-- logo.png
| | | |-- user/
| | | | |-- user-icon.png
| | | | |-- user-default-avatar.png
| |-- index.html
Um bom exemplo de estruturação angular de aplicativos é implementado pelo angular-app - https://github.com/angular-app/angular-app/tree/master/client/src
Isso também é considerado pelos geradores de aplicativos modernos - https://github.com/yeoman/generator-angular/issues/109