【问题标题】:What's the recommended way to extend AngularJS controllers?扩展 AngularJS 控制器的推荐方法是什么?
【发布时间】:2013-05-08 13:06:43
【问题描述】:

我有三个非常相似的控制器。我想要一个控制器,这三个扩展并共享其功能。

【问题讨论】:

    标签: javascript angularjs dry


    【解决方案1】:

    你可以直接使用 $controller('ParentController', {$scope:$scope}) 示例

    module.controller('Parent', ['$scope', function ($scope) {
        //code
    }])
    
    module.controller('CtrlImplAdvanced', ['$scope', '$controller', function ($scope, $controller) {
        //extend parent controller
        $controller('CtrlImpl', {$scope: $scope});
    }]);
    

    【讨论】:

      【解决方案2】:

      我认为扩展控制器是一种不好的做法。而是将您的共享逻辑放入服务中。 javascript 中的扩展对象往往变得相当复杂。如果你想使用继承,我会推荐 typescript。不过,在我看来,瘦控制器是更好的选择。

      【讨论】:

        【解决方案3】:

        我为此编写了一个函数:

        function extendController(baseController, extension) {
            return [
                '$scope', '$injector',
                function($scope, $injector) {
                    $injector.invoke(baseController, this, { $scope: $scope });
                    $injector.invoke(extension, this, { $scope: $scope });
                }
            ]
        }
        

        你可以这样使用它:

        function() {
            var BaseController = [
                '$scope', '$http', // etc.
                function($scope, $http, // etc.
                    $scope.myFunction = function() {
                        //
                    }
        
                    // etc.
                }
            ];
        
            app.controller('myController',
                extendController(BaseController,
                    ['$scope', '$filter', // etc.
                    function($scope, $filter /* etc. */)
                        $scope.myOtherFunction = function() {
                            //
                        }
        
                        // etc.
                    }]
                )
            );
        }();
        

        优点:

        1. 您不必注册基本控制器。
        2. 任何控制器都不需要知道 $controller 或 $injector 服务。
        3. 它适用于 Angular 的数组注入语法 - 如果您的 javascript 要被缩小,这是必不可少的。
        4. 您可以轻松地将额外的可注入服务添加到基本控制器,还不必记住将它们添加到所有子控制器并从中传递它们。

        缺点:

        1. 必须将基本控制器定义为变量,这可能会污染全局范围。在我的使用示例中,我通过将所有内容包装在一个匿名的自执行函数中来避免这种情况,但这确实意味着所有子控制器都必须在同一个文件中声明。
        2. 此模式适用于直接从您的 html 实例化的控制器,但不适用于您通过 $controller() 服务从代码创建的控制器,因为它对注入器的依赖会阻止您直接注入调用代码中的额外非服务参数。

        【讨论】:

          【解决方案4】:

          对于继承,您可以使用标准的 JavaScript 继承模式。 这是一个使用$injector的演示

          function Parent($scope) {
            $scope.name = 'Human';
            $scope.clickParent = function() {
              $scope.name = 'Clicked from base controller';
            }    
          }
          
          function Child($scope, $injector) {
            $injector.invoke(Parent, this, {$scope: $scope});
            $scope.name = 'Human Child';
            $scope.clickChild = function(){
              $scope.clickParent();
            }       
          }
          
          Child.prototype = Object.create(Parent.prototype);
          

          如果您使用controllerAs 语法(我强烈推荐),则使用经典继承模式会更容易:

          function BaseCtrl() {
            this.name = 'foobar';
          }
          BaseCtrl.prototype.parentMethod = function () {
            //body
          };
          
          function ChildCtrl() {
            BaseCtrl.call(this);
            this.name = 'baz';
          }
          ChildCtrl.prototype = Object.create(BaseCtrl.prototype);
          ChildCtrl.prototype.childMethod = function () {
            this.parentMethod();
            //body
          };
          
          app.controller('BaseCtrl', BaseCtrl);
          app.controller('ChildCtrl', ChildCtrl);
          

          另一种方法是只创建“抽象”构造函数,它将作为您的基本控制器:

          function BaseController() {
            this.click = function () {
              //some actions here
            };
          }
          
          module.controller('ChildCtrl', ['$scope', function ($scope) {
            BaseController.call($scope);
            $scope.anotherClick = function () {
              //other actions
            };
          }]);
          

          Blog post on this topic

          【讨论】:

            【解决方案5】:

            也许不扩展控制器,但可以扩展控制器或使单个控制器成为多个控制器的混合。

            module.controller('CtrlImplAdvanced', ['$scope', '$controller', function ($scope, $controller) {
                // Initialize the super class and extend it.
                angular.extend(this, $controller('CtrlImpl', {$scope: $scope}));
                … Additional extensions to create a mixin.
            }]);
            

            创建父控制器时,其中包含的逻辑也会被执行。 有关更多信息,请参阅 $controller(),但仅需要传递 $scope 值。所有其他值将正常注入。

            @mwarren,Angular 依赖注入会自动神奇地解决您的问题。您只需要注入 $scope,但如果需要,您可以覆盖其他注入的值。 举个例子:

            (function(angular) {
            
            	var module = angular.module('stackoverflow.example',[]);
            
            	module.controller('simpleController', function($scope, $document) {
            		this.getOrigin = function() {
            			return $document[0].location.origin;
            		};
            	});
            
            	module.controller('complexController', function($scope, $controller) {
            		angular.extend(this, $controller('simpleController', {$scope: $scope}));
            	});
            
            })(angular);
            <script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.15/angular.js"></script>
            
            <div ng-app="stackoverflow.example">
                <div ng-controller="complexController as C">
                    <span><b>Origin from Controller:</b> {{C.getOrigin()}}</span>
                </div>
            </div>

            虽然$document在被'complexController'创建时并没有传入'simpleController',但是$document是为我们注入的。

            【讨论】:

            • 迄今为止最快、最干净、最简单的解决方案!谢谢!
            • 完美的解决方案!
            • 我觉得你不需要那个$.extend(),你可以直接打电话给$controller('CtrlImpl', {$scope: $scope});
            • @tomraithel 不使用angular.extend(或$.extend)实际上意味着仅扩展$scope,但如果您的基本控制器还定义了一些属性(例如this.myVar=5),则您只能访问使用angular.extend时扩展控制器中的this.myVar
            • 这太棒了!只要确保记住扩展所有需要的函数,即我有类似的东西:handleSubmitClick,它会调用handleLogin,而loginSuccessloginFail。因此,在我的扩展控制器中,我必须重载 handleSubmitClickhandleLoginloginSucess 才能使用正确的 loginSuccess 函数。
            【解决方案6】:

            您可以将 Angular “as”语法与纯 JavaScript 继承结合使用

            在此处查看更多详细信息 http://blogs.microsoft.co.il/oric/2015/01/01/base-controller-angularjs/

            【讨论】:

              【解决方案7】:

              您可以在任何控制器中创建服务并通过注入它来继承其行为。

              app.service("reusableCode", function() {
              
                  var reusableCode = {};
              
                  reusableCode.commonMethod = function() {
                      alert('Hello, World!');
                  };
              
                  return reusableCode;
              });
              

              然后在您想要从上述 reusableCode 服务扩展的控制器中:

              app.controller('MainCtrl', function($scope, reusableCode) {
              
                  angular.extend($scope, reusableCode);
              
                  // now you can access all the properties of reusableCode in this $scope
                  $scope.commonMethod()
              
              });
              

              演示插件:http://plnkr.co/edit/EQtj6I0X08xprE8D0n5b?p=preview

              【讨论】:

                【解决方案8】:

                article 中获取的另一个很好的解决方案:

                // base controller containing common functions for add/edit controllers
                module.controller('Diary.BaseAddEditController', function ($scope, SomeService) {
                    $scope.diaryEntry = {};
                
                    $scope.saveDiaryEntry = function () {
                        SomeService.SaveDiaryEntry($scope.diaryEntry);
                    };
                
                    // add any other shared functionality here.
                }])
                
                module.controller('Diary.AddDiaryController', function ($scope, $controller) {
                    // instantiate base controller
                    $controller('Diary.BaseAddEditController', { $scope: $scope });
                }])
                
                module.controller('Diary.EditDiaryController', function ($scope, $routeParams, DiaryService, $controller) {
                    // instantiate base controller
                    $controller('Diary.BaseAddEditController', { $scope: $scope });
                
                    DiaryService.GetDiaryEntry($routeParams.id).success(function (data) {
                        $scope.diaryEntry = data;
                    });
                }]);
                

                【讨论】:

                • 这对我来说非常有效。它的优点是从一个控制器开始,创建另一个非常相似的控制器,然后想要使代码更干燥的情况下轻松重构。不用改代码,拉出来就搞定了。
                【解决方案9】:

                您可以使用 servicesfactoriesproviders 进行扩展。它们是相同的,但具有不同程度的灵活性。

                这里是使用工厂的示例:http://jsfiddle.net/aaaflyvw/6KVtj/2/

                angular.module('myApp',[])
                
                .factory('myFactory', function() {
                    var myFactory = {
                        save: function () {
                            // saving ...
                        },
                        store: function () {
                            // storing ...
                        }
                    };
                    return myFactory;
                })
                
                .controller('myController', function($scope, myFactory) {
                    $scope.myFactory = myFactory;
                    myFactory.save(); // here you can use the save function
                });
                

                在这里你也可以使用 store 功能:

                <div ng-controller="myController">
                    <input ng-blur="myFactory.store()" />
                </div>
                

                【讨论】:

                  【解决方案10】:

                  好吧,我不确定您想要实现什么,但通常服务是要走的路。 您还可以使用 Angular 的 Scope 继承特性在控制器之间共享代码:

                  <body ng-controller="ParentCtrl">
                   <div ng-controller="FirstChildCtrl"></div>
                   <div ng-controller="SecondChildCtrl"></div>
                  </body>
                  
                  function ParentCtrl($scope) {
                   $scope.fx = function() {
                     alert("Hello World");
                   });
                  }
                  
                  function FirstChildCtrl($scope) {
                    // $scope.fx() is available here
                  }
                  
                  function SecondChildCtrl($scope) {
                    // $scope.fx() is available here
                  }
                  

                  【讨论】:

                  • 在执行类似操作的控制器之间共享相同的变量和函数(一个用于编辑,另一个用于创建等...)。这绝对是解决方案之一...
                  • $scope 继承是迄今为止最好的方法,比公认的答案要好得多。
                  • 最后这似乎是最有角度的方法。我在三个不同的页面上有三个非常简短的 parentController,它们只是设置了一个 $scope 值,childController 可以获取该值。原本想扩展的 childController 包含了所有的控制器逻辑。
                  • $scope.$parent.fx( ) 不会是一种更清洁的方法,因为它实际上是在哪里定义的?
                  【解决方案11】:

                  您可以尝试这样的事情(尚未测试):

                  function baseController(callback){
                      return function($scope){
                          $scope.baseMethod = function(){
                              console.log('base method');
                          }
                          callback.apply(this, arguments);
                      }
                  }
                  
                  app.controller('childController', baseController(function(){
                  
                  }));
                  

                  【讨论】:

                  • 是的。无需扩展,只需使用上下文调用
                  【解决方案12】:

                  您不扩展控制器。如果它们执行相同的基本功能,则需要将这些功能转移到服务中。该服务可以注入到您的控制器中。

                  【讨论】:

                  • 谢谢,但我有 4 个已经使用服务的功能(保存、删除等),并且在所有三个控制器中都存在。如果扩展不是一种选择,是否有可能使用“混合”?
                  • @vladexogija 我同意 Bart 的观点。我认为服务是混合的。您应该尝试将尽可能多的逻辑移出控制器(进入服务)。因此,如果您有 3 个需要执行类似任务的控制器,那么服务似乎是正确的方法。在 Angular 中扩展控制器并不自然。
                  • @vladexogija 这是我的意思的一个例子:jsfiddle.net/ERGU3 这是非常基本的,但你会明白的。
                  • 如果没有任何论据和进一步的解释,答案是毫无用处的。我也认为你有点错过了 OP 的观点。已经有共享服务了。您唯一要做的就是直接公开该服务。我不知道这是否是个好主意。如果需要访问范围,您的方法也会失败。但是按照你的推理,我会明确地将范围作为范围的属性公开给视图,以便它可以作为参数传递。
                  • 一个典型的例子是您的网站上有两个表单驱动的报表,每个报表都依赖于大量相同的数据,并且每个报表都使用大量共享服务。从理论上讲,您可以尝试将所有单独的服务放入具有数十个 AJAX 调用的大型服务中,然后使用诸如“getEverythingINeedForReport1”和“getEverythingINeedForReport2”之类的公共方法,并将其全部设置为一个庞大的范围对象,但随后您将真正将本质上是控制器逻辑的东西放入您的服务中。在某些情况下,扩展控制器绝对有用例。
                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 2014-04-24
                  • 2011-09-04
                  • 1970-01-01
                  • 1970-01-01
                  • 2018-08-18
                  • 2016-10-30
                  相关资源
                  最近更新 更多