NgRx is an implementation of the popular pattern, Redux, that's meant for use with Angular. It is entirely possible to create an Angular application without NgRx. You might even be very successful in doing so. There are situations, though, where Redux can really help; you can get that help by using NgRx.
So, what is Redux and when do we need it? Redux is about adding predictability to your app. Predictability is about knowing who did what to the state in your application.
Single source of truth is a concept that Redux lives up to as it promotes adding all your data to one store. At any given moment, you will be able to tell what state your application is in. This is great if you want to save the state and come back to it (also called rehydration) like you would in a game where you create a save point and later resume your game from that save point.
It's not only about having a single source of truth; it's also about knowing who is allowed to change the content, or the state, of the store. A challenge you often face is that as an application grows, you need to add a lot of views and controllers, you gradually lose the overview of what code affects what state in the application. Redux helps you with this by ensuring that a view can't change the state directly but will have to dispatch actions that represent the intent of how you want the state to change.
Another thing that might happen is that a lot of user interaction kicks off a lot of changes to the state. Some of those actions should lead to immediate changes and some lead to asynchronous actions that will eventually change the state of the application. The important thing at this point is that we ensure that all those changes happen in the right order. Redux helps us with that by queuing up all the actions and ensures that our app changes its state in a predictable way.
A very important aspect of Redux is that when it changes the state, it does not mutate it. It replaces the state with a copy of the old state, but with the latest action applied to it. If we take our game analogy again, imagine that you have a game and you want to add a potion to your backpack. When you do that in Redux, we replace the main character; we replace it with a main character that has a potion in its backpack. The fact that we do things this way makes it easy for us to remember each previous state and return to an earlier state if we need to, which is known as time-travel-debugging. To enable us to replace the previous state with a new state, we are using something called pure functions. Pure functions ensure that we only create a copy of the data instead of mutating it.
There are a lot of benefits to knowing what state your app contains at a given point. However, not all the state in your app will need Redux. It comes down to preference. Some prefer to put all the state in the store, some prefer to put some state in the store, and some other state is fine if it only lives as local state in a specific component. Think of it this way, if you were to restore your app, what state would be okay to lose; the answer may be a drop-down selection made or something else, if anything. Having everything in the store will ensure that you don't, by mistake, make extra Ajax calls if the store already holds the data, so it is a way of helping you with caching as well.