Angular Event Emitters with RxJS Subscriptions and Subjects

Home / Angular Event Emitters with RxJS Subscriptions and Subjects

While I was working through my post regarding generically formatting Angular data within a component, another generic implementation that I needed arose. I needed a way to, in a very generic fashion, raise events from a child component to its parent. Within the context of an Angular table, this could typically coincide with a click event that needs to take action, or apply some logic, to the child’s bound data. Event Emitters in Angular readily facilitate this type of functionality.


For my use case, I wanted to put a check-box within my table to let users choose a row. That, in and of itself, doesn’t really require any sort of event handling. However, I wanted to perform two simple logic checks:

  • Ensure that only a single item is checked
  • Ensure that at least one item is checked (IE – can’t uncheck the selected item)

The screen capture below illustrates this concept. We have a simple table which a checkbox column. Only one row can be selected and we will perform some action when a row is clicked. A “cellClicked” event can be seen the developer console along with the row of data being passed through the event.

The logic here isn’t really all that important. The relevant thing is that whatever model property the check-box is bound to must be handled/manipulated by the parent component. The parent component, therefore, must know when a click occurs. In specific (non-generalized) components, this is easily accomplished by the @Input and @Output attributes. When we’re applying run time, generic eventting, those attributes won’t help.

Since this is going to be used with my Custom Table, I created a simple class called CustomTableEmitter that is injectable:

import { Injectable, EventEmitter} from '@angular/core';
import { Observable, Subject, Subscription, Subscriber } from 'rxjs/Rx';

@Injectable()
export class CustomTableEmitter {
    private events = new Subject();
    subscribe(next, error?, complete?): Subscription {
        return this.events.subscribe(next, error, complete);
    }
    next(event) { this.events.next(event); }
}

The Subject provides us with an Observable to which others can subscribe (using an RxJS Subscription). Subjects, if you don’t know, are a special type of Observable that allows multi-casting to lots of subscribers, which is exactly what we’re looking for in this scenario of event emitting.

In order for this to work from a parent/child (sort of kind of more like a singleton pattern) the two, or more components, that are emitting and receiving must share the same object. This means that we have to define the class as a provider in the consuming component. The consumer is, in my case, the top-level component that will host the custom table. We do this in the components definition:

@Component({
    templateUrl: 'rootcomponent.html',
    providers: [CustomTableEmitter],
    changeDetection: ChangeDetectionStrategy.OnPush
})

With that definition in place, any custom table that we have on the page will receive the same emitter. This is important since the “root,” or any other child of the root, will be a subscriber.

Now, in the CustomTable, I define my template, specifically, with a checkbox that has a click event. We define a click event that will call a method like this:

<td *ngFor="let column of options.columns">
    <a *ngIf="column.isAnchor" [routerLink]="getRouterLink(row, column)" [innerHTML]="getCellValue(row, column)"></a>
    <span *ngIf="!column.isDisabledCheckbox && !column.isCheckbox && !column.isAnchor" [innerHTML]="getCellValue(row, column)"></span>
    <input type="checkbox" *ngIf="column.isDisabledCheckbox || column.isCheckbox" [checked]="getCellValue(row, column)"
            [disabled]="column.isDisabledCheckbox" (click)="setCellValue(row, column, !getCellValue(row, column), $event)">
</td>

During the “setCellValue” click event, we will emit a “cellClicked” event and pass in the data for the row. This allows any subscriber to have a meaningful event to handle.

Within the custom table directive, we simply use the CustomTableEmitter’s exposed “next” method to trigger the event. This part is short and sweet:

if (this.emitter) {
    this.emitter.next({ name: 'cellClicked', data: { row: row, column: column, value: value } });
}

Back to the rootComponent, subscribing to this event can be handled in ngOnInit().

ngOnInit() {
    this.emitterSubscribe();
    this.initTableOptions();
}

The emitterSubscribe will take the CustomTableEmitter that is injected (and called emitter) and perform the necessary subscribing. We’ll also track the Subscription by maintaining a handle to it. This is important for cleanup so that we unsubscribe (typically during ngOnDestroy) when we want.

private emitterSubscription: Subscription;

emitterSubscribe() {
    this.emitterSubscription =
        this.emitter.subscribe(
            (msg) => {
                console.log(msg);
                if (msg.name === 'cellClicked') {
                    var column: CustomTableColumnDefinition = <CustomTableColumnDefinition>msg.data.column;
                    // Now do something with the bound data in msg.data.row ...
                }
            });
}

And that’s it. Now, every time any checkbox (selector) that I have on each row in the CustomTable will trigger an event to the containing component so that, if necessary, an action can be taken when a row is selected. I haven’t finished putting together a plunker to demo all of this, but will add one soon.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.