title: AngularJS - CRUD directive / service / controller patterns
body: <p>I'm wanting to create some directives that interacts with the parent controller / scope in some ways. I do CRUD in 2 ways:
  A) Route based where if you're about to edit a item I use $location to change url to a given url
  B) Same-Page based where if you click edit on a item it sets $scope.template from $scope.tpl, then in the partial I have a ng-hide / show which hides and shows the table view / detail view.</p>
  
  <p>What I want to achieve is maybe to have less code in my directives and maybe use a more service-based approach or hints? </p>
  
  <h1>Directives</h1>
  
  <pre><code>'use strict';
  
  /* Directives */
  var directives = angular.module("app.directives", ["ui"]);
  
  
  function CrudCtrl($scope, $attrs, $location, $parse) {
      function getScope(scopeName) {
          scopeName = typeof scopeName || "$parent";
          var ngModel = $parse(scopeName, $scope);
          return ngModel($scope)
      }
  
      function refreshObjects(scopeName) {
          $scope.svc.query($scope.params, function(objects) {
              var parentScope = getScope(scopeName)
              parentScope.objects = objects
          });
      }
  
      if (!$scope.refreshObjects) {
          $scope.refreshObjects = function() {
              refreshObjects($attrs.modelname)
          }
      }
  
      if (!$scope.crudAdd) {
          $scope.crudAdd = function() {
              if ($attrs.url) {
                  $location.path($attrs.url);
                  return;
              } else {
                  var parentScope = getScope($attrs.scope);
                  parentScope.object = new $scope.svc();
                  parentScope.template = parentScope.tpl;
              }
          }
      }
  
      if (!$scope.crudDelete) {
          $scope.crudDelete = function() {
              /* Fire off a delete and as a callback we update objects */
              $scope.svc.delete({accountId: $scope.account.uuid, id: $scope.object.id}, function() {
                  refreshObjects($attrs.scopeName)
              });
          };
      }
  
      if (!$scope.crudEdit) {
          $scope.crudEdit = function() {
              if ($attrs.url) {
                  $location.path($attrs.url);
                  return;
              } else {
                  var parentScope = getScope($attrs.scopeName);
                  parentScope.object = $scope.object;
                  parentScope.template = parentScope.tpl;
              }
          };
      }
  
      if (!$scope.crudSave) {
          $scope.crudSave = function() {
              var params = {}
              params.accountId = $scope.params.accountId
              if ($scope.object.id) { params.id = $scope.object.id }
  
              $scope.object.$save(params, function() {
                  if ($attrs.url) {
                      $scope.back();
                  } else {
                      refreshObjects($attrs.scopeName);
  
                      var parentScope = getScope($attrs.scopeName);
                      parentScope.template = undefined;
                  }
              });
          };
      }
  
      if (!$scope.crudCancel) {
          $scope.crudCancel = function() {
              if (parentScope.template) {
                  var parentScope = getScope($attrs.scopeName);
                  parentScope.template = undefined;
              } else {
                  $scope.back();
              }
          };
      };
  };
  
  
  directives.directive("refresh", function() {
      return {
          restrict: "E",
          replace: true,
          controller: CrudCtrl,
          scope: true,
          link: function(scope, element, attrs) {
              scope.display_text = attrs.text;
          },
          template: '&lt;button class="btn btn-mini btn-primary" ng-click="refreshObjects()"&gt;&lt;i class="icon-refresh"&gt;&lt;/i&gt; Refresh&lt;/button&gt;',
      };
  });
  
  
  /* Create something new */
  directives.directive("create", function() {
      return {
          restrict: "E",
          replace: true,
          controller: CrudCtrl,
          scope: true,
          link: function(scope, element, attrs) {
              scope.display_text = attrs.text;
          },
          template: '&lt;button class="btn btn-mini btn-success" ng-click="crudAdd()"&gt;&lt;i class="icon-plus"&gt;&lt;/i&gt; {{display_text || "Add"}}&lt;/button&gt;',
      };
  });
  
  
  /* Delete button and update objects */
  directives.directive("delete", function() {
      return {
          restrict: "E",
          replace: true,
          controller: CrudCtrl,
          scope: true,
          link: function(scope, element, attrs) {
              scope.display_text = attrs.text;
          },
          template: '&lt;button class="btn btn-mini btn-danger" ng-click="crudDelete()"&gt;&lt;i class="icon-remove icon-white"&gt;&lt;/i&gt; {{display_text}}&lt;/button&gt;',
      }
  });
  
  /* Helper to create a edit button */
  directives.directive("edit", function() {
      return {
          restrict: "E",
          replace:  true,
          controller: CrudCtrl,
          scope: true,
          link: function(scope, element, attrs) {
              scope.display_text = attrs.text;
          },
          template: '&lt;button class="btn btn-mini btn-info" ng-click="crudEdit()"&gt;&lt;i class="icon-edit"&gt;&lt;/i&gt; {{display_text || "Edit"}}&lt;/a&gt;',
      }
  });
  
  /* Save the object and return to the previous page */
  directives.directive("save", function() {
      return {
          restrict: "E",
          replace: true,
          controller: CrudCtrl,
          scope: true,
          link: function(scope, element, attrs) {
              scope.display_text = attrs.text;
          },
          template: '&lt;button class="btn btn-success" ng-click="crudSave()"&gt;&lt;i class="icon-ok"&gt; {{display_text || "Save"}}&lt;/i&gt;&lt;/a&gt;',
      };
  });
  
  /* Cancel the current action */
  directives.directive("cancel", function() {
      return {
          restrict: "E",
          replace: true,
          controller: CrudCtrl,
          scope: true,
          link: function(scope, element, attrs) {
              scope.display_text = attrs.text;
          },
          template: '&lt;button class="btn" ng-click="crudCancel()"&gt;&lt;i class="icon-remove"&gt;&lt;/i&gt; {{display_text || "Cancel"}}&lt;/button&gt;'
      }
  });
  </code></pre>
  
  <h1>An example controller</h1>
  
  <pre><code>function BookingCtrl($scope, Booking) {
        $scope.svc = Booking;
        $scope.objects = $scope.svc.query($scope.params);
    }
  </code></pre>
  
  <p>Then in a partial for a overview I have:</p>
  
  <pre><code>&lt;div ng-hide="template"&gt;
  &lt;refresh&gt;&lt;/refresh&gt;
  &lt;create url="/{{params.accountId}}/entity/add"&gt;&lt;/create&gt;
  
  &lt;table class="table table-condensed table-hover"&gt;
      &lt;thead&gt;
          &lt;tr&gt;
              &lt;th&gt;&lt;/th&gt;
              &lt;th&gt;Name&lt;/th&gt;
              &lt;th&gt;Description&lt;/th&gt;
          &lt;/tr&gt;
      &lt;/thead&gt;
      &lt;tbody&gt;
          &lt;tr ng-repeat="object in objects"&gt;
              &lt;td&gt;
                  &lt;delete&gt;&lt;/delete&gt;
                  &lt;edit url="/{{params.accountId}}/category/{{object.resource_id}}"&gt;&lt;/edit&gt;
              &lt;/td&gt;
              &lt;td&gt;{{object.resource_name}}&lt;/td&gt;
              &lt;td&gt;{{object.description}}&lt;/td&gt;
          &lt;/tr&gt;
          &lt;/tr&gt;
          &lt;/tr&gt;
      &lt;/tbody&gt;
  &lt;/table&gt;
  &lt;/div&gt;
  
  &lt;ng-show="template" ng-include src="template"&gt;&lt;/ng-show&gt;
  </code></pre>
  
  <p>The Detail partial:</p>
  
  <pre><code>&lt;div class="span4"&gt;
      &lt;h3&gt;Category: {{category.resource_name}}&lt;/h3&gt;
      &lt;form name="detail_form" class="form-horizontal"&gt;
          &lt;div class="control-group"&gt;
              &lt;label class="control-label"&gt;&lt;strong&gt;Name&lt;/strong&gt;&lt;/label&gt;
              &lt;div class="controls"&gt;
                  &lt;input required ng-model="object.resource_name" placeholder="Name" type="text" class="input-small"&gt;
              &lt;/div&gt;
          &lt;/div&gt;
          &lt;div class="control-group"&gt;
              &lt;label class="control-label"&gt;&lt;strong&gt;Description&lt;/strong&gt;&lt;/label&gt;
              &lt;div class="controls"&gt;
                  &lt;textarea ng-model="object.description" placeholder="Description" type="textarea" rows=5&gt;&lt;/textarea&gt;
              &lt;/div&gt;
          &lt;/div&gt;
          &lt;div class="control-group"&gt;
              &lt;save scope-name="$parent.$parent"&gt;&lt;/save&gt;
              &lt;cancel scope-name="$parent.$parent"&gt;&lt;/cancel&gt;
          &lt;/div&gt;
      &lt;/form&gt;
  &lt;pre&gt;form = {{object | json}}&lt;/pre&gt;
  &lt;/div&gt;
  </code></pre>
  
  <p>This seems excessive to use $parent.$parent if there's a better way to solve this please help me out!</p>