在 AngularJS 中结合 CREATE 和 EDIT 控制器是一种好习惯吗?

Posted

技术标签:

【中文标题】在 AngularJS 中结合 CREATE 和 EDIT 控制器是一种好习惯吗?【英文标题】:Is it good practice to combine CREATE and EDIT controllers in AngularJS? 【发布时间】:2014-09-22 15:33:02 【问题描述】:

CREATE 和 EDIT 控制器之间有很多重复的代码。 这些控制器可以合二为一,以最大限度地减少重复代码。

问题:我需要区分表单提交时使用哪种方法 - 例如 create() 或 edit()。

解决方案:例如,我可以添加$scope.mode,如果用户单击“编辑”按钮,则设置$scope.mode='edit';如果用户单击“添加”按钮,则设置$scope.mode='add'

我可以使用服务来最大限度地减少重复代码,但仍然会有重复的代码。例如,在两个控制器中,我都有 cancel() 方法,它可以清除表单并将其隐藏。我可以将 clearForm() 和 hideForm() 存储在服务中,但是此代码将在两个控制器中重复:

$scope.cancel = function() 
    Service.clearForm();
    Service.hideForm();
;

问题:

有没有什么好的做法可以减少重复代码?

【问题讨论】:

除了提交用户输入时会发生什么之外,每个功能之间是否有任何区别?根据我的经验,这将是非常不寻常的。 【参考方案1】:

是的。使用 1 个控制器。

这就是为什么使用 1 个控制器的原因

控制器的工作是支持视图。您的创建视图和编辑视图完全相同 - 只是一个具有预先填充的数据(编辑)而另一个没有(创建)。 此外,此视图的“目的”是让用户在表单中更改或输入新值。您唯一的区别应该是 reset() 之类的。但即使在那里,您也可以从一个空的模型对象开始,例如$scope.entity = 在 CREATE 的情况下,您将从 $scope.entity = $http.get() 开始。

2 个控制器的重复问题

使用 2 个不同的控制器和服务,您至少会产生以下重复:

$scope.cancel = function() 
    Service.cancel();
;

$scope.validate = function() 
   ValidtionSvc.validate();

.
.
.//other stuff similar

但问题是为什么即使像你所说的这种重复。

(此处为更新,因为上面是第一个问题的答案)

如何重复使用 1 个控制器?

有没有什么好的做法可以减少重复代码?

问题重新定义:是否有消除 CREATE 和 EDIT 表单中重复代码的良好做法?

据我所知,不存在正式的“最佳实践”来避免在这种特定情况下重复代码。但是我建议不要使用 mode=edit/create。在这种情况下,控制器的原因应该几乎没有区别,因为他们的工作纯粹是在用户交互时获取/更新模型。

以下是您在这种情况下会遇到的差异,以及如何使用 mode=create/edit 避免 if/then/else:

1) 使用现有值填充表单与创建的空表单。

要获取现有实体,您需要一些键/查询数据。如果存在这样的关键数据,您可以这样做

var masterEntity = ;
if(keyData) 
   masterEntity = MyEntityResourceFactory.getEntity(keyData);
 
$scope.entity = masterEntity;//for Create this would be 

2) reset() 形式 应该很简单

   $scope.reset = function() 
      $scope.entity = masterEntity;
   

3) 更新/创建

$http.post()//should not be different in today's world since we are treating PUT as POST

4) 验证 - 这是一个完美的重用 - 应该没有区别。

5) 初始/默认值

您可以使用 masterEntity = Defaults 代替 。

【讨论】:

非常感谢您的回答。使用 2 个控制器和服务时,我在应用程序中有完全相同的代码重复。 用第二部分“如何?”更新了我的答案【参考方案2】:

将 CREATE 和 EDIT 控制器组合在 AngularJS?

根据我的经验,是的,在 99.9% 的情况下这是一个好主意。我通常通过 $routeProvider 解析功能将 formType 变量注入我的控制器。所以我会有以下内容:

$routeProvider
    .when('/item/create', 
        templateUrl: '/app/item/itemForm.html',
        controller: 'itemFormController',
        resolve: 
            item: ['$route', 'itemRepository', function ($route, itemRepository) 
                return itemRepository.getNew();
            ],
            formType: function ()  return Enums.FormType.CREATE; 
        ,
    )
    .when('/item/edit/:itemId', 
        templateUrl: '/app/item/itemForm.html',
        controller: 'itemFormController',
        resolve: 
            item: ['$route', 'itemRepository', function ($route, itemRepository) 
                return itemRepository.get($route.current.params.itemId);
            ],
            formType: function ()  return Enums.FormType.EDIT; ,
        ,
    );

这样您就可以将实体和表单操作类型注入控制器。我也共享相同的模板,因此保存表单我可以依赖我的存储库/服务来确定要调用的 REST 端点,或者我可以根据注入的 formType 在控制器内部进行简单检查。

有没有什么好的做法可以减少重复代码?

我用来保持干燥的一些东西:

如果您在服务器 API 上保持通用约定,则可以使用基本工厂/存储库/类(无论您想调用什么)来进行数据访问。例如:

GET  -> /resource?listQueryString     // Return resource list
GET  -> /resource/id                // Return single resource
GET  -> /resource/id/resourceview // Return display representation of resource
PUT  -> /resource/id                // Update existing resource
POST -> /resource/                    // Create new resource
etc.

然后我们使用一个 AngularJs 工厂,它返回一个基础存储库类,我们称之为abstractRepository。然后,对于每个资源,我为该特定资源创建一个具体的存储库,该资源在原型上继承自 abstractRepository,因此我从 abstractRepository 继承所有共享/基本功能,并将任何资源特定功能定义到具体存储库。这样绝大多数数据访问代码都可以在 abstractRepository 中定义。这是一个使用 Restangular 的示例:

abstractRepository

app.factory('abstractRepository', [function () 

    function abstractRepository(restangular, route) 
        this.restangular = restangular;
        this.route = route;
    

    abstractRepository.prototype = 
        getList: function (params) 
            return this.restangular.all(this.route).getList(params);
        ,
        get: function (id) 
            return this.restangular.one(this.route, id).get();
        ,
        getView: function (id) 
            return this.restangular.one(this.route, id).one(this.route + 'view').get();
        ,
        update: function (updatedResource) 
            return updatedResource.put();
        ,
        create: function (newResource) 
            return this.restangular.all(this.route).post(newResource);
        
        // etc.
    ;

    abstractRepository.extend = function (repository) 
        repository.prototype = Object.create(abstractRepository.prototype);
        repository.prototype.constructor = repository;
    ;

    return abstractRepository;
]);

具体的仓库,我们以客户为例:

app.factory('customerRepository', ['Restangular', 'abstractRepository', function (restangular, abstractRepository) 

    function customerRepository() 
        abstractRepository.call(this, restangular, 'customers');
    

    abstractRepository.extend(customerRepository);
    return new customerRepository();
]);

如果您使用此基本存储库模式,您会发现大多数 CRUD 控制器也将共享许多公共代码,因此我通常创建一个基本 CRUD 控制器,我的控制器从该控制器继承。有些人不喜欢基本控制器的想法,但在我们的例子中,它也起到了作用。

【讨论】:

【参考方案3】:

第一个问题的答案可能取决于具体情况。

如果两个控制器共享大量操作,并且只需要更改一两个函数的行为 - 为什么不呢!也许不是最优雅的解决方案,但嘿,不管用什么。

如果许多或所有控制器操作的行为将取决于'$scope.mode'...我会说要小心。这似乎是一条危险的道路。

在最小化控制器之间的代码复制方面,Angular 服务一直对我很有帮助。如果有“减少重复代码的良好做法”,我会说它是服务。它们对您的应用来说是全局的,可以毫无问题地注入到多个控制器中。

希望对您有所帮助!

【讨论】:

非常感谢您的回答。我更新了问题并在那里添加了我对服务的看法。

以上是关于在 AngularJS 中结合 CREATE 和 EDIT 控制器是一种好习惯吗?的主要内容,如果未能解决你的问题,请参考以下文章

“未捕获的类型错误:在 angularJS/NodeJS 中使用条带支付方法 Stripe.charges.create() 时无法读取未定义的属性‘创建’”

将BootstrapJS和AngularJS结合使用以及为啥不用jQuery

如何结合 AngularJS 和 Thymeleaf 模板

结合 AngularJS 和 Twitter Bootstrap 的最佳方式

将BootstrapJS和AngularJS结合使用以及为啥不用jQuery

玩转spring boot——结合AngularJs和JDBC