控制器间通信,角度方式

Posted

技术标签:

【中文标题】控制器间通信,角度方式【英文标题】:Inter-Controller communication, the angular way 【发布时间】:2015-03-16 03:42:27 【问题描述】:

我试图找出控制器/指令之间共享属性或状态的“首选”或“角度方式”。有几种方法可以实现这一点,但我想保持最佳实践。以下是一些关于如何实施的平庸示例:


1.使用 $scope.$watch

// The parent controller/scope
angular.module('myModule').controller('parentController', ['$scope', function($scope) 
    $scope.state = 
        myProperty: 'someState'; // Default value to be changed by some DOM element
    ;
]);

// The child controller/scope.
angular.module('myModule').controller('childController', ['$scope', function($scope) 
    $scope.$watch('state.myProperty', function (newVal) 
        // Do some action here on state change
    );
]);

编辑:根据下面的答案,这是不好的做法,应该避免。它是不可测试的,并且会产生不必要的 DOM 依赖。


2。使用 $broadcast

// The parent controller
angular.module('myModule').controller('parentController', ['$scope', function($scope) 
    var myProperty = 'someState';
    $scope.setState = function (state) 
        myProperty = state; // Set by some other controller action or DOM interaction.
        $scope.$broadcast('stateChanged', state); // Communicate changes to child controller
    
]);

// The child controller.
angular.module('myModule').controller('childController', ['$scope', function($scope) 
    $scope.$on('stateChanged', function (evt, state) 
        // Do some action here
    
]);

编辑:同样不好的做法,因为您需要知道控制器在 DOM 中的位置,以便确定使用 $broadcast(DOM 下方)或 $emit(DOM 上方)的天气.


3.使用服务

angular.module('myModule').factory('stateContainer', [function () 
    var state = 
            myProperty: 'defaultState'
        ,
        listeners = [];

    return 
        setState: function (newState) 
            state.myProperty = newState;
            angular.forEach(listeners, function (listener) 
                listener(newState);
            );
        ,
        addListener: function (listener) 
            listeners.push(listener);
        
    
]);

// The parent controller
angular.module('myModule').controller('parentController', ['$scope', 'stateContainer', function($scope, stateContainer) 
    $scope.setState = function (state) 
        stateContainer.setState(state);
    ;
]);

// The child controller.
angular.module('myModule').controller('childController', ['$scope', 'stateContainer', function($scope, stateContainer) 
    stateContainer.addListener(function (newState) 
        // Do some action here
    );
]);

我可能在这里错过了一些方法,但你明白了。我正在尝试找到 最佳 方法。虽然冗长,但我个人倾向于此处列表中的#3。但我来自广泛使用侦听器的 Java 和 jQuery 背景。

编辑:下面的答案很有见地。有人谈到使用require 指令配置在父/子指令之间共享状态。另一个讨论直接向范围共享服务或服务属性。我相信根据需要,它们在 Angular 的最佳实践中是正确的还是不是最佳实践。

【问题讨论】:

【参考方案1】:

如果正确完成,其中任何一个都可以工作,但服务的变体是 AFAIK 的首选方式。

问题是,您甚至需要在服务案例中使用侦听器吗? Angular 本身会更新任何视图(这是控制器的目的),那么为什么需要监听器或监视呢?改变视图本身的值就足够了。

app.factory('stateService',function() 
  return 
     myState: "foo"
  
)
.controller('one',function($scope,stateService) 
    $scope.changeState = function() 
      stateService.myState = $scope.state;
    ;
)
.controller('two',function($scope,stateService) 
    $scope.svc = stateService;
)

然后您可以在您的视图中执行以下操作(不完整):

<div ng-controller="one">
  <input name="state" ng-model="state"></input>
  <button type="submit" ng-click="changeState()">Submit</button>
</div>
<div ng-controller="two">svc.myState</div>

事实是,您甚至不需要为拥有一个按钮和一个功能而走那么远。如果您只是将ng-model 绑定在一起,它将起作用:

<div ng-controller="one">
  <input name="state" ng-model="svc.myState"></input>
</div>
<div ng-controller="two">svc.myState</div>

试试下面的jsfiddle http://jsfiddle.net/cwt9L6vn/1/

【讨论】:

没有想到将整个服务应用到范围。好一个! :-) 这被称为 范围出血,这是一种非常糟糕的做法。 那么@MaximeMorin,服务中的访问者你有什么建议?然后我又回到让监听器在每次属性更改时触发。 我相信视图不应该知道数据来自哪里。在这个例子中,视图知道服务。我认为控制器应该保持状态,然后视图将只使用控制器。在视图中:ctrl.myState(使用 ControllerAs 语法)在 Ctrl 中:ctrl.myState = myService.myState; 这样,控制器可以决定保留状态副本或原始引用。这为保存/取消、验证检查等打开了大门…… 是的,所有评论者都是 100% 正确的;将服务直接暴露给视图是不好的做法。这只是一个简单的例子,表明它有效,但你是对的。但是,请小心使用 Oystein 的示例,因为如果它只是文本,您会失去一些观看。一般来说,您希望您的视图正在查看对象,这就是我选择最简单路径的原因。我会在上面更新它,但要注意“我需要一个点”nathanleclaire.com/blog/2014/04/19/…【参考方案2】:

AngularJS 中没有父子控制器之类的东西。只有父指令和子指令,但没有控制器。指令可以有一个控制器,它作为 API 公开给其他指令。

控制器与 DOM 层次结构无关,因此它们不能有子级。他们也不创建自己的范围。所以你永远不知道是否必须$broadcast$emit 才能与其他控制器交谈。

如果您从一个控制器开始使用$broadcast,那么您将无法知道另一个控制器是up 还是down。这就是人们开始做$rootScope.$broadcast(..) 之类的事情的时候,这是一种非常糟糕的做法

您要查找的是 require 其他指令的指令。

var app = angular.modeul('myApp',[]);
// use a directive to define a parent controller
app.directive('parentDir',function() 
     return 
         controller: function($scope) 
             this.myFoo = function() 
                alert("Hello World");
             
         
     );
// use a directive to enforce parent-child relationship
app.directive('childDir',function() 
     return 
          require: '^parentDir',
          link: function($scope, $el, $attr, parentCtrl) 
             // call the parent controller
             parentCtrl.myFoo();
          
     );

使用指令的require 特性有两件重要的事情。

    如果关系不是可选的,Angular 将强制执行。 父控制器被注入到子链接函数中。

无需$broadcast$emit

另一个同样有效的选项是使用指令来公开 API。

// this directive uses an API
app.directive('myDir',function() 
     return 
          scope:  
            'foo': '&'
          ,
          link: function($scope, $el, $attr) 
             // when needed, call the API
             $scope.foo();
          
     );

 // in the template
 <div ng-controller="parentController">
     <div my-dir foo="parentController.callMyMethod();"></div>
 </div>

【讨论】:

我意识到我在这里松散地使用了术语父/子“控制器”。我的意思是父/子范围。我问的原因是我想在不同的角度对象之间共享应用程序状态。我理解对上述示例 #1 的反对意见,但这不仅适用于指令(Web 组件),因为应用程序基础架构不仅仅包含指令。 ...但是 +1 用于对象间通信的附加选项。 :-) 关键是尽可能地保持对象之间的解耦。这提高了测试和可维护性。当控制器或指令依赖于 $scope 的状态由一些 magical 外部影响维持,或者业务逻辑仅在 $scope 接收到来自未知广播者的事件时执行,那么你会发现将来必须更改、测试和维护该代码。 我完全同意,如果你所说的“神奇的外力”是指 $scope.$parent 或 $broadcast/$emit。但是指令(恕我直言)是 small 可重用的组件。不是整页。我认为即使是指令也可以安全地将状态存储在服务中以供其他组件重用。这应该是可测试的,就好像指令是用自己的状态存储分层设计的一样。 我认为我们彼此同意,但使用不同的术语,所以听起来我们在谈论不同的事情。

以上是关于控制器间通信,角度方式的主要内容,如果未能解决你的问题,请参考以下文章

Linux入门进程间的通信

Linux入门进程间的通信

Linux入门进程间的通信

深入浅出Binder进程间通信机制

angularjs控制器之间通信,事件通知服务

Qt例子,线程间通信,如何在线程外部对线程进行控制,问题请看问题补充,多谢了先