Оновити значення області дії, коли дані служби змінюються


76

У моєму додатку є така послуга:

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

Потім я використовую його в контролері, як це:

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

Тому в основному моя служба оновлень factory.taskListкожні 5 секунд , і я пов'язав це factory.taskListз $scope.taskList. Потім я спробував різні методи, такі як $apply, $digestале зміни factory.taskListв моєму контролері та поданні не відображаються $scope.taskList.

Він залишається порожнім у моєму шаблоні. Чи знаєте ви, як я можу поширити ці зміни?

Відповіді:


78

Хоча використання $watchможе вирішити проблему, це не найефективніше рішення. Можливо, ви захочете змінити спосіб зберігання даних у службі.

Проблема полягає в тому, що ви замінюєте місце пам’яті, з яким taskListпов’язано, кожного разу, коли ви присвоюєте йому нове значення, коли область застрягла, вказуючи на старе місце. Ви можете бачити, як це відбувається в цьому планку .

Робіть кучу знімків з Chrome, коли ви вперше завантажуєте плагін, і після натискання кнопки ви побачите, що розташування пам'яті, на яке вказує область, ніколи не оновлюється, тоді як список вказує на інше місце пам'яті.

Ви можете легко це виправити, якщо ваша служба має об’єкт, що містить змінну, яка може змінитися (щось на зразок data:{task:[], x:[], z:[]}). У цьому випадку "дані" ніколи не слід міняти, але будь-який його учасник може бути змінений, коли вам потрібно. Потім ви передаєте цю змінну даних в область дії, і поки ви не перевизначаєте її, намагаючись призначити "дані" чомусь іншому, кожен раз, коли поле всередині даних змінює область, буде знати про це і буде правильно оновлюватися.

Цей плагін показує той самий приклад, що працює за допомогою виправлення, запропонованого вище. Не потрібно використовувати будь-яких спостерігачів у цій ситуації, і якщо трапляється, що щось не оновлюється у поданні, ви знаєте, що все, що вам потрібно зробити, це запустити область $applyдля оновлення подання.

Таким чином ви усуваєте необхідність спостерігачів, які часто порівнюють змінні для змін та потворних налаштувань, пов’язаних із випадками, коли вам потрібно спостерігати за багатьма змінними. Єдина проблема такого підходу полягає в тому, що у вашому поданні (html) у вас будуть "дані". префікс у всьому, де раніше ви мали просто ім’я змінної.


1
У чому причина повернення послуги "new" + iife?
lloop

1
У цьому випадку це дозволить вам повернути клас, видалення нового призведе до порушення коду. Я, як правило, віддаю перевагу цій структурі, ніж структурі об'єкта (наприклад, "{attr: value, attr2: value2 ...}"), оскільки я вважаю її більш гнучкою. Ось плюнкер для нього, і ось трохи інформації про це від нестандартної команди. Як бачите, для цього ви можете використовувати службу замість заводу, але будь-яка з них працює чудово.
Piacenti

4
привіт @GabrielPiacenti - у вас тут дійсно хороша відповідь, я думаю, це допомогло б, якщо ви трохи очистили вміст / форматування ... зробіть його більш читабельним ...
sfletche

Габріелю, вам слід видалити більшу частину коду і просто зосередити відповідь на тому, що потрібно. Ви маєте рацію, але цей код виглядає надто складним. Подивіться на 2 відповіді нижче, набагато чистіші (але їм бракує інформації, яку ви маєте про купу пам’яті). Чудово зроблено в будь-якому випадку!
Mark Pieszak - Trilon.io

Я знаю, що це справді давня відповідь, але я робив це багато в своїх додатках, і це працює як мрія. В Angular 1.5 двосторонній прив'язок даних був вилучений (здебільшого), тож чи потрапляє цей метод до тих самих підводних каменів, що й видалення двостороннього прив'язки даних, розроблених для вирішення?
Тайлер

71

Angular (на відміну від Ember та деяких інших фреймворків), не забезпечує особливих загорнутих об’єктів, які напівчаровно синхронізуються. Об'єкти, якими ви маніпулюєте, є звичайними об'єктами javascript, і, як кажуть var a = b;, не пов’язують змінні aі b, кажучи $scope.taskList = uaProgressService.taskList, не пов’язують ці два значення.

Для такого роду link-ву, angularзабезпечує $watchна$scope . Ви можете спостерігати за значенням uaProgressService.taskListі оновлювати значення, $scopeколи воно змінюється:

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

Перший вираз, переданий $watchфункції, виконується в кожному $digestциклі, а другий аргумент - це функція, яка викликається із новим та старим значенням.


1
Гаразд, я так розумію. Я спробував ваш фрагмент, і тепер мій додаток працює належним чином :) Чи багато впливає на ефективність програми перегляд?
Alex Grs

1
Хоча іноді людям доводилося вживати крайніх заходів для його оптимізації , це працює досить добре для більшості додатків.
musically_ut

1
Я так довго був сліпим, дякую. Я думав, що оскільки це
одиночка,

4
Я знайшов цю відповідь буквально днями. Дякую. Я здивований, що цей варіант використання не є краще задокументованим, оскільки це єдина причина, через яку я використовую послугу (rpc / pubsub).
Senica Gonzalez

5
$ watch - це хак в цьому випадку. Правильна техніка описується двома іншими відповідями, зокрема @Gabriel Piacenti
Бернард

44

Я не впевнений, чи це допомагає, але те, що я роблю, це прив'язка функції до $ scope.value. Наприклад

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

Тоді я просто прив'язую це в DOM як

<li ng-repeat="(key,value) in data() "></li>

Таким чином ви можете уникнути використання $ watch у своєму коді.


4
НАЙКРАЩА ВІДПОВІДЬ ігноруйте $ watch відповіді ось як це слід робити. Вищевикладене також правильно, але надзвичайно заплутано.
Mark Pieszak - Trilon.io

1
Вибачте за стару відповідь, але це рішення для мене ідеально працює, і мені цікаво, чи є у нього якісь недоліки, про які я повинен знати? Я випробував різні способи зв'язування областей застосування та послуг, але жодне рішення не є настільки елегантним, як це.
CT14.IT

4
Вибачте за пізню відповідь. Це не слід використовувати в тих випадках, коли ваша функція даних досить складна. Кожного разу, коли запускається дайджест $, метод буде викликаний.
Едуард Яцко

Щоб вирішити цю проблему, використовуйте завод, він повертає значення та оновлюється самостійно при зміні даних.
Тіаго Соуса,

Я би хотів, щоб я міг подякувати вам більше, ніж просто сказати подяку .. !! не міг знайти рішення, навіть годинник не працював для мене з якоїсь дивної причини, але цей робить свою справу! ДЯКУЮ!
Kiss Koppány

5

Ні $watchабо тощо не потрібно. Ви можете просто визначити наступне

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

Оскільки функція getTaskListналежить до $scopeїї поверненого значення, буде обчислюватися (та оновлюватися) при кожній змініuaProgressService.taskList


3

Легка альтернатива полягає в тому, що під час ініціалізації контролера ви підписуєтесь на шаблон сповіщувача, встановлений у службі.

Щось на зразок:

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

А послуга має щось на зразок:

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

Це може дозволити вам більш ретельно налаштуватися, коли ваші контролери оновлюються від служби.


Як примітка, з тих пір я знайшов набагато ефективніше рішення цього. Використовуйте вкладені маршрути з Angular UI router і розміщуйте всі довговічні сповіщувачі подій у кореневому контролері.
Бон

1
Я хотів мати можливість розмістити список даних де завгодно, і надихнувся Вашою відповіддю написати директиву, яка використовує послугу, засновану на Вашому принципі передплати. Я думаю, що at директива є набагато більш простим рішенням, ніж вкладені маршрути. Але, мабуть, це залежить ... у будь-якому випадку, дякую за надання дуже приємного та корисного фрагмента коду :-)
Джетт,

2

Як сказав Габріель П'яченті, годинники не потрібні, якщо ви обертаєте дані, що змінюються, в об'єкт.

АЛЕ для коректного оновлення змінених службових даних у області, важливо, щоб значення області дії контролера, який використовує службові дані, не вказувало безпосередньо на змінні дані (поле). Натомість значення області має вказувати на об'єкт, який обертає дані, що змінюються.

Наступний код повинен пояснити це більш чітко. У моєму прикладі я використовую службу NLS для перекладу. Токени NLS оновлюються через http.

Сервіс:

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

Вираз контролера та області

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

<div ng-controller="MenuCtrl">
  <span class="navPanelLiItemText">{{NLS.get.maintenance}}</span>
</div>

Вищезазначений код працює, але спочатку я хотів отримати безпосередній доступ до своїх токенів NLS (див. Наступний фрагмент), і тут значення не оновились.

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

<div ng-controller="MenuCtrl">
  <span class="navPanelLiItemText">{{NLS.maintenance}}</span>
</div>
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.