2019年12月29日 星期日


在使用 Angularjs 的 directive 時,
有時我們會想要在外層 (即使用 directive 的 controller 或其他 directive) 去呼叫 directive 裡面的 function,
去改變一些 directive 的內部狀態,
這時使用 service 就是一個比較好的做好,
service 可以作為 directive 及使用 directive 的元件 (例如外部 controller 或其他 directive)
的溝通橋梁,
利用把 directive 的 scope 或 內部 controller 綁定設定到 service 中,
我們即可在外部將 service 注入,並使用 service 來控制 directive。

下面給出一個範例 : 可以看到,<my-form-directive> 是我們實作的一個 directive,
裡面有一個名為 "pristineForm" 的 function ,
其可以設定 $scope 裡面的 myForm (為在 directive template 中 name="myForm" 的 <form>)
成初始狀態 ( 使用 formController.$setPristine() )。

在 <my-form-directive> 外,有一個 <div> 被設定了  ng-click="ctrl.pristineForm() ,
會去呼叫外層 controller 的 pristineForm(),
而 pristineForm() 會利用引入的 directiveHandler 這個 service ,
去呼叫 myFormDirective 這個 directive 內部的 pristineForm()。

下面是詳細的程式碼:

Javascript :
angular.module("app", [])
  .controller("controller", ["directiveHandler", function(directiveHandler) {
    var self = this;
    self.pristineForm = function(directiveName) {
     var directiveName = directiveName ? directiveName : 'myFormDirective';
      directiveHandler.getDirective(directiveName).pristineForm();
    }

  }])
  .directive("myFormDirective", ["directiveHandler", function(directiveHandler) {
    return {
      restrict: "E",
      template: `<div>
                  <form name="myForm">
                    Username : <input type="text" name="username" ng-model="username" required/>
                    <div ng-show="myForm.username.$dirty && myForm.username.$invalid">
                      Please fill in username.
                    </div>
                    <div>Form $pristine : {{myForm.$pristine}}</div>
                  </form>
            </div>`,
      replace: true,
      scope: {
      },
      link: function($scope, $elm, $attrs, $ctrl) {
        var directiveNameAttr = $attrs.dirName ? $attrs.dirName : "myFormDirective";
        directiveHandler.registerDirective(directiveNameAttr, $scope);

        $scope.username = "";
        $scope.pristineForm = function() {
          $scope.myForm.$setPristine();
        }
      }
    };
  }]).factory('directiveHandler', function() {
    var instance_map = {};
    var service = {
      registerDirective: registerDirective,
      getDirective: getDirective,
      deregisterDirective: deregisterDirective
    };

    return service;

    function registerDirective(name, ctrl) {
      instance_map[name] = ctrl;
    }

    function getDirective(name) {
      return instance_map[name];
    }

    function deregisterDirective(name) {
      instance_map[name] = null;
    }
  });

HTML :
<div ng-app="app" ng-controller="controller as ctrl">
  <my-form-directive dir-name="myFormDir"></my-form-directive>
  <div ng-click="ctrl.pristineForm('myFormDir')"><button>Set Form Pristine</button></div>
  
  <my-form-directive dir-name="myFormDir2"></my-form-directive>
  <div ng-click="ctrl.pristineForm('myFormDir2')"><button>Set Form Pristine</button></div>
</div>



說明:
在 directiveHandler 中,設定了一個內部管理的 instance_map,
用 key-value 的次式將需要的 directive instance (可能是 isolate scope 的 scope,可能是 directive 設定的 controller 等,依情況自行決定) 存起來,
設定了三個 function,

主要的為  registerDirective(name, ctrl) 和 getDirective(name),
registerDirective(name, ctrl) 主要在 directive 初始化時使用一次,
用想要的 name 作為 key,把想要暴露給外部使用的 instance 作為 ctrl 設定進來。

getDirective(name) 在外部使用,例如此例的 controller,
用 name 值來取得 directive 的內部 instance,例如此例即為 <my-form-directive> 的 isolate scope,
取得 instance 後,即可執行 instance 上所設定的 function,當然取得 instance 上設定的值也是可以的。

在 html 中使用 <my-form-directive> 時,給了一個 dir-name 屬性,
dir-name 屬性在 directive 用來作為 name 參數呼叫 registerDirective(name, ctrl),
如果有多個 directive 的話,為了區分各個 directive,
給定不同的 name 作為 instance 在 directiveHandler 中的 key 值將會
使管理各個 directive 的 instance 更為方便。

為了演示,我們在 html 中使用了兩個 <my-form-directive>,並給它們設定了不同的 dir-anem 屬性值。

參考資料:

  1. How to call a method defined in an AngularJS directive? (最喜歡Mudassir Ali的回答)

沒有留言 :

張貼留言