UI-Router:为啥开发者都不喜欢Angular.js内置的路由

Angular.js
是一个用来构建“富客户端”的神奇JavaScript框架。但是事实却是许多开发者却不利用其内置的路由模块。反而接纳AngularUI项目的
UI-Router模块来代表之。

这是因为UI-Router有多少个基本点的表征:

  • 多样化视图
  • 嵌入式视图

这篇小说将表明这六个特色,和运用现实生活的事例显示这六个特征的显要。

为什么您需要运用UI-Router

多样化视图

多数的应用程序都得以分解为一个一个区块。最简易的事态,一个应用程序有头部(header),主体内容(main
content area),以及一个尾巴(footer)。

常备一个应用程序会有一个分外的侧边栏(sidebar )在页面的左手或者右侧。

一体化布局如下图所示:

运用结构图

大多数用例中,这么些区块将同时体现在页面上。Angular.js
的内置路由ngRoute只同意一个视图(ng-view)出现在页面上。这样限制的状态下,人们得以行使含有页面(ng-include)或者
其他的变通方法为运用创造一个搭架子(layout)或主页(master
page)。UI-Router扶助多样化视图,并且每一个视图都有谈得来相应的决定,所以每个区块都是包装好,可以复用到全部应用程序需要的地方。

嵌入式视图

常见的事例中,一个行使的嵌入式页面一般是主页的详情页面,更有血有肉的说,就是列表的详情页面。许多应用程序,都有列表页面,点击其中一个列表元素,能够进来到列表的详情页面。更进一步说,你点击列表中一个行的总是,进入一个
可查看 详情页面或是一个 可编辑 的表单。

如下图所示:

嵌入式视图示意图

要是列表页面和详情页面是独自分开的(或者他们被Angujar.js回调),使用Angular.js的内置路由ngRoute
是非常容易完成的。可是,要是您想要保持列表不变,而详情页面出现在列表的出手或者下边,这样就变得可怜富有挑衅性了。

需要澄清的是,这样的要求可以接纳ngRoute来完成。但是你需要让五个控制器(一个用来列表,一个用以呈现和隐藏详情)共享一个视图。这样的结果不是优异的,因为我们想要列表和详情页面有各自的控制器和视图,并且职责单一(突显列表或者显示列表项目标详情)。封装这多少个用户接口模块到它们各自的视图,那样大家就有更多的“可组合UI”,允许我们将各样区块整合到一同,或者遵照要求拆分。嵌入式视图,不仅可以让这一个视图同时出现,仍是可以让一个视图嵌入到另一个视图中。

历史

Angular.js第一次宣布ngRoute的时候,是有相近意义的路由存在的。这样路由包含在Backbone.js中,以及独立路由库History.jsSammy.js。总是,他们映射一个路由或者是URL所急需周转的JavaScript代码,当URL改变时,需要将其扩展到浏览器的历史记录中,制止按回退按钮不会毁掉路由。

最终狂胜的JavaScriptMV*框架,是想Ember.js和\Durandal.js
**如此这般,创制出更强壮的路由,以扶助多样化视图和内嵌式视图,并且在内部采用“状态机”设计形式。

AngularJS官方回应称,从1.1.6版本将ngRoute从angular.js主题中删去(更多的传教是1.2)。ngRoute反之亦然可以从AngularJS的官网上取得,不过它曾经不在核心之中。

AngularJS的社区觉得,更受欢迎的路由库是AngularUI项目的UI-Router

AngularJS的多少个顾问包括Rob Eisenberg(Durandal.js
和Aurelia的主创者),正在重写ngRoute,并声称最后将在某个时刻点一向回来,预期的版本是Angular.js
2.0。(注1)

设若您想询问更详尽的野史和各类路由的得失,你可以查看Angular.js
2.0的路由设计公开文档。(需翻墙)

https://docs.google.com/document/d/1I3UC0RrgCh9CKrLxeE4sxwmNSBl3oSXQGt9g3KZnTJI/edit\#heading=h.xgjl2srtytjt

其一文档你可以点击右上方的黑色按钮,接纳提出格局(suggesting)和查看格局(viewing),使页面更显明。

指出情势

翻开形式

安装

使用UI-Router,基于Angular.js
1.2.x或Angular.13.x,你可以经过以下一种办法拿到其JavaScript源码:

下载

下载源码文件,或者混淆压缩后版本:

bower install

$ bower install angular-ui-router

npm install

$ npm install angular-ui-router

引入文件

引入angular-ui-router.jsangular-ui-router.min.js到你的index.html,必须Angualr.js主题文件之后,如下:

<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.1.5/angular.min.js">
</script><script src="js/angular-ui-router.min.js"></script>

(注2)

引入倚重

将“ui.router”依赖添加到你的主Angular.js module中。

var myApp = angular.module('myApp', ['ui.router']);

注意:是ui.router不是ui-router,后者是多多益善人通常犯的一无是处。

路由状态机

UI-Router
引进了状态机设计格局,抽象高于传统的路由。路由成了情景,URL就成了动静的一个简易属性。

var app = angular.module('demo', ['ui.router']);

app.config(['$stateProvider', '$urlRouterProvider', function($stateProvider, $urlRouterProvider) {
    $urlRouterProvider.otherwise('/');

    $stateProvider
        .state('home', {
            url:'/',
            templateUrl: 'templates/home.html',
            controller: 'HomeController'
        })
        .state('about', {
            url:'/about',
            templateUrl: 'templates/about.html',
            controller: 'AboutController'
        })

}]);

当您想经过ui-sref开创一个链接是,使用的是情景而不是URL。

使用:
<a ui-sref="home">Home</a>

放弃:
<a href="#/">Home</a>

在上头的例证中,ui-sref可以这样精通:uiAngularUI品类所有指令的前缀,sref是包装了观念HTML锚点标签的href性能和景色判断。

控制器中动用

下边例子体现的是,尽管在一个控制器中重定向一个动静。

$scope.redirectToAbout = function(){
    $state.go('about'); 
}

$stateProvider 替换 $routeProvider

当使用UI-Router时,为Angular.js服务注入路由帮助,就由$routeProvider
*** ngRoute变成了$stateProvider***。

$urlRouterProvider

$urlRouterProvider
在此间有多少个重点目的。一是树立一个默认路由,用于管理未知的URL(统一跳转到某处)。

app.config(['$stateProvider', '$urlRouterProvider', function($stateProvider, $urlRouterProvider) {
    $urlRouterProvider.otherwise('/');
    ...
}]);

二是监听浏览器地址栏URL的变化,重定向到路由定义的情事中。

app.config(['$stateProvider', '$urlRouterProvider', function($stateProvider, $urlRouterProvider) {
    $urlRouterProvider
        .when('/legacy-route', {
            redirectTo: '/'
        });
}]);

总之,$urlRouterProvider让我们处理状态机抽象的$stateProvider并未检测到的状态。

当今你对UI-Router有了一个中坚认识,UI-Router的这一个特征,让它比ngRoute更好。

UI-Router实践

大家将先来看一个嵌入式视图的事例,然后在观察一个多样化视图的例子。之后,我们再重头看哪样将六头共同使用到一个具体世界的事例。

UI-Router嵌入式视图案例

UI-Router嵌入式视图的列表详情页面。这些例子显示的是一个电视机节目的列表。

电视机节目列表

比方您点击其中一行,你能够看来这行的详情描述。

剧目详情

index.html

AngularJS的应用程序是单页应用程序,视图是插入到shell页中的。这里就是大家的shell页——index.html:

<!doctype html>
<html id="data-ng-app" data-ng-app="demo">
    <head>
        <meta charset="utf-8">
        <title>ui router demo</title>
        <style type="text/css">
            .selected{background-color: #efefef; width:120px; } 
            .detail{width: 300px;margin: 30px;border-top: 1px solid #efefef;}
        </style>
        <!-- IE8-HTML5: https://code.google.com/p/html5shiv/ -->
        <script src="js/libs/html5shiv.js"></script>

    </head>
    <body id="index">

        <!-- Angular UI Router Directive for template insertion -->
        <div id="content" ui-view></div>

        <script src="js/libs/angular.js"></script>
        <script src="js/libs/underscore.js"></script>
        <script src="js/libs/angular-ui-router.js"></script>        
        <script src="js/main.js"></script>      
    </body>
</html>

UI-Router
将首先级视图或是父视图(在例子中是shows.html)彰显在<div id="content" ui-view></div>这个div之中。

主页视图(templates/shows.html)

shows.html是列表页面。

<ul>
    <li ui-sref-active="selected" ng-repeat="show in shows">
        <a ui-sref="shows.detail({id: show.id})">{{show.name}}</a>
    </li>
</ul>

<div class="detail" ui-view></div>

正如前方所提到的,index.html中有一个ui-view性能指令,当相应的路由被呼吁时,视图(shows.html)则会渲染在这一个div中。

请留心,这里有另一个ui-view嵌入在shows.html中。这个ui-view代表的是一个当父视图已渲染之后,再出新的子视图。在这个例子中,是shows-detail.html。

来得详情视图(templates/shows-detail.html)

shows-detail.html是详情页面。

<h3>{{selectedShow.name}}</h3>
<p>
    {{selectedShow.description}}
</p>
</code>

控制器

下边是各样视图相应的控制器。

ShowsController

ShowsControllerShowsService加载一个内存中的数组显示。

app.controller('ShowsController', ['$scope','ShowsService', function($scope, ShowsService) {
    $scope.shows = ShowsService.list();
 }]);
ShowsDetailController

ShowsDetailController
ShowsService收获要显得项的id,并设置给$scope.selectedShow

app.controller('ShowsDetailController', ['$scope','$stateParams', 'ShowsService', function($scope, $stateParams, ShowsService) {
        $scope.selectedShow = ShowsService.find($stateParams.id);
 }]);

配置

我们需要利用$stateProvider配置UI-Router

当我们按照父状态名.子状态名的法门定义一个情况,UI-Router便知道子状态是内嵌在父状态中的。

app.config(['$stateProvider', '$urlRouterProvider', function($stateProvider, $urlRouterProvider) {
    $urlRouterProvider.otherwise('/shows');

    $stateProvider
        .state('shows', {
            url:'/shows',
            templateUrl: 'templates/shows.html',
            controller: 'ShowsController'
        })
        .state('shows.detail', {
            url: '/detail/:id',
            templateUrl: 'templates/shows-detail.html',
            controller: 'ShowsDetailController'
        });
}]);

实在英雄的是,嵌入式视图是列表控制器实现列表和详情相关的有些,而详情控制器只担负展现详情。

示范咋样解耦合,我们只需要变更如有配置,将嵌入式页面更改为单身的六个虚拟页(一个列表路由,一个端详路由)。更有血有肉的说,就是将shows.detail更改为detail

$stateProvider
    .state('shows', {
        url:'/shows',
        templateUrl: 'templates/shows.html',
        controller: 'ShowsController'
    })
    .state('detail', {
        url: '/detail/:id',
        templateUrl: 'templates/shows-detail.html',
        controller: 'ShowsDetailController'
    });
    ...

而且将链接状态的地点由<a ui-sref="shows.detail({id: show.id})">{{show.name}}</a>,更改为<a ui-sref="detail({id: show.id})">{{show.name}}</a>

现行大家的例证,变成了连个独立的页面分别展现。

Service

ShowsService在这么些例子中,使我们的数额访问层。它的职责就是保持一个数组在内存中,使用underscore.js(注3)非凡容易实现这一点。

app.factory('ShowsService',function(){
    var shows = [{
        id: 1,
        name: 'Walking Dead',
        description: 'The Walking Dead is an American post-apocalyptic horror drama television series developed by Frank Darabont. It is based on the comic book series of the same name by Robert Kirkman, Tony Moore, and Charlie Adlard. It stars Andrew Lincoln as sheriff\'s deputy Rick Grimes, who awakens from a coma to find a post-apocalyptic world dominated by flesh-eating zombies.'
    },
    {
        id: 2,
        name: 'Breaking Bad',
        description: 'Breaking Bad is an American crime drama television series created and produced by Vince Gilligan. The show originally aired on the AMC network for five seasons, from January 20, 2008 to September 29, 2013. The main character is Walter White (Bryan Cranston), a struggling high school chemistry teacher who is diagnosed with inoperable lung cancer at the beginning of the series.'   
    },
    {
        id: 3,
        name: '7D', 
        description: 'The 7D is an American animated television series produced by Disney Television Animation, and broadcast on Disney XD starting in July 7, 2014. It is a re-imagining of the titular characters from the 1937 film Snow White and the Seven Dwarfs by Walt Disney Productions'
    }];


    return {
        list: function(){
            return shows;
        },
        find: function(id){
            return _.find(shows, function(show){return show.id == id});
        }
    }
 });

UI-Router多样化视图案例

上边这一个事例有两个区块在一个页面,有headercontentfooter。它们被UI-Router用多样化视图所管理。

多样化视图案例

这是一些主导航,和在这些应用中遵照用户导航填充的应有尽有的虚拟页。

子页

index.html

<!DOCTYPE html>
 <html class="no-js">
    <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <title>Index</title>
        <meta name="description" content="">
        <meta name="viewport" content="width=device-width, initial-scale=1">


    </head>
    <body ng-app="demo">

       <div ui-view="header"></div>
       <div ui-view="content"></div>
       <div ui-view="footer"></div>

    <script src="/js/bower_components/angular/angular.js"></script>
    <script src="/js/bower_components/angular-ui-router/release/angular-ui-router.js"></script>
    <script src="/js/main.js"></script>

    </body>
</html>

请注意,这里的ui-view性能指令都被赋予了一个名字:headercontentfooter。这些名字是当大家在安排路由,要指定view/template和控制器需要功力于页面上的特别区块时引用。

Templates/Views

那个模版是直接省略的例子。Header.html有部分导航。这么些导航应用ui-sref指令导航到指定的情景。

partials/header.html
<div class="ul">
    <li><a ng-href="/">Home</a></li>    
    <li ui-sref-active="active"><a ui-sref="dashboard">Dashboard</a></li>
    <li ui-sref-active="active"><a ui-sref="campaigns">Campaigns</a></li>
</div>
partials/content.html
<p>This is the default content.</p>
partials/footer.html
<p>This is the footer.</p>
partials/dashboard.html
<h2>Dashboard</h2>
partials/campaigns.html
<h2>Campaigns</h2>

当相应的路由被呼吁时,将用相应的沙盘,如dashboard.htmlcampaigns.html替换掉content.html

配置

像上一个事例中这样,大家应用$stateProvider布置意况(路由)。

上边的基本点是小心,一个url不是一个只有一个templateUrlcontroller属性了,而是利用views性能,赋予它一个各自的templateUrlcontroller的集合。

所以本来如此:

.state('home',{
        url: '/',
        templateUrl: '/templates/partials/header.html',
        controller: 'HomeController'
    })

就改成了如此:

.state('home',{
        url: '/',
        views: {
            'header': {
                templateUrl: '/templates/partials/header.html',
                controller: 'HeaderController'
            },
            'content': {
                templateUrl: '/templates/partials/content.html',
                controller: 'ContentController' 
            },
            'footer': {
                templateUrl: '/templates/partials/footer.html',
                controller: 'FooterController'
            }
        }
    })

下边是共同体的代码。(请留意:为了维持完整性,我把不需要控制器的写在上头)

var app = angular.module('demo', ['ui.router']);

app.config(function($stateProvider, $urlRouterProvider){

    $urlRouterProvider.otherwise('/');

    $stateProvider
    .state('home',{
        url: '/',
        views: {
            'header': {
                templateUrl: '/templates/partials/header.html'
            },
            'content': {
                templateUrl: '/templates/partials/content.html' 
            },
            'footer': {
                templateUrl: '/templates/partials/footer.html'
            }
        }
    })

    .state('dashboard', {
        url: '/dashboard',
        views: {
            'header': {
                templateUrl: '/templates/partials/header.html'
            },
            'content': {
                templateUrl: 'templates/dashboard.html',
                controller: 'DashboardController'
            }
        }

    })

    .state('campaigns', {
        url: '/campaigns',
        views: {
            'content': {
                templateUrl: 'templates/campaigns.html',
                controller: 'CampaignController'
            },
            'footer': {
                templateUrl: '/templates/partials/footer.html'
            }
        }

    })
});

另外索要小心的是,即使本身从不填写一个区块或打算,那么用户导航到该路由时,将不会展现。这是糟糕好的,并且违反DRY规格(注4)。所以接下去的机关,我们将见到什么行使内嵌式视图去除冗余。

UI-Router的内嵌式视图和多样化视图案例

方今我们询问了这个伟人的特色(内嵌式视图和多样化视图),让我们用这一个特点一起利用到一个实打实世界的应用程序中。

配置

因为视图的沙盘与多样化视图的例证一样,所用我们复用它的配置。

var app = angular.module('demo', ['ui.router']);

app.config(function($stateProvider, $urlRouterProvider){

    $urlRouterProvider.otherwise('/');

    $stateProvider
    .state('app',{
        url: '/',
        views: {
            'header': {
                templateUrl: '/templates/partials/header.html'
            },
            'content': {
                templateUrl: '/templates/partials/content.html' 
            },
            'footer': {
                templateUrl: '/templates/partials/footer.html'
            }
        }
    })

    .state('app.dashboard', {
        url: 'dashboard',
        views: {
            'content@': {
                templateUrl: 'templates/dashboard.html',
                controller: 'DashboardController'
            }
        }

    })

    .state('app.campaigns', {
        url: 'campaigns',
        views: {
            'content@': {
                templateUrl: 'templates/campaigns.html',
                controller: 'CampaignController'
            }
        }

    })

    .state('app.subscribers', {
        url: 'subscribers',
        views: {
            'content@': {
                templateUrl: 'templates/subscribers.html',
                controller: 'SubscriberController'      
            }
        }

    })
    .state('app.subscribers.detail', {
        url: '/:id',
        /*
        templateUrl: 'templates/partials/subscriber-detail.html',
        controller: 'SubscriberDetailController'
        */

        views: {
            'detail@app.subscribers': {
                templateUrl: 'templates/partials/subscriber-detail.html',
                controller: 'SubscriberDetailController'        
            }
        }

    });

});

我们在/路由上树立一个默认的气象app。在app路由上定义默认的情节区块,头部区块和尾巴区块。然后,我们想在这么些应用中定义被的路由,只需要在app后使用.的语法,如:app.campaigns。请留意,咱们只需要替换内容区块(ui-view=’content’),除非我们想要改变头部和尾巴。因为这些视图都是概念在app路由之下的。

状态名

在上边的代码中,最难精晓的概念是状态名中的@语法。状态名的这个语法可以做如下解释:

写一个状态名,需要应对多少个问题:

  1. 当路由被呼吁时,我应当拿自身的沙盘去替换这多少个区块?更具象的说,状态名就是ui-view属性指令的值。下边一个例子显示了ui-view特性指令和它对应的区块:
  • ui-view=’content’ = content
  • ui-view=’header’ = header
  • ui-view=’footer’ = footer
  1. 哪儿可以找到ui-view所针对的区块?
  • ui-view接纳的不是直接的templateUrl,而是包含该模板的事态
  • ui-view和视图区块包含在应用程序的壳模板(index.html)中时,因为index.html没有概念任何动静,你应有安装为空字符串或者不设置。

把这多少个位于一块儿说,这些似乎question1@question2的语法,更现实的说,其实是区块@状态名

因此,你需要在index.html页面上找内容区块时,你需要如此写:content@

  • 这之后的多个都是在@符后是空手的情形名,说明她们都是在index.html上的区块。

假诺你要找到subscribers.html上的详情内容区块,你需要这么写:detail@app.subscribers

壳页面(index.html)

在上头的例子中,index.html并不曾生出转移,只是简短地定义了逐一区块:头部,尾部和内容。

Views/Templates

Header (partials/header.html)

Header的更新,是通过ui-sref引用嵌入的事态。如.campaigns不是调用campaigns意况,而是基于当下情景,估量出.campaigns的父状态。

<div class="ul">
    <li><a ng-href="/">Home</a></li>    
    <li ui-sref-active="active"><a ui-sref=".dashboard">Dashboard</a></li>
    <li ui-sref-active="active"><a ui-sref=".campaigns">Campaigns</a></li>
    <li ui-sref-active="active"><a ui-sref=".subscribers">Subscribers</a></li>
</div>

上面是一个新用户的模版的例证。

partials/subscribers.html
<h2>Subscribers</h2>

<ul>
    <li ng-repeat="subscriber in subscribers">

        <a ui-sref=".detail({id: subscriber.id})" > {{subscriber.name}}</a>
        {{subscriber.email}}
    </li>
</ul>

<div ui-view="detail"></div>
partials/subscriber-detail.html
{{selected.description}}

结论

精通这样的图景名语法是有些不便的。不过有一个健康的路由的益处是,允许你打包view/controller对组合你的用户界面。我以为这么的困难是有价值的。所以,放心使用最终一个例子作为起源,构建一个神奇的,可珍重的使用。让自己领悟您是否利用UI-Router,或者其余什么问题,请评论。

译者注

  • 注1: 2.0近日发表了,还没去看有没有再次回到
  • 注2: 由于国内网络的不可抗因素,不指出利用谷歌的cdn
  • 注3: Underscore.js
    是一个javascript工具库,提供了一整套函数式编程辅助。在angular中运用参考
  • 注4:DRY原则,Don’t Repeat
    Yourself。不要再度自己,即降调代码是去冗余,同样的代码只写一遍,不同地方调用。

初稿地址:http://www.funnyant.com/angularjs-ui-router/

相关文章