Using same controller for all CRUD operations (Rails-alike)

前端 未结 3 1079
没有蜡笔的小新
没有蜡笔的小新 2020-12-29 15:07

I have an angular controller that fetches a resource on creation:

angular.module(\'adminApp\')
  .controller(\'PropertiesCtrl\', function ($log, $scope, Pro         


        
相关标签:
3条回答
  • 2020-12-29 15:44

    It's fine to have multiple views (and controllers) using the same resource... It's not a bad design.

    If you need more than 1 resource to do all the CRUD operations, that would be a problem.

    Go with your first solution. 1 controller per view. It's the resource that regroup all the CRUD operations, not a single controller.

    0 讨论(0)
  • 2020-12-29 15:45

    My reaction is that it sounds like you are trying to use a controller as a service, and like you are trying to put to many features into one controller.

    So there are two main things you should think about. First of all it is fairly important create controller that only have one specific purpose each. Note that this is not the same thing as using the controller only once, you are encuraged to use the same controller in several different places if you have a feature that should appear in more than one place. It just means that the controller shouldn't be doing several things at once.

    Let's take a photo gallery as an example. While you could create one controller that gets all the photos, lets you add new photos, and lets you edit and delete existing photos all in one, this would be a bad idea. What if you decide that adding a photo can also be done from another page, "Page X"? If you where to reuse the same controller then you would also be requesting the gallery from the server and setting up controls for things you don't intend to be on that page.

    If you instead made one controller that is only responsible for getting the content, a separate controller for adding new photos, another one for editing, etc, then it would be easy. You would just implement the create-controller on "Page X" and you don't have to worry about accidentaly triggering more than you wanted. You can choose to implement exactly the feature you want on a page and only that feature. This also keeps your controllers small, easy to read and quick to edit/bugfix, so it's a win/win!

    Secondly, if you want to gather all your CRUD resources into one object (which I would also want to do), they should not be in a controller, they should be in a service. So you have one PhotoAPI which exposes CREATE, READ, UPDATE, and DELETE functions. Then your index controller just calls the READ function, your create controller the CREATE function etc. The controllers define what functions and data are available where, but the logic is in the combined service. That way you can clump your resources to make them easy to find, without creating the problems with having a combined controller.

    So something like:

    app.service('PhotoAPIService', [
    function() {
       this.READ = function() {
         // Read logic
       }
    
      this.CREATE = function() {
         // Create logic
       }
    }]);
    
    app.controller('PhotoIndexController', [
    '$scope',
    'PhotoAPIService',
    function($scope, PhotoAPIService) {
       $scope.photos = PhotoAPIService.READ(<data>);
    }]);
    
    
    app.controller('PhotoCreateController', [
    '$scope',
    'PhotoAPIService',
    function($scope, PhotoAPIService) {
       $scope.createPhoto = PhotoAPIService.CREATE;
    }]);
    
    0 讨论(0)
  • 2020-12-29 16:05

    I see from your question (and from your SO tags) that you want to create a Rails-like controllers in AngularJS. Since both frameworks (Rails and AngularJS) share a similar MVC principle this is actually quite easy to accomplish.

    Both frameworks allow you to instruct different routes to use the same controller.

    In Rails, your usual index/show/new/edit/destroy methods (actions) are provided out of the box (with scaffolding). These default actions are mapped to different, well established routes and HTTP methods.

    CRUD/List routes in Rails enter image description here

    Now, in AngularJS applications (or all SPAs for that matter) you need only a subset of these routes, because client-side routing understands only GET requests:

    CRU/List routes in AngularJS enter image description here

    AngularJS natively does not provide a scaffolding mechanism that would generate all your CRUD routes for you. But nevertheless it provides you with at least two different ways of wiring up your CRUD/List routes with a single controller.

    Option 1 (Using $location.path())

    Using location.path() method you can structure your PhotosCtrl to do different things depending on, well, location path.

    Routes:

    app.config(
      [
        '$routeProvider',
        function ($routeProvider) {
    
          $routeProvider
            .when('/photos', {
              templateUrl: 'photos/index.html',
              controller: 'PhotosCtrl'
            })
            .when('/photos/new', {
              templateUrl: 'photos/new.html',
              controller: 'PhotosCtrl'
            })
            .when('/photos/:id', {
              templateUrl: 'photos/show.html',
              controller: 'PhotosCtrl'
            })
            .when('/photos/:id/edit', {
              templateUrl: 'photos/edit.html',
              controller: 'PhotosCtrl'
            });
    
        }
      ]
    );
    

    Controller:

    app.controller('PhotosCtrl', [
      '$scope',
      'Photos', // --> Photos $resource with custom '$remove' instance method
      '$location',
      '$routeParams',
      function($scope, Photos, $location, $routeParams){
        if($location.path() === '/photos'){
          // logic for listing photos
          $scope.photos = Photos.query();
        }
    
        if($location.path() === '/photos/new'){
          // logic for creating a new photo
          $scope.photo = new Photos();
        }
    
        if(/\/photos\/\d*/.test($location.path())){ // e.g. /photos/44
          // logic for displaying a specific photo
          $scope.photo = Photos.get({id: $routeParams.id});
        }
    
        if(/\/photos\/\d*\/edit/.test($location.path())){ // e.g. /photos/44/edit
          // logic for editing a specific photo
          $scope.photo = Photos.get({id: $routeParams.id});
        }
    
        // Method shared between 'show' and 'edit' actions
        $scope.remove = function(){
          $scope.photo.$remove();
        }
    
        // Method shared between 'new' and 'edit' actions
        $scope.save = function(){
          $scope.photo.$save();
        }
    
      }
    ]);
    

    These four ifs makes the controller look a bit messy, but when replacing 4 different controllers with one, few conditionals are inevitable.

    Option 2 (Using resolve property)

    This option employes the resolveproperty of the route configuration object to produce different 'action identifier' for different routes.

    Routes:

    app.config(
      [
        '$routeProvider',
        function ($routeProvider) {
    
          $routeProvider
            .when('/photos', {
              templateUrl: 'photos/index.html',
              controller: 'PhotosCtrl',
              resolve: {
                action: function(){return 'list';}
              }
            })
            .when('/photos/new', {
              templateUrl: 'photos/new.html',
              controller: 'PhotosCtrl',
              resolve: {
                action: function(){return 'new';}
              }
            })
            .when('/photos/:id', {
              templateUrl: 'photos/show.html',
              controller: 'PhotosCtrl',
              resolve: {
                action: function(){return 'show';}
              }
            })
            .when('/photos/:id/edit', {
              templateUrl: 'photos/edit.html',
              controller: 'PhotosCtrl',
              resolve: {
                action: function(){return 'edit';}
              }
            });
    
        }
      ]
    );
    

    Controller:

    app.controller('PhotosCtrl', [
      '$scope',
      'Photos',
      '$routeParams',
      'action'
      function($scope, Photos, $routeParams, action){
        if(action === 'list'){
          // logic for listing photos
          $scope.photos = Photos.query();
        }
    
        if(action === 'new'){
          // logic for creating a new photo
          $scope.photo = new Photos();
        }
    
        if(action === 'show')
          // logic fordisplaying a specfiic photo
          $scope.photo = Photos.get({id: $routeParams.id});
        }
    
        if(action === 'edit')
          // logic for editing a specfic photo
          $scope.photo = Photos.get({id: $routeParams.id});
        }
    
        // Method shared between 'show' and 'edit' actions
        $scope.remove = function(){
          $scope.photo.$remove();
        }
    
        // Method shared between 'new' and 'edit' actions
        $scope.save = function(){
          $scope.photo.$save();
        }
    
      }
    ]);
    

    Both methods require using some conditionals in your controller, but the second method is at least a bit clearer to read, because the exact action is resolved inside the routing mechanism, which takes some logic off of your busy controller.

    Of course, in any real-world application you'll probably have many more methods defined inside the controller, in which case your controller might get quite unreadable. These examples use a simple $resource instance (Phones) which relies on a simple RESTfull backend API (Rails?). But, when your view logic becomes complex you will probably want to employ Angular services/factories in order to abstract some of the code in your controllers.

    0 讨论(0)
提交回复
热议问题