Subject, BehaviorSubject, and ReplaySubject

We introduced RxJs Subjects a few weeks back.

BehaviorSubject

The most common type of Subject used with Angular is BehaviorSubject. Why is that? Because a BehaviorSubject has two exciting features that a plain Subject does not have:

  • It starts with a default value, which means it is never empty.
  • When we subscribe to a behavior subject, it will immediately give us the last emitted value.

Imagine subscribing to a magazine and receiving its latest published issue immediately. That’s what a BehaviorSubject does. This is helpful if you have components that need to know about the app’s current user. When the component subscribes to the “current user,” we want to get that info immediately and not wait for the next user update.

Also, since behavior subjects always have a value, they have a getValue() method that synchronously returns the current value.

ReplaySubject

A ReplaySubject is very similar to a BehaviorSubject, with two key differences:

  • No default value
  • Can replay more than just the last value

The constructor parameter determines how many values should be replayed to new subscribers:

Subject

A plain Subject has none of the above capabilities. When a value is emitted, current subscribers receive it, but future subscribers won’t. There is no replaying of the latest value(s), which makes plain Subjects less interesting to work with.

ngOnInit vs. constructor in Angular

Today’s post is all about clarifying when to use a constructor and when to use ngOnInit to initialize data in our Angular components.

From a chronological standpoint, the constructor is called first. Then, once all component inputs are initialized, Angular is going to call ngOnInit.

From that definition, it makes sense to use ngOnInit only when your initialization code needs a @Input value to be set. Something along those lines:

And that’s the only rule. Some might be surprised to read this, but you could initialize everything in your constructor if your component has no inputs. There is no performance impact in doing one or the other.

I would argue that triggering an HTTP request in the constructor is better as it’s going to happen sooner (we’re talking milliseconds, though) than if we do it in ngOnInit.

As a result, in the absence of any official recommendation in the Angular style guide, you can choose one or the other.

Resolver function for the Angular Router

The last router function of our guard series is resolve. It is the replacement for class-based resolvers. A resolver function runs after all guards have run and before the destination component is rendered. Such a function is typically used to preload component data before rendering it on the screen.

For instance, in this example:

heroResolver is a function that returns either some data or a Promise or an Observable of that data. Such data will be stored in the hero property of a data object returned by an Observable from the ActivatedRoute service as follows:

Of course, we could resolve multiple different pieces of data using the following syntax:

resolve: {user: userResolver, session: sessionResolver},Code language: JavaScript (javascript)

A resolver function can use the inject function to use services and resolve data as follows:

export const userResolver: ResolveFn<User> = () => { 
    return inject(UserService).getCurrentUser(); 
 };Code language: TypeScript (typescript)

RxJs share and shareReplay operators

Last week, we covered the difference between hot and cold Observables in RxJs.

Today, let’s talk about how we can make a cold Observable hot. Why would we want that? Say our Observable is an HTTP request we want to trigger before any component subscribes to it. We saw that Observables from the HttpClient are cold, meaning that they only start fetching data when a subscriber comes over.

By making the Observable hot, the HTTP request would get triggered immediately. The share operator would do exactly that for us:

Under the hood, share turns our Observable into a Subject, which can handle multiple subscribers and is hot by default.

The only problem with the above example is that the data could be emitted before any subscriber gets the chance to subscribe to that new hot Observable.

As a result, it makes more sense to use shareReplay instead as it’s going to replay the latest value(s) to any new subscriber (just like a ReplaySubject):

This code is much better because:

  • The request is going to fire instantly before any subscription happens
  • Any future subscriber will receive the same data from that original request instead of firing a new request.

You can look at this blog post for an illustration of the above operators with a code example.

canMatch Router Guard

After introducing guards and the most common guard functions (canActivate, canActivateChild, and canDeactivate), we will cover one slightly different guard that can run when trying to access a route.

canMatch was introduced in Angular 15.1. When canMatch returns false, Angular will continue looking at other routes that would match that path instead of preventing any navigation.

Let’s consider the following example:

With the above code, both routes are configured for the /user path, but if the user is an admin user, they’ll land on an AdminDetails component, and if they’re a regular user, they’ll land on a UserDetails component.

This approach can be helpful when working with multiple user roles that display different components.

Route guards – canActivate, canActivateChild, canDeactivate

Yesterday, we introduced how route guards can control whether or not a user can navigate to a new URL in our application.

The main guard to achieve that behavior is canActivate. This guard will run every time the user tries to access a path and only allow the user to proceed if the guard function returns true.

Here’s an example that’s going to check if the user is logged in before proceeding:

If we want to redirect the user to a login component when they’re not logged in, we can do so by using the Router service and returning a UrlTree object as follows:

Note how the inject function is used in all these guards to access any service we need.

canActivate works well when navigating to components. If we want to prevent the lazy-loading of a child route or prevent access to any path of the child route, then canActivateChild is what we need instead of canActivate.

Finally, if you want to prevent the user from navigating away from the current path/component, you can implement the canDeactivate guard. A typical example of such a use case is when working with complex forms where we want to make sure the user has saved their progress before navigating away from the current screen/component:

Another exciting feature is that we can access the current component itself instead of a service, which is especially useful for canDeactivate:

If you like those new framework features, I covered some more utility router functions released with Angular 14+ in this post.

How to use Angular route guards?

The Angular router has 5 different route guards that can be used to control user navigation in the browser.

Such guards can be used in scenarios such as:

  • Preventing users from navigating to parts of an application without authorization
  • Preventing users from leaving a form before it is saved or completed
  • Making sure the user is logged in before proceeding to the next page

A guard is a function (class-based guards are deprecated as of Angular 15.2) that can return three different values:

  • true if we want to allow the user to proceed (then the navigation happens)
  • false if we want to prevent the user from proceeding (then the navigation doesn’t happen)
  • A UrlTree if we want to redirect the user to another component (usually a login component or an error component)

Sometimes, we want to ask a server about the user, which means that our guard is going to do asynchronous work. That’s fine because guards can return an Observable or a Promise of the above values, which means that a guard can return any of the six following types:

Any route can be protected by one or more guards. In the router config, we define an array of guards for each route as follows:

The above code means that in order to access the path /team/:id, a user has to satisfy the requirement of canActivateTeam, a guard function that returns one of the values documented above.

Path mapping to simplify verbose imports

When an Angular application contains several modules or sub-folders, it can become challenging to maintain the readability of your Typescript imports. For instance:

import {something} from '../../../../../shared/shared.service'

It’s always better to rely on your IDE to write those paths for you using autocompletion (usually ctrl + space). Still, imports don’t have to be that long. Typescript has a solution for that called path mapping.

The idea is to define common paths in the tsconfig.json file of your projects. Then, in the compilerOptions property, add or edit the paths config to provide some aliases for all of your long paths:

"paths": {
  "stubs/*": [
    "./src/app/stubs/*"
  ],
  "state/*": [
    "./src/app/state/*"
  ],
  "shared/*": [
    "./src/app/shared/*"
  ]
}

The above results in a shorter import syntax everywhere in your code:

import {something} from 'shared/shared.service'

Of course, you can define as many path mappings as you need. Read that post for more of my favorite Typescript tips: 5 Typescript tricks for Angular. Some have already been covered in this newsletter, but all need reminders occasionally.

Hot and cold RxJs Observables

You’ve probably heard about cold and hot observables in RxJs. What does that mean, and what’s the difference?

It’s pretty straightforward:

  • A cold observable creates a new “task” for each subscriber. For instance, observables returned by the Angular HttpClient are cold. If we subscribe to the same
    observable three times, we’re firing three different HTTP requests. Another consequence is that cold observables don’t do anything unless they get subscribed to.
  • A hot observable, as you might have guessed, is the opposite. Hot observables share their data with all subscribers. They are multicast. They don’t need to be subscribed to get started. Subjects are examples of hot observables. The observables we get from Angular FormControl are hot observables, too.

I’ll share more tips on RxJs hot and cold observables in the coming weeks.

The inject() function

Next in our dependency injection series is the inject() function. You’re probably familiar with injecting services using class constructors as follows:

We can also use the inject function to do the same:

The inject function can be used in 3 different places. We can use it when initializing the field of an “Angular” class (component/service/pipe/directive/module) or in the body of the constructor of such a class:

Or it can be used in a provider factory function (see yesterday’s post as an example):

For now, we can consider that function as a syntax alternative. That said, we must start using it as soon as possible because router class guards have been deprecated in Angular 15.2. And the alternative is functional guards that might need dependencies injected – using the inject function: