英雄指南——路由

版本:4.0.0+2

有一些英雄指南應(yīng)用的新需求:

  • 添加一個儀表盤 視圖咸作。
  • 添加在英雄 視圖和 儀表盤 視圖之間導(dǎo)航的功能智末。
  • 用戶無論在哪個視圖中點擊一個英雄采转,都會導(dǎo)航到所選英雄的詳情視圖璧亮。
  • 用戶在郵件中點擊一個深鏈接乖订,會打開一個特定英雄的詳情視圖终息。

完成時夺巩,用戶就能像這樣在應(yīng)用中導(dǎo)航:

把 Angular 路由加入到應(yīng)用中贞让,以滿足這些需求。

更多關(guān)于路由器的信息柳譬,請看 Routing and Navigation喳张。

當(dāng)完成本章的學(xué)習(xí),應(yīng)用看起來這樣——在線示例 (查看源碼)美澳。

我們離開的地方

在繼續(xù)英雄指南之前销部,檢查你是否有如下結(jié)構(gòu)。

如果應(yīng)用不運行了制跟,啟動應(yīng)用舅桩。當(dāng)你做出修改時,通過刷新瀏覽器保持繼續(xù)運行雨膨。

行動計劃

下面是我們的計劃:

  • AppComponent變成應(yīng)用程序的“殼”擂涛,它只處理導(dǎo)航。
  • 把現(xiàn)在由AppComponent關(guān)注的英雄移到一個獨立的HeroesComponent中聊记。
  • 添加路由歼指。
  • 創(chuàng)建一個新的DashboardComponent組件。
  • 儀表盤 加入導(dǎo)航結(jié)構(gòu)中甥雕。

路由導(dǎo)航 的另一個名字踩身。路由器 是從一個視圖導(dǎo)航到另一個視圖的機制。

拆分 AppComponent

現(xiàn)在的應(yīng)用載入AppComponent后立刻顯示出英雄列表社露。修改后的應(yīng)用應(yīng)該呈現(xiàn)一個選擇視圖(儀表盤和英雄)的殼挟阻,然后默認(rèn)顯示其中之一。

AppComponent組件應(yīng)該只處理導(dǎo)航峭弟,所以你要把英雄列表的顯示附鸽,從AppComponent移到它自己的HeroesComponent組件中。

HeroesComponent

AppComponent現(xiàn)在的功能已經(jīng)專注于英雄數(shù)據(jù)了瞒瘸。與其把AppComponent中所有的東西都移出去坷备,不如索性把它改名為HeroesComponent,然后創(chuàng)建一個單獨的AppComponent殼情臭。

按如下來做:

  • 重命名并移動app_component.*文件到src/heroes_component.*省撑。
  • 移除導(dǎo)入路徑中的src/前綴。
  • 重命名AppComponent類為HeroesComponent(本地重命名俯在,只在這個文件中)竟秫。
  • 重命名選擇器my-appmy-heroes
  • 改變模板 URL 為heroes_component.html跷乐,以及樣式文件heroes_component.css肥败。
// lib/src/heroes_component.dart (showing renamings only)

@Component(
  selector: 'my-heroes',
  templateUrl: 'heroes_component.html',
  styleUrls: const ['heroes_component.css'],
)
class HeroesComponent implements OnInit {
  HeroesComponent(
      this._heroService,
      );
}

創(chuàng)建 AppComponent

新的AppComponent是應(yīng)用的“殼”。它將在頂部放一些導(dǎo)航鏈接,并在下面有個顯示區(qū)域馒稍。

執(zhí)行這些步驟:

  • 創(chuàng)建lib/app_component.dart文件皿哨。
  • 定義一個AppComponent類。
  • 在類的上方添加一個帶有my-app選擇器的@Component注解纽谒。
  • 從 heroes 組件中移動下面的東西到AppComponent
    • title 類屬性往史。
    • @Component 的模板中包含了title綁定的 <h1> 元素。
  • 在應(yīng)用模板緊跟標(biāo)題的下面添加<my-heroes>元素佛舱,以便你仍然能看到英雄椎例。
  • HeroesComponent添加到AppComponentdirectives列表中,以便 Angular 能夠識別<my-heroes>標(biāo)簽请祖。
  • HeroService添加到AppComponentproviders列表中订歪,因為在其它每個視圖中你都需要它。
  • HerosComponentproviders列表中移除HeroService肆捕,因為它已經(jīng)被提升到AppComponent了刷晋。
  • AppComponent添加import語句。

第一稿看起來這樣:

// lib/app_component.dart

import 'package:angular/angular.dart';

import 'src/hero_service.dart';
import 'src/heroes_component.dart';

@Component(
  selector: 'my-app',
  template: '''
    <h1>{{title}}</h1>
    <my-heroes></my-heroes>
  ''',
  directives: const [HeroesComponent],
  providers: const [HeroService],
)
class AppComponent {
  final title = 'Tour of Heroes';
}

刷新瀏覽器慎陵。應(yīng)用仍然運行眼虱,并顯示英雄列表。

添加路由

英雄列表應(yīng)該在用戶點擊按鈕之后顯示席纽,而不是自動顯示捏悬。換句話說,用戶應(yīng)該能夠?qū)Ш降接⑿哿斜怼?/p>

更新 pubspec 文件

使用 Angular 路由(angular_router)使導(dǎo)航成為可能润梯。由于路由在它自己的包里过牙,首先添加包到應(yīng)用的 pubspec 文件:

// {toh-4 → toh-5}/pubspec.yaml

dependencies:            
       angular: ^4.0.0            
       angular_forms: ^1.0.0            
+     angular_router: ^1.0.2

不是所有的應(yīng)用都需要路由,這就是為什么 Angular 路由是在一個獨立的纺铭、可選的包中寇钉。

導(dǎo)入庫

Angular 路由是多個服務(wù) (ROUTER_PROVIDERS)
、指令(ROUTER_DIRECTIVES)
和配置類的組合舶赔。通過導(dǎo)入路由庫來獲取它們:

// lib/app_component.dart (router import)

import 'package:angular_router/angular_router.dart';

使路由可用

在應(yīng)用的 bootstrap 函數(shù)中指定 ROUTER_PROVIDERS 來告訴 Angular 你的應(yīng)用使用了路由扫倡。

// web/main.dart

import 'package:angular/angular.dart';
import 'package:angular_router/angular_router.dart';
import 'package:angular_tour_of_heroes/app_component.dart';

void main() {
  bootstrap(AppComponent, [
    ROUTER_PROVIDERS,
    // Remove next line in production
    provide(LocationStrategy, useClass: HashLocationStrategy),
  ]);
}
使用哪個位置策略

默認(rèn)的 LocationStrategyPathLocationStrategy,所以在生產(chǎn)環(huán)境中竟纳,你可以使用沒有 LocationStrategy 提供器覆寫的ROUTER_PROVIDERS撵溃。在開發(fā)過程中,由于pub serve不支持深鏈接蚁袭,使用 HashLocationStrategy 更方便征懈。詳細(xì)信息請看附錄:位置策略和瀏覽器 URL 模式

接下來揩悄,添加 ROUTER_DIRECTIVES@Component注解,并移除HeroesComponent

// lib/app_component.dart (directives)

directives: const [ROUTER_DIRECTIVES],

由于AppComponent沒有直接顯示英雄鬼悠,那是路由的工作删性,你可以從指令列表中移除HeroesComponent亏娜。很快你將從模板中移除<my-heroes>

<base href>

打開index.html并確保在<head>部分的頂部有一個<base href="...">元素(或一個動態(tài)設(shè)置這個元素的 script 標(biāo)簽)蹬挺。

正如在路由和導(dǎo)航章節(jié)的 Set the base href 部分所述维贺,示例應(yīng)用使用了下面的腳本:

// web/index.html (base-href)

<head>
  <script>
    // WARNING: DO NOT set the <base href> like this in production!
    // Details: https://webdev.dartlang.org/angular/guide/router
    (function () {
      var m = document.location.pathname.match(/^(\/[-\w]+)+\/web($|\/)/);
      document.write('<base href="' + (m ? m[0] : '/') + '" />');
    }());
  </script>

配置路由

當(dāng)用戶點擊鏈接或者把 URL 粘貼到瀏覽器地址欄時,路由定義 告訴路由器應(yīng)該顯示哪個視圖巴帮。

創(chuàng)建一個路由配置(RouteConfig)來保存應(yīng)用路由定義 的列表溯泣。定義第一個路由作為 heroes 組件的路由:

//  lib/app_component.dart (Heroes route)

@RouteConfig(const [
  const Route(path: '/heroes', name: 'Heroes', component: HeroesComponent)
])

路由定義是一個包含如下命名參數(shù)的 Route 對象:

  • path:路由器把這個字符串和瀏覽器地址欄中的URL(/heroes)進行匹配。
  • name:路由名(Heroes)榕茧。必須以大寫字母開頭垃沦,避免與路徑相混淆。
  • component:當(dāng)路由被導(dǎo)航到(HeroesComponent)時組件將會被激活用押。

更多關(guān)于路由定義的內(nèi)容請看——路由和導(dǎo)航肢簿。

路由插座

如果你訪問 localhost:8080/#/heroes,路由器會把 URL 匹配到 heroes 路由蜻拨,并顯示HeroesComponent池充。然而,你必須告訴路由器在哪里顯示這個組件缎讼。

為此收夸,在模板的底部添加一個<router-outlet>元素。 RouterOutletROUTER_DIRECTIVES中的一個血崭。當(dāng)用戶在應(yīng)用中導(dǎo)航時咱圆,路由器會立刻在<router-outlet>下面顯示每個組件。

刷新瀏覽器功氨,然后訪問 localhost:8080/#/heroes序苏。應(yīng)該能看到英雄列表。

路由器鏈接

用戶不應(yīng)該往地址欄中粘貼一個路由地址捷凄,而應(yīng)該在模板中添加一個錨標(biāo)簽忱详。當(dāng)點擊時,觸發(fā)到HeroesComponent的導(dǎo)航跺涤。

修改過的模板看起來是這樣的:

// lib/app_component.dart (template)

template: '''
  <h1>{{title}}</h1>
  <a [routerLink]="['Heroes']">Heroes</a>
  <router-outlet></router-outlet>
''',

注意匈睁,錨點標(biāo)簽中的[routerLink]綁定。當(dāng)用戶點擊這個鏈接時桶错, RouterLink 指令告訴路由器應(yīng)該導(dǎo)航到哪里航唆。

你定義了一個帶鏈接參數(shù)列表路由指示。在我們的小例子中院刁,列表只有一個元素糯钙,用引號引起來的路由的 name 。回去查看路由配置任岸,證實'Heroes'HeroesComponent路由的 name再榄。

更多關(guān)于鏈接參數(shù)列表內(nèi)容請看路由章節(jié)。

刷新瀏覽器享潜。瀏覽器顯示了應(yīng)用標(biāo)題和英雄鏈接困鸥,沒有英雄列表。點擊 Heroes 導(dǎo)航鏈接剑按。地址欄變成/#/heroes(或是等價的/#heroes)疾就,并且英雄列表顯示出來了。

AppComponent 現(xiàn)在看起來這樣:

// lib/app_component.dart

import 'package:angular/angular.dart';
import 'package:angular_router/angular_router.dart';

import 'src/hero_service.dart';
import 'src/heroes_component.dart';

@Component(
  selector: 'my-app',
  template: '''
    <h1>{{title}}</h1>
    <a [routerLink]="['Heroes']">Heroes</a>
    <router-outlet></router-outlet>
  ''',
  directives: const [ROUTER_DIRECTIVES],
  providers: const [HeroService],
)
@RouteConfig(const [
  const Route(path: '/heroes', name: 'Heroes', component: HeroesComponent)
])
class AppComponent {
  final title = 'Tour of Heroes';
}

AppComponent 有一個路由器艺蝴,并且顯示路由視圖猬腰。因此,為了和其它類型的組件區(qū)分吴趴,這種組件類型稱為路由器組件漆诽。

添加一個儀表盤

只有當(dāng)多個視圖存在的時候,路由才有意義锣枝。為了添加其它視圖厢拭,先創(chuàng)建DashboardComponent占個位置。

// lib/src/dashboard_component.dart (v1)

import 'package:angular/angular.dart';

@Component(
  selector: 'my-dashboard',
  template: '<h3>My Dashboard</h3>',
)
class DashboardComponent {}

稍后你將會使這個組件更有用撇叁。

配置儀表盤路由

添加一個和英雄路由相似的儀表盤路由:

// lib/app_component.dart (Dashboard route)

const Route(
  path: '/dashboard',
  name: 'Dashboard',
  component: DashboardComponent,
),

添加重定向路由

當(dāng)前供鸠,瀏覽器啟動時地址欄是/。當(dāng)應(yīng)用開始時陨闹,它應(yīng)該顯示儀表盤楞捂,并在地址欄中顯示路徑/#/dashboard

添加一個重定向路由來實現(xiàn)這一點:

// lib/app_component.dart (Redirect route)

const Redirect(path: '/', redirectTo: const ['Dashboard']),

或者趋厉,你可以定義Dashboard默認(rèn)路由寨闹。更多關(guān)于默認(rèn)路由重定向的內(nèi)容請看路由與導(dǎo)航章節(jié)。

添加導(dǎo)航到儀表盤

在模板中添加一個儀表盤導(dǎo)航鏈接君账,就放在Heroes鏈接的上方繁堡。

// lib/app_component.dart (template)

template: '''
  <h1>{{title}}</h1>
  <nav>
    <a [routerLink]="['Dashboard']">Dashboard</a>
    <a [routerLink]="['Heroes']">Heroes</a>
  </nav>
  <router-outlet></router-outlet>
''',

<nav>標(biāo)簽現(xiàn)在什么也不做,但稍后乡数,當(dāng)你對這些鏈接添加樣式時椭蹄,它們會很有用。

在瀏覽器中打開净赴,進入到應(yīng)用的根(/)路徑并重新加載绳矩。應(yīng)用顯示了儀表盤,并且你可以在儀表盤和英雄列表之間導(dǎo)航玖翅。

給儀表盤添加英雄

為了使儀表盤更有趣翼馆,你會一眼就能看到四個頂級英雄割以。

使用templateUrl屬性替換template元數(shù)據(jù),它將指向一個新的模板文件写妥,同時添加如下所示的指令(很快你會添加必要的導(dǎo)入):

// lib/src/dashboard_component.dart (metadata)

@Component(
  selector: 'my-dashboard',
  templateUrl: 'dashboard_component.html',
  directives: const [CORE_DIRECTIVES, ROUTER_DIRECTIVES],
)

templateUrl的值可以是這個包或其它包的資源拳球。當(dāng)使用另一個包的資源時审姓,使用完整的包引用珍特,例如:'package:some_other_package/dashboard_component.html'

創(chuàng)建的模板文件包含以下內(nèi)容:

// lib/src/dashboard_component.html

<h3>Top Heroes</h3>
<div class="grid grid-pad">
  <div *ngFor="let hero of heroes">
    <div class="module hero">
      <h4>{{hero.name}}</h4>
    </div>
  </div>
</div>

再次使用*ngFor來遍歷一個英雄列表魔吐,并顯示它們的名字扎筒。額外的<div>元素,有助于稍后的樣式美化酬姆。

共享 HeroService

你可以再次使用HeroService來填充組件的heroes列表嗜桌。

早先,從HeroesComponentproviders列表中移除了HeroService辞色,并把它添加到AppComponentproviders列表中骨宠。這個移動創(chuàng)建了一個單獨的HeroService實例,它對應(yīng)用中的所有組件都有效相满。Angular 會注入HeroService层亿,然后你可以在DashboardComponent中使用它了。

獲取英雄數(shù)據(jù)

dashboard_component.dart添加如下import語句立美。

// lib/src/dashboard_component.dart (imports)

import 'dart:async';

import 'package:angular/angular.dart';
import 'package:angular_router/angular_router.dart';

import 'hero.dart';
import 'hero_service.dart';

現(xiàn)在創(chuàng)建DashboardComponent類匿又,像這樣:

// lib/src/dashboard_component.dart (class)

class DashboardComponent implements OnInit {
  List<Hero> heroes;

  final HeroService _heroService;

  DashboardComponent(this._heroService);

  Future<Null> ngOnInit() async {
    heroes = (await _heroService.getHeroes()).skip(1).take(4).toList();
  }
}

這種邏輯也用于HeroesComponent

  • 定義一個heroes列表屬性。
  • HeroService注入到構(gòu)造函數(shù)建蹄,并且把它保存在一個私有的_heroService字段中碌更。
  • 在 Angular 的ngOnInit()生命周期鉤子里,調(diào)用服務(wù)獲取英雄洞慎。

在這個儀表盤中痛单,指定了四個英雄(第 2 、 3 劲腿、 4 旭绒、 5 個)。

刷新瀏覽器谆棱,在這個新的儀表盤中會看到四個英雄快压。

導(dǎo)航到英雄詳情

雖然所選英雄的詳情顯示在了HeroesComponent的底部,但用戶應(yīng)該能夠通過以下額外的方式導(dǎo)航到HeroDetailComponent

  • 從儀表盤到所選英雄垃瞧。
  • 從英雄列表到所選英雄蔫劣。
  • 從粘貼到瀏覽器地址欄的“深鏈接” URL。

路由到一個英雄詳情

AppComponent中定義其它路由的地方个从,添加一個到HeroDetailComponent的路由脉幢。

這個新路由的不尋常之處在于歪沃,必須告訴HeroDetailComponent該顯示哪個英雄。你不需要告訴 HeroesComponentDashboardComponent任何事情嫌松。

現(xiàn)在沪曙,父組件HeroesComponent使用如下綁定設(shè)置組件的hero屬性到一個英雄對象:

<hero-detail [hero]="selectedHero"></hero-detail>

但這種綁定在任意路由場景中都無法工作。

參數(shù)化路由

你可以添加英雄的id到路由路徑中萎羔。當(dāng)路由到一個id為 11 的英雄時液走,你可能期望看到像這樣的路徑:

/detail/11

/detail/部分是固定不變的。但后面跟著的數(shù)字id部分會隨著英雄的不同而變化贾陷。你需要使用代表英雄id參數(shù) 來表示路由的可變部分缘眶。

添加帶參數(shù)的路由

首先,導(dǎo)入英雄詳情組件:

import 'src/hero_detail_component.dart';

然后髓废,添加如下路由:

// lib/app_component.dart (HeroDetail route)

const Route(
  path: '/detail/:id',
  name: 'HeroDetail',
  component: HeroDetailComponent,
),

路徑中的冒號(:)表明:id是一個導(dǎo)航到HeroDetailComponent時巷懈,特定英雄id的占位符。

你已經(jīng)完成了本應(yīng)用的路由慌洪。

你沒有往模板中添加一個英雄詳情鏈接顶燕,這是因為用戶不會直接點擊一個導(dǎo)航鏈接 去查看一個特定的英雄;他們只會點擊英雄名冈爹,不論是顯示在儀表盤上的名字還是在英雄列表中的名字涌攻。但這并不工作,直到HeroDetailComponent被修改好并且能夠被導(dǎo)航過去犯助。

修改 HeroDetailComponent

這里是HeroDetailComponent現(xiàn)在的樣子:

// lib/src/hero_detail_component.dart (current)

import 'package:angular/angular.dart';
import 'package:angular_forms/angular_forms.dart';
import 'hero.dart';
@Component(
  selector: 'hero-detail',
  template: '''
    <div *ngIf="hero != null">
      <h2>{{hero.name}} details!</h2>
      <div><label>id: </label>{{hero.id}}</div>
      <div>
        <label>name: </label>
        <input [(ngModel)]="hero.name" placeholder="name"/>
      </div>
    </div>
  ''',
  directives: const [CORE_DIRECTIVES, formDirectives],
)
class HeroDetailComponent {
  @Input()
  Hero hero;
}

模板不用修改癣漆。英雄名會用同樣的方式顯示。主要的變化是如何獲取英雄名剂买。

你不會再從父組件的屬性綁定中接收英雄惠爽,所以你可以從hero字段移除 @Input() 注解

// lib/src/hero_detail_component.dart (hero with @Input removed)

class HeroDetailComponent implements OnInit {
  Hero hero;
}

新的HeroDetailComponent會從路由器的RouteParams服務(wù)中得到id參數(shù),并使用HeroService瞬哼,通過這個id來獲取英雄婚肆。

添加下面的導(dǎo)入:

// lib/src/hero_detail_component.dart (added-imports)

import 'dart:async';

import 'package:angular_router/angular_router.dart';

import 'hero_service.dart';

RouteParamsHeroServiceLocation 服務(wù)注入到構(gòu)造函數(shù)中坐慰,并將它們的值保存到私有字段:

// lib/src/hero_detail_component.dart (constructor)

final HeroService _heroService;
final RouteParams _routeParams;
final Location _location;

HeroDetailComponent(this._heroService, this._routeParams, this._location);

告訴這個類较性,要實現(xiàn)OnInit接口。

class HeroDetailComponent implements OnInit {

ngOnInit生命周期鉤子中结胀,從RouteParams服務(wù)中提取id參數(shù)值赞咙,并且使用HeroService通過這個id來獲取英雄。

// lib/src/hero_detail_component.dart (ngOnInit)

Future<Null> ngOnInit() async {
  var _id = _routeParams.get('id');
  var id = int.parse(_id ?? '', onError: (_) => null);
  if (id != null) hero = await (_heroService.getHero(id));
}

注意我們是怎樣通過調(diào)用RouteParams.get()方法提取id的糟港。

英雄的id是一個數(shù)字攀操。而路由參數(shù)總是字符串。所以路由參數(shù)的值被轉(zhuǎn)換成了數(shù)字秸抚。

添加 HeroService.getHero()

ngOnInit()中速和,你使用了一個HeroService還沒有的getHero()方法歹垫。打開HeroService并添加一個通過idgetHeroes()過濾英雄列表的getHero()方法。

// lib/src/hero_service.dart (getHero)

Future<Hero> getHero(int id) async =>
    (await getHeroes()).firstWhere((hero) => hero.id == id);

找到回來的路

用戶有多種方式導(dǎo)航到HeroDetailComponent颠放。

用戶可以點擊AppComponent中的兩個鏈接排惨,或點擊瀏覽器的“后退”按鈕,來導(dǎo)航到其它地方∨鲂祝現(xiàn)在添加第三種方式暮芭,一個goBack()方法,它使用之前注入的Location服務(wù)在瀏覽器的歷史棧中后退一步痒留。

// lib/src/hero_detail_component.dart (goBack)

void goBack() => _location.back();

回退太多步可能會使用戶離開應(yīng)用谴麦。在真實的應(yīng)用種蠢沿,你可以使用 routerCanDeactivate() 鉤子來阻止這個問題伸头。更多內(nèi)容請看 CanDeactivate 章節(jié)。

在組件模板中添加 Back 按鈕舷蟀,并使用事件綁定綁定到這個方法恤磷。

<button (click)="goBack()">Back</button>

把模板移到它自己的hero_detail_component.html文件中:

// lib/src/hero_detail_component.dart (metadata)

<div *ngIf="hero != null">
  <h2>{{hero.name}} details!</h2>
  <div>
    <label>id: </label>{{hero.id}}</div>
  <div>
    <label>name: </label>
    <input [(ngModel)]="hero.name" placeholder="name" />
  </div>
  <button (click)="goBack()">Back</button>
</div>

更新組件的元數(shù)據(jù),使用templateUrl指向剛剛創(chuàng)建的模板文件野宜。

// lib/src/hero_detail_component.dart (metadata)

@Component(
  selector: 'hero-detail',
  templateUrl: 'hero_detail_component.html',
  directives: const [CORE_DIRECTIVES, formDirectives],
)

刷新瀏覽器并訪問 localhost:8080/#detail/11扫步。11號英雄的詳情會被顯示。在儀表盤或是英雄列表中選擇英雄還不起作用匈子。接下來你將處理這個問題河胎。

選擇一個儀表盤中的英雄

當(dāng)用戶從儀表盤中選擇了一位英雄時,應(yīng)用應(yīng)該導(dǎo)航到HeroDetailComponent以允許用戶查看和編輯所選的英雄虎敦。

儀表盤英雄的行為應(yīng)該像錨標(biāo)簽一樣:當(dāng)鼠標(biāo)移動到一個英雄上時游岳,目標(biāo) URL 應(yīng)該顯示在瀏覽器的狀態(tài)欄上,并且用戶應(yīng)該能復(fù)制鏈接或者在新標(biāo)簽頁打開英雄詳情視圖其徙。

要實現(xiàn)這種效果胚迫,打開dashboard.component.html,使用錨點代替<div *ngFor...>(子元素保持不變):

// lib/src/dashboard_component.html (repeated <a> tag)

<a *ngFor="let hero of heroes" [routerLink]="['HeroDetail', {id: hero.id.toString()}]" class="col-1-4">
  <div class="module hero">
    <h4>{{hero.name}}</h4>
  </div>
</a>

注意 [routerLink] 綁定唾那。正如本章路由鏈接 部分所述访锻,AppComponent模板中的頂級導(dǎo)航有兩個設(shè)置為固定的目標(biāo)路由名,/dashboard/heroes闹获。

這次期犬,你綁定到了一個包含鏈接參數(shù)列表的表達(dá)式。該列表有兩個元素:目標(biāo)路由名字和一個設(shè)置為當(dāng)前英雄id值的路由參數(shù)避诽。

這兩個列表項分別對應(yīng)之前在參數(shù)化英雄詳情路由定義中添加的name:id

// lib/app_component.dart (HeroDetail route)

const Route(
  path: '/detail/:id',
  name: 'HeroDetail',
  component: HeroDetailComponent,
),

刷新瀏覽器龟虎,并從儀表盤中選擇一位英雄;應(yīng)用就會導(dǎo)航到該英雄的詳情茎用。

HeroesComponent 中選擇一位英雄

HeroesComponent中遣总,當(dāng)前模板展示了一個"主從"風(fēng)格的視圖:上方是英雄列表睬罗,底下是所選英雄的詳情。

// lib/src/heroes_component.html

<h2>My Heroes</h2>
<ul class="heroes">
  <li *ngFor="let hero of heroes"
      [class.selected]="hero === selectedHero"
      (click)="onSelect(hero)">
    <span class="badge">{{hero.id}}</span> {{hero.name}}
  </li>
</ul>
<hero-detail [hero]="selectedHero"></hero-detail>

這里你將不再展示完整的HeroesComponent旭斥。相反容达,你會在它自己的頁面顯示英雄詳情,并像在儀表盤中所做的路由到它垂券。做以下改變:

  • 從模板的最后一行刪除<hero-detail>元素花盐。
  • directives列表中移除HeroDetailComponent
  • 移除英雄詳情導(dǎo)入菇爪。

當(dāng)用戶從列表中選擇一個英雄時算芯,他們并不會進入詳情頁。相反凳宙,他們會在頁看到一個迷你的英雄詳情熙揍,并且必須點擊一個按鈕來導(dǎo)航到完整的英雄詳情頁。

添加迷你 英雄詳情

在模板底部原來放<hero-detail>的地方添加下列 HTML 片段:

// lib/src/heroes_component.html (mini detail)

<div *ngIf="selectedHero != null">
  <h2>
    {{selectedHero.name | uppercase}} is my hero
  </h2>
  <button (click)="gotoDetail()">View Details</button>
</div>

HeroesComponent中添加如下方法:

// lib/src/heroes_component.dart (gotoDetail stub)

Future<Null> gotoDetail() => null;

稍后氏涩,點擊一個英雄(但現(xiàn)在別做届囚,因為它還不工作),用戶應(yīng)該能在英雄列表下方看到像下面的樣子:

英雄的名字被顯示成大寫字母是尖,因為在插值表達(dá)式綁定中意系,管道操作符(|)的后面,包含了uppercase管道饺汹。

{{selectedHero.name | uppercase}} is my hero

管道是一個格式化字符串蛔添、貨幣金額、日期和其它顯示數(shù)據(jù)的好方法兜辞。Angular 自帶了幾個管道迎瞧,并且你可以寫自己的管道。

在你能夠在模板中使用 Angular 管道之前弦疮,你需要在組件的@Component注解的參數(shù)pipes中列出要使用的管道夹攒。你可以添加單獨的管道,或者使用更方便的管道集合如 COMMON_PIPES胁塞。

//  lib/src/heroes_component.dart (pipes)

@Component(
  selector: 'my-heroes',
  pipes: const [COMMON_PIPES],
)

更多關(guān)于管道的內(nèi)容請看 Pipes咏尝。

刷新瀏覽器。從英雄列表中選擇一個英雄將會激活迷你詳情視圖⌒グ眨現(xiàn)在查看詳情按鈕還不起作用编检。

更新 HeroesComponent

點擊按鈕時,HeroesComponent導(dǎo)航到HeroesDetailComponent扰才。該按鈕的點擊事件被綁定到gotoDetail()方法允懂,它通過告訴路由器應(yīng)該去哪兒進行命令式地導(dǎo)航。

該方法需要對組件類做以下改變:

  1. 導(dǎo)入 angular_router衩匣。
  2. HeroService 一起蕾总,在構(gòu)造函數(shù)中注入 Router粥航。
  3. 通過調(diào)用路由器的navigate()方法,實現(xiàn) gotoDetail()生百。

下面是修改后的HeroesComponent類:

// lib/src/heroes_component.dart (class)

class HeroesComponent implements OnInit {
  final HeroService _heroService;
  final Router _router;
  List<Hero> heroes;
  Hero selectedHero;

  HeroesComponent(
      this._heroService,
      this._router
      );

  Future<Null> getHeroes() async {
    heroes = await _heroService.getHeroes();
  }

  void ngOnInit() => getHeroes();

  void onSelect(Hero hero) => selectedHero = hero;

  Future<Null> gotoDetail() => _router.navigate([
        'HeroDetail',
        {'id': selectedHero.id.toString()}
      ]);
}

gotoDetail()中递雀,你往路由器的navigate()方法中傳遞了一個有兩個元素的鏈接參數(shù)列表——一個路由名和這個路由的參數(shù),就和之前在DashboardComponent中使用[routerLink]綁定所做的一樣蚀浆。

刷新瀏覽器缀程,并開始點擊。用戶能在應(yīng)用中導(dǎo)航:從儀表盤到英雄詳情再回來市俊,從英雄列表到迷你英雄詳情杨凑,再到英雄詳情,再回到英雄列表摆昧。

你已經(jīng)滿足了在本章開頭設(shè)定的所有導(dǎo)航需求撩满。

給應(yīng)用添加樣式

應(yīng)用的功能已經(jīng)完成了,但它需要添加樣式据忘。儀表盤英雄應(yīng)該顯示在一行的矩形中鹦牛。你會看到大約 60 行 CSS 來實現(xiàn)它,包括一些為響應(yīng)式設(shè)計而寫的簡單的媒體查詢勇吊。

正如你所知道的,在組件的styles元數(shù)據(jù)中添加這些 CSS 會使組件邏輯模糊不清窍仰。所以汉规,在一個獨立的.css文件中添加這些 CSS。

儀表盤樣式

lib/src目錄下創(chuàng)建一個dashboard_component.css 文件驹吮,并在組件元數(shù)據(jù)的styleUrls列表屬性中引用它针史,就像這樣:

// lib/src/dashboard_component.dart (styleUrls) 

@Component(
  selector: 'my-dashboard',
  templateUrl: 'dashboard_component.html',
  styleUrls: const ['dashboard_component.css'],
  directives: const [CORE_DIRECTIVES, ROUTER_DIRECTIVES],
)
// lib/src/dashboard_component.css

[class*='col-'] {
  float: left;
  text-decoration: none;
  padding-right: 20px;
  padding-bottom: 20px;
}
[class*='col-']:last-of-type {
  padding-right: 0;
}
*, *:after, *:before {
    -webkit-box-sizing: border-box;
    -moz-box-sizing: border-box;
    box-sizing: border-box;
}
h3 {
  text-align: center; margin-bottom: 0;
}
h4 {
  position: relative;
}
.grid {
  margin: 0;
}
.col-1-4 {
  width: 25%;
}
.module {
    padding: 20px;
    text-align: center;
    color: #eee;
    max-height: 120px;
    min-width: 120px;
    background-color: #607D8B;
    border-radius: 2px;
}
.module:hover {
  background-color: #EEE;
  cursor: pointer;
  color: #607d8b;
}
.grid-pad {
  padding: 10px 0;
}
.grid-pad > [class*='col-']:last-of-type {
  padding-right: 20px;
}
@media (max-width: 600px) {
    .module {
      font-size: 10px;
      max-height: 75px; }
}
@media (max-width: 1024px) {
    .grid {
      margin: 0;
    }
    .module {
      min-width: 60px;
    }
}

英雄詳情樣式

lib/src目錄下創(chuàng)建一個hero_detail_component.css文件,并且在組件元數(shù)據(jù)的styleUrls列表中引用它:

// lib/src/hero_detail_component.dart(styleUrls)

@Component(
  selector: 'hero-detail',
  templateUrl: 'hero_detail_component.html',
  styleUrls: const ['hero_detail_component.css'],
  directives: const [CORE_DIRECTIVES, formDirectives],
)
// lib/src/hero_detail_component.css

label {
  display: inline-block;
  width: 3em;
  margin: .5em 0;
  color: #607D8B;
  font-weight: bold;
}
input {
  height: 2em;
  font-size: 1em;
  padding-left: .4em;
}
button {
  margin-top: 20px;
  font-family: Arial;
  background-color: #eee;
  border: none;
  padding: 5px 10px;
  border-radius: 4px;
  cursor: pointer; cursor: hand;
}
button:hover {
  background-color: #cfd8dc;
}
button:disabled {
  background-color: #eee;
  color: #ccc;
  cursor: auto;
}

給導(dǎo)航鏈接添加樣式

lib目錄下創(chuàng)建一個app_component.css文件碟狞,并且在組件元數(shù)據(jù)的styleUrls列表中引用它:

// lib/app_component.dart(styleUrls)

styleUrls: const ['app_component.css'],
// lib/app_component.css

h1 {
  font-size: 1.2em;
  color: #999;
  margin-bottom: 0;
}
h2 {
  font-size: 2em;
  margin-top: 0;
  padding-top: 0;
}
nav a {
  padding: 5px 10px;
  text-decoration: none;
  margin-top: 10px;
  display: inline-block;
  background-color: #eee;
  border-radius: 4px;
}
nav a:visited, a:link {
  color: #607D8B;
}
nav a:hover {
  color: #039be5;
  background-color: #CFD8DC;
}
nav a.router-link-active {
  color: #039be5;
}

提供的 CSS 使得在AppComponent中的導(dǎo)航鏈接看起來更像是可選按鈕啄枕。早前,你使用了一個<nav>元素包圍著這些鏈接:

router-link-active CSS 類

Angular 路由器添加router-link-activeCSS 類到那些路由匹配激活的路由的 HTML 導(dǎo)航元素族沃。你唯一要做的就是為它定義樣式频祝。

應(yīng)用的全局樣式

當(dāng)你給一個組件添加樣式時,你要使組件所需的一切——HTML脆淹、CSS常空、程序代碼,都集中放在一個方便的地方盖溺。這樣漓糙,無論是把它們打包起來還是在別的其它地方復(fù)用這個組件都會很容易。

你也可以在所有組件之外創(chuàng)建應(yīng)用級別的樣式烘嘱。

設(shè)計師提供了一些基本的樣式昆禽,適用于貫穿整個應(yīng)用的元素蝗蛙。這些和之前在配置開發(fā)環(huán)境中安裝的全套主樣式一致。下面是摘錄:

// web/styles.css(excerppt)

@import url(https://fonts.googleapis.com/css?family=Roboto);
@import url(https://fonts.googleapis.com/css?family=Material+Icons);

/* Master Styles */
h1 {
  color: #369;
  font-family: Arial, Helvetica, sans-serif;
  font-size: 250%;
}
h2, h3 {
  color: #444;
  font-family: Arial, Helvetica, sans-serif;
  font-weight: lighter;
}
body {
  margin: 2em;
}
body, input[text], button {
  color: #888;
  font-family: Cambria, Georgia;
}
/* ··· */
/* everywhere else */
* {
  font-family: Arial, Helvetica, sans-serif;
}

如有必要的話醉鳖,創(chuàng)建web/styles.css文件歼郭。確保文件中包含 master styles provided here 的內(nèi)容。并且編輯web/index.html來引用這個樣式辐棒。

// web/index.html(link ref)

<link rel="stylesheet" href="styles.css">

現(xiàn)在看看這個應(yīng)用病曾。儀表盤、英雄和導(dǎo)航鏈接都被應(yīng)用了樣式漾根。

應(yīng)用結(jié)構(gòu)和代碼

在線示例 (查看源碼)中檢查本章的示例源代碼泰涂。驗證你是否已經(jīng)有如下結(jié)構(gòu):

angular_tour_of_heroes/
|___lib/
|   |___app_component.{css,dart}
|___src/
|   |   |___dashboard_component.{css,dart,html}
|   |   |___hero.dart
|   |   |___hero_detail_component.{css,dart,html}
|   |   |___hero_service.dart
|   |   |___heroes_component.{css,dart,html}
|   |   |___mock_heroes.dart
|___test/
|   |___app_test.dart
|   |___...
|___web/
|   |___index.html
|   |___main.dart
|   |___styles.css
|___pubspec.yaml

走過的路

以下是你在本章中完成的:

  • 添加 Angular 路由,在不同組件之間導(dǎo)航辐怕。
  • 學(xué)會了如何創(chuàng)建路由鏈接來表示導(dǎo)航欄的菜單項逼蒙。
  • 使用路由鏈接參數(shù)來導(dǎo)航到用戶所選英雄的詳情。
  • 在多個組件之間共享HeroService服務(wù)寄疏。
  • 添加uppercase管道來格式化數(shù)據(jù)是牢。

你的應(yīng)用看起來應(yīng)該是這樣在線示例 (查看源碼)。

下一步

HTTP

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末陕截,一起剝皮案震驚了整個濱河市驳棱,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌农曲,老刑警劉巖社搅,帶你破解...
    沈念sama閱讀 206,602評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異乳规,居然都是意外死亡形葬,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評論 2 382
  • 文/潘曉璐 我一進店門暮的,熙熙樓的掌柜王于貴愁眉苦臉地迎上來笙以,“玉大人,你說我怎么就攤上這事冻辩〔螅” “怎么了?”我有些...
    開封第一講書人閱讀 152,878評論 0 344
  • 文/不壞的土叔 我叫張陵微猖,是天一觀的道長谈息。 經(jīng)常有香客問我豹障,道長账胧,這世上最難降的妖魔是什么常遂? 我笑而不...
    開封第一講書人閱讀 55,306評論 1 279
  • 正文 為了忘掉前任砰琢,我火速辦了婚禮吼句,結(jié)果婚禮上缭裆,老公的妹妹穿的比我還像新娘市咆。我一直安慰自己慢宗,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,330評論 5 373
  • 文/花漫 我一把揭開白布余素。 她就那樣靜靜地躺著豹休,像睡著了一般。 火紅的嫁衣襯著肌膚如雪桨吊。 梳的紋絲不亂的頭發(fā)上威根,一...
    開封第一講書人閱讀 49,071評論 1 285
  • 那天,我揣著相機與錄音视乐,去河邊找鬼洛搀。 笑死,一個胖子當(dāng)著我的面吹牛佑淀,可吹牛的內(nèi)容都是我干的留美。 我是一名探鬼主播,決...
    沈念sama閱讀 38,382評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼伸刃,長吁一口氣:“原來是場噩夢啊……” “哼谎砾!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起捧颅,我...
    開封第一講書人閱讀 37,006評論 0 259
  • 序言:老撾萬榮一對情侶失蹤景图,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后隘道,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體症歇,經(jīng)...
    沈念sama閱讀 43,512評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,965評論 2 325
  • 正文 我和宋清朗相戀三年谭梗,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片宛蚓。...
    茶點故事閱讀 38,094評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡激捏,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出凄吏,到底是詐尸還是另有隱情远舅,我是刑警寧澤,帶...
    沈念sama閱讀 33,732評論 4 323
  • 正文 年R本政府宣布痕钢,位于F島的核電站图柏,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏任连。R本人自食惡果不足惜蚤吹,卻給世界環(huán)境...
    茶點故事閱讀 39,283評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧裁着,春花似錦繁涂、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至桶雀,卻和暖如春矿酵,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背矗积。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評論 1 262
  • 我被黑心中介騙來泰國打工全肮, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人漠魏。 一個月前我還...
    沈念sama閱讀 45,536評論 2 354
  • 正文 我出身青樓倔矾,卻偏偏與公主長得像,于是被迫代替她去往敵國和親柱锹。 傳聞我的和親對象是個殘疾皇子哪自,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,828評論 2 345

推薦閱讀更多精彩內(nèi)容

  • 版本:4.0.0+2 在本章,你會做以下改進禁熏。 從一個服務(wù)器獲取英雄數(shù)據(jù)壤巷。 讓用戶添加、編輯和刪除英雄瞧毙。 保存改變...
    soojade閱讀 977評論 0 3
  • 版本:4.0.0+2 隨著英雄指南應(yīng)用的進化胧华,你將會添加更多的需要訪問英雄數(shù)據(jù)的組件。 你將創(chuàng)建一個單獨的可復(fù)用的...
    soojade閱讀 487評論 0 1
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理宙彪,服務(wù)發(fā)現(xiàn)矩动,斷路器,智...
    卡卡羅2017閱讀 134,599評論 18 139
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,512評論 25 707
  • #Eric愛分享-1分鐘職場智慧# 該如何向領(lǐng)導(dǎo)表達(dá)自己的不滿呢释漆?直說悲没,做個耿直的人,只要對事不對人就行男图。這樣做當(dāng)...
    行業(yè)觀察小朋友閱讀 589評論 0 0