【问题标题】:access the data from the resolve function without relading the controller从解析函数访问数据而不重新加载控制器
【发布时间】:2016-07-14 15:01:57
【问题描述】:

我们如何在不重新加载控制器的情况下从解析函数访问数据?

我们目前正在开发一个使用angular-ui-router 的项目。 我们有两个独立的视图:左侧是父元素列表,右侧是子数据元素。

如果选择左侧的父级,我们将其子数据解析为右侧的子视图。

在选择其他父元素时不要导入父母控制器(和视图),我们在notify:false

我们设法在不重新加载控制器和视图的情况下“重新解析”子控制器数据,但数据(范围)不会刷新。

我们做了一个小程序来演示我们的问题here

首先单击一个数字以实例化控制器childCtrl。每次单击都应更改子范围数据 - 这不起作用。 您可能会注意到alert 输出已经包含我们要显示的刷新数据。

【问题讨论】:

    标签: angularjs angular-ui-router


    【解决方案1】:

    基于使用特殊服务的 sielakos 回答,我想出了这个解决方案。 首先,我需要一个额外的服务来保存来自解析的数据的引用。

    服务

    .service('dataLink', function () {
      var storage = null;
    
      function setData(data) {
          storage = data;
      }
    
      function getData() {
          return storage;
      }
    
      return {
          setData: setData,
          getData: getData
      };
    })
    

    好吧,我必须像这样在我的解析功能中使用该服务

    解析功能

    resolve: {
        detailResolver: function($http, $stateParams, dataLink) {
            return $http.get('file' + $stateParams.id + '.json')
                .then(function(response) {
                    alert('response ' + response.data.id);
                    dataLink.setData(response.data);
                    return response.data;
                });
        }
    }
    

    注意dataLink.setData(response.data); 这一行。它将数据保留在服务中的解析中,以便我可以从控制器中访问它。

    控制器

    我稍微修改了控制器。我将所有初始化内容包装在一个函数中,当数据更改时我可以执行。 第二件事是看dataLink.getData();的返回值

    截至https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$watch $scope.$watch 提供监视函数返回值的功能。

    这是一些问答示例:

    .controller('childCtrl', function($scope, $log, detailResolver, $interval, dataLink) {
        initialise();
        /*
        * some stuff happens here
        */
    
        $interval(function() {
            console.log(detailResolver.id)
        }, 1000);
    
        $scope.$watch(dataLink.getData, function(newData) {
            detailResolver = newData;
            initialise();
        });
    
        function initialise() {
            $log.info('childCtrl detailResolver.id == ' + detailResolver);
            $scope.id = detailResolver;
        }
    })
    

    $scope.$watch(dataLink.getData, function(newData) { ... }); 行可以解决问题。每次 dataLink 服务中的数据更改时,回调就会启动并用新数据替换旧数据。 我已经创建了一个 plunker,所以你可以试试https://plnkr.co/edit/xyZKQgENrwd4uEwS9QIM

    使用此解决方案您不必担心内存泄漏,因为 Angular 会自动删除观察者。请参阅https://stackoverflow.com/a/25114028/6460149 了解更多信息。

    【讨论】:

      【解决方案2】:

      不太漂亮,但可行的解决方案是使用事件。好吧,也许它并没有那么糟糕,至少它并不复杂。 https://plnkr.co/edit/SNRFhaudhsWLKUNMFos6?p=preview

      angular.module('app',[
          'ui.router'
        ])
        .config(function($stateProvider) {
          $stateProvider.state('parent', {
            views:{
              'parent':{
                controller: 'parentCtrl',
                template: '<div id="parent">'+
                  '<button ng-click="go(1)">1</button><br>'+
                  '<button ng-click="go(2)">2</button><br>'+
                  '<button ng-click="go(3)">3</button><br>'+
                '</div>'
              },
            },
            url: ''
          });
      
      
          $stateProvider.state('parent.child', {
            views:{
              'child@':{
                controller: 'childCtrl',
                template:'<b>{{ id }}</b>'
              }
            },
            url: '/:id/child',
            resolve: {
              detailResolver: function($http, $stateParams, $rootScope) {
                return $http.get('file'+$stateParams.id+'.json')                
                  .then(function(response) {
                    alert('response ' + response.data.id);
      
                    $rootScope.$broadcast('newData', response.data);
      
                    return response.data;
                  });
              }
            }
          });
        })
        .controller('parentCtrl', function ($log, $scope, $state) {
          $log.info('parentCtrl');
          var notify = true;
          $scope.go = function (id) {
            $state.go('parent.child', {id: id}, {notify:notify});
            notify = false;
          };
        })
        .controller('childCtrl', function ($scope, $log, detailResolver, $interval) {
          /*
           * some stuff happens here
           */
      
          $log.info('childCtrl detailResolver.id == ' + detailResolver);
      
          $scope.$on('newData', function (event, detailResolver) {
            $scope.id = detailResolver;
          });
      
          $scope.id = detailResolver;
          $interval(function(){
            console.log(detailResolver.id)
          },1000)
        })
      ;
      

      编辑: 稍微复杂一点的解决方案,需要将 promise creator 函数更改为 observables,但可以: https://plnkr.co/edit/1j1BCGvUXjtv3WhYN84T?p=preview

      angular.module('app', [
          'ui.router'
        ])
        .config(function($stateProvider) {
          $stateProvider.state('parent', {
            views: {
              'parent': {
                controller: 'parentCtrl',
                template: '<div id="parent">' +
                  '<button ng-click="go(1)">1</button><br>' +
                  '<button ng-click="go(2)">2</button><br>' +
                  '<button ng-click="go(3)">3</button><br>' +
                  '</div>'
              },
            },
            url: ''
          });
      
      
          $stateProvider.state('parent.child', {
            views: {
              'child@': {
                controller: 'childCtrl',
                template: '<b>{{ id }}</b>'
              }
            },
            url: '/:id/child',
            resolve: {
              detailResolver: turnToObservable(['$http', '$stateParams', function($http, $stateParams) { //Have to be decorated either be this or $inject
                return $http.get('file' + $stateParams.id + '.json')
                  .then(function(response) {
                    alert('response ' + response.data.id);
                    return response.data;
                  });
              }])
            }
          });
        })
        .controller('parentCtrl', function($log, $scope, $state) {
          $log.info('parentCtrl');
          var notify = true;
          $scope.go = function(id) {
            $state.go('parent.child', {id: id}, {notify: notify});
            notify = false;
          };
        })
        .controller('childCtrl', function($scope, $log, detailResolver, $interval) {
          /*
           * some stuff happens here
           */
      
          $log.info('childCtrl detailResolver.id == ' + detailResolver);
      
          detailResolver.addListener(function (id) {
            $scope.id = id;
          });
        });
      
      function turnToObservable(promiseMaker) {
        var promiseFn = extractPromiseFn(promiseMaker);
        var listeners = [];
      
        function addListener(listener) {
          listeners.push(listener);
      
          return function() {
            listeners = listeners.filter(function(other) {
              other !== listener;
            });
          }
        }
      
        function fireListeners(result) {
          listeners.forEach(function(listener) {
            listener(result);
          });
        }
      
        function createObservable() {
          promiseFn.apply(null, arguments).then(fireListeners);
      
          return {
            addListener: addListener
          };
        }
      
        createObservable.$inject = promiseFn.$inject;
      
        return createObservable;
      }
      
      function extractPromiseFn(promiseMaker) {
        if (angular.isFunction(promiseMaker)) {
          return promiseMaker;
        }
      
        if (angular.isArray(promiseMaker)) {
          var promiseFn = promiseMaker[promiseMaker.length - 1];
          promiseFn.$inject = promiseMaker.slice(0, promiseMaker.length - 1);
      
          return promiseFn;
        }
      }
      

      【讨论】:

      • 当然,使用全局事件是可行的。这将是我们的plan-b。我希望找到一个更优雅的灵魂。我会再等一段时间,希望有人能提供一个解决方案,而不是通过 rootScope 发送垃圾邮件。
      • @nilsK 这是一个相当有趣的问题,所以我提出了解决这个问题的其他方法,它需要将返回承诺的函数更改为返回可观察对象的函数,但它可以工作。虽然它依赖于一些全局可用的函数,但如果你愿意的话,你可以将它们破解为提供者。
      • 这仍然有点老套,需要修饰函数,但这样做很有趣。
      • 我喜欢带有承诺的第二种方法。但我不确定,我将如何取消所有听众?解决(子)状态将发生很大变化,用户将在浏览器窗口打开的情况下工作数小时。我害怕内存泄漏。
      • @nilsK 好吧,这是此解决方案的问题之一,您必须在 $scope.$on('$destroy' 上手动取消所有侦听器。但这与直接取消注册与$rootScope 一起使用的侦听器没有什么不同。您也可以尝试修改 turnToObservable 函数,以便在状态更改时销毁所有侦听器。这可能是可能的,虽然很棘手。
      【解决方案3】:

      1) 对于当前任务,不需要 ng-view (恕我直言)。如果您需要两个不同的范围,那么重新设计 ng-views 使其成为具有自己控制器的指令。这将防止 Angular 重新加载它们

      2) 如果您需要在作用域之间共享数据,则可以使用服务来存储数据(请参阅the following code 中的 helperService)

      3) 如果我们谈论当前的代码简化,那么可以这样做:使用 2) 中的服务并只使用一个控制器:

      (function() {
        angular.module('app',[
          'ui.router'
        ]);
      })();
      
      (function() {
        angular
          .module('app')
          .service('helperService', helperService);
      
        helperService.$inject = ['$http', '$log'];
        function helperService($http, $log) {
          var vm = this;
      
          $log.info('helperService');
      
          vm.data = {
            id: 0
          };
          vm.id = 0;
          vm.loadData = loadData;
      
          function loadData(id) {
            vm.id = id;
      
            $http
              .get('file'+id+'.json')
              .then(function(response) {
                alert('response ' + response.data.id);
                vm.data = response.data;
              });
          }
        }
      })();
      
      (function() {
        angular
          .module('app')
          .controller('AppController', ParentController);
      
        ParentController.$inject = ['helperService', '$log'];
        function ParentController(helperService, $log) {
          var vm = this;
      
          $log.info('AppController');
      
          vm.helper = helperService;
        }
      })();
      

      4) 也不需要间隔、观看、广播等

      完整代码在这里:plunker

      附:不要忘记angularjs-best-practices/style-guide

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2023-03-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多