Observable from <button> click event in Angular2
What's the preferred way to create an observable from a button's onclick event using Angular 2?
I'm not sure if it's considered best practice to grab the native element from the DOM in the component code (how do I do this?), or if there's some other shortcut I don't know about.
Don't overthink it.
@ViewChild('button') button;
clicks$:Observable<any>;
ngOnInit() {
this.clicks$ = Observable.fromEvent(this.button.nativeElement, 'click');
}
You can use Observable.fromEvent
like explained in Angular2 RxJS getting 'Observable_1.Observable.fromEvent is not a function' error
Or just forward to an observable like
private obs = new Subject();
public obs$ = this.obs.asObservable();
@HostListener('click', ['$event'])
clickHandler(event){
this.obs.next(event);
}
or
<button (click)="obs.next($event)">
@Gunter's example didn't quite work for me, because my compiler didn't recognize publ
.
Here's an example of what worked for me:
modal.component.ts
import { Output, Component } from '@angular/core';
import {Subject} from "rxjs/Subject";
export class MyModal{
private clickStream = new Subject<Event>();
@Output() observ = this.clickStream.asObservable();
buttonClick(event:Event){
this.clickStream.next(event);
}
}
Inside modal.component.html
:
<button type="button" class="btn btn-default" (click)="buttonClick($event)">click me</button>
If you try to use @ViewChild and your button isn't visible on the page at init time (due to an *ngIf) then the assignment will be null.
You can use a setter in conjunction with @ViewChild, and run your initialization when the button first appears.
@ViewChild('btnAdd')
set btnAdd(btnAdd: Button) { ... }
This quickly gets clumsy and inconvenient - especially if you create an observable stream from this.
A hybrid way might be as follows:
btnAskAnotherClicks$ = new Subject<Event>();
<button mat-flat-button (click)="btnAskAnotherClicks$.next($event)">Ask another question...</button>
This allows you to use the click stream to create chains, but no issues if the button is initially hidden due to *ngIf.
Don't like next
in your template? Neither do I particularly. But I'm ok with async
, and they're both implementation details. Well that's up to you to decide -)
For those using AngularMaterial buttons and pipeable RxJS operators, some slight mods to @JoshuaDavid's answer:
Some button in template tagged with a template variable:
<button #btnTemplateName mat-icon-button></button>
The component code:
import { Observable, fromEvent } from 'rxjs';
// Note importing from lettable/pipeable operators - 'operators' plural
import { tap } from 'rxjs/operators';
import { MatButton } from '@angular/material/button';
//Access the button through the template variable, typed to MatButton
@ViewChild('btnTemplateName') myBtn: MatButton;
myBtnClicks$: Observable<any>;
ngAfterViewInit() {
// Note the need to access the native element in MatButton through the extended property chain
this.myBtnClicks$ =
Observable.fromEvent(this.myBtn._elementRef.nativeElement, 'click');
// Can now subscribe (using lettable/pipeable operators)
this.myBtnClicks$.pipe(
tap(() => console.log("Button clicked")),
)
.subscribe(event => console.log("Event:" + JSON.stringify(event)));
}