在不重新连接控制器的情况下从resolve函数访问数据

时间:2016-07-14 15:01:57

标签: angularjs angular-ui-router

我们如何在不关闭控制器的情况下从resolve函数访问数据?

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

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

目标是不对子控制器(和视图)进行自动加载,在选择不同的父元素时,我们设置notify:false

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

我们做了一个小傻瓜来证明我们的问题here

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

3 个答案:

答案 0 :(得分:2)

基于使用特殊服务的sielakos答案,我提出了这个解决方案。 首先,我需要一个额外的服务,保持resovle数据的参考。

<强>服务

.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提供观看函数返回值的功能。

以下是一些Q&amp; D示例:

.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

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

答案 1 :(得分:1)

不太好,但工作解决方案是使用事件。好吧,也许它不是那么糟糕,至少它并不复杂。 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创建函数更改为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;
  }
}

答案 2 :(得分:1)

1)对于当前任务,不需要ng-view(恕我直言)。如果您需要两个不同的范围,则重新设计ng-views以使用自己的控制器成为指令。这将阻止角度重新加载

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

P.S。不要忘记angularjs-best-practices/style-guide