Back to the homepage
Angular

Angular Standalone API

Angular introduced the Standalone API in its 14th version, and as part of the 15th version. It was marked as stable (previously it was in “developer preview” status). A change that has reverberated throughout the community, being one of the biggest breaths of fresh air in our framework since the introduction of IVY. In this article, we’ll take an in-depth look at what this means for us, what benefits and risks Standalone components bring, and how they might potentially change in the future.

Creating a standalone component

Standalone component (worth noting, this also applies to directives and pipes), is created by setting the newly added standalone flag in the component decorator to true.

Such a declared component can be imported by another standalone component or module or used within a routing declaration. Important to note, such a component cannot be included in the module declaration. If we try to do this we will get a compilation error.

In our components from now on, we can also import other Standalone components and modules analogously to modules.

Injectors

So far in Angular, we have distinguished between two types of Injectors – module and element. However, the possibility of creating an Angular application without any module (except those coming from Angular itself), has forced us to change the nomenclature to some extent, since the name module injector seems somewhat unwarranted. Thus, from now on, what we used to call module injectors took the name environment injectors. 

Environment injectors can be configured as follows: 

  • @NgModule.providers
  • @Injectable({provideIn: “…”})
  • providers as a parameter of the bootstrapAplication function (in the case of a “standalone” application)
  • providers within the routing statement

While the first two ways are not new here, the last two are worth a closer look. 

bootstrapAplication

This is a new function that we have obtained to initialize an application running on standalone components. It takes as a parameter the ‘root component’ of our application, which must be marked as standalone, and as a second parameter a configuration object, at this point consisting only of a collection of providers to be included in our root injector. The function itself returns Promise<ApplicationRef>.

The difference in the initialization of a module-based application and a component is as follows:


 

 

Since we no longer have modules in the standalone application, resolving Injector dependencies based on them is no longer possible. Providers within a component are resolved in a different way than was the case with modules (more on that later in the article). For this purpose, we got another helper function – importProvidersFrom. This function is used to collect all providers from among the indicated modules and standalone components (or rather, modules imported within standalone components). The function can only be used within a bootstrapApplication method or routing declaration. It cannot be used within provider declarations within a component. Why haven’t we needed this type of function so far? Because previously, working within a module-based application, when importing a module, Angular would add to our injector all its providers and the providers of the modules that the indicated module imported. Graphically, this is represented by the following graph. It is also a representation of how the importProvidersFrom function will work on the indicated modules. Based on our example of initializing a standalone application, HttpClient and Router, among others, will be added to our injector.

Also keep in mind that this feature may disappear in the future or be required much less often, as Angular will provide us with dedicated functions to configure individual modules, for example, this may be the case for routing.

Router providers

With this change, within our routing declaration, we can also add providers that will be available in components under the indicated path and its children.

To get a good understanding of how router providers will work, let me introduce it by analogy to how the environment injector for lazy loaded modules currently works. For each lazy module, Angular creates a new environment injector, which contains a copy of our root environment injector and all the providers declared in the modules that are in the lazy loaded module tree. (Analogous for our image from the previous section, only instead of the root environment injector there would be our environment injector created as part of the lazy loading). 

Currently, router providers work in the same way, although now to create an environment injector for a specific group of components under a given routing, it no longer needs to work on the basis of Api lazy loading. This happens for each provider group within the routing statement. Such an environment injector will contain copies of the root environment injector and the providers declared in the providers for a given path. 

For many of us working with any state management, routing will become the destination for initializing the next piece of our state.

Why not CartComponent we might ask? Because just the way the component providers work hasn’t changed, and anything we add within the component to our providers will be added to the Element Injector associated with our component instance, rather than being passed to the top as was the case with the module what our graphic depicted. The application’s state is global, once registered, we may want to access a registered piece of state in a completely different part of the application, so it must be configured globally. 

Standalone Injector

It would seem that the changes so far have already messed up a lot, and it’s not over yet. In some cases, Angular is forced to create a new “standalone injector”. All this in order to merge the two worlds – modular and standalone. The most common case when such an injector will be created is the following situation: 

The standalone component (DateModalComponent) uses another component (DatePickerComponent), which is not standalone, so we are forced to import the module in which it resides (DatePickerModule). In addition, the component used uses a service (CalendarService), which is also provided at the level of the said module. 

When Angular creates our standalone component, it needs to make sure that it provides all the necessary providers for the functioning of this component and its dependencies, including those based on modules. And it is for this purpose that a “standalone injector” will be created as a child of the “environment injector”, under which the component being created falls.

Routing i lazy loading

The router API has also been simplified to support standalone components. We now no longer need modules to implement lazy loading.

Lazy loading of a single component

Now we can lazy load any standalone component using the loadComponent method:

Lazy loading of component groups

Currently, loadChildren allows you to load the entire set of child routes without creating an additional lazy loaded module to declare them by using RouterModule.forChild

Summary

As you can see the scale of the improvements is large, yet this is a very big step in terms of Angular development and any future changes seem even more exciting.

Let us know in the comments how you are working with the new API.

About the author

Mateusz Stefańczyk

Angular Developer at House of Angular. Mateusz is an avid editor of angular.love. In the portal for many years. A fan of all types of consoles and india games. He is a football fan – both real and virtual. Every weekend, he gets comfortable in his armchair, fastens his seat belt, and enjoys the European football games.

Don’t miss anything! Subscribe to our newsletter. Stay up-to-date with the latest trends, tips, meetups, courses and be a part of a thriving community. The job market appreciates community members.

Leave a Reply

Your email address will not be published. Required fields are marked *