AngularJSコントローラーを拡張するための推奨される方法は何ですか?


193

非常によく似た3つのコントローラーがあります。これらの3つを拡張して機能を共有するコントローラーが欲しい。

回答:


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 Dependency Injectionによって自動的に魔法のように処理されます。必要な場合は$ 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

完璧で素晴らしいソリューション!
Kamil Lach 14

8
私はあなたはそれを必要としないと思います$.extend()、あなたは単に電話することができます$controller('CtrlImpl', {$scope: $scope});
tomraithel

5
@tomraithelを使用しないangular.extend(または$.extend)は実際には$scope唯一の拡張を意味しますが、ベースコントローラーがいくつかのプロパティ(たとえばthis.myVar=5)も定義しているthis.myVar場合、使用時に拡張コントローラーでのみアクセスできますangular.extend
schellmax

1
これは素晴らしい!:念必要なすべての機能を拡張することを忘れないようにします、つまり、私のようなものだった handleSubmitClick呼んでhandleLogin順番に持っていたloginSuccessとします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
}

同様のことを行うコントローラー間で同じ変数と関数を共有します(1つは編集、もう1つは作成など)。これは間違いなく解決策の1つです...
vladexologija '14年

1
$ scopeの継承は、これを行うための最善の方法であり、受け入れられた答えよりもはるかに優れています。
snez、2014

結局これが最も角張った道のように思えました。childControllerが取得できる$ scope値を設定するだけの、3つの異なるページに3つの非常に短いparentControllersがあります。私が元々拡張について考えていたchildControllerには、すべてのコントローラーロジックが含まれています。
mwarren 2015年

$scope.$parent.fx( ) それが実際に定義されている場所なので、それを行うためのはるかにクリーンな方法ではないでしょうか?
Akash 2016

15

コントローラーは拡張しません。それらが同じ基本機能を実行する場合、それらの機能をサービスに移動する必要があります。そのサービスをコントローラーに注入できます。


3
おかげで、私はすでにサービスを使用している4つの機能(保存、削除など)を持っていて、3つすべてのコントローラーに同じものが存在しています。拡張がオプションではない場合、「ミックスイン」の可能性はありますか?
vladexologija 2013年

4
@vladexologija私はここでバートに同意します。サービスはミックスインだと思います。できるだけ多くのロジックをコントローラーから(サービスに)移動する必要があります。したがって、同様のタスクを実行する必要がある3つのコントローラーがある場合、サービスは適切なアプローチのようです。コントローラを拡張することは、Angularでは自然に感じられません。
ganaraj 2013年

6
@vladexologija これが私の意味の例です:jsfiddle.net/ERGU3これは非常に基本的ですが、あなたはアイデアを得るでしょう。
バート

3
答えは何の議論も詳細な説明もなしにはかなり役に立たない。また、OPのポイントを多少見逃していると思います。すでに共有サービスがあります。あなたがする唯一のことは、そのサービスを直接公開することです。それが良い考えかどうかはわかりません。スコープへのアクセスが必要な場合も、アプローチは失敗します。しかし、あなたの推論に従って、スコープをスコープのプロパティとしてビューに明示的に公開し、引数として渡すことができるようにします。
より良いオリーブ

6
典型的な例は、サイトに2つのフォーム駆動型レポートがあり、それぞれが同じデータに多く依存し、それぞれが多くの共有サービスを使用する場合です。理論的には、数十のAJAX呼び出しを使用して、個別のサービスをすべて1つの大きなサービスにまとめ、「getEverythingINeedForReport1」や「getEverythingINeedForReport2」などのパブリックメソッドを使用して、すべてを1つの巨大なスコープオブジェクトに設定できますが、基本的にコントローラーロジックであるものを実際にサービスに組み込みます。コントローラーの拡張は、状況によっては絶対に使用例があります。
tobylaroni、2016年

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
これは私にとって非常にうまくいきました。これには、1つのコントローラーから始め、非常に類似したコントローラーを作成し、コードをDRYerにしたいという状況から簡単にリファクタリングできるという利点があります。コードを変更する必要はありません。コードを抜いて完了です。
Eli Albert

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


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、2013年

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から直接インスタンス化されたコントローラーに適していますが、インジェクターに依存しているため、追加の非-呼び出しコードのサービスパラメータ。

1

コントローラを拡張することは悪い習慣だと思います。むしろ、共有ロジックをサービスに入れます。JavaScriptの拡張オブジェクトはかなり複雑になる傾向があります。継承を使用したい場合は、typescriptをお勧めします。それでも、私の見解では、薄いコントローラーの方が適しています。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.