65.9K
CodeProject 正在变化。 阅读更多。
Home

AngularJS: 带有加载条的路由

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.96/5 (6投票s)

2015年2月8日

CPOL

5分钟阅读

viewsIcon

44111

downloadIcon

605

一个使用GitHub的Angular应用程序,主要用于演示angular-loading-bar和ui-router的使用,以便在较长的请求期间提供更好的反馈。

引言

在开发Angular应用程序时,请求有时需要一些时间来处理。您会给用户什么样的指示来表明请求仍在加载?如果您开发了自定义解决方案,如何使其在所有HTTP请求中都可重用?

我们将使用angular-loading-bar库来解决这个问题。页面顶部有一个小的进度条,类似于您最近在YouTube上看到的。

此外,我们将使用ui-router库。这将使我们能够创建一个灵活的路由系统。我将在本文后面详细介绍。

最后,我们将构建一个体面的AngularJS应用程序,其中包含一些新概念,如一次性绑定和控制器别名。我们还将使我们的应用程序模块化。

必备组件

  1. AngularJs - 我不会介绍模块、控制器、服务和指令等简单概念。这些概念不是此项目的目标,但具备一些知识将有帮助。
  2. JavaScript/HTML - 当然

我们要构建什么:一个简单的GitHub应用程序(噔!噔!噔!)

为了涵盖这些示例,我们将构建一个非常简单的GitHub应用程序,该应用程序将搜索用户的仓库,并允许用户查看一些仓库的详细信息。应用程序外观如下。

这是一个非常简单的应用程序,主要用于涵盖一些简单概念。

背景

在回顾代码之前,我想先介绍一些概念。

IIFE 模式

我使用了这种模式以及“use strict”来保持代码的整洁、模块化,并将其与全局作用域隔离开。IIFE 是一个在页面加载时执行的匿名函数。代码被包装在匿名函数中,以避免与全局作用域发生任何冲突。示例如下:

(function () {
    'use strict';
    // Any code you execute here gets executed upon script load.
}());

AngularJs 一次性绑定 {{::$scope.title}}

如果您还不知道,这是v1.3中的一项新功能。“::”允许您进行一次性绑定。您应该谨慎使用此功能。您不希望错误地对您可能期望稍后更新的属性进行一次性绑定。另一方面,在可能的情况下使用它,例如当绑定的数据实际上是静态的时,比如一个百万行的表。(提示:避免这样!)

AngularJs 控制器“as”

这是v1.2中引入的一项功能。它允许您在模板HTML中使用控制器中的“this”作用域对象,而不是注入$scope。您可以在此处阅读更多内容。该项目中的控制器使用此语法,而不是$scope

angular-loading-bar

这是一个很酷的项目,我真的很喜欢。将“angular-loading-bar”注入您的应用程序后,它会自动跟踪$http$resource请求,并显示加载条。加载条会自动显示,您无需做任何事情!

这是项目页面,您可以在此处或MaxCDN获取脚本。

var app = angular.module("gitHubApp", ["angular-loading-bar"]);

ui-router

这是一个基于状态的Angular路由系统。与ng-router不同,它不基于URL,而是基于应用程序的状态。您可以定义状态,例如:productEditproductViewproductsAll。对于每个状态,您可以指定一个控制器、模板、解析(resolve)、抽象(abstract)和其他一些属性。UI-router还支持使用部分状态(partial states)进行视图分割。这个概念超出了本文的范围。您可以在此处阅读有关部分状态的更多信息。

定义路由

此代码已注释,以便于理解。

var app = angular.module("gitHubApp", ["ui.router"]);
app.config(
[
    "$stateProvider",
        function ($stateProvider) {
            $stateProvider
                // The search state : loads the search page
                .state("search", {
                    // The Url to set for this state
                    url: "/", 
                    // The template to load
                    templateUrl: "app/github/views/searchView.html",
                    // Set the controller using the "as" feature released in v1.2.
                    controller: "SearchController as vm"
                })
                // Repository Detail state
                .state("repoDetail", {
                    // Set the url and accept two query string parameters.
                    url: "/repo/:username/:repoName",
                    templateUrl: "app/github/views/repoDetailView.html",
                    controller: "RepoDetailController as vm",
                    // Resolve : This is called before the template or controller is loaded.
                    // This is perfect since it makes the $http request 
                    // and the loading bar shows
                    // but the template loads after our data loads.
                    resolve: {
                        // DI, inject this service into repoDetail() below
                        gitHubService: "gitHubService",
                        // repoDetail is another DI method, which will be resolved 
                        // and injected into our controller.
                        // $stateParams is automatically injected into the function
                        repoDetail: function (gitHubService, $stateParams) {
                            return gitHubService.getRepoInfo
                                ($stateParams.username, $stateParams.repoName);
                        }
                    }
                });
    }
]);

在本文的下面,您可以找到仓库视图控制器(Repository View Controller)的代码,并了解resolve如何注入repoDetail

从控制器加载状态

在控制器中,您可以通过注入$state然后调用$state(stateName, {optionStates});来加载新状态。

示例

// Create out controller and inject the $state
angular.module("gitHubApp").controller("SearchController", ["$state", searchController]);
function searchController($state) {
    var vm = this;
    vm.viewDetail = function() {
        // Specify the stateName and pass in the parameters required by that state.
        $state.go("repoDetail", {username: vm.username, repoName: vm.repoName});
        // If no paramaters, then simple $state.go("repoDetail");
    }
}

从视图加载状态

您可以使用ui-srefdata-ui-sref来指定状态和附加参数。

<a ui-sref="repoDetail({username: result.owner.login, repoName: result.name})">
{{::result.full_name}}</a>
<a ui-sref="search">go back</a>

Using the Code

您已经在上面ui-routerangular-loading-bar部分的代码中看到了应用程序的代码。在这里,我将重点介绍其他部分。

项目组织

项目组织方式如下:

我认为将模块放在app目录中并分组相关的js和视图文件更容易。这使得查找相关的视图和控制器文件更加容易。通用服务、指令、过滤器等都放在common目录中。

至于命名约定,它是*Controller.js*View.html。例如:searchController.jssearchView.html

GitHub服务

我最终为GitHub API调用创建了一个单独的服务。我不想在这个简单的例子中使事情过于复杂。代码非常直观。

(function () {
    'use strict';
    
    angular.module("gitHubApp").service("gitHubService", ["$http", gitHubService]);
    
    function gitHubService($http) {
        var getReposByUser = function (query) {
            return $http.get("https://api.github.com/users/" + 
                                  encodeURIComponent(query) + "/repos");
        }

        var getRepoInfo = function (username, repoName) {
            return $http.get("https://api.github.com/repos/" + 
                   encodeURIComponent(username) + "/" + encodeURIComponent(repoName));
        }

        return {
            getReposByUser: getReposByUser,
            getRepoInfo: getRepoInfo
        };
    }
}());

搜索控制器

此搜索控制器主要负责处理搜索Web服务调用以获取GitHub结果。

(function () {
    'use strict';

    angular.module("gitHubApp").controller
                  ("SearchController", ["gitHubService", searchController]);
    function searchController(gitHubService) {
        var vm = this;
        // Search function called when search button is clicked
        vm.search = function () {
            // Check if our vm.query is specified.
            if (!vm.query || vm.query.length == 0) {
                alert('Please specify a search query');
                return;
            }
            
            // Make call to webservice to get repos for the user.
            gitHubService.getReposByUser(vm.query).then(function (response) {
                // We can handle error using response.status != 200, 
                // or using the sucess() error() callbacks, instead of then()
                vm.results = response.data;
            });
        }
    }
}());

搜索视图

我们添加了一个搜索UI和结果表。搜索输入绑定到ng-model="vm.query"。结果表仅在有有效结果时显示。我们还有几个验证div来显示可能的错误。

<!-- Since we use Controller as vm, the vm property automatically gets injected here. -->
<div>
    <label for="inputUsername">Username:</label>
    <!-- The Search Input : binds to vm.query -->
    <input type="text" class="form-control" placeholder="Username" 
                       id="inputUsername" ng-model="vm.query" required>
    <!-- Search button fires the search using ng-click -->
    <button class="btn btn-primary" type="button" ng-click="vm.search();">Search</button>
</div>

<!-- Possibly invalid search query. -->
<div class="alert alert-warning" ng-if="vm.query && vm.results.message">
    No results found. Please try again.
</div>
<div class="alert alert-warning" ng-if="vm.results && vm.results.length == 0">
    No repos found. Please try again.
</div>

<!-- Go through our results, if any, and display them -->
<table class="table table-striped" ng-if="vm.results">
    <thead>
        <tr>
            <th>Id</th>
        </tr>
    </thead>
    <tbody>
        <tr ng-repeat="result in vm.results">
            <!-- "::" is one time binding released in AngularJs 1.3-->
            <td>{{::result.id}}</td>
        </tr>
    </tbody>
</table>

仓库详情控制器

这更有趣。:) 如果您还记得ui-router的状态“repoDetail”,您会记得我们使用了resolve属性来返回一个repoDetail对象。如果您不记得了,请参考上面的ui-router。代码如下:

(function () {
    'use strict';

    angular.module("gitHubApp").controller("RepoDetailController", 
                                ["repoDetail", repoDetailController]);
    // repoDetail automatically resolved and got injected here from the state.
    function repoDetailController(repoDetail) {
        var vm = this;
        vm.repo = repoDetail.data; // Use statusText != 200 to catch errors
    }

}());

FYI:控制器和模板不会加载,直到resolve函数完成。然而,angular-loading-bar会显示进度条,指示内容正在加载,这非常棒。

仓库详情视图

这里没有什么太有趣的事情可以分享,除了返回按钮,它使用了ui-sref

<!-- ui-sref="stateName" -->
<a class="btn btn-primary" ui-sref="search" style="margin-top: 10px;">go back</a>

最终应用程序

最终应用程序建立在BootStrap之上,并包含更多细节。同样,这是一个概念应用程序。目标是保持简单并涵盖不同的概念。

关注点

我认为angular-loading-barui-routerresolve属性的组合效果非常好!它的工作方式就像浏览器一样,用户可以预期。当请求正在处理时,您会看到一个进度条,当内容加载时,您会看到实际的视图。

ui-router库也非常强大。状态的概念使应用程序的开发和管理非常简单明了。

最后但同样重要的是,使用{::property}进行一次性绑定非常棒。这是我期待了很长时间的功能。(即使我上个月才开始接触Angular,我也一直在关注绑定性能的提升)。

历史

  1. 发布日期:2014年7月2日:添加了简单的GitHub应用程序,该应用程序可以拉取用户的仓库并显示仓库详情。该应用程序还演示了ui-routerangular-loading-bar组件。
© . All rights reserved.