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

时间:2021-12-12 10:23:22

How do we access the data from the resolve function without relading the controller?

如何在不重置控制器的情况下从解析功能访问数据?

We are currently working on a project which uses angular-ui-router. We have two seperated views: on the left a list of parent elements, on the right that elements child data.

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

If selecting a parent on the left, we resolve it's child data to the child-view on the right.

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

With the goal not to reaload the childs controller (and view), when selecting a different parent element, we set notify:false.

为了不实现子控制器(和视图)的目标,在选择不同的父元素时,我们设置notify:false。

We managed to 're-resolve' the child controllers data while not reloading the controller and view, but the data (scope) won't refresh.

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

We did a small plunker to demonstrate our problem here

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

First click on a number to instantiate the controllers childCtrl. Every following click should change the child scopes data - which does not work. You might notice the alert output already has the refreshed data we want to display.

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

3 个解决方案

#1


1  

Not so pretty, but working solution would be to use events. Well, maybe it is not that bad, at least it is not complicated. https://plnkr.co/edit/SNRFhaudhsWLKUNMFos6?p=preview

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

EDIT: A little bit more complicated solution, that requires changing promise creator function into observables, but works: https://plnkr.co/edit/1j1BCGvUXjtv3WhYN84T?p=preview

编辑:一个更复杂的解决方案,需要将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


2  

Based on sielakos answer using an special service i came up with this solution. First, i need a additional service which keeps a reference of the data from the resovle.

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

Service

.service('dataLink', function () {
  var storage = null;

  function setData(data) {
      storage = data;
  }

  function getData() {
      return storage;
  }

  return {
      setData: setData,
      getData: getData
  };
})

Well, i have to use the service in my resolve function like so

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

Resolve function

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;
            });
    }
}

Notice the line dataLink.setData(response.data);. It keeps the data from the resolve in the service so I can access it from within the controller.

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

Controller

I modified the controller a little. I wrapped all the initialisation suff in an function i can execute when the data changes. The second thing is to watch the return value of the dataLink.getData();

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

As of https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$watch $scope.$watch provides functionality to watch return values of functions.

从https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$watch $ scope开始。$ watch提供了观察函数返回值的功能。

Here is some Q&D example:

这是一些Q&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;
    }
})

The line $scope.$watch(dataLink.getData, function(newData) { ... }); does the trick. Every time the data in the dataLink service changes the callback kicks in and replaces the old data with the new one. Ive created a plunker so you can give it a try https://plnkr.co/edit/xyZKQgENrwd4uEwS9QIM

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

You don't have to be afraid of memory leaks using this solution cause angular is removing watchers automatically. See https://*.com/a/25114028/6460149 for more information.

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

#3


1  

1) For current task ng-view is not needed (IMHO). If you need two different scopes then redesign ng-views to become directives with their own controllers. This will prevent angular to reload them

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

2) if you need to share data between scopes then service could be used to store data (see helperService in the following code)

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

3) if we talk about current code simplification then it could be done so: use service from 2) and just use one controller:

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) interval, watch, broadcast, etc are not needed as well

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

Full code is here: plunker

完整代码在这里:plunker

P.S. don't forget about angularjs-best-practices/style-guide

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

#1


1  

Not so pretty, but working solution would be to use events. Well, maybe it is not that bad, at least it is not complicated. https://plnkr.co/edit/SNRFhaudhsWLKUNMFos6?p=preview

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

EDIT: A little bit more complicated solution, that requires changing promise creator function into observables, but works: https://plnkr.co/edit/1j1BCGvUXjtv3WhYN84T?p=preview

编辑:一个更复杂的解决方案,需要将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


2  

Based on sielakos answer using an special service i came up with this solution. First, i need a additional service which keeps a reference of the data from the resovle.

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

Service

.service('dataLink', function () {
  var storage = null;

  function setData(data) {
      storage = data;
  }

  function getData() {
      return storage;
  }

  return {
      setData: setData,
      getData: getData
  };
})

Well, i have to use the service in my resolve function like so

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

Resolve function

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;
            });
    }
}

Notice the line dataLink.setData(response.data);. It keeps the data from the resolve in the service so I can access it from within the controller.

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

Controller

I modified the controller a little. I wrapped all the initialisation suff in an function i can execute when the data changes. The second thing is to watch the return value of the dataLink.getData();

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

As of https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$watch $scope.$watch provides functionality to watch return values of functions.

从https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$watch $ scope开始。$ watch提供了观察函数返回值的功能。

Here is some Q&D example:

这是一些Q&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;
    }
})

The line $scope.$watch(dataLink.getData, function(newData) { ... }); does the trick. Every time the data in the dataLink service changes the callback kicks in and replaces the old data with the new one. Ive created a plunker so you can give it a try https://plnkr.co/edit/xyZKQgENrwd4uEwS9QIM

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

You don't have to be afraid of memory leaks using this solution cause angular is removing watchers automatically. See https://*.com/a/25114028/6460149 for more information.

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

#3


1  

1) For current task ng-view is not needed (IMHO). If you need two different scopes then redesign ng-views to become directives with their own controllers. This will prevent angular to reload them

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

2) if you need to share data between scopes then service could be used to store data (see helperService in the following code)

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

3) if we talk about current code simplification then it could be done so: use service from 2) and just use one controller:

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) interval, watch, broadcast, etc are not needed as well

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

Full code is here: plunker

完整代码在这里:plunker

P.S. don't forget about angularjs-best-practices/style-guide

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