New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[译] 探索Angular 1.5 之component() 方法 #11

Open
huguangju opened this Issue Jun 23, 2016 · 2 comments

Comments

Projects
None yet
3 participants
@huguangju
Owner

huguangju commented Jun 23, 2016

Angular 1.5 引入了 .component() 辅助方法, 它的定义比 .directive() 更简单。 .component() 允许开发者以更接近Angular2的方式写Angular1的代码,方便以后无痛地升级到Angular2。

.component().directive() 使用了更加简洁抽象的语法。

.directive().component()

以下是它们的语法差异:

// before
module.directive(name, fn);

// after
module.component(name, options);

先用Angular 1.4.x 写一个简单的计数器,下边再用 v1.5.0 重构它。

.directive('counter', function counter() {
  return {
    scope: {},
    bindToController: {
      count: '='
    },
    controller: function () {
      function increment() {
        this.count++;
      }
      function decrement() {
        this.count--;
      }
      this.increment = increment;
      this.decrement = decrement;
    },
    controllerAs: 'counter',
    template: [
      '<div class="todo">',
        '<input type="text" ng-model="counter.count">',
        '<button type="button" ng-click="counter.decrement();">-</button>',
        '<button type="button" ng-click="counter.increment();">+</button>',
      '</div>'
    ].join('')
  };
});

jsfiddle : https://jsfiddle.net/toddmotto/avdezer7/

方法名改变,并且Function 参数变为 Object

开始自上而下重构这个示例:

// before
.directive('counter', function counter() {
  return {

  };
});

// after
.component('counter', {

});

.directive 中本质上需要返回一个函数,而 .component 只需要传一个对象了

scopebindToController 变为 bindings

// before
.directive('counter', function counter() {
  return {
    // scope 用于创建独立作用域或继承父级作用域
    // 由于这个选项用来创建独立作用域基本上是不可或缺的,所以每次都要写就很繁琐了
    scope: {},
    // bindToController 可以直接定义那些想传入独立作用域的属性,并把它们绑定到controller上
    bindToController: {
      count: '='
    }
  };
});

// after
.component('counter', {
  // 用bindings可以简单地定义要传递哪些属性到component中,且并component拥有独立作用域
  bindings: {
    count: '='
  }
});

ControllercontrollerAs 的变化

在定义controller的方式上倒没有什么变化,唯一一点不同就是 controllerAs 多了一个默认值: $ctrl

在1.4中直接定义 controller

{
  ...
  controller: function () {}
  ...
}

或者在别的地方定义,此处引用

{
  ...
  controller: 'otherCtrl'
  ...
}

又或者用 controllerAs 起别名

{
  ...
  controller: 'otherCtrl'
  controllerAs: 'other'
  ...
}

然后就可以在模板中使用 other.prop之类来访问Controller实例。

.component() 中就不会那么麻烦了,在我们没明确指定Controller实例别名时,它会自动用3种方式创建 controllerAs 属性,Angular中相关源码如下:

controllerAs: identifierForController(options.controller) || options.controllerAs || '$ctrl',

其中第一个 identifierForController 会中controller属性为字符串时(controller: 'SomeCtrl as something'), 提取 as 后边的名字,源码如下:

var CNTRL_REG = /^(\S+)(\s+as\s+(\w+))?$/;
function identifierForController(controller, ident) {
  if (ident && isString(ident)) return ident;
  if (isString(controller)) {
    var match = CNTRL_REG.exec(controller);
    if (match) return match[3];
  }
}

第二个 controllerAs 用于当controller属性为function时的情况。

第三个 '$ctrl' 默认值让我们可以忽略掉 controllerAs

.component('test', {
  controller: function () {
    // 所以可以直接在模板中通过 $ctrl.testing访问
    this.testing = 123;
  }
});

说了那么多,终于可以在重构中把 controllerAs 干掉了:

// before
.directive('counter', function counter() {
  return {
    scope: {},
    bindToController: {
      count: '='
    },
    controller: function () {
      ...
    },
    controllerAs: 'counter'
  };
});

// after
.component('counter', {
  bindings: {
    count: '='
  },
  controller: function () {
    ...
  }
});

require 继承

{
  ...
  require: {
    parent: '^^parentComponent'
  },
  controller: function () {
    // 用 this.parent 访问依赖对象(在controller的parent属性上绑定)
    this.parent.foo();
  }
  ...
}

单向绑定

用新的表达式语法创建独立作用域:

{
  ...
  bindings: {
    oneWay: '<',
    twoWay: '='
  },
  ...
}

参见:One-way data-binding in Angular 1.5

Lifecycle hooks

Each component has a well-defined set of lifecycle hooks, read the full article here.
每个组件都预定义了一组 Lifecycle hooks(生命周期钩子):

  • $onInit
  • $postLink
  • $onChanges
  • $onDestroy

参见:Comprehensive dive into Angular 1.5 lifecycle hooks

关闭独立作用域

Component 始终都会创建独立作用域,相关源码部分如下:

{
  ...
  scope: {},
  ...
}

无状态的components

参见:Stateless Angular components

基本上我们可以只用 templatebindings:

var NameComponent = {
  bindings: {
    name: '<',
    age: '<'
  },
  template: [
    '<div>',
      '<p>Name: </p>',
      '<p>Age: </p>',
    '</div>'
  ].join('')
};

angular
  .module('app', [])
  .component('nameComponent', NameComponent);

源码比较

https://github.com/angular/angular.js/blob/v1.5.7/src/ng/compile.js#L1112-L1165

  this.component = function registerComponent(name, options) {
    var controller = options.controller || function() {};

    function factory($injector) {
      function makeInjectable(fn) {
        if (isFunction(fn) || isArray(fn)) {
          return function(tElement, tAttrs) {
            return $injector.invoke(fn, this, {$element: tElement, $attrs: tAttrs});
          };
        } else {
          return fn;
        }
      }

      var template = (!options.template && !options.templateUrl ? '' : options.template);
      var ddo = {
        controller: controller,
        controllerAs: identifierForController(options.controller) || options.controllerAs || '$ctrl',
        template: makeInjectable(template),
        templateUrl: makeInjectable(options.templateUrl),
        transclude: options.transclude,
        scope: {},
        bindToController: options.bindings || {},
        restrict: 'E',
        require: options.require
      };

      // Copy annotations (starting with $) over to the DDO
      forEach(options, function(val, key) {
        if (key.charAt(0) === '$') ddo[key] = val;
      });

      return ddo;
    }

    // TODO(pete) remove the following `forEach` before we release 1.6.0
    // The component-router@0.2.0 looks for the annotations on the controller constructor
    // Nothing in Angular looks for annotations on the factory function but we can't remove
    // it from 1.5.x yet.

    // Copy any annotation properties (starting with $) over to the factory and controller constructor functions
    // These could be used by libraries such as the new component router
    forEach(options, function(val, key) {
      if (key.charAt(0) === '$') {
        factory[key] = val;
        // Don't try to copy over annotations to named controller
        if (isFunction(controller)) controller[key] = val;
      }
    });

    factory.$inject = ['$injector'];

    return this.directive(name, factory);
  };

升级到Angular 2

用这种方式写组件很容易地升级到Angular 2。用 ECMAScript 5 和新模板语法写的示例:

var Counter = ng
.Component({
  selector: 'counter',
  template: [
    '<div class="todo">',
      '<input type="text" [(ng-model)]="count">',
      '<button type="button" (click)="decrement();">-</button>',
      '<button type="button" (click)="increment();">+</button>',
    '</div>'
  ].join('')
})
.Class({
  constructor: function () {
    this.count = 0;
  },
  increment: function () {
    this.count++;
  },
  decrement: function () {
    this.count--;
  }
});

原文
https://toddmotto.com/exploring-the-angular-1-5-component-method/

@usernameisMan

This comment has been minimized.

Show comment
Hide comment
@usernameisMan

usernameisMan Mar 15, 2017

不错不错 写得不错!

usernameisMan commented Mar 15, 2017

不错不错 写得不错!

@freewind

This comment has been minimized.

Show comment
Hide comment
@freewind

freewind Sep 2, 2018

在标题上写明是“翻译”可能会更好一些

freewind commented Sep 2, 2018

在标题上写明是“翻译”可能会更好一些

@huguangju huguangju changed the title from 探索Angular 1.5 之component() 方法 to [译] 探索Angular 1.5 之component() 方法 Sep 3, 2018

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment