วิธีที่แนะนำในการขยายคอนโทรลเลอร์ AngularJS คืออะไร


193

ฉันมีสามคอนโทรลเลอร์ที่ค่อนข้างคล้ายกัน ฉันต้องการมีคอนโทรลเลอร์ซึ่งทั้งสามตัวนี้ขยายและแบ่งปันฟังก์ชั่นของมัน

คำตอบ:


302

บางทีคุณอาจไม่ขยายคอนโทรลเลอร์ แต่ก็เป็นไปได้ที่จะขยายคอนโทรลเลอร์หรือทำให้คอนโทรลเลอร์เดี่ยวเป็น mixin ของคอนโทรลเลอร์หลายตัว

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ความกังวลของคุณได้รับการดูแลโดยอัตโนมัติอย่างน่าอัศจรรย์โดยการฉีดพึ่งพาเชิงมุม สิ่งที่คุณต้องใช้คือการฉีดขอบเขต $ ถึงแม้ว่าคุณสามารถแทนที่ค่าที่ฉีดอื่น ๆ ได้หากต้องการ นำตัวอย่างต่อไปนี้:

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

แม้ว่าเอกสาร $ จะไม่ถูกส่งผ่านไปยัง 'simpleController' เมื่อมันถูกสร้างขึ้นโดย 'documentController' $ document จะถูกฉีดให้เรา


1
เท่าที่ผ่านมาวิธีที่เร็วที่สุดสะอาดและง่ายที่สุดสำหรับเรื่องนี้! ขอบคุณ!
MK

สมบูรณ์แบบทางออกที่ยอดเยี่ยม!
Kamil Lach

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ดังนั้นในการควบคุมการขยายของฉันฉันแล้วก็ต้องเกินhandleSubmitClick, handleLoginและloginSucessสำหรับการที่ถูกต้องloginSuccessฟังก์ชั่นที่จะใช้
Justin Kruse

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
$ ขอบเขตการสืบทอดเป็นวิธีที่ดีที่สุดที่จะทำได้ดีกว่าคำตอบที่ยอมรับ
snez

ในท้ายที่สุดสิ่งนี้ดูเหมือนจะเป็นวิธีที่เป็นไปได้มากที่สุด ฉันมี parentControllers สั้น ๆ สามหน้าในสามหน้าที่แตกต่างกันซึ่งเพิ่งตั้งค่า $ scope ที่ childController สามารถรับได้ childController ซึ่งตอนแรกฉันคิดว่าเกี่ยวกับการขยายประกอบด้วยตรรกะควบคุมทั้งหมด
mwarren

จะไม่$scope.$parent.fx( ) เป็นวิธีที่สะอาดกว่าที่จะทำเพราะนั่นคือสิ่งที่มันถูกกำหนดจริง?
Akash

15

คุณไม่ขยายคอนโทรลเลอร์ หากพวกเขาทำหน้าที่ขั้นพื้นฐานเดียวกันฟังก์ชั่นเหล่านั้นจะต้องถูกย้ายไปยังบริการ บริการดังกล่าวสามารถถูกฉีดเข้าไปในตัวควบคุมของคุณ


3
ขอบคุณ แต่ฉันมี 4 ฟังก์ชั่นที่ใช้บริการอยู่แล้ว (บันทึกลบ ฯลฯ ) และมีอยู่ในตัวควบคุมทั้งสาม หากการขยายไม่ใช่ตัวเลือกจะมีความเป็นไปได้สำหรับ 'มิกซ์' หรือไม่?
vladexologija

4
@vladexologija ฉันเห็นด้วยกับบาร์ตที่นี่ ฉันคิดว่าการบริการเป็นของมิกซ์ คุณควรพยายามย้ายลอจิกออกจากคอนโทรลเลอร์มากที่สุดเท่าที่จะทำได้ (ไปยังเซอร์วิส) ดังนั้นหากคุณมีตัวควบคุม 3 ตัวที่จำเป็นต้องทำงานที่คล้ายกันบริการก็น่าจะเป็นแนวทางที่ถูกต้อง การขยายตัวควบคุมไม่รู้สึกเป็นธรรมชาติในเชิงมุม
ganaraj

6
@vladexologija ต่อไปนี้เป็นตัวอย่างของสิ่งที่ฉันหมายถึง: jsfiddle.net/ERGU3มันธรรมดามาก แต่คุณจะได้รับความคิด
บาร์ต

3
คำตอบนั้นไร้ประโยชน์โดยไม่มีข้อโต้แย้งและคำอธิบายเพิ่มเติม ฉันยังคิดว่าคุณค่อนข้างพลาดประเด็นของ OP มีบริการแชร์แล้ว สิ่งเดียวที่คุณทำคือเปิดเผยบริการนั้นโดยตรง ฉันไม่รู้ว่าเป็นความคิดที่ดีหรือไม่ แนวทางของคุณก็ล้มเหลวเช่นกันหากต้องการการเข้าถึงขอบเขต แต่จากการใช้เหตุผลของคุณฉันจะเปิดเผยขอบเขตเป็นคุณสมบัติของขอบเขตไปยังมุมมองอย่างชัดเจนเพื่อให้สามารถส่งผ่านเป็นอาร์กิวเมนต์ได้
ที่ดีกว่า

6
ตัวอย่างแบบคลาสสิกคือเมื่อคุณมีรายงานที่ขับเคลื่อนด้วยฟอร์มสองรายการในเว็บไซต์ของคุณซึ่งแต่ละรายงานนั้นขึ้นอยู่กับข้อมูลเดียวกันจำนวนมากและแต่ละรายการนั้นใช้บริการที่ใช้ร่วมกันจำนวนมาก ในทางทฤษฎีคุณสามารถลองและแยกบริการทั้งหมดของคุณให้เป็นหนึ่งบริการใหญ่ด้วยการโทร AJAX หลายสิบครั้งและมีวิธีการสาธารณะเช่น วางสิ่งที่เป็นหลักตรรกะควบคุมในบริการของคุณจริงๆ การขยายตัวควบคุมมีกรณีการใช้งานอย่างแน่นอนในบางสถานการณ์
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 คุณไม่จำเป็นต้องเปลี่ยนรหัสเพียงแค่ดึงมันออกมาและทำได้
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()

});

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 services
  3. มันทำงานได้ดีกับไวยากรณ์การฉีดอาเรย์ของแองกูลาร์ - ซึ่งเป็นสิ่งจำเป็นหากจาวาสคริปต์ของคุณกำลังจะลดขนาดลง
  4. คุณสามารถเพิ่มบริการพิเศษที่สามารถฉีดลงในคอนโทรลเลอร์พื้นฐานได้โดยไม่ต้องจำที่จะเพิ่มและส่งผ่านตัวควบคุมลูกทั้งหมดของคุณ

จุดด้อย:

  1. จะต้องกำหนดตัวควบคุมฐานเป็นตัวแปรซึ่งมีความเสี่ยงที่ก่อให้เกิดมลพิษทั่วโลก ฉันได้หลีกเลี่ยงสิ่งนี้ในตัวอย่างการใช้งานของฉันโดยห่อทุกอย่างในฟังก์ชั่นสั่งงานด้วยตนเองแบบไม่ระบุชื่อ แต่นี่หมายความว่าต้องมีการประกาศตัวควบคุมลูกทั้งหมดในไฟล์เดียวกัน
  2. รูปแบบนี้ใช้งานได้ดีสำหรับคอนโทรลเลอร์ที่อินสแตนซ์โดยตรงจาก html ของคุณ แต่ไม่ดีสำหรับคอนโทรลเลอร์ที่คุณสร้างจากรหัสของคุณผ่านบริการ $ controller () เนื่องจากการพึ่งพาหัวฉีดทำให้คุณไม่ต้องฉีดเสริมโดยตรง - บริการพารามิเตอร์จากรหัสโทรของคุณ

1

ฉันถือว่าการขยายตัวควบคุมเป็นการทำงานที่ไม่ดี ค่อนข้างทำให้ตรรกะที่ใช้ร่วมกันของคุณเป็นบริการ วัตถุที่ขยายในจาวาสคริปต์มีแนวโน้มที่จะค่อนข้างซับซ้อน หากคุณต้องการใช้การสืบทอดฉันจะแนะนำ typescript ถึงกระนั้นตัวควบคุมแบบบางก็เป็นวิธีที่ดีกว่าที่จะไปในมุมมองของฉัน

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.