æŽæ°ãããAngularã®ãªãªãŒã¹ããååãªæéãçµéããŸããã çŸåšãå€ãã®ãããžã§ã¯ããå®äºããŠããŸãã ãã¯ããã«ããããå€ãã®éçºè
ããã§ã«ãã®ãã¬ãŒã ã¯ãŒã¯ãšãã®æ©èœã®ææçŸ©ãªäœ¿çšã«ç§»è¡ããèœãšã穎ãåé¿ããæ¹æ³ãåŠã³ãŸããã åéçºè
ããã³/ãŸãã¯ããŒã ã¯ãç¬èªã®ã¹ã¿ã€ã«ã¬ã€ããšãã¹ããã©ã¯ãã£ã¹ããã§ã«äœæããŠãããããŸãã¯ä»ã®ãã®ã䜿çšããŠããŸãã ãããåæã«ããã®ãã¬ãŒã ã¯ãŒã¯ã®å€ãã®æ©èœã䜿çšããããã€/ãŸãã¯AngularJSã®ã¹ã¿ã€ã«ã§èšè¿°ãããŠããªãå€ãã®Angularã³ãŒããæ±ãå¿
èŠããããŸãã
ãã®èšäºã§ã¯ãAngularãã¬ãŒã ã¯ãŒã¯ã䜿çšããæ©èœãšæ©èœã®äžéšã玹ä»ããŸããèè
ã®æ§ãããªæèŠã«ãããšããããã¯ããã¥ã¢ã«ã§ååã«ã«ããŒãããŠããªãããéçºè
ã«ãã£ãŠäœ¿çšãããŠããŸããã ãã®èšäºã§ã¯ããã€ã³ã¿ãŒã»ãã¿ãŒãHTTPèŠæ±ã®äœ¿çšããŠãŒã¶ãŒãžã®ã¢ã¯ã»ã¹ãå¶éããããã®ã«ãŒãã¬ãŒãã®äœ¿çšã«ã€ããŠèª¬æããŸãã RxJSã®äœ¿çšãšã¢ããªã±ãŒã·ã§ã³ç¶æ
ã®ç®¡çã«é¢ããããã€ãã®æšå¥šäºé
ã瀺ãããŠããŸãã ãŸãããããžã§ã¯ãã³ãŒãã®èšèšã«é¢ããæšå¥šäºé
ã瀺ããŸããããã«ããããããããããžã§ã¯ãã³ãŒãããããããããããããçè§£ãããããªããŸãã èè
ã¯ããã®èšäºããAngularã«ç²Ÿéãå§ããã°ããã®éçºè
ã ãã§ãªããçµéšè±å¯ãªéçºè
ã«ã圹ç«ã€ããšãæãã§ããŸãã
HTTPã䜿çšãã
ã¯ã©ã€ã¢ã³ãWebã¢ããªã±ãŒã·ã§ã³ã®æ§ç¯ã¯ããµãŒããŒãžã®HTTPèŠæ±ãäžå¿ã«è¡ãããŸãã ãã®ããŒãã§ã¯ãHTTPãªã¯ãšã¹ããåŠçããããã®Angularãã¬ãŒã ã¯ãŒã¯ã®æ©èœã®ããã€ãã«ã€ããŠèª¬æããŸãã
ã€ã³ã¿ãŒã»ãã¿ãŒã®äœ¿çš
å Žåã«ãã£ãŠã¯ããªã¯ãšã¹ãããµãŒããŒã«å°éããåã«å€æŽããå¿
èŠãããå ŽåããããŸãã ãŸãã¯ãååçã倿Žããå¿
èŠããããŸãã Angular 4.3以éãæ°ããHttpClientããªãªãŒã¹ãããŸããã ã€ã³ã¿ãŒã»ãã¿ãŒã䜿çšããŠãªã¯ãšã¹ããã€ã³ã¿ãŒã»ããããæ©èœã远å ããŸããïŒã¯ãããããã¯æçµçã«ããŒãžã§ã³4.3ã§ã®ã¿è¿ãããŸããïŒããã¯ãAngularJã®æãæåŸ
ãããŠããªãã£ãæ©èœã®1ã€ã§ãAngularã«ç§»è¡ããŸããã§ããïŒã ããã¯ãhttp-apiãšå®éã®ãªã¯ãšã¹ãã®éã®äžçš®ã®ããã«ãŠã§ã¢ã§ãã
äžè¬çãªäœ¿çšäŸã®1ã€ã«èªèšŒããããŸãã ãµãŒããŒããå¿çãååŸããã«ã¯ããªã¯ãšã¹ãã«äœããã®èªèšŒã¡ã«ããºã ã远å ããå¿
èŠããããŸãã ã€ã³ã¿ãŒã»ãã¿ãŒã䜿çšãããã®ã¿ã¹ã¯ã¯ãéåžžã«ç°¡åã«è§£æ±ºãããŸãã
import { Injectable } from "@angular/core"; import { Observable } from "rxjs/Observable"; import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest } from @angular/common/http"; @Injectable() export class JWTInterceptor implements HttpInterceptor { intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { req = req.clone({ setHeaders: { authorization: localStorage.getItem("token") } }); return next.handle(req); } }
ã¢ããªã±ãŒã·ã§ã³ã¯è€æ°ã®ã€ã³ã¿ãŒã»ãã¿ãŒãæã€ããšãã§ããããããã§ãŒã³ã§ç·šæãããŸãã æåã®èŠçŽ ã¯ãAngularãã¬ãŒã ã¯ãŒã¯èªäœã«ãã£ãŠåŒã³åºãããŸãã ãã®åŸã次ã®ã€ã³ã¿ãŒã»ãã¿ãŒã«ãªã¯ãšã¹ããéä¿¡ãã責任ããããŸãã ãããè¡ãã«ã¯ãçµäºåŸããã«ãã§ãŒã³å
ã®æ¬¡ã®èŠçŽ ã®handleã¡ãœãããåŒã³åºããŸãã ã€ã³ã¿ãŒã»ãã¿ãŒãæ¥ç¶ããŸãã
import { BrowserModule } from "@angular/platform-browser"; import { NgModule } from "@angular/core"; import { AppComponent } from "./app.component"; import { HttpClientModule } from "@angular/common/http"; import { HTTP_INTERCEPTORS } from "@angular/common/http"; @NgModule({ declarations: [AppComponent], imports: [BrowserModule, HttpClientModule], providers: [ { provide: HTTP_INTERCEPTORS, useClass: JWTInterceptor, multi: true } ], bootstrap: [AppComponent] }) export class AppModule {}
ã芧ã®ãšãããã€ã³ã¿ãŒã»ãã¿ãŒã®æ¥ç¶ãšå®è£
ã¯éåžžã«ç°¡åã§ãã
é²æè¿œè·¡
HttpClient
ã®æ©èœã®1ã€ã¯ãèŠæ±ã®é²è¡ç¶æ³ã远跡ããæ©èœã§ãã ããšãã°ã倧ããªãã¡ã€ã«ãããŠã³ããŒãããå¿
èŠãããå ŽåãããããããŠã³ããŒãã®é²è¡ç¶æ³ããŠãŒã¶ãŒã«å ±åããå¿
èŠããããŸãã é²è¡ç¶æ³ãååŸããã«ã¯ã HttpRequest
ãªããžã§ã¯ãã®reportProgress
ããããã£ãtrue
èšå®ããå¿
èŠãããtrue
ã ãã®ã¢ãããŒããå®è£
ãããµãŒãã¹ã®äŸïŒ
import { Observable } from "rxjs/Observable"; import { HttpClient } from "@angular/common/http"; import { Injectable } from "@angular/core"; import { HttpRequest } from "@angular/common/http"; import { Subject } from "rxjs/Subject"; import { HttpEventType } from "@angular/common/http"; import { HttpResponse } from "@angular/common/http"; @Injectable() export class FileUploadService { constructor(private http: HttpClient) {} public post(url: string, file: File): Observable<number> { var subject = new Subject<number>(); const req = new HttpRequest("POST", url, file, { reportProgress: true }); this.httpClient.request(req).subscribe(event => { if (event.type === HttpEventType.UploadProgress) { const percent = Math.round((100 * event.loaded) / event.total); subject.next(percent); } else if (event instanceof HttpResponse) { subject.complete(); } }); return subject.asObservable(); } }
postã¡ãœããã¯ãããŠã³ããŒãã®é²è¡ç¶æ³ã衚ãObservable
ãè¿ããŸãã ããã§å¿
èŠãªã®ã¯ãã³ã³ããŒãã³ãã®ããŠã³ããŒãã®é²è¡ç¶æ³ã衚瀺ããããšã ãã§ãã
ã«ãŒãã£ã³ã° ã«ãŒãã¬ãŒãã®äœ¿çš
ã«ãŒãã£ã³ã°ã䜿çšãããšãã¢ããªã±ãŒã·ã§ã³èŠæ±ãã¢ããªã±ãŒã·ã§ã³å
ã®ç¹å®ã®ãªãœãŒã¹ã«ãããã§ããŸãã ç¶æ³ã«ãã£ãŠã¯ãç¹å®ã®ã³ã³ããŒãã³ããé
眮ãããŠãããã¹ã®å¯èŠæ§ãå¶éããåé¡ã解決ããå¿
èŠãããå ŽåããããããŸãã ãããã®å ŽåãAngularã«ã¯é·ç§»å¶éã¡ã«ããºã ããããŸãã äŸãšããŠãã«ãŒãã¬ãŒããå®è£
ãããµãŒãã¹ããããŸãã ã¢ããªã±ãŒã·ã§ã³ã§ããŠãŒã¶ãŒèªèšŒãJWTã䜿çšããŠå®è£
ãããŠãããšããŸãã ãŠãŒã¶ãŒãæ¿èªãããŠãããã©ããã確èªãããµãŒãã¹ã®ç°¡æããŒãžã§ã³ã¯ã次ã®ããã«è¡šãããšãã§ããŸã
@Injectable() export class AuthService { constructor(public jwtHelper: JwtHelperService) {} public isAuthenticated(): boolean { const token = localStorage.getItem("token");
ã«ãŒãã¬ãŒããå®è£
ããã«ã¯ã1ã€ã®canActivate
颿°ã§æ§æãããCanActivate
ã€ã³ã¿ãŒãã§ã€ã¹ãå®è£
ããå¿
èŠããããŸãã
@Injectable() export class AuthGuardService implements CanActivate { constructor(public auth: AuthService, public router: Router) {} canActivate(): boolean { if (!this.auth.isAuthenticated()) { this.router.navigate(["login"]); return false; } return true; } }
AuthGuardService
å®è£
ã§ã¯ãäžèšã®AuthGuardService
䜿çšããŠãŠãŒã¶ãŒã®æ¿èªãæ€èšŒããŸãã canActivate
ã¡ãœããã¯ãã«ãŒãã®ã¢ã¯ãã£ã忡件ã§äœ¿çšã§ããããŒã«å€ãè¿ããŸãã
ããã§ãäœæããã«ãŒãã¬ãŒããä»»æã®ã«ãŒããŸãã¯ãã¹ã«é©çšã§ããŸãã ãããè¡ãã«ã¯ã Routes
ã宣èšãããšãã«ã canActivate
ã»ã¯ã·ã§ã³ã§CanActivate
ã€ã³ã¿ãŒãã§ã€ã¹ãç¶æ¿ãããµãŒãã¹ãæå®ããŸãã
export const ROUTES: Routes = [ { path: "", component: HomeComponent }, { path: "profile", component: UserComponent, canActivate: [AuthGuardService] }, { path: "**", redirectTo: "" } ];
ãã®å Žåã /profile
ã«ãŒãã«ã¯ãªãã·ã§ã³ã®æ§æå€canActivate
ãŸãã AuthGuard
ã¯ããã®canActivate
ããããã£ãžã®åŒæ°ãšããŠæž¡ãããŸãã æ¬¡ã«ã誰ãã/profile
ãã¹ã«ã¢ã¯ã»ã¹ããããšãããã³ã«canActivate
ã¡ãœãããåŒã³åºãããŸãã ãŠãŒã¶ãŒãèš±å¯ãããŠããå Žåã /profile
ãã¹ã«ã¢ã¯ã»ã¹/login
ãŸããããã§ãªãå Žåã /login
ãã¹ã«ãªãã€ã¬ã¯ããããŸãã
canActivate
ã§ã¯ããã®ãã¹ã§ã³ã³ããŒãã³ããã¢ã¯ãã£ãåããããšã¯ã§ããŸãããåãæ¿ããããšã¯ã§ããŸããã ã³ã³ããŒãã³ãã®ã¢ã¯ãã£ããŒã·ã§ã³ãšããŒããä¿è·ããå¿
èŠãããå Žåããã®å Žåã¯canLoad
ã䜿çšã§ããŸãã CanLoad
å®è£
ã¯ã顿šã«ãã£ãŠè¡ãããšãã§ããŸãã
ã¯ããã³ã°RxJS
Angularã¯RxJSã®äžã«æ§ç¯ãããŠããŸãã RxJSã¯ãç£èŠå¯èœãªã·ãŒã±ã³ã¹ã䜿çšããŠãéåæããã³ã€ãã³ãããŒã¹ã®ããŒã¿ã¹ããªãŒã ãæäœããããã®ã©ã€ãã©ãªã§ãã RxJSã¯ãReactiveX APIã®JavaScriptå®è£
ã§ãã ã»ãšãã©ã®å Žåããã®ã©ã€ãã©ãªãæäœãããšãã«çºçãããšã©ãŒã¯ããã®å®è£
ã®åºæ¬ã«é¢ãã衚é¢çãªç¥èã«é¢é£ããŠããŸãã
ã€ãã³ãã«ãµã€ã³ã¢ãããã代ããã«éåæã䜿çšãã
Angularãã¬ãŒã ã¯ãŒã¯ã䜿çšããããã«ãªã£ãã°ããã®å€æ°ã®éçºsubscribe
ã¯ã Observable
subscribe
æ©èœã䜿çšããŠãã³ã³ããŒãã³ãå
ã®ããŒã¿ãåä¿¡ããã³ä¿åããŸãã
@Component({ selector: "my-component", template: ` <span>{{localData.name}} : {{localData.value}}</span>` }) export class MyComponent { localData; constructor(http: HttpClient) { http.get("api/data").subscribe(data => { this.localData = data; }); } }
代ããã«ãéåæãã€ãã䜿çšããŠãã³ãã¬ãŒããä»ããŠãµãã¹ã¯ã©ã€ãã§ããŸãã
@Component({ selector: "my-component", template: ` <p>{{data.name | async}} : {{data.value | async}}</p>` }) export class MyComponent { data; constructor(http: HttpClient) { this.data = http.get("api/data"); } }
ãã³ãã¬ãŒããä»ããŠãµãã¹ã¯ã©ã€ãããããšã«ãããAngularã¯ã³ã³ããŒãã³ããç Žæãããšãã«Observable
ããèªåçã«ãµãã¹ã¯ã©ã€ãè§£é€ããããããã¡ã¢ãªãªãŒã¯ãåé¿ããŸãã ãã®å ŽåãHTTPãªã¯ãšã¹ãã®å Žåãéåæãã€ãã䜿çšããŠãå®éã«ã¯å©ç¹ã¯ãããŸãããã1ã€ãé€ããŠ-ããŒã¿ãäžèŠã«ãªã£ãå Žåãéåæã¯ãªã¯ãšã¹ãããã£ã³ã»ã«ãããªã¯ãšã¹ãã®åŠçãå®äºããŸããã
Observables
å€ãã®æ©èœã¯ãæåã§ãµãã¹ã¯ã©ã€ããããšãã«ã¯äœ¿çšãããŸããã Observables
åäœã¯ãç¹°ãè¿ãïŒhttpèŠæ±ã§ã®å詊è¡ãªã©ïŒãã¿ã€ããŒããŒã¹ã®æŽæ°ããŸãã¯äºåãã£ãã·ã¥ã«ãã£ãŠæ¡åŒµã§ããŸãã
芳枬å¯èœç©ã瀺ãããã«$
æ¬¡ã®æ®µèœã¯ãã¢ããªã±ãŒã·ã§ã³ã®ãœãŒã¹ã³ãŒãã®èšèšã«é¢é£ããåã®æ®µèœããç¶ããŸãã Observable
ãåçŽãªå€æ°ãšåºå¥ããããã«ã倿°ãŸãã¯ãã£ãŒã«ãã®ååã«ã $
ãèšå·ã䜿çšããããã¢ããã€ã¹ããããšããããããŸãã ãã®åçŽãªããªãã¯ã¯ãéåæã䜿çšãããšãã®å€æ°ã®æ··ä¹±ãè§£æ¶ããŸãã
import { Component } from "@angular/core"; import { Observable } from "rxjs/Rx"; import { UserClient } from "../services/user.client"; import { User } from "../services/user"; @Component({ selector: "user-list", template: ` <ul class="user_list" *ngIf="(users$ | async).length"> <li class="user" *ngFor="let user of users$ | async"> {{ user.name }} - {{ user.birth_date }} </li> </ul>` }) export class UserList { public users$: Observable<User[]>; constructor(public userClient: UserClient) {} public ngOnInit() { this.users$ = this.client.getUsers(); } }
éäŒãããšãïŒéäŒïŒ
Angularãç°¡åã«çè§£ãããšãã«éçºè
ãæ±ããæãäžè¬çãªè³ªåã¯ããŸã 賌èªãè§£é€ããå¿
èŠãããå Žåãšããã§ãªãå Žåã§ãã ãã®è³ªåã«çããã«ã¯ããŸãçŸåšäœ¿çšãããŠããObservable
çš®é¡ã決å®ããå¿
èŠããããŸãã Angularã«ã¯ã2çš®é¡ã®Observable
ããããŸã-æéãšç¡éãããã€ãã¯ããããæéãçæããä»ã¯ããããç¡éã®å€ãçæããŸãã
Http
Observable
ã¯ã³ã³ãã¯ãã§ãDOMã€ãã³ãã®ãªã¹ããŒ/ãªã¹ããŒã¯ç¡éã®Observable
ã§ãã
ç¡éã®Observable
å€ãžã®ãµãã¹ã¯ã©ã€ããæåã§ïŒéåæãã€ãã䜿çšããã«Observable
è¡ãããå Žåãå¿
ãå¿çããå¿
èŠããããŸãã æéã®Observableãæåã§ãµãã¹ã¯ã©ã€ãããå Žåããµãã¹ã¯ã©ã€ããè§£é€ããå¿
èŠã¯ãããŸãããRxJSããããåŠçããŸãã ã³ã³ãã¯ããªObservables
ã®å Žåã Observables
å®è¡æéãå¿
èŠä»¥äžã«é·ãå ŽåïŒããšãã°ãè€æ°ã®HTTPãªã¯ãšã¹ããªã©ïŒã«ç»é²ãè§£é€ã§ããŸãã
ã³ã³ãã¯ããªObservables
ã®äŸïŒ
export class SomeComponent { constructor(private http: HttpClient) { } ngOnInit() { Observable.timer(1000).subscribe(...); this.http.get("http://api.com").subscribe(...); } }
ç¡éãªãã¶ãŒããã«ã®äŸ
export class SomeComponent { constructor(private element : ElementRef) { } interval: Subscription; click: Subscription; ngOnInit() { this.interval = Observable.interval(1000).subscribe(...); this.click = Observable.fromEvent(this.element.nativeElement, "click").subscribe(...); } ngOnDestroy() { this.interval.unsubscribe(); this.click.unsubscribe(); } }
以äžã«ãç»é²ãè§£é€ããå¿
èŠãããå Žåã®è©³çްã瀺ããŸãã
- ãã©ãŒã ããã³ãµãã¹ã¯ã©ã€ãå
ã®åã
ã®ã³ã³ãããŒã«ãããµãã¹ã¯ã©ã€ããè§£é€ããå¿
èŠããããŸãã
export class SomeComponent { ngOnInit() { this.form = new FormGroup({...}); this.valueChangesSubs = this.form.valueChanges.subscribe(...); this.statusChangesSubs = this.form.statusChanges.subscribe(...); } ngOnDestroy() { this.valueChangesSubs.unsubscribe(); this.statusChangesSubs.unsubscribe(); } }
- ã«ãŒã¿ãŒ ããã¥ã¡ã³ãã«ãããšãAngularã¯èªèº«ã®è³Œèªãè§£é€ããå¿
èŠããããŸããã ããã¯èµ·ãããŸãã ã ãããã£ãŠããããªãåé¡ãåé¿ããããã«ã次ã®ããã«æžããŸãã
export class SomeComponent { constructor(private route: ActivatedRoute, private router: Router) { } ngOnInit() { this.route.params.subscribe(..); this.route.queryParams.subscribe(...); this.route.fragment.subscribe(...); this.route.data.subscribe(...); this.route.url.subscribe(..); this.router.events.subscribe(...); } ngOnDestroy() {
- ç¡éã®ã·ãŒã±ã³ã¹ã äŸã¯ã
interva()
ãŸãã¯ã€ãã³ããªã¹ããŒ(fromEvent())
ã䜿çšããŠäœæãããã·ãŒã±ã³ã¹ã§ãã
export class SomeComponent { constructor(private element : ElementRef) { } interval: Subscription; click: Subscription; ngOnInit() { this.intervalSubs = Observable.interval(1000).subscribe(...); this.clickSubs = Observable.fromEvent(this.element.nativeElement, "click").subscribe(...); } ngOnDestroy() { this.intervalSubs.unsubscribe(); this.clickSubs.unsubscribe(); } }
takeUntilããã³takeWhile
RxJSã®ç¡éObservables
ã§ã®äœæ¥ãç°¡çŽ åããããã«ã2ã€ã®äŸ¿å©ãªé¢æ°takeUntil
ãštakeWhile
ãŸãã ãããã¯åãã¢ã¯ã·ã§ã³ãå®è¡ããŸã-ããæ¡ä»¶ã®çµããã«Observable
ãããµãã¹ã¯ã©ã€ããè§£é€ããŸããéãã¯åãå
¥ããããå€ã®ã¿ã§ãã takeWhile
ã¯boolean
åãå
¥ãã takeUntil
Subject
takeUntil
ã
takeWhile
äŸïŒ
export class SomeComponent implements OnDestroy, OnInit { public user: User; private alive: boolean = true; public ngOnInit() { this.userService .authenticate(email, password) .takeWhile(() => this.alive) .subscribe(user => { this.user = user; }); } public ngOnDestroy() { this.alive = false; } }
ãã®å Žåã alive
ãã©ã°ã倿Žããããšã Observable
ã¯ãµãã¹ã¯Observable
ãè§£é€ããŸãã ãã®äŸã§ã¯ãã³ã³ããŒãã³ããç Žæ£ãããããµãã¹ã¯ã©ã€ããè§£é€ããŸãã
takeUntil
äŸïŒ
export class SomeComponent implements OnDestroy, OnInit { public user: User; private unsubscribe: Subject<void> = new Subject(void); public ngOnInit() { this.userService.authenticate(email, password) .takeUntil(this.unsubscribe) .subscribe(user => { this.user = user; }); } public ngOnDestroy() { this.unsubscribe.next(); this.unsubscribe.complete(); } }
ãã®å Žåã Observable
ãããµãã¹ã¯Observable
ãè§£é€ããObservable
ã¯ã subject
ãæ¬¡ã®å€ãååŸããŠå®äºããããšãå ±åããŸãã
ãããã®é¢æ°ã䜿çšãããšããªãŒã¯ãåé¿ãããããŒã¿ã®ãµãã¹ã¯ã©ã€ãè§£é€ã«ããäœæ¥ãç°¡çŽ åãããŸãã 䜿çšããæ©èœã¯ïŒ ãã®è³ªåãžã®çãã¯ãå人çãªå¥œã¿ãšçŸåšã®èŠä»¶ã«åºã¥ããŠããå¿
èŠããããŸãã
Angularã¢ããªã±ãŒã·ã§ã³ã®ç¶æ
管çã@ ngrx / store
å€ãã®å Žåãè€éãªã¢ããªã±ãŒã·ã§ã³ãéçºãããšããç¶æ
ãä¿åãããã®å€æŽã«å¯Ÿå¿ããå¿
èŠã«çŽé¢ããŠããŸãã ReactJsãã¬ãŒã ã¯ãŒã¯äžã§éçºãããã¢ããªã±ãŒã·ã§ã³çšã®ã©ã€ãã©ãªã倿°ãããã¢ããªã±ãŒã·ã§ã³ã®ç¶æ
ãå¶åŸ¡ãããã®å€æŽã«å¯Ÿå¿ã§ããŸã-FluxãReduxãRedux-sagaãªã© Angularã¢ããªã±ãŒã·ã§ã³ã«ã¯ãReduxã«è§ŠçºãããRxJSããŒã¹ã®ç¶æ
ã³ã³ããïŒ@ ngrx / storeïŒããããŸãã ã¢ããªã±ãŒã·ã§ã³ã®ç¶æ
ãé©åã«ç®¡çããããšã§ãã¢ããªã±ãŒã·ã§ã³ã®ãããªãæ¡åŒµã«äŒŽãå€ãã®åé¡ããéçºè
ãæãããšãã§ããŸãã
Reduxãéžã¶çç±
Reduxã¯ãJavaScriptã¢ããªã±ãŒã·ã§ã³ã®äºæž¬å¯èœãªç¶æ
ã³ã³ãããŒãšããŠã®å°äœã確ç«ããŠããŸãã Reduxã¯FluxãšElmã«è§ŠçºãããŠããŸãã
Reduxã¯ãã¢ããªã±ãŒã·ã§ã³ãäžé£ã®ã¢ã¯ã·ã§ã³ã«ãã£ãŠå€æŽå¯èœãªåæç¶æ
ãšããŠèããããšãææ¡ããŠããŸããããã¯ãè€éãªWebã¢ããªã±ãŒã·ã§ã³ãæ§ç¯ããããã®åªããã¢ãããŒããšãªããŸãã
Reduxã¯ç¹å®ã®ãã¬ãŒã ã¯ãŒã¯ã«é¢é£ä»ããããŠããããReactçšã«éçºãããŸããããAngularãŸãã¯jQueryã§äœ¿çšã§ããŸãã
Reduxã®äž»ãªä»®å®ïŒ
- ã¢ããªã±ãŒã·ã§ã³ã®ç¶æ
å
šäœã®ããã®1ã€ã®ã¹ãã¢
- èªã¿åãå°çšç¶æ
- 倿Žã¯ãçŽç²ãªãæ©èœã«ãã£ãŠè¡ãããæ¬¡ã®èŠä»¶ã«åŸããŸãã
- ãããã¯ãŒã¯ãŸãã¯ããŒã¿ããŒã¹ãä»ããŠå€éšåŒã³åºããè¡ã£ãŠã¯ãªããŸããã
- æž¡ããããã©ã¡ãŒã¿ãŒã®ã¿ã«äŸåããå€ãè¿ããŸãã
- åŒæ°ã¯äžå€ãã€ãŸã 颿°ã¯ãããã倿Žãã¹ãã§ã¯ãããŸããã
- åãåŒæ°ã§çŽç²ãªé¢æ°ãåŒã³åºããšãåžžã«åãçµæãè¿ãããŸãã
ç¶æ
ç®¡çæ©èœã®äŸïŒ
// counter.ts import { ActionReducer, Action } from "@ngrx/store"; export const INCREMENT = "INCREMENT"; export const DECREMENT = "DECREMENT"; export const RESET = "RESET"; export function counterReducer(state: number = 0, action: Action) { switch (action.type) { case INCREMENT: return state + 1; case DECREMENT: return state - 1; case RESET: return 0; default: return state; } }
ã¢ããªã±ãŒã·ã§ã³ã®ã¡ã€ã³ã¢ãžã¥ãŒã«ã§ãReducerãã€ã³ããŒãããã StoreModule.provideStore(reducers)
颿°ã䜿çšããŠãAngularã€ã³ãžã§ã¯ã¿ãŒã§äœ¿çšã§ããããã«ããŸãã
// app.module.ts import { NgModule } from "@angular/core"; import { StoreModule } from "@ngrx/store"; import { counterReducer } from "./counter"; @NgModule({ imports: [ BrowserModule, StoreModule.provideStore({ counter: counterReducer }) ] }) export class AppModule { }
次ã«ã Store
ãµãŒãã¹ãå¿
èŠãªã³ã³ããŒãã³ããšãµãŒãã¹ã«å°å
¥ãããŸãã store.selectïŒïŒé¢æ°ã䜿çšããŠããã¹ã©ã€ã¹ãç¶æ
ãéžæããŸãã
// app.component.ts ... interface AppState { counter: number; } @Component({ selector: "my-app", template: ` <button (click)="increment()">Increment</button> <div>Current Count: {{ counter | async }}</div> <button (click)="decrement()">Decrement</button> <button (click)="reset()">Reset Counter</button>` }) class AppComponent { counter: Observable<number>; constructor(private store: Store<AppState>) { this.counter = store.select("counter"); } increment() { this.store.dispatch({ type: INCREMENT }); } decrement() { this.store.dispatch({ type: DECREMENT }); } reset() { this.store.dispatch({ type: RESET }); } }
@ ngrx /ã«ãŒã¿ãŒã¹ãã¢
å Žåã«ãã£ãŠã¯ãã¢ããªã±ãŒã·ã§ã³ã®ç¶æ
ãã¢ããªã±ãŒã·ã§ã³ã®çŸåšã®ã«ãŒãã«é¢é£ä»ãããšäŸ¿å©ã§ãã ãããã®å Žåã@ ngrx / router-storeã¢ãžã¥ãŒã«ãååšããŸãã ã¢ããªã±ãŒã·ã§ã³ãrouter-store
ã䜿çšããŠç¶æ
ãä¿åããã«ã¯ã routerReducer
ãæ¥ç¶ããã¡ã€ã³ã¢ããªã±ãŒã·ã§ã³ã¢ãžã¥ãŒã«ã§RouterStoreModule.connectRoute
ãžã®åŒã³åºãã远å ããã ãã§ãã
import { StoreModule } from "@ngrx/store"; import { routerReducer, RouterStoreModule } from "@ngrx/router-store"; @NgModule({ imports: [ BrowserModule, StoreModule.provideStore({ router: routerReducer }), RouterStoreModule.connectRouter() ], bootstrap: [AppComponent] }) export class AppModule { }
次ã«ã RouterState
ãã¢ããªã±ãŒã·ã§ã³ã®ã¡ã€ã³ç¶æ
ã«è¿œå ããŸãã
import { RouterState } from "@ngrx/router-store"; export interface AppState { ... router: RouterState; };
ããã«ãã¹ãã¢ã宣èšãããšãã«ã¢ããªã±ãŒã·ã§ã³ã®åæç¶æ
ã瀺ãããšãã§ããŸãã
StoreModule.provideStore( { router: routerReducer }, { router: { path: window.location.pathname + window.location.search } } );
ãµããŒããããŠããã¢ã¯ã·ã§ã³ïŒ
import { go, replace, search, show, back, forward } from "@ngrx/router-store"; // store.dispatch(go(["/path", { routeParam: 1 }], { query: "string" })); // store.dispatch(replace(["/path"], { query: "string" })); // store.dispatch(show(["/path"], { query: "string" })); // store.dispatch(search({ query: "string" })); // store.dispatch(back()); // store.dispatch(forward());
UPDïŒã³ã¡ã³ãã¯ããããã®ã¢ã¯ã·ã§ã³ã¯ãæ°ããããŒãžã§ã³ã®https://github.com/ngrx/platform/blob/master/MIGRATION.md#ngrxrouter-storeã®æ°ããããŒãžã§ã³@ngrxã§ã¯äœ¿çšã§ããªãããšã瀺åããŠããŸãã
ç¶æ
ã³ã³ããã䜿çšãããšãè€éãªã¢ããªã±ãŒã·ã§ã³ãéçºããéã®å€ãã®åé¡ãè§£æ¶ãããŸãã ãã ããç¶æ
管çãã§ããã ãåçŽã«ããããšãéèŠã§ãã å€ãã®å Žåãç¶æ
ãé床ã«ãã¹ããããŠããã¢ããªã±ãŒã·ã§ã³ãåŠçããå¿
èŠããããã¢ããªã±ãŒã·ã§ã³ã®çè§£ãè€éã«ãªããŸãã
ã³ãŒãç·šæ
import
ã§ããã°ãåŒãåãé€ã
å€ãã®éçºè
ã¯ã import
åŒimport
ããªãããã°ãç¶æ³ãèªèããŠããŸãã ããã¯ãåå©çšå¯èœãªã©ã€ãã©ãªã倿°ããå€§èŠæš¡ãªã¢ããªã±ãŒã·ã§ã³ã§ç¹ã«é¡èã§ãã
import { SomeService } from "../../../core/subpackage1/subpackage2/some.service";
ãã®ã³ãŒãã§ä»ã«äœãæªãã§ããïŒ ã³ã³ããŒãã³ããå¥ã®ãã£ã¬ã¯ããªã«è»¢éããå¿
èŠãããå Žåã import
ã®åŒã¯ç¡å¹ã«ãªããŸãã
ãã®å Žåããšã€ãªã¢ã¹ã䜿çšãããšã import
ããã°ãåŒãimport
ããŠãã³ãŒãããããããã«ããããšãã§ããŸãã ãšã€ãªã¢ã¹ã䜿çšã§ããããã«ãããžã§ã¯ããæºåããã«ã¯ãtsconfig.jsonã«baseUrlããããã£ãšpathããããã£ã远å ããå¿
èŠããããŸãã
/ tsconfig.json { "compilerOptions": { ... "baseUrl": "src", "paths": { "@app/*": ["app/*"], "@env/*": ["environments/*"] } } }
ãããã®å€æŽã«ããããã©ã°ã€ã³ãç°¡åã«ç®¡çã§ããŸãã
import { Component, OnInit } from "@angular/core"; import { Observable } from "rxjs/Observable"; import { SomeService } from "@app/core"; import { environment } from "@env/environment"; import { LocalService } from "./local.service"; @Component({ }) export class ExampleComponent implements OnInit { constructor( private someService: SomeService, private localService: LocalService ) { } }
ãã®äŸã§ã¯ãé¢åãªè¡šçŸã§ã¯ãªãã SomeService
@app/core
ããçŽæ¥ã€ã³ããŒããããŸãïŒäŸïŒ @app/core/some-package/some.service
ïŒã ããã¯ãã¡ã€ã³ã®index.ts
ãã¡ã€ã«å
ã®ãããªãã¯ã³ã³ããŒãã³ãã®åãšã¯ã¹ããŒãã®ãããã§å¯èœã§ãã ãã¹ãŠã®ãããªãã¯ã¢ãžã¥ãŒã«ãåãšã¯ã¹ããŒãããå¿
èŠãããããã±ãŒãžããšã«index.ts
ãã¡ã€ã«ãäœæããããšããå§ãããŸãã
// index.ts export * from "./core.module"; export * from "./auth/auth.service"; export * from "./user/user.service"; export * from "./some-service/some.service";
ã³ã¢ãå
±æãããã³æ©èœã¢ãžã¥ãŒã«
ã¢ããªã±ãŒã·ã§ã³ã³ã³ããŒãã³ããããæè»ã«ç®¡çããããã«ãæç®ãããŸããŸãªã€ã³ã¿ãŒããããªãœãŒã¹ã§ãã³ã³ããŒãã³ãã®å¯èŠæ§ãåºããããšããå§ãããŸãã ãã®å Žåãã¢ããªã±ãŒã·ã§ã³ã®ã³ã³ããŒãã³ãã®ç®¡çãç°¡çŽ åãããŸãã æ¬¡ã®åé¢ãæãäžè¬çã«äœ¿çšãããŸãïŒã³ã¢ãå
±æãããã³æ©èœã¢ãžã¥ãŒã«ã
ã³ã¢ã¢ãžã¥ãŒã«
CoreModuleã®äž»ãªç®çã¯ãã¢ããªã±ãŒã·ã§ã³å
šäœã«å¯ŸããŠ1ã€ã®ã€ã³ã¹ã¿ã³ã¹ãæã€ãµãŒãã¹ãèšè¿°ããããšã§ãïŒã€ãŸããã·ã³ã°ã«ãã³ãã¿ãŒã³ãå®è£
ããŸãïŒã å€ãã®å Žåããããã«ã¯èªèšŒãµãŒãã¹ãŸãã¯ãŠãŒã¶ãŒæ
å ±ãååŸããããã®ãµãŒãã¹ãå«ãŸããŸãã CoreModuleã®äŸïŒ
import { NgModule, Optional, SkipSelf } from "@angular/core"; import { CommonModule } from "@angular/common"; import { HttpClientModule } from "@angular/common/http"; import { SomeSingletonService } from "./some-singleton/some-singleton.service"; @NgModule({ imports: [CommonModule, HttpClientModule], declarations: [], providers: [SomeSingletonService] }) export class CoreModule { constructor( @Optional() @SkipSelf() parentModule: CoreModule ) { if (parentModule) { throw new Error("CoreModule is already loaded. Import only in AppModule"); } } }
å
±æã¢ãžã¥ãŒã«
ãã®ã¢ãžã¥ãŒã«ã§ã¯ãåçŽãªã³ã³ããŒãã³ãã«ã€ããŠèª¬æããŸãã ãããã®ã³ã³ããŒãã³ãã¯ãä»ã®ã¢ãžã¥ãŒã«ããã³ã³ã¹ãã©ã¯ã¿ãŒã«äŸåé¢ä¿ãã€ã³ããŒããŸãã¯æ¿å
¥ããŸããã ã³ã³ããŒãã³ããã³ãã¬ãŒãã®å±æ§ãä»ããŠãã¹ãŠã®ããŒã¿ãåãåãå¿
èŠããããŸãã SharedModule
ã¯ã¢ããªã±ãŒã·ã§ã³ã®ä»ã®éšåã«äŸåãããAngular Materialã³ã³ããŒãã³ããŸãã¯ä»ã®UIã©ã€ãã©ãªãã€ã³ããŒãããã³åãšã¯ã¹ããŒãããã®ã«ãçæ³çãªå Žæã§ãã
import { NgModule } from "@angular/core"; import { CommonModule } from "@angular/common"; import { FormsModule } from "@angular/forms"; import { MdButtonModule } from "@angular/material"; import { SomeCustomComponent } from "./some-custom/some-custom.component"; @NgModule({ imports: [CommonModule, FormsModule, MdButtonModule], declarations: [SomeCustomComponent], exports: [ CommonModule, FormsModule, MdButtonModule, SomeCustomComponent ] }) export class SharedModule { }
æ©èœã¢ãžã¥ãŒã«
ããã§ãAngularã¹ã¿ã€ã«ã¬ã€ããç¹°ãè¿ãããšãã§ããŸãã ç¬ç«ããã¢ããªã±ãŒã·ã§ã³æ©èœããšã«åå¥ã®FeatureModuleãäœæãããŸãã FeatureModuleã¯ã CoreModule
ããã®CoreModule
ãµãŒãã¹ãã€ã³ããŒãããå¿
èŠããããŸãã ããã¢ãžã¥ãŒã«ãå¥ã®ã¢ãžã¥ãŒã«ãããµãŒãã¹ãã€ã³ããŒãããå¿
èŠãããå Žåããããããã®ãµãŒãã¹ãCoreModule
ã«ç§»åããå¿
èŠããããŸãã
å Žåã«ãã£ãŠã¯ãäžéšã®ã¢ãžã¥ãŒã«ã§ã®ã¿ãµãŒãã¹ã䜿çšããå¿
èŠãããã CoreModule
ã«ãšã¯ã¹ããŒãããå¿
èŠã¯ãããŸããã ãã®å Žåããããã®ã¢ãžã¥ãŒã«ã§ã®ã¿äœ¿çšãããç¹å¥ãªSharedModule
äœæã§ããŸãã
ã¢ãžã¥ãŒã«ãäœæãããšãã«äœ¿çšãããåºæ¬çãªã«ãŒã«ã¯ãä»ã®ã¢ãžã¥ãŒã«ã«äŸåããã CoreModule
æäŸãããµãŒãã¹ãšSharedModule
æäŸããã³ã³ããŒãã³ãã®ã¿ã«äŸåããã¢ãžã¥ãŒã«ãäœæããããšSharedModule
ã
ããã«ãããéçºãããã¢ããªã±ãŒã·ã§ã³ã®ã³ãŒããããã¯ãªãŒã³ã«ãªããä¿å®ããã³æ¡åŒµã容æã«ãªããŸãã ãŸãããªãã¡ã¯ã¿ãªã³ã°ã«å¿
èŠãªåŽåãåæžããŸãã ãã®ã«ãŒã«ã«åŸãã°ã1ã€ã®ã¢ãžã¥ãŒã«ã倿ŽããŠããã¢ããªã±ãŒã·ã§ã³ã®æ®ãã®éšåã«åœ±é¿ãäžããããç Žå£ãããããããšã¯ãããŸããã
åç
§è³æ
- https://github.com/ngrx/store
- http://stepansuvorov.com/blog/2017/06/angular-rxjs-unsubscribe-or-not-unsubscribe/
- https://medium.com/@tomastrajan/6-best-practices-pro-tips-for-angular-cli-better-developer-experience-7b328bc9db81
- https://habr.com/post/336280/
- https://angular.io/docs