Skip to content

Instantly share code, notes, and snippets.

@kazuma1989
Last active April 27, 2020 12:20
Show Gist options
  • Save kazuma1989/8dc8a5e148fcf1737540f3d531feb151 to your computer and use it in GitHub Desktop.
Save kazuma1989/8dc8a5e148fcf1737540f3d531feb151 to your computer and use it in GitHub Desktop.
Angular Guard の動作確認と、ローディング中表示の実装
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { FooComponent } from 'app/foo/foo.component';
import { BarComponent } from 'app/bar/bar.component';
import { AuthGuard } from 'app/guard/auth.guard';
import { PubComponent } from 'app/pub/pub.component';
import { NotFoundComponent } from 'app/not-found/not-found.component';
const routes: Routes = [
{
path: 'pub',
component: PubComponent
},
{
path: '',
// canActivate に指定すると
// /pub <-> /foo/1 or /bar 間の遷移のときに canActivate() が呼ばれる
// /foo/1 <-> /bar 間の遷移のときは呼ばれない
canActivate: [AuthGuard],
// canActivateChild に指定すると、
// /pub <-> /foo/1 or /bar 間の遷移のとき
// /foo/1 <-> /bar 間の遷移のとき
// どちらも canActivateChild() が呼ばれる
canActivateChild: [AuthGuard],
children: [
{
path: 'foo/:id',
// resolve に指定すると、遷移前に resolve() メソッドが呼ばれ、
// その戻り値の Observable が解決したあとに遷移する。
// :id で指定したアイテムを HTTP API から取得してから遷移、ということが可能。
// 取得したデータは { item: someItem } のようなハッシュとして得られる(AuthGuard を item というキーに割り当てているので)。
// AuthGuard にやらせるのは間違っているが、Service を増やすのが面倒なのでこうしている
resolve: {
item: AuthGuard
},
component: FooComponent
},
{
path: 'bar',
component: BarComponent
},
],
},
{
path: '**',
component: NotFoundComponent
}
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
<ul>
<li>
<a routerLink="/pub">/pub</a>
</li>
<li>
<a routerLink="/foo/a">/foo/a</a>
</li>
<li>
<a routerLink="/foo/404">/foo/404</a>
</li>
<li>
<a routerLink="/bar">/bar</a>
</li>
</ul>
<router-outlet></router-outlet>
<app-loading></app-loading>
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { AuthGuard } from './guard/auth.guard';
import { FooComponent } from './foo/foo.component';
import { BarComponent } from './bar/bar.component';
import { PubComponent } from './pub/pub.component';
import { LoadingService } from './service/loading.service';
import { LoadingComponent } from './loading/loading.component';
import { NotFoundComponent } from './not-found/not-found.component';
@NgModule({
declarations: [
AppComponent,
FooComponent,
BarComponent,
PubComponent,
LoadingComponent,
NotFoundComponent
],
imports: [
BrowserModule,
AppRoutingModule
],
providers: [AuthGuard, LoadingService],
bootstrap: [AppComponent]
})
export class AppModule { }
import { Injectable } from '@angular/core';
import { Router, CanActivate, CanActivateChild, Resolve, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/of';
import 'rxjs/add/observable/throw';
import 'rxjs/add/operator/delay';
import 'rxjs/add/operator/do';
import { LoadingService } from 'app/service/loading.service';
@Injectable()
export class AuthGuard implements CanActivate, CanActivateChild, Resolve<any> {
constructor(
// LoadingService は独自のサービスで、ロード中の「ぐるぐる」を制御するサービス
private loading: LoadingService,
private router: Router
) { }
canActivate(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
console.log('canActivate');
return true;
}
canActivateChild(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
console.log('canActivateChild');
return true;
}
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<any> {
const id = route.paramMap.get('id');
console.log(state.url, id);
this.loading.start();
// id に該当するアイテムを HTTP API から取得する、というのを模倣している
let result;
if (id === 'a') {
result = Observable.of({
id,
name: 'A'
});
} else {
result = Observable.throw(`${id} not found`);
}
// 遷移を呼び出してから 1000 ms 後に Observable が解決し、遷移が始まる
return result.delay(1000).do(
// onnext のときはやることがないのでスルー
null,
// onerror のときは Not found ページへ遷移
// URL を書き換えないほうがいいので(どのページへ遷移しようとしてエラーになったかがわからなくなってしまうため)、
// skipLocationChange オプションを有効にしている。
() => {
this.router.navigate(['404'], {
skipLocationChange: true
});
this.loading.end();
},
// oncomplete のとき、LoadingService を止める
() => this.loading.end(),
);
}
}
import { Component, OnInit } from '@angular/core';
import { LoadingService } from 'app/service/loading.service';
@Component({
selector: 'app-loading',
template: `
<div [hidden]="hidden">
<div class="wrapper">
<div class="loader"></div>
<span class="loader-text">Loading...</span>
</div>
</div>
`,
style: `
.wrapper {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
height: 100vh;
color: white;
position: fixed;
top: 0;
width: 100%;
background-color: rgba(0, 0, 0, 0.5);
}
.loader {
border: 16px solid #f3f3f3;
border-top: 16px solid #4055ae;
border-radius: 50%;
width: 120px;
height: 120px;
animation: spin 2s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.loader-text {
display: block;
margin-top: 1em;
}
`
})
export class LoadingComponent implements OnInit {
hidden: boolean;
constructor(
private loading: LoadingService
) { }
ngOnInit() {
this.hidden = true;
// LoadingService#end() が呼び出されると hidden 状態に戻る
this.loading.subject.subscribe(value => {
this.hidden = value === 'end';
});
}
}
import { Injectable } from '@angular/core';
import { Subject } from 'rxjs/Subject';
@Injectable()
export class LoadingService {
subject: Subject<any>;
constructor() {
this.subject = new Subject<any>();
}
start() {
console.log('start');
this.subject.next('start');
}
end() {
console.log('end');
this.subject.next('end');
}
}
@kazuma1989
Copy link
Author

メインのファイルは二つ

  • app-routing.module.ts
  • auth.guard.ts

それにより理解できたこと

  • canActivate(), canActivateChild() が理解できた
  • resolve() が理解できた

おまけ

  • resolve() 待ちの間、ローディングマークを表示するコンポーネントを作った
    • loading.service.ts
    • loading.component.ts
    • loading.component.html
    • loading.component.css

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment