【问题标题】:AngularJS REST app - design optimisationAngularJS REST 应用程序 - 设计优化
【发布时间】: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)
      }
    );
  }
}]);

【问题讨论】:

    标签: django angularjs rest


    【解决方案1】:

    你有一个好的开始,你只需要更深入地研究 Angular,一切都会水到渠成。

    首先,use Services/Factories/Providers。它们的功能很像资源,可以注入,它们作为单例工作。您应该始终使用服务而不是 $rootScope 作为最佳实践,即使它们的工作方式相似,因为您不会在服务方面犯那么多愚蠢的错误,而且您的代码会更干净。

    例如,对于问题 1,您可以为您的项目和城市提供服务,在幕后使用您的项目和城市资源;该服务将充当您的数据存储单例而不是 $rootScope,并且它可以提供方便的方法,因此消费者不必手动执行 query() 调用。

    对于问题 2,是否仅返回更改的项目或服务器上的所有项目取决于您。我建议返回所有项目以避免您发现的问题,或者让服务器接受一个参数,让消费者选择他们想要返回的数据。

    对于问题 3,根据经验,如果您必须手动拨打 $apply(),您可能做错了什么。如果您在 Angular 框架之外执行代码(例如,如果您使用 jQuery 方法或处理自定义事件)并且需要更新模型以响应您所做的事情,则应该只调用 $apply()。在您的情况下,由于您使用的是 Angular 的资源,因此您不需要调用 $apply()。

    我认为您真正想做的是$watch() 您的数据服务进行更改。基本上,这就像调用 $apply(),但不同之处在于您让 Angular 确定何时需要更新,这样可以更高效、更清洁、更安全。下面是一个假设示例,说明如何设置:

    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
        );
    }
    

    现在,简而言之,这就是将要发生的事情:

    • 您将发出 GET 请求,该请求将检索数据并将其存储在 DataService 中。
    • Angular 会检查它的 $watches
    • 您的 $watch 会注意到 DataService.getCoolProjects() 与以前不同
    • 您的代码将更改 $scope 的属性
    • Angular 会像往常一样自动应用您的更改

    试一试,看看它们是如何工作的。一旦掌握了 Services 和 $watch 的窍门,您会发现编写始终有效的良好 Angular 代码要容易得多,而不仅仅是“偶然”。

    【讨论】:

    • 谢谢,我已经实现了一个 ProjectService 并发布了一些代码,它运行良好。仍然需要研究这种平面与嵌套 REST 响应。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-06-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多