保持CRUD DRY与Restangular:指令,继承控制器或包装服务?

时间:2013-11-04 01:41:03

标签: javascript angularjs crud restangular

我对AngularJS比较陌生,我一直在尝试各种方法来使用Restangular保持我的AngularJS CRUD方法DRY,所以我很感激社区的任何建议。

我有很多API端点,每个我想显示一个项目列表,允许选择每个项目(以显示该项目的详细信息)并删除,以及添加项目到列表。

到目前为止,我尝试过的方法都是非常非DRY,没有正确地挂钩到$ scope,或者有点hacky ......

环绕Restangular

将Restangular包装到服务中,并使用addElementTransformer在返回的集合上设置其他方法似乎是一种不错的方法; selectadd方法是干净的,可以从模板轻松实现,并在解析承诺时自动更新列表。但是,因为delete方法实际上是从元素范围(集合的子集)中调用的,所以在删除后更新列表需要在服务中注入非常可疑的$ scope - 我能想到的最好的是如下:

(注意:我通过仅说明单个API端点/控制器/模板使示例更容易阅读)

应用/ JS / services.js

function ListTransformer(collection) {
    collection.selected = {}
    collection.assignedTo = {}
    collection.assignedName = ""

    collection.add = function(item) {
        collection.post(item)
        .then(function(response){
            collection.push(response);
        })
    };
    collection.delete = function(item, scope) {
        item.remove()
        .then(function(response){
            console.log(item)
            console.log(collection)
            collection.assignedTo[collection.assignedName] = _.without(collection, item);
        })
    };
    collection.select = function(item) {
        this.selected = item;
    };
    collection.assignTo = function(scope, name) {
        scope[name] = collection;
        collection.assignedTo = scope
        collection.assignedName = name
    };
    return collection;
};

angular.module('myApp.services', ['restangular'])
.factory('Company', ['Restangular',
    function(Restangular){
        var restAngular = Restangular.withConfig(function(Configurer) {
                Configurer.addElementTransformer('companies', true, ListTransformer);
        });

        var _companyService = restAngular.all('companies');

        return {
            getList: function() {
                return _companyService.getList();
            }
        }
    }
])

应用/ JS / controllers.js

angular.module('myApp.controllers', [])
.controller('CompanyCtrl', ['$scope', 'Company', function($scope, Company) {

    Company.getList()
    .then(function(companies) {
        companies.assignTo($scope, 'companies'); // This seems nasty
    });
}]);

应用/分音/ companies.html

<ul>
    <li ng-repeat="company in companies">
      <a href="" ng-click="companies.select(company)">{{company.name}}</a> <a href="" ng-click="clients.delete(client)">DEL</a>
    </li>
</ul>
<hr>
{{companies.selected.name}}<br />
{{companies.selected.city}}<br />

扩展基本控制器

我尝试的另一种方法是在基本控制器中将模板连接到服务,我的其他控制器继承。但是我在这里仍然有$ scope问题,最终需要从模板中传递范围,这似乎不对:

(仅编辑为delete方法)

应用/ JS / services.js

angular.module('myApp.services', ['restangular'])
.factory('ListController', function() {
    return {
        delete: function(scope, item, list) {
            item.remove();
            scope.$parent[list] = _.without(scope.$parent[list], item); 
        }
    };
});

应用/ JS / controllers.js

.controller('CompanyCtrl', ['$scope', 'ListController', 'Restangular', function($scope, ListController, Restangular) {
    angular.extend($scope, ListController);

    $scope.companies = Restangular.all('companies').getList();
}])

应用/分音/ companies.html

<ul>
    <li ng-repeat="company in companies">
      <a href="" ng-click="companies.select(company)">{{company.name}}</a> <a href="" ng-click="delete(this, companies, company)">DEL</a>
    </li>
</ul>

指令

我对指令有点新意,但经过大量研究后,它似乎是最接近 AngularJS 的方法,所以我潜入了。我已经进行了很多实验但是请原谅任何明显的错误;基本上问题仍然是,虽然我可以从列表中删除项目,除非我将所有变量传递给指令我无法访问列表最初分配给$ scope的属性,因此它不会在视图中更新。

应用/ JS / directives.js

angular.module('myApp.directives', [])
.directive('delete', function() {
    return {
        restrict: 'E',
        replace: true,
        template: '<button>Delete</button>',
        link: function (scope, element, attrs) {
            element.bind('click', function() {
                var item = scope[attrs.item];
                item.remove();
                scope.$parent[attrs.from] = _.without(scope.$parent[attrs.from], item)
            });
        },

    };
});

应用/分音/ companies.html

<ul>
    <li ng-repeat="company in companies">
      <a href="" ng-click="companies.select(company)">{{company.name}}</a> <delete item="company" from="companies">
    </li>
</ul>

基本上,我可以让一切工作,但似乎我要么做了很多重复的代码(这是完全错误的),或者发送我的范围以及我的所有调用(这似乎也是非常错误的),或者通过传入范围来初始化我的服务(不是我的控制器的最新解决方案,但似乎是实现这一目标的最可维护和最脆弱的方法。

当然,我可以删除功能,或将其移动到详细视图或复选框/批量操作功能,但我全神贯注于问题,并有兴趣找到最佳解决方案:)

如果我遗漏了一些明显的东西,请道歉!

1 个答案:

答案 0 :(得分:3)

从指令开始我只是记下笔记:

最大的学习曲线是范围继承。一般的经验法则是,如果不向指令返回的对象添加scope属性,则该指令会继承它的父作用域。

因此,您显示删除按钮的指令与CompanyCtrl具有相同的范围,因为您尚未在指令内部定义范围对象。

这意味着您可以在任一位置编写删除功能并在删除按钮上使用它。远离使用element.bind('click')创建自己的点击处理程序。主要原因是当你操作其中的范围项时,你通常必须调用范围。$ apply()让角度知道范围从外部事件改变。使用ng-click因为它直接绑定到范围。对于你的删除功能:

减少属性。现在只是一个标签,它有一个自定义模板和一个角度范围绑定点击处理程序

<!-- company object comes from ng-repeat so can pass it right into a click handler as param-->
<delete ng-click="deleteCompany(company)">

可以在指令或控制器中编写此函数......因为它们共享相同的范围。

.controller('CompanyCtrl', function($scope){
    $scope.deleteCompany=function(company){
        /* I've never used restangular...using native methods*/
        /* call my AJAX service and on resolve*/
        var idx= $scope.companies.indexOf(comapny);
        /* remove from array*/
        $scope.companies.splice(idx,1); 
    }
})

在指令中将是相同的功能

link:function(scope,elem,attrs){
      /* call my AJAX service and on resolve*/
    var idx= scope.companies.indexOf(comapny);
    /* remove from array*/
    scope.companies.splice(idx,1); 
 }

我看到你试图通过将一个控制器注入另一个控制器来在控制器之间共享数据。第一线思路是让他们共享服务,类似于需要时的指令,例如深度嵌套指令。

将数据存储在服务中,在需要它的控制器/指令中注入服务,只要所有数据都存储为对象而不是基元(字符串或布尔变量),对象可以在应用程序的任何位置引用并共享原型继承并从任何地方进行修改,无论范围嵌套和角度对象监视都会在引用该对象的任何地方更改DOM。

这是我之前玩过的一个小演示,对于想要从表单提交视图移动到另一个控制器的另一个视图并将数据传递给控制器​​的人。它基本上采用表单数据,将其传递给共享服务,进行模拟AJAX调用,在服务中存储表单数据然后更改路径初始化新模板和控制器。由于数据存储在服务中,所以当第二个视图控制器需要它时它就准备就绪。可能会帮助您想象我正在谈论的内容......也许不是。还显示了自动角度形式验证如何启动

Switch views/controllers passing data through service demo