Atualizar o valor do escopo quando os dados do serviço são alterados

Eu tenho o seguinte serviço no meu aplicativo:

uaInProgressApp.factory('uaProgressService', function(uaApiInterface, $timeout, $rootScope){ var factory = {}; factory.taskResource = uaApiInterface.taskResource() factory.taskList = []; factory.cron = undefined; factory.updateTaskList = function() { factory.taskResource.query(function(data){ factory.taskList = data; $rootScope.$digest console.log(factory.taskList); }); factory.cron = $timeout(factory.updateTaskList, 5000); } factory.startCron = function () { factory.cron = $timeout(factory.updateTaskList, 5000); } factory.stopCron = function (){ $timeout.cancel(factory.cron); } return factory; }); 

Então eu uso em um controlador como este:

 uaInProgressApp.controller('ua.InProgressController', function ($scope, $rootScope, $routeParams, uaContext, uaProgressService) { uaContext.getSession().then(function(){ uaContext.appName.set('Testing house'); uaContext.subAppName.set('In progress'); uaProgressService.startCron(); $scope.taskList = uaProgressService.taskList; }); } ); 

Então, basicamente, meu serviço atualiza factory.taskList cada 5 segundos e eu $scope.taskList este factory.taskList a $scope.taskList . Em seguida, tentei methods diferentes, como $apply , $digest mas as alterações em factory.taskList não são refletidas em meu controlador e $scope.taskList .

Ele permanece vazio no meu modelo. Você sabe como eu posso propagar essas mudanças?

Enquanto usar $watch pode resolver o problema, não é a solução mais eficiente. Você pode querer mudar a maneira como você está armazenando os dados no serviço.

O problema é que você está substituindo o local de memory ao qual sua taskList está associada toda vez que você atribui a ele um novo valor enquanto o escopo está preso apontando para o local antigo. Você pode ver isso acontecendo neste plunk .

Tire um instantâneo de heap com o Chrome quando você carregar o plunk pela primeira vez e, depois de clicar no botão, você verá que o local da memory para o qual o escopo aponta nunca é atualizado enquanto a lista aponta para um local de memory diferente.

Você pode facilmente consertar isso fazendo com que seu serviço mantenha um object que contenha a variável que pode mudar (algo como data:{task:[], x:[], z:[]} ). Nesse caso, “data” nunca deve ser alterado, mas qualquer um de seus membros pode ser alterado sempre que você precisar. Em seguida, você passa essa variável de dados para o escopo e, desde que você não a substitua tentando atribuir “dados” a outra coisa, sempre que um campo dentro de dados for alterado, o escopo saberá sobre ele e será atualizado corretamente.

Este plunk mostra o mesmo exemplo usando a correção sugerida acima. Não há necessidade de usar observadores nessa situação e, se acontecer de alguma coisa não estar atualizada na exibição, você sabe que tudo o que precisa fazer é executar um escopo $apply para atualizar a exibição.

Dessa forma, você elimina a necessidade de observadores que freqüentemente comparam variables ​​para mudanças e a configuração feia envolvida nos casos em que você precisa observar muitas variables. O único problema com essa abordagem é que na sua visão (html) você terá “dados”. prefixando tudo onde você costumava ter apenas o nome da variável.

Angular (ao contrário de Ember e alguns outros frameworks), não fornece objects especiais que permanecem semi-magicamente em sincronia. Os objects que você está manipulando são objects javascript simples e como dizer var a = b; não vincula as variables a e b , dizendo que $scope.taskList = uaProgressService.taskList não vincula esses dois valores.

Para esse tipo de link , o angular fornece $watch no $scope . Você pode observar o valor do uaProgressService.taskList e atualizar o valor no $scope quando ele muda:

 $scope.$watch(function () { return uaProgressService.taskList }, function (newVal, oldVal) { if (typeof newVal !== 'undefined') { $scope.taskList = uaProgressService.taskList; } }); 

A primeira expressão passada para a function $watch é executada em cada loop $digest e o segundo argumento é a function que é invocada com o novo valor e o antigo.

Não tenho certeza se isso ajuda, mas o que estou fazendo é vincular a function a $ scope.value. Por exemplo

 angular .module("testApp", []) .service("myDataService", function(){ this.dataContainer = { valA : "car", valB : "bike" } }) .controller("testCtrl", [ "$scope", "myDataService", function($scope, myDataService){ $scope.data = function(){ return myDataService.dataContainer; }; }]); 

Então eu apenas ligá-lo em DOM como

 
  • Desta forma você pode evitar usar $ watch em seu código.

    Nenhum $watch ou etc. é necessário. Você pode simplesmente definir o seguinte

     uaInProgressApp.controller('ua.InProgressController', function ($scope, $rootScope, $routeParams, uaContext, uaProgressService) { uaContext.getSession().then(function(){ uaContext.appName.set('Testing house'); uaContext.subAppName.set('In progress'); uaProgressService.startCron(); }); $scope.getTaskList = function() { return uaProgressService.taskList; }; }); 

    Como a function getTaskList pertence ao $scope seu valor de retorno será avaliado (e atualizado) em cada alteração de uaProgressService.taskList

    Uma alternativa leve é ​​que, durante a boot do controlador, você assina um padrão de notificador configurado no serviço.

    Algo como:

     app.controller('YourCtrl'['yourSvc', function(yourSvc){ yourSvc.awaitUpdate('YourCtrl',function(){ $scope.someValue = yourSvc.someValue; }); }]); 

    E o serviço tem algo como:

     app.service('yourSvc', ['$http',function($http){ var self = this; self.notificationSubscribers={}; self.awaitUpdate=function(key,callback){ self.notificationSubscribers[key]=callback; }; self.notifySubscribers=function(){ angular.forEach(self.notificationSubscribers, function(callback,key){ callback(); }); }; $http.get('someUrl').then( function(response){ self.importantData=response.data; self.notifySubscribers(); } ); }]); 

    Isso permite ajustar com mais precisão quando seus controladores são atualizados de um serviço.

    Como Gabriel Piacenti disse, nenhum relógio é necessário se você transformar os dados alterados em um object.

    MAS para atualizar os dados de serviço alterados no escopo corretamente, é importante que o valor do escopo do controlador que usa os dados do serviço não aponte diretamente para os dados alterados (campo). Em vez disso, o valor do escopo deve apontar para o object que envolve a alteração dos dados.

    O código a seguir deve explicar isso mais claro. No meu exemplo, eu uso um serviço NLS para traduzir. Os tokens do NLS estão sendo atualizados via http.

    O serviço:

     app.factory('nlsService', ['$http', function($http) { var data = { get: { ressources : "gdc.ressources", maintenance : "gdc.mm.maintenance", prewarning : "gdc.mobMaint.prewarning", } }; // ... asynchron change the data.get = ajaxResult.data... return data; }]); 

    Expressão de controlador e escopo

     app.controller('MenuCtrl', function($scope, nlsService) { $scope.NLS = nlsService; } ); 
    {{NLS.get.maintenance}}

    O código acima funciona, mas primeiro eu queria acessar meus tokens do NLS diretamente (consulte o snippet a seguir) e aqui os valores não foram atualizados.

     app.controller('MenuCtrl', function($scope, nlsService) { $scope.NLS = nlsService.get; } ); 
    {{NLS.maintenance}}