AngularJS指令開發(2)——模塊化方案

上一章節,給你們回顧了一下AngularJS指令參數的基礎使用。若是有紕漏,歡迎你們給我留言,相互探討探討。html

指令化,其實本質就是代碼的通用化與模塊化,AngularJS的指令化工做,將邏輯與DOM都結合在一塊兒,可以作到即插即用,與Asp的Component是類似的概念。前端

要作到模塊化,必要的要求就是通用代碼與業務代碼的解耦。而解耦並不表明徹底的隔絕,解耦要作的是,通用模塊與業務模塊的隔離,同時也保留接口提供二者通信segmentfault

囉嗦,趕忙實例吧!後端

某天,產品老大壓下來需求,要作個學生信息填寫卡,說白了就是一個表單編輯器,so easy,前端單身狗拍拍腦殼立刻開工。app

<body>
    <div ng-controller='demoCtrl'>
        <div class="panel">
            <h3>student card</h3>
            <p>
                <span>name:</span>
                <input type="text" ng-model="stu.name" />
            </p>
            <p>
                <span>sexy :</span>
                <select ng-model="stu.sexy">
                    <option value="1">male</option>
                    <option value="2">female</option>
                </select>
            </p>
            <p>......</p>
            <button class="btn btn-info" type="button" ng-click="saveEditing()">save</button>
        </div>
    </div>

    <script>
        var app = angular.module("app", []);

        app.controller('demoCtrl', function ($scope) {
            $scope.stu = {
                name: "mark",
                sexy: 1
            };

            $scope.saveEditing = function () {
                //$http.post("...", { stu: $scope.guy });
                console.log($scope.guy.name + " is saving!");
                console.log($scope.guy);
            };
            
            //這裏省略信息卡交互邏輯…… 
        });               
    </script>
</body>

出來結果,還行。編輯器

clipboard.png

隔了一天,產品老大想了想,要加強體驗,在選擇性別的時候,名稱要根據男女顏色變化;輸入名稱的時候,要自動匹配近似名稱,而且首字母大寫。行,改唄。demoCtrl加上如下邏輯模塊化

$scope.nameChange = function () {
    //處理名稱變動
};

$scope.sexyChange = function () {
    //處理性別變動
};

又隔了一天,產品老大終於定稿,這個學生信息填寫卡,要應用到demo頁、demo1頁、demo2頁上,而3個頁面保存功能指向的後端接口都不同。函數

前端單身狗:雖然不至於問候您大爺,但至少要好好考慮如何實現才省功夫吧。難道要把demoCtrl的相關代碼都copy到另外兩個頁面嗎?這個時候,只要是寫過代碼的同志都會say no吧!oop

趕忙指令化。post

隔離

首先,把信息卡的DOM結構獨立開來,建立指令student。指令scope參數要設爲{},若是設爲falsetrue達不到徹底隔離的效果哦,不理解緣由的童鞋請回顧上一章節。

<div ng-controller='demoCtrl'>
    <student></student>
</div>

<div ng-controller='demo1Ctrl'>
    <student></student>
</div>

<div ng-controller='demo2Ctrl'>
    <student></student>
</div>

<!--信息卡DOM模板->
<script type="text/html" id="t1">
    <div class="panel">
        <p>
            <span>name:</span>
            <input type="text" ng-model="stu.name" />
        </p>
        <p>
            <span>sexy :</span>
            <select ng-model="stu.sexy">
                <option value="1">male</option>
                <option value="2">female</option>
            </select>
        </p>
        <button class="btn btn-info" type="button" ng-click="onSave()">save</button>
    </div>
</script>

<script>
    var app = angular.module("app", []);

   app.directive('student', function () {
        return {
            restrict: 'E',
            scope: {},
            template: function (elem, attr) {
                return document.getElementById('t1').innerHTML;
            },
            controller:function($scope){
                //這裏省略信息卡交互邏輯……                    
            }                
        };
    });
</script>

雖然獨立了信息卡代碼,但有兩個問題是顯而易見的

  1. 隔離之後,指令內部如何得到3個demoCtrl的stu?

  2. 保存按鈕又是如何調用外部3個demoCtrl不一樣的save方法?

問題其實指明瞭解決思路,student須要兩個接口與外部通信:scope.stu & scope.save()

通信

爲你們介紹3種通信方案。

scope {}

指令scope {}參數,能夠徹底隔離做用域,可是也預留了3種綁定策略,實現子域與父域通信。

爲指令創建兩個通信接口,stu採起=雙向綁定父域對象的策略;而onSave則採起$反向調用父域函數策略。

scope: {
    stu: '=',      //=爲雙向綁定策略
    onSave: '&'    //$反向調用父域函數策略
},

優勢:簡單、簡潔
缺點:

  1. 通信接口要求比較多、複雜的狀況下,指令scope {}要配置的綁定策略也比較多;

  2. 形成指令與指令之間的通信容易混亂;

  3. 指令內部好像沒有辦法,往onSave()函數裏面傳參,這個不肯定,求助你們。

DOM寫法:

<student stu="guy" on-save="saveEditing()"></student>

完整例子:

<body>
    <div ng-controller='demoCtrl'>
        <!--指令scope.stu雙向綁定demoCtrl scope.guy-->
        <!--指令scope.onSave函數指向demoCtrl scope.saveEditing()-->
        <student stu="guy" on-save="saveEditing()"></student>
    </div>

    <script type="text/html" id="t1">
        <div class="panel">
            <p>
                <span>name:</span>
                <input type="text" ng-model="stu.name" />
            </p>
            <p>
                <span>sexy :</span>
                <select ng-model="stu.sexy">
                    <option value="1">male</option>
                    <option value="2">female</option>
                </select>
            </p>
            <button class="btn btn-info" type="button" ng-click="onSave()">save</button>
        </div>
    </script>

    <script>
        var app = angular.module("app", []);

        app.controller('demoCtrl', function ($scope) {
            $scope.guy = {
                name: "mark",
                sexy: 1
            };

            $scope.saveEditing = function () {
                //$http.post("...", { stu: $scope.guy });
                console.log($scope.guy.name + " is saving!");
                console.log($scope.guy);
            };
        });

        app.directive('student', function () {
            return {
                restrict: 'E',
                scope: {
                    stu: '=',      //=爲雙向綁定策略
                    onSave: '&'    //$反向調用父域函數策略
                },
                template: function (elem, attr) {
                    return document.getElementById('t1').innerHTML;
                },
                controller: function ($scope, $element, $attrs, $transclude) {
                    //這裏省略信息卡交互邏輯……
                }
            };
        });
    </script>
</body>

效果:

clipboard.png

ngModel

ngModel,這個內置指令相信你們都不會陌生。自定義指令引用其自身的ngModel指令,其原理就是:

  • 父域對象 綁定 ngModel

  • ngModel 綁定 指令子域對象

優勢:能夠充分利用ngModel的特性,例如commit、rollback等特性,也能夠搭配ng-model-options進行使用。

缺點:

  1. 相對於方案1,父子對象綁定中間還要多一層ngModel的綁定,性能必然下降;

  2. 相對於方案1,使用比較麻煩;

  3. 自身不能實現反向調用父域函數,須要藉助$parse轉換表達式方案實現

DOM寫法:

<student ng-model="guy" on-save="saveEditing()"></student>

完整例子:

<body>
    <div ng-controller='demoCtrl'>
        <student ng-model="guy" on-save="saveEditing()"></student>
    </div>

    <script type="text/html" id="t1">
        <div class="panel">
            <p>
                <span>name:</span>
                <input type="text" ng-model="ngModel.$modelValue.name" />
            </p>
            <p>
                <span>sexy :</span>
                <select ng-model="ngModel.$modelValue.sexy">
                    <option value="1">male</option>
                    <option value="2">female</option>
                </select>
            </p>
            <button class="btn btn-info" type="button" ng-click="onSave()">save</button>
        </div>
    </script>

    <script>
        var app = angular.module("app", []);

        app.controller('demoCtrl', function ($scope) {
            $scope.guy = {
                name: "mark",
                sexy: 1
            };

            $scope.saveEditing = function () {
                //$http.post("...", { stu: $scope.guy });
                console.log($scope.guy.name + " is saving!");
                console.log($scope.guy);
            };
        });

        app.directive('student', ["$parse", function ($parse) {
            return {
                restrict: 'E',
                require: ['student', 'ngModel'],
                scope: {},
                template: function (elem, attr) {
                    return document.getElementById('t1').innerHTML;
                },
                link: function (scope, element, attr, ctrls) {
                    //link階段,經過require獲取指令自身的控制器,及ngModel指令的控制器
                    var stCtrl = ctrls[0];
                    var ngModelCtrl = ctrls[1];

                    //並將ngModel指令的控制器,經過自身控制器的init()方法傳入到其中
                    stCtrl.init(ngModelCtrl);
                },
                controller: function ($scope, $element, $attrs, $transclude) {
                    //建立controller的對外初始化方法,並將外部ngModel的控制器設置本地做用域對象
                    this.init = function (ngModelCtrl) {
                        $scope.ngModel = ngModelCtrl;
                    };

                    //得到on-save屬性指向的表達式{{saveEditing()}}
                    var saveInvoker = $parse($attrs.onSave);

                    $scope.onSave = function () {
                        //在父域中,執行表達式{{saveEditing()}}——執行父域saveEditing()
                        saveInvoker($scope.$parent, null);
                    };
                }
            };
        }]);


    </script>
</body>

$parse

在ngModel的解決方案中,已經有過經過$parse獲取執行表達式操做父域函數的例子:$parse($attrs.onSave)。這個方案不只可以執行父域函數表達式,同時也可以執行對象的get/set表達式。可是本方案本質實際上是對於父域$scope.$parent的直接操做,只是經過$parse服務實現解耦。

關於$parse服務,想了解更多,請移步——
http://segmentfault.com/a/1190000002749571

<body>
    <div ng-controller='demoCtrl'>
        <student stu="guy" on-save="saveEditing()"></student>
    </div>

    <script type="text/html" id="t1">
        <div class="panel">
            <p>
                <span>name:</span>
                <input type="text" ng-model="stu.name" />
            </p>
            <p>
                <span>sexy :</span>
                <select ng-model="stu.sexy">
                    <option value="1">male</option>
                    <option value="2">female</option>
                </select>
            </p>
            <button class="btn btn-info" type="button" ng-click="onSave()">save</button>
        </div>
    </script>

    <script>
        var app = angular.module("app", []);

        app.controller('demoCtrl', function ($scope) {
            $scope.guy = {
                name: "mark",
                sexy: 1
            };

            $scope.saveEditing = function () {
                //$http.post("...", { stu: $scope.guy });
                console.log($scope.guy.name + " is saving!");
                console.log($scope.guy);
            };
        });

        app.directive('student', ["$parse", function ($parse) {
            return {
                restrict: 'E',
                scope: {},
                template: function (elem, attr) {
                    return document.getElementById('t1').innerHTML;
                },
                controller: function ($scope, $element, $attrs, $transclude) {
                    var getClassName,
                        setClassName,
                        saveInvoker = angular.noop;

                    //注意:建議查閱一下$parse內置表達式轉換函數的使用方法。

                    //得到stu屬性指向的表達式{{guy}}
                    getStudent = $parse($attrs.stu);
                    setStudent = getStudent.assign;

                    //得到on-save屬性指向的表達式{{saveEditing()}}
                    saveInvoker = $parse($attrs.onSave);

                    //監聽父域的{{guy}}
                    $scope.$parent.$watch(getStudent, function (stu) {
                        $scope.stu = stu;
                    });

                    $scope.onSave = function () {
                        //在父域中,執行表達式{{guy}} assign——將本地對象stu設置到父域guy
                        setStudent($scope.$parent, $scope.stu);

                        //在父域中,執行表達式{{saveEditing()}}——執行父域saveEditing()
                        saveInvoker($scope.$parent, null);
                    };
                }
            };
        }]);


    </script>
</body>

效果:

clipboard.png

End

說了一大堆,你們仍是動手試試哪一個方案更合適你的項目,或者有更好的開發思路。

下一章節,爲你們介紹一下關於指令的自動化測試。歡迎繼續捧場。

相關文章
相關標籤/搜索