Search Lessons, Code Snippets, and Videos
search by algolia
X
#native_cta# #native_desc# Sponsored by #native_company#

User Presence System in Realtime - Online, Offline, Away

Episode 41 written by Jeff Delaney
full courses for pro members

The Firebase blog provides a good overview for building a presence system in Firebase, but it doesn’t exactly fit with AngularFire2. In this lesson, I will show you how to create a full-fledged user presence system with Angular4 and Firebase. This is a must-have feature if you’re building a chat app or anything that has realtime user interaction.

Our users will have three possible status.

  1. Online - Connected to database
  2. Offline - Disconnected from database
  3. Away - Connected to database, but no recent activity

realtime user status updates to offline or online in the firebase database

Prerequisites

I am assuming that you already have user authentication in place with AngularFire2. The topic of Firebase user auth is covered extensively, so I will only be brushing over it. This user presence system will work with any Firebase auth paradigm, i.e. OAuth, Anonymous, Email/Password, Phone, etc.

See if User is Connected to the Firebase Database

Firebase has a secret database location .info/connected that will tell you if a connection is currently active to the realtime database. This will be the variable that determines our online/offline statuses. It is not tied to a specific user, but rather the instance of an app that is consuming it.

Set Online Status

Below we have created an AuthService. When the user successfully authenticates, we will subscribe to the connection info and update their status accordingly. We are giving users in the database a status property that we can query from anywhere in the app.

how your user presence system should look in the database

import { Injectable } from '@angular/core';
import { AngularFireDatabase, FirebaseListObservable } from 'angularfire2/database';
import { AngularFireAuth } from 'angularfire2/auth';
import * as firebase from 'firebase/app';

@Injectable()
export class AuthService {

userId: string; // current user uid

constructor(private afAuth: AngularFireAuth,
private db: AngularFireDatabase) {

/// Subscribe to auth state in firebase
this.afAuth.authState
.do(user => {
if (user) {
this.userId = user.uid
this.updateOnConnect()
}

})
.subscribe();


}

/// Helper to perform the update in Firebase
private updateStatus(status: string) {
if (!this.userId) return

this.db.object(`users/` + this.userId).update({ status: status })
}


/// Updates status when connection to Firebase starts
private updateOnConnect() {
return this.db.object('.info/connected')
.do(connected => {
let status = connected.$value ? 'online' : 'offline'
this.updateStatus(status)
})
.subscribe()
}


}

Set Offline Status

Currently, when a users closers their browser tab or app their status will remain online. So how do we update the status to offline after the app is closed?. Thankfully, the core Firebase SDK has an onDisconect() function that will be fired when the connection is closed. In this case, we can just make a reference to the user’s status, then update the value to offline.

user status updates to offline after closing the app

constructor(private afAuth: AngularFireAuth,
private db: AngularFireDatabase) {

this.afAuth.authState
.do(user => {
if (user) {
this.userId = user.uid
this.updateOnConnect()
this.updateOnDisconnect() // <-- new line added
}

})
.subscribe();

}


/// ...omitted

/// Updates status when connection to Firebase ends
private updateOnDisconnect() {
firebase.database().ref().child(`users/${this.userId}`)
.onDisconnect()
.update({status: 'offline'})
}

Set Away Status Status

First, import a few RxJS pieces.

import { Observable } from 'rxjs/Observable'
import { Subscription } from 'rxjs/Subscription'
import 'rxjs/add/observable/timer';
import 'rxjs/add/operator/throttleTime';

We can determine if a user has gone idle by listening for mousemove events. Here’s how the process works:

  1. Convert mouse events to an Observable
  2. Reset an Observable timer when a new mouse event fires
  3. If timer runs out, set user status to away

user status updates to away after inactivity

Mouse events fire way more often than we need, but we can throttle then with RxJS throttleTime. In this example, we throttle every 2 seconds, so this will emit only the first event within a 2 second time period.

This code creates an Observable timer that will emit a value after 5000ms (5 seconds). In the real world, you would want to set it much longer than that - maybe 5 minutes or so.

Keep in mind, we are are creating two new subscriptions here. Because this could result in memory leaks, you want to unsubscribe when you no longer need them. In this case, we call unsubscribe when the user signs out.

mouseEvents:  Subscription
timer: Subscription;

constructor(private afAuth: AngularFireAuth,
private db: AngularFireDatabase) {

/// Subscribe to auth state in firebase
this.afAuth.authState
.do(user => {
if (user) {
this.userId = user.uid
this.updateOnConnect()
this.updateOnDisconnect()
this.updateOnIdle() // <-- new line added
}

})
.subscribe();


}

/// ... Omitted

/// Listen for mouse events to update status
private updateOnIdle() {

this.mouseEvents = Observable
.fromEvent(document, 'mousemove')
.throttleTime(2000)
.do(() => {
this.updateStatus('online')
this.resetTimer()
})
.subscribe()
}

/// Reset the timer
private resetTimer() {
if (this.timer) this.timer.unsubscribe()

this.timer = Observable.timer(5000)
.do(() => {
this.updateStatus('away')
})
.subscribe()
}

/// Make sure to close these subscriptions when no longer needed.
signOut() {
this.updateStatus('offline')
this.mouseEvents.unsubscribe()
this.timer.unsubscribe()

this.afAuth.auth.signOut();
}

Displaying the User Status with NgSwitch

This is a perfect opportunity to use the NgSwitch directive in Angular. It works just like a switch statement in JavaScript or any other programming language for that matter.

user-list.html

First, you’re probably going to be looping over a list of users in a parent component. You should have something that looks like this.

<div *ngFor="let user of users | async">
{{ user.name }}

<user-status [status]="user.status"></user-status>
</div>

user-status.component.html

With NgSwitch we show a different message, CSS style, and icon based on the status. If none of the statuses match the switch cases, then it will just show the default unknown template at the bottom.

<span [ngSwitch]="status">


<span *ngSwitchCase="'online'" class="help is-success">
Online <i class="fa fa-circle"></i>
</span>


<span *ngSwitchCase="'offline'" class="help is-danger">
Offline <i class="fa fa-circle"></i>
</span>

<span *ngSwitchCase="'away'" class="help is-warning">
Away <i class="fa fa-circle"></i>
</span>


<span *ngSwitchDefault class="help">
Unknown
</span>

</span>

user-status.component.html

In the TypeScript, we can use the @Input decorator to pass the status variable from the parent to the child. Learn more about sharing data between Angular components.

import { Component, Input } from '@angular/core';

@Component({
selector: 'user-status',
templateUrl: './user-status.component.html',
styleUrls: ['./user-status.component.scss']
})
export class UserStatusComponent {

@Input() status: string;

}

The End

That’s it for realtime user presence with Firebase and Angular - pretty easy! Let me know what you think in the comments are via Slack.