【发布时间】:2014-02-24 14:20:08
【问题描述】:
我正在使用 AngularJS 和由 Django Rest Framework 提供支持的 REST 后端开发一个小型单页应用程序。
到目前为止,这相当简单,一切正常,但在我继续之前,有些点感觉不“正确”......
到目前为止,该应用程序管理着与城市相关联的项目列表。
所以我使用 $resource 实现了 2 个工厂:Project 和 City。
- GET /projects 以 json 格式返回所有项目
- GET /cities 返回 json 中的所有城市
现在我使用 ui-router 和以下状态在应用中导航:
- projects : 显示项目列表
- project/{id} : 显示一个项目
- project/edit/{id} : 创建一个新项目(如果 id 为空)或更新项目
城市同上。
现在我确实为每个状态关联了一个控制器,基本上每次状态更改时我都会查询后端以获取项目列表或单个项目。
1) 我认为通过在我的 app.run() 中调用 Project.query() 和 City.query() 并将它们保存在 $rootScope 中来维护项目列表是有意义的。
现在每当我更新或删除一个对象时,我都需要在整个 $rootScope.projects 上迭代 (forEach() ) 以寻找匹配的 id 并相应地更新/删除。
打开单个项目 (/project/{id}) 时也是如此,我需要搜索 $rootScope.projects 以找到该项目。
这样可以吗,还是在此类操作时始终与服务器同步是最佳做法?目前只有一个用户编辑项目,但将来可能会改变。
2) 因为无论如何都需要在我的项目中拥有城市名称(而不仅仅是 id),所以当我 GET /projects 时,我会收到一个城市的嵌套对象,例如{id:1,名称:'纽约'}。当我更新/创建时,我发送我的项目的平面表示,仅带有 id(例如城市:1)。如果一切顺利,服务器会回复 201 OK 并附加项目。问题是附加的项目也是扁平的,所以当我更新 $rootScope.projects 时,我需要:
- 先找到要更新的项目的id
- 遍历城市以查找与该项目相关联的城市
- 将 project.city 替换为上面找到的城市对象。
这没关系...但可能不是最佳的。有更好的方法吗?我可以有一个平面 GET 并使用 $rootScope.cities 相应地填充模板。
3) 如果我在只有一个项目的状态下直接打开应用程序,例如 /#/project/1,控制器会在 GET 完成之前尝试在 $rootScope.projects 中找到项目 id 1。我添加了:
if(!$rootScope.$$phase) { //this is used to prevent an overlap of scope digestion
$rootScope.$apply(); //this will kickstart angular to recognize the change
}
就在两个 query() 调用之后,它就可以工作了。只是再次想知道这是否是一种好习惯......
嗯,我很想听听你们中的一些人已经对此进行了实验。我可以提供更多代码,但正如我所说,它正在工作,所以我更多的是寻找有关 Angular 应用程序实际设计的反馈。
谢谢!
编辑 1
感谢您的回复,我已经添加了一项服务,我什至不必$watch,似乎工作得很好。我想分享一些代码,只是为了指出一些不好的做法或可以改进的东西。我发现很难找到完整的例子......
所以你去吧,这很简单,我希望 cmets 说得够清楚。基本上这是我的 projects.js 脚本,它定义了用于通过 REST 与服务器通信的 Project 资源、处理控制器和 Porject 资源之间通信的 ProjectService 以及在应用程序的不同状态下使用的不同控制器(列表项目、查看项目和编辑项目)。
/**
* 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)
}
);
}
}]);
【问题讨论】: