In the last chapter we learned about Redux, the popular and elegant data architecture. In that chapter, we built an extremely basic app that tied our Angular components and the Redux store together.
In this chapter we’re going to take on those ideas and build on them to create a more sophisticated chat app.
Here’s a screenshot of the app we’re going to build:

Context For This Chapter
Earlier in this book we built a chat app using RxJS. We’re going to be building that same app again only this time with Redux. The point is for you to be able to compare and contrast how the same app works with different data architecture strategies.
You are not required to have read the RxJS chapter in order to work through this one. This chapter stands on its own with regard to the RxJS chapters. If you have read that chapter, you’ll be able to skim through some of the sections here where the code is largely the same (for instance, the data models themselves don’t change much).
We do expect that you’ve read through the previous Redux chapter or at least have some familiarity with Redux.
Chat App Overview
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/redux/redux-chat
npm install
npm start
Now open your browser to http://localhost:4200
.
Notice a few things about this application:
- You can click on the threads to chat with another person
- The bots will send you messages back, depending on their personality
- The unread message count in the top corner stays in sync with the number of unread messages
Let’s look at an overview of how this app is constructed. We have
- 3 top-level Angular Components
- 3 models
- and 2 reducers, with their respective action creators
Let’s look at them one at a time.
Components
The page is broken down into three top-level components:

ChatNavBarComponent
- contains the unread messages count
ChatThreadsComponent
- shows a clickable list of threads, along with the most recent message and the conversation avatar
ChatWindowComponent
- shows the messages in the current thread with an input box to send new messages
Models
This application also has three models:

User
- stores information about a chat participant
Message
- stores an individual message
Thread
- stores a collection of Messages
as well as some data about the conversation
Reducers
In this app, we have two reducers:
UsersReducer
- handles information about the current user
ThreadsReducer
- handles threads and their messages
Summary
At a high level our data architecture looks like this:
- All information about the users and threads (which hold messages) are contained in our central store
- Components subscribe to changes in that store and display the appropriate data (unread count, list of threads, the messages themselves
- When the user sends a message, our components dispatch an action to the store
In the rest of this chapter, we’re going to go in-depth on how we implement this using Angular and Redux. We’ll start by implementing our models, then look at how we create our app state and reducers, and then finally we’ll implement the Components.
Implementing the Models
Let’s start with the easy stuff and take a look at the models.
We’re going to be specifying each of our model definitions as interface
s. This isn’t a requirement and you’re free to use more elaborate objects if you wish. That said, objects with methods that mutate their internal state can break the functional model that we’re striving for.
That is, all mutations to our app state should only be made by the reducers - the objects in the state should be immutable themselves.
So by defining an interface
for our models,
- we’re able to ensure that the objects we’re working with conform to an expected format at compile time and
- we don’t run the risk of someone accidentally adding a method to the model object that would work in an unexpected way.
User
Our User
interface has an id
, name
, and avatarSrc
.
/**
* A User represents an agent that sends messages
*/
export interface User {
id: string;
name: string;
avatarSrc: string;
isClient?: boolean;
}
We also have a boolean isClient
(the question mark indicates that this field is optional). We will set this value to true
for the User
that represents the client, the person using the app.
Thread
Similarly, Thread
is also a TypeScript interface:
import { Message } from '../message/message.model';
/**
* Thread represents a group of Users exchanging Messages
*/
export interface Thread {
id: string;
name: string;
avatarSrc: string;
messages: Message[];
}
We store the id
of the Thread
, the name
, and the current avatarSrc
. We also expect an array of Message
s in the messages
field.
Message
Message
is our third and final model interface
:
import { User } from '../user/user.model';
import { Thread } from '../thread/thread.model';
/**
* Message represents one message being sent in a Thread
*/
export interface Message {
id?: string;
sentAt?: Date;
isRead?: boolean;
thread?: Thread;
author: User;
text: string;
}
Each message has:
id
- the id of the message
sentAt
- when the message was sent
isRead
- a boolean indicating that the message was read
author
- the User
who wrote this message
text
- the text of the message
thread
- a reference to the containing Thread
App State
Now that we have our models, let’s talk about the shape of our central state. In the previous chapter, our central state was a single object with the key counter
which had the value of a number
. This app, however, is more complicated.
Here’s the first part of our app state:
export interface AppState {
users: UsersState;
threads: ThreadsState;
}
Our AppState
is also an interface
and it has two top level keys: users
and threads
- these are defined by two more interfaces UsersState
and ThreadsState
, which are defined in their respective reducers.
A Word on Code Layout
This is a common pattern we use in Redux apps: the top level state has a top-level key for each reducer. In our app we’re going to keep this top-level reducer in app.reducer.ts
.
Each reducer will have it’s own file. In that file we’ll store:
- The
interface
that describes that branch of the state tree
- The value of the initial state, for that branch of the state tree
- The reducer itself
- Any selectors that query that branch of the state tree - we haven’t talked about selectors yet, but we will soon.
The reason we keep all of these different things together is because they all deal with the structure of this branch of the state tree. By putting these things in the same file it’s very easy to refactor everything at the same time.
You’re free to have multiple layers of nesting, if you so desire. It’s a nice way to break up large modules in your app.
The Root Reducer
Since we’re talking about how to split up reducers, let’s look at our root reducer now:
export interface AppState {
users: UsersState;
threads: ThreadsState;
}
const rootReducer: Reducer<AppState> = combineReducers<AppState>({
users: UsersReducer,
threads: ThreadsReducer
});
export default rootReducer;
Notice the symmetry here - our UsersReducer
will operate on the users
key, which is of type UsersState
and our ThreadsReducer
will operate on the threads
key, which is of type ThreadsState
.
This is made possible by the combineReducers
function which takes a map of keys and reducers and returns a new reducer that operates appropriately on those keys.
Of course we haven’t finished looking at the structure of our AppState
yet, so let’s do that now.
Our UsersState
holds a reference to the currentUser
.
export interface UsersState {
currentUser: User;
};
const initialState: UsersState = {
currentUser: null
};
You could imagine that this branch of the state tree could hold information about all of the users, when they were last seen, their idle time, etc. But for now this will suffice.
We’ll use initialState
in our reducer when we define it below, but for now we’re just going to set the current user to null
.
The ThreadsState
Let’s look at the ThreadsState
:
export interface ThreadsEntities {
[id: string]: Thread;
}
export interface ThreadsState {
ids: string[];
entities: ThreadsEntities;
currentThreadId?: string;
};
const initialState: ThreadsState = {
ids: [],
currentThreadId: null,
entities: {}
};
We start by defining an interface called ThreadsEntities
which is a map of thread id
s to Thread
s. The idea is that we’ll be able to look up any thread by id in this map.
In the ThreadsState
we’re also storing an array of the ids
. This will store the list of possible ids that we might find in entities
.
This strategy is used by the commonly-used library normalizr. The idea is that when we standardize how we store entities in our Redux state, we’re able to build helper libraries and it’s clearer to work with. Instead of wondering what the format is for each tree of the state, when we use normalizr
a lot of the choices have been made for us and we’re able to work more quickly.
I’ve opted not to teach normalizr
in this chapter because we’re learning so many other things. That said, I would be very likely to use normalizr
in my production applications.
That said, normalizr
is totally optional - nothing major changes in our app by not using it.
If you’d like to learn how to use normalizr
, checkout the official docs, this blog post, and the thread referenced by Redux creator Dan Abramov here
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)