An Angular 2 chat app using Angular 2, Redux, Webpack, TypeScript, Services, Injectables, Karma, Forms, SCSS, and tslint by the ng-book 2 team
This repo shows an example chat application using Redux and Angular 2. The goal is to show how to use the Redux data architecture pattern within Angular 2, using the core Redux library. It also features:
- A step-by-step tutorial on how to write a minimal Redux clone in Typescript
- How to write a minimal counter app with Redux and Angular 2
- Webpack configuration with TypeScript, Karma, SCSS, and tslint
- How to write injectable services in Angular 2
- How to separate presentational vs. container components
- Using action creators
- How to compose reducers
- And much more
Try the live demo here
# clone the repo
git clone https://github.com/ng-book/angular2-redux-chat.git
# change into the repo directory
cd angular2-redux-chat
# install
npm install
# run
npm run go
Then visit http://localhost:8080 in your browser.
The app has three models:
Message
- holds individual chat messagesThread
- holds metadata for a group ofMessage
sUser
- holds data about an individual user
There are two reducers:
ThreadsReducer
- manages theThread
s and theirMessage
sUsersReducer
- manages the currentUser
There are also three top-level components:
ChatNavBar
- for the top navigation bar and unread messages countChatThreads
- for our clickable list of threadsChatWindow
- where we hold our current conversation
In this project, we're using the official Redux library instead of a wrapper or Redux-inspired spin-off. At the top of our app we create a new Redux Store and provide it to the dependency injection system. This let's us inject it into our components.
Our container components inject the Redux Store and subscribe to any changes. Consider this excerpt from the nav-bar which keeps the count of unread messages:
export default class ChatNavBar {
unreadMessagesCount: number;
constructor(@Inject(AppStore) private store: Store<AppState>) {
store.subscribe(() => this.updateState());
this.updateState();
}
updateState() {
// getUnreadMessagesCount is a selector function
this.unreadMessagesCount = getUnreadMessagesCount(this.store.getState());
}
}
You can see that in the constructor we inject our Store
(which is typed to AppState
). We immediately subscribe to any changes in the store. This callback will not be called unless an action is dispatched to the store, so we need to make sure we load the initial data. To do this, we call this.updateState()
one time after the subscription.
updateState
reads the state from the store (this.store.getState()
) and then calls the selector function getUnreadMessagesCount
(you can find the implementation of that here). getUnreadMessagesCount
calculates the number of unread messages. We then take that value and set it to this.unreadMessagesCount
. Because unreadMessagesCount
is an instance variable which appears in the template, Angular will rerender this component when the value changes.
This pattern is used throughout the app.
Understanding how everything fits together with Redux can be tricky, but this code is heavily commented. One strategy to understand this code is to start at the components and see how they read the Store with selectors, dispatch actions, and follow that through the reducers. The other strategy is to get a copy of ng-book 2 where we explain each line in detail over ~60 pages.
The top-level state has two keys: users
and threads
:
interface AppState {
users: UsersState;
threads: ThreadsState;
}
interface UsersState {
currentUser: User;
};
export interface ThreadsEntities {
[id: string]: Thread;
}
export interface ThreadsState {
ids: string[];
entities: ThreadsEntities;
currentThreadId?: string;
};
ThreadsState stores the list of Threads indexed by id in entities
, as well as a complete list of the ids in ids
.
We also store the id of the current thread so that we know what the user is currently looking at - this is valuable for the unread messages count, for instance.
In this app, we store the Messages in their respective Thread and we don't store the Messages apart from that Thread. In your app you may find it useful to separate Messages into their own Messages reducer and keep only a list of Message ids in your Threads.
Here's a screenshot using Redux Devtools of the initial state:
This app implements a few simple chat bots. For instance:
- Echo bot
- Reversing bot
- Waiting bot
Here's an overview of how the files are laid out in this project:
angular2-redux-chat
├── Makefile * Easy access to common tasks
├── README.md * This file
├── app/ * Where our application code is stored
│ ├── css/ * Contains our CSS and SCSS files
| |
│ ├── images/ * App Images
| |
│ ├── index.html * HTML entry point
| |
│ └── ts/ * All of our TypeScript is here
│ │
│ ├── ChatExampleData.ts * Contains our bots and sample data
| |
│ ├── actions/ * Our Redux actions
│ │ ├── ThreadActions.ts * Actions for Threads
│ │ ├── UserActions.ts * Actions for Users
│ │ └── index.ts
| |
│ ├── app-store.ts * A token for DI of the Store
│ ├── app.ts * App entry point
| |
│ ├── components/ * Pure Components go here
│ │ ├── ChatMessage.ts * Shows a single Message
│ │ └── ChatThread.ts * Shows a single Thread
| |
│ ├── containers/ * Containers that access the Store here
│ │ ├── ChatNavBar.ts * The nav bar
│ │ ├── ChatThreads.ts * The list of threads
│ │ └── ChatWindow.ts * The window where we chat
| |
│ ├── models/ * Models here
│ │ ├── Message.ts * Message model
│ │ ├── Thread.ts * Thread model
│ │ ├── User.ts * User model
│ │ └── index.ts
| |
│ ├── pages/ * Pages of the app here
│ │ └── ChatPage.ts * The main chat page
| |
│ ├── pipes/ * Our pipes here
│ │ └── FromNowPipe.ts * 'n minutes ago' pipe
| |
│ ├── reducers/ * Redux reducers
│ │ ├── ThreadsReducer.ts * Manages thread state
│ │ ├── UsersReducer.ts * Manages users state
│ │ └── index.ts
| |
│ ├── util/ * Utility functions
│ │ └── uuid.ts
│ └── vendor.ts * Dependencies loaded here
|
├── karma.conf.js * Test configuration
|
├── minimal/ * A minimal counter app w/ Redux/ng2
│ ├── CounterComponent.ts
│ ├── app-state.ts
│ ├── app-store.ts
│ ├── app.ts
│ ├── counter-action-creators.ts
│ ├── counter-reducer.ts
│ ├── index.html
│ └── tutorial/ * Tutorial to build a store
│ ├── 01-identity-reducer.ts
│ ├── 02-adjusting-reducer.ts
│ ├── 03-adjusting-reducer-switch.ts
│ ├── 04-plus-action.ts
│ ├── 05-minimal-store.ts
│ ├── 06-rx-store.ts
│ ├── redux2.ts
│ └── tsconfig.json
├── package.json * npm dependencies
├── test/ * tests go here
├── test.bundle.js * webpack test loader
├── tsconfig.json * compiler configuration
├── tslint.json * code standards configuration
├── typings/ * TypeScript typings definitions
├── typings.json * typings configuration
└── webpack.config.js * Webpack configuration
Step 1: Install Node.js from the Node Website.
We recommend Node version 4.1 or above. You can check your node version by running this:
$ node -v
vv4.1...
Step 2: Install Dependencies
npm install
npm run go
Then visit http://localhost:8080 in your browser.
You can run the unit tests with:
npm run test
This repository contains a step-by-step tutorial on how to build a minimal-redux store in Typescript. You can read a blog post explaining this code here. You can also find the code in minimal/tutorial
. The final result looks like this (with or without Observables):
This repository also contains an example of a minimal integration of Redux with Angular 2 to build a counter app. You can also read about how to build this project here at the ng-book blog.
This repo is part of a series of projects that discuss data architecture with Angular 2. You can find this same project implemented with Observable streams instead of Redux here:
There are lots of other little things that need cleaned up such as:
- More tests
- Cleaning up the vendor scripts / typings
If you'd like to contribute, feel free to submit a pull request and we'll likely merge it in.
If you're having trouble getting this project running, feel free to open an issue, join us on Gitter, or email us!
This repo was written and is maintained by the ng-book 2 team. In the book we talk about each line of code in this app and explain why it's there and how it works.
This app is only one of several apps we have in the book. If you're looking to learn Angular 2, there's no faster way than by spending a few hours with ng-book 2.