Angular 7: Data Architecture with Observables - Part 1: Services

Observables and RxJS

In Angular, we can structure our application to use Observables as the backbone of our data architecture. Using Observables to structure our data is called Reactive Programming.

But what are Observables, and Reactive Programming anyway? Reactive Programming is a way to work with asynchronous streams of data. Observables are the main data structure we use to implement Reactive Programming. But I’ll admit, those terms may not be that clarifying. So we’ll look at concrete examples through the rest of this chapter that should be more enlightening.

Note: Some RxJS Knowledge Required

I want to point out this book is not primarily about Reactive Programming. There are several other good resources that can teach you the basics of Reactive Programming and you should read them. We’ve listed a few below.

Consider this chapter a tutorial on how to work with RxJS and Angular rather than an exhaustive introduction to RxJS and Reactive Programming.

In this chapter, I’ll explain in detail the RxJS concepts and APIs that we encounter. But know that you may need to supplement the content here with other resources if RxJS is still new to you.

Use of Underscore.js in this chapter

Underscore.js is a popular library that provides functional operators on JavaScript data structures such as Array and Object. We use it a bunch in this chapter alongside RxJS. If you see the _ in code, such as _.map or _.sortBy know that we’re using the Underscore.js library. You can find the docs for Underscore.js here.

Learning Reactive Programming and RxJS

If you’re just learning RxJS I recommend that you read this article first:

After you’ve become a bit more familiar with the concepts behind RxJS, here are a few more links that can help you along the way:

Throughout this chapter I’ll provide links to the API documentation of RxJS. The RxJS docs have tons of great example code that shed light on how the different streams and operators work.

Do I have to use RxJS to use Angular? - No, you definitely don’t. Observables are just one pattern out of many that you can use with Angular. We talk more about other data patterns you can use here.

I want to give you fair warning: learning RxJS can be a bit mind-bending at first. But trust me, you’ll get the hang of it and it’s worth it. Here’s a few big ideas about streams that you might find helpful:

  1. Promises emit a single value whereas streams emit many values. - Streams fulfill the same role in your application as promises. If you’ve made the jump from callbacks to promises, you know that promises are a big improvement in readability and data maintenance vs. callbacks. In the same way, streams improve upon the promise pattern in that we can continuously respond to data changes on a stream (vs. a one-time resolve from a promise)
  2. Imperative code “pulls” data whereas reactive streams “push” data - In Reactive Programming our code subscribes to be notified of changes and the streams “push” data to these subscribers
  3. RxJS is functional - If you’re a fan of functional operators like map, reduce, and filter then you’ll feel right at home with RxJS because streams are, in some sense, lists and so the powerful functional operators all apply
  4. Streams are composable - Think of streams like a pipeline of operations over your data. You can subscribe to any part of your stream and even combine them to create new streams

Chat App Overview

In this chapter, we’re going to use RxJS to build a chat app. Here’s a screenshot:

logo

Usually we try to show every line of code here in the book text. However, this chat application has a lot of moving parts, so in this chapter we’re not going to have every single line of code in the text. You can find the sample code for this chapter in the folder code/rxjs/rxjs-chat. We’ll call out each filter where you can view the context, where appropriate.

In this application we’ve provided a few bots you can chat with. Open up the code and try it out:

{lang=shell,line-numbers=off}
cd code/rxjs/rxjs-chat npm install npm start

Now open your browser to http://localhost:4200.

Notice a few things about this application:

Let’s look at an overview of how this app is constructed. We have

Let’s look at them one at a time.

Components

The page is broken down into three top-level components:

logo

Models

This application also has three models:

logo

Services

In this app, each of our models has a corresponding service. The services are singleton objects that play two roles:

  1. Provide streams of data that our application can subscribe to
  2. Provide operations to add or modify data

For instance, the UsersService:

Summary

At a high level, the application data architecture is straightforward:

For instance, the ChatThreads component listens for the most recent list of threads from the ThreadService and the ChatWindow subscribes for the most recent list of messages.

In the rest of this chapter, we’re going to go in-depth on how we implement this using Angular and RxJS. We’ll start by implementing our models, then look at how we create Services to manage our streams, and then finally implement the Components.

Implementing the Models

Let’s start with the easy stuff and take a look at the models.

User

Our User class is straightforward. We have an id, name, and avatarSrc.

    import { uuid } from '../util/uuid';
    
    /**
     * A User represents an agent that sends messages
     */
    export class User {
      id: string;
    
      constructor(public name: string,
                  public avatarSrc: string) {
        this.id = uuid();
      }
    }

Notice above that we’re using a TypeScript shorthand in the constructor. When we say public name: string we’re telling TypeScript that 1. we want name to be a public property on this class and 2. assign the argument value to that property when a new instance is created.

Thread

Similarly, Thread is also a straightforward TypeScript class:

    import { Message } from '../message/message.model';
    import { uuid } from '../util/uuid';
    
    /**
     * Thread represents a group of Users exchanging Messages
     */
     export class Thread {
       id: string;
       lastMessage: Message;
       name: string;
       avatarSrc: string;
    
       constructor(id?: string,
                   name?: string,
                   avatarSrc?: string) {
         this.id = id || uuid();
         this.name = name;
         this.avatarSrc = avatarSrc;
       }
     }

Note that we store a reference to the lastMessage in our Thread. This lets us show a preview of the most recent message in the threads list.

Message

Message is also a simple TypeScript class, however in this case we use a slightly different form of constructor:

    import { User } from '../user/user.model';
    import { Thread } from '../thread/thread.model';
    import { uuid } from './../util/uuid';
    
    /**
     * Message represents one message being sent in a Thread
     */
     export class Message {
       id: string;
       sentAt: Date;
       isRead: boolean;
       author: User;
       text: string;
       thread: Thread;
    
       constructor(obj?: any) {
         this.id              = obj && obj.id              || uuid();
         this.isRead          = obj && obj.isRead          || false;
         this.sentAt          = obj && obj.sentAt          || new Date();
         this.author          = obj && obj.author          || null;
         this.text            = obj && obj.text            || null;
         this.thread          = obj && obj.thread          || null;
       }
     }

The pattern you see here in the constructor allows us to simulate using keyword arguments in the constructor. Using this pattern, we can create a new Message using whatever data we have available and we don’t have to worry about the order of the arguments. For instance we could do this:

{lang=typescript,line-numbers=off}
let msg1 = new Message();

  # or this
  
let msg2 = new Message({
  text: "Hello Nate Murray!"
})

Now that we’ve looked at our models, let’s take a look at our first service: the UsersService.

Implementing UsersService

The point of the UsersService is to provide a place where our application can learn about the current user and also notify the rest of the application if the current user changes.

 
This page is a preview of ng-book 2.
Get the rest of this chapter plus hundreds of pages Angular 7 instruction, 5 sample projects, a screencast, and more.

 

Ready to master Angular 7?

  • What if you could master the entire framework – with solid foundations – in less time without beating your head against a wall? Imagine how quickly you could work if you knew the best practices and the best tools?
  • Stop wasting your time searching and have everything you need to be productive in one, well-organized place, with complete examples to get your project up without needing to resort to endless hours of research.
  • You will learn what you need to know to work professionally with ng-book: The Complete Book on Angular 7 or get your money back.
Download the First Chapter (for free)