Angular

Learn how to use Angular with deepstream

This page needs to be improved/might be out of date! Raise a PR if you feel like adding a few details or totally revamping it.

The “new Angular” popularly known as “Angular”, though a bad name, is component architected and follows the W3C component standards thereby forcing us ( for a good reason ) to build apps as reusable UI components/widgets. This means you can’t continue to build apps the exact same way you did while using Angular 1.x.

You can get started with Angular 2 using the Quickstart guide which is enough knowledge to start using deepstream in Angular. Using deepstream in Angular is what this guide is about.

1. Setup Angular

Angular provides a CLI tool which takes the pain of booting and scaffolding an Angular project by reducing the process to just one command:

# Creates a new Angular project
ng new deepchat

Of course, you would get an error because ng is not installed to your PATH but you can do so with npm:

# Installs the Angular CLI tool
npm install -g angular-cli

You can run the new command again to start a new project. Once that is done, the new project can actually be launched and you can do that using:

# First, move a step into
# the project directory
# if you are not there yet
cd deepchat
# Launch app
ng serve

first run

2. Install deepstream

deepstream needs to be installed on both the client and server. Angular is a client tool so we are focusing on the client installation but you can follow the steps here to install deepstream on the server.

Installing deepstream in Angular projects is quite simple. Angular bundles up scripts using Webpack and splits these bundles in a manner that makes it easier for browsers load them faster. To help Angular with this process, rather than just installing the scripts anywhere, we install with npm and load the script as a vendor file.

First, install via npm:

npm install @deepstream/client

To tell Angular that the install dependency is a vendor file and should be loaded accordingly, add deepstream script to the scripts array in ./angular-cli.json:

. . .
"scripts": [
   "../node_modules/@deepstream/client/dist/deepstream.js"
 ],
. . .

You can see that installation was successful by running the following command in your AppComponent’s constructor:

export class AppComponent {
    constructor() {
      // Logs the deepstream function
      console.log(deepstream);
    }
}

Depending on your Typescript configuration, calling deepstream from nowhere might throw an error.

3. Create deepstream Service (DsService)

deepstream will work perfectly fine when used directly in the component but as your project grows large, you might find yourself in the deep mess of violating DRY. A common pattern in Angular (both 1.x and the newer versions) is to create a service that abstracts a group of tasks so this services can be used and re-used by multiple components if need be.

In our case, we need a service to group all our deepstream task and expose methods to our components to interact with:

// ./src/app/services/ds.service.ts
import { Injectable } from '@angular/core';

@Injectable()
export class DsService {

    private ds;
    public dsInstance;

    constructor() {
       this.ds = this.dsInstance = new DeepstreamClient('localhost:6020');
    }
}

The dsInstance is public (though we know the deal with privacy in JavaScript) so you can access all deepstream’s method from it. If that you think this is all you need from such service, fine, but you can also wrap the common methods by creating more members on the service:

// ./src/app/services/ds.service.ts
import { Injectable } from '@angular/core';

@Injectable()
export class DsService {

    private ds;
    public dsInstance;

    constructor() {
      this.ds = this.dsInstance = new DeepstreamClient('localhost:6020');
    }

    login (credentials?, loginHandler?) {
      // {username: 'chris', password:'password'}
      this.ds.login(credentials, loginHandler);
    }

    getRecord(name) {
      return this.ds.record.getRecord(name);
    }

    getList(name){
      return this.ds.record.getList(name);
    }
}

Those are the three methods we need to log in, create or get a record and create or get a list.

4. Login to deepstream

Authenticating to deepstream server does not require credentials and for simple stuffs like a demo, you can go ahead and perform and anonymous authentication:

// ./src/app/app.component.ts
import { Component, OnInit } from '@angular/core';
import { DsService } from './services/ds.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  // ...
})
export class AppComponent implements OnInit{
  title = 'deepChat';
  username;

  constructor(
    private ds: DsService
  ) {}

  ngOnInit() {
    // Get username from
    // window prompt and use 'anonymous' for
    // null or invalid response
    const defaultUsername = 'anonymous';
    const username = window.prompt('Please enter your username', defaultUsername);
    if (username != null) {
      this.username = username;
    } else {
      this.username = defaultUsername;
    }
    // Login without credentials
    this.ds.login(null, this.loginHandler);
  }
        
  loginHandler(success, data) {
    console.log('logged in', success, data);
  }

}

The ngOnInit method is a lifecycle method that is called when the component is initialized. This means that when the component is initialized, we try to authenticate deepstream with no credentials. The username is gotten from prompt is just used to identify who is who.

5. Chat Messaging

Let’s create another method that will be called when the user creates a chat message:

// ./src/app/app.component.ts
import { Component, OnInit } from '@angular/core';
import { DsService } from './services/ds.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit{
  title = 'deepChat';
  username;
  text;
  chats;

  constructor(
    private ds: DsService
  ) {}

  // . . .

  addChat() {

    const recordName = 'chat/' + this.ds.dsInstance.getUid();

    const chatRecord = this.ds.getRecord(recordName);
    chatRecord.set({username: this.username, text: this.text});
    this.text = '';
    // Update the chats list
    this.chats.addEntry(recordName);
  }
}

The addChat method creates a record, sets a value to the record, clears the text property and updates the deepstream chat list with the record name.

The markup for creating messages is quite simple:

// ./src/app/app.component.html
<div class="compose">
  <input type="text" [(ngModel)]="text">
  <button (click)="addChat()">Send</button>
</div>

We perform a two-way binding to the input with the text property as well as add a click event listener to the send button which calls addChat method.

6. Chat Listing

The chats property, for now, is undefined and is supposed to be a deepstream list which will hold the collection of deepstream records.

We can create this list when the component is ready, and subscribe to it as well so as to print the chats as they come in:

// ./src/app/app.component.ts
// ...

@Component(...)
export class AppComponent implements OnInit{
  title = 'deepChat';
  username;
  text;
  chats;
  chatArray = [];

  constructor(
    private ds: DsService
  ) {}

  ngOnInit() {
    // . . . 

    this.chats = this.ds.getList('chats');

    this.chats.on('entry-added', recordName => {
            
      this.ds.getRecord( recordName ).whenReady( record => {

        record.subscribe( (data) => {
          if(data.username && data.text) {
            // Update bindable property  
            this.chatArray.push(data);
          }
        }, true );

      });
    })
  }

  // . . .
}

What is going is, we created a list that records can be added to as seen in the addChat method and then listen to this list for data entry. Once the list is updated, we update the chatArray which is the property that we need to iterate on the UI to display the chats as shown below:

<!-- ./src/app/app.component.html -->
<div class="chats">
      <p *ngFor="let chat of chatArray"><strong>{{chat.username}}: </strong> {{chat.text}}</p>
    </div>