英雄指南——路由

版本:4.0.0+2

有一些英雄指南應用的新需求:

  • 添加一個儀表盤 視圖。
  • 添加在英雄 視圖和 儀表盤 視圖之間導航的功能。
  • 用戶無論在哪個視圖中點擊一個英雄,都會導航到所選英雄的詳情視圖。
  • 用戶在郵件中點擊一個深鏈接,會打開一個特定英雄的詳情視圖。

完成時,用戶就能像這樣在應用中導航:

把 Angular 路由加入到應用中,以滿足這些需求。

更多關于路由器的信息,請看 Routing and Navigation

當完成本章的學習,應用看起來這樣——在線示例 (查看源碼)。

我們離開的地方

在繼續英雄指南之前,檢查你是否有如下結構。

如果應用不運行了,啟動應用。當你做出修改時,通過刷新瀏覽器保持繼續運行。

行動計劃

下面是我們的計劃:

  • AppComponent變成應用程序的“殼”,它只處理導航。
  • 把現在由AppComponent關注的英雄移到一個獨立的HeroesComponent中。
  • 添加路由。
  • 創建一個新的DashboardComponent組件。
  • 儀表盤 加入導航結構中。

路由導航 的另一個名字。路由器 是從一個視圖導航到另一個視圖的機制。

拆分 AppComponent

現在的應用載入AppComponent后立刻顯示出英雄列表。修改后的應用應該呈現一個選擇視圖(儀表盤和英雄)的殼,然后默認顯示其中之一。

AppComponent組件應該只處理導航,所以你要把英雄列表的顯示,從AppComponent移到它自己的HeroesComponent組件中。

HeroesComponent

AppComponent現在的功能已經專注于英雄數據了。與其把AppComponent中所有的東西都移出去,不如索性把它改名為HeroesComponent,然后創建一個單獨的AppComponent殼。

按如下來做:

  • 重命名并移動app_component.*文件到src/heroes_component.*
  • 移除導入路徑中的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,
      );
}

創建 AppComponent

新的AppComponent是應用的“殼”。它將在頂部放一些導航鏈接,并在下面有個顯示區域。

執行這些步驟:

  • 創建lib/app_component.dart文件。
  • 定義一個AppComponent類。
  • 在類的上方添加一個帶有my-app選擇器的@Component注解。
  • 從 heroes 組件中移動下面的東西到AppComponent
    • title 類屬性。
    • @Component 的模板中包含了title綁定的 <h1> 元素。
  • 在應用模板緊跟標題的下面添加<my-heroes>元素,以便你仍然能看到英雄。
  • HeroesComponent添加到AppComponentdirectives列表中,以便 Angular 能夠識別<my-heroes>標簽。
  • HeroService添加到AppComponentproviders列表中,因為在其它每個視圖中你都需要它。
  • HerosComponentproviders列表中移除HeroService,因為它已經被提升到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';
}

刷新瀏覽器。應用仍然運行,并顯示英雄列表。

添加路由

英雄列表應該在用戶點擊按鈕之后顯示,而不是自動顯示。換句話說,用戶應該能夠導航到英雄列表。

更新 pubspec 文件

使用 Angular 路由(angular_router)使導航成為可能。由于路由在它自己的包里,首先添加包到應用的 pubspec 文件:

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

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

不是所有的應用都需要路由,這就是為什么 Angular 路由是在一個獨立的、可選的包中。

導入庫

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

// lib/app_component.dart (router import)

import 'package:angular_router/angular_router.dart';

使路由可用

在應用的 bootstrap 函數中指定 ROUTER_PROVIDERS 來告訴 Angular 你的應用使用了路由。

// 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),
  ]);
}
使用哪個位置策略

默認的 LocationStrategyPathLocationStrategy,所以在生產環境中,你可以使用沒有 LocationStrategy 提供器覆寫的ROUTER_PROVIDERS。在開發過程中,由于pub serve不支持深鏈接,使用 HashLocationStrategy 更方便。詳細信息請看附錄:位置策略和瀏覽器 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="...">元素(或一個動態設置這個元素的 script 標簽)。

正如在路由和導航章節的 Set the base href 部分所述,示例應用使用了下面的腳本:

// 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>

配置路由

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

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

//  lib/app_component.dart (Heroes route)

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

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

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

更多關于路由定義的內容請看——路由和導航

路由插座

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

為此,在模板的底部添加一個<router-outlet>元素。 RouterOutletROUTER_DIRECTIVES中的一個。當用戶在應用中導航時,路由器會立刻在<router-outlet>下面顯示每個組件。

刷新瀏覽器,然后訪問 localhost:8080/#/heroes。應該能看到英雄列表。

路由器鏈接

用戶不應該往地址欄中粘貼一個路由地址,而應該在模板中添加一個錨標簽。當點擊時,觸發到HeroesComponent的導航。

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

// lib/app_component.dart (template)

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

注意,錨點標簽中的[routerLink]綁定。當用戶點擊這個鏈接時, RouterLink 指令告訴路由器應該導航到哪里。

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

更多關于鏈接參數列表內容請看路由章節。

刷新瀏覽器。瀏覽器顯示了應用標題和英雄鏈接,沒有英雄列表。點擊 Heroes 導航鏈接。地址欄變成/#/heroes(或是等價的/#heroes),并且英雄列表顯示出來了。

AppComponent 現在看起來這樣:

// 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 有一個路由器,并且顯示路由視圖。因此,為了和其它類型的組件區分,這種組件類型稱為路由器組件

添加一個儀表盤

只有當多個視圖存在的時候,路由才有意義。為了添加其它視圖,先創建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,
),

添加重定向路由

當前,瀏覽器啟動時地址欄是/。當應用開始時,它應該顯示儀表盤,并在地址欄中顯示路徑/#/dashboard

添加一個重定向路由來實現這一點:

// lib/app_component.dart (Redirect route)

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

或者,你可以定義Dashboard默認路由。更多關于默認路由重定向的內容請看路由與導航章節。

添加導航到儀表盤

在模板中添加一個儀表盤導航鏈接,就放在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>標簽現在什么也不做,但稍后,當你對這些鏈接添加樣式時,它們會很有用。

在瀏覽器中打開,進入到應用的根(/)路徑并重新加載。應用顯示了儀表盤,并且你可以在儀表盤和英雄列表之間導航。

給儀表盤添加英雄

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

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

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

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

templateUrl的值可以是這個包或其它包的資源。當使用另一個包的資源時,使用完整的包引用,例如:'package:some_other_package/dashboard_component.html'

創建的模板文件包含以下內容:

// 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列表中。這個移動創建了一個單獨的HeroService實例,它對應用中的所有組件都有效。Angular 會注入HeroService,然后你可以在DashboardComponent中使用它了。

獲取英雄數據

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';

現在創建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注入到構造函數,并且把它保存在一個私有的_heroService字段中。
  • 在 Angular 的ngOnInit()生命周期鉤子里,調用服務獲取英雄。

在這個儀表盤中,指定了四個英雄(第 2 、 3 、 4 、 5 個)。

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

導航到英雄詳情

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

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

路由到一個英雄詳情

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

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

現在,父組件HeroesComponent使用如下綁定設置組件的hero屬性到一個英雄對象:

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

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

參數化路由

你可以添加英雄的id到路由路徑中。當路由到一個id為 11 的英雄時,你可能期望看到像這樣的路徑:

/detail/11

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

添加帶參數的路由

首先,導入英雄詳情組件:

import 'src/hero_detail_component.dart';

然后,添加如下路由:

// lib/app_component.dart (HeroDetail route)

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

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

你已經完成了本應用的路由。

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

修改 HeroDetailComponent

這里是HeroDetailComponent現在的樣子:

// 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服務中得到id參數,并使用HeroService,通過這個id來獲取英雄。

添加下面的導入:

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

import 'dart:async';

import 'package:angular_router/angular_router.dart';

import 'hero_service.dart';

RouteParamsHeroServiceLocation 服務注入到構造函數中,并將它們的值保存到私有字段:

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

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

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

告訴這個類,要實現OnInit接口。

class HeroDetailComponent implements OnInit {

ngOnInit生命周期鉤子中,從RouteParams服務中提取id參數值,并且使用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));
}

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

英雄的id是一個數字。而路由參數總是字符串。所以路由參數的值被轉換成了數字。

添加 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);

找到回來的路

用戶有多種方式導航到HeroDetailComponent

用戶可以點擊AppComponent中的兩個鏈接,或點擊瀏覽器的“后退”按鈕,來導航到其它地方。現在添加第三種方式,一個goBack()方法,它使用之前注入的Location服務在瀏覽器的歷史棧中后退一步。

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

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

回退太多步可能會使用戶離開應用。在真實的應用種,你可以使用 routerCanDeactivate() 鉤子來阻止這個問題。更多內容請看 CanDeactivate 章節。

在組件模板中添加 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>

更新組件的元數據,使用templateUrl指向剛剛創建的模板文件。

// 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號英雄的詳情會被顯示。在儀表盤或是英雄列表中選擇英雄還不起作用。接下來你將處理這個問題。

選擇一個儀表盤中的英雄

當用戶從儀表盤中選擇了一位英雄時,應用應該導航到HeroDetailComponent以允許用戶查看和編輯所選的英雄。

儀表盤英雄的行為應該像錨標簽一樣:當鼠標移動到一個英雄上時,目標 URL 應該顯示在瀏覽器的狀態欄上,并且用戶應該能復制鏈接或者在新標簽頁打開英雄詳情視圖。

要實現這種效果,打開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模板中的頂級導航有兩個設置為固定的目標路由名,/dashboard/heroes

這次,你綁定到了一個包含鏈接參數列表的表達式。該列表有兩個元素:目標路由名字和一個設置為當前英雄id值的路由參數

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

// lib/app_component.dart (HeroDetail route)

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

刷新瀏覽器,并從儀表盤中選擇一位英雄;應用就會導航到該英雄的詳情。

HeroesComponent 中選擇一位英雄

HeroesComponent中,當前模板展示了一個"主從"風格的視圖:上方是英雄列表,底下是所選英雄的詳情。

// 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
  • 移除英雄詳情導入。

當用戶從列表中選擇一個英雄時,他們并不會進入詳情頁。相反,他們會在頁看到一個迷你的英雄詳情,并且必須點擊一個按鈕來導航到完整的英雄詳情頁。

添加迷你 英雄詳情

在模板底部原來放<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;

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

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

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

管道是一個格式化字符串、貨幣金額、日期和其它顯示數據的好方法。Angular 自帶了幾個管道,并且你可以寫自己的管道。

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

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

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

更多關于管道的內容請看 Pipes

刷新瀏覽器。從英雄列表中選擇一個英雄將會激活迷你詳情視圖。現在查看詳情按鈕還不起作用。

更新 HeroesComponent

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

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

  1. 導入 angular_router
  2. HeroService 一起,在構造函數中注入 Router
  3. 通過調用路由器的navigate()方法,實現 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()方法中傳遞了一個有兩個元素的鏈接參數列表——一個路由名和這個路由的參數,就和之前在DashboardComponent中使用[routerLink]綁定所做的一樣。

刷新瀏覽器,并開始點擊。用戶能在應用中導航:從儀表盤到英雄詳情再回來,從英雄列表到迷你英雄詳情,再到英雄詳情,再回到英雄列表。

你已經滿足了在本章開頭設定的所有導航需求。

給應用添加樣式

應用的功能已經完成了,但它需要添加樣式。儀表盤英雄應該顯示在一行的矩形中。你會看到大約 60 行 CSS 來實現它,包括一些為響應式設計而寫的簡單的媒體查詢。

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

儀表盤樣式

lib/src目錄下創建一個dashboard_component.css 文件,并在組件元數據的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目錄下創建一個hero_detail_component.css文件,并且在組件元數據的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;
}

給導航鏈接添加樣式

lib目錄下創建一個app_component.css文件,并且在組件元數據的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中的導航鏈接看起來更像是可選按鈕。早前,你使用了一個<nav>元素包圍著這些鏈接:

router-link-active CSS 類

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

應用的全局樣式

當你給一個組件添加樣式時,你要使組件所需的一切——HTML、CSS、程序代碼,都集中放在一個方便的地方。這樣,無論是把它們打包起來還是在別的其它地方復用這個組件都會很容易。

你也可以在所有組件之外創建應用級別的樣式。

設計師提供了一些基本的樣式,適用于貫穿整個應用的元素。這些和之前在配置開發環境中安裝的全套主樣式一致。下面是摘錄:

// 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;
}

如有必要的話,創建web/styles.css文件。確保文件中包含 master styles provided here 的內容。并且編輯web/index.html來引用這個樣式。

// web/index.html(link ref)

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

現在看看這個應用。儀表盤、英雄和導航鏈接都被應用了樣式。

應用結構和代碼

在線示例 (查看源碼)中檢查本章的示例源代碼。驗證你是否已經有如下結構:

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 路由,在不同組件之間導航。
  • 學會了如何創建路由鏈接來表示導航欄的菜單項。
  • 使用路由鏈接參數來導航到用戶所選英雄的詳情。
  • 在多個組件之間共享HeroService服務。
  • 添加uppercase管道來格式化數據。

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

下一步

HTTP

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 版本:4.0.0+2 在本章,你會做以下改進。 從一個服務器獲取英雄數據。 讓用戶添加、編輯和刪除英雄。 保存改變...
    soojade閱讀 1,042評論 0 3
  • 版本:4.0.0+2 隨著英雄指南應用的進化,你將會添加更多的需要訪問英雄數據的組件。 你將創建一個單獨的可復用的...
    soojade閱讀 528評論 0 1
  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,915評論 18 139
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,242評論 25 708
  • #Eric愛分享-1分鐘職場智慧# 該如何向領導表達自己的不滿呢?直說,做個耿直的人,只要對事不對人就行。這樣做當...
    行業觀察小朋友閱讀 646評論 0 0