试试AngularJS的新路由器

因为我参加了于2015年3月21日举办的ng-japan活动,听到了有关AngularJS新路由的介绍,所以我顺便做了些笔记。

新路由器是什么

在会议上演讲的布莱恩·福尔德说:

    • Angular 1.4(もうすぐリリース予定)と併せてリリースされる, Angularの新しいRouting機構

 

    • Angular 2.x / 1.x の両方で動作することを目標としている.

 

    ngRouterだけでなく、ui-routerのように広く利用されているルーティングモジュール開発者の意見を取り入れながら開発した.

无论如何,试着去碰一下。

如果你想快速试一试,建议使用 npm 安装 new Router。

npm install angular-new-router
cd node_modules/angular-new-router
npm install

将 node_modules/angular-new-router 设置为当前工作目录,并启动 Web 服务器。

npm install -g http-server
http-server

如果从浏览器中输入 http://localhost:8080/examples/angular-1/ ,您就可以查看example列表。

路由的基础

在这里,我们将以example/angular-1/animation作为示例,来阅读源代码。

angular.module('example', [
  'example.goodbye',
  'example.welcome',
  'ngAnimate',
  'ngNewRouter'
])
.controller('AppController', ['$router', AppController]);

AppController.$routeConfig = [
  { path: '/',              redirectTo: '/welcome' },
  { path: '/welcome',       component: 'welcome' },
  { path: '/goodbye',       component: 'goodbye' }
];
function AppController($router) {
  this.greeting = 'Hello';
}

如果使用新的路由器,需要在模块定义时加载依赖关系到 ‘ngNewRouter’ 模块,这是应用的起点组件。

为什么$routeConfig在那里?

特点在于,AppController.$routeConfig=… 的部分。
作为控制器属性,它描述了路由的定义。
考虑到AngularJS的主要路由模块一般是通过将.config和Provider组合来定义路由,所以刚开始看到new Router的定义方式是有些奇怪的。

angular.modlue('app', ['ui.router']).config(function ($stateProvider) {
  $stateProvider.state('welcome', {url: '/welcome', ...});
});

考虑到Angular 2.x的情况,这是可以理解的。实际上,在会议演讲中也提到了,之前的AppControler在Angular 2.x中可以按照以下方式进行编写。

@Component({selector: 'my-app'})
@Template({url: 'myApp.html'})
@RouteConfig([
  { path: '/',              redirectTo: '/welcome' },
  { path: '/welcome',       component: 'welcome' },
  { path: '/goodbye',       component: 'goodbye' }
])
class AppComponent {
  greeting: string;
  constructor() {
    this.greeting = 'Hello';
  }
}

在Angular 2.x中,通过注解(或运行时装饰器)可以向框架传递各种信息,以供组件使用。附加在类上的注解会被TypeScript(不再需要称之为AtScript了)展开为类的静态属性。因此,实现既能在1.x版本又能在2.x版本上运行的路由机制,需要将路由定义作为控制器的属性进行编码。相反,1.x版本中使用的依赖注入.provider本身在Angular 2.x中不存在(感谢@shuhei的指正)。

组件和命名规则

接下来是每个组件代码中被父组件引用的部分。

angular.module('example.welcome', []).
    controller('WelcomeController', WelcomeController);

function WelcomeController() {
  this.heading = 'Welcome to The New Angular Router Demo!';
}
<section>
  <h2>{{welcome.heading}}</h2>
</section>

由于goodbye.js和goodbye.html几乎相同,所以我将跳过对它们的解释。

新しい路由器通过名为“组件”的单元来控制路由。
“组件”是指

    • Angular 1.xでは、Controller + Template HTMLのセットと考えればよい。

 

    Angular 2.xでは@Component(…) が付与されたclassであり、テンプレートとの紐付けも@Template で制御する模様.

至少在Angular 1.x中,注册Component到new Router很重要的是命名规则。
最起码需要遵循以下规则。

当将组件名设为’xxxYyy’时,请

    • Componentに対応するControllerは、.controllerでの登録時、XxxYyyController という名前にしなくてなはならない。

 

    • (xxxCtrl とか xxxだと、new Routerがwarnを上げてくる)

 

    • ComponentのViewとなるHTMLについては、components/xxx-yyy/xxx-yyy.htmlに配置する必要がある

 

    Template HTMLからは、Component名(=xxxYyy)が自動的にcontrollerAs名として利用できるようになっている.

比如说,如果组件名为’myWidget’,那么结果如下所示:

    • Controller: controller.(‘MyWidgetController’, …)で登録.

 

    • Template HTML: ./components/my-widget/my-widget.html に配置.

 

    Templateで利用可能なcontrollerAs名: myWidget

为什么会存在这样的命名规则,仔细考虑起来是很显而易见的事情,$routeConfig 提供的路由定义相比其他机制来说信息太少了。

AppController.$routeConfig = [
  { path: '/',              redirectTo: '/welcome' },
  { path: '/welcome',       component: 'welcome' },
  { path: '/goodbye',       component: 'goodbye' }
];

无论是ngRouter还是ui-router,在路由定义时都需要以下3个要点,考虑到这一点,定义信息很少。

    • URLのパスフラグメント

 

    • ng-view(またはui-view)に展開されるhtmlのURL

 

    管理するController名

为了补充减少的信息,新的路由模块采用了命名规则,一旦确定组件名称,模板HTML的URL和控制器名称就会自动确定。

顺便提一下,虽然我自己没有尝试过,但似乎可以通过 $componentLoaderProvider 来改变这些命名规则。

angular.module('app', ['ngNewRouter']).config(function ($componentLoaderProvider) {
  $componentLoaderProvider.setCtrlNameMapping(function (componentName) {
    // component名が'xxx'の場合に、Controller xxxCtrlと紐付ける
    return componentName + 'Ctrl';
  });
});

视口,深链接

现在,我们已经介绍了如何读取.js等文件并通过指令在组件的显示区域进行设置。以下是相应的index.html文档:

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <base href="/examples/angular-1/animation/">
  <link rel="stylesheet" href="./app.css">
  <title>Routing</title>
</head>
<body ng-app="example" ng-strict-di ng-controller="AppController as app">
  <nav class="navbar navbar-default navbar-fixed-top" role="navigation">
    <div class="collapse navbar-collapse">
      <ul class="nav navbar-nav">
        <li><a ng-link="welcome">welcome</a></li>
        <li><a ng-link="goodbye">goodbye</a></li>
      </ul>

      <ul class="nav navbar-nav navbar-right">
        <li class="loader" ng-if="router.isNavigating">
          <i class="fa fa-spinner fa-spin fa-2x"></i>
        </li>
      </ul>
    </div>
  </nav>

  <div class="page-host">
    <ng-viewport></ng-viewport>
  </div>


  <script src="/node_modules/angular/angular.js"></script>
  <script src="/node_modules/angular-animate/angular-animate.js"></script>
  <script src="/dist/router.es5.js"></script>

  <script src="./app.js"></script>
  <script src="./components/goodbye/goodbye.js"></script>
  <script src="./components/welcome/welcome.js"></script>
</body>
</html>

标签中的ng-link指令通过指定目标组件的名称,将其展开为对应URL片段的href(深度链接)。
对于那些接触过ui-router的人来说,这个机制与ui-view和ui-sref的行为是一样的,所以应该不会感到不自然。

URL参数

与ngRouter和ui-router类似,可以在URL中使用参数。

通过使用变量名来定义路径参数

angular.module('app', ['ngNewRouter'])
  .controller('AppController', ['$router', AppController]);

function AppController ($router) {
  $router.config([
    { path: '/',           component: 'home' },
    { path: '/detail/:id', component: 'detail' }
  ]);
}

类似于ui-sref,可以在跳转时传递参数作为参数。

<a ng-link="detail({id: 5})">link to detal</a>

我们可以通过Component从$routeParams中提取参数值。

angular.module('app.detail', ['ngNewRouter'])
  .controller('DetailController', ['$routeParams', DetailController]);

function DetailController ($routeParams) {
  this.id = $routeParams.id;
}

多视图

在先前的例子中,ng-viewport只有一个,但是也可以将多个viewport分配给路径。

$router.config([{
  path: '/home',
  components: {
    main: 'list',
    sub: 'result'
  }
}]);
<div ng-viewport="main" ></div>
<div ng-viewport="sub" ></div>

如果您已经熟悉ui-router,那么与URL参数类似,这一点也不会感到不自然。

组件的嵌套

对于在ui-router中实现的“view名@所属state名”定义时相应的功能,是否有对应的功能,这一点让我很在意。如果我了解到任何信息,我会在后面补充。

计划在修复后删除:
目前,在组件化的模板HTML中嵌套ng-viewport会导致ngNewRouter内部发生错误。这个问题已在issue:193中提到,并计划在v0.5.1中修复。

在讨论中心问题:117和问题:127时,讨论的是关于嵌套组件的行为。

生命周期钩子

新的路由器提供了在路由前后插入处理的扩展点。

    • canActivate: このComponentへ遷移する直前. 「このComponentに遷移する権限があるか」のように、認可処理に用いると便利.

 

    • activate: このComponentへ遷移した直後. Ajaxによるデータ取得など、ある程度「重たい」処理を初期処理として挟み込むとよい.

 

    • canDeactivate: このComponentから遷移する直前. 終了確認等に用いるとよい.

 

    deactivate: このComponentから遷移した直後.

为了添加对这些扩展点的处理,可以通过在组件中定义上述名称的方法来实现。即,代码将如下所示(注意到方法定义中出现的.prototype之类的东西,可以看出它强烈地意识到了Angular 2.x的Class写法)。

function MyController(user, $http) {
  this.user = user;
  this.$http = $http;
}

MyController.prototype.canActivate = function() {
  return this.user.isAdmin;
};

MyController.prototype.activate = function() {
  return this.bigFiles = this.$http.downloadBigFiles();
};

如果从oldComponent迁移到newComponent,这些扩展点将按照以下顺序启动。

    1. (开始迁移)

 

    1. oldComponent可以停用

 

    1. (创建newComponent实例)

 

    1. newComponent可以激活

 

    1. oldComponent停用

 

    1. newComponent激活

 

    (迁移完成)

当这些扩展点返回以下任一选项时,可以取消转换。

    • false

 

    • promiseオブジェクト(その後rejectされた場合)

 

    promiseオブジェクト(その後resolve(false)された場合)

尽管可以使用一种粗糙的技巧,在Component的构造函数中抛出错误来取消转移,但根据指南,“不要将转移控制的处理放在构造函数中”。

我是一个喜欢ui-router的孩子,所以在导航时,我主要是通过以下方式实现的。

    1. 取消迁移本身:监听$stateChangeStart事件,如果需要,执行$state.go()。

 

    获取迁移后所需的数据:通过$resolve将数据注入到目标Controller中。

在新的路由器中,使用第一种模式时可以使用.canDeactivate或.canActivate,并且在第二种情况下最好使用.activate。(严格来说,在组件实例化之前的处理执行是不存在的,因此似乎也没有任何类似于resolve的时机,但考虑到扩展点变成了实例方法,存在一个“在实例化之前运行的点”也是有点奇怪的…)

对于ui-router来说,它也为State提供了onEnter和onExit处理程序(我并没有经常使用),而且可以认为它们分别对应着activate和deactivate方法,这样一来使用ui-router的人将更容易过渡。

可能并非完全相关,但当看到组件作为类存在与生命周期相关的扩展点时,我不禁想到了类似于ReactJS的componentWillMount和componentDidMount方法。或许只有我一个人这样想吧…

你能立即使用吗?

如果问题是放弃ui-router,转而使用新的Router,目前的感觉是有些无法确切地回答。

虽然考虑到生命周期钩子和注解的组件导向并没有问题,但是 UI-Router 的 “State” 概念的消失可能会让人感到难过。

此外,目前尚未找到能够实现父 – 子 – 孙嵌套的方法(官方文档中也没有任何说明),存在一些实际应用上可能出现的问题尚未解决清楚。

从细节上来说,我还关注如何处理抽象的状态之类的问题。

再稍微追逐一下,等时机成熟的时候,嗯。。。

总结

    • Angular new RouterはAngular 2.xを強く意識したルーティング機構.

 

    • Angular 2.xを見据え、Componentの概念が導入されている.

 

    • ネーミングルール重要

 

    • Angular 1.xユーザでも, 今から触っておくことで、Angular 2.xにアプリを移行するときに楽できる… かも?

 

    • 画面遷移の拡張ポイントがいくつかある.

 

    multi-viewやPath階層とComponentの配置階層のマッピング等、未整理箇所が多数.

参考资料- 只需一种选择

new Routerのサイト ※ ガイド中のコードサンプルはちょいちょいtypoが残っているので要注意.
ng-japanでのBrianスライド資料

广告
将在 10 秒后关闭
bannerAds