2017/8/25
RxJSを活用した
Angularアプリの状態管理設計
hashtag #d3_angular
Rx.Observable.fromEvent(window, 'click')
.distinctUntilChanged(
(prev, next) => prev.pageX === next.pageX
)
.filter(ev => ev.pageX < 400 && ev.pageY < 400)
.map(ev => 'mouse click, X:' + ev.pageX + ', Y:' + ev.pageY)
.subscribe(msg => console.log(msg));
このスライドに出てくるコードをCopyボタンでコピーしてコンソールに貼って動かしてみてください
var button = document.querySelector('#the-button');
button.addEventListener('click', () => console.log('Clicked!'));
Promise.resolve('oi')
.then(msg => console.log(msg));
var button2 = document.querySelector('#the-button2');
Rx.Observable.fromEvent(button2, 'click')
.subscribe(() => console.log('Clicked!'));
Rx.Observable.fromPromise(Promise.resolve('oi'))
.subscribe(msg => console.log(msg));
うれしくなくてもAngularが
使ってるので覚えざるを得ない😂
6つのコンセプトに分類できる
Rx.Observable.from(['foo', 'bar', 'baz', 'qux'])
.subscribe(console.log); // 'foo', 'bar', 'baz', 'qux'
Rx.Observable.range(1,4)
.subscribe(console.log); // 1,2,3,4
Rx.Observable.fromEvent(window, 'mousemove')
.subscribe(console.log); // すごいことに
myObservable
.subscribe({
next: (x) => console.log(x),
error: (e) => console.error(e),
complete: () => console.log('completed')
});
// nextだけの場合短く書ける
// myObservable.subscribe((x) => console.log(x))
var observer = {
next: (x) => console.log(x),
error: (e) => console.error(e),
complete: () => console.log('completed')
};
XMLHttpRequestと組み合わせてオレオレRxJSHttpクライアントが作れそう?
var observable = Rx.Observable.create((observer) => {
observer.next(1);
observer.next(2);
setTimeout(() => {
observer.next(3);
observer.complete();
}, 1000);
});
observable.subscribe(
(x) => console.log('value: ' + x),
(e) => console.log(e),
() => console.log('completed')
);
@Component({
template: `
<h1>User</h1>
<p>{{ user.name }}<p>`
})
export class UserComponent {
user: User;
constructor(private http: HttpClient) {
http.get<User>('/api/user')
.subscribe(user => this.user = user);
}
}
@Component({
template: `
<div *ngIf="user$ | async as user else loading">
<p>{{ user.name }}</p>
</div>
<ng-template #loading>
<span>loading</span>
</ng-template>`
})
export class UserComponent {
// Observable<T>な変数に$をつける文化っぽい
user$: Observable<User>
constructor(private http: HttpClient) {
this.user$ = http.get<User>('/api/user');
}
}
// true:ログイン成功 false:失敗
login(name: string, password: string): Observable<boolean> {
return http.post('/login', { name, password })
.mapTo(true)
.catch((res: HttpErrorResponse) => {
if (res.status === 401) {
return Observable.of(false);
} else {
return Observable.throw(res);
}
});
}
constructor(
private route: ActivatedRoute,
private service: HeroService
) {}
ngOnInit() {
// /heroes?id=10 のようなクエリパラメータの更新を監視
this.route.paramMap
.switchMap((params: ParamMap) =>
this.service.getHero(params.get('id'))
)
.subscribe((hero: Hero) => this.hero = hero);
}
@Component({
template: `
<input type="text" #input
(keyup)="onChange(input.value)" />
<p>さては{{ suggest$ | async }}だなオメー</p>`
})
export class AppComponent {
word$ = new Subject<string>();
suggest$: Observable<string>
constructor(private http: HttpClient) {
this.suggest$ = this.word$
.debounceTime(500)
.distinctUntilChanged()
.switchMap(word => {
return this.http.get<string>(`/search?word=${word}`);
});
}
onChange(word: string) {
this.word$.next(word);
}
}
// 再掲
var observable = Rx.Observable.create((observer) => {
observer.next(1);
observer.next(2);
setTimeout(() => {
observer.next(3);
observer.complete();
}, 1000);
});
observable.subscribe(
(x) => console.log('value: ' + x),
(e) => console.log(e),
() => console.log('completed')
);
// Angularのコード
getUser(): Observable<User> {
// http.get<T>はObservable<T>を返す
return this.http.get<User>('/api/user');
}
// ユーザーデータをUserProfileComponentでも
// UserAvatarComponentでも使いたい
// けど2回API叩くのは良くない
// getUser()を実行するたびにHTTPリクエストが飛ぶ
// user$をいろんなところからsubscribeする
public user$ = new Subject();
getUser() {
this.http.get<User>('/api/user')
.subscribe(user => user$.next(user));
// user$をsubscribeしている全員に値が配信される
}
下記コードをコンソール上に貼り付けて実行した後、どちらかのボタンを押してsubscribeする。
BehaviorSubjectはその時点の値が流れてくるが、Subjectは次のsetInterval実行まで(最大2秒間)値が来ない
window.behaviorSubject = new Rx.BehaviorSubject(0);
window.subject = new Rx.Subject();
window.count = 0;
setInterval(() => {
behaviorSubject.next(++count);
subject.next(count);
}, 2000);