Использование одного и того же контроллера для всех операций CRUD (Rails-alike)

У меня есть угловой контроллер, который выбирает ресурс при создании:

angular.module('adminApp')
  .controller('PropertiesCtrl', function ($log, $scope, Property, $location) {
    $scope.properties = Property.query()  
  });

Теперь я хочу добавить логику в контроллер, чтобы иметь возможность создавать ресурс "Свойство":

angular.module('adminApp')
  .controller('PropertiesCtrl', function ($log, $scope, Property, $location) {
    $scope.properties = Property.query()  
    $scope.create = function(){
      //logic to create
    };
  });

Тем не менее, когда я нахожусь в форме, чтобы создать "Свойство", ненужный вызов делается для извлечения всех свойств в первую очередь. Как мне избежать этого?


Потенциальные решения?

  1. Я мог бы создать отдельный контроллер специально для создания свойства, которое не будет извлекать свойства. Однако было бы проще инкапсулировать все операции CRUD для одного ресурса под одним контроллером.
  2. Я мог бы создать функцию для извлечения всех свойств. Тем не менее, моя страница индекса использует "свойства" напрямую. Сначала мне нужно получить данные, вызвав какой-то метод, а затем использовать данные (как-нибудь?)

3 ответа

Решение

Моя реакция заключается в том, что кажется, что вы пытаетесь использовать контроллер в качестве службы, и вы пытаетесь использовать множество функций в одном контроллере.

Итак, есть две основные вещи, о которых вы должны подумать. Прежде всего, очень важно создать контроллер, который имеет только одну конкретную цель каждый. Обратите внимание, что это не то же самое, что использование контроллера только один раз, вам рекомендуется использовать один и тот же контроллер в нескольких разных местах, если у вас есть функция, которая должна появляться в нескольких местах. Это просто означает, что контроллер не должен делать несколько вещей одновременно.

Давайте возьмем фотогалерею в качестве примера. Хотя вы можете создать один контроллер, который получает все фотографии, позволяет добавлять новые фотографии и позволяет редактировать и удалять существующие фотографии в одном, это будет плохой идеей. Что, если вы решите, что добавление фотографии также можно сделать с другой страницы, "Страница X"? Если вам нужно повторно использовать тот же контроллер, вы также будете запрашивать галерею с сервера и настраивать элементы управления для вещей, которые вы не собираетесь размещать на этой странице.

Если бы вместо этого вы создали один контроллер, который отвечает только за получение контента, отдельный контроллер для добавления новых фотографий, другой для редактирования и т. Д., То это было бы легко. Вы просто реализуете контроллер create на "странице X", и вам не нужно беспокоиться о случайном срабатывании больше, чем вы хотели. Вы можете выбрать для реализации именно ту функцию, которую вы хотите на странице, и только эту функцию. Это также делает ваши контроллеры маленькими, легкими для чтения и быстрыми для редактирования / исправления ошибок, так что это выигрыш / выигрыш!

Во-вторых, если вы хотите собрать все свои ресурсы CRUD в один объект (что я также хотел бы сделать), они не должны быть в контроллере, они должны быть в службе. Таким образом, у вас есть один PhotoAPI, который предоставляет функции CREATE, READ, UPDATE и DELETE. Тогда ваш контроллер индексов просто вызывает функцию READ, ваш контроллер создания - функцию CREATE и т. Д. Контроллеры определяют, какие функции и данные доступны где, но логика находится в комбинированном сервисе. Таким образом, вы можете объединить свои ресурсы, чтобы их было легко найти, не создавая проблем с наличием комбинированного контроллера.

Так что-то вроде:

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

Из вашего вопроса (и из ваших SO-тегов) я вижу, что вы хотите создать Rails-подобные контроллеры в AngularJS. Поскольку обе платформы (Rails и AngularJS) имеют схожий принцип MVC, это на самом деле довольно легко осуществить.

Обе платформы позволяют указывать разные маршруты для использования одного и того же контроллера.

В Rails ваши обычные методы (действия) index/show/new/edit/destroy предоставляются прямо из коробки (со скаффолдингом). Эти действия по умолчанию сопоставлены с различными, хорошо установленными маршрутами и методами HTTP.

CRUD / Список маршрутов в Rails

Теперь, в приложениях AngularJS (или во всех SPA), вам нужен только поднабор этих маршрутов, потому что клиентская маршрутизация распознает только запросы GET:

CRU / Список маршрутов в AngularJS

AngularJS изначально не предоставляет механизм скаффолдинга, который генерировал бы все ваши маршруты CRUD для вас. Но, тем не менее, он предоставляет вам как минимум два различных способа подключения маршрутов CRUD/List к одному контроллеру.

Вариант 1 (Использование $location.path())

Используя метод location.path(), вы можете структурировать PhotosCtrl делать разные вещи в зависимости от, ну, пути расположения.

Маршруты:

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

    }
  ]
);

контроллер:

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

  }
]);

Эти четыре ifs заставляет контроллер выглядеть немного грязно, но при замене 4-х разных контроллеров на один, несколько условий неизбежны.

Вариант 2 (Использование свойства разрешения)

Этот вариант использует resolveсвойство объекта конфигурации маршрута для создания разных "идентификаторов действий" для разных маршрутов.

Маршруты:

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

    }
  ]
);

контроллер:

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

  }
]);

Оба метода требуют использования некоторых условий в вашем контроллере, но второй метод, по крайней мере, немного понятнее для чтения, потому что точное действие решается внутри механизма маршрутизации, который снимает некоторую логику с вашего занятого контроллера.

Конечно, в любом реальном приложении у вас, вероятно, будет гораздо больше методов, определенных внутри контроллера, и в этом случае ваш контроллер может стать совершенно нечитаемым. В этих примерах используется простой экземпляр $resource (Phones), который опирается на простой RESTfull backend API (Rails?). Но когда ваша логика представления становится сложной, вы, вероятно, захотите использовать сервисы / фабрики Angular, чтобы абстрагировать часть кода в ваших контроллерах.

Хорошо иметь несколько представлений (и контроллеров), использующих один и тот же ресурс... Это неплохой дизайн.

Если вам требуется более 1 ресурса для выполнения всех операций CRUD, это будет проблемой.

Пойдите со своим первым решением. 1 контроллер на просмотр. Это ресурс, который перегруппирует все операции CRUD, а не один контроллер.

Другие вопросы по тегам