AngularJs 控件小结
AngularJs 控件小结
wonderzhou 发表于1年前
AngularJs 控件小结
  • 发表于 1年前
  • 阅读 238
  • 收藏 0
  • 点赞 0
  • 评论 0

腾讯云 技术升级10大核心产品年终让利>>>   

摘要: 基于bootstrape 3和AngularJs的自定义控件入门小结
  • AngularJs控件入门知识:

AngularJs自定义控件又叫自定义指令,自定义指令使用AngularJS扩展HTML的功能。通过添加自定义的特殊元素标签、标签属性等特殊标识来封装JavaScript代码,对AngularJs进行功能扩展,并且有利于代码的复用。常见的自定义指令形式如下:

  • Element directives - 指令遇到时激活一个匹配的元素。

  • Attribute - - 指令遇到时激活一个匹配的属性。

  • CSS - - 指令遇到时激活匹配CSS样式。

  • Comment - - 指令遇到时激活匹配的注释。

AngularJs控件的定义名称为directive,以下是一个基本的控件定义方法示例:

var mainApp = angular.module("mainApp", []);

//第一个参数是控件的自定义名称,当Html页面中出现“student”标识时,这个控件就会被激活。
mainApp.directive('student', function() {
   //定义控件对象
   var directive = {};
   //restrict = E, 表示这个控件是一个标签元素匹配型控件。
   directive.restrict = 'E';
   //template定义的模板标签将在定义的控件标签下自动加入,当replace属性定义为true时则会使用模板
   //替换掉控件标签。 
   directive.template = "Student: <b>{{student.name}}</b> , Roll No: <b>{{student.rollno}}</b>";\
   //如果设置为true,那么模版将会替换当前元素,而不是作为子元素添加到当前元素中。
   //(注:为true时,模版必须有一个根节点)
   directive.replace = true;
   //scope属性定义控件使用全局scope,还是使用一个隔离的局部的scope。
   directive.scope = {
       student : "=name"
   }
   //指令编译方法,在AngularJs初始化应用时执行,且只执行一次,用于操作、编译指令生成的Html。
   directive.compile = function(element, attributes) {
      element.css("border", "1px solid #cccccc");
	  //linkFunction 用于连接Html中需要赋值的参数,对参数进行赋值。
      var linkFunction = function($scope, element, attributes) {
          element.html("Student: <b>"+$scope.student.name +"</b> , Roll No: <b>"+$scope.student.rollno+"</b><br/>");
          element.css("background-color", "#ff00ff");
      }
      return linkFunction;
   }
   return directive;
});

 

更多指令详细介绍请访问:https://my.oschina.net/ilivebox/blog/289670

  • 几个AngularJs控件示例

  1. CSS控制指令
    <aps-button color="red"></aps-button>
    app.directive('apsButton', function() {
    
        var directive = {};
        directive.restrict = 'E';
        directive.template = "<button class='btn'>red</button>";
        directive.scope = {
            color : "=color"
        }
        directive.replace = true;
        directive.compile = function(element, attributes) {
            var linkFunction = function($scope, element, attributes) {
                element.css("background-color", attributes.color);
            }
            return linkFunction;
        }
        return directive;
    });

    在compile指令中利用element参数配置元素的CSS属性,可以通过获取自定义标签中的自定义属性来设置指定的样式。

  2. 带Label的input输入框指令
    <div class="form-group">
        <input aps-input label="节点名称" name="name" id="name" ng-model="node.name" />
        <span><strong>Your input value is : {{node.name}}</strong></span>
    </div>
    app.directive('apsInput', function() {
    	var directive = {};
    	directive.restrict = 'A';
    	directive.require = 'ngModel';
    	directive.transclude = true;
    	directive.compile = function(element, attributes) {
    		//获取指令属性中定义的label名称,并按照Bootstrap风格定义label标签。
    		var labelHtml = '<label class="control-label col-md-2 " for="'+attributes.label+'">'+attributes.label+':</label>';
    		var divHtml = '<div class="col-md-4 form-inline"></div>';
    		element.addClass("form-control col-md-2");
    		var linkFunction = function (scope, ele, attrs, ngModel) {
    			//将定义的HTML标签转换成元素。
    			var lableEle = angular.element(labelHtml);
    			var divEle = angular.element(divHtml);
    			//在input元素前插入label元素。
    			element.before(lableEle);
    			//在input元素外包装一个div元素。
    			element.wrap(divEle);
    			}
    		return linkFunction;
    	}
    	return directive;
    });

    生成带label的input控件重点在于element元素的操作;注意在添加DOM元素前需要将HTML标签用angular.element(); 转换成元素后再进行操作。

    关于element操作方法详情:http://www.th7.cn/web/html-css/201506/104549.shtml

  3. 输入框自定义验证指令

    AngularJs的验证主要发生在model 的值在前后台通过管道传递的过程中,下面先来介绍ngModel的在viewValue和modelValue之间的转换过程。

    ngmodel的转换过程

    angularjs将model和view之间的联系切断,自己内部通过ng-model去实现ViewModel层。这里写图片描述

    在每个使用ng-model的地方,都会创建一个ngModelController实例,这个实例负责管理存储在模型(由model指定)中的值与元素显示值之间的数据绑定。

    ngModelController包含有$formatters和$parsers数组,会在每次更新数据绑定时调用。

    当我们从页面或通过$setViewValue改变绑定的属性时,会遍历执行$parsers数组里面的方法;而当我们直接在js里面通过赋值语句修改model的值时,则会调用$formatters函数数组。详见:1、http://stackoverflow.com/questions/22841225/ngmodel-formatters-and-parsers
    2、http://www.cnblogs.com/whitewolf/p/angular-input-box-format.html

    而我们所需要定义的验证逻辑也是加在这些函数中。

    <div class="form-group">
       <label class="col-md-2 control-label">用户名验证:</label>
       <div class="col-md-2 form-inline">
          <input name-validator class="form-control" ng-model="userName" name="user" id="user"/>
       </div>
       <span>校验结果为:{{vform.user}}</span>
    </div>
    app.directive("nameValidator", function(){
        var directive = {};
        directive.restrict = 'A';
        directive.require = 'ngModel';
        directive.link = function($scope, $element, $attrs, $ngModelCtrl) {
            //验证规则为不能全为数字、大写字母、小写字母。
            var verifyRule = [/^\d+$/, /^[a-z]+$/, /^[A-Z]+$/];
            var verify = function(input) {
                return !(verifyRule[0].test(input) ||
                verifyRule[1].test(input) ||
                verifyRule[2].test(input));
            };
            
            $ngModelCtrl.$parsers.push(function(input) {
                var validity = verify(input);
                $ngModelCtrl.$setValidity('defined', validity);
                return validity ? input : undefined;
            });
            $ngModelCtrl.$formatters.push(function(input) {
                var validity = verify(input);
                $ngModelCtrl.$setValidity('defined', validity);
                return  input ;
            })
        };
        return directive;
    });

    以上代码是一个用户名验证自定义控件,验证规则为不能全为数字、大写字母、小写字母。

    在$parsers中定义了一个由viewValue到modelValue时执行的函数,执行过程中会将验证结果通过$ngModelCtrl.$setValidity('defined', validity);语句保存;当验证通过时返回原值,不通过时返回undefined。

    在$formatters中也定义了一个model值传到view时执行的函数,同样会保存验证结果;直接返回原值到view;

  4. echarts封装指令

    AngularJs控件同样适用于封装一些图表组件。下面以百度echarts为例介绍数据图的封装。

    <div class="row">
       <div class="col-md-12">
           <line id="main" legend="legend" item="item" data="data" theme="'macarons'" unit="'次'"></line>
       </div>
    </div>
    app.directive("line", function(){
        var directive = {};
        directive.restrict = 'E';
        directive.scope = {
            id: "@",
            legend: "=",
            item: "=",
            data: "=",
            theme: "=",
            unit: "="
        };
        directive.template = "<div style='height: 400px;'></div>";
        directive.replace = true;
        directive.link = function ($scope, element, attrs, controller) {
            var option = {
                // 提示框,鼠标悬浮交互时的信息提示
                tooltip: {
                    trigger: 'axis'
                },
                // 图例
                legend: {
                    data: $scope.legend
                },
                // 横轴坐标轴
                xAxis: [{
                    type: 'category',
                    data: $scope.item
                }],
                // 纵轴坐标轴
                yAxis: [{
                    type: 'value',
                    axisLabel : {
                        formatter: '{value} ' + $scope.unit
                    }
                }],
                // 数据内容数组
                series: function(){
                    var serie=[];
                    for(var i=0;i<$scope.legend.length;i++){
                        var item = {
                            name : $scope.legend[i],
                            type: 'line',
                            data: $scope.data[i]
                        };
                        serie.push(item);
                    }
                    return serie;
                }()
            };
            var myChart = echarts.init(document.getElementById($scope.id), $scope.theme);
            myChart.setOption(option);
        };
        return directive;
    });
      $scope.legend = ["请求数", "响应数"];
      $scope.item = ["2016-11-01", "2016-11-02", "2016-11-03", "2016-11-04", "2016-11-05", "2016-11-06", "2016-11-07", "2016-11-08", "2016-11-09", "2016-11-10", "2016-11-11", "2016-11-12"];
      $scope.data = [
        ["0", "46", "0", "23", "33", "0", "50", "0", "0", "78", "37", "70"],
        ["0", "40", "0", "23", "63", "0", "0", "32", "0", "53", "0", "35"]
      ];

    echarts的封装配置方面很简单,重点在于数据结构的整理,需要数据按照它的要求进行整理并赋值。

  5. 外部指令封装

    Angular同样可以进行控件的嵌套封装。下面通过演示Kendo UI中的日期选择控件来介绍控件的嵌套封装。

    <div class="form-horizontal form-group">
        <label for="time" class="control-label col-md-2 col-md-offset-2">请选择日期:</label>
        <div class="col-md-2 form-inline">
            <aps-date-picker id="time" model="dateString" style="width:100%;"></aps-date-picker>
        </div>
        <div class="col-md-2 form-inline">
            <span>你选择的日期是:{{dateString}}</span></div></br>
        </div>
    </div>
    app.directive("apsDatePicker", function($compile){
       var directive = {};
        kendo.culture("zh-CN");//kendo控件的语言设置
        directive.restrict = "E";
        directive.replace = true;
    	directive.require = "ngModel";
        directive.template = '<input kendo-date-picker ng-model="dateStr" style="width: 100%;" />';
        directive.link = function(scope, element, attrs, ngModel) {
            //当日期已经预先定义了时,需要将值传递到日期控件中
            if(scope[attrs['model']]){
            	ngModel.$setViewValue(scope[attrs['model']]);
            }
            //监听方法,用于监控日期选择后的值变化,将新值更新到model中
            scope.$watch('dateStr', function(newVal){
            	scope[attrs['model']] = newVal;
            });
        }
        return directive;
    });
    

    控件的嵌套封装重点在于自定义model值和原控件值的同步;需要加入合适的监听方法,在原控件值更新时,将自定义model值同时更新。

    两种监听方法($watch, $observe)简介:
        $observe和$watch都可以用来对属性进行监控;
        但$observe方法是一个异步解析的操作,是在其他表达式都已经解析之后再解析,这样使它拥有了处理像{{}}插值字符串的机会。$observe是属性对象上的方法,因此它是用来监控DOM属性上的值的变化,它仅用在指令内部,当你需要在指令内部监控包含有插值表达式的DOM属性的时候,就要用到这个方法。
        $watch更复杂一点,它可以监视表达式,这个表达式可以是函数或者字符串,假如表达式是字符串的话,会被封装成一个函数,然后在digest循环的时候被调用。 这个字符串表达式不能包含{{}},$watch是一个scope对象上的方法,所以它可以在任何你可以访问到作用域的地方被调用。比如,控制器中或者link函数中。因为字符串是被当做angular的表达式解析的,所以$watch经常被用在当你想要监控一个模型或者作用域对象的时候。
    详细介绍:http://www.ngnice.com/posts/2314014da4eea8

:directive中controller、compile和 link函数的区别:

  1. 这个3个函数都有element参数,都可以操作自定义指令标签。
  2. controller在编译前运行,主要用于处理为呈现视图之前而准备的数据或者是与其他指令进行数据交互,目的是为了指令间进行交流。当一个指令需要使用另一个指令的controller进行数据交互,则在require参数中设置该controller即可。
  3. compile函数主要用于对template中的元素进行诸如添加和删除节点等DOM操作,compile函数仅在指令初始化时运行一次,而link函数则主要负责将scope中的数据与DOM进行链接,每个指令的实例都会运行link函数。这里需要注意的是,compile函数和link函数在directive下是不能同时定义的,否则只会运行compile函数,想要同时使用两者,只能在compile函数中将link函数作为返回值返回;而link函数中的element对象即是compile函数对template元素进行处理后的实例元素,同时还包含了一个scope对象。
  4. 在compile函数中的返回值中,可以定义preLink和postLink两种link函数返回。当只定义一种link函数时,默认为postLink函数。而当link函数中出现指令嵌套(指令DOM下包含指令)时,preLink函数会从外到内依次执行而postLink则会从子指令到外,反向执行,这样可以确保在父指令标签执行时,子指令是已经运行过的。所以postLink是最安全或者默认的写业务逻辑的地方。详见:http://www.jb51.net/article/58229.htm

        

小结:

controller主要用作数据的准备和指令间数据交流。
compile仅在指令初始化时运行一次,用于操作模板,准备DOM。
Link函数用于compile之后与scope进行数据绑定时调用,是指令逻辑最常写入的地方。
另外,在指令定义之外也可以使用Link,例如构造弹出框时,将页面标签使用var linkFn = $compile(angular.element(tpl));命令进行编译,然后linkFn(scope),即可将scope与页面元素进行绑定。

需要注意的点:

  1. Interpolated attributes (i.e., attributes that use {{}}s) and isolate scope properties defined with '@' are not available when the link function runs. You need to use attrs.$observe() (or scope.$watch( @ property here, ...)) to get the values (asynchronously). ---摘自StackOverFlow  http://stackoverflow.com/questions/15438837/nesting-directives-within-directives

    翻译:差值属性(带{{}}的属性)和使用‘@’定义的隔离scope属性在link时是不可用的。如果要使用它们的值,可以使用attrs.$observe() (或 scope.$watch( @ property here, ...))来异步的获取。

  2. $parse/$eval的区别
    $parse跟$eval都是用来解析表达式的, 但是$parse是作为一个单独的服务存在的。

    var getter = $parse('user.name'); 
    var setter = getter.assign; 
    getter(context, locals) // 传入作用域,返回值
    setter(scope,'new name') // 修改映射在scope上的属性的值为‘new value’

    $eval是作为scope的方法来使用的。是执行当前作用域下的表达式。$eval的内部实现实际使用的就是$parse,只不过作用域设置为当前作用域scope了。

    $eval: function(expr, locals) {
        return $parse(expr)(this, locals);
    },

    参考:http://www.ngnice.com/posts/2314014da4eea8

  • 总结

编写Angularjs控件时,需要注意区分不同的函数的特性:编译时间、编译顺序、编译结果以及函数的用途。例如controller、compile、link函数;

另外还需注意函数中各个参数的正确使用,熟悉参数中的属性内容对提高代码的简洁度有很大帮助。

在编写控件之前最好对Angularjs控件函数有较为清晰的认知,最好对照官方文档学习和使用各个函数,增强熟练度。

  • Angularjs博客参考:

Angularjs开发一些经验总结: http://www.cnblogs.com/whitewolf/archive/2013/03/24/2979344.html

AngularJs input格式化: http://www.cnblogs.com/whitewolf/p/angular-input-box-format.html

Angular实例讲解: http://blog.51yip.com/jsjquery/1607.html


 

共有 人打赏支持
粉丝 2
博文 2
码字总数 3173
×
wonderzhou
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
* 金额(元)
¥1 ¥5 ¥10 ¥20 其他金额
打赏人
留言
* 支付类型
微信扫码支付
打赏金额:
已支付成功
打赏金额: