Angular中用代理服務實現控制器與指令之間的通信
場景
一般來說,angular中,控制器與指令之間的通信可以通過共享作用域的方式來實現。這種處理方式可以滿足大部分需求,但是比如說指令如果是獨立作用域的話這種情況就玩不轉了~在開發中就碰到這樣一種尷尬的情況:控制器想要操作某個dom元素(滾動或者獲取元素坐標),angular中是不提倡在控制器中直接操作dom而是在指令中。這個時候就可以通過一個代理服務來實現。
代理服務
這一段代理服務的代碼短小精悍,從著名的 ionic 中拷貝出來。
大致思路是通過 angular.DelegateService
來返回一個代理服務,指令將接口注冊到代理服務,控制器調用代理服務中的接口即可操作指令從而操作dom元素。
擴展Angular屬性
angular.DelegateService = function (methodNames) { //... return ['$log', function ($log) { //... return new DelegateService(); }]; };
</div>
angular中最全局的全局變量當然就是 angular
了,在此之上添加函數,就可以在模塊定義的時候使用。這個函數返回的是標準的聲明service的數組,前面一個 $log
服務,后面一個實現函數。而這個服務本身就是新創建的 DelegateService
實例。
先來看看這個 methodNames
參數有什么用處。 methodNames
看名字就可以知道這是一個數組,包含了一些函數的名稱(字符串)。
if (methodNames.indexOf('$getByHandle') > -1) { throw new Error("Method '$getByHandle' is implicitly added to each delegate service. Do not list it as a method."); }
</div>
避免內置函數 $getByHandle
和自定義的函數沖突。
methodNames.forEach(function (methodName) { DelegateInstance.prototype[methodName] = instanceMethodCaller(methodName); });
</div>
將所數組中的函數一個個復制到代理服務的原型 prototype
上。
再來看看如何調用實例函數。
function instanceMethodCaller(methodName) { return function caller() { var handle = this.handle, args = arguments, foundInstancesCount = , returnValue; this._instances.forEach(function (instance) { if ((!handle || handle === instance.$$delegateHandle) && instance.$$filterFn(instance)) { foundInstancesCount++; var ret = instance[methodName].apply(instance, args); if (foundInstancesCount === 1) { returnValue = ret; } } }); if (!foundInstancesCount && handle) { return $log.warn('Delegate for handle "' + handle + '" could not find a ' + 'corresponding element with id/delegate="' + handle + '"! ' + methodName + '() was not called!\n' + 'Possible cause: If you are calling ' + methodName + '() immediately, and ' + 'your element with delegate-handle="' + handle + '" is a child of your ' + 'controller, then your element may not be compiled yet. Put a $timeout ' + 'around your call to ' + methodName + '() and try again.'); } return returnValue; }; }
</div>
其實很簡單,這里返回了一個 caller
函數,這個函數就是遍歷_instances中的實例,然后匹配函數名稱,調用并返回結果。默認返回第一個實例或者指定的實例。
下面來看看這個實例。
代理服務實例
function DelegateInstance(instances, handle) { this._instances = instances; this.handle = handle; }
</div>
這是一個代理服務,自然會有很多實例,這里用 _instances
這個數組來保存實例。至于 handle
可以理解為實例id。
DelegateService.prototype = DelegateInstance.prototype;
</div>
這里首先將 DelegateInstance
的原型復制到 DelegateService
上。
function trueFn() { return true; } DelegateService.prototype._registerInstance = function (instance, handle, filterFn) { var instances = this._instances; instance.$$delegateHandle = handle; instance.$$filterFn = filterFn || trueFn; instances.push(instance); return function deregister() { var index = instances.indexOf(instance); if (index !== -1) { instances.splice(index, 1); } }; };
</div>
這里在代理服務 DelegateService
的原型上聲明了一個函數 _registerInstance
用來注冊服務。該函數將服務保存在代理服務中,同時返回一個注銷服務的函數。 filterFn
實際上就是一個過濾函數,如果實例中擁有私有函數,可以通過自定義過濾函數,只暴露公有函數。
DelegateService.prototype.$getByHandle = function (handle) { return new DelegateInstance(this._instances, handle); };
</div>
私有函數,通過 handle
這個id來獲取實例。
使用實例
這里的例子是控制器通過代理服務來操作指令的例子
編寫指令,注冊服務
angular.module('directive', []).directive('keyboard', ['keyboardService', function (keyboardService) { return { replace: true, templateUrl: 'template/keyboard.html', link: function (scope, elm, attrs) { var instance = { toggle: function (flag) { window.console.log(flag); } }; keyboardService._registerInstance(instance); } }; }]).service('keyboardService', angular.DelegateService(['toggle']));
</div>
控制器中通過服務調用指令
angular.module('app', ['directive']).controller('ctrl', ['$scope', 'keyboardService', function($scope, keyboardService){ keyboardService.toggle(true); }]);
</div>
代理服務源碼
https://github.com/driftyco/ionic/blob/master/js/utils/delegateService.js
如果覺得閱讀這篇文章有收獲,不妨點個贊吧^_^
博客: http://yalishizhude.github.io
作者:亞里士朱德
</div> </div>
來自: http://yalishizhude.github.io/2016/02/28/angular-directive-delegate/