AngularのRouter
angular@angular/core": 4.1.0
Angular2とangular-cliでTODOを作る - sambaiz.net
angular-cliは@angular/cliに変更された。
routingを行うのでnewで--routingオプションを付けている。
$ npm install -g @angular/cli
$ ng -v
@angular/cli: 1.0.1
$ ng new angular4-routing --routing
$ cd angular4-routing/
$ cat package.json | grep @angular/core
"@angular/core": "^4.0.0",
$ ng serve
** NG Live Development Server is running on http://localhost:4200 **
--routingを付けたのでapp-routing.module.tsが作成され、app.module.tsにAppRoutingModuleが追加される。
index.htmlのheadにはpushStateのroutingが働くように
base要素が
追加されている。
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
const routes: Routes = [
{
path: '',
children: []
}
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
また、app.component.htmlにrouter-outletが置かれていてroutingによるComponentはこの下に描画される。
<h1>
{{title}}
</h1>
<router-outlet></router-outlet>
動的に追加されるコンポーネントのstyleは@HostBindingで設定できる。
export class TodoMainComponent implements OnInit {
@HostBinding('style.width') width = '100%';
...
}
ルーティング定義
childrenでchild routeを設定しているが、
これは親コンポーネントのrouter-outletの下に描画される。
$ ng g component todo/todo-main
$ ng g component todo/todo-list
$ ng g component todo/todo-item
$ ng g component not-found
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { TodoMainComponent } from './todo/todo-main/todo-main.component'
import { TodoListComponent } from './todo/todo-list/todo-list.component'
import { TodoItemComponent } from './todo/todo-item/todo-item.component'
import { NotFoundComponent } from './not-found/not-found.component'
const routes: Routes = [
{
path: 'todo',
component: TodoMainComponent,
children: [
{
path: ':id',
component: TodoItemComponent
},
{
path: '',
component: TodoListComponent
}
]
},
{
path: '**',
component: NotFoundComponent,
}
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
<p>
todo-main works!
</p>
<router-outlet></router-outlet>
これで http://localhost:4200/todo にアクセスすると、TodoMainComponentとTodoListComponentが表示される。
app works!
todo-main works!
todo-list works!
パラメータの取得
ActivatedRouteをDIしてObservableなparamsをSubscribeする。
import { Component, OnInit, HostBinding } from '@angular/core';
import { Router, ActivatedRoute, Params } from '@angular/router';
@Component({
selector: 'app-todo-item',
templateUrl: './todo-item.component.html',
styleUrls: ['./todo-item.component.css']
})
export class TodoItemComponent implements OnInit {
id: number;
constructor(
private route: ActivatedRoute,
) { }
ngOnInit() {
this.route.params.subscribe(
(params: Params) => this.id = +params['id']
);
}
}
<p>
todo-item ({{id}}) works!
</p>
遷移
タグなら<a routerLink>を、コードならRouter.navigateを使って遷移できる。
<p>
todo-item ({{id}}) works!
<button (click)="onClickNext()">Next</button>
<a routerLink="/todo">todos</a>
</p>
...
export class TodoItemComponent implements OnInit {
constructor(
private route: ActivatedRoute,
private router: Router
) { }
...
onClickNext() {
if(typeof this.id !== 'undefined'){
this.router.navigate([`/todo/${this.id+1}`, {hoge: "fuga"}]);
}
}
}
navigateでhogeという適当なパラメータを付けているが、呼ぶとhttp://localhost:4200/todo/10;hoge=fugaのように
クエリパラメータが?, &ではなく;で区切られたURLに遷移する。
これをmatrix URL notationといって、結構由緒正しいものらしい。
Guard
routeの遷移時に何かするためのもの。 具体的にはログインしているかどうかをチェックしたりとか、 遷移する前にデータを一時保存したりとかそういうのに使える。
$ ng g guard auth
canActivate() はrouteに遷移するときに呼ばれ、trueを返すとそのまま続行され、falseを返すと中断される。
import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs/Observable';
@Injectable()
export class AuthGuard implements CanActivate {
canActivate(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
console.log(`canActivate(): ${state.url}`);
return true;
}
}
AppModuleのprovidersにAuthGuardを入れて、routesにもcanActivateとしてAuthGuardを設定すると呼ばれるようになる。
import { AuthGuard } from './auth.guard'
const routes: Routes = [
{
path: 'todo',
component: TodoMainComponent,
canActivate: [AuthGuard],
children: [
{
path: ':id',
component: TodoItemComponent
},
{
path: '',
component: TodoListComponent
}
]
},
{
path: '**',
component: NotFoundComponent,
}
];
この設定だと上で追加したNextボタンを押してchild routeに遷移したときには呼ばれない。
canActivateChild()にするとchild routeに遷移したときにも呼ばれるようになる。
canActivateChild(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
return this.canActivate(route, state);
}
{
path: 'todo',
component: TodoMainComponent,
canActivateChild: [AuthGuard],
children: [
{
path: ':id',
component: TodoItemComponent
},
{
path: '',
component: TodoListComponent
}
]
}
これらの他には
-
canDeactivate(): 今のrouteから離れるとき
-
resolve(): コンポーネントを表示する前。pre-fetchのため。
-
canLoad():
loadChildrenで指定したModuleをlazy load するとき。ドキュメントではAdminModuleに対して、認証されていなかったらロードしないようにしている。
のGuardがある。
アニメーション
app.module.tsにBrowserAnimationsModuleを追加。
$ npm install --save @angular/animations
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
@NgModule({
...
imports: [
...
BrowserAnimationsModule
],
...
})
animations.tsを作成する。
import { animate, AnimationEntryMetadata, state, style, transition, trigger } from '@angular/core';
// Component transition animations
export const slideInDownAnimation: AnimationEntryMetadata =
trigger('routeAnimation', [
state('*',
style({
opacity: 1,
transform: 'translateX(0)'
})
),
transition(':enter', [
style({
opacity: 0,
transform: 'translateX(-100%)'
}),
animate('0.2s ease-in')
]),
transition(':leave', [
animate('0.5s ease-out', style({
opacity: 0,
transform: 'translateY(100%)'
}))
])
]);
これを@Componentのanimationsに入れて@HostBindingでanimationのトリガー(routeAnimation)を発火させる。
import { Component, OnInit, HostBinding } from '@angular/core';
import { Router, ActivatedRoute, Params } from '@angular/router';
import { slideInDownAnimation } from '../../animations';
@Component({
selector: 'app-todo-item',
templateUrl: './todo-item.component.html',
styleUrls: ['./todo-item.component.css'],
animations: [ slideInDownAnimation ]
})
export class TodoItemComponent implements OnInit {
@HostBinding('@routeAnimation') routeAnimation = true;
@HostBinding('style.display') display = 'block';
@HostBinding('style.position') position = 'absolute';
id: number;
constructor(
private route: ActivatedRoute,
private router: Router
) { }
ngOnInit() {
this.route.params.subscribe(
(params: Params) => this.id = +params['id']
);
}
onClickNext() {
this.router.navigate(['/todo', {id: this.id + 1, hoge: "fuga"}])
}
}
こんな感じにアニメーションする。
