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จะกลับมาฉันจะได้รับข้อยกเว้นตัวชี้ null เท่าที่ฉันสามารถบอกได้จากการอ่านคำถามอื่น ๆ ที่ถามที่นี่และที่นี่ฉันมีตัวเลือกน้อย แต่ไม่มีคำถามใดที่ดูสะอาดมาก (บางทีฉันอาจขาดอะไรซักอย่าง):

บริการติดตั้งด้วย "run"

เมื่อตั้งค่าแอพของฉันทำสิ่งนี้:

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

วิธีนี้ใช้งานได้บางเวลา แต่ถ้าข้อมูลแบบอะซิงโครนัสเกิดขึ้นใช้เวลานานกว่าทุกอย่างในการเริ่มต้นฉันจะได้รับข้อยกเว้นตัวชี้ null เมื่อฉันโทร doStuff()

ใช้วัตถุสัญญา

นี่อาจจะใช้ได้ ข้อเสียเพียงอย่างเดียวทุกที่ที่ฉันเรียก MyService ฉันจะต้องรู้ว่า doStuff () คืนสัญญาและรหัสทั้งหมดจะต้องให้เราthenโต้ตอบกับสัญญา ฉันจะรอจนกว่า 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
    });
});

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

มันก็ใช้ได้เหมือนกัน แต่หลังจากนั้นฉันก็มีตัวแปรจาวาสคริปต์ที่มีกลิ่นไม่ดี

ตัวเลือกเหล่านี้ของฉันเท่านั้นหรือไม่ ตัวเลือกเหล่านี้ดีกว่าตัวเลือกอื่นหรือไม่? ฉันรู้ว่านี่เป็นคำถามที่ค่อนข้างยาว แต่ฉันต้องการแสดงให้เห็นว่าฉันได้พยายามสำรวจตัวเลือกทั้งหมดของฉันแล้ว คำแนะนำใด ๆ จะได้รับการชื่นชมอย่างมาก


angular - bootstrap แบบอะซิงโครนัสเดินผ่านรหัสเพื่อดึงข้อมูลจากเซิร์ฟเวอร์ด้วย$httpจากนั้นบันทึกข้อมูลในบริการจากนั้นบู๊ตแอป
Steven Wexler

คำตอบ:


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 ฉันปรับปรุง plunker ของคุณกับสถานการณ์ของฉัน: plnkr.co/edit/465Cupaf5mtxljCl5NuF?p=preview มีวิธีใดที่ทำให้ MyOtherService รอให้ MyService เริ่มต้นใช้งานได้
test123

2
ฉันเดาว่าฉันจะเชื่อมโยงสัญญาใน MyOtherService - ฉันได้อัปเดตพลั่วเกอร์ด้วยการผูกมัดและความคิดเห็น - ลักษณะนี้มีลักษณะอย่างไร plnkr.co/edit/Z7dWVNA9P44Q72sLiPjW?p=preview
joakimbl

3
ฉันลองสิ่งนี้แล้วยังพบปัญหาบางอย่างเพราะฉันมีคำสั่งและตัวควบคุมอื่น ๆ (ตัวควบคุมที่ฉันใช้กับ $ routeProvider กำลังจัดการสิ่งที่นำทางหลักลำดับที่สอง ... นั่นคือ 'MyOtherService') ที่ต้องรอจนกระทั่ง 'MyService 'ได้รับการแก้ไข ฉันจะพยายามต่อไปและอัปเดตสิ่งนี้ด้วยความสำเร็จที่ฉันมี ฉันแค่หวังว่าจะมีตะขอในมุมที่ฉันสามารถรอให้ข้อมูลกลับมาก่อนที่จะเริ่มต้นตัวควบคุมและคำสั่งของฉัน ขอบคุณอีกครั้งสำหรับความช่วยเหลือของคุณ ถ้าฉันมีตัวควบคุมหลักที่ห่อทุกอย่างนี้จะทำงานได้
ทดสอบ 123

43
คำถามที่นี่ - คุณจะกำหนดresolveคุณสมบัติให้กับคอนโทรลเลอร์ที่ไม่ได้กล่าวถึง$routeProviderได้อย่างไร ตัวอย่างเช่น<div ng-controller="IndexCtrl"></div>. ที่นี่ตัวควบคุมจะถูกกล่าวถึงอย่างชัดเจนและไม่ได้โหลดผ่านการกำหนดเส้นทาง ในกรณีเช่นนี้เราจะชะลอการสร้างอินสแตนซ์ของคอนโทรลเลอร์ได้อย่างไร?
callmekatootie

19
อืมถ้าคุณไม่ได้ใช้เส้นทาง? นี่เป็นเหมือนการบอกว่าคุณไม่สามารถเขียนแอปเชิงมุมด้วยข้อมูลแบบอะซิงโครนัสเว้นแต่ว่าคุณใช้การกำหนดเส้นทาง วิธีที่แนะนำในการรับข้อมูลลงในแอปคือการโหลดแบบอะซิงโครนัส แต่ทันทีที่คุณมีคอนโทรลเลอร์มากกว่าหนึ่งตัวและคุณให้บริการ BOOM เป็นไปไม่ได้
wired_in

88

ขึ้นอยู่กับวิธีแก้ปัญหาของ Martin Atkins นี่คือวิธีการแก้ปัญหาแบบ 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

นี่เป็นการปรับปรุงเล็กน้อยเหนือโซลูชันของ Martin ซึ่งเลื่อนการดึงการกำหนดค่าออกไปจนกระทั่งหลังจากเอกสารพร้อม เท่าที่ฉันรู้ไม่มีเหตุผลที่จะชะลอการเรียก $ http สำหรับสิ่งนั้น

การทดสอบหน่วย

หมายเหตุ: ฉันได้ค้นพบวิธีแก้ปัญหานี้ใช้งานไม่ได้เมื่อทดสอบหน่วยเมื่อรหัสรวมอยู่ในapp.jsไฟล์ของคุณ นี่คือเหตุผลที่รหัสข้างต้นทำงานทันทีเมื่อโหลดไฟล์ JS ซึ่งหมายความว่ากรอบการทดสอบ (จัสมินในกรณีของฉัน) $httpไม่ได้มีโอกาสที่จะให้การดำเนินงานของการเยาะเย้ย

ทางออกของฉันซึ่งฉันไม่พอใจอย่างสมบูรณ์คือการย้ายรหัสนี้ไปยังindex.htmlไฟล์ของเราดังนั้นโครงสร้างการทดสอบหน่วย Grunt / Karma / Jasmine จึงไม่เห็น


1
กฎเช่น 'ไม่สร้างมลภาวะให้กับขอบเขตทั่วโลก' ควรปฏิบัติตามเพียงเท่าที่พวกเขาทำให้โค้ดของเราดีขึ้น (ซับซ้อนน้อยกว่าบำรุงรักษาได้มากขึ้นปลอดภัยกว่า ฯลฯ ) ฉันไม่สามารถเห็นได้ว่าโซลูชันนี้ดีกว่าเพียงแค่การโหลดข้อมูลไปยังตัวแปรส่วนกลางเดียว ฉันพลาดอะไรไป
david004

4
อนุญาตให้คุณใช้ระบบฉีดพึ่งพาของแองกูลาร์เพื่อเข้าถึงค่าคงที่ 'กำหนดค่า' ในโมดูลที่ต้องการ แต่คุณไม่เสี่ยงต่อการอุดตันโมดูลอื่นที่ไม่ต้องการ ตัวอย่างเช่นหากคุณใช้ตัวแปร 'config' ระดับโลกอาจมีโอกาสที่รหัสอื่น ๆ ของบุคคลที่สามอาจมองหาตัวแปรเดียวกัน
JBCP

1
ฉันเป็นมือใหม่เชิงมุมนี่คือบันทึกบางส่วนเกี่ยวกับวิธีที่ฉันได้รับการแก้ไขการพึ่งพาโมดูล config ในแอปของฉัน: gist.github.com/dsulli99/0be3e80db9b21ce7b989 อ้างอิง: tutorials.jenkov.com/angularjs/ขอบคุณสำหรับการแก้ปัญหานี้
dps

7
มันถูกกล่าวถึงในความคิดเห็นในหนึ่งในวิธีการแก้ปัญหา bootstrap ด้วยตนเองอื่น ๆ ด้านล่าง แต่เป็นมือใหม่เชิงมุมที่ไม่เห็นมันฉันสามารถชี้ให้เห็นว่าคุณต้องลบคำสั่ง ng-app ของคุณในรหัส html ของคุณเพื่อให้ทำงานได้อย่างถูกต้อง - มันกำลังแทนที่ bootstrap อัตโนมัติ (ผ่าน ng-app) ด้วยวิธีการด้วยตนเองนี้ หากคุณไม่นำแอพออกมาแอปพลิเคชันอาจใช้งานได้จริง แต่คุณจะเห็นข้อผิดพลาดของผู้ให้บริการที่ไม่รู้จักในคอนโซล
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

UPDATE

ฉันขอแนะนำให้ใช้วิธีการที่อธิบายไว้ด้านล่างโดย Martin Atkins และ 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 เป็นหนทางไป
fatlinesofcode

44

กรณี "bootstrap ด้วยตนเอง" สามารถเข้าถึงบริการ 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);
    }
);

หรือสำหรับแอปที่มีขนาดเล็กลงคุณสามารถฉีดการกำหนดค่าทั่วโลกลงในบริการของคุณได้โดยตรงโดยเสียค่าใช้จ่ายในการเผยแพร่ความรู้เกี่ยวกับรูปแบบการกำหนดค่าทั่วทั้งแอปพลิเคชัน

แน่นอนเนื่องจากการดำเนินการ async ที่นี่จะบล็อก bootstrap ของแอปพลิเคชันและทำให้บล็อกการรวบรวม / การเชื่อมโยงของเทมเพลตจึงควรใช้ 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 ขอเร็วขึ้น เฉพาะการโทร bootstrap เท่านั้นที่จะต้องรอจนกว่า DOM พร้อม
Martin Atkins

2
ฉันสร้างโมดูล bower ด้วยวิธีการของคุณ: 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 เท่านั้น ถ้าคุณมีคอนโทรลเลอร์ (สำหรับการนำทางระดับบนสุดสมมติว่ามี) ที่อยู่นอกมุมมอง ng และต้องเริ่มต้นด้วยข้อมูลก่อนที่เส้นทางจะเริ่มเกิดขึ้น เราจะหลีกเลี่ยงการล้อเล่นบนฝั่งเซิร์ฟเวอร์เพื่อให้ทำงานได้อย่างไร

ใช้บูตคู่มือและคงเชิงมุม XHR ในแบบ naiive ทำให้คุณได้รับข้อมูลของคุณและคุณบูตบู๊ตเชิงมุมในการติดต่อกลับซึ่งเกี่ยวข้องกับปัญหาการซิงค์ของคุณ ในตัวอย่างด้านล่างคุณไม่จำเป็นต้องสร้างตัวแปรส่วนกลาง ข้อมูลที่ส่งคืนจะมีเฉพาะในขอบเขตเชิงมุมในฐานะที่เป็นแบบฉีดและไม่มีแม้แต่ในตัวควบคุมบริการและอื่น ๆ เว้นแต่ว่าคุณจะฉีดข้อมูล (มากเท่าที่คุณจะฉีดเอาท์พุทของ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 เปลือยออกไปแถบจำนวนของ niceties ที่$httpหรือ JQuery จะดูแลสำหรับคุณ 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;
          }
      })
}

เชิงมุมจะไม่แสดงผลแม่แบบหรือทำให้ตัวควบคุมพร้อมใช้งานจนกว่าจะมีการเรียกใช้ 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 มีข้อมูลที่กำหนดให้กับคุณสมบัติข้อมูลและสัญญาในวัตถุการแก้ไขเส้นทางได้รับการแก้ไขแล้วคอนโทรลเลอร์ของเราสำหรับเส้นทางเริ่มทำงานและเราสามารถกำหนดข้อมูลจากบริการไปยังวัตถุคอนโทรลเลอร์ของเรา

CONTROLLER

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

ตอนนี้การรวมทั้งหมดของเราในขอบเขตของคอนโทรลเลอร์จะสามารถใช้ข้อมูลที่มาจาก MyService ได้


ฉันจะให้ช็อตนี้เมื่อฉันมีเวลามากขึ้น นี่คล้ายกับสิ่งที่คนอื่นพยายามทำใน ngModules
ทดสอบ 123

1
ฉันชอบวิธีการนี้และฉันเคยใช้มาก่อน แต่ตอนนี้ฉันกำลังพยายามหาวิธีทำอย่างสะอาดเมื่อฉันมีเส้นทางหลายเส้นทางแต่ละเส้นทางอาจขึ้นหรือไม่ขึ้นอยู่กับข้อมูลที่ดึงมาล่วงหน้า ความคิดใด ๆ
ivarni

btw ฉันกำลังโน้มตัวไปข้างหน้าเพื่อให้แต่ละบริการที่ต้องการข้อมูล prefetched ร้องขอเมื่อมันเริ่มต้นและกลับสัญญาแล้วตั้งค่าการแก้ไขวัตถุที่มีบริการที่ต้องการโดยเส้นทางที่แตกต่างกัน ฉันแค่หวังว่าจะมีวิธี verbose น้อยกว่า
ivarni

1
@dewd นั่นคือสิ่งที่ฉันตั้งใจ แต่ฉันจะชอบมากถ้ามีวิธีที่จะพูดว่า "ดึงสิ่งทั้งหมดนี้ก่อนโดยไม่คำนึงถึงเส้นทางที่โหลด" โดยไม่ต้องทำบล็อกแก้ปัญหาซ้ำอีก พวกเขามีสิ่งที่พวกเขาพึ่งพา แต่มันไม่ได้เป็นเรื่องใหญ่มันก็จะรู้สึกแห้งเล็ก ๆ น้อย ๆ :)
ivarni

2
นี่คือเส้นทางที่ฉันได้รับยกเว้นว่าฉันต้องทำresolveวัตถุที่มีคุณสมบัติเป็นฟังก์ชั่น ดังนั้นจึงเป็นได้resolve:{ dataFetch: function(){ // call function here } }
aron.duby

5

ดังนั้นฉันจึงพบวิธีแก้ปัญหา ฉันสร้างบริการ angularJS เราจะเรียกมันว่า MyDataRepository และฉันสร้างโมดูลขึ้นมา ฉันจะให้บริการไฟล์จาวาสคริปต์นี้จากคอนโทรลเลอร์ฝั่งเซิร์ฟเวอร์ของฉัน:

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 สำหรับรายการที่ไม่ใช่สากลสร้างกลิ่นและข้อมูลควรถูกกำหนดให้กับขอบเขต $ controller น่าเสียดายที่ฉันคิดว่าแนวทางของคุณในขณะที่สร้างสรรค์ไม่เหมาะ (ให้ความเคารพในการค้นหาสิ่งที่เหมาะกับคุณ) ฉันแค่แน่ใจว่าต้องมีคำตอบจากฝั่งลูกค้าเท่านั้นซึ่งต้องรอข้อมูลและอนุญาตการมอบหมายขอบเขต การค้นหาดำเนินต่อไป!
น้ำค้าง

ในกรณีที่เป็นประโยชน์กับบางคนฉันเพิ่งเห็นวิธีการที่แตกต่างกันในการดูโมดูลที่คนอื่น ๆ เขียนและเพิ่มในเว็บไซต์ ngModules เมื่อฉันมีเวลามากขึ้นฉันจะต้องเริ่มใช้อย่างใดอย่างหนึ่งหรือหาว่าพวกเขาทำอะไรและเพิ่มลงในสิ่งของของฉัน
test123

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

เพียงแค่วางขอบเขต 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;
    });
  };

หมายเหตุ:คุณสามารถใช้วิธีการนี้หากคุณไม่มีรหัสเดียวกันมากกว่าหนึ่งที่


ไม่แนะนำให้ใช้ ngInit ตามเอกสาร: docs.angularjs.org/api/ng/directive/ngInit
fodma1
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.