穂乃果「わーい」
ことり「すごーい」
海未「たーのしー」
海未「私としたことが・・・語彙を失うとはこういうことなのですね」
穂乃果「でもでもっ!ほんとにすごかったよねっ!」
ことり「ちょっと早い桜が咲いたね♪・・・横浜アリーナに」
海未「技術はまだまだ拙いですが、チームワークと精神力、パフォーマーシップにはすでに十分な貫禄を感じます」
穂乃果「羽根、ちゃんと受け取ってくれたんだねっ!」
ことり「2日目だけだけど、現地チケット手に入ってよかったよね♪」
海未「ええ、本当に。譲っていただいた方、ありがとうございました」
穂乃果「ねえねえ、あれ、やってみようよ!」
ことり「あれ?・・・うん、いいと思う!」
海未「・・・仕方ないですね(実はやってみたかったとは言えない)」
穂乃果「0から1へ! 今、全力で輝こう! あくあーーーっ!」
ほのことうみ「「「さーーーんしゃいん!!!」」」
(2017/3/3執筆)
海未「さて今回は、ルーティング、要するに画面遷移を扱います。完成形はPart2: できあがりイメージを確認しておいてくださいね」
穂乃果「ここまで1画面だけでやってきたもんね」
海未「今回はボリュームが大きいので、最初に作業全体の概略を整理しましょう。まず、AppComponent
は遷移を司るだけにして、そこにあった処理は他のコンポーネントに切り出します」
ことり「いろいろ詰め込んじゃってたもんね」
海未「それから、Augularのルーターの仕組みを使って画面遷移できるようにします。最後に、ダッシュボードのコンポーネントを作成してルーティングに組み込んで完成です」
海未「まずはアイドルの一覧を表示している部分を別コンポーネントにしましょう。といっても処理の大部分を移すことになるので、app.component.ts
の名前を変えてから、新規にapp.component.ts
を作ることにしましょう」
ことり「app.component.ts
が新しいアイドル一覧コンポーネントになるんだね」
海未「そうです。app.component.ts
をidols.component.ts
にリネームしてください。それから、AppComponent
クラスをIdolsComponent
に、セレクタmy-app
をmy-idols
に、それぞれ修正してください」
穂乃果「いつもみたいにapp.module.ts
にも書き足せばいいのかな?」
海未「さすがに穂乃果も学習してきましたね。IdolsComponent
をインポートして、declarations
に追加してください」
海未「次にapp.component.ts
を作成します。最初はシンプルに、このような内容にしましょう」
1 2 3 4 5 6 7 8 9 10 11 12 |
import { Component } from '@angular/core'; @Component({ selector: 'my-app', template: ` <h1>{{title}}</h1> <my-idols></my-idols> ` }) export class AppComponent { title = 'School idol project'; } |
海未「app.module.ts
はこのようにします。この際、IdolsComponent
のproviders
にあったIdolService
をこのAppModule
に移します。」
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { FormsModule } from '@angular/forms'; import { AppComponent } from './app.component'; import { IdolDetailComponent } from './idol-detail.component'; import { IdolsComponent } from './idols.component'; import { IdolService } from './idol.service'; @NgModule({ imports: [ BrowserModule, FormsModule ], declarations: [ AppComponent, IdolDetailComponent, IdolsComponent ], providers: [ IdolService ], bootstrap: [ AppComponent ] }) export class AppModule { } |
ことり「IdolsComponent
のproviders
のところは消しちゃっても大丈夫?」
海未「AppModule
に書かれていれば全てのコンポーネントでIdolService
が使えますから、消してしまって大丈夫です」
海未「では本題のルーティングの話に進みましょう。下準備として、index.html
の<head>
セクションの最初に<base href="/">
という記述を追加してください。このファイルが画面遷移の基準になります」
海未「app.module.ts
にRouterModule
をインポートして、ルーティングの定義を行います」
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { FormsModule } from '@angular/forms'; import { RouterModule } from '@angular/router'; import { AppComponent } from './app.component'; import { IdolDetailComponent } from './idol-detail.component'; import { IdolsComponent } from './idols.component'; import { IdolService } from './idol.service'; @NgModule({ imports: [ BrowserModule, FormsModule, RouterModule.forRoot([ { path: 'idols', component: IdolsComponent } ]) ], declarations: [ AppComponent, IdolDetailComponent, IdolsComponent ], providers: [ IdolService ], bootstrap: [ AppComponent ] }) export class AppModule { } |
海未「このコードの以下の部分がルーティングの定義です」
1 2 3 4 5 6 7 |
RouterModule.forRoot([ { path: 'idols', component: IdolsComponent } ]) |
海未「path
がURLに対応する部分です。この場合、ローカル開発環境ではhttp://localhost:3000/idols
にアクセスするとIdolsComponent
が表示される、といった具合になります」
穂乃果「・・・表示されないよ?」
海未「気が早いですよ」
海未「そのIdolsComponent
を表示する場所を用意しましょう。AppComponent
のテンプレートを修正します」
1 2 3 4 5 6 |
template: ` <h1>{{title}}</h1> <a routerLink="/idols">Idols</a> <router-outlet></router-outlet> ` |
海未「このようにrouter-outlet
タグを書いておくと、その部分にルータによって決定されたコンポーネントが表示されるようになります」
穂乃果「ほんとだ、今度は表示された!」
ことり「その上のrouterLink
は何なのかな?」
海未「それはルーティングの定義にあるpath
を使って画面遷移を行うためのディレクティブです。書き方は普通のa
タグのhref
がrouterLink
に変わるだけですから、今のところは難しくないでしょう」
海未「それでは、Part2で示したような、トリオユニットを表示するダッシュボード画面を実装しましょう。dashboard.component.ts
を作成して簡単な実装を入れておきます」
1 2 3 4 5 6 7 8 |
import { Component } from '@angular/core'; @Component({ selector: 'my-dashboard', template: ' My Dashboard'}) export class DashboardComponent { } |
海未「app.module.ts
でDashboardComponent
をインポートして、先ほどのルーティングの定義にダッシュボードを追加します」
1 2 3 4 5 6 7 8 9 10 11 12 |
RouterModule.forRoot([ { path: 'dashboard', component: DashboardComponent }, { path: 'idols', component: IdolsComponent } ]) ], |
海未「app.module.ts
のdeclarations
にもDashboradComponent
を追加してください」
穂乃果「これで/dashboard
にアクセスすると・・・うん、My Dashboardって表示されるね」
ことり「画面を追加するのは難しくないね」
海未「そうですね。これが一番基本的な方法ですが、これだけで十分という場合もあるでしょう」
海未「さて、このダッシュボード画面をスタートページに使いたいとします。つまり、特にパスを指定せずにアクセスされたらこのダッシュボードを表示するということです」
ことり「リダイレクト?」
海未「はい。ダッシュボードのURLはたとえばhttp://localhost:3000/dashboard
ですが、http://localhost:3000/
へのアクセスもダッシュボードへリダイレクトさせたいとしましょう」
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
RouterModule.forRoot([ { path: '', redirectTo: '/dashboard', pathMatch: 'full' }, { path: 'dashboard', component: DashboardComponent }, { path: 'idols', component: IdolsComponent } ]) ], |
海未「このように定義を追加すると、パスの指定がなかった場合はダッシュボードへリダイレクトされるようになります」
海未「AppComponent
のテンプレートにもリンクを追加しておきましょう」
1 2 3 4 5 6 7 8 9 |
template: ` <h1>{{title}}</h1> <nav> <a routerLink="/dashboard">Dashboard</a> <a routerLink="/idols">Idols</a> </nav> <router-outlet></router-outlet> ` |
海未「ではダッシュボードの画面を実装していきましょう。テンプレートの書き方を、これまでとは少し変えてみます」
1 2 3 4 5 6 |
@Component({ moduleId: module.id, selector: 'my-dashboard', templateUrl: './dashboard.component.html', }) |
海未「templateUrl
を使うと、テンプレートを直接記述するのではなく外部のHTMLファイルを読み込むことができます」
ことり「テンプレートが大きくなると、別ファイルになってる方が読みやすそうだね」
穂乃果「そっか、普通のHTMLファイルならエディタでシンタックスハイライトも補完も効くねっ!」
ことり「Visual Studio Codeとかだとテンプレート直書きでも多少の補完は効くけどね・・・」
海未「そんなわけで、dashboard.component.html
を作成します」
1 2 3 4 5 6 7 8 9 |
<h3>Trio Unit</h3> <div class="grid grid-pad"> <div *ngFor="let idol of idols" class="col-1-3"> <div class="module idol"> <h4>{{idol.name}}</h4> </div> </div> </div> |
海未「書き方そのものはこれまでと変わりません。dashboard.component.ts
の実装に進みましょう」
1 2 3 4 5 |
import { Component, OnInit } from '@angular/core'; import { Idol } from './idol'; import { IdolService } from './idol.service'; |
海未「まずは必要なクラスのインポートです。ここまでで出てきたものばかりですね」
穂乃果「IdolService
はここでも使えるんだ」
ことり「provider
の指定をIdolsComponent
からAppModule
に移したんだったよね」
海未「続いてDashboardComponent
の実装です」
1 2 3 4 5 6 7 8 9 10 11 12 |
export class DashboardComponent implements OnInit { idols: Idol[] = []; constructor(private idolService: IdolService) { } ngOnInit(): void { this.idolService.getIdols() .then(idols => this.idols = idols.slice(0, 3)); } } |
海未「ユニットの選定ロジックは置いておきましょう。ひとまず、先頭3人を選ぶことにします」
ことり「選挙になるもんね・・・」
海未「あとは見覚えのあるコードだと思うので、大丈夫でしょう」
穂乃果「あ、ダッシュボードに穂乃果と海未ちゃんとことりちゃんが出てきたよっ!」
海未「ダッシュボードはこれで完成です。さて、今までアイドルの詳細は、一覧の下に表示していました。これを独立した画面にしてみましょう」
穂乃果「それって、えーと、ルーティングの定義を1つ増やせばいいだけじゃないの?IdolDetailComponent
の」
ことり「穂乃果ちゃん、海未ちゃんにぶっぶーって言われるよ」
海未「言いません!・・・いえ、確かにぶっぶーなのですが」
穂乃果「駄目なんだ・・・」
海未「考えてみてください。IdolDetailComponent
はアイドル1人の詳細を表示するものですが、誰を表示するかは外から指定しないといけません」
穂乃果「あ、そっか・・・今までは@Input()
で受け取ってたんだっけ」
ことり「じゃあ、画面を表示するときにパラメータを受け取らないといけないね」
海未「そのためにパラメータ付きのルート定義を書くことができます」
1 2 3 4 5 |
{ path: 'detail/:id', component: IdolDetailComponent }, |
海未「:id
の部分がパラメータとして扱われます。/detail/11
にアクセスすると、idが11の穂乃果が表示されるといったように」
ことり「URLの一部がパラメータになるんだね」
海未「ここで一旦ルーティングからそれて、IdolService
にIdol
を取得するメソッドを用意しておきましょう。この後の実装で使います」
1 2 3 4 5 |
getIdol(id: number): Promise<Idol> { return this.getIdols() .then(idols => idols.find(idol => idol.id === id)); } |
海未「もともとあったgetIdols
と大差はないですね。指定したidのアイドルを返すメソッドです」
海未「では、idol-detail.component.ts
を修正していきましょう」
1 2 3 4 5 6 7 |
import { Component, Input, OnInit } from '@angular/core'; import { ActivatedRoute, Params } from '@angular/router'; import { Location } from '@angular/common'; import 'rxjs/add/operator/switchMap'; import { IdolService } from './idol.service'; import { Idol } from './idol'; |
海未「インポートの部分はこのようになります。増えたものの詳細はこの後出てきますから、今は気にしなくてもよいでしょう。次に、必要なサービスをDIしておきます」
1 2 3 4 5 6 |
constructor( private idolService: IdolService, private route: ActivatedRoute, private location: Location ) {} |
海未「以前と同様、データの取得はngOnInit
で行います。OnInit
インターフェイスを実装する必要がありましたね」
1 2 |
export class IdolDetailComponent implements OnInit { |
海未「ngOnInit
の中身に進みましょう」
1 2 3 4 5 6 |
ngOnInit(): void { this.route.params .switchMap((params: Params) => this.IdolService.getIdol(+params['id'])) .subscribe(idol => this.idol = idol); } |
海未「これはこれまで出てこなかった内容ですね」
穂乃果「switchMap
?subscribe
?」
ことり「えーと、route
はActivatedRoute
なんだよね・・・でも、そもそもActivatedRoute
って?」
海未「順番にいきましょう。ActivatedRoute
のparams
プロパティには、pathに付加されたパラメータが格納されています。今回のケースでは表示対象のアイドルのidが含まれています」
ことり「そこは、なんとなくわかるかな」
海未「switchMap
は、端的に言うと関数を受け取ってその実行結果を返すというものです。ところで、IdolService
のgetIdol
はPromise
を用いた非同期処理でしたね?」
穂乃果「さっき作ったやつだよね」
海未「非同期処理を伴う関数をswitchMap
に渡した場合、最初の非同期処理の結果が返ってくる前に次の非同期処理がリクエストされると、最初の処理をキャンセルして次の処理を行う、という動きをします」
ことり「同時に1つしか処理しないんだ」
海未「今回のような初期化処理の場合、複数回取ってくる意味はありませんから・・・この、複数の処理をリクエストされた場合の挙動をコントロールするのに、flatMap
やconcatMap
といったものもあります」
ことり「今回は最後のリクエストで、params
からidを取り出し・・・あれ、この+
って何かな?」
海未「Params
の中身は全てstring
ですから、そこでnumber
に変換してあげています」
ことり「そっか、getIdol
の引数はnumber
だったよね」
ことり「subscribe
は?」
海未「switchMap
の結果、この場合はIdol
ですが、それを引数に取る関数を渡すと、switchMap
に渡した非同期処理が完了したタイミングで実行されます」
ことり「簡単に整理すると、Params
に入ってるidでIdol
を取ってきてidol
プロパティに入れてる、ってことだね」
海未「ここまでで、ダッシュボード、一覧、詳細の各画面を表示できるようになりました」
穂乃果「できあがり?今回は長かったね・・・」
海未「まだです。特に詳細画面については、遷移のためのボタンやリンクが必要ですね。それに、templateUrl
を使ったファイルの分割整理や、スタイルシートで見た目を整える作業もあります」
穂乃果「うえぇ」
海未「はじめに、詳細画面から1つ前の画面に戻る機能を用意しましょう。どこから来たかは特定できませんから、履歴から1つ戻すことにします。IdolDetailComponent
に」
1 2 3 4 |
goBack(): void { this.location.back(); } |
海未「1つ戻るメソッドを追加します。普通のJavaScriptで書く場合と似ていますね」
穂乃果「location
ってなんだっけ」
ことり「穂乃果ちゃん、さっきインジェクションしたやつだよ」
海未「あとは、ボタンをクリックしたときにこれが呼ばれればよいので、テンプレートを修正します。この際、先ほどと同様HTMLファイルに切り出してしまいましょう」
1 2 3 4 5 6 7 8 9 10 11 |
<div *ngIf="idol"> <h2>{{idol.name}} details!</h2> <div> <label>id: </label>{{idol.id}}</div> <div> <label>name: </label> <input [(ngModel)]="idol.name" placeholder="name" /> </div> <button (click)="goBack()">Back</button> </div> |
海未「IdolDetailComponent
の@Component
はこうなります」
1 2 3 4 5 6 |
@Component({ moduleId: module.id, selector: 'my-idol-detail', templateUrl: './idol-detail.component.html', }) |
海未「さて、現在ダッシュボードには3人のアイドルが表示されていますが、それをクリックしたら詳細画面を表示したいところですね」
ことり「そうだね。一覧と同じ動き・・・あれ、一覧も、画面下じゃなくて詳細画面に表示しなきゃだよね」
海未「そうですね。一覧の方は後にして、まずダッシュボードを片付けましょう」
海未「ダッシュボードでは、アイドルの名前は<div>
で表示していました。これを<a>
に置き換えてリンクにします」
1 2 3 4 5 6 7 8 9 |
<h3>Trio Unit</h3> <div class="grid grid-pad"> <a *ngFor="let idol of idols" [routerLink]="['/detail', idol.id]" class="col-1-3"> <div class="module idol"> <h4>{{idol.name}}</h4> </div> </a> </div> |
海未「routerLink
には配列でパラメータを渡すことができます。この例では/detail
というパスにアイドルのidを付けて遷移する、という動きになります」
穂乃果「じゃあ動作確認担当の穂乃果だよっ!・・・うん、ちゃんとそれぞれの詳細画面が表示されるね」
海未「ここまでで、ルーティングの定義がだんだん増えてきました。と言ってもまだ4つですが、アプリケーションによってはこれがもっと多くなることは予想がつきますね」
ことり「app.module.ts
がルーティングの定義で埋まっちゃいそう・・・」
海未「そこで、です。ルーティングの定義だけをまとめたモジュールを別に用意して、AppModule
の見通しをよくしましょう。app-routing.module.ts
を作成します」
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { DashboardComponent } from './dashboard.component'; import { IdolsComponent } from './Idols.component'; import { IdolDetailComponent } from './idol-detail.component'; const routes: Routes = [ { path: '', redirectTo: '/dashboard', pathMatch: 'full' }, { path: 'dashboard', component: DashboardComponent }, { path: 'detail/:id', component: IdolDetailComponent }, { path: 'Idols', component: IdolssComponent } ]; @NgModule({ imports: [ RouterModule.forRoot(routes) ], exports: [ RouterModule ] }) export class AppRoutingModule {} |
海未「インポートしたRouterModule
に対してルーティングの設定を行い、それをエクスポートしています。これで、このAppRoutingModule
をインポートすればここで設定したルーティングを適用できるようになります」
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { FormsModule } from '@angular/forms'; import { AppComponent } from './app.component'; import { DashboardComponent } from './dashboard.component'; import { IdolDetailComponent } from './idol-detail.component'; import { IdolsComponent } from './idols.component'; import { IdolService } from './idol.service'; import { AppRoutingModule } from './app-routing.module'; @NgModule({ imports: [ BrowserModule, FormsModule, AppRoutingModule ], declarations: [ AppComponent, DashboardComponent, IdolDetailComponent, IdolsComponent ], providers: [ IdolService ], bootstrap: [ AppComponent ] }) export class AppModule { } |
海未「app.module.ts
はこうなります」
ことり「だいぶすっきりしたね」
海未「何事も分類整理は大切です。聞いていますか穂乃果」
穂乃果「あーあー、聞こえない、聞こえない~」
海未「次からイヤモニでも付けさせますか」
ことり「あれ、よく外れるんだよね・・・」
海未「では、先ほど話が出ましたが一覧画面の方も詳細画面へ遷移するようにします。ここでは一工夫加えてみましょう」
穂乃果「一工夫って?」
海未「一覧で誰かをクリックしたら、簡単な詳細を表示して、そこから詳細画面に遷移できるようにするのです」
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<h2>Belonging Idols</h2> <ul class="idols"> <li *ngFor="let idol of idols" (click)="onSelect(idol)" [class.selected]="idol === selectedIdol"> <span class="badge">{{idol.id}}</span><span>{{idol.name}}</span> </li> </ul> <div *ngIf="selectedIdol"> <h2> School Idol: {{selectedIdol.name | uppercase}} </h2> <button (click)="gotoDetail()">View Detail</button> </div> |
海未「IdolsComponent
のテンプレートをこのように修正します。これで、クリックしたアイドルの名前と、詳細画面へ遷移するボタンを表示することができました」
ことり「ちょっとわからないところがあるんだけど・・・」
海未「おや、ことりが珍しいですね」
ことり「データは全部小文字で登録してたよね? 今追加したところ、大文字で表示されるんだけど・・・」
海未「なるほど、それは{{selectedIdol.name | uppercase}}
の部分が肝ですね。Angularにはパイプという機能があって、データに様々な加工を施せるのです」
ことり「えーっと、| uppercase
って書いてるのがパイプ?」
海未「はい。uppercase
は入力、つまり|
の左側の文字列を全て大文字にするパイプです」
穂乃果「パイプって、他にも種類があるのかな?」
海未「uppercase
の逆のlowercase
や日付をフォーマットするdate
などいろいろありますし、自分で作ることも可能です」
海未「そろそろidols.component.ts
も大きくなって扱いにくくなってきましたね。これも、テンプレートとスタイルシートを別ファイルに切り出してしまいましょう」
1 2 3 4 5 6 7 |
@Component({ moduleId: module.id, selector: 'my-idols', templateUrl: './idols.component.html', styleUrls: [ './idols.component.css' ] }) |
海未「IdolsComponent
のメタデータはこうなります。styleUrls
は初めて出てきましたが、templateUrl
同様読み込むファイルを指定するものです。注意点としては、styleUrls
は配列の形で複数ファイルを指定できる点ですね」
海未「では、詳細へ遷移するボタンを実装しましょう。テンプレートには(click)="gotoDetail()"
とすでに書いてしまいましたので、このgotoDetail
を実装します」
1 2 3 4 |
gotoDetail(): void { this.router.navigate(['/detail', this.selectedIdol.id]); } |
海未「このようになります。パラメータの渡し方はさきほど見たのと同じですね。ですが、これだけでは動きません」
穂乃果「router
がないね」
海未「まずはこれが必要ですね」
1 2 |
import { Router } from '@angular/router'; |
海未「それから、IdolsComponent
のコンストラクタでインジェクションしてあげれば完成です」
1 2 3 4 5 |
constructor( private idolService: IdolService, private router: Router ) {} |
海未「最後にCSSで見た目を整えましょう。ここからは、テンプレートとスタイルシートは別ファイルに切り出す前提で進めます。まずはIdolDetailComponent
から」
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
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; } |
海未「次にDashboardComponent
です」
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
[class*='col-'] { float: left; padding-right: 20px; padding-bottom: 20px; } [class*='col-']:last-of-type { padding-right: 0; } a { text-decoration: none; } *, *: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-3 { width: 33%; } .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; } } |
海未「AppComponent
にもナビゲーションリンク部分がありますね」
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
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.active { color: #039be5; } |
海未「最後に、アプリケーション全体に適用されるグローバルスタイルを用意しましょう。app
ディレクトリの1階層上に、styles.css
があると思いますので、内容を見比べつつ追記します」
ことり「QuickStartのバージョンアップで変わるかもしれないしね」
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
/* 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; } |
海未「長くなりましたがこれでおしまいです。ルーティングの話は半分くらいでしたが、これでアプリケーションとして動作するレベルになりました」
穂乃果「ふえぇ~、ほんとに長かったよ~」
ことり「OneNoteが悲鳴上げてる・・・」
海未「OneNote?」
ことり「ううん、こっちの話~♪」
海未「では次回は、いよいよHTTP通信に挑戦です。総仕上げですね」