[Javascript] AngularJS: تهيئة الخدمة بالبيانات غير المتزامنة


Answers

استنادًا إلى حل مارتن أتكينز ، نقدم لك حل Angry Angular Angular:

(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. هذا يعني أن إطار الاختبار (ياسمين في حالتي) لا تتوفر له فرصة تقديم تطبيق وهمي لـ $http .

الحل الذي لم أكن راضيًا عنه تمامًا ، كان نقل هذا الرمز إلى ملف index.html بنا ، لذلك لا تراه البنية التحتية لاختبار وحدة Grunt / Karma / Jasmine.

Question

لدي خدمة 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 سأحصل على استثناء مؤشر فارغة. بقدر ما استطيع ان اقول من قراءة بعض الأسئلة الأخرى here here لدي بعض الخيارات ، ولكن أيا منها لا يبدو نظيفا جدا (ربما أفتقد شيئا):

خدمة الإعداد مع "تشغيل"

عند إعداد تطبيقي ، افعل ذلك:

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() تهيئته ، أحصل على استثناء فارغ عند استدعاء doStuff()

استخدام الكائنات الوعد

هذا من المحتمل أن يعمل. الجانب السلبي الوحيد في كل مكان أسميه MyService سيكون عليّ أن أعرف أن doStuff () يعيد الوعد ، وكل الكود سيكون عندنا للتفاعل مع الوعد. أفضل بدلاً من الانتظار حتى تعود myData قبل تحميل التطبيق الخاص بي.

Bootstrap اليدوي

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

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

هذا من شأنه أن يعمل أيضا ، ولكن بعد ذلك لدي متغير جافا سكريبت العالمي الذي رائحة سيئة.

هل هذه خياراتي الوحيدة؟ هل أحد هذه الخيارات أفضل من الآخرين؟ أعلم أن هذا سؤال طويل جدًا ، لكنني أردت أن أثبت أنني حاولت استكشاف كل الخيارات المتاحة أمامي. أي توجيه سيكون موضع تقدير كبير.




ما يمكنك القيام به هو في .config الخاص بالتطبيق هو إنشاء كائن التصميم للمسار وفي تمرير الوظيفة في $ q (وعد الكائن) واسم الخدمة التي تعتمد عليها ، وحل الوعد في وظيفة رد الاتصال لـ http $ في الخدمة مثل:

ROUTE CONFIG

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

لن يؤدي الزاوي إلى عرض القالب أو إتاحة وحدة التحكم حتى يتم استدعاء 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 بتخصيص البيانات لخاصية البيانات الخاصة بها ، وتم حل الوعد في مسار حل المشكلة ، تبدأ وحدة التحكم الخاصة بنا في المسار في الحياة ، ويمكننا تخصيص البيانات من الخدمة إلى كائن وحدة التحكم الخاصة بنا.

مراقب

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

الآن سوف يكون كل ما لدينا في نطاق وحدة التحكم قادراً على استخدام البيانات التي نشأت من MyService.




يمكنك أيضًا استخدام الأساليب التالية لتوفير الخدمة على مستوى العالم قبل تنفيذ وحدات التحكم الفعلية: https://.com/a/27050497/1056679 . فقط حل البيانات الخاصة بك على الصعيد العالمي وثم تمريرها إلى الخدمة الخاصة بك في كتلة run على سبيل المثال.




أسهل طريقة لجلب أي دليل استخدام دليل ng-init.

ما عليك سوى وضع نطاق div لـ ng-init حيث تريد جلب بيانات 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;
    });
  };

ملاحظة: يمكنك استخدام هذه المنهجية إذا لم يكن لديك نفس الرمز أكثر من مكان واحد.




يمكن لحالة "bootstrap اليدوي" الوصول إلى الخدمات الزاوي عن طريق إنشاء حاقن يدويًا قبل بدء التشغيل. سيكون هذا الحاقن الأولي قائمًا بذاته (لا يتم ربطه بأي عناصر) ويتضمن فقط مجموعة فرعية من الوحدات التي تم تحميلها. إذا كان كل ما تحتاج إليه هو خدمات Angular الأساسية ، يكفي تحميل ng فقط ، على 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 ثابت كمثال.