Angular学习笔记(17)—ui-router

安装

$ bower install angular-ui-router --save

<script type="text/javascript"
 src="app/bower_components/angular-ui-router/release/angular-ui-router.js"></script>

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

现在,不同于内置的ngRoute服务,由于ui-router基于状态工作,而不是简单的url,因此可以将它嵌套在视图中。
在处理ngRoute服务时我们不再使用ng-view,而改为使用ui-view指令。
ui-router内处理路由和状态时,我们主要关心的是应用程序处在哪个状态以及Web应用当前处在哪个路由位置。

<div ng-controller="DemoController"> 
    <div ui-view></div>
</div>

定义在任意给定状态内的模板都处在<div ui-view></div>元素内。此外,每个模板都可以包含自己的ui-view。 这事实上就允许你在路由中嵌套视图。定义路由:

.config(function($stateProvider,$urlRouterProvider) {
    $stateProvider.state('start', {
            url: '/start',
            templateUrl: 'partials/start.html'
        })
});

这一步给状态配置对象分配了一个名为start的状态。这个状态配置对象的参数如下:

1.template、templateUrl、templateProvider

在每个视图上设置模板的方式有三种。

  • template:一个HTML内容字符串或者返回HTML的函数
  • templateUrl:一个模板URL路径字符串或者是返回URL路径字符串的函数
  • templateProvider:一个返回HTML内容字符串的函数

2.controller

可以给已经注册好的控制器关联一个URL(使用字符串),也可以创建一个控制器函数作为状态控制器。如果没有定义模板,就不会创建这个控制器。

3.resolve

我们还可以使用resolve功能解析要注入到控制器中的依赖列表。这个resolve选项就是一个对象,其中键就是要注入到控制器中的依赖名称,而其值就是待解析的factories
如果传入一个字符串,angular-route会尝试匹配一个现有的已注册的服务。如果传入一个函数,则注入这个函数,而函数的返回值就是依赖。如果这个函数返回一个promise,它会在控制器被实例化之前解析,同时其值(就像ngRoute)会注入到控制器中。

$stateProvider.state('home', {
    resolve: {
        // 当结果不是promise时立即返回
        person: function() {
            return {
                name: "Ari",
                email: "ari@fullstack.io"
            }
        },
        // 这个函数返回一个promise,它会在控制器实例化之前解析
        currentDetails: function($http) {
            return $http({
                method: 'JSONP',
                url: '/current_details'
            });
        },
        // 还可以在另一个解析中使用上面返回的promise
        facebookId: function($http, currentDetails) {
            $http({
                method: 'GET',
                url: 'http://facebook.com/api/current_user',
                params: {
                    email: currentDetails.data.emails[0]
                }
            });
        }
    },
    controller: function($scope. person, currentDetails, facebookId) {
        $scope.person = person;
    }
});

4.url

url选项可以给应用程序的状态分配一个唯一的URL。这个url选项提供了与深度链接同样的功能,它通过状态导航应用,而不是简单地通过URL导航。
基本路由可以像这样指定:

$stateProvider
    .state('inbox', {
        url: '/inbox',
        template: '<h1>Welcome to your inbox</h1>'
    });

当用户导航到/inbox时,应用会转换到inbox状态,然后使用模板内容填充主要的ui-view指令。
URL可以接受一系列不同的选项,它还可以在url中设置基本的参数。

$stateProvider
    .state('inbox', {
        url: '/inbox/:inboxId',
        template: '<h1>Welcome to your inbox</h1>',
        controller: function($scope, $stateParams) {
            $scope.inboxId = $stateParams.inboxId;
        }
    });

应用会捕获作为URL第二个组成部分的:inboxId。例如,如果用户转换到/inbox/1$stateParams.inboxId就会变成1(因为$stateParams{inboxId: 1})。
还可以使用不同的语法:

url: '/inbox/{inboxId}'

这里路径必须与URL精确匹配。和ngRoute不同,如果用户导航到/inbox/,上面的路径能够正常工作。但是,当导航导到/inbox时,上述示例配置中的状态不会被激活。
此外,你还可以在路径参数内使用正则表达式,因此你可以设置一个匹配路由的规则。

// 只匹配包含6个十六进制数字的inbox ID
url: '/inbox/{inboxId: [0-9a-fA-f]{6}}',
// 或者匹配每个URL中`/inbox`后面的`inboxId`(全部捕获)
url: '/inbox/{inboxId:.*} '

注意,不能在路由内使用正则捕获组,因为路由解析器将无法解析这个路由。
甚至还可以在路由中指定查询参数:

// 匹配诸如/inbox?sort=ascending形式的路由
url: '/inbox?sort'

5.嵌套路由

你可以使用url参数以插入路由的方式提供嵌套路由。这让你可以在页面或者模板内有多个ui-views

$stateProvider.state('inbox', {
    url: '/inbox/:inboxId',
    template: '<div><h1>Welcome to your inbox</h1>\
        <a ui-sref="inbox.priority">Show priority</a>\
        <div ui-view></div></div>'
    controller: function($scope, $stateParams) {
        $scope.inboxId = $stateParams.inboxId;
    }
})
.state('inbox.priority', {
    url: '/priority',
    template: '<h2>Your priority inbox</h2>'
});

第一个路由会按预期匹配。现在这里有了第二个路由,也就是一个匹配父路由inbox之下的子路由。

  • /inbox/1匹配第一个状态。
  • /inbox/1/priority匹配第二个状态。

使用这种语法,你可以在父路由内嵌套URL。父视图中的ui-view会解析priority收件箱。

6.params

params选项是一个参数名数组或者是一个正则表达式数组。不能将这个选项与url选项联合使用。当状态被激活时,这些参数会被填充到$stateParams服务中。

7.views

ui-router的一个强大的特性就是可以在一个状态内设置多个命名视图。在独立的视图内,你可以在独立模板中定义多个要引用的视图。
如果设置了views参数,那么状态的templateUrltemplatetemplateProvider就会被忽略。如果你想在路由中包含父模板,就需要创建一个包含模板的抽象状态。
比方说我们有一个视图看起来像这样:

<div>
    <div ui-view="filters"></div>
    <div ui-view="mailbox"></div>
    <div ui-view="priority"></div>
</div>

现在,你可以创建命名视图来填充每个独立的模板。每个子视图都可以包含它自己的模板、控制器和使用resolve关键字解析的数据。

$stateProvider.state('inbox', {
    views: {
        'filters': {
            template: '<h4>Filter inbox</h4>',
            controller: function($scope) {}
        },
        'mailbox': {template: 'partials/mailbox.html'},
        'priority': {
            template: '<h4>Priority inbox</h4>',
            resolve: {
                facebook: function() {return FB.messages(); }
            }
        }
    }
});

8.abstract

抽象模板永远不能直接激活,但是可以设置被激活的子节点。
你可以使用抽象模板提供一个模板包装器来包裹多个命名视图,或者传递$scope对象给子节点。你还可以使用它们来传递解析后的依赖或者自定义数据,或者在同一url下嵌套多个路由(比如,所有的路由都在/adminURL之下)。
设置抽象模板与设置常规状态一样,区别只在于设置abstract属性:

$stateProvider
  .state('admin', {
      abstract: true,
      url: '/admin',
      template: ;<div ui-view></div>' 
  })
  .state('admin.index', {
      url: '/index',
      template: '<h3>Admin index</h3>' 
  })
  .state('admin.users', {
      url: '/users',
      template: '<ul>...</ul>'
  });

9.onEnter、onExit

Angular会在用户(分别)进入或者离开视图时调用这些回调函数。对于这两个选项,你可以设置希望调用的函数。这些函数可以访问被解析的数据。
这些回调函数让你可以在新视图上或者进入另一个状态时触发某个行为。使用它们可以很好地实现一个“你确定吗?”形式的模态视图,或者在用户进入这个状态之前要求用户登录。

10.data

你可以附加任意数据给你的状态配置对象configObject。这个选项跟resolve属性很像,但是它的数据不会被注入到控制器中,promise也不会被解析。
当需要从父状态给子状态传递数据时,这个选项特别有用。

事件

angular-route服务会在状态生命周期的不同阶段触发不同的事件。
在应用程序内可以通过监听$scope对象的方式附加函数给这些事件。以下所有事件都会触发在$ootScope上,因此可以在任意$scope对象上监听这些事件。

1.状态改变事件

可以使用如下方式监听这个事件:

$scope.$on('$stateChangeStart',
  function(evt, toState, roParams, fromState, fromParams) { 
      // 可以阻止这一状态完成
      evt.preventDefault();
  });

这个事件可能会以如下方式触发。
$stateChangeStart从一个状态开始过渡到另一个状态时触发这个事件。 $stateChangeSuccess从一个状态过渡到下一个状态完成时触发这个事件。 $stateChangeError当过渡期间发生错误时触发这个事件。通常,模板不能被解析或者解析promise失败时会引发错误。

2.视图加载事件

ui-router还在视图加载阶段提供了事件。
$viewContentLoading视图开始加载时,DOM被渲染之前,触发这个事件。
你可以像这样监听这个事件:

$scope.$on('$viewContentLoading', 
  function(event, viewConfig) {
      // 在这里可以访问所有视图配置属性 
      // 以及一个特殊的“targetView”属性 
      // viewConfig.targetView
  });

$viewContentLoaded在视图加载完成以及DOM渲染之后触发这个事件。

$stateParams

$stateParams服务展示了如何根据URL的不同组成部分处理数据。
例如,如果在inbox状态中有个看起来像这样的URL:

url: 'inbox/:inboxId/messages/{sorted}}?from&to' 

然后用户到达这个路由:

/inbox/123/messages/ascending?from=10&to=20 

那么$stateParams对象的结果就是:

{inboxId: '123', sorted: 'ascending', from: 10, to: 20}

$urlRouterProvider

你可以使用路由提供程序构建规则,规定当特定的URL被激活时会发生什么。 创建的这些状态负责在不同的URL中激活自身,因此不一定需要$urlRouterProvider来管理激活和加载状态。当你想要管理发生在状态作用域之外的行为时,它就可以派上用场了,比如重定向或者身份验证。
你可以在模块配置函数中使用$urlRouterProvider

when()

when()接受两个参数:想要匹配的入口路径和用于重定向的路径(或者是在路径匹配时调用的函数)。
为了设置重定向,需要给when方法设置一个字符串参数。
例如,如果想将一个空路由重定向到/inbox:

.config(function($urlRouterProvider) { 
    $urlRouterProvider.when('', '/inbox'); 
});

如果传入一个函数,它会在路径匹配时调用。这个处理程序可能返回以下三个值中的一个。

  • falsy:这个值告诉$urlRouter该规则不匹配,同时它应该尝试找到一个不同的状态来匹配。如果想要确保用户可以正确地访问一个URL,它将很有帮助。
  • 字符串:$urlRouter会把这个字符串值当作重定向的URL。
  • truthy or undefined:这个值让$urlRouter知道已经处理了URL。

otherwise()

otherwise()方法在没有其他路由匹配时发起重定向。这个方法是创建默认URL的一种很好的方式。
otherwise()方法接受一个参数:一个字符串或者函数。
如果传入一个字符串,任何无效或者不匹配的路由都会重定向到字符串指定的URL。
如果传入一个函数,它会在没有其他路由匹配时被调用,同时负责处理返回结果。

.config(function() {
    $urlRouterProvider.otherwise('/');
    // 或者
    $urlRouterProvider.otherwise(function($injector, $location) { 
        $location.path('/');
    });
});

rule()

如果想要绕过所有的URL匹配,或者想要在操作其他路由之前对路由做一些操作, 可以使用rule()函数。
使用rule()函数时必须返回一个有效路径字符串。

.config(function($urlRouterProvider) { 
    $rulRouterProvider.rule(function($injector, $location) { 
        return '/index';
    });
});

创建一个导航程序

当我们想要为用户创建一个注册向导的时候,就需要使用ui-router了,这是一个非常合适的应用场景。
我们将使用ui-router创建一个快速注册服务,它包含一个控制器,用于处理注册任务。首先,需要创建应用视图:

<div ng-controller="WizardSignupController"> 
    <h2>Signup wizard</h2>
    <div ui-view></div>
</div>

接下来,在这个注册向导中还需要有三个阶段。

  • start:在这个阶段,我们获取用户名并向其介绍注册向导。
  • email:在这里,接受用户的邮件。
  • finish:此时,用户完成注册过程,我们要向其展示一个完整的页面。

在真实的应用中,finish阶段应该将注册资料发送给服务器,同时进行真实的注册 操作。在这里,由于没有后端,因此暂时只显示这个视图。
这个注册程序依赖于wizardapp.controllers模块,我们将在其中编写包含控制器:WizardSignupController

angular.module('wizardApp', [ 
    'ui.router', 
    'wizardapp.controllers'
]);

WizardSignupController简单地提供了$scope.user对象,在注册过程以及注册行为中,我 们都会使用这个对象。

angular.module('wizardapp.controllers', []) 
  .controller('WizardSignupController', 
      function($scope, $state) { 
        $scope.user = {}; 
        $scope.signup = function() {}
  });

向导程序逻辑覆盖了大部分工作。你可以将这些逻辑设置到应用的config()函数中:

angular.module('wizardApp', [
    'ui.router', 'wizardapp.controllers'
])
.config(function($stateProvider, $urlRouterProvider) { 
    $stateProvider
        .state('start', {
            url: '/step_1',
            /templateUrl: 'partials/wizard/step_1.html' 
        })
        .state('email', {
            url: '/step_2',
            templateUrl: 'partials/wizard/step_2.html'
        })
        .state('finish', {
            url: '/finish',
            templateUrl: 'partials/wizard/step_3.html' 
        });
});

设置这些选项之后,基本流程就全部完成了。现在,如果用户导航到路由/step_1,他们将被定向到流程的起点。尽管整个流程也可以都发生在根URL上(即/step_1),但你可能更希望将 它们放在子路由中(如/wizard/step_1)。
为此,只需要设置一个包装其他步骤的abstract状态就可以了。

.config(function($stateProvider, $urlRouterProvider) { 
    $stateProvider
        .state('wizard', {
            abstract: true,
            url: '/wizard',
            template: '<div><div ui-view></div></div>' 
        })
        .state('wizard.start', {
            url: '/step_1',
            templateUrl: 'partials/wizard/step_1.html' 
        })
        .state('wizard.email', {
            url: '/step_2',
            templateUrl: 'partials/wizard/step_2.html' 
        })
        .state('wizard.finish', {
            url: '/finish',
            templateUrl: 'partials/wizard/step_3.html' 
        });
});

现在,这些路由不再定义在顶级路由中了,你可以将它们(子路由)安全地嵌套在/wizardURL内。
此外,我们还想在注册程序的尾部附加一个功能:在父控制器WizardSignupController上调用signup函数。我们只需在向导程序的尾部设置一个控制器来调用$scope上的函数就行了。 由于整个向导程序都封装在WizardSignupController中,这就表示可以正常使用作用域嵌套作用域属性。

.state('wizard.finish',{
    url: '/finish',
    templateUrl: 'partials/wizard/step_3.html', 
    controller: function($scope) { 
        $scope.signup();
    }
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容