جاوااسکریپت - آیا یک کنترلر AngularJS میتواند کنترلر دیگری را فراخوانی کند؟
آیا ممکن است یک کنترلر از کنترلر دیگر استفاده کند؟
به عنوان مثال:
این سند HTML به سادگی پیامی را که توسط کنترلکننده MessageCtrl در فایل messageCtrl.js ارائه شده است، چاپ میکند.
<html xmlns:ng="http://angularjs.org/">
<head>
<meta charset="utf-8" />
<title>Inter Controller Communication</title>
</head>
<body>
<div ng:controller="MessageCtrl">
<p>{{message}}</p>
</div>
<!-- Angular Scripts -->
<script src="http://code.angularjs.org/angular-0.9.19.js" ng:autobind></script>
<script src="js/messageCtrl.js" type="text/javascript"></script>
</body>
</html>
فایل کنترلر شامل کدهای زیر است:
function MessageCtrl()
{
this.message = function() {
return "The current date is: " + new Date().toString();
};
}
که به سادگی تاریخ فعلی را چاپ میکند؛
اگر میخواستم یک کنترلکننده دیگر اضافه کنم، DateCtrl که تاریخ را در یک فرمت مشخص به MessageCtrl بازمیگرداند، چگونه میتوان این کار را انجام داد؟ به نظر میرسد چارچوب DI به XmlHttpRequests و دسترسی به سرویسها علاقهمند است.
پاسخ شماره ۱
راههای متعددی برای برقراری ارتباط بین کنترلرها وجود دارد.
بهترین گزینه احتمالاً اشتراک یک سرویس است:
function FirstController(someDataService)
{
// use the data service, bind to template...
// or call methods on someDataService to send a request to server
}
function SecondController(someDataService)
{
// has a reference to the same instance of the service
// so if the service updates state for example, this controller knows about it
}
راه دیگری، انتشار یک رویداد در حوزه است:
function FirstController($scope)
{
$scope.$on('someEvent', function(event, args) {});
// another controller or even directive
}
function SecondController($scope)
{
$scope.$emit('someEvent', args);
}
در هر دو مورد، شما همچنین میتوانید با هر دستورالعملی ارتباط برقرار کنید.
پاسخ شماره ۲
این فیدل را ببینید: http://jsfiddle.net/simpulton/XqDxG/
همچنین ویدئوی زیر را تماشا کنید: ارتباط بین کنترلرها
اچ تی ام ال :
<div ng-controller="ControllerZero">
<input ng-model="message" >
<button ng-click="handleClick(message);">LOG</button>
</div>
<div ng-controller="ControllerOne">
<input ng-model="message" >
</div>
<div ng-controller="ControllerTwo">
<input ng-model="message" >
</div>
جاوااسکریپت:
var myModule = angular.module('myModule', []);
myModule.factory('mySharedService', function($rootScope) {
var sharedService = {};
sharedService.message = '';
sharedService.prepForBroadcast = function(msg) {
this.message = msg;
this.broadcastItem();
};
sharedService.broadcastItem = function() {
$rootScope.$broadcast('handleBroadcast');
};
return sharedService;
});
function ControllerZero($scope, sharedService) {
$scope.handleClick = function(msg) {
sharedService.prepForBroadcast(msg);
};
$scope.$on('handleBroadcast', function() {
$scope.message = sharedService.message;
});
}
function ControllerOne($scope, sharedService) {
$scope.$on('handleBroadcast', function() {
$scope.message = 'ONE: ' + sharedService.message;
});
}
function ControllerTwo($scope, sharedService) {
$scope.$on('handleBroadcast', function() {
$scope.message = 'TWO: ' + sharedService.message;
});
}
ControllerZero.$inject = ['$scope', 'mySharedService'];
ControllerOne.$inject = ['$scope', 'mySharedService'];
ControllerTwo.$inject = ['$scope', 'mySharedService'];
پاسخ شماره ۳
اگر میخواهید یک کنترلر را به کنترلر دیگر فراخوانی کنید، چهار روش موجود است
- $rootScope.$emit() و $rootScope.$broadcast()
- اگر کنترلر دوم فرزند باشد، میتوانید از ارتباط والد و فرزند استفاده کنید.
- استفاده از خدمات
- نوعی هک - با کمک angular.element()
1. $rootScope.$emit() و $rootScope.$broadcast()
کنترلر و حوزه آن می توانند از بین بروند، اما $rootScope در سراسر برنامه باقی میماند، به همین دلیل ما $rootScope را میگیریم چون $rootScope والد تمام حوزهها است.
اگر شما در حال انجام ارتباط از والد به فرزند هستید و حتی فرزند هم میخواهد با خواهر و برادران خود ارتباط برقرار کند، میتوانید از $broadcast استفاده کنید
اگر شما در حال انجام ارتباط از فرزند به والد هستید و هیچ خواهر و برادری درگیر نیست، میتوانید از $rootScope.$emit استفاده کنید
اچتیامال
<body ng-app="myApp">
<div ng-controller="ParentCtrl" class="ng-scope">
// ParentCtrl
<div ng-controller="Sibling1" class="ng-scope">
// Sibling first controller
</div>
<div ng-controller="Sibling2" class="ng-scope">
// Sibling Second controller
<div ng-controller="Child" class="ng-scope">
// Child controller
</div>
</div>
</div>
</body>
کد انگولارجیاس
var app = angular.module('myApp',[]);//We will use it throughout the example
app.controller('Child', function($rootScope) {
$rootScope.$emit('childEmit', 'Child calling parent');
$rootScope.$broadcast('siblingAndParent');
});
app.controller('Sibling1', function($rootScope) {
$rootScope.$on('childEmit', function(event, data) {
console.log(data + ' Inside Sibling one');
});
$rootScope.$on('siblingAndParent', function(event, data) {
console.log('broadcast from child in parent');
});
});
app.controller('Sibling2', function($rootScope) {
$rootScope.$on('childEmit', function(event, data) {
console.log(data + ' Inside Sibling two');
});
$rootScope.$on('siblingAndParent', function(event, data) {
console.log('broadcast from child in parent');
});
});
app.controller('ParentCtrl', function($rootScope) {
$rootScope.$on('childEmit', function(event, data) {
console.log(data + ' Inside parent controller');
});
$rootScope.$on('siblingAndParent', function(event, data) {
console.log('broadcast from child in parent');
});
});
در کد بالا، کنسول $emit 'childEmit' درون خواهر و برادرهای فرزند فراخوانی نمیشود و تنها در والد فراخوانی میشود، جایی که $broadcast داخل خواهر و برادرها و والد نیز فراخوانی میشود. این همان جایی است که عملکرد وارد عمل میشود. $emit ترجیح داده میشود، اگر شما در حال استفاده از ارتباط فرزند به والد هستید زیرا برخی از بررسیهای کثیف را نادیده میگیرد.
2. اگر کنترلر دوم کودکی باشد، میتوانید از ارتباط والد و کودک استفاده کنید
این یکی از بهترین روشها است، اگر میخواهید ارتباط کودک با والد را انجام دهید که کودک میخواهد با والد مستقیم ارتباط برقرار کند، در این صورت نیازی به هیچ گونه $broadcast یا $emit نخواهید داشت، اما اگر میخواهید ارتباط از والد به کودک برقرار شود، باید از سرویس یا $broadcast استفاده کنید
برای مثال HTML:-
<div ng-controller="ParentCtrl">
<div ng-controller="ChildCtrl">
</div>
</div>
انگولارجاس
app.controller('ParentCtrl', function($scope) {
$scope.value='Its parent';
});
app.controller('ChildCtrl', function($scope) {
console.log($scope.value);
});
هر وقت که از ارتباط فرزند به والد استفاده میکنید، Angularjs به دنبال یک متغیر داخل فرزند میگردد، اگر آنجا وجود نداشته باشد، آن را در کنترلکننده والد جستجو میکند.
۳. استفاده از خدمات
AngularJS از مفاهیم «جداسازی نگرانیها» با استفاده از معماری سرویسها پشتیبانی میکند. سرویسها توابع جاوااسکریپت هستند و مسئول انجام وظایف خاصی تنها میباشند. این باعث میشود که آنها یک واحد مستقل باشند که قابل نگهداری و تست است. سرویسها با استفاده از مکانیزم تزریق وابستگی (Dependency Injection) در AngularJS استفاده میشوند.
کد انگولارجیاس:
app.service('communicate',function(){
this.communicateValue='Hello';
});
app.controller('ParentCtrl',function(communicate){//Dependency Injection
console.log(communicate.communicateValue+" Parent World");
});
app.controller('ChildCtrl',function(communicate){//Dependency Injection
console.log(communicate.communicateValue+" Child World");
});
این خروجی Hello Child World و Hello Parent World را میدهد. طبق مستندات Angular درباره سرویسها تکنمونهها - هر کامپوننتی که به یک سرویس وابسته است، مرجعی به تنها نمونهای که توسط کارخانه سرویس ایجاد شده است دریافت میکند.
۴. نوع هک - با کمک angular.element()
این روش scope() را از عنصر با شناسه یا کلاس یکتا آن میگیرد. متد angular.element() عنصر را برمیگرداند و scope() متغیر $scope یک متغیر دیگر را با استفاده از متغیر $scope یک کنترلر داخل کنترلر دیگر میدهد که یک کار غیر اصولی است.
اچتیامال:-
<div id='parent' ng-controller='ParentCtrl'>{{varParent}}
<span ng-click='getValueFromChild()'>Click to get ValueFormChild</span>
<div id='child' ng-controller='childCtrl'>{{varChild}}
<span ng-click='getValueFromParent()'>Click to get ValueFormParent </span>
</div>
</div>
انگولارجاس:-
app.controller('ParentCtrl',function($scope){
$scope.varParent="Hello Parent";
$scope.getValueFromChild=function(){
var childScope=angular.element('#child').scope();
console.log(childScope.varChild);
}
});
app.controller('ChildCtrl',function($scope){
$scope.varChild="Hello Child";
$scope.getValueFromParent=function(){
var parentScope=angular.element('#parent').scope();
console.log(parentScope.varParent);
}
});
در کد بالا، کنترلرها مقدار خود را در HTML نشان میدهند و وقتی روی متن کلیک کنید، مقادیر مربوطه را در کنسول دریافت خواهید کرد. اگر روی عنصر span کنترلر والد کلیک کنید، مرورگر مقدار کنترلر فرزند را در کنسول نمایش میدهد و بالعکس.
پاسخ شماره ۴
در اینجا یک نمونه تک صفحهای از دو کنترلر که دادههای سرویس را به اشتراک میگذارند آمده است:
<!doctype html>
<html ng-app="project">
<head>
<title>Angular: Service example</title>
<script src="http://code.angularjs.org/angular-1.0.1.js"></script>
<script>
var projectModule = angular.module('project',[]);
projectModule.factory('theService', function() {
return {
thing : {
x : 100
}
};
});
function FirstCtrl($scope, theService) {
$scope.thing = theService.thing;
$scope.name = "First Controller";
}
function SecondCtrl($scope, theService) {
$scope.someThing = theService.thing;
$scope.name = "Second Controller!";
}
</script>
</head>
<body>
<div ng-controller="FirstCtrl">
<h2>{{name}}</h2>
<input ng-model="thing.x"/>
</div>
<div ng-controller="SecondCtrl">
<h2>{{name}}</h2>
<input ng-model="someThing.x"/>
</div>
</body>
</html>
همچنین اینجا: https://gist.github.com/3595424
پاسخ شماره ۵
اگر به دنبال انتشار و پخش رویدادها برای به اشتراکگذاری دادهها یا فراخوانی توابع در بین کنترلرها هستید، لطفاً به این لینک مراجعه کنید و پاسخ zbynour (پاسخ با بیشترین رای) را بررسی کنید. من پاسخ او را نقل قول میکنم !!!
اگر دامنه firstCtrl والد دامنه secondCtrl باشد، کد شما باید با جایگزینی $emit با $broadcast در firstCtrl کار کند:
function firstCtrl($scope){
$scope.$broadcast('someEvent', [1,2,3]);
}
function secondCtrl($scope){
$scope.$on('someEvent', function(event, mass) {console.log(mass)});
}
در صورتی که رابطه والد-فرزند بین اسکوپهای شما وجود نداشته باشد، میتوانید $rootScope را به کنترلر تزریق کرده و رویداد را به همه اسکوپهای فرزند (یعنی همچنین secondCtrl) پخش کنید.
function firstCtrl($rootScope){
$rootScope.$broadcast('someEvent', [1,2,3]);
}
در نهایت، زمانی که نیاز دارید یک رویداد را از یک کنترلکننده فرزند به سمت بالا در لایههای scope ارسال کنید، میتوانید از $scope.$emit استفاده کنید. اگر scope کنترلکننده اول (firstCtrl) والد scope کنترلکننده دوم (secondCtrl) باشد:
function firstCtrl($scope){
$scope.$on('someEvent', function(event, data) { console.log(data); });
}
function secondCtrl($scope){
$scope.$emit('someEvent', [1,2,3]);
}
پاسخ شماره ۶
دو ویولن دیگر: (رویکرد غیر خدماتی)
1) برای کنترلکننده والد-فرزند - استفاده از $scope کنترلکننده والد برای ارسال/پخش رویدادها. http://jsfiddle.net/laan_sachin/jnj6y/
۲) استفاده از $rootScope در کنترلکنندههای غیرمرتبط. http://jsfiddle.net/VxafF/
پاسخ شماره ۷
در واقع استفاده از emit و broadcast ناکارآمد است زیرا رویداد به بالا و پایین سلسله مراتب اسکوپ منتشر میشود که میتواند به راحتی به یک گلوگاه عملکردی برای یک برنامه پیچیده تبدیل شود.
من پیشنهاد میکنم از یک سرویس استفاده کنید. اینجا نحوهای است که من اخیراً آن را در یکی از پروژههایم پیادهسازی کردهام - https://gist.github.com/3384419.
ایده پایه - یک سامانه انتشار-اشتراک/اتوبوس رویداد را به عنوان یک سرویس ثبت کنید. سپس آن اتوبوس رویداد را هر جایی که نیاز به اشتراک یا انتشار رویدادها/موضوعات دارید، وارد کنید.
