Onde colocar dados e comportamento do modelo?

Eu estou trabalhando com o AngularJS para o meu último projeto. Na documentação e nos tutoriais, todos os dados do modelo são colocados no escopo do controlador. Eu entendo que tem que estar lá para estar disponível para o controlador e, portanto, dentro das vistas correspondentes.

No entanto, eu não acho que o modelo realmente deveria ser implementado lá. Pode ser complexo e ter atributos privados, por exemplo. Além disso, pode-se querer reutilizá-lo em outro contexto / aplicativo. Colocar tudo no controlador quebra totalmente o padrão MVC.

O mesmo vale para o comportamento de qualquer modelo. Se eu usasse a arquitetura DCI e separasse o comportamento do modelo de dados, precisaria introduzir objects adicionais para manter o comportamento. Isso seria feito pela introdução de papéis e contextos.

É claro que dados e comportamento de modelos podem ser implementados com objects javascript simples ou qualquer padrão de “class”. Mas qual seria a maneira do AngularJS fazer isso? Usando serviços?

Então, tudo se resume a essa pergunta:

Como você implementa modelos desacoplados do controlador, seguindo as melhores práticas do AngularJS?

Você deve usar serviços se você quiser algo utilizável por vários controladores. Aqui está um exemplo simples:

myApp.factory('ListService', function() { var ListService = {}; var list = []; ListService.getItem = function(index) { return list[index]; } ListService.addItem = function(item) { list.push(item); } ListService.removeItem = function(item) { list.splice(list.indexOf(item), 1) } ListService.size = function() { return list.length; } return ListService; }); function Ctrl1($scope, ListService) { //Can add/remove/get items from shared list } function Ctrl2($scope, ListService) { //Can add/remove/get items from shared list } 

Atualmente estou tentando esse padrão, que, embora não seja DCI, fornece um desacoplamento de serviço / modelo clássico (com serviços para falar com serviços da Web (também conhecido como modelo CRUD) e modelo que define as propriedades e methods do object).

Observe que eu só uso esse padrão sempre que o object de modelo precisar de methods trabalhando em suas próprias propriedades, que provavelmente usarei em qualquer lugar (como getter / setters aprimorados). Eu não estou defendendo fazer isso para todos os serviços sistematicamente.

EDIT: Eu costumava pensar que este padrão iria contra o mantra “Angular modelo é simples antigo object de javascript”, mas parece-me agora que esse padrão é perfeitamente bem.

EDIT (2): Para ser ainda mais claro, eu uso uma class Model apenas para fatorar getters / setters simples (por exemplo: para ser usado nos templates de view). Para lógica de grandes negócios, recomendo usar serviços separados que “saibam” sobre o modelo, mas sejam mantidos separados deles e incluam apenas lógica de negócios. Chame isso de uma camada de serviço “especialista em negócios” se você quiser

service / ElementServices.js (observe como o Element é injetado na declaração)

 MyApp.service('ElementServices', function($http, $q, Element) { this.getById = function(id) { return $http.get('/element/' + id).then( function(response) { //this is where the Element model is used return new Element(response.data); }, function(response) { return $q.reject(response.data.error); } ); }; ... other CRUD methods } 

model / Element.js (usando angularjs Factory, feito para criação de objects)

 MyApp.factory('Element', function() { var Element = function(data) { //set defaults properties and functions angular.extend(this, { id:null, collection1:[], collection2:[], status:'NEW', //... other properties //dummy isNew function that would work on two properties to harden code isNew:function(){ return (this.status=='NEW' || this.id == null); } }); angular.extend(this, data); }; return Element; }); 

A documentação do Angularjs afirma claramente:

Ao contrário de muitos outros frameworks, a Angular não faz restrições ou requisitos sobre o modelo. Não há classs para herdar ou methods especiais de access para acessar ou alterar o modelo. O modelo pode ser primitivo, hash de object ou um tipo de object completo. Em suma, o modelo é um object JavaScript simples.

Então, isso significa que você decide como declarar um modelo. É um simples object JavaScript.

Eu pessoalmente não usarei os Serviços Angulares como eles deveriam se comportar como objects singleton que você pode usar, por exemplo, para manter estados globais em seu aplicativo.

O DCI é um paradigma e, como tal, não há maneira angular de fazer isso, nem o suporte ao idioma DCI ou não. O JS suporta muito bem o DCI se você estiver disposto a usar a transformação de origem e com algumas desvantagens se não estiver. Novamente, o DCI não tem mais a ver com a injeção de dependência do que a class C # e definitivamente não é um serviço. Portanto, a melhor maneira de fazer o DCI com o angulusJS é fazer o DC do jeito JS, o que é bem próximo de como o DCI é formulado em primeiro lugar. A menos que você faça uma transformação de origem, não será possível fazer isso completamente, já que os methods de function farão parte do object, mesmo fora do contexto, mas esse geralmente é o problema com o DCI baseado em injeção de método. Se você olhar para fullOO.info o site oficial para DCI você pode dar uma olhada nas implementações de ruby que eles também usam injeção de método ou você poderia dar uma olhada aqui para obter mais informações sobre DCI. É principalmente com exemplos RUby, mas o material DCI é agnóstico para isso. Uma das chaves da DCI é que o que o sistema faz é separado do que o sistema é. Assim, o object de dados é bastante burro, mas, uma vez vinculado a uma function em um papel de contexto, os methods tornam certo comportamento disponível. Um papel é simplesmente um identificador, nada mais, um quando acessando um object através desse identificador, então os methods de function estão disponíveis. Não há object / class de function. Com a injeção de método, o escopo dos methods de papel não é exatamente como descrito, mas próximo. Um exemplo de um contexto em JS poderia ser

 function transfer(source,destination){ source.transfer = function(amount){ source.withdraw(amount); source.log("withdrew " + amount); destination.receive(amount); }; destination.receive = function(amount){ destination.deposit(amount); destination.log("deposited " + amount); }; this.transfer = function(amount){ source.transfer(amount); }; } 

Como afirmado por outros pôsteres, o Angular não fornece nenhuma class base para modelagem, mas pode-se fornecer várias funções:

  1. Métodos para interagir com uma API RESTful e criar novos objects
  2. Estabelecendo relações entre modelos
  3. Validando dados antes de persistir no backend; também é útil para exibir erros em tempo real
  4. Cache e carregamento lento para evitar solicitações HTTP desnecessárias
  5. Ganchos da máquina de estado (antes / depois de salvar, atualizar, criar, novo, etc)

Uma biblioteca que faz todas essas coisas bem é ngActiveResource ( https://github.com/FacultyCreative/ngActiveResource ). Divulgação completa – eu escrevi essa biblioteca – e usei-a com sucesso na criação de vários aplicativos de escala empresarial. É bem testado e fornece uma API que deve ser familiar aos desenvolvedores do Rails.

Minha equipe e eu continuamos a desenvolver ativamente esta biblioteca, e eu adoraria ver mais desenvolvedores Angular contribuindo com ela e batalhando para testá-la.

Uma questão mais antiga, mas acho que o tópico é mais relevante do que nunca, dada a nova direção do Angular 2.0. Eu diria que uma prática recomendada é escrever código com o menor número possível de dependencies em uma estrutura específica. Use somente as partes específicas da estrutura onde ela agrega valor direto.

Atualmente, parece que o serviço Angular é um dos poucos conceitos que chegarão à próxima geração do Angular, por isso é provavelmente inteligente seguir a diretriz geral de mover toda a lógica para os serviços. No entanto, eu diria que você pode fazer modelos desacoplados mesmo sem uma dependência direta dos serviços angulares. Criar objects autocontidos apenas com dependencies e responsabilidades necessárias é provavelmente o caminho a percorrer. Isso também facilita muito a vida ao realizar testes automatizados. Responsabilidade única é um trabalho de buzz nos dias de hoje, mas faz muito sentido!

Aqui está um exemplo de um padrão que considero bom para desacoplar o modelo de object do dom.

http://www.syntaxsuccess.com/viewarticle/548ebac8ecdac75c8a09d58e

Um dos principais objectives é estruturar seu código de uma forma que facilite o uso de testes de unidade e de uma visualização. Se você conseguir isso, você está bem posicionado para escrever testes realistas e úteis.

Eu tentei resolver esse problema exato nesta postagem do blog .

Basicamente, a melhor casa para modelagem de dados é em serviços e fábricas. No entanto, dependendo de como você recupera seus dados e da complexidade dos comportamentos necessários, há várias maneiras diferentes de implementar a implementação. Angular atualmente não possui uma maneira padrão ou uma melhor prática.

A postagem abrange três abordagens, usando $ http , $ resource e Restangular .

Aqui está um exemplo de código para cada um, com um método getResult() personalizado no modelo de trabalho:

Restangular (fácil peasy):

 angular.module('job.models', []) .service('Job', ['Restangular', function(Restangular) { var Job = Restangular.service('jobs'); Restangular.extendModel('jobs', function(model) { model.getResult = function() { if (this.status == 'complete') { if (this.passed === null) return "Finished"; else if (this.passed === true) return "Pass"; else if (this.passed === false) return "Fail"; } else return "Running"; }; return model; }); return Job; }]); 

$ resource (um pouco mais complicado):

 angular.module('job.models', []) .factory('Job', ['$resource', function($resource) { var Job = $resource('/api/jobs/:jobId', { full: 'true', jobId: '@id' }, { query: { method: 'GET', isArray: false, transformResponse: function(data, header) { var wrapped = angular.fromJson(data); angular.forEach(wrapped.items, function(item, idx) { wrapped.items[idx] = new Job(item); }); return wrapped; } } }); Job.prototype.getResult = function() { if (this.status == 'complete') { if (this.passed === null) return "Finished"; else if (this.passed === true) return "Pass"; else if (this.passed === false) return "Fail"; } else return "Running"; }; return Job; }]); 

$ http (hardcore):

 angular.module('job.models', []) .service('JobManager', ['$q', '$http', 'Job', function($q, $http, Job) { return { getAll: function(limit) { var deferred = $q.defer(); $http.get('/api/jobs?limit=' + limit + '&full=true').success(function(data) { var jobs = []; for (var i = 0; i < data.objects.length; i ++) { jobs.push(new Job(data.objects[i])); } deferred.resolve(jobs); }); return deferred.promise; } }; }]) .factory('Job', function() { function Job(data) { for (attr in data) { if (data.hasOwnProperty(attr)) this[attr] = data[attr]; } } Job.prototype.getResult = function() { if (this.status == 'complete') { if (this.passed === null) return "Finished"; else if (this.passed === true) return "Pass"; else if (this.passed === false) return "Fail"; } else return "Running"; }; return Job; }); 

A postagem do blog entra em detalhes no raciocínio por trás de por que você pode usar cada abordagem, além de exemplos de código de como usar os modelos em seus controladores:

Modelos de dados do AngularJS: $ http VS $ resource VS Restangular

Existe a possibilidade de o Angular 2.0 oferecer uma solução mais robusta para modelagem de dados que coloque todos na mesma página.