Введення діапазону $ у функцію кутового обслуговування ()


107

У мене є Сервіс:

angular.module('cfd')
  .service('StudentService', [ '$http',
    function ($http) {
    // get some data via the $http
    var path = 'data/people/students.json';
    var students = $http.get(path).then(function (resp) {
      return resp.data;
    });     
    //save method create a new student if not already exists
    //else update the existing object
    this.save = function (student) {
      if (student.id == null) {
        //if this is new student, add it in students array
        $scope.students.push(student);
      } else {
        //for existing student, find this student using id
        //and update it.
        for (i in students) {
          if (students[i].id == student.id) {
            students[i] = student;
          }
        }
      }
    };

Але коли я дзвоню save(), я не маю доступу до $scopeта отримую ReferenceError: $scope is not defined. Отже, логічним кроком (для мене) є надання save () для $scope, і, таким чином, я також повинен надати / ввести його в service. Тож якщо я так роблю:

  .service('StudentService', [ '$http', '$scope',
                      function ($http, $scope) {

Я отримую таку помилку:

Помилка: [$ injector: unpr] Невідомий постачальник: $ rangeProvider <- $ obseg <- StudentService

Посилання на помилку (вау, що це акуратно!) Дає мені знати, що це пов'язано з інжектором, і, можливо, це стосується порядку декларування файлів js. Я спробував їх переупорядкувати в index.html, але думаю, що це щось простіше, наприклад, те, як я їх вводять.

Використання Angular-UI та Angular-UI-Router

Відповіді:


183

Те, $scopeщо ви бачите, що його вводять у контролери, - це не якась послуга (як, наприклад, решта ін'єкційних матеріалів), а об'єкт Scope. Можна створити багато об'єктів сфери застосування (як правило, прототипічно успадковуються від батьківського діапазону). Корінь усіх областей - це те, $rootScopeі ви можете створити нове дочірнє поле, використовуючи $new()метод будь-якої області (включаючи $rootScope).

Мета Області - "склеїти" презентацію та ділову логіку вашого додатка. Немає особливого сенсу передавати $scopeпослугу.

Служби - це одноособові об'єкти, які використовуються (серед іншого) для обміну даними (наприклад, між кількома контролерами) і, як правило, інкапсулюють багаторазові фрагменти коду (оскільки їх можна вводити та пропонувати свої "послуги" в будь-якій частині вашої програми, яка їм потрібна: контролери, директиви, фільтри, інші послуги тощо).

Впевнений, для вас підійдуть різні підходи. Одне таке:
Оскільки StudentServiceце відповідає за обробку даних студентів, ви можете мати StudentServiceнабір студентів та дозволити їм "поділитися" ним із тим, хто може зацікавити (наприклад, вашим $scope). Це має ще більший сенс, якщо є інші види / контролери / фільтри / служби, яким потрібно мати доступ до цієї інформації (якщо зараз таких немає, не дивуйтеся, якщо вони незабаром почнуть вискакувати).
Кожного разу, коли додається новий студент (використовуючи save()метод сервісу ), власний масив учнів служби буде оновлюватися, і кожен інший об'єкт, що обмінюється цим масивом, також автоматично оновлюється.

Виходячи з описаного вище підходу, ваш код може виглядати так:

angular.
  module('cfd', []).

  factory('StudentService', ['$http', '$q', function ($http, $q) {
    var path = 'data/people/students.json';
    var students = [];

    // In the real app, instead of just updating the students array
    // (which will be probably already done from the controller)
    // this method should send the student data to the server and
    // wait for a response.
    // This method returns a promise to emulate what would happen 
    // when actually communicating with the server.
    var save = function (student) {
      if (student.id === null) {
        students.push(student);
      } else {
        for (var i = 0; i < students.length; i++) {
          if (students[i].id === student.id) {
            students[i] = student;
            break;
          }
        }
      }

      return $q.resolve(student);
    };

    // Populate the students array with students from the server.
    $http.get(path).then(function (response) {
      response.data.forEach(function (student) {
        students.push(student);
      });
    });

    return {
      students: students,
      save: save
    };     
  }]).

  controller('someCtrl', ['$scope', 'StudentService', 
    function ($scope, StudentService) {
      $scope.students = StudentService.students;
      $scope.saveStudent = function (student) {
        // Do some $scope-specific stuff...

        // Do the actual saving using the StudentService.
        // Once the operation is completed, the $scope's `students`
        // array will be automatically updated, since it references
        // the StudentService's `students` array.
        StudentService.save(student).then(function () {
          // Do some more $scope-specific stuff, 
          // e.g. show a notification.
        }, function (err) {
          // Handle the error.
        });
      };
    }
]);

Одне, з чим слід бути обережним при використанні цього підходу, - це ніколи не призначати масив служби, оскільки тоді будь-які інші компоненти (наприклад, сфери дії) все ще будуть посилатися на початковий масив, і ваш додаток зламається.
Наприклад, щоб очистити масив у StudentService:

/* DON'T DO THAT   */  
var clear = function () { students = []; }

/* DO THIS INSTEAD */  
var clear = function () { students.splice(0, students.length); }

Дивіться також цю коротку демонстрацію .


МАЛКО ОНОВЛЕННЯ:

Кілька слів, щоб уникнути плутанини, яка може виникнути під час розмови про використання послуги, але не створювати її за допомогою service()функції.

Цитуючи документи на$provide :

Кутова служба - це однотонний об'єкт, створений фабрикою обслуговування . Ці фабрики послуг - це функції, які, у свою чергу, створюються постачальником послуг . В сервіс - провайдери є функціями конструктора. При екземплярі вони повинні містити властивість $get, що називається , яка виконує функцію фабричного обслуговування .
[...]
... $provideсервіс має додаткові допоміжні методи для реєстрації послуг без вказівки постачальника:

  • провайдер (провайдер) - реєструє постачальника послуг за допомогою інжектора $
  • константа (obj) - реєструє значення / об'єкт, до якого можуть отримати доступ провайдери та послуги.
  • value (obj) - реєструє значення / об'єкт, доступ до якого можуть отримати лише сервіси, а не провайдери.
  • factory (fn) - реєструє функцію фабрики послуг, fn, яка буде обгорнута об'єктом постачальника послуг, властивість якого $ get буде містити задану функцію заводу.
  • service (class) - реєструє функцію конструктора, клас, який буде загорнутий у об’єкт постачальника послуг, властивість $ get якого інстанціює новий об'єкт за допомогою заданої функції конструктора.

В основному, це говорить про те, що кожна послуга Angular реєструється за допомогою $provide.provider(), але існують методи "швидкого доступу" для більш простих сервісів (два з яких є service()і factory()).
Це все "зводиться" до послуги, тому це не має великої різниці, який метод ви використовуєте (до тих пір, поки вимоги до вашої послуги можуть бути покриті цим методом).

BTW, providervs servicevs factoryє однією з найбільш заплутаних концепцій для кутових новачків, але, на щастя, існує маса ресурсів (тут на SO), щоб полегшити справи. (Просто пошукайте навколо.)

(Я сподіваюся, що це очистить - дайте мені знати, якщо це не так.)


1
Одне питання. Ви говорите сервіс, але ваш приклад коду використовує фабрику. Я тільки починаю розуміти різницю між фабриками, службами та постачальниками, просто хочу бути впевненим, що найкращим варіантом є використання фабрики, оскільки я користувався послугою. Багато чого навчився на своєму прикладі. Дякую за загадку та ДУЖЕ чітке пояснення.
chris Frisina

3
@chrisFrisina: оновив відповідь з невеликим поясненням. В основному це не має великої різниці, якщо ви користуєтесь serviceабо factory- закінчите і з, і з кутовим сервісом . Просто переконайтеся, що ви розумієте, як працює кожен з них та чи відповідає вашим потребам.
gkalpak

Гарний пост! Це мені дуже допомагає!
Oni1

Дякую брате! ось приємна стаття з подібної справи stsc3000.github.io/blog/2013/10/26/…
Terafor

@ExpertSystem $scope.studentsБуде порожнім, якщо виклик Ajax не закінчиться? Або $scope.studentsбуде частково заповнений, якщо цей блок коду працює? students.push(student);
Іц Чжан

18

Замість того, щоб намагатися змінити $scopeслужбу в межах служби, ви можете впровадити $watchконтролер у межах свого контролера, щоб переглянути властивості у вашій службі за змінами, а потім оновити властивість на $scope. Ось приклад, який ви можете спробувати у контролері:

angular.module('cfd')
    .controller('MyController', ['$scope', 'StudentService', function ($scope, StudentService) {

        $scope.students = null;

        (function () {
            $scope.$watch(function () {
                return StudentService.students;
            }, function (newVal, oldVal) {
                if ( newValue !== oldValue ) {
                    $scope.students = newVal;
                }
            });
        }());
    }]);

Варто зазначити, що всередині вашої послуги для того, щоб studentsмайно було видно, воно повинно знаходитись на об’єкті служби або thisтак:

this.students = $http.get(path).then(function (resp) {
  return resp.data;
});

12

Ну (довгий) ... якщо ви наполягаєте на тому, щоб мати $scopeдоступ до послуги, ви можете:

Створіть послугу геттера / налаштування

ngapp.factory('Scopes', function (){
  var mem = {};
  return {
    store: function (key, value) { mem[key] = value; },
    get: function (key) { return mem[key]; }
  };
});

Введіть його і збережіть в ньому область контролера

ngapp.controller('myCtrl', ['$scope', 'Scopes', function($scope, Scopes) {
  Scopes.store('myCtrl', $scope);
}]);

Тепер знайдіть сферу застосування в іншій службі

ngapp.factory('getRoute', ['Scopes', '$http', function(Scopes, $http){
  // there you are
  var $scope = Scopes.get('myCtrl');
}]);

Як руйнуються сфери дії?
Ж.К.

9

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


Я розумію, що ти кажеш. Однак у моєму випадку у мене є багато контролерів, і я хотів би налаштувати їх сфери застосування з дуже схожим набором $ годин. Як / де ти це зробив би? В даний час я дійсно передаю область в якості параметра службі, яка встановлює $ годинник.
moritz

@moritz, можливо, реалізує вторинну директиву (та, яка має сферу застосування: false, тому вона використовує область, визначену іншими директивами), і вона робить прив'язки годинника, а також все, що вам потрібно. Таким чином ви могли використовувати цю іншу директиву в будь-якому місці, де вам потрібно визначити такі годинники. Тому що передача сфери обслуговування справді дуже жахлива :) (повірте, я там був, робив це, врешті-решт ударив головою об стіну)
tfrascaroli

@TIMINeutron, що звучить набагато краще, ніж пройти рамки, я спробую, що наступного разу з'явиться сценарій! Дякую!
moritz

Звичайно. Я все ще вчуся, і саме ця проблема є однією з них, яку я нещодавно вирішив саме цим способом, і вона спрацювала як шарм для мене.
tfrascaroli

3

Ви можете зробити свою службу абсолютно невідомої сфери, але в контролері дозволяють асинхронно оновлювати область.

Проблема у вас полягає в тому, що ви не знаєте, що http-дзвінки здійснюються асинхронно, а значить, ви не отримуєте значення одразу, як можливо. Наприклад,

var students = $http.get(path).then(function (resp) {
  return resp.data;
}); // then() returns a promise object, not resp.data

Існує простий спосіб обійти це, і це забезпечити функцію зворотного дзвінка.

.service('StudentService', [ '$http',
    function ($http) {
    // get some data via the $http
    var path = '/students';

    //save method create a new student if not already exists
    //else update the existing object
    this.save = function (student, doneCallback) {
      $http.post(
        path, 
        {
          params: {
            student: student
          }
        }
      )
      .then(function (resp) {
        doneCallback(resp.data); // when the async http call is done, execute the callback
      });  
    }
.controller('StudentSaveController', ['$scope', 'StudentService', function ($scope, StudentService) {
  $scope.saveUser = function (user) {
    StudentService.save(user, function (data) {
      $scope.message = data; // I'm assuming data is a string error returned from your REST API
    })
  }
}]);

Форма:

<div class="form-message">{{message}}</div>

<div ng-controller="StudentSaveController">
  <form novalidate class="simple-form">
    Name: <input type="text" ng-model="user.name" /><br />
    E-mail: <input type="email" ng-model="user.email" /><br />
    Gender: <input type="radio" ng-model="user.gender" value="male" />male
    <input type="radio" ng-model="user.gender" value="female" />female<br />
    <input type="button" ng-click="reset()" value="Reset" />
    <input type="submit" ng-click="saveUser(user)" value="Save" />
  </form>
</div>

Це видалило частину вашої ділової логіки для стислості, і я фактично не перевіряв код, але щось подібне спрацювало б. Основна концепція - передача зворотного дзвінка від контролера до сервісу, який дзвонить пізніше в майбутньому. Якщо ви знайомі з NodeJS, це та сама концепція.


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

0

Потрапив у той самий склад. Я закінчився наступним. Тож тут я не ввожу об’єкт сфери в завод, а встановлюю область $ в самому контролері, використовуючи концепцію обіцянки, повернуту службою $ http .

(function () {
    getDataFactory = function ($http)
    {
        return {
            callWebApi: function (reqData)
            {
                var dataTemp = {
                    Page: 1, Take: 10,
                    PropName: 'Id', SortOrder: 'Asc'
                };

                return $http({
                    method: 'GET',
                    url: '/api/PatientCategoryApi/PatCat',
                    params: dataTemp, // Parameters to pass to external service
                    headers: { 'Content-Type': 'application/Json' }
                })                
            }
        }
    }
    patientCategoryController = function ($scope, getDataFactory) {
        alert('Hare');
        var promise = getDataFactory.callWebApi('someDataToPass');
        promise.then(
            function successCallback(response) {
                alert(JSON.stringify(response.data));
                // Set this response data to scope to use it in UI
                $scope.gridOptions.data = response.data.Collection;
            }, function errorCallback(response) {
                alert('Some problem while fetching data!!');
            });
    }
    patientCategoryController.$inject = ['$scope', 'getDataFactory'];
    getDataFactory.$inject = ['$http'];
    angular.module('demoApp', []);
    angular.module('demoApp').controller('patientCategoryController', patientCategoryController);
    angular.module('demoApp').factory('getDataFactory', getDataFactory);    
}());

0

Код для роботи зі змінними сфери дії повинен надходити в контролер, а виклики служби - до служби.

Ви можете вводити ін’єкції $rootScopeз метою використання $rootScope.$broadcastта $rootScope.$on.

Інакше уникайте ін’єкцій $rootScope. Побачити

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.