AngularJS: инициализировать сервис с асинхронными данными


475

У меня есть служба AngularJS, которую я хочу инициализировать с некоторыми асинхронными данными. Что-то вроде этого:

myModule.service('MyService', function($http) {
    var myData = null;

    $http.get('data.json').success(function (data) {
        myData = data;
    });

    return {
        setData: function (data) {
            myData = data;
        },
        doStuff: function () {
            return myData.getSomeData();
        }
    };
});

Очевидно, что это не сработает, потому что если что-то попытается вызвать doStuff()до того, как myDataвернется, я получу исключение нулевого указателя. Насколько я могу судить по прочтению некоторых других вопросов, заданных здесь и здесь, у меня есть несколько вариантов, но ни один из них не кажется очень чистым (возможно, я что-то упускаю):

Настройка службы с помощью «Выполнить»

При настройке моего приложения сделайте это:

myApp.run(function ($http, MyService) {
    $http.get('data.json').success(function (data) {
        MyService.setData(data);
    });
});

Тогда мой сервис будет выглядеть так:

myModule.service('MyService', function() {
    var myData = null;
    return {
        setData: function (data) {
            myData = data;
        },
        doStuff: function () {
            return myData.getSomeData();
        }
    };
});

Это работает некоторое время, но если асинхронные данные занимают больше времени, чем требуется для инициализации всего, я получаю исключение нулевого указателя при вызове doStuff()

Используйте обещанные объекты

Это, вероятно, сработает. Единственный недостаток - везде, где я звоню MyService, я должен знать, что doStuff () возвращает обещание, и весь код будет у насthen взаимодействовать с обещанием. Я предпочел бы просто подождать, пока myData вернется, прежде чем загружать мое приложение.

Ручная начальная загрузка

angular.element(document).ready(function() {
    $.getJSON("data.json", function (data) {
       // can't initialize the data here because the service doesn't exist yet
       angular.bootstrap(document);
       // too late to initialize here because something may have already
       // tried to call doStuff() and would have got a null pointer exception
    });
});

Глобальный Javascript Var Я мог бы отправить свой JSON напрямую в глобальную переменную Javascript:

HTML:

<script type="text/javascript" src="data.js"></script>

data.js:

var dataForMyService = { 
// myData here
};

Тогда это будет доступно при инициализации MyService:

myModule.service('MyService', function() {
    var myData = dataForMyService;
    return {
        doStuff: function () {
            return myData.getSomeData();
        }
    };
});

Это тоже будет работать, но тогда у меня есть глобальная переменная javascript, которая плохо пахнет.

Это мои единственные варианты? Один из этих вариантов лучше, чем другие? Я знаю, что это довольно длинный вопрос, но я хотел показать, что я попытался изучить все мои варианты. Любое руководство будет с благодарностью.


angular - при начальной загрузке выполняется асинхронная прогулка по коду для извлечения данных с сервера $http, затем сохранение данных в службе, а затем загрузка приложения.
Стивен Векслер

Ответы:


327

Вы смотрели на $routeProvider.when('/path',{ resolve:{...} ? Это может сделать подход к обещанию немного чище:

Выставьте обещание в вашем сервисе:

app.service('MyService', function($http) {
    var myData = null;

    var promise = $http.get('data.json').success(function (data) {
      myData = data;
    });

    return {
      promise:promise,
      setData: function (data) {
          myData = data;
      },
      doStuff: function () {
          return myData;//.getSomeData();
      }
    };
});

Добавьте resolveк вашему маршруту конфигурации:

app.config(function($routeProvider){
  $routeProvider
    .when('/',{controller:'MainCtrl',
    template:'<div>From MyService:<pre>{{data | json}}</pre></div>',
    resolve:{
      'MyServiceData':function(MyService){
        // MyServiceData will also be injectable in your controller, if you don't want this you could create a new promise with the $q service
        return MyService.promise;
      }
    }})
  }):

Ваш контроллер не будет создан до разрешения всех зависимостей:

app.controller('MainCtrl', function($scope,MyService) {
  console.log('Promise is now resolved: '+MyService.doStuff().data)
  $scope.data = MyService.doStuff();
});

Я сделал пример на plnkr: http://plnkr.co/edit/GKg21XH0RwCMEQGUdZKH?p=preview


1
Большое вам спасибо за ваш ответ! Это будет работать для меня, если у меня еще нет службы на карте разрешения, использующей MyService. Я обновил ваш плункер в моей ситуации: plnkr.co/edit/465Cupaf5mtxljCl5NuF?p=preview . Есть ли способ заставить MyOtherService ждать инициализации MyService?
testing123

2
Думаю, я бы связал обещания в MyOtherService - я обновил плункер цепочкой и некоторыми комментариями - как это выглядит? plnkr.co/edit/Z7dWVNA9P44Q72sLiPjW?p=preview
joakimbl

3
Я попробовал это и все еще столкнулся с некоторыми проблемами, потому что у меня есть директивы и другие контроллеры (контроллер, который я использую с $ routeProvider, обрабатывает основной, вторичный материал навигации ... то есть 'MyOtherService'), который должен ждать до 'MyService решено. Я буду продолжать пытаться и обновлять это с любым успехом, который я имею. Я просто хотел бы, чтобы в angular был хук, где я мог дождаться возвращения данных, прежде чем инициализировать свои контроллеры и директивы. В очередной раз благодарим за помощь. Если бы у меня был главный контроллер, который обернул все, это работало бы.
testing123

43
Вопрос здесь - как вы назначите resolveсвойство контроллеру, который не упомянут в $routeProvider. Например, <div ng-controller="IndexCtrl"></div>. Здесь контроллер явно упоминается и не загружается через маршрутизацию. В таком случае, как тогда задержать создание экземпляра контроллера?
callmekatootie

19
Хм, а если вы не используете маршрутизацию? Это почти то же самое, что сказать, что вы не можете написать угловое приложение с асинхронными данными, если не используете маршрутизацию. Рекомендуемый способ загрузки данных в приложение - это асинхронная загрузка, но как только у вас есть более одного контроллера и вы добавляете сервисы, BOOM становится невозможным.
wired_in

88

Основанное на решении Мартина Аткинса, вот полное и сжатое чисто угловое решение:

(function() {
  var initInjector = angular.injector(['ng']);
  var $http = initInjector.get('$http');
  $http.get('/config.json').then(
    function (response) {
      angular.module('config', []).constant('CONFIG', response.data);

      angular.element(document).ready(function() {
          angular.bootstrap(document, ['myApp']);
        });
    }
  );
})();

Это решение использует самовыполняющуюся анонимную функцию для получения службы $ http, запроса конфигурации и внедрения ее в константу с именем CONFIG, когда она становится доступной.

Полностью, мы ждем, пока документ не будет готов, и затем загружаем приложение Angular.

Это небольшое улучшение по сравнению с решением Мартина, которое откладывает получение конфигурации до тех пор, пока документ не будет готов. Насколько я знаю, нет причин откладывать вызов $ http для этого.

Модульное тестирование

Примечание. Я обнаружил, что это решение не работает при модульном тестировании, когда код включен в ваш app.jsфайл. Причина этого в том, что приведенный выше код запускается сразу после загрузки файла JS. Это означает, что тестовый фреймворк (в моем случае Jasmine) не имеет возможности предоставить имитационную реализацию $http.

Мое решение, которое меня не совсем устраивало, состояло в том, чтобы перенести этот код в наш index.htmlфайл, чтобы инфраструктура модульного тестирования Grunt / Karma / Jasmine его не видела.


1
Такое правило, как «не загрязнять глобальную область действия», должно соблюдаться только в той мере, в которой они делают наш код лучше (менее сложным, более обслуживаемым, более безопасным и т. Д.). Я не вижу, как это решение лучше, чем просто загрузка данных в одну глобальную переменную. Что мне не хватает?
david004

4
Это позволяет вам использовать систему внедрения зависимостей Angular для доступа к константе 'CONFIG' в модулях, которые в ней нуждаются, но вы не рискуете засорять другие модули, которые этого не делают. Например, если вы использовали глобальную переменную 'config', существует вероятность, что другой сторонний код также может искать ту же переменную.
JBCP

1
Я новичок в этой области, вот несколько заметок о том, как я разрешил зависимость модуля конфигурации в моем приложении: gist.github.com/dsulli99/0be3e80db9b21ce7b989 ref: tutorials.jenkov.com/angularjs/… Спасибо за это решение.
дпс

7
Это упомянуто в комментарии одного из других решений по начальной загрузке, приведенного ниже, но, как угловой новичок, который не заметил этого, я могу просто указать, что вам нужно удалить директиву ng-app в вашем HTML-коде, чтобы это работало должным образом - он заменяет автоматический загрузчик (через ng-app) этим ручным методом. Если вы не возьмете приложение ng, приложение может действительно работать, но вы увидите различные ошибки неизвестных поставщиков в консоли.
IrishDubGuy

49

Я использовал подход, аналогичный описанному @XMLilley, но хотел иметь возможность использовать сервисы AngularJS, такие как $httpзагрузка конфигурации и дальнейшая инициализация без использования низкоуровневых API или jQuery.

Использование resolveна маршрутах также не было вариантом, потому что мне нужно, чтобы значения были доступны в качестве констант при запуске моего приложения, даже в module.config()блоках.

Я создал небольшое приложение AngularJS, которое загружает конфигурацию, устанавливает их как константы в реальном приложении и загружает их.

// define the module of your app
angular.module('MyApp', []);

// define the module of the bootstrap app
var bootstrapModule = angular.module('bootstrapModule', []);

// the bootstrapper service loads the config and bootstraps the specified app
bootstrapModule.factory('bootstrapper', function ($http, $log, $q) {
  return {
    bootstrap: function (appName) {
      var deferred = $q.defer();

      $http.get('/some/url')
        .success(function (config) {
          // set all returned values as constants on the app...
          var myApp = angular.module(appName);
          angular.forEach(config, function(value, key){
            myApp.constant(key, value);
          });
          // ...and bootstrap the actual app.
          angular.bootstrap(document, [appName]);
          deferred.resolve();
        })
        .error(function () {
          $log.warn('Could not initialize application, configuration could not be loaded.');
          deferred.reject();
        });

      return deferred.promise;
    }
  };
});

// create a div which is used as the root of the bootstrap app
var appContainer = document.createElement('div');

// in run() function you can now use the bootstrapper service and shutdown the bootstrapping app after initialization of your actual app
bootstrapModule.run(function (bootstrapper) {

  bootstrapper.bootstrap('MyApp').then(function () {
    // removing the container will destroy the bootstrap app
    appContainer.remove();
  });

});

// make sure the DOM is fully loaded before bootstrapping.
angular.element(document).ready(function() {
  angular.bootstrap(appContainer, ['bootstrapModule']);
});

Посмотреть его в действии (используя $timeoutвместо $http) здесь: http://plnkr.co/edit/FYznxP3xe8dxzwxs37hi?p=preview

ОБНОВИТЬ

Я бы рекомендовал использовать подход, описанный ниже Мартином Аткинсом и JBCP.

ОБНОВЛЕНИЕ 2

Поскольку это было необходимо в нескольких проектах, я только что выпустил модуль bower, который позаботится об этом: https://github.com/philippd/angular-deferred-bootstrap

Пример, который загружает данные из серверной части и устанавливает константу APP_CONFIG в модуле AngularJS:

deferredBootstrapper.bootstrap({
  element: document.body,
  module: 'MyApp',
  resolve: {
    APP_CONFIG: function ($http) {
      return $http.get('/api/demo-config');
    }
  }
});

11
deferredBootstrapper
лучший

44

В случае «ручной начальной загрузки» можно получить доступ к службам Angular, вручную создав инжектор перед начальной загрузкой. Этот начальный инжектор будет автономным (не привязанным к каким-либо элементам) и будет включать только подмножество загруженных модулей. Если все, что вам нужно, это основные сервисы Angular, достаточно просто загрузить их ng, например так:

angular.element(document).ready(
    function() {
        var initInjector = angular.injector(['ng']);
        var $http = initInjector.get('$http');
        $http.get('/config.json').then(
            function (response) {
               var config = response.data;
               // Add additional services/constants/variables to your app,
               // and then finally bootstrap it:
               angular.bootstrap(document, ['myApp']);
            }
        );
    }
);

Например, вы можете использовать module.constantмеханизм, чтобы сделать данные доступными для вашего приложения:

myApp.constant('myAppConfig', data);

Это myAppConfigтеперь может быть введена так же , как и любой другой сервис, и , в частности , он доступен на этапе конфигурации:

myApp.config(
    function (myAppConfig, someService) {
        someService.config(myAppConfig.someServiceConfig);
    }
);

или, для небольшого приложения, вы можете просто внедрить глобальную конфигурацию непосредственно в вашу службу, за счет распространения знаний о формате конфигурации по всему приложению.

Конечно, поскольку асинхронные операции здесь блокируют загрузку приложения и, таким образом, блокируют компиляцию / компоновку шаблона, целесообразно использовать ng-cloak директиву, чтобы предотвратить появление необработанного шаблона во время работы. Вы могли бы также предоставить некоторую индикацию загрузки в DOM, предоставив некоторый HTML, который отображается только до инициализации AngularJS:

<div ng-if="initialLoad">
    <!-- initialLoad never gets set, so this div vanishes as soon as Angular is done compiling -->
    <p>Loading the app.....</p>
</div>
<div ng-cloak>
    <!-- ng-cloak attribute is removed once the app is done bootstrapping -->
    <p>Done loading the app!</p>
</div>

Я создал полный рабочий пример этого подхода на Plunker, загружая конфигурацию из статического файла JSON в качестве примера.


Я не думаю, что вам нужно отложить $ http.get () до тех пор, пока документ не будет готов.
JBCP

@JBCP Да, вы правы, что это работает так же хорошо, если вы меняете события так, что мы не ждем, пока документ станет готовым, до тех пор, пока не будет возвращен ответ HTTP, с преимуществом возможности начать HTTP запрос быстрее. Только вызов начальной загрузки должен ждать, пока DOM не будет готов.
Мартин Аткинс

2
Я создал модуль беседки с вашим подходом: github.com/philippd/angular-deferred-bootstrap
philippd

@MartinAtkins, я только что обнаружил, что ваш отличный подход не работает с Angular v1.1 +. Похоже, ранние версии Angular просто не понимают «тогда», пока приложение не будет загружено. Чтобы увидеть это в своем Plunk, замените Angular URL на code.angularjs.org/1.1.5/angular.min.js
vkelman

16

У меня была та же проблема: я люблю resolveобъект, но это работает только для содержания ng-view. Что если у вас есть контроллеры (скажем, для навигации верхнего уровня), которые существуют за пределами ng-view и которые необходимо инициализировать данными, прежде чем маршрутизация даже начнет происходить? Как мы можем избежать обмана на стороне сервера только для того, чтобы заставить это работать?

Используйте ручную загрузку и угловую постоянную . Наивный XHR получает вам ваши данные, и вы загружаете его в свой обратный вызов, который решает ваши проблемы с асинхронностью. В приведенном ниже примере вам даже не нужно создавать глобальную переменную. Возвращенные данные существуют только в угловой области как инъекционные, и даже не присутствуют внутри контроллеров, служб и т. Д., Если вы не внедрили их. (Как бы вы ни вводили вывод вашегоresolve объекта в контроллер для маршрутизируемого представления.) Если вы после этого предпочитаете взаимодействовать с этими данными как сервисом, вы можете создать сервис, внедрить данные, и никто никогда не станет мудрее. ,

Пример:

//First, we have to create the angular module, because all the other JS files are going to load while we're getting data and bootstrapping, and they need to be able to attach to it.
var MyApp = angular.module('MyApp', ['dependency1', 'dependency2']);

// Use angular's version of document.ready() just to make extra-sure DOM is fully 
// loaded before you bootstrap. This is probably optional, given that the async 
// data call will probably take significantly longer than DOM load. YMMV.
// Has the added virtue of keeping your XHR junk out of global scope. 
angular.element(document).ready(function() {

    //first, we create the callback that will fire after the data is down
    function xhrCallback() {
        var myData = this.responseText; // the XHR output

        // here's where we attach a constant containing the API data to our app 
        // module. Don't forget to parse JSON, which `$http` normally does for you.
        MyApp.constant('NavData', JSON.parse(myData));

        // now, perform any other final configuration of your angular module.
        MyApp.config(['$routeProvider', function ($routeProvider) {
            $routeProvider
              .when('/someroute', {configs})
              .otherwise({redirectTo: '/someroute'});
          }]);

        // And last, bootstrap the app. Be sure to remove `ng-app` from your index.html.
        angular.bootstrap(document, ['NYSP']);
    };

    //here, the basic mechanics of the XHR, which you can customize.
    var oReq = new XMLHttpRequest();
    oReq.onload = xhrCallback;
    oReq.open("get", "/api/overview", true); // your specific API URL
    oReq.send();
})

Теперь ваша NavDataконстанта существует. Идите вперед и введите его в контроллер или службу:

angular.module('MyApp')
    .controller('NavCtrl', ['NavData', function (NavData) {
        $scope.localObject = NavData; //now it's addressable in your templates 
}]);

Конечно, использование голого XHR-объекта лишает вас многих тонкостей, которые $httpJQuery позаботится о вас, но этот пример работает без особых зависимостей, по крайней мере, для простого get. Если вы хотите немного больше мощности для вашего запроса, загрузите внешнюю библиотеку, чтобы помочь вам. Но я не думаю, что $httpв этом контексте можно получить доступ к угловым или другим инструментам.

(ТАК связанный пост )


8

В вашем .config для приложения вы можете создать объект разрешения для маршрута и передать функцию в $ q (объект обещания) и имя службы, от которой вы зависите, и разрешить обещание в Функция обратного вызова для $ http в сервисе выглядит так:

МАРШРУТ КОНФИГ

app.config(function($routeProvider){
    $routeProvider
     .when('/',{
          templateUrl: 'home.html',
          controller: 'homeCtrl',
          resolve:function($q,MyService) {
                //create the defer variable and pass it to our service
                var defer = $q.defer();
                MyService.fetchData(defer);
                //this will only return when the promise
                //has been resolved. MyService is going to
                //do that for us
                return defer.promise;
          }
      })
}

Angular не будет отображать шаблон или делать контроллер доступным, пока не будет вызван defer.resolve (). Мы можем сделать это в нашем сервисе:

СЛУЖБА

app.service('MyService',function($http){
       var MyService = {};
       //our service accepts a promise object which 
       //it will resolve on behalf of the calling function
       MyService.fetchData = function(q) {
             $http({method:'GET',url:'data.php'}).success(function(data){
                 MyService.data = data;
                 //when the following is called it will
                 //release the calling function. in this
                 //case it's the resolve function in our
                 //route config
                 q.resolve();
             }
       }

       return MyService;
});

Теперь, когда MyService имеет данные, назначенные его свойству data, и обещание в объекте разрешения маршрута разрешено, наш контроллер для маршрута запускается, и мы можем назначить данные из сервиса нашему объекту контроллера.

КОНТРОЛЛЕР

  app.controller('homeCtrl',function($scope,MyService){
       $scope.servicedata = MyService.data;
  });

Теперь все наши привязки в области действия контроллера смогут использовать данные, полученные от MyService.


Я дам этому шанс, когда у меня будет больше времени. Это похоже на то, что другие пытались сделать в ngModules.
testing123

1
Мне нравится этот подход, и я использовал его раньше, но в настоящее время я пытаюсь понять, как сделать это аккуратно, когда у меня есть несколько маршрутов, каждый из которых может зависеть или не зависеть от предварительно выбранных данных. Есть мысли по этому поводу?
ivarni

Кстати, я склоняюсь к тому, чтобы каждая служба, для которой требуются предварительно выбранные данные, выполняла запрос при его инициализации и возвращала обещание, а затем настраивала решающие объекты со службами, необходимыми для различных маршрутов. Я просто хочу, чтобы был менее многословный путь.
ivarni

1
@dewd Это то, к чему я стремился, но я бы предпочел, чтобы был какой-то способ просто сказать «сначала извлечь все эти вещи независимо от того, какой маршрут загружен», без необходимости повторять мои блоки разрешения. У них у всех есть то, от чего они зависят. Но это не так
уж и сложно,

2
это маршрут, по resolveкоторому я закончил, за исключением того, что мне нужно было создать объект со свойством, являющимся функцией. так что в итоге это былоresolve:{ dataFetch: function(){ // call function here } }
aron.duby

5

Поэтому я нашел решение. Я создал сервис angularJS, мы назовем его MyDataRepository и создал для него модуль. Затем я подаю этот файл javascript с моего контроллера на стороне сервера:

HTML:

<script src="path/myData.js"></script>

Серверный:

@RequestMapping(value="path/myData.js", method=RequestMethod.GET)
public ResponseEntity<String> getMyDataRepositoryJS()
{
    // Populate data that I need into a Map
    Map<String, String> myData = new HashMap<String,String>();
    ...
    // Use Jackson to convert it to JSON
    ObjectMapper mapper = new ObjectMapper();
    String myDataStr = mapper.writeValueAsString(myData);

    // Then create a String that is my javascript file
    String myJS = "'use strict';" +
    "(function() {" +
    "var myDataModule = angular.module('myApp.myData', []);" +
    "myDataModule.service('MyDataRepository', function() {" +
        "var myData = "+myDataStr+";" +
        "return {" +
            "getData: function () {" +
                "return myData;" +
            "}" +
        "}" +
    "});" +
    "})();"

    // Now send it to the client:
    HttpHeaders responseHeaders = new HttpHeaders();
    responseHeaders.add("Content-Type", "text/javascript");
    return new ResponseEntity<String>(myJS , responseHeaders, HttpStatus.OK);
}

Затем я могу добавить MyDataRepository туда, где мне это нужно:

someOtherModule.service('MyOtherService', function(MyDataRepository) {
    var myData = MyDataRepository.getData();
    // Do what you have to do...
}

Это отлично сработало для меня, но я открыт для любых отзывов, если у кого-то есть. }


Мне нравится ваш модульный подход. Я обнаружил, что $ routeScope доступен службе, запрашивающей данные, и вы можете назначить ей данные в обратном вызове $ http.success. Однако использование $ routeScope для неглобальных элементов создает запах, и данные должны быть действительно назначены контроллеру $ scope. К сожалению, я думаю, что ваш подход, хотя и новаторский, не идеален (хотя уважение к тому, чтобы найти то, что работает для вас) Я просто уверен, что должен быть ответ только на стороне клиента, который каким-то образом ожидает данные и позволяет присваивать область. Поиски продолжаются!
роса

В случае, если это кому-то пригодится, я недавно увидел несколько разных подходов к просмотру модулей, которые другие люди написали и добавили на сайт ngModules. Когда у меня будет больше времени, мне придется либо начать использовать один из них, либо выяснить, что они сделали, и добавить его к своим материалам.
testing123

2

Кроме того, вы можете использовать следующие методы для предоставления вашей услуги в глобальном масштабе, прежде чем будут выполнены фактические контроллеры: https://stackoverflow.com/a/27050497/1056679 . Просто разрешите ваши данные глобально, а затем передайте их, например, в службу run.


1

Ты можешь использовать JSONP для асинхронной загрузки служебных данных. Запрос JSONP будет сделан во время начальной загрузки страницы, и результаты будут доступны до запуска вашего приложения. Таким образом, вам не придется раздувать вашу маршрутизацию с избыточными разрешениями.

Ваш HTML будет выглядеть так:

<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script>

function MyService {
  this.getData = function(){
    return   MyService.data;
  }
}
MyService.setData = function(data) {
  MyService.data = data;
}

angular.module('main')
.service('MyService', MyService)

</script>
<script src="/some_data.php?jsonp=MyService.setData"></script>

-1

Самый простой способ получить любую инициализацию - использовать каталог ng-init.

Просто поместите область видимости ng-init, где вы хотите получить данные инициализации

index.html

<div class="frame" ng-init="init()">
    <div class="bit-1">
      <div class="field p-r">
        <label ng-show="regi_step2.address" class="show-hide c-t-1 ng-hide" style="">Country</label>
        <select class="form-control w-100" ng-model="country" name="country" id="country" ng-options="item.name for item in countries" ng-change="stateChanged()" >
        </select>
        <textarea class="form-control w-100" ng-model="regi_step2.address" placeholder="Address" name="address" id="address" ng-required="true" style=""></textarea>
      </div>
    </div>
  </div>

index.js

$scope.init=function(){
    $http({method:'GET',url:'/countries/countries.json'}).success(function(data){
      alert();
           $scope.countries = data;
    });
  };

ПРИМЕЧАНИЕ: вы можете использовать эту методологию, если у вас нет одного и того же кода более чем в одном месте.


Не рекомендуется использовать ngInit в соответствии с документами: docs.angularjs.org/api/ng/directive/ngInit
fodma1
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.