/ / App REST AngularJS - otimização de design - django, angularjs, rest

AngularJS REST app - otimização de design - django, angularjs, rest

Estou desenvolvendo um pequeno aplicativo de página única usando AngularJS e um back-end REST desenvolvido pelo Django Rest Framework.

É bastante simples até agora e tudo está funcionando, mas antes de ir mais longe, alguns pontos não parecem "certos" ...

O aplicativo gerencia até agora uma lista de projetos que estão associados a uma cidade.

Então implementei 2 fábricas usando $ resource: Projeto e Cidade.

  • GET / projects retorna todos os projetos em json
  • GET / cities retorna todas as cidades em json

Agora eu navego no aplicativo usando ui-router e os seguintes estados:

  • projetos: exibe a lista de projetos
  • projeto / {id}: exibe um projeto
  • project / edit / {id}: cria um novo projeto (se id for nulo) ou atualiza o projeto

idem para cidades.

Agora associei um controlador para cada estado e basicamente estava consultando o back-end toda vez que o estado mudava para obter a lista de projetos ou o projeto único.

1) Achei que faria sentido manter a lista de projetos um e para todos, chamando Project.query () e City.query () em meu app.run () e salvando-os em $ rootScope.

Agora, sempre que eu atualizo ou excluo um objeto, preciso iterar (forEach ()) em todo o $ rootScope.projects procurando por um id correspondente e atualizar / remover de acordo.

O mesmo acontece ao abrir um único projeto (/ project / {id}), preciso pesquisar o $ rootScope.projects para encontrar o projeto.

Tudo bem ou é uma prática recomendada sempre sincronizar com o servidor durante essa operação? Por enquanto, há apenas um usuário editando projetos, mas isso pode mudar no futuro.

2) Como é obrigatório ter o nome da cidade(e não apenas o id) no meu projeto, quando recebo / recebo um objeto aninhado para a cidade, por exemplo, {id: 1, nome: "New York"}. Quando eu atualizo / crio eu envio uma representação plana do meu projeto apenas com o id (por exemplo, cidade: 1). Se tudo correr bem, o servidor responde com 201 OK e o projeto anexado. O problema é que o projeto anexado também é simples, então, quando atualizo $ rootScope.projects, preciso:

  • primeiro encontre o id do projeto para atualizar
  • percorra as cidades para descobrir qual cidade está associada a este projeto
  • substitua project.city pelo objeto cidade encontrado acima.

Isso está ok ... mas talvez não seja o ideal. Existe uma maneira melhor de fazer isso ? Posso ter um GET simples e usar $ rootScope.cities para preencher o modelo de acordo.

3) Se eu abrir diretamente o aplicativo em um estado com apenas um projeto, como / # / project / 1, o controlador tenta encontrar o ID do projeto 1 em $ rootScope.projects antes que o GET seja concluído. Eu já adicionei :

if(!$rootScope.$$phase) { //this is used to prevent an overlap of scope digestion
$rootScope.$apply(); //this will kickstart angular to recognize the change
}

logo após as chamadas de query () e funciona. Só mais uma vez me perguntando se é uma boa prática ...

Bem, eu estaria interessado em ouvir alguns dosvocê que já experimentou com isso. Posso fornecer mais código, mas, como disse, está funcionando, então estou mais procurando um feedback sobre o design real do aplicativo angular.

Obrigado !

EDITAR 1

Obrigado pela sua resposta, eu adicionei um serviço, eunão precisa nem $ assistir, parece funcionar perfeitamente. Eu gostaria de compartilhar algum código, apenas para apontar algumas práticas ruins ou coisas que podem ser melhoradas. Achei difícil encontrar exemplos completos ...

Então aqui está, é bastante simplesespero que os comentários tornem isso claro o suficiente. Basicamente, esse é o meu script projects.js, que define o recurso Project usado para comunicar via REST com o servidor, o ProjectService que trata da comunicação entre os controladores e o recurso Porject, e os diferentes controladores usados ​​nos diferentes estados da aplicação (listar projetos, ver e editar projetos).

/**
* Controllers and Resource manager for projects.
*/


/**
* This is the resource link to the REST framework.
* Adapted the "update" method (available view $update) to
* use the PUT method as per Django Rest requirements
*/
angularApp.factory("Project", ["$resource", function($resource){
return $resource("/api/projects/:id", {id: "@id"}, {
update: {method:"PUT", params: {id: "@id"}},
});
}]);

/**
* This Service handles the project management
*/
angularApp.factory("ProjectService", ["Project", function(Project) {
var projectsLoaded = false,
projects = [];

return {

/**
* Returns the complete list of the projects
* from the server.
* If the projects have already been loaded, then
* use the cache instead.
*/
getProjects: function() {
if (projectsLoaded) {
return projects;
} else {
projects = Project.query(function(){
projectsLoaded= true;
});
return projects;
}
},

/**
* Load a single project from the server.
* If the full list has already been loaded, then
* find it in the list instead
*
* @param Integer projectId
*/
getSingleProject: function(projectId) {
var toReturn = false;
if(!projectsLoaded) {
toReturn = Project.get({id: projectId});
} else {
projects.forEach(function(project, index) {
if(project.id == projectId) {
toReturn = project;
}
});
}
return toReturn;
},

getNewProject: function() {
return new Project();
},

/**
* Deletes a project.
* If the project list is already loaded, then update the list
* accordingly
*
* @param Project project : project to delete
* @param callbackSuccess function(result)
* @param callbackRejection function(rejection)
*/
delete: function(project, callbackSuccess, callbackRejection) {
project.$delete().then(function(result){
if(projectsLoaded) {
projects.forEach(function(project, index) {
if(project.id == result.id) {
projects.splice(index, 1);
}
})
};
callbackSuccess(result);
}, function(rejection) {
callbackRejection(rejection);
});
},

/**
* Creates a new project.
* If the project list is loaded, then add the newly created
* project to the list.
*
* @param Project projectToSave : the project to save in the database
* @param callbackSuccess function(result) : result is the value returned by the server
* @param callbackRejection function(rejection)
*/
save: function(projectToSave, callbackSuccess, callbackRejection) {
projectToSave.$save().then(function(result) {
if(projectsLoaded) {
projects.unshift(result);
}
callbackSuccess(result);
}, function(rejection) {
callbackRejection(rejection);
});
},

/**
* Updates a project, also updates the list if needed
*
* @param Project projectToUpdate to update
* @param callbackSuccess function(result)
* @param callbackRejection function(rejection)
*/
update: function(projectToUpdate, callbackSuccess, callbackRejection) {
projectToUpdate.$update().then(function(result){
if(projectsLoaded) {
projects.forEach(function(project, index) {
if(result.id == project.id) {
project = result;
}
})
}
callbackSuccess(result);
}, function(rejection) {
callbackRejection(rejection);
});
},


};

}]);

/**
* Controller to display the list of projects
*/
angularApp.controller("ProjectListCtrl", ["$scope", "ProjectService", function ($scope, ProjectService) {
$scope.projects = ProjectService.getProjects();
}]);


/**
* Controller to edit/create a project
*/
angularApp.controller("ProjectEditCtrl", ["$scope", "$stateParams", "ProjectService", "$state", function ($scope, $stateParams, ProjectService, $state) {
$scope.errors = null;
if($stateParams.id) {
$scope.project = ProjectService.getSingleProject($stateParams.id)
} else {
$scope.project = ProjectService.getNewProject();
}

$scope.save = function() {
ProjectService.save($scope.project,
function(result) {
$state.go("project_view", {id:result.id});
}, function(rejection) {
$scope.errors = rejection.data;
}
);
};

$scope.update = function() {
ProjectService.update($scope.project,
function(result) {
$state.go("project_view", {id: result.id});
}, function(rejection) {
$scope.errors = rejection.data;
}
);
};

}]);

/**
* Controller to show one project and delete it
*/
angularApp.controller("ProjectCtrl", ["$scope", "$stateParams", "ProjectService", "$state", function($scope, $stateParams, ProjectService, $state) {
$scope.project = ProjectService.getSingleProject($stateParams.id)

$scope.delete = function() {
ProjectService.delete($scope.project,
function(result){
$state.go("projects")},
function(rejection){
console.log(rejection)
}
);
}
}]);

Respostas:

2 para resposta № 1

Você teve um bom começo, só precisa se aprofundar um pouco mais no Angular e tudo se encaixará.

Começar com, usar Serviços / Fábricas / Provedores. Eles funcionam muito como recursos e podem ser injetados, e funcionam como singletons. Você deve SEMPRE usar serviços em vez de $rootScope como prática recomendada, embora funcionem de maneira semelhante, porque você não pode cometer tantos erros bobos com serviços e seu código ficará mais limpo.

Para a pergunta 1, por exemplo, você pode fazer umserviço para seus projetos e cidades que usa seus recursos de projeto e cidade nos bastidores; este serviço funcionaria como seu singleton de armazenamento de dados em vez de $rootScope, e pode fornecer métodos de conveniência para que o consumidor não precise fazer manualmente query() chamadas.

Para a pergunta 2, caberia a você decidir seretornar apenas o projeto alterado ou todos os projetos no servidor. Eu recomendaria retornar todos os projetos para evitar o problema que você encontrou, ou talvez fazer com que o servidor aceite um parâmetro para permitir que o consumidor escolha quais dados deseja que sejam retornados.

Para a pergunta 3, como regra geral, se você tiver que ligar manualmente $apply(), você pode estar fazendo algo errado. Você só deveria estar ligando $apply() se você está executando código fora do Angularframework (como se você estiver usando um método jQuery, ou manipulando um evento personalizado) e precisa que seu modelo seja atualizado em resposta ao que você fez. No seu caso, uma vez que você está usando o recurso Angular, você não deve precisar para chamar $ apply ().

Eu acho que o que você realmente quer fazer é $watch() seus serviços de dados para alterações. Isso é, basicamente, como chamar $apply(), mas a diferença é que você "está permitindo que o Angular determine quando as atualizações são necessárias, o que pode ser mais eficiente, limpo e seguro. Aqui está um exemplo hipotético de como você pode configurar isso:

function MySuperController($scope, DataService){
//get data from the service singleton
$scope.projects = DataService.getCoolProjects();

//watch for changes in that data
$scope.$watch(
//thing to watch
//Will get called a LOT, so make sure it"s not time-intensive!
function(){
return DataService.getCoolProjects();
},
//what to do on change
function(changedData){
$scope.projects = changedData;
//no $apply() needed; angular will do automatically!
},
//do a "deep" watch that checks the value of each project
true
);
}

Agora, aqui está o que vai acontecer, em poucas palavras:

  • Você fará sua solicitação GET, que irá recuperar os dados e armazená-los no DataService.
  • A Angular verificará seus relógios $
  • Seu $ watch notará que DataService.getCoolProjects () é diferente do que costumava ser
  • Seu código mudará uma propriedade do seu escopo $
  • O Angular aplicará automaticamente sua alteração, como de costume

Experimente e veja como funcionam.Depois de pegar o jeito de Services e $ watch, você descobrirá que é muito mais fácil escrever um bom código Angular que funcione o tempo todo, e não apenas "por acidente".