Javascript 如何将设置对象从控制器传送到服务? tl;博士

Javascript 如何将设置对象从控制器传送到服务? tl;博士,javascript,angularjs,singleton,state,Javascript,Angularjs,Singleton,State,我需要在绑定到控制器范围的数据中传递多个服务所需的状态和源状态。一个好的“角度禅”方法是什么 背景故事 我正在开发一个单页应用程序,经过深思熟虑,我决定使用AngularJS。页面的布局方式类似于: 实际布局无关紧要,类似布局的概念保持不变。 我需要将绑定到SettingsController范围的信息传达给ngView中的控制器所需的服务。当用户修改任何滑块时,我还需要更新从控制器中的服务获得的内容 我试过的 我想到的唯一方法是:我必须自己编写一个绑定,并向作用域添加侦听器。我可能离这里很远

我需要在绑定到控制器范围的数据中传递多个服务所需的状态和源状态。一个好的“角度禅”方法是什么

背景故事 我正在开发一个单页应用程序,经过深思熟虑,我决定使用AngularJS。页面的布局方式类似于:

实际布局无关紧要,类似布局的概念保持不变。 我需要将绑定到
SettingsController
范围的信息传达给
ngView
中的控制器所需的服务。当用户修改任何滑块时,我还需要更新从控制器中的服务获得的内容

我试过的 我想到的唯一方法是:我必须自己编写一个绑定,并向作用域添加侦听器。我可能离这里很远,有一个明显的“角度”方式来做这件事——然而,尽管我努力了,我还是找不到它

/code from fiddle.
var app = angular.module("myApp",[]);

app.controller("HomeCtrl",function($scope,FooService,$interval){
    FooService.change(function(){
        console.log("HI",FooService.getFoo());
        $scope.foo = FooService.getFoo();
    });
});

app.factory("Configuration",function(){
    var config = {data:'lol'};
    var callbacks = [];
    return {
        list:function(){ return config;},
        update:function(){
            callbacks.forEach(function(x){ x();});
        },
        change:function(fn){
            callbacks.push(fn); // I never remove these, so this is a memory leak!
        }
    }
});
app.service("FooService",function(Configuration){
    return {
        getFoo: function(){
            return Configuration.list().data+" bar";    
        },change:function(fn){
            Configuration.change(fn);    
        }
    }
});
app.controller("SettingsCtrl",function($scope,Configuration){

    $scope.config = Configuration.list();
    $scope.$watch('config',function(){
        Configuration.update();
    },true);
});
我也考虑过
$rootScope
广播,但这似乎更像是一种全球性的状态

无论我怎么做,我都有一个全球状态的独生子女,我们都知道

因为这似乎是一个相当常见的角度用例。解决这个问题的惯用方法是什么?

您有两种选择:

  • 将共享服务与
    $watch
    结合使用。也就是说,你实施了什么,或者用不同的方式表达了什么
  • 使用
    $scope.$broadcast
    和/或
    $scope.$emit
    $scope.$on
    事件来传达更改
  • 就我个人而言,我认为选项(1)没有任何问题。我也不认为这是传统意义上的全球状态。您的配置包含在
    configuration
    服务中,您只能通过DI注入来访问此服务,从而使其可测试


    一个可能的改进是创建一个ConfigMediator服务,并将更新和回调功能移动到该服务中以分离关注点。

    我过去也遇到过类似的问题,并考虑了四种可能的方法:

    • 使用
      $broadcast
    • 将设置存储在
      $rootScope
    • 观察者模式(如此处所示)
    • 从控制器使用
      $watch
    以下是我对每一点的看法:

    $broadcast

    在我看到的AngularJS演示中,Miško Hevery谈到了
    $broadcast
    (即事件)的使用以及此类事件的用例。要点是,
    $broadcast
    更适合于对与您正在处理的内容不密切相关的事件作出反应,否则可能会有更好的替代方案。关于这一主题,angular wiki上的指南建议:

    仅对原子事件使用.$broadcast()、.$emit()和.$on():事件 与整个应用程序(如用户)全局相关的 验证或关闭应用程序)

    这里,由于您的设置与填充
    ng视图的内容密切相关,因此建议使用
    $broadcast
    的替代方案

    $rootScope

    正如你所提到的,这是一个全球性的状态(并且想要避免)。我个人也不喜欢在整个应用程序中公开设置,尽管这通常是一个简单的选择。我个人保留
    $rootScope
    用于配置设置和“软”变量,如页面标题等。我不会选择使用此选项

    观察者模式

    针对配置工厂注册回调是一种可靠的方法。关于持久回调,您可以监听作用域上的
    $destroy
    事件,在配置工厂上调用
    remove
    方法来删除回调。这可以被认为是如何使用
    $broadcast
    的一个好例子;控制器与事件有关,必须对其作出反应,但事件本身并不特定于控制器/配置服务共享的数据

    $watch

    通过使用共享服务,可以将其注入与设置相关的任何控制器中。现在,对配置的任何更改都将触发回调,而某些视图可能只涉及一个或两个配置设置<代码>$watch
    将允许您更轻松地观察仅对这些属性的更改。我不能谈论开销和注册回调,但这对我来说是最“有棱角”的方式

    这是如何使用
    $watch
    实现的:

    var app = angular.module("myApp",[]);
    
    app.factory("Configuration",function(){
       var data = {
         settingOne: true,
         settingTwo: false 
       };
       return data;
    })
    
    app.controller("SettingsCtrl",function($scope, Configuration){
      // do something
    })
    
    app.controller("HomeCtrl",function($scope, Configuration){
       // detect any change to configuration settings
       $scope.$watch(function() {
         return Configuration;
       }, function(data) {
         // do something
       }, true)
    
       // alternatively only react to settingTwo changing
       $scope.$watch(function() {
         return Configuration.settingTwo
       }, function(data) {
         // do something
       })
    })
    
    请注意,如果您需要稍微复杂一点的配置工厂,您可以转而使用getter/setter方法,并保持配置设置本身的私有性。然后,在
    $watch
    中,您应该查看方法调用,而不是属性本身

    更新:

    在回答问题时,我更喜欢控制器内的
    $watch
    方法。在使用该框架开发了一段时间后,我现在尝试将
    $watch
    完全排除在控制器之外,而在可能的情况下,更倾向于在值发生变化时直接调用函数,或者通过利用
    ng change

    这样做的一个原因是它增加了测试控制器的复杂性,但可能更为复杂,因此效率低下:对于每个
    $digest
    周期角度调用,每个注册的
    $watch
    都将被评估,而不管如何,它很可能是对使用现有
    $watch
    的值所做的更改做出响应

    这里有一篇关于这个问题的非常好的文章,而不是从这个角度总结缺点和解决方案:

    更新:) 我做了一个演示:

    app.factory('FooService', function($rootScope, Configuration){ $scope = $rootScope.$new(); $scope.Configuration = Configuration; var foo = { data : null }; $scope.$watch('Configuration', function(config){ foo.data = // do something with config }, true) return foo; }); app.factory('Configuration', function(){ return { data : 'lol' } }); app.controller('SettingsCtrl', function($scope, Configuration){ $scope.config = Configuration; }); app.controller("HomeCtrl",function($scope, FooService){ $scope.foo = FooService; });
    app.factory('Configuration', function($rootScope){
      return {
        var config = {
          user: "xxxx"
        }
    
        return {
          config: config,
    
          set: function(item, value) {
            config[item] = value;
            $rootScope.$emit("configChanged." + item);
          },
    
          changed: function(item, callback, scope) {
            var deregister = $rootScope.$on("configChanged." + item, function() {
              callback(config[item], config)
            });
    
            callback(config[item], config);
    
            if (scope) {
              scope.$on("$destroy", deregister);
            }
          }
        }
      }
    });
    
    app.controller('SettingsCtrl', function($scope, $timeout, Configuration){
      // Get a local copy - configuration shouldn't change until change 
      // is completed
      $scope.data = angular.copy(Configuration.config);
    
      // Keep UI interactions in the controller
      // If more complex UI is required another way could even use a 
      // directive for this 
      $scope.$watch("data.user", function(user) {
          Configuration.set('user', $scope.data.user);
      });
    });
    
    app.factory('DetailsService', function(Configuration, $http){
    
      var details = {
        data : null,
      };
    
      Configuration.changed("user", function(user) {
        // Handle user change ... 
      });
    });