Cập nhật giá trị phạm vi khi dữ liệu dịch vụ được thay đổi


76

Tôi có dịch vụ sau trong ứng dụng của mình:

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

Sau đó, tôi sử dụng nó trong một bộ điều khiển như thế này:

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

Vì vậy, về cơ bản dịch vụ của tôi cập nhật factory.taskList5 giây một lần và tôi đã liên kết điều này factory.taskListvới $scope.taskList. Sau đó, tôi đã thử các phương pháp khác nhau như $apply, $digestnhưng các thay đổi trên factory.taskListkhông được phản ánh trong bộ điều khiển và chế độ xem của tôi $scope.taskList.

Nó vẫn trống trong mẫu của tôi. Bạn có biết tôi có thể tuyên truyền những thay đổi này như thế nào không?

Câu trả lời:


78

Mặc dù sử dụng $watchcó thể giải quyết được vấn đề, nhưng nó không phải là giải pháp hiệu quả nhất. Bạn có thể muốn thay đổi cách bạn đang lưu trữ dữ liệu trong dịch vụ.

Vấn đề là bạn đang thay thế vị trí bộ nhớ mà bạn taskListliên kết với mỗi khi bạn gán cho nó một giá trị mới trong khi phạm vi bị kẹt khi trỏ đến vị trí cũ. Bạn có thể thấy điều này xảy ra trong plunk này .

Chụp nhanh heap bằng Chrome khi bạn tải plunk lần đầu tiên và sau khi nhấp vào nút, bạn sẽ thấy rằng vị trí bộ nhớ mà phạm vi trỏ đến không bao giờ được cập nhật trong khi danh sách trỏ đến một vị trí bộ nhớ khác.

Bạn có thể dễ dàng sửa lỗi này bằng cách yêu cầu dịch vụ của bạn giữ một đối tượng có chứa biến có thể thay đổi (đại loại như data:{task:[], x:[], z:[]}). Trong trường hợp này, "dữ liệu" không bao giờ được thay đổi nhưng bất kỳ thành viên nào của nó cũng có thể được thay đổi bất cứ khi nào bạn cần. Sau đó, bạn chuyển biến dữ liệu này cho phạm vi và miễn là bạn không ghi đè nó bằng cách cố gắng gán "dữ liệu" cho một thứ khác, bất cứ khi nào một trường bên trong dữ liệu thay đổi, phạm vi sẽ biết về nó và sẽ cập nhật chính xác.

Phần mềm này hiển thị cùng một ví dụ đang chạy bằng cách sử dụng bản sửa lỗi được đề xuất ở trên. Không cần sử dụng bất kỳ trình theo dõi nào trong tình huống này và nếu có điều gì đó không được cập nhật trên chế độ xem, bạn biết rằng tất cả những gì bạn cần làm là chạy một phạm vi $applyđể cập nhật chế độ xem.

Bằng cách này, bạn loại bỏ sự cần thiết của những người theo dõi thường xuyên so sánh các biến để thay đổi và thiết lập xấu liên quan đến các trường hợp khi bạn cần theo dõi nhiều biến. Vấn đề duy nhất với phương pháp này là trên chế độ xem của bạn (html), bạn sẽ có "dữ liệu". tiền tố mọi thứ mà bạn đã từng chỉ có tên biến.


1
Lý do 'mới' + iife trong việc quay lại dịch vụ là gì?
lloop

1
Trong trường hợp này, nó sẽ cho phép bạn trả về một lớp, việc xóa lớp mới sẽ phá vỡ mã. Tôi có xu hướng thích cấu trúc này hơn cấu trúc đối tượng (ví dụ: "{attr: value, attr2: value2 ...}") vì tôi thấy nó linh hoạt hơn. Đây là chiếc plunker cho nó, và đây là một chút thông tin về nó từ đội ngũ angle . Như bạn thấy, bạn có thể sử dụng dịch vụ thay vì nhà máy cho việc này nhưng một trong hai hoạt động tốt.
Piacenti

4
hey @GabrielPiacenti - bạn có một câu trả lời thực sự tốt ở đây, tôi nghĩ sẽ hữu ích nếu bạn làm sạch nội dung / định dạng một chút ... làm cho nó dễ đọc hơn ...
sfletche

Gabriel, bạn nên xóa hầu hết mã và chỉ có câu trả lời tập trung vào những gì cần thiết. Bạn đúng, nhưng mã này trông phức tạp. Hãy xem 2 câu trả lời bên dưới, rõ ràng hơn nhiều (nhưng chúng thiếu thông tin bạn có về đống bộ nhớ). Thực hiện tốt một trong hai cách!
Mark Pieszak - Trilon.io

Tôi biết đây là một câu trả lời thực sự cũ, nhưng tôi đã làm điều này rất nhiều trong các ứng dụng của mình và nó hoạt động như một giấc mơ. Trong Angular 1.5, liên kết dữ liệu hai chiều đã bị loại bỏ (hầu hết), vậy kỹ thuật này có rơi vào những cạm bẫy về hiệu suất giống như việc loại bỏ ràng buộc dữ liệu hai chiều được đặt ra để giải quyết không?
Tyler

71

Angular (không giống như Ember và một số khung công tác khác), không cung cấp các đối tượng được bao bọc đặc biệt mà vẫn đồng bộ một cách bán kỳ diệu . Các đối tượng bạn đang thao tác là các đối tượng javascript thuần túy và giống như nói var a = b;không liên kết các biến ab, nói $scope.taskList = uaProgressService.taskListkhông liên kết hai giá trị đó.

Đối với loại link-ing này, angularcung cấp $watchtrên$scope . Bạn có thể xem giá trị của uaProgressService.taskListvà cập nhật giá trị $scopekhi nó thay đổi:

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

Biểu thức đầu tiên được truyền cho $watchhàm được thực thi trên mọi $digestvòng lặp và đối số thứ hai là hàm được gọi với giá trị mới và cũ.


1
Ok, tôi hiểu như vậy. Tôi đã thử đoạn mã của bạn và bây giờ ứng dụng của tôi đang hoạt động như mong đợi :) Có nhiều đồng hồ ảnh hưởng đến hiệu suất ứng dụng không?
Alex Grs

1
Mặc dù đôi khi mọi người đã phải thực hiện các biện pháp khắc nghiệt để tối ưu hóa nó , nhưng nó hoạt động khá tốt cho hầu hết các ứng dụng.
musically_ut

1
Tôi đã bị mù quá lâu, cảm ơn. Tôi nghĩ rằng bởi vì nó là một singleton các thuộc tính sẽ lây lan trong một cách kỳ diệu: S
Dvid Silva

4
Tôi đã mất nhiều ngày để tìm câu trả lời này. Cảm ơn. Tôi ngạc nhiên là trường hợp sử dụng này không được ghi chép tốt hơn vì đây là lý do duy nhất tôi sử dụng một dịch vụ (rpc / pubsub).
Senica Gonzalez

5
$ watch là một hack trong trường hợp này. Các kỹ thuật chính xác được mô tả bằng hai câu trả lời khác, đặc biệt @Gabriel Piacenti
Bernard

44

Tôi không chắc liệu điều đó có hữu ích không nhưng những gì tôi đang làm là liên kết hàm với $ scope.value. Ví dụ

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

Sau đó, tôi chỉ liên kết nó trong DOM dưới dạng

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

Bằng cách này, bạn có thể tránh sử dụng $ watch trong mã của mình.


4
CÂU TRẢ LỜI TỐT NHẤT bỏ qua các câu trả lời của $ watch, đây là cách nên làm. Ở trên có nó cũng đúng, nhưng cực kỳ khó hiểu.
Mark Pieszak - Trilon.io

1
Rất tiếc khi đưa ra một câu trả lời cũ, nhưng giải pháp này hoạt động hoàn hảo đối với tôi và tôi tự hỏi liệu có bất kỳ nhược điểm nào của nó mà tôi cần lưu ý không? Tôi đã thử nhiều cách khác nhau để liên kết các phạm vi và dịch vụ nhưng không có giải pháp nào phù hợp với cách này.
CT14.IT

4
Xin lỗi vì phản hồi muộn. Điều này không nên được sử dụng trong trường hợp chức năng dữ liệu của bạn khá phức tạp. Mỗi khi chạy thông báo $, phương thức sẽ được gọi.
Eduard Jacko,

Để giải quyết vấn đề đó, hãy sử dụng một nhà máy, nó trả về giá trị và nó tự cập nhật khi dữ liệu bị thay đổi.
Tiago Sousa

Tôi ước tôi có thể cảm ơn bạn nhiều hơn là chỉ nói lời cảm ơn .. !! không thể tìm thấy bất kỳ giải pháp nào, ngay cả đồng hồ cũng không hoạt động với tôi vì một số lý do kỳ lạ nhưng cái này làm được việc của nó! CẢM ƠN BẠN!
Kiss Koppány

5

Không $watchhoặc vv là bắt buộc. Bạn chỉ cần xác định những điều sau

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

Bởi vì hàm getTaskListthuộc $scopevề giá trị trả về của nó sẽ được đánh giá (và cập nhật) trên mọi thay đổi củauaProgressService.taskList


3

Thay thế nhẹ là trong quá trình khởi tạo bộ điều khiển, bạn đăng ký một mẫu trình thông báo được thiết lập trong dịch vụ.

Cái gì đó như:

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

Và dịch vụ có những thứ như:

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

Điều này có thể cho phép bạn tinh chỉnh cẩn thận hơn khi bộ điều khiển của bạn làm mới từ một dịch vụ.


Như một lưu ý, tôi đã tìm ra một giải pháp hiệu quả hơn nhiều cho vấn đề này. Sử dụng các tuyến lồng nhau với bộ định tuyến Angular UI và đặt tất cả các trình thông báo sự kiện tồn tại lâu dài trong bộ điều khiển gốc.
Bổ sung

1
Tôi muốn có thể đặt danh sách dữ liệu ở bất cứ đâu và lấy cảm hứng từ câu trả lời của bạn để viết chỉ thị sử dụng dịch vụ dựa trên nguyên tắc đăng ký của bạn. Tôi nghĩ rằng tại chỉ thị là một giải pháp đơn giản hơn nhiều so với các tuyến lồng nhau. Nhưng tôi đoán nó phụ thuộc ... anyway, cảm ơn bạn đã cung cấp một mảnh rất tốt đẹp và hữu ích của mã :-)
Jette

2

Giống như Gabriel Piacenti đã nói, không cần đồng hồ nếu bạn bọc dữ liệu thay đổi vào một đối tượng.

NHƯNG để cập nhật dữ liệu dịch vụ đã thay đổi trong phạm vi một cách chính xác, điều quan trọng là giá trị phạm vi của bộ điều khiển sử dụng dữ liệu dịch vụ không trỏ trực tiếp đến (trường) dữ liệu thay đổi. Thay vào đó, giá trị phạm vi phải trỏ đến đối tượng bao bọc dữ liệu đang thay đổi.

Đoạn mã sau sẽ giải thích điều này rõ ràng hơn. Trong ví dụ của tôi, tôi sử dụng Dịch vụ NLS để dịch. Các Token NLS đang được cập nhật qua http.

Dịch vụ:

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

Bộ điều khiển và biểu thức phạm vi

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

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

Đoạn mã trên hoạt động, nhưng trước tiên tôi muốn truy cập trực tiếp Mã thông báo NLS của mình (xem đoạn mã sau) và ở đây các giá trị không được cập nhật.

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

<div ng-controller="MenuCtrl">
  <span class="navPanelLiItemText">{{NLS.maintenance}}</span>
</div>
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.