321

各モデルとそのデータがそれぞれのサービスを使用してフェッチされるまで、 AngularJS が新しいルートの表示を遅らせる方法 (Gmail と同様) があるかどうか疑問に思っています。

たとえば、ProjectsControllerすべてのプロジェクトを一覧表示しproject_index.html、これらのプロジェクトを表示するテンプレートがあった場合Project.query()、新しいページを表示する前に が完全に取得されます。

それまでは、古いページが引き続き表示されます (たとえば、別のページを閲覧していて、このプロジェクトのインデックスを表示することにした場合)。

4

13 に答える 13

375

$routeProvider resolveプロパティを使用すると、データがロードされるまでルート変更を遅らせることができます。

最初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関数は promise を返す責任があります。すべてのプロミスが収集され、ルート変更はすべてのプロミスが解決されるまで延期されます。

実際のデモ : http://mhevery.github.com/angular-phonecat/app/#/phones

于 2012-08-15T15:12:59.057 に答える
51

これはAngular1.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()はすでにpromise(別名defered)を返すため、実際には独自のpromiseを作成する必要はありません。したがって、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/

于 2012-09-10T23:43:56.440 に答える
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';
}]);

このコードは質問/最も人気のある回答から派生したものであるため、テストされていませんが、縮小に適した角度コードを作成する方法を既に理解している場合は、正しい方向に進むはずです. 私自身のコードが必要としなかった 1 つの部分は、「電話」の解決関数への「電話」の挿入であり、「遅延」オブジェクトもまったく使用しませんでした。

この YouTube ビデオhttp://www.youtube.com/watch?v=P6KITGRQujQ&list=UUKW92i7iQFuNILqQOUOCrFw&index=4&feature=plcpもお勧めします。

興味があれば、私自身のコード (coffeescript で書かれたもの) も貼り付けることにしたので、どのように動作したかを確認できます。

参考までに、事前に、いくつかのモデルで 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 ....
    ]
于 2013-03-14T19:11:38.267 に答える
18

バージョン 1.1.5 以降の一部であるこの commit$promiseは、 のオブジェクトを公開します$resource。このコミットを含む ngResource のバージョンでは、次のようなリソースを解決できます。

$routeProvider

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

コントローラ

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

    $scope.data = data;

}]);
于 2013-05-26T12:06:31.400 に答える
16

このスニペットは依存性注入に対応しており (私はngminuglifyを組み合わせて使用​​しています)、より洗練されたドメイン駆動ベースのソリューションです。

以下の例では、Phone リソース定数 phoneRoutesを登録します。これには、その (電話) ドメインのすべてのルーティング情報が含まれます。提供された回答で気に入らなかったのは、解決ロジックの場所でした-メインモジュールは、リソース引数がコントローラーに提供される方法について何も知らないか、気にする必要はありません。このようにして、ロジックは同じドメインに留まります。

注: ngminを使用している場合 (使用していない場合: 使用する必要があります)、DI 配列規則を使用して解決関数を記述するだけで済みます。

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

私の最新の(今後の)実験では、それが見事に見られます。このメソッドは私にとっては問題なく動作しますが、Promiseオブジェクトである何かの注入を検出したときに$injector が何かの構築を遅らせないのはなぜなのか、本当に疑問に思っています。それは物事をすっごく簡単にします。

編集:Angular v1.2(rc2)を使用

于 2013-10-06T21:01:13.670 に答える
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>
于 2012-09-03T17:59:53.767 に答える
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;
  }
}
于 2012-09-07T05:04:46.757 に答える
7

AngularJS 1.1.5 の使用

AngularJS 1.1.5構文を使用して、Justen の回答の「電話」機能を更新します。

オリジナル:

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

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

    return deferred.promise;
}

更新しました:

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

Angular チームと貢献者のおかげで、かなり短くなりました。:)

これはマクシミリアン・ホフマンの答えでもあります。どうやらそのコミットは1.1.5になりました。

于 2013-07-28T07:03:39.200 に答える
5

$routeProvider resolveプロパティを使用して、データが読み込まれるまでルートの変更を遅らせることができます。

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プロパティがルートで定義されていることに注意してください。

EntitiesCtrlResolveandEntityCtrlResolveは、 andコントローラーと同じファイルで定義された定数オブジェクトです。EntitiesCtrlEntityCtrl

// 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..
});
于 2014-11-14T12:04:21.357 に答える
3

AngularJS を初めて使用する開発チームが理解しやすく、すぐに作業できるため、darkporter のアイデアが気に入っています。

2 つの div を使用するこの適応を作成しました。1 つはローダー バー用で、もう 1 つはデータが読み込まれた後に表示される実際のコンテンツ用です。エラー処理は別の場所で行われます。

$scope に「準備完了」フラグを追加します。

$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 プログレス バーのドキュメント

于 2013-02-27T18:51:03.957 に答える
0

画面レイヤーが無効になっている、複雑なマルチレベルのスライド パネル インターフェイスがありました。次のような状態を実行するためのクリックイベントを作成するディレクティブを無効スクリーンレイヤーに作成します

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

フリック効果を生み出していました。history.back() の代わりに正常に機能しましたが、私の場合は常に履歴に戻るとは限りません。SO私が見つけたのは、無効化画面で state.go の代わりに属性 href を作成するだけで、魅力的に機能したことです。

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


    });
});
于 2016-01-08T01:51:23.603 に答える
-2

考えられる解決策の 1 つは、モデルを使用している要素で ng-cloak ディレクティブを使用することです。

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

これが一番手間がかからないと思います。

于 2014-10-03T18:08:34.100 に答える