Which practice is best for handling HTML Events in Angular? Angular event binding or RxJS fromEvent function?

Viewed 286

In Angular the most common and easy practice to handle HTML event, it is a simple event binding, but when logic become more complex, it is rational to use RxJS operators.

Let's see at two boilerplate examples:

Live code

@Component({
  selector: 'app-native-handler',
  template: `
    <ul>
      <li *ngFor="let user of users">{{ user }}</li>
    </ul>

    <form [formGroup]="form" (ngSubmit)="onSubmit()">
      <label>
        Name:
        <input type="text" formControlName="name">
      </label>

      <input type="submit" [disabled]="form.invalid">
    </form>
  `,
  styleUrls: ['./native-handler.component.css']
})
class NativeHandlerComponent implements OnInit {
  form = new FormGroup({
    name: new FormControl('', Validators.required)
  });

  users: string[] = ['Foo', 'Bar', 'Baz'];

  constructor() {}

  ngOnInit() {}

  onSubmit() {
    let name = this.form.controls.name.value;

    this.users.push(name);

    this.form.reset();
  }
}


@Component({
  selector: 'app-rxjs-handler',
  template: `
    <ul>
      <li *ngFor="let user of users">{{ user }}</li>
    </ul>

    <form #formEl [formGroup]="form">
      <label>
        Name:
        <input type="text" formControlName="name">
      </label>

      <input type="submit" [disabled]="form.invalid">
    </form>
  `,
  styleUrls: ['./rxjs-handler.component.css']
})
class RxjsHandlerComponent implements OnInit, AfterViewInit {
  @ViewChild('formEl') formEl: ElementRef;

  form = new FormGroup({
    name: new FormControl('', Validators.required)
  });

  users: string[] = ['Foo', 'Bar', 'Baz'];

  constructor() {}

  ngOnInit() {}

  ngAfterViewInit() {
    fromEvent(this.formEl.nativeElement, 'submit')
      .pipe(tap(() => this.onSubmit()))
      .subscribe();
  }

  onSubmit() {
    let name = this.form.controls.name.value;

    this.users.push(name);

    this.form.reset();
  }
}

In the first example, we don't have flexibility to do an extra async operations, unless we won't create another Observable inside onSubmit() function and subscribe to it double (first time when we do the event binding and second when we subscribe inside onSubmit() function).

On another hand, in the second example, we can pipe() whatever async strategy we want (switchMap, concatMap, mergeMap and etc) and we don't need to create any extra Observable. For example:

fromEvent(this.formEl.nativeElement, 'submit')
  .pipe(
    takeUntil(this.unsubscribe$),
    tap(() => this.onSubmit()),
    concatMap(() => this.http.post('/api/user', { name })),
    concatMap(() => this.storageSerivce.push(name)),
    catchError(...)
    ...
  )
  .subscribe()

So my question is, is fromEvent function better than the event binding? Is it readable enough? Is it used commonly or do you think it should be used commonly (at least when logic become complex)?

In another words, is it best practice?

2 Answers

Isn't it quite obvious, its event binding. Mainly for 3 reason

  • Angular Event binding was created for this exact functionality.
  • fromEvent is an RxJs feature, which you have to subscribe. And later you have to unsubscribe since it persist, else it creates memory leakage. Note with Angular event binding you don't have to subscribe and unsubscribe.
  • Finally, with fromEvent you have to pass the DOM element and hence you have to access it using this.formEl.nativeElement. With Angular you want to avoid such access/manipulation of DOM elements directly on code, such style are typical of jQuery/Javascript.

This can be an opinion based question. But in general BOTH are used depending on what you are trying to achive.

  • If you are trying to just open a modal on a button click or just do a simple action, the event binding is meant for that.
  • If you are chaining rxjs functions and trying to achive a more complex behavior then you can and should use observables and rxjs. And you wouldn't need the formElement ref or anything like that.

BUT you shouldn't use it as the example you provided above with the fromEvent. You would probably create a new observable and emit it on the submit event.

ex (html):

<form [formGroup]="form" (ngSubmit)="formSubmit.next(form.value)">
</form>

ex (ts):

this.formSubmit.pipe(
    takeUntil(this.unsubscribe$),
    tap(() => this.onSubmit()),
    concatMap(() => this.http.post('/api/user', { name })),
    concatMap(() => this.storageSerivce.push(name)),
    catchError(...)
    ...
  )
  .subscribe()
Related