Conditional Dependency Injection

Yesterday, we saw how to use our component (and module) hierarchy to configure dependency injection with Angular.

Today, let’s look at how we can make dependency injection conditional. For instance, say we have a LoginService for production that requires features unavailable at dev time or that we do not want to use during the development phase.

What we can do then is configure our providers to make a decision based on the current environment:

Using the ternary operator [if true] ? [then this] : [else that], we can decide when to use a service over an alternative version.

If you need to make that dependency injection decision depending on other factors, such as data from other services, or if you have several different possible services to inject, you can use a factory function that relies on other services (deps) that are injected in the factory function as follows:

The order of the dependencies in deps has to match the order of the parameters in the factory function passed to useFactory.

Angular Signals are in the works!

This is probably the most significant change coming to the framework since the release of Angular v2 many years ago.

A new feature called Signals is being prototyped in Angular, and we can follow the entire discussion on GitHub.

Where do signals come from?

They are the answer from the Angular team to several different requests from the developer community over the years, more specifically:

  • Being able to use Observables with @Input
  • Being able to use Angular without Zone.js to have more control over change detection.
  • Having state management built-in with the framework so we don’t need libraries like NgRx or NgXs anymore.
  • Being able to use Angular in a reactive way without relying on RxJs.

In other words, Signals will be a clear and unified model for how data flows through an application and will work without any dependency (no RxJs, Zone, or other third-party library needed).

Will we have to change everything in our apps?

No, because the Angular team has communicated that:

  • Signals will be optional, and the current way of working with Angular will remain in place.
  • Signals will provide ways to play nicely with RxJs (a Signal can be turned into an Observable and vice-versa)

What will signals look like?

It’s still early to have a definite API. We’re not 100% sure what signals will look like, but the early prototype API looks like this:

Creating a signal with a default value (of 0 in this example)

The above line of code would be very much like doing const counter = new BehaviorSubject(0);

Setting a new value to a signal

Updating a value derived from the current value

Updating a value within a signal (mutation of the internal state)

Displaying the value of a signal in an HTML template or Typescript code

The above would be the equivalent of counter | async when using RxJs. The nice thing is that with Signals, there will be no more .subscribe() and .unsubscribe().

If you want to dive into more details, here is the documentation of the current prototype. Again, this is still early and I would not expect Signals to become fully available before Angular 17 or 18, but this is very exciting nevertheless!

Standalone Application and Router config

With this daily post, let’s get back to the new Angular standalone features. So far, we have seen how to create standalone components, add dependencies, and lazy-loading of standalone components.

Since the primary goal of standalone components is to have less NgModules, what about creating an Angular application with no NgModule at all?

Enter the bootstrapApplication function (from @angular/platform-browser). All it needs is the root standalone AppComponent as a parameter:

And that’s it. No AppModule needed. Now you’re probably wondering: How to add some router config?

There’s a provideRouter function for that, along with several router utility functions to configure preloading, guards, error handling, and more:

Last but not least, if you need to use services from a third-party module and don’t want to import the other features of that module (components/pipes/directives), you can also do the following:

As you can see, standalone components have paved the way for many new function-based features (instead of classes/services), and we’ll see even more of those in the coming posts.

Lazy-loading standalone components

Now that we’ve covered both standalone components and lazy-loading Angular modules, we can introduce the concept of lazy-loading a standalone component, which wasn’t possible before Angular 14.

From a syntax standpoint, the only difference is that we use loadComponent instead of loadChildren. Everything else remains the same in terms of route configuration:

Now, one of the benefits of lazy-loading a module is that it can have its own routing config, thus lazy-loading multiple components for different routes at once.

The good news is that we can also lazy-load multiple standalone components. All it takes is creating a specific routing file that we point loadChildren to, like so:

One last cool thing to share today: Along with the above syntax, Angular now supports default exports in Typescript with both loadChildren and loadComponent.

This means that the previous verbose syntax:

loadComponent: () => import('./admin/panel.component').then(mod => mod.AdminPanelComponent)},

Can now become:

loadComponent: () => import('./admin/panel.component')

This works if that component is the default export in its file, meaning that the class declaration looks like this:

export default class PanelComponent

The same applies to loadChildren if the array of routes (or NgModule) is the default export in its file. You can see an example in Stackblitz here.

Lazy-loading for better Angular performance

Before we continue our series on standalone components, it is important to talk about the most important tool at our disposal to create more performant Angular applications: Lazy-loading.

When we use lazy-loading, instead of building our application as one single bundle of code that gets downloaded as soon as our user accesses the app in a browser, we divide our code into several different pieces that get downloaded on-demand when they are needed.

For instance, without lazy-loading, if our application is 25 MB big in terms of Javascript code, a browser has to download, then parse, and run those 25 MB of code, which can slow things down a lot on mobile devices or slower internet connections.

Instead, we can divide our application into different modules containing components, pipes, directives, and services. In our example, let’s assume we create an AdminModule that includes all of the features needed for the Admin section of the application. If this module ends up containing 10 MB of code and we use lazy-loading with it, then the initial bundle of the application is down from 25 MB to 15 MB, which is a big difference.

Only Admin users would ever have to download the 10 MB of code for the admin section, which is great for both performance and safety (hackers can’t reverse-engineer code that has never been downloaded in their browser).

The best part of lazy-loading is that a single line of code initiates it in our router config:

The above line will get the Angular compiler to automatically create a bundle of code for AdminModule and enable lazy-loading of that code when /items is accessed. That’s it!

Adding dependencies to standalone components

Now that we’ve introduced standalone components, you might have tested them and quickly realized that if you start using directives such as *ngIf, or other components, your code doesn’t compile anymore.

That’s because those template dependencies (used only in the HTML template of your component) are not imported in Typescript (yet), so Angular cannot compile your templates. This doesn’t happen when we use modules because our components are declared there. We also import CommonModule by default, which contains all of the primary directives and pipes of the Angular framework.

If we want to import all these features into our standalone components, we can use the import property in the decorator as follows:

And if you want to import just one feature instead of an entire module, you can do that too – but only if that feature is declared as standalone:

This means that the Angular team has modified all Angular directives and pipes to be available as standalone features (see the ngIf source code here as an example).

Of course, we can still import entire modules if needed or list individual template dependencies, which means that all pipes, directives, and components should be listed individually in the imports array:

You can see an example on Stackblitz here.

What are standalone components?

Since Angular 14, any Angular feature (component, pipe, or directive) can be created as “standalone.” This week, we will dive into what standalone components are, what feature they bring to the table, and how to use them.

One quick note before we start: Whenever you see the words “standalone components,” it really means “standalone components/pipes/directives.” Perhaps we should call those “standalone features,” but I’ll stick with the naming convention used by the Angular team so far, so I don’t stand… alone.

What’s a standalone component?

A standalone component is a component that doesn’t belong to any NgModule. It can be imported on its own and used as-is.

For instance, in the past, we might have a ButtonComponent and a ButtonDirective in a ButtonModule (just like Angular Material does). This means that if we want to use ButtonComponent, we have to import the ButtonModule in our AppModule or feature module. This will make both ButtonComponent and ButtonDirective available for use in our app, even if you just use one of those features and don’t need the other.

Standalone components are different. They can be imported in a module just like other modules get imported in the array of imports:

Importing only the features we need is always better for performance, as the build output will be smaller than if we import an entire module of dependencies. So that would be benefit number one of standalone components.

How to create a standalone component?

With the Angular CLI: ng generate component Button --standalone

We can also make an existing component standalone by adding the standalone: true property in the @Component decorator like so:

You can see that example in action here on Stackblitz.

The power of Angular selectors

Yesterday, we saw how using directives instead of components can increase code reusability by not tying our code logic to a specific HTML template.

Now, if we want to write some code once and for all and make it work for all HTML elements in our app that match some specific conditions, we have another underused tool at our disposal: CSS selectors.

For instance, let’s say we want the isActive directive from our previous example to be applied to all buttons in our application. Of course, we could manually look for all buttons and add the isActive attribute to them, but that would be tedious and error-prone.

Or we could be more imaginative and change the selector of that directive so that it applies to all buttons and any other elements that have an isActive attribute:

With that implementation, all current (and future) buttons will have that directive applied to them. But what if we want to make any exceptions and disable the directive on some buttons? That’s how we could do it:

Then all we would need is to add the disableIsActive attribute to those buttons that don’t want the isActive directive:

My point here is that there are some creative use cases for CSS selectors, and those selectors are the same as those used for CSS styling, meaning that we can rely on classes, attributes, element names, or any combination of the above!

The Angular framework does precisely that in quite a few places. If you’ve ever wondered how forms get auto-magically handled by Angular, now you know the answer. The ngForm directive has a complex selector that would apply to all form elements by default:

When to create a directive vs. a component?

Most Angular applications are composed of a majority of components and just a few directives if any. Today, I’d like to show you an example where a directive makes much more sense than a component.

Consider the following component template:

We have created a component tied to one single HTML element (button), and all the logic in that template is about changing attribute values (for the isActive class and the disabled attribute).

To achieve this, we needed three specialized inputs that resulted in a loss of reusability:

The above isn’t ideal because:

  • Our button can only display text, not any HTML (so no icons, for instance)
  • Our button assumes that its state always depends on saved and hasError, but what if we want to add a third variable to the mix for other use cases?

Let’s create a directive instead:

Which would be used like so:

This implementation is a lot better because:

  • It would work with any HTML element, not just buttons (assuming the element supports disabled properly)
  • The button can display anything, not just text
  • The criteria for isActive to be true depends on the use case and isn’t tied to just saved and hasError.

The point here is that using a directive in that scenario made our code more flexible, and as a result, more reusable. That’s why popular component libraries rely heavily on directives rather than components. Here’s an example from the Angular Material documentation:

You can see that mat-button is a directive, not a component. That way, it can be used with a button or an a tag. You can find the example code of this post on Stackblitz.

Think about this the next time you’re tempted to create a component with one or two lines of HTML in its template. Most likely, what you need instead is a directive.

Check out this tutorial for another example of a directive that makes perfect sense instead of using a component.