Объект AngularJS Factory не обновляется на контроллере и в представлении

У меня проблема с привязкой объекта Factory и Controller и его представления.

Я пытаюсь получить fileUri изображения, выбранного пользователем. Все идет нормально. Проблема в том, что я сохраняю значение этого файла в overlays.dataUrl. Но я ссылаюсь на него в представлении, и он не обновляется. (Я проверил, и значение действительно сохраняется в переменной overlays.dataUrl.

Вот исходный код settings.service.js:

(function () {
    "use strict";
    angular
    .module("cameraApp.core")
    .factory("settingsService", settingsService);

    settingsService.$inject = ["$rootScope", "$cordovaFileTransfer", "$cordovaCamera"];

    function settingsService($rootScope, $cordovaFileTransfer, $cordovaCamera) {

         var overlays = {
             dataUrl: "",
             options: {
                 sourceType: Camera.PictureSourceType.PHOTOLIBRARY,
                 destinationType: Camera.DestinationType.FILE_URI
             }
         };

         var errorMessages = [];

         var service = {
             overlays: overlays,
             selectOverlayFile: selectOverlayFile,
             errorMessages: errorMessages
         };

         return service;

         function selectOverlayFile() {
             $cordovaCamera.getPicture(overlays.options).then(successOverlay, errorOverlay);
         }

         //Callback functions
         function successOverlay(imageUrl) {
        //If user has successfully selected a file
             var extension = "jpg";
             var filename = getCurrentDateFileName();
             $cordovaFileTransfer.download(imageUrl, cordova.file.dataDirectory + filename + '.' + extension, {}, true)
                 .then(function (fileEntry) {
                      overlays.dataUrl = fileEntry.nativeURL;
                 }, function (e) {
                      errorMessages.push(e);
                 });
    }

    function errorOverlay(message) {
        //If user couldn't select a file
        errorMessages.push(message);
        //$rootScope.$apply();
    }
    }
})();

Теперь контроллер:

(function () {
    angular
    .module("cameraApp.settings")
    .controller("SettingsController", SettingsController);

    SettingsController.$inject = ["settingsService"];

    function SettingsController(settingsService) {
        var vm = this;
        vm.settings = settingsService;

        activate();

        //////////////////

        function activate(){
           // Nothing here yet
        }
    }
})();

Наконец на вид:

<h1>{{vm.settings.overlays.dataUrl}}</h1>
<button id="overlay" class="button"
                ng-click="vm.settings.selectOverlayFile()">
            Browse...
</button>

Всякий раз, когда я меняю значение на фабрике, оно не меняется в представлении.

Заранее спасибо!


person Rafael Lourenço    schedule 05.07.2016    source источник


Ответы (2)


К сожалению, фабрики в angularjs не предназначены для использования в качестве двухсторонних привязок. Фабрики и Сервисы — это всего лишь синглтоны. Их можно использовать только при вызове.

Бывший завод:

app.factory('itemFactory', ['$http', '$rootScope', function($http, $rootScope) {
var service = {};

service.item = null;

service.getItem = function(id) {
    $http.get(baseUrl + "getitem/" + id)
        .then(function successCallback(resp) {
                service.item = resp.data.Data;
                $rootScope.$broadcast("itemready");
        }, function errorCallback(resp) {
            console.log(resp)
        });
};

return service;
}]);

Я использую $broadcast, поэтому, если я вызову getItem, мой контроллер знает, что нужно получить свежие данные.

Ex директива:

angular.module("itemApp").directive("item", ['itemFactory', '$routeParams', '$location', '$rootScope', '$timeout', function (itemFactory, $routeParams, $location, $rootScope, $timeout) {
return {
    restrict: 'E',
    templateUrl: "components/item.html",
    link: function (scope, elem, attr) {
        scope.item = itemFactory.item;

        scope.changeMade = function(){
           itemFactory.getItem(1);
        }

        scope.$on("itemready", function () {
            scope.item = itemFactory.item;
        })
    }
}

}]);

Итак, как вы можете видеть в моем коде выше, в любое время, когда мне нужен свежий item, я использую $broadcast и $on для обновления моей службы и директивы. Я надеюсь, что это имеет смысл, не стесняйтесь задавать любые вопросы.

person Ohjay44    schedule 05.07.2016
comment
На мой ответ в директиве, когда я делаю vm.overlay = settingsService.overlay, он не проходит обновление. Только когда я передаю обновленное значение в качестве аргумента функции (в $on) и делаю: $on("overlaysUpdate", function (event, overlays) { vm.overlays = overlays; });, я получаю обновленное значение... Это нормально? - person Rafael Lourenço; 07.07.2016
comment
@RafaelLourenco это правильно, за исключением того, что вам не нужно передавать обновленное значение через $on, если вы этого не хотите, в качестве альтернативы вы можете просто снова установить vm.overlay = settingsService.overlay после завершения обратного вызова. Исходная переменная vm.overlay не будет обновляться автоматически, потому что такова природа синглтонов, и ее необходимо принудительно обновить, независимо от того, делаете ли вы это, передавая ее через $on или просто сбрасывая ее после получения обратного вызова от $on. - person Ohjay44; 07.07.2016

Как указал Ohjay44, фабрика не обновляется в представлении. Способ сделать это - использовать директиву (также, как сказал Ohjay44). Чтобы использовать $broadcast, $emit и $on и сохранить инкапсуляцию, я сделал то, что рекомендует Джон Papa's Angular Style Guide: создал фабрику (в моем случае назвал ее comms).

Вот только что созданная директива (overlay.directive.js):

(function () {
    angular
    .module('cameraApp.settings')
    .directive('ptrptSettingsOverlaysInfo', settingsOverlaysInfo);

    settingsOverlaysInfo.$inject = ["settingsService", "comms"];

    function settingsOverlaysInfo(settingsService, comms) {

        var directive = {
            restrict: "EA",
            templateUrl: "js/app/settings/overlays.directive.html",
            link: linkFunc,
            controller: "SettingsController",
            controllerAs: "vm",
            bindToController: true // because the scope is isolated
        };

        return directive;

        function linkFunc(scope, element, attrs, vm) {
            vm.overlays = settingsService.overlays;

            comms.on("overlaysUpdate", function (event, overlays) {
                vm.overlays = overlays;
            });
        }
    }
})();

Я создал overlay.directive.html с помощью:

<div class="item item-thumbnail-left">
    <img ng-src="{{vm.overlays.dataUrl}}">
    <h2>{{vm.overlays.dataUrl}}</h2>
</div>

И, наконец, я помещаю $emit на settingsService, где обновляется overlay:

(function () {
"use strict";
angular
.module("cameraApp.core")
.factory("settingsService", settingsService);

settingsService.$inject = ["comms", "$cordovaFileTransfer", "$cordovaCamera"];

function settingsService(comms, $cordovaFileTransfer, $cordovaCamera) {

         var overlays = {
             dataUrl: "",
             options: {
                 sourceType: Camera.PictureSourceType.PHOTOLIBRARY,
                 destinationType: Camera.DestinationType.FILE_URI
             }
         };

         var errorMessages = [];

         var service = {
             overlays: overlays,
             selectOverlayFile: selectOverlayFile,
             errorMessages: errorMessages
         };

         return service;

         function selectOverlayFile() {
             $cordovaCamera.getPicture(overlays.options).then(successOverlay, errorOverlay);
         }

         //Callback functions
         function successOverlay(imageUrl) {
        //If user has successfully selected a file
             var extension = "jpg";
             var filename = getCurrentDateFileName();
             $cordovaFileTransfer.download(imageUrl, cordova.file.dataDirectory + filename + '.' + extension, {}, true)
                 .then(function (fileEntry) {
                      overlays.dataUrl = fileEntry.nativeURL;
                      // New code!!!!
                      comms.emit("overlaysUpdated", overlays);
                 }, function (e) {
                      errorMessages.push(e);
                 });
        }

        function errorOverlay(message) {
            //If user couldn't select a file
            errorMessages.push(message);
            //$rootScope.$apply();
        }
    }
})();

Я использовал $emit вместо широковещательной передачи, чтобы предотвратить всплытие, как описано здесь: in-angularjs/19498009#19498009">Как правильно взаимодействовать между контроллерами в AngularJS?

Надеюсь, это поможет кому-то еще.

Ваше здоровье!

person Rafael Lourenço    schedule 06.07.2016