Skip to content

Commit f65d37f

Browse files
content
1 parent a42aae9 commit f65d37f

File tree

9 files changed

+290
-0
lines changed

9 files changed

+290
-0
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<div class="chapter">
2+
<markdown [src]="chapterPath$ | async"></markdown>
3+
<a [routerLink]="nextChapterUrl$ | async">Next chapter</a>.
4+
</div>
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
.chapter {
2+
width: 80%;
3+
margin: auto;
4+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/* tslint:disable:no-unused-variable */
2+
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
3+
import { By } from '@angular/platform-browser';
4+
import { DebugElement } from '@angular/core';
5+
6+
import { ChaptersComponent } from './chapters.component';
7+
8+
describe('ChaptersComponent', () => {
9+
let component: ChaptersComponent;
10+
let fixture: ComponentFixture<ChaptersComponent>;
11+
12+
beforeEach(async(() => {
13+
TestBed.configureTestingModule({
14+
declarations: [ ChaptersComponent ]
15+
})
16+
.compileComponents();
17+
}));
18+
19+
beforeEach(() => {
20+
fixture = TestBed.createComponent(ChaptersComponent);
21+
component = fixture.componentInstance;
22+
fixture.detectChanges();
23+
});
24+
25+
it('should create', () => {
26+
expect(component).toBeTruthy();
27+
});
28+
});
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { Component, OnInit } from '@angular/core';
2+
import { ActivatedRoute } from '@angular/router';
3+
import { map } from 'rxjs/operators';
4+
5+
@Component({
6+
selector: 'app-chapters',
7+
templateUrl: './chapters.component.html',
8+
styleUrls: ['./chapters.component.scss']
9+
})
10+
export class ChaptersComponent implements OnInit {
11+
12+
chapterPath$ = this.route.paramMap.pipe(
13+
map(params => params.get('chapter')),
14+
map(chapter => `assets/content/chapter-${chapter}.md`),
15+
);
16+
17+
nextChapterUrl$ = this.route.paramMap.pipe(
18+
map(params => +params.get('chapter')),
19+
map(chapter => [`/chapters/${chapter + 1}`]),
20+
);
21+
22+
constructor(
23+
private readonly route: ActivatedRoute,
24+
) { }
25+
26+
ngOnInit() {
27+
}
28+
29+
30+
31+
}

src/assets/content/chapter-1.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Chapter 1: Introduction to NgRx
2+
3+
## Preface
4+
5+
*This tutorial was created and is maintained by Armen Vardanyan, an Angular/NgRx enthusiast just like you. The project is open sourced, read about how you can also contribute to is here*
6+
7+
I want to help people master skills necessary for WebDev as quick as possible, so I won't be using big words and long introductions here. Just a few sentences about this tutorial
8+
9+
### What will you learn?
10+
11+
After you complete this tutorial, you will
12+
13+
1. Know the basic and advanced concepts of NgRx
14+
2. Be able to create applications using NgRx
15+
3. Understand existing NgRx codebases
16+
4. Convert existing Angular apps that don't use NgRx to start using NgRx
17+
5. Understand RxJS better
18+
19+
### What will we do?
20+
21+
I hold a firm belief that in order to learn and understand something thoroughly, one has to put that knowledge to practice as soon as possible. So in this tutorial, we won't be just learning NgRx concepts and exploring small examples; we will be building a (not so large) Angular app on NgRx, that has practical value and real-world challenges. What kind of project is that? Well, to better understand the concepts of state management, we will build a financial logging application, a tool that allows the user to add income and expense logs, monitor their spending, categorize it, and even view charts. We will explore **all** NgRx concepts during the development of that app. The app already exists, written by me, and can be found at this GitHub Repository (visit it and follow instructions to install and run it on your machine). You can of course view the existing app, but it would be far more beneficial to build it together, step-by-step, grasping all the concepts and using the best known practices.
22+
23+
### How is this tutorial structured?
24+
25+
The tutorial is divided into simple chapters (you are, as you've noticed, reading Chapter 1 right now). After you finish a chapter, you can move on to the next one.
26+
Each chapter explores one single, isolated topic, with small examples, and then puts it into use in the application that we are building.
27+
28+
### What's next?
29+
30+
Now, that we know what and why we are doing, let's move on to Chapter 2, where we will actually start doing stuff.

src/assets/content/chapter-2.md

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# Chapter 2 - Getting started: Installing dependencies and creating the project
2+
3+
## Installations
4+
5+
### NodeJS/Angular
6+
7+
If you already have NodeJS and Angular CLI on your machine, skip this step. Otherwise, do the following:
8+
9+
1. Download NodeJS
10+
2. Install it on your machine. This may take several minutes
11+
3. Install Angular CLI: open your command line tool/terminal and run the following command: `npm install @angular/cli -g`
12+
4. We are done. You can read more about Angular CLI in the official docs
13+
14+
### Creating the project
15+
16+
As I mentioned in the previous chapter, we are going to create an Angular finance logging application with NgRx. Let's create this project and get started:
17+
18+
1. Open a preferred directory of yours
19+
2. Run the following command `ng new finance-logger`
20+
3. You can select a number of options, add routing, SCSS, and so on. Those options are irrelevant tou this tutorial
21+
4. Wait a bit
22+
5. Application has been created
23+
24+
Now let's add NgRx to the party:
25+
26+
1. Run the command `cd financial-logger` to move to the root directory of our project
27+
2. Run the command `npm install @ngrx/store` to get NgRx
28+
3. NgRx consists of several packages, but the most basic functionality is inside `@ngrx/store`.
29+
4. We will add more packages as we dive into more complex concepts
30+
31+
That's it. Now we have an empty project with NgRx installed.
32+
33+
## Up and running
34+
35+
In the `app.module.ts` file, add the following import:
36+
37+
```ts
38+
import { StoreModule } from '@ngrx/store';
39+
```
40+
41+
And in the `imports` array of the `AppModule` declarations, add this line:
42+
43+
```ts
44+
@NgModule({
45+
// other metadata
46+
imports: [
47+
// other imports
48+
StoreModule.forRoot(),
49+
],
50+
})
51+
export class AppModule {}
52+
```
53+
54+
1. Run `ng serve`
55+
2. Open `http://localhost:4200/`
56+
3. See our fresh Angular app
57+
58+
That's it, we did the basic installations, let's now move on to chapter 3, where we will explore the the
59+

src/assets/content/chapter-3.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Chapter 3 - NgRx: the What
2+
3+
## What is NgRx?
4+
5+
### Flux design pattern
6+
7+
During the last decade, Facebook has popularized an approach in building single page Web Applications called the [Flux](https://facebook.github.io/flux/docs/in-depth-overview/). [Redux](https://redux.js.org/), a vastly popular state management library for React, is built using the Flux approach, and so is NgRx. They have similar (almost the same) concepts, and serve the same need. NgRx can be called "Redux + RxJS". It combines the simplicity and centralization of Redux with the power of `Observables`.
8+
9+
#### So what is Flux?
10+
11+
In Flux, the state of the application is considered as a one unified entity, that can only be modified using a single approach, in a predesigned manner, and only in specific ways. And when we say "application state", we mean all the data that an application holds and uses to display the UI to the end user, and that can be shared between different components and layers of our application. For example, in our project, the state contains an array of categories of income, an array of expenses, an array of all expenses, and so on. This state can change through the lifecycle of the app: a user can add a new category, log some expense, or delete a log about income that was submitted erroneously. All those changes can be triggered from different parts of the application, but might be reflected in a vastly different part of the UI. For example, if a user adds a category from a special menu, it should appear in the screen where the user logs income, in a dropdown from where they can select the specific category of said income. So how do we describe such an interconnected system in a programming language without Flux? Well, there are several approaches, all of which have a varying degree of painfulness.
12+
13+
1. Use a global object. Of course, we can create a global `State` object, which will hold the entire application data, share the reference between the components, and use it in UI rendering. But this approach brings forward several problems:
14+
- State can be modified from unexpected places in unexpected ways. Someone might carelessly overwrite an existing value, thus creating a bug, and then the next developer will have a ridiculously hard time trying to find out exactly from where the problem arises.
15+
- Asynchronous programming poses yet another big threat to this idea, with async callbacks accessing the same reference of the object and modifying it, possible overwriting data that should have been used by another callback, introducing hard-to-fix issues like race conditions and such.
16+
2. Using an event delivery system (like an event bus) that will notify component about specific events to which those components need to react accordingly. This would be a better approach, but still introduces problems:
17+
- We need to subscribe to events in every component, possibly more than a dozen of those. This can make the component code unreadable.
18+
- When an event is sent, it is hard to really determine *how exactly* the state of the application is being modified. We would need to go through all subscriptions of that event to get a grasp of what it really does (and it still might not be enough)
19+
- Such a system might tempt developers to write lots of side effects (we will talk about side effects and how we deal with them in NgRx in later chapters)
20+
- If two components access the same state, we will have a problem synchronizing them together, paving way for new hard-to-fix bugs
21+
22+
#### So what does Flux do?
23+
24+
Flux solves this problem with a series of easy to grasp concepts, which bundled together comprise a state management system. It sort of utilizes both previous approaches in a way that
25+
26+
- Makes state easy to synchronize
27+
- Makes changes to the state easy to track
28+
- Allows to a actually debug state changes
29+
- Is declarative
30+
31+
NgRx does the same, but for Angular. If at this point you think "why do I event bother? My app works fine right now", then let's now explore why exactly do we need solutions like NgRx.
32+
33+
34+

src/assets/content/chapter-4.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# NgRx: the Why
2+
3+
## Why do we need NgRx?
4+
5+
### Reasonable state
6+
7+
As mentioned in the previous chapter, we need a state management solution that is predictable, scalable and easy to reason about. Those might sound like big words, but in reality it simply means the following:
8+
9+
> I want to be able to fix bugs without having a migraine
10+
11+
### How do bugs happen?
12+
13+
In most cases, we have bugs not because the code is wrong (in that case we essentially didn't tell the computer what *exactly* we wanted), but because the data is not in the expected state. The variable `age` all of a sudden holds a negative value, because someone misclicked when the IDE offered an autocomplete or a correction. Or maybe the Array of users is empty, because we called the wrong API, or stored the data in the wrong variable, or forgot to subscribe. Problems like this happen *all the time*. Remember the last vry annoying bug you encountered. Was it because of a very minor mistake like a typo? If yes, answer the following: how frustrating was the whole experience?
14+
15+
## What does NgRx offer?
16+
17+
NgRx, as mentioned, is built on concepts of Redux. It utilizes core ideas like `Stores` (the place where the application states is stored), `Actions` (events that notify the `Store` that some change to the state is to happen), `Reducers` (functions that determine how a certain `Action` affects the `State`), and also adds concepts like `Selectors` (functions that allow to pick a slice of the `State` as an `Observable`) and `Effects` (special functions that work on side effects like data loading and API calls). It also provides utility functions to make writing boilerplate code easier and even utility libraries like `@ngrx/entity` to work with large lists of data. We will learn in depth about all of those concepts and tools in corresponding chapters.
18+
19+
Together, this tools allow us to build a truly scalable state management solution.
20+
21+
## Isn't this just overengineering?
22+
23+
At this point you might think "again, I don't need all of these ideas and abstract concepts. In practice, my app works fine!". But think about how scalable your architecture is. Will it be easy to incorporate more and more interconnected relations between layers and components of your app? Will it be easy to add new modules that depend on existing data as well as introduce new states? If you overthink now, know that NgRx got you covered.
24+
25+
## What NgRx is **not**
26+
27+
Some developers assume that "better architecture" means "less code". This is far from true. Actually, sometimes it is better to have a few more lines of code that are explicit and simple rather than a very short, but mystical algorithm. NgRx certainly **does not** reduce the lines of code (LOC). Sure, your components will become simpler and have less LOC (which is part of our goal), but instead we will write lots of `Actions`, `Reducers` and so on, which will definitely compensate for the small gains that we receive in the component class code. So why bother? Because in return we will get simplicity that we so much desire
28+
29+
So, let's finally get down to learning those concepts and putting them into practice.

src/assets/content/chapter-5.md

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# Actions
2+
3+
## What are Actions?
4+
5+
`Actions` are the most simple core concept of NgRx (and Redux in general). Action is a unique event that is used to trigger a change in the state. What does it mean? For example, we might have an action that says "Home page has been loaded". It might mean some changes in the state. For example, in our application, it might trigger an API call for lists of expenses and incomes, which will in turn trigger an event that outs that data in the `Store`, resulting ina change in the UI. Or we might have an action that says "Add a category", which will create a new category of income/expense in the `Store`, again resulting in a UI change. Again, essentially `Actions` are like commands to the `Store`, or methods that allow to update its contents.
6+
7+
## What does an Action look like?
8+
9+
Actions are simple. An action is usually an object with just two properties: a `type`, a string that indicates what *exactly* that action represents, and an optional `payload` that is kind of like a function argument. In our example, an action that adds a category might look like this:
10+
11+
```ts
12+
const addCategoryAction = {
13+
type: '[Categories List] Add Category',
14+
payload: {name: 'Food'},
15+
};
16+
```
17+
18+
This is an action that adds a category named `Food` to the list of all categories. Of course, we still haven't written the logic that actually uses this action to put the data in the store, but for now we are focusing on the `Actions`. In NgRx, there is a simpler, built-in way of creating `Actions`, namely the `createAction` function. To start learning about it, let's create a folder names `state` in our `src/app` directory, and a file called `actions.ts` inside it. Now, let's put this code inside that file:
19+
20+
```ts
21+
// src/app/state/actions.ts
22+
import { createAction, props } from '@ngrx/store';
23+
24+
export const addCategory = createAction(
25+
'[Category List] Add Category',
26+
props<{name: string}>(),
27+
);
28+
```
29+
30+
Let's break down this example. First of all, the name `createAction` is a bit deceptive; it does not create an action; in fact, it creates a function which, when called, wil produce an action object. The first argument is the `type` of the action that will be produced. When called, the `addCategory` function will **always** create an action with type "[Category List] Create Category". The second argument uses the bizarre function `props`, which is a generic function that allows us to define the type of the `payload` which the created action will have. Essentially, it explains that in order to create the action using the `addCategory` function, we should call it and provide an object that has a property `name` which is a `string`. Let's do this and `console.log` the result.
31+
32+
```ts
33+
// src/app/app.component.ts
34+
import { Component, OnInit } from '@angular/core';
35+
36+
import { addCategory } from './state/actions.ts';
37+
38+
@Component({
39+
// component boilerplate omitted for the sake of brevity
40+
})
41+
export class AppComponent implements OnInit {
42+
ngOnInit() {
43+
console.log(addCategory({name: 'Food'}));
44+
}
45+
}
46+
```
47+
48+
In the console, we will see the following:
49+
50+
```js
51+
{type: '[Category List] Add Category', payload: {name: 'Food'}}
52+
```
53+
54+
So essentially, `createAction` provided us with an easy way of creating actions of the same type. `addCategory` in our case is an `ActionCreator`, a function which produces an action object whenever called, and `props` explained what argument that `ActionCreator` function expects.
55+
56+
### Homework
57+
58+
Yes, you've read it correctly: we have learned how to write some basic code in NgRx, so it is time for some homework!
59+
*For this homework, assume categories cannot have duplicate names. We will deal with this problem later*
60+
61+
1. Create an action for deleting a category. It should receive a string with the name of the category, and in the next chapter we will use that code to write the actual logic for deleting the category.
62+
2. Create an action for updating a category. It must receive a `Category` object (`{name: string}`), and again, we will right the code to update the category in the next chapter
63+
64+
> **Important!** Do not move to the next chapter without adding the homework code! We will be using that code in the next chapters
65+
66+
Example of a solution to the homework:
67+
68+
>! 1. const deleteCategory = createAction('[Category List] Delete Category, props<string>()');
69+
>! 2. const updateCategory = createAction('[Category List] Update Category', props<{name: string}>());
70+
71+
In this chapter, we learned how to create `Actions`, unique events that specify what should happen to the state. In the next one, we will be writing code that actually does the transformation in the state.

0 commit comments

Comments
 (0)