การหน่วงเวลาการเปลี่ยนเส้นทาง AngularJS จนกระทั่งรุ่นโหลดเพื่อป้องกันการสั่นไหว


321

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

ตัวอย่างเช่นหากมีProjectsControllerรายการที่ระบุโครงการทั้งหมดและproject_index.htmlซึ่งเป็นแม่แบบที่แสดงโครงการเหล่านี้Project.query()จะถูกดึงข้อมูลอย่างสมบูรณ์ก่อนที่จะแสดงหน้าใหม่

ก่อนหน้านั้นหน้าเก่าจะยังคงแสดงต่อไป (ตัวอย่างเช่นถ้าฉันเรียกดูหน้าอื่นแล้วตัดสินใจดูดัชนีโครงการนี้)

คำตอบ:


374

$ routeProvider คุณสมบัติการแก้ไขช่วยให้การหน่วงเวลาการเปลี่ยนเส้นทางจนกว่าจะโหลดข้อมูล

ก่อนกำหนดเส้นทางที่มีresolveคุณลักษณะเช่นนี้

angular.module('phonecat', ['phonecatFilters', 'phonecatServices', 'phonecatDirectives']).
  config(['$routeProvider', function($routeProvider) {
    $routeProvider.
      when('/phones', {
        templateUrl: 'partials/phone-list.html', 
        controller: PhoneListCtrl, 
        resolve: PhoneListCtrl.resolve}).
      when('/phones/:phoneId', {
        templateUrl: 'partials/phone-detail.html', 
        controller: PhoneDetailCtrl, 
        resolve: PhoneDetailCtrl.resolve}).
      otherwise({redirectTo: '/phones'});
}]);

สังเกตว่าresolveมีการกำหนดคุณสมบัติบนเส้นทาง

function PhoneListCtrl($scope, phones) {
  $scope.phones = phones;
  $scope.orderProp = 'age';
}

PhoneListCtrl.resolve = {
  phones: function(Phone, $q) {
    // see: https://groups.google.com/forum/?fromgroups=#!topic/angular/DGf7yyD4Oc4
    var deferred = $q.defer();
    Phone.query(function(successData) {
            deferred.resolve(successData); 
    }, function(errorData) {
            deferred.reject(); // you could optionally pass error data here
    });
    return deferred.promise;
  },
  delay: function($q, $defer) {
    var delay = $q.defer();
    $defer(delay.resolve, 1000);
    return delay.promise;
  }
}

ขอให้สังเกตว่าคำจำกัดความของตัวควบคุมประกอบด้วยวัตถุการแก้ไขซึ่งประกาศสิ่งที่ควรมีให้กับตัวสร้างตัวควบคุม นี่phonesคือการฉีดเข้าไปในตัวควบคุมและมันถูกกำหนดไว้ในresolveคุณสมบัติ

resolve.phonesฟังก์ชั่นเป็นผู้รับผิดชอบสำหรับการกลับมาสัญญา สัญญาทั้งหมดได้รับการรวบรวมและการเปลี่ยนเส้นทางล่าช้าจนกว่าจะมีการแก้ไขสัญญาทั้งหมด

ตัวอย่างการทำงาน: http://mhevery.github.com/angular-phonecat/app/#/phones ที่มา: https://github.com/mhevery/ith-phonecat/commit/ba33d3ec2d01b70eb5d3d531619bf90153496831


10
@MiskoHevery - จะเกิดอะไรขึ้นถ้าคอนโทรลเลอร์ของคุณอยู่ในโมดูลและกำหนดเป็นสตริงแทนที่จะเป็นฟังก์ชัน คุณสามารถตั้งค่าแอตทริบิวต์การแก้ไขเช่นเดียวกับคุณได้อย่างไร?
aar0n

53
วิธีนี้ใช้ในangular.controller()คำจำกัดความตัวควบคุมประเภท? ใน$routeProviderสิ่งที่ฉันคิดว่าคุณต้องใช้ชื่อสตริงของตัวควบคุม
Ben Lesh

6
ตัวอย่างใด ๆ ที่ใช้ angular.controller () และกับรุ่นล่าสุดของ AngularJS?
Laurent

22
@blesh เมื่อคุณใช้angular.controller()คุณสามารถกำหนดผลลัพธ์ของฟังก์ชั่นนี้ให้กับตัวแปร ( var MyCtrl = angular.controller(...)) แล้วทำงานกับมันต่อไป ( MyCtrl.loadData = function(){..}) ลองดูวิดีโอของ egghead โค้ดจะแสดงขึ้นมาทันที: egghead.io/video/0uvAseNXDr0
petrkotek

17
ฉันยังคงต้องการวิธีที่ดีในการทำโดยไม่ต้องวางตัวควบคุมของคุณในระดับโลก ฉันไม่ต้องการที่จะทิ้งขยะกับก้อนกลมทั่วสถานที่ คุณสามารถทำได้ด้วยค่าคงที่ แต่มันจะดีถ้าคุณใส่ฟังก์ชั่นแก้ไขบน / ในคอนโทรลเลอร์ไม่ใช่ที่อื่น
Erik Honn

51

นี่คือตัวอย่างการทำงานขั้นต่ำที่ใช้กับ Angular 1.0.2

แม่แบบ:

<script type="text/ng-template" id="/editor-tpl.html">
    Editor Template {{datasets}}
</script>

<div ng-view>

</div>

JavaScript:

function MyCtrl($scope, datasets) {    
    $scope.datasets = datasets;
}

MyCtrl.resolve = {
    datasets : function($q, $http) {
        var deferred = $q.defer();

        $http({method: 'GET', url: '/someUrl'})
            .success(function(data) {
                deferred.resolve(data)
            })
            .error(function(data){
                //actually you'd want deffered.reject(data) here
                //but to show what would happen on success..
                deferred.resolve("error value");
            });

        return deferred.promise;
    }
};

var myApp = angular.module('myApp', [], function($routeProvider) {
    $routeProvider.when('/', {
        templateUrl: '/editor-tpl.html',
        controller: MyCtrl,
        resolve: MyCtrl.resolve
    });
});​

http://jsfiddle.net/dTJ9N/3/

รุ่นที่ปรับปรุง:

เนื่องจาก $ http () คืนสัญญา (หรือรอการตัดบัญชี) เราจึงไม่จำเป็นต้องสร้างของเราเอง ดังนั้นเราสามารถทำให้ MyCtrl ง่ายขึ้น แก้ไขเป็น:

MyCtrl.resolve = {
    datasets : function($http) {
        return $http({
            method: 'GET', 
            url: 'http://fiddle.jshell.net/'
        });
    }
};

ผลของ $ http () ที่มีข้อมูล , สถานะ , ส่วนหัวและการตั้งค่าวัตถุดังนั้นเราจึงจำเป็นต้องเปลี่ยนร่างกายของ MyCtrl ไปที่:

$scope.datasets = datasets.data;

http://jsfiddle.net/dTJ9N/5/


ฉันพยายามทำสิ่งนี้ แต่มีปัญหากับการฉีด 'ชุดข้อมูล' เนื่องจากไม่ได้กำหนดไว้ ความคิดใด ๆ
Rob Bygrave

เฮ้ mb21 ฉันคิดว่าคุณน่าจะช่วยฉันได้ด้วยคำถามนี้: stackoverflow.com/questions/14271713/…
winduptoy

มีคนช่วยฉันแปลงคำตอบนี้เป็นรูปแบบ app.controller ('MyCtrl') ได้ไหม jsfiddle.net/5usya/1ไม่ทำงานสำหรับฉัน
user1071182

ฉันได้รับข้อผิดพลาด:Unknown provider: datasetsProvider <- datasets
chovy

คุณสามารถทำให้คำตอบของคุณง่ายขึ้นโดยแทนที่ชุดข้อมูลด้วย:function($http) { return $http({method: 'GET', url: '/someUrl'}) .then( function(data){ return data;}, function(reason){return 'error value';} ); }
Morteza Tourani

32

ฉันเห็นบางคนถามว่าจะทำอย่างไรโดยใช้วิธีการ angular.controller พร้อมการฉีดแบบพึ่งพาอย่างเป็นมิตร ตั้งแต่ฉันเพิ่งได้งานนี้ฉันรู้สึกว่าจำเป็นต้องกลับมาและช่วย นี่คือทางออกของฉัน (นำมาจากคำถามเดิมและคำตอบของ Misko):

angular.module('phonecat', ['phonecatFilters', 'phonecatServices', 'phonecatDirectives']).
  config(['$routeProvider', function($routeProvider) {
    $routeProvider.
      when('/phones', {
        templateUrl: 'partials/phone-list.html', 
        controller: PhoneListCtrl, 
        resolve: { 
            phones: ["Phone", "$q", function(Phone, $q) {
                var deferred = $q.defer();
                Phone.query(function(successData) {
                  deferred.resolve(successData); 
                }, function(errorData) {
                  deferred.reject(); // you could optionally pass error data here
                });
                return deferred.promise;
             ]
            },
            delay: ["$q","$defer", function($q, $defer) {
               var delay = $q.defer();
               $defer(delay.resolve, 1000);
               return delay.promise;
              }
            ]
        },

        }).
      when('/phones/:phoneId', {
        templateUrl: 'partials/phone-detail.html', 
        controller: PhoneDetailCtrl, 
        resolve: PhoneDetailCtrl.resolve}).
      otherwise({redirectTo: '/phones'});
}]);

angular.controller("PhoneListCtrl", [ "$scope", "phones", ($scope, phones) {
  $scope.phones = phones;
  $scope.orderProp = 'age';
}]);

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

ฉันขอแนะนำวิดีโอ youtube นี้http://www.youtube.com/watch?v=P6KITGRQujQ&list=UUKW92i7iQFuNILqQOUOCrFw&index=4&feature=plcpซึ่งช่วยฉันได้ไม่น้อย

หากคุณสนใจฉันได้ตัดสินใจที่จะวางรหัสของฉันเอง (เขียนด้วยกาแฟ) เพื่อให้คุณเห็นว่าฉันทำงานได้อย่างไร

FYI ล่วงหน้าฉันใช้คอนโทรลเลอร์ทั่วไปที่ช่วยฉันทำ CRUD ในหลายรุ่น:

appModule.config ['$routeProvider', ($routeProvider) ->
  genericControllers = ["boards","teachers","classrooms","students"]
  for controllerName in genericControllers
    $routeProvider
      .when "/#{controllerName}/",
        action: 'confirmLogin'
        controller: 'GenericController'
        controllerName: controllerName
        templateUrl: "/static/templates/#{controllerName}.html"
        resolve:
          items : ["$q", "$route", "$http", ($q, $route, $http) ->
             deferred = $q.defer()
             controllerName = $route.current.controllerName
             $http(
               method: "GET"
               url: "/api/#{controllerName}/"
             )
             .success (response) ->
               deferred.resolve(response.payload)
             .error (response) ->
               deferred.reject(response.message)

             return deferred.promise
          ]

  $routeProvider
    .otherwise
      redirectTo: '/'
      action: 'checkStatus'
]

appModule.controller "GenericController", ["$scope", "$route", "$http", "$cookies", "items", ($scope, $route, $http, $cookies, items) ->

  $scope.items = items
      #etc ....
    ]

ฉันอนุมานอย่างถูกต้องจากตัวอย่างของคุณและความพยายามที่ล้มเหลวซึ่งตอนนี้ไม่สามารถอ้างอิงresolveฟังก์ชันในคอนโทรลเลอร์ใน Angular รุ่นล่าสุดได้หรือไม่ ดังนั้นจะต้องมีการประกาศสิทธิ์ในการกำหนดค่าตามที่นี่หรือไม่
XML

@ XMLilley ฉันค่อนข้างแน่ใจว่าเป็นกรณี ตัวอย่างนี้มาจาก 1.1.2 เมื่อฉันเขียนฉันเชื่อว่า ฉันไม่เห็นเอกสารใด ๆ เกี่ยวกับการแก้ไขปัญหาภายในคอนโทรลเลอร์
bitwit

2
เจ๋งขอบคุณ มีตัวอย่างมากมายของการทำเช่นนั้นบน SO (เหมือนสองอันดับแรกที่นี่) แต่พวกเขาทั้งหมดมาจากปี 2012 และต้นปี 2013 มันเป็นวิธีที่สง่างาม แต่ดูเหมือนจะเลิกใช้แล้ว ทางเลือกที่สะอาดที่สุดในขณะนี้ดูเหมือนว่าจะเขียนบริการส่วนบุคคลที่เป็นวัตถุสัญญา
XML

ขอบคุณสิ่งนี้ใช้ได้สำหรับฉัน สำหรับผู้อื่นที่ได้รับข้อผิดพลาดเกี่ยวกับ$deferบริการที่ไม่ได้กำหนดโปรดทราบว่าใน AngularJS เวอร์ชัน 1.5.7 คุณต้องการใช้$timeoutแทน
racl101

18

นี้กระทำการซึ่งเป็นส่วนหนึ่งของรุ่น 1.1.5 และสูงกว่าตีแผ่วัตถุของ$promise $resourceรุ่นของ ngResource รวมถึงการคอมมิชชันอนุญาตการแก้ไขรีซอร์สเช่นนี้:

$ routeProvider

resolve: {
    data: function(Resource) {
        return Resource.get().$promise;
    }
}

ตัวควบคุม

app.controller('ResourceCtrl', ['$scope', 'data', function($scope, data) {

    $scope.data = data;

}]);

โปรดรวมเวอร์ชันที่ยอมรับได้ไหม
XML

เวอร์ชันที่ไม่เสถียรล่าสุด (1.1.5) รวมการกระทำนี้ ajax.googleapis.com/ajax/libs/angularjs/1.1.5/angular.min.js
Maximilian Hoffmann

ฉันชอบวิธีการ verbose น้อยกว่านี้ คงจะดีถ้าสร้างสัญญาจากวัตถุข้อมูลจริงและส่งผ่านโดยตรง แต่นี่เป็นโค้ดขนาดเล็กที่ทำงานได้ดี
Sam Barnum

1
ทรัพยากรเข้าถึง $ routeParams อย่างไร ตัวอย่างเช่นในGET '/api/1/apps/:appId'-> App.get({id: $routeParams.appId}).$promise();ฉันไม่สามารถใช้สิ่งนี้ได้
zeronone

2
@zeronone คุณ$routeสามารถแก้ไขและใช้งาน$route.current.paramsได้ ระวัง$routeParamsยังชี้ไปที่เส้นทางเก่า
Brice Stacey

16

ตัวอย่างข้อมูลนี้เป็นฉีดพึ่งพาเลี้ยง (ผมยังใช้ในการรวมกันของngminและทำให้น่าเกลียด ) และมันเป็นสง่างามมากขึ้นโดเมนขับเคลื่อนการแก้ปัญหาตาม

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

หมายเหตุ: หากคุณใช้ngmin (และหากไม่ใช่: คุณควร) คุณจะต้องเขียนฟังก์ชันแก้ไขด้วยหลักการ DI array

angular.module('myApp').factory('Phone',function ($resource) {
  return $resource('/api/phone/:id', {id: '@id'});
}).constant('phoneRoutes', {
    '/phone': {
      templateUrl: 'app/phone/index.tmpl.html',
      controller: 'PhoneIndexController'
    },
    '/phone/create': {
      templateUrl: 'app/phone/edit.tmpl.html',
      controller: 'PhoneEditController',
      resolve: {
        phone: ['$route', 'Phone', function ($route, Phone) {
          return new Phone();
        }]
      }
    },
    '/phone/edit/:id': {
      templateUrl: 'app/phone/edit.tmpl.html',
      controller: 'PhoneEditController',
      resolve: {
        form: ['$route', 'Phone', function ($route, Phone) {
          return Phone.get({ id: $route.current.params.id }).$promise;
        }]
      }
    }
  });

ชิ้นต่อไปคือการฉีดข้อมูลเส้นทางเมื่อโมดูลอยู่ในสถานะการกำหนดค่าและใช้มันไป$ routeProvider

angular.module('myApp').config(function ($routeProvider, 
                                         phoneRoutes, 
                                         /* ... otherRoutes ... */) {

  $routeProvider.when('/', { templateUrl: 'app/main/index.tmpl.html' });

  // Loop through all paths provided by the injected route data.

  angular.forEach(phoneRoutes, function(routeData, path) {
    $routeProvider.when(path, routeData);
  });

  $routeProvider.otherwise({ redirectTo: '/' });

});

การทดสอบการกำหนดค่าเส้นทางด้วยการตั้งค่านี้ก็ค่อนข้างง่าย:

describe('phoneRoutes', function() {

  it('should match route configuration', function() {

    module('myApp');

    // Mock the Phone resource
    function PhoneMock() {}
    PhoneMock.get = function() { return {}; };

    module(function($provide) {
      $provide.value('Phone', FormMock);
    });

    inject(function($route, $location, $rootScope, phoneRoutes) {
      angular.forEach(phoneRoutes, function (routeData, path) {

        $location.path(path);
        $rootScope.$digest();

        expect($route.current.templateUrl).toBe(routeData.templateUrl);
        expect($route.current.controller).toBe(routeData.controller);
      });
    });
  });
});

คุณสามารถดูได้ในรัศมีเต็มรูปแบบในล่าสุด (ที่จะเกิดขึ้น) การทดสอบของฉัน แม้ว่าวิธีนี้ใช้ได้ดีสำหรับฉันฉันสงสัยจริงๆว่าทำไม $ injector ไม่สร้างความล่าช้าในการก่อสร้างอะไรเลยเมื่อตรวจพบการฉีดของสิ่งใดก็ตามที่เป็นวัตถุสัญญา มันจะทำให้สิ่งต่าง ๆ soooOOOOOOOOOOOOO ง่ายขึ้นมาก

แก้ไข: used Angular v1.2 (rc2)


2
คำตอบที่ยอดเยี่ยมนี้ดูเหมือนจะสอดคล้องกับปรัชญา "เชิงมุม" (การห่อหุ้ม ฯลฯ ) เราทุกคนควรใช้ความพยายามอย่างมีสติในการหยุดตรรกะไม่ให้คลานไปทั่ว codebase อย่างคุดสุ
zakdances

I really wonder why the $injector isn't delaying construction of anything when it detects injection of anything that is a promise objectฉันเดาว่าพวกเขาจะละเว้นฟังก์ชั่นนี้เพราะมันอาจส่งเสริมรูปแบบการออกแบบที่ส่งผลเสียต่อการตอบสนองของแอพ แอพที่ดีที่สุดในใจของพวกเขาคือแอพแบบอะซิงโครนัสอย่างแท้จริงดังนั้นการแก้ไขควรเป็นกรณีขอบ
zakdances

11

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

$scope.httpStatus = 0; // in progress
$scope.projects = $resource.query('/projects', function() {
    $scope.httpStatus = 200;
  }, function(response) {
    $scope.httpStatus = response.status;
  });

จากนั้นในมุมมองคุณสามารถทำอะไรก็ได้:

<div ng-show="httpStatus == 0">
    Loading
</div>
<div ng-show="httpStatus == 200">
    Real stuff
    <div ng-repeat="project in projects">
         ...
    </div>
</div>
<div ng-show="httpStatus >= 400">
    Error, not found, etc. Could distinguish 4xx not found from 
    5xx server error even.
</div>

6
บางทีการเปิดเผยสถานะ HTTP ไปยังมุมมองนั้นไม่ถูกต้องอีกต่อไปกว่าการจัดการกับคลาส CSS และองค์ประกอบ DOM ที่อยู่ในตัวควบคุม ฉันอาจใช้แนวคิดเดียวกัน แต่สถานะนามธรรมอยู่ใน isValid () และ isLoaded ()
jpsimons

1
นี่ไม่ใช่การแยกข้อกังวลที่ดีที่สุดรวมทั้งมันจะพังถ้าคุณมีตัวควบคุมแบบซ้อนที่ขึ้นอยู่กับวัตถุเฉพาะ
null

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

สิ่งนี้จะใช้ได้ถ้าปัญหาเป็นเพียงการชะลอมุมมอง แต่การแก้ไขจะถูกใช้ดีที่สุดหากคุณต้องการชะลอการสร้างอินสแตนซ์ของคอนโทรลเลอร์ - ไม่ใช่เฉพาะมุมมอง (เช่นถ้าคุณต้องการให้แน่ใจว่าโหลด JSON ของคุณแล้วเนื่องจากตัวควบคุมของคุณส่งไปยังคำสั่งก่อนที่จะต่อสาย) จากเอกสาร: "เราเตอร์จะรอให้พวกเขาแก้ไขทั้งหมดหรือปฏิเสธก่อนที่ตัวควบคุมจะเป็น อินสแตนซ์ "
ด่าน

8

ฉันทำงานจากโค้ดของ Misko ด้านบนและนี่คือสิ่งที่ฉันได้ทำไปแล้ว นี้เป็นทางออกที่เป็นปัจจุบันมากขึ้นนับตั้งแต่มีการเปลี่ยนแปลงไป$defer อย่างไรก็ตามการ$timeoutแทนที่จะ$timeoutรอรอบระยะเวลาการหมดเวลา (ในรหัส Misko ของ 1 วินาที) จากนั้นส่งคืนข้อมูลโดยหวังว่าจะได้รับการแก้ไขในเวลา ด้วยวิธีนี้มันส่งคืนโดยเร็ว

function PhoneListCtrl($scope, phones) {
  $scope.phones = phones;
  $scope.orderProp = 'age';
}

PhoneListCtrl.resolve = {

  phones: function($q, Phone) {
    var deferred = $q.defer();

    Phone.query(function(phones) {
        deferred.resolve(phones);
    });

    return deferred.promise;
  }
}

7

ใช้ AngularJS 1.1.5

การอัปเดตฟังก์ชั่น 'โทรศัพท์' ในคำตอบของ Justen โดยใช้ไวยากรณ์AngularJS 1.1.5

เดิม:

phones: function($q, Phone) {
    var deferred = $q.defer();

    Phone.query(function(phones) {
        deferred.resolve(phones);
    });

    return deferred.promise;
}

Updated:

phones: function(Phone) {
    return Phone.query().$promise;
}

ขอบคุณที่สั้นลงมากสำหรับทีม Angular และผู้สนับสนุน :)

นี่คือคำตอบของ Maximilian Hoffmann เห็นได้ชัดว่าการกระทำทำให้เป็น 1.1.5


1
ฉันไม่สามารถดูเหมือนจะหาอะไรเกี่ยวกับ$promiseในเอกสาร มันอาจถูกนำออกมาตั้งแต่ v2.0 +
zakdances

มีเฉพาะใน 1.2
โทมัส

5

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

angular.module('app', ['ngRoute']).
  config(['$routeProvider', function($routeProvider, EntitiesCtrlResolve, EntityCtrlResolve) {
    $routeProvider.
      when('/entities', {
        templateUrl: 'entities.html', 
        controller: 'EntitiesCtrl', 
        resolve: EntitiesCtrlResolve
      }).
      when('/entity/:entityId', {
        templateUrl: 'entity.html', 
        controller: 'EntityCtrl', 
        resolve: EntityCtrlResolve
      }).
      otherwise({redirectTo: '/entities'});
}]);

ขอให้สังเกตว่าresolveมีการกำหนดคุณสมบัติบนเส้นทาง

EntitiesCtrlResolveและEntityCtrlResolveเป็นวัตถุคงที่กำหนดไว้ในไฟล์เดียวกับEntitiesCtrlและEntityCtrlตัวควบคุม

// EntitiesCtrl.js

angular.module('app').constant('EntitiesCtrlResolve', {
  Entities: function(EntitiesService) {
    return EntitiesService.getAll();
  }
});

angular.module('app').controller('EntitiesCtrl', function(Entities) {
  $scope.entities = Entities;

  // some code..
});

// EntityCtrl.js

angular.module('app').constant('EntityCtrlResolve', {
  Entity: function($route, EntitiesService) {
    return EntitiesService.getById($route.current.params.projectId);
  }
});

angular.module('app').controller('EntityCtrl', function(Entity) {
  $scope.entity = Entity;

  // some code..
});

3

ฉันชอบความคิดของผู้มืดมิดเพราะมันจะเป็นเรื่องง่ายสำหรับทีมพัฒนาที่เพิ่งเริ่มใช้งาน AngularJS เพื่อทำความเข้าใจและทำงานได้ทันที

ฉันสร้างการปรับตัวนี้ซึ่งใช้ 2 divs หนึ่งอันสำหรับโหลดเดอร์บาร์และอีกอันสำหรับเนื้อหาจริงที่แสดงหลังจากโหลดข้อมูลแล้ว การจัดการข้อผิดพลาดจะกระทำที่อื่น

เพิ่มการตั้งค่าสถานะ 'พร้อม' ในขอบเขต $:

$http({method: 'GET', url: '...'}).
    success(function(data, status, headers, config) {
        $scope.dataForView = data;      
        $scope.ready = true;  // <-- set true after loaded
    })
});

ในมุมมอง html:

<div ng-show="!ready">

    <!-- Show loading graphic, e.g. Twitter Boostrap progress bar -->
    <div class="progress progress-striped active">
        <div class="bar" style="width: 100%;"></div>
    </div>

</div>

<div ng-show="ready">

    <!-- Real content goes here and will appear after loading -->

</div>

ดูเพิ่มเติมที่: เอกสารแถบความคืบหน้า Boostrap


แยกกันเล็กน้อยถ้าคุณโหลดข้อมูลหลายชิ้น คุณจะรู้ได้อย่างไรว่าทุกอย่างโหลด?
toxaq

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

ฉันมาถึงช้าไปหน่อย แต่การจัดการกับข้อมูลหลายชิ้นไม่ใช่เรื่องใหญ่ คุณต้องใช้ตัวแปรแยกต่างหาก (booleans: isReadyData1, isReadyData2 ฯลฯ ) สำหรับแต่ละคำขอและตั้ง $ scope.ready = isReadyData1 && isReadyData2 ... ; ทำงานได้ดีสำหรับฉัน
GuillaumeA

1

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

ฉันติดอยู่ในสถานการณ์ที่คล้ายกันซึ่งฉันแก้ไข URL ด้วยข้อมูลบางอย่างที่ดึงมาจากคำขอแรกจากเซิร์ฟเวอร์ ปัญหาที่ฉันเผชิญคือถ้าสัญญาคือrejectedอะไร

ฉันถูกใช้เป็นผู้ให้บริการที่กำหนดเองที่ใช้ในการส่งคืนPromiseซึ่งได้รับการแก้ไขโดยresolveการ$routeProviderที่เวลาของขั้นตอนการตั้งค่า

สิ่งที่ฉันต้องการเน้นที่นี่คือแนวคิดของwhenมันทำอะไรเช่นนี้

มันเห็น url ใน url bar และจากนั้นwhenblock ในส่วนควบคุมที่เรียกว่าและวิวนั้นถูกเรียกว่าดีมาก

ให้บอกว่าฉันมีรหัสเฟส config ต่อไปนี้

App.when('/', {
   templateUrl: '/assets/campaigns/index.html',
   controller: 'CampaignListCtr',
   resolve : {
      Auth : function(){
         return AuthServiceProvider.auth('campaign');
      }
   }
})
// Default route
.otherwise({
   redirectTo: '/segments'
});

บนรูท url ในบล็อกแรกของการเรียกใช้เบราว์เซอร์otherwiseจะถูกเรียกเป็นอย่างอื่น

ลองนึกภาพสถานการณ์ที่ฉันกดรูทUrlในAuthServicePrivider.auth()ฟังก์ชั่นแถบที่อยู่ถูกเรียก

ให้บอกว่าสัญญาที่ส่งคืนอยู่ในสถานะ ปฏิเสธแล้ว ???

ไม่มีอะไรแสดงผลเลย

Otherwise block จะไม่ถูกดำเนินการเนื่องจากเป็น URL ที่ไม่ได้กำหนดไว้ในบล็อกการตั้งค่าและไม่รู้จักเฟสการตั้งค่า angularJs

เราจะต้องจัดการกับเหตุการณ์ที่ถูกไล่ออกเมื่อสัญญานี้ไม่ได้รับการแก้ไข ในความล้มเหลวได้รับการยิง$routeChangeErorr$rootScope

สามารถบันทึกได้ตามที่แสดงในรหัสด้านล่าง

$rootScope.$on('$routeChangeError', function(event, current, previous, rejection){
    // Use params in redirection logic.
    // event is the routeChangeEvent
    // current is the current url
    // previous is the previous url
    $location.path($rootScope.rootPath);
});

IMO เป็นความคิดที่ดีที่จะวางรหัสติดตามเหตุการณ์ไว้ในบล็อกแอพพลิเคชั่น รหัสนี้ทำงานหลังจากขั้นตอนการกำหนดค่าของแอปพลิเคชัน

App.run(['$routeParams', '$rootScope', '$location', function($routeParams, $rootScope, $location){
   $rootScope.rootPath = "my custom path";
   // Event to listen to all the routeChangeErrors raised
   // by the resolve in config part of application
   $rootScope.$on('$routeChangeError', function(event, current, previous, rejection){
       // I am redirecting to rootPath I have set above.
       $location.path($rootScope.rootPath);
   });
}]);

วิธีนี้เราสามารถจัดการสัญญาที่ล้มเหลวในช่วงเวลาของการกำหนดค่า


0

ฉันมีอินเทอร์เฟซแผงเลื่อนแบบหลายระดับที่ซับซ้อนพร้อมเลเยอร์หน้าจอที่ปิดใช้งาน การสร้างคำสั่งในการปิดการใช้งานเลเยอร์หน้าจอที่จะสร้างเหตุการณ์การคลิกเพื่อดำเนินการสถานะเช่น

$state.go('account.stream.social.view');

ถูกผลิตผลสะบัด history.back () แทนที่จะใช้งานได้ แต่มันก็ไม่ได้กลับมาเสมอในประวัติศาสตร์ในกรณีของฉัน ดังนั้นสิ่งที่ฉันพบคือถ้าฉันเพียงสร้างคุณลักษณะ href บนหน้าจอปิดการใช้งานแทน state.go ทำงานเหมือนมีเสน่ห์

<a class="disable-screen" back></a>

คำสั่ง 'ย้อนกลับ'

app.directive('back', [ '$rootScope', function($rootScope) {

    return {
        restrict : 'A',
        link : function(scope, element, attrs) {
            element.attr('href', $rootScope.previousState.replace(/\./gi, '/'));
        }
    };

} ]);

app.js ฉันเพิ่งบันทึกสถานะก่อนหน้า

app.run(function($rootScope, $state) {      

    $rootScope.$on("$stateChangeStart", function(event, toState, toParams, fromState, fromParams) {         

        $rootScope.previousState = fromState.name;
        $rootScope.currentState = toState.name;


    });
});

-2

ทางออกหนึ่งที่เป็นไปได้อาจจะใช้คำสั่ง ng-cloak กับองค์ประกอบที่เรากำลังใช้โมเดลเช่น

<div ng-cloak="">
  Value in  myModel is: {{myModel}}
</div>

ฉันคิดว่าอันนี้ใช้ความพยายามน้อยที่สุด

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