扩展AngularJS控制器的推荐方法是什么?


193

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

Answers:


302

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

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。


1
迄今为止,最快,最清洁,最简单的解决方案!谢谢!
MK 2014年

完美的解决方案!
卡米尔·拉赫

8
我认为您不需要$.extend(),您可以致电$controller('CtrlImpl', {$scope: $scope});
tomraithel 2014年

5
@tomraithel不使用angular.extend(或$.extend)实际上意味着$scope仅扩展,但是如果您的基本控制器也定义了某些属性(例如this.myVar=5),则您只能this.myVar在使用angular.extend
schellmax

1
这很棒!只需确保记得扩展所有需要的功能即可,即我有类似的东西: handleSubmitClick它将调用handleLogin,而后者又具有loginSuccessand loginFail。所以,在我的扩展控制器然后我不得不超载handleSubmitClickhandleLogin以及loginSucess正确的loginSuccess功能使用。
贾斯汀·克鲁斯

52

对于继承,您可以使用标准的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
  };
}]);

关于此主题的博客文章


16

好吧,我不确定您要实现什么目标,但是通常来说,服务是必经之路。您还可以使用Angular的作用域继承特性在控制器之间共享代码:

<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
}

在执行相似功能的控制器之间共享相同的变量和功能(一个用于编辑,另一个用于创建等)。这绝对是解决方案之一...
vladexologija

1
到目前为止,$ scope继承是做到这一点的最佳方法,比接受的答案要好得多。
snez 2014年

最后,这似乎是最有角度的方法。我在三个不同的页面上有三个非常简短的parentControllers,它们只是设置了一个$ scope值,childController可以选择该值。我最初考虑扩展的childController包含所有控制器逻辑。
mwarren 2015年

这样做不是$scope.$parent.fx( ) 更干净的方法,因为那是实际定义的地方?
阿卡什(Akash)2016年

15

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


3
谢谢,但是我有4个已经使用服务的功能(保存,删除等),并且在所有三个控制器中都存在。如果不能扩展,是否有可能使用“ mixin”?
vladexologija 2013年

4
@vladexologija我在这里同意Bart。我认为服务是mixin的。您应该尝试将尽可能多的逻辑移出控制器(移入服务)。因此,如果您有3个需要执行类似任务的控制器,那么采用服务似乎是正确的选择。扩展控制器在Angular中并不自然。
ganaraj

6
@vladexologija这是我的意思的示例:jsfiddle.net/ERGU3这是非常基本的,但是您会明白的。
巴特

3
没有任何论点和进一步的解释,答案是毫无用处的。我也认为您有点怀念OP的观点。已经有共享服务。您要做的唯一一件事就是直接公开该服务。我不知道这是个好主意。如果需要访问范围,则您的方法也会失败。但是按照您的推理,我将把作用域作为作用域的属性显式公开给视图,以便可以将其作为参数传递。
更好的Oliver,2015年

6
一个典型的例子是您的站点上有两个表单驱动的报表,每个报表都依赖于许多相同的数据,并且每个报表都使用大量共享服务。从理论上讲,您可以尝试通过数十个AJAX调用将所有单独的服务放入一个大服务中,然后使用“ getEverythingINeedForReport1”和“ getEverythingINeedForReport2”之类的公共方法,并将其全部设置为一个庞大的作用域对象,真正将本质上是控制器逻辑的东西放入您的服务中。在某些情况下,扩展控制器绝对具有用例。
tobylaroni

10

本文采取的另一个好的解决方案:

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

1
这对我来说非常有效。它的优点是,在您从一个控制器开始,创建另一个非常相似的控制器,然后想编写代码DRYer的情况下,可以轻松地进行重构。您无需更改代码,只需将其拉出并完成即可。
伊莱·艾伯特

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

});

DEMO PLUNKER:http ://plnkr.co/edit/EQtj6I0X08xprE8D0n5b?p=preview


5

您可以尝试以下操作(未经测试):

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

app.controller('childController', baseController(function(){

}));

1
对。无需扩展,只需调用上下文即可
TaylorMac

4

您可以扩展与服务工厂提供者。它们相同,但灵活性不同。

这里是使用工厂的示例: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
});

在这里您还可以使用存储功能:

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

4

您可以直接使用$ 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});
}]);


1

我写了一个函数来做到这一点:

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()服务从代码创建的控制器,因为它对注入器的依赖性阻止了您直接注入多余的,非您的调用代码中的-service参数。

1

我认为扩展控制器是不好的做法。而是将共享逻辑放入服务中。JavaScript中的扩展对象往往变得相当复杂。如果要使用继承,建议使用打字稿。在我看来,精简控制器是更好的选择。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.