AngularJS:我应该将指令的链接功能转换为控制器吗?

时间:2015-01-05 00:17:19

标签: angularjs angularjs-directive angularjs-controller

我听说在使用隔离范围的指令中使用controllerAs语法和bindToController: true是一个好习惯。参考文献:onetwo

假设我有这样的指示:

angular.module('MyModule').directive('MyDirective', function(User) {
  return {
    scope: {
      name: '='
    },
    templateUrl: 'my-template.html',
    link: function(scope) {
      scope.User = User;
      scope.doSomething = function() {
        // Do something cool
      };
    }
  };
});
<!-- my-template.html -->
<div>
  User Id: {{ User.id }}
  Name: {{ name }}
  <button ng-click="doSomething()">Do it</button>
</div>

如您所见,此指令中没有控制器。但是,为了能够利用controllerAsbindToController: true,我必须拥有一个控制器。

将链接功能转换为控制器是最佳做法吗?

angular.module('MyModule').directive('MyDirective', function(User) {
  return {
    scope: {
      name: '='
    },
    templateUrl: 'my-template.html',
    bindToController: true,
    controllerAs: 'myCtrl',
    controller: function() {
      this.User = User;
      this.doSomething = function() {
        // Do something cool
      };
    }
  };
});
<!-- my-template.html -->
<div>
  User Id: {{ myCtrl.User.id }}
  Name: {{ myCtrl.name }}
  <button ng-click="myCtrl.doSomething()">Do it</button>
</div>

我的理解是指令的控制器应该被用作公开指令API的机制,用于指令到指令的通信。

如果考虑到Angular 2.0,有没有人能够了解最近的最佳做法?

5 个答案:

答案 0 :(得分:14)

我认为最好的做法是在指令的控制器中移动初始化代码和/或公开API函数,因为它有两个目的:

1. Intialization of $scope 
2. Exposing an API for communication between directives

范围的初始化

假设您的指令定义了子范围(或继承范围)。如果初始化链接函数内部的作用域,则子作用域将无法通过作用域继承访问此处定义的任何作用域变量。这是因为父链接功能总是在子链接功能之后执行。因此,范围初始化的适当位置在控制器功能内部。

公开Controller API

子指令可以通过指令定义对象上的'require'属性访问父指令的控制器。这允许指令进行通信。为了使其工作,必须完全定义父控制器,以便可以从子指令的链接函数访问它。实现这一点的最佳位置是控制器功能本身的定义。父控制器函数总是在子控制器函数之前调用。

最后的想法

重要的是要理解链接功能和控制器功能有两个非常不同的用途。控制器功能是为初始化和指令通信而设计的,链接器功能是为运行时行为而设计的。根据代码的意图,您应该能够确定它是属于控制器,还是属于链接器。

您是否应该将任何初始化范围的代码从链接功能移动到控制器功能?

是的,这是控制器功能存在的主要原因之一:初始化范围,并允许其范围参与原型范围继承。

您应该将$ watch处理程序从链接功能移动到控制器功能吗?

没有。链接函数的目的是连接行为并可能操纵DOM。在link函数中,所有指令都已编译,并且所有子链接函数都已执行。这使得它成为一个理想的连接行为的地方,因为它尽可能接近DOM准备就绪(直到渲染阶段之后才真正准备好DOM)。

答案 1 :(得分:3)

我将从你的最后一句话开始。这就是你想要如何编写角度代码的全部内容。如果你想坚持为角度1.x编写好的代码的指南,那么甚至不用太在意什么是理想的。但是,如果您想为下一版本的Angular以及即将推出的Web技术做准备,我建议您开始采用新概念并根据您今天编写代码的方式进行调整。在这种情况下,请记住没有对错。

谈到angular 2.0和ES6,我想强调指令的概念将更符合Web Components技术。

在Angular 2.0中(根据当前的设计)将摆脱定义指令的复杂方式;那不再是DDO。因此,如果你开始以这种方式思考,我认为会更好。组件只有一个View和一个控制器。

例如,

@ComponentDirective({
    selector:'carousel',
    directives:[NgRepeat]
})
export class Carousel{  
    constructor(panes:Query<CarouselItem>) {
        this.items= panes;
    }

    select(selectedCarouselItem:CarouselItem) { ... }
}

上面的代码是用AtScript(打字稿和ES6的超集)编写的,但你也可以在ES5中表达同样的东西。你可以看到事情会变得多么简单。在np这样的概念,如链接功能或编译等。

另外,上述组件的视图将直接绑定到上述类;因此,您已经可以找到与controllerAs语法的相似性。

所以从本质上讲,我建议你先看看Web Components背后的一般思路,以及Web Developments的未来如何,然后我想你会开始编写Angular 1.x代码。 。

总之,尝试以有利于Angular当前版本的方式编写代码,但是如果您认为代码的某些部分可以包含下一版本的某些概念,那么请执行此操作。我不相信它会伤害你。尽量保持简单,因为新版本的Angular会更简单。

我建议您阅读以下帖子:

  1. https://www.airpair.com/angularjs/posts/component-based-angularjs-directives
  2. http://eisenbergeffect.bluespire.com/all-about-angular-2-0/
  3. https://www.airpair.com/angularjs/posts/preparing-for-the-future-of-angularjs
  4. http://teropa.info/blog/2014/10/24/how-ive-improved-my-angular-apps-by-banning-ng-controller.html

答案 2 :(得分:2)

更新

(在底部我添加了一个显示方法的代码/ plnkr)

除了你提到的文章:https://www.airpair.com/angularjs/posts/preparing-for-the-future-of-angularjs#3-3-match-controllers-with-directives,它基本上不仅提倡你要求的模式,而且基于组件的前端,我发现:http://joelhooks.com/blog/2014/02/11/lets-make-full-ass-angularjs-directives/(它提倡< em>最少使用链接函数并使用ui-bootstrap作为使用此类模式的示例)。我不能同意这两篇文章。

关于Angular2.0的另一件事:在angular2.0中不再有$scope - https://www.youtube.com/watch?v=gNmWybAyBHI&t=12m14s,所以如果你能尽可能地摆脱$scope,那么过渡应该更顺畅。

我也犯了一个小错误:

  

不过,我更喜欢在controller中定义所有功能而只是调用   他们通过link的范围。理想情况下,这只是一个电话:    scope.init ctrl.init(/*args*/)(其中ctrl是   指令的控制者)。


在某种程度上,这是一个品味问题,但有一些正当理由可以使link功能保持尽可能薄:

  1. 链接功能中的逻辑不易测试。当然,您可以在单元测试中编译该指令并测试其行为,但链接函数本身就是一个黑盒子。

  2. 如果你必须使用controller(比如指令间通信),那么最终会有两个地方放置你的代码。这很令人困惑,但是如果您决定让link函数变薄,那么controller中可放入的所有内容都应该放在controller中。

  3. 您无法直接向link函数注入其他依赖项(您仍然可以使用注入主指令函数的函数)。在controller方法的情况下,没有这样的问题。为什么重要:

    • 通过使依赖关系更接近需要它们的上下文来保持更好的代码结构
    • 非JS背景的角色仍然存在功能关闭如何在JS中起作用的问题
  4. 那么必须在链接功能中添加什么:

    1. 将元素插入DOM后需要运行的所有内容。如果$element暴露$on('linked')事件,则基本上此点无效。
    2. 抓取对控制器require:的引用。再次,如果可以将它们直接注入controller ......
    3. 不过,我更喜欢在controller中定义所有函数,只需通过link的范围调用它们。理想情况下,这只是一个电话:scope.init


      Misko Hevery曾多次告诉他,DDO远非完美且易于理解,而且它已经发展到现在的状态。我很确定,如果设计决策是预先制定的,那么就可以在一个地方放置指令的逻辑 - 就像在angular2.0中一样。


      现在回答您的问题是否应将link功能转换为controller。它确实取决于许多标准,但如果代码是积极开发的,那么可能值得考虑。我的经历(以及我谈过的几个人)可以用这张图片来说明:The Link Mess Zenith

      关于angular2.0 - 它将是一个构造转变,所以从这个角度看它应该没什么关系,但controller的方法似乎更接近于指令/组件的方式将通过ES6课程在v2.0中声明。

      最后一件事:在某种程度上,这是一个品味问题,但有一些正当理由让 CONTROLLER 功能变薄(通过将逻辑委托给服务) )。


      更新 - PLNKR

      PLNKR举例说明了这种方法:

      <强> HTML

      <input ng-model="data.name"/>
      
      <top-directive>
        <my-directive my-config="data">
      
        </my-directive>
      </top-directive>
      

      <强> JS

      var app = angular.module('plunker', []);
      
      app.controller('MainCtrl', function($scope) {
        $scope.data = { name : 'Hello, World'};
      });
      
      app.controller('MyCtrl', function($scope){
      
        var self = this;
      
        this.init = function(top){
          this.topCtrl = top;
          this.getTopName = top.getName.bind(top);
          this.getConfigName = function(){return this.config.name}; 
          console.log('initilizing', this, $scope, this.getConfigName, this.getTopName());
        }
      
        // if you want to $watch you have to inject $scope
        // you have access to the controller via name defined 
        // in contollerAs
        $scope.$watch('myCtrl.config', function(){
          console.log('config changed', self.getConfigName());
        }, true);
      });
      
      app.directive('topDirective', function(){
      
        return {
          controller : function(){
            this.name = "Hello, Top World";
            this.getName = function(){return this.name};
          }
        }
      
      });
      
      app.directive('myDirective', function(){
      
        return {
      
          require: ['myDirective', '^topDirective'],
      
          controller : 'MyCtrl',
          bindToController: true,
          controllerAs: 'myCtrl',
      
          template : '{{myCtrl.getConfigName() + " --- " + myCtrl.getTopName()}} ',
      
          scope : {
           config : "=myConfig",
          },
      
          link : function(scope, element, attrs,  Ctrls){
            Ctrls[0].init(Ctrls[1]);
          }
        }
      
      });
      

答案 3 :(得分:-1)

根据最新的documentation,这仍然是推荐的做法&#34;当您想要将API暴露给其他指令时使用控制器。否则使用链接。&#34;我也想听听别人和他们使用的方法。

答案 4 :(得分:-1)

分享here的内容,(我没有足够的声誉将其作为评论)

“where do I put code, in ‘controller’ or ‘link’?”

  • 编译前? - 控制器
  • 编译后? - 链接

Couple of things to note:

  1. 控制器'$ scope'和链接'scope'是一回事。不同之处在于发送给控制器的参数通过依赖注入来实现(因此需要调用'$ scope'),其中发送到链接的参数是基于标准顺序的函数。在上下文中,所有角度示例都将使用“范围”,但出于理智原因,我通常将其称为范围:http://plnkr.co/edit/lqcoJj?p=preview

  2. 此示例中的$ scope / scope只是从父控制器传入的范围。

  3. 指令中的
  4. 'link'实际上是'post-link'函数(参见下面的渲染管道)。由于很少使用预链接,因此“链接”选项只是设置“后链接”功能的快捷方式。

  5. 那么,最新的例子是什么?好吧,当我决定的时候,我会这样做:

    • “我只是做模板和范围的事情吗?” - 进入控制器
    • “我是否添加了一些coolbeans jquery库?” - 进入链接

    答案归功于 jasonmore