AngularJS入门教程(六)组件化开发

接着上一章AngularJS的指令学习,本章我将带领大家学习AngularJS的组件。包含可复用组件的创建、组件的生命周期以及一些自带组件的使用。

AngularJS组件(Component)

组件其实就是指令的一种特性形式,它规避了一些指令中晦涩难理解的东西,比如compile函数,link函数,scope,restrict等,所以组件的目的就是能让我们更为傻瓜式的创建指令,能更好的遵循组件化的开发模式,提高性能以及更容易向AngularJS 2.0迁移。

组件的创建

使用Module的component方法创建组件:

var mainModule = angular.module("mainModule", []);
mainModule.component("myComponent", {
  templateUrl: "myTemplate.html",
  controller: function() {

  },
  bindings: {
    name: "="
  }
});

component方法的第一个参数是组件名称,命名规则和使用方法与指令一样,第二个参数和创建指令有点不同,它并不是一个函数,而是一个对象,在该对象中对组件的配置和在指令中的配置方式很类似。

我们先来看看组件和指令之间有哪些区别:

  • 组件中不提供手动配置作用域,默认的作用域就是隔离域。 
  • 组件中通过bindings属性进行数据绑定,除了=,@,&三种绑定方式以外还增加了一种<方式,既单向绑定,但不限于字符串。从而保证了组件有自己的清晰的输入输出API。并且通过bindings对象绑定的属性直接绑定在组件的Controller上。 
  • 组件的Controller默认名称为$ctrl,当然也可以使用controllerAs属性指定Controller的名称。 
  • 组件只能以标签形式使用。 
  • 组件中没有link函数,compile函数,priority属性,restrict属性。 
  • 组件只能控制自身的输入输出,组件不允许修改属于自己隔离域以外的任何数据和DOM元素。一般情况下,AngularJS通过作用域(Scope)继承的特性支持跨层级修改数据的能力,但是如果当修改数据职责不清晰或不恰当的时候就会导致各种问题,所以这也就是组件的作用域默认都是隔离域的原因。

使用起来和指令比较类似:

// modules.js
var mainModule = angular.module("mainModule", []);
mainModule.controller("MyController", function() {
  this.person = {
    name: "Jason"
  }
})
mainModule.component("myComponent", {
  templateUrl: "myTemplate.html",
  controller: function() {

  },
  bindings: {
    person: "="
  }
});
<!--index.html-->
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Demo for Component</title>
    <script src="../angular-1.5.8.js"></script>
    <script src="modules.js"></script>
  </head>
  <body ng-app="mainModule">
    <div ng-controller="MyController as mc">
      <my-component person="mc.person"></my-component>
    </div>
  </body>
</html>
<!--myTemplate.html-->
<span>Name: {{$ctrl.person.name}}</span>

组件的生命周期

在组件的整个生命周期里,AngularJS提供了五个关键点的方法,可供我们监听到组件的运行状态: 

  • $onInit():该方法在组件及其所有 binding 初始化之后被调用,从而我们就有了一个清晰的地方统一存放数据初始化的逻辑:

    var mainModule = angular.module("mainModule", []);
    mainModule.component("myComponent", {
      templateUrl: "myTemplate.html",
      controller: function() {
        this.name = "jason";
      }
    });
    // 将初始化数据的逻辑放在onInit方法中
    mainModule.component("myComponent", {
      templateUrl: "myTemplate.html",
      controller: function() {
        this.$onInit = function() {
          this.name = "jason";
        }
      }
    });
    
  • $onChanges(changesObj):当组件中单向绑定的属性值发生变化时被调用,这里要注意的是只有绑定属性值的引用发生变化时才能监听到,如果只是在指令内对属性进行修改,该方法是无法监听到的。通过该方法的参数可以获取到被修改数据当前的值、修改之前的值、是否时第一次修改:

    mainModule.component("myComponent", {
      templateUrl: "myTemplate.html",
      controller: function() {
        this.$onChanges = function(changesObj) {
          if(changesObj.name) {
            // name当前的值
            var nameCurrentValue = changesObj.name.currentValue;
            // name修改前的值
            var namePreviousValue = changesObj.name.previousValue;
            // 是否是第一次修改
            var isFirstChange = changesObj.name.isFirstChange();
          }
        }
      },
      bindings: {
        name: "<"
      }
    });
  • $doCheck():该方法和$onChanges(changesObj)作用类似,但是该方法可以监听到在指令内对属性进行修改的行为:

    mainModule.component("myComponent", {
      templateUrl: "myTemplate.html",
      controller: function() {
        // 当name在指令内修改时
        this.name = "Green";
        this.$doCheck = function() {
          // doCheck方法会被调用
        }
      },
      bindings: {
        name: "<"
      }
    });
  • $onDestroy():当作用域被销毁时调用该方法。
  • $postLink():当指令所在标签与子标签链接时调用该方法。

组件化开发

我们先来看看示例效果:
AngularJS组件开发实例

既然是组件化开发,那么我们来看看上面这个示例有几个组件:
AngularJS入门教程(六)组件化开发

从上图可以看到,整个示例一共用了三个组件,其中有两个组件进行了复用,下面我们来看看每个组件是如何定义的。

自定义personList组件

该组件主要用来初始化数据源,定义对数据源操作的函数:

mainModule.component("personList", {
  templateUrl: "personList.html",
  controller: function() {
    this.$onInit = function() {
      this.list = [{
        name: "Jason",
        job: "Developer"
      },{
        name: "Green",
        job: "Doctor"
      }];
    };
    this.updatePerson = function(person, job, value) {
      person[job] = value;
    };
    this.deletePerson = function(person) {
      var idx = this.list.indexOf(person);
      if(idx >= 0) {
        this.list.splice(idx, 1);
      }
    };
  }
});

首先在$onInit函数中初始化数据源,定义了Person对象数组,然后定义了更新指定Person对象的方法updatePerson及删除指定Person对象的方法deletePerson。

再来看看它的UI模板文件personList.html:

<b>Person</b><br>
<person-detail ng-repeat="person in $ctrl.list" person="person" on-update="$ctrl.updatePerson(person, job, value)" on-delete="$ctrl.deletePerson(person)"></person-detail>

该文件共有两部分,第一部分是用原生HTML标签定义了标题,第二部分是使用了另外一个组件personDetail。ng-repeat指令是AngularJS内置的指令,作用不言而喻,就是循环数据源,同时组件也跟据循环次数增加。person,on-update,on-delete是在personDetail组件中定义的数据绑定属性,用大白话解释就是,personDetail组件中的person变量与personList组件中的Person对象进行了绑定,personDetail组件中的onUpdate和onDelete方法分别与personList组件中的updatePerson和deletePerson方法进行了绑定

定义personDetail组件

该组件主要用于展示Person对象的具体内容:

mainModule.component("personDetail", {
  templateUrl: "personDetail.html",
  bindings: {
    person: "<",
    onUpdate: "&",
    onDelete: "&"
  },
  controller: function() {
    this.update = function(job, value) {
      this.onUpdate({person: this.person, job: job, value: value});
    };
    this.delete = function() {
      this.onDelete(this.person);
    }
  }
});
<hr>
<div>
  Name: {{$ctrl.person.name}}<br>
  Job: <editable-field field-value="$ctrl.person.job" on-update="$ctrl.update('job', value)"></editable-field>
  <button ng-click="$ctrl.delete()">Delete</button>
</div>

在personDetail.html文件里,首先访问了person对象的name属性,将其展示出来,注意,这里由$ctrl.preson访问到的其实是单向绑定的personList组件中的person对象。而且在update函数中调用了与personList组件的updatePerson函数绑定的onUpdate函数,也就是子组件调用了父组件的方法。然后使用了第三个组件editableField,该组件同样有一些属性和方法和personDetail组件中对应的属性和方法进行了绑定。最后增加了一个按钮,并使用ng-click指令指定了按钮的点击事件。

editableField组件

该组件的主要作用是展示并修改person对象中的job属性:

mainModule.component("editableField", {
  templateUrl: "editableField.html",
  bindings: {
    fieldValue: "<",
    onUpdate: "&"
  },
  controller: function() {
    this.$onInit = function() {
      this.editMode = false;
      this.fieldValueCopy = this.fieldValue;
    };
    this.handModelChange = function() {
      if(this.editMode) {
        this.onUpdate({job: "job", value: this.fieldValue});
        this.fieldValueCopy = this.fieldValue;
      }
      this.editMode = !this.editMode;
    };
    this.reset = function() {
      this.fieldValue = this.fieldValueCopy;
    };
  }
});

从最开始的运行效果中可以看到editableField是有形态变化的,所以在$onInit函数中定义了是否为编辑模式的标识符editMode以及代表输入框内容的fieldValue变量,因为有reset功能,所以还定义存储修改之前值的变量fieldValueCopy。然后定义了点击Edit或Save按钮触发的函数handModelChange,并在该函数中调用了和personDetail组件的update函数绑定的onUpdate函数,同样由子组件调用了父组件的方法。还定义了点击Reset按钮触发的函数reset。

<span ng-switch="$ctrl.editMode">
  <input ng-switch-when="true" type="text" ng-model="$ctrl.fieldValue">
  <span ng-switch-default>{{$ctrl.fieldValue}}</span>
</span>
<button ng-click="$ctrl.handModelChange()">{{$ctrl.editMode ? "Save" : "Edit"}}</button>
<button ng-if="$ctrl.editMode" ng-click="$ctrl.reset()">Reset</button>

在editableField.html文件中展示了person对象的job属性,定义了修改job属性的输入框以及两个按钮。这里出现了一组之前没见过的AngularJS内置指令,ng-switch、ng-switch-when、ng-switch-default,这三个指令一般组合使用,作用类似if else语句,通过这组指令和deitMode变量就可以达到动态变换DOM元素的功能。

最后来看看简单的index.html文件:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Demo for Directive</title>
    <script src="../angular-1.5.8.js"></script>
    <script src="modules.js"></script>
  </head>
  <body ng-app="mainModule">
    <person-list></person-list>
  </body>
</html>

从上面的这个示例中可以看出在editableField和personDetail组件中都没有真正意义上去修改数据源,而是通过函数绑定一路将修改数据源的行为传递到了定义数据源的组件personList中,由它最后真正完成对数据源的修改,这也遵循了组件不允许修改属于自己隔离域以外的任何数据和DOM元素的原则。

版权声明:本文为博主原创文章,未经博主允许不得转载。

AngularJS入门教程(六)组件化开发

: » AngularJS入门教程(六)组件化开发

原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/251167.html

(0)
上一篇 2022年5月2日
下一篇 2022年5月2日

相关推荐

发表回复

登录后才能评论