<

Mastering Standalone Components in Angular: An Introductory Overview

AngularJS Software Development
Mastering Standalone Components in Angular: An Introductory Overview

We’ll explore the essentials to kickstart your experience, including declaring standalone entities, integrating them into existing applications, getting rid of NgModule, tackling routing, and delving into other nerdy technicalities. And we’ve included practical examples to illustrate the concepts. So, buckle up and prepare for an enlightening and entertaining Angular adventure!

All by myself

All By Myself

With Angular version 14, the highly anticipated standalone API has arrived following months of deliberation. This significant, game-changing feature bids farewell to the familiar NgModule and welcomes its modern counterpart, the standalone: true attribute. This innovative addition enables the declaration of standalone components, pipes, and directives. Furthermore, beginning with Angular version 15, the standalone API has transitioned from an experimental feature to a stable and reliable part of the Angular ecosystem.

So let’s take a look at the structure of the standalone component:

@Component({
selector: 'standalone',
standalone: true,
templateUrl: './standalone.component.html'
})
export class StandaloneComponent {


As I said earlier, components, directives, and pipes can now be declared as standalone. If that’s the case, we can’t declare them in an NgModule and we can directly import them into another standalone component. In the StandaloneComponent example above, if we want to use it in the template of some other standalone component, we have to import it there, like in the following example:

@Component({
selector: 'parent',
standalone: true,
imports: [StandaloneComponent],
templateUrl: './parent.component.html'
// uses '< standalone >'
})
export class ParentComponent {


This applies to every component, directive, or pipe you use in a standalone component. So if the template of ParentComponent, for example, uses a standalone LoremIpsumPipe and a standalone LoremIpsumDirective, then they also need to be declared into the imports of the component:

@Component({
selector: 'parent',
standalone: true,
imports: [StandaloneComponent, LoremIpsumPipe, LoremIpsumDirective],
templateUrl: './parent.component.html'
// uses '< standalone >', `loremIpsumPipe` and `loremIpsumDirective`
})
export class ParentComponent {


The same rule applies to other components, directives, and pipes offered by Angular itself. The ever present ngIf directive, for example, needs to be declared in the imports array before it’s used in a template. And the good news is that it exists as a standalone directive and is available in CommonModule.

@Component({
selector: 'parent',
standalone: true,
imports: [CommonModule, RouterModule, StandaloneComponent, LoremIpsumPipe, LoremIpsumDirective],
templateUrl: './parent.component.html'
// uses`*ngIf`, `routerLink`, '< standalone >', `loremIpsumPipe` and `loremIpsumDirective`
})
export class ParentComponent {


The same importing principle applies for third-party libraries and there is also a way of directly importing directives like NgFor shown in the following example:

@Component({
selector: 'parent',
standalone: true,
imports: [NgFor, Drag&DropModule, StandaloneComponent],
templateUrl: './parent.component.html'
// uses`*ngFor`, `drag&drop`, '< standalone >'
})
export class ParentComponent {


Now that we know the basics, you might wonder how we can use the newly created StandaloneComponent above in an existing application that doesn’t yet have any standalone components. The answer is to simply import the standalone component in the imports of an NgModule.

@NgModule({
declarations: [AppComponent],
imports: [BrowserModule, HttpClientModule, StandaloneComponent],
bootstrap: [AppComponent]
})
export class AppModule {}


A sound use case for standalone components in an existing project would be converting the SharedModule with common components, pipes and directives in a standalone version. That way, you can import only what you need instead of importing the full SharedModule.

 

CLI and Standalone API

Angular version 14 standalone components, directives, and pipes can be created via CLI by adding a new flag –standalone to ng generate command: ng generate component --standalone loremIpsum

or a shorthand ng g c --standalone loremIpsum

Once the component is created, its scaffolding will have the standalone: true and the imports will already be populated with CommonModule since it will be used in the majority of components anyway.

@Component({
selector: 'lorem-ipsum',
standalone: true,
imports: [CommonModule],
templateUrl: './lorem-ipsum.component.html'
})
export class LoremIpsumComponent {

In order to create all components with the –standalone flag, it’s necessary to set the option directly in angular.json (same applies to directives and pipes):

"schematics": {
"@schematics/angular:component": {
"standalone": true
}
}

Application Bootstrap Without NgModule

Embracing the standalone approach, you can develop an application solely using standalone entities to effectively eliminate NgModule from the equation. In scenarios where neither AppModule nor NgModule is present, bootstrapping the application can be achieved through the novel function, bootstrapApplication(), sourced from @angular/platform-browser. This innovative method serves as an alternative to the conventional platformBrowserDynamic().bootstrapModule(AppModule) process, paving the way for streamlined Angular application development.

So our main.ts file will contain the following: bootstrapApplication(AppComponent)where AppComponent represents a root standalone component, which is then bootstrapped and the application is up and running.
Since we’re not using NgModule, in order to define providers available for the component, we can use the second parameter of the bootstrapApplication() function: providers.

bootstrapApplication(AppComponent, { providers: [] });

We can use importProvidersFrom(module) to bridge the gap with modules that expose providers. importProvidersFrom can only be used in bootstrapApplication() and not in the component providers. Additionally, it is responsible for Dependency Injection and that’s where providers must be declared.

bootstrapApplication(AppComponent, {
providers: [importProvidersFrom(SomeModuleWithProviders)]
});

It’s also worth noting that BrowserModule providers are automatically included when starting an application with bootstrapApplication().
When it comes to the HttpClient, a new way of providing it since Angular v15 is provideHttpClient(). By doing so, we make sure HttpClient is available for injection in your application.

bootstrapApplication(AppComponent, {
providers: [provideHttpClient()]
});

If you’re migrating an application to a standalone API version, make sure to replace every instance of HttpClientModule with provideHttpClient() (and HttpClientTestingModule with provideHttpClientTesting() in tests).

 

Routing in a Standalone Approach

When it comes to the routing, provideRouter() function is the way to go since Angular v14.2

bootstrapApplication(AppComponent, {
providers: [provideRouter(routes)]
});

It can be further customized, so if for example you want to support the preloading strategy that fetches lazy-loading parts of the application in the background, you can use:

bootstrapApplication(AppComponent, {
providers: [provideRouter(routes, withPreloading(PreloadAllModules))]
});

Other available with… options to enable router features include withDebugTracing, withRouterConfig, and withInMemoryScrolling, along with others (you can check the documentation to see the whole list).

Lazy-loading is also supported in a standalone world. First, make sure the component is standalone and then just directly lazy-load the component with loadComponent. Yes, it’s that simple!

{
path: 'lazy',
loadComponent: () => import('./lazy/lazy-standalone.component').then(m => m.LazyStandaloneComponent)
},

The import can be further simplified if the component is exported by default:

{
path: 'lazy',
loadComponent: () => import('./lazy/lazy-standalone.component')
},

Providers can also be made available only in the components of the lazy-loaded module, like the case with the NgModule. To do so, it’s possible to declare providers directly on a route, thus making them only available for this route and its children:

{
path: 'lazy',
providers: [LazyService],
loadComponent: () => import('./lazy/lazy-standalone.component').then(c => c.LazyStandaloneComponent)
}

In the example above, the service is not lazy-loaded, but the components are. In order to lazy-load the service to you can do it like this:

{
path: 'lazy',
loadChildren: () => import('./lazy/lazy.routes').then(c => c.lazyRoutes)
},


export const lazyRoutes: Routes = [
{
path: '',
pathMatch: 'prefix',
providers: [LazyService], // ← this is the way
children: [{ path: '', component: LazyStandaloneComponent }]
}
];

That’s All Folks

As we reach the conclusion of this insightful journey, let’s take a moment to recap the highlights. We introduced you to the innovative Angular feature aptly named “standalone components.” We explored declaring standalone entities, integrating them into existing projects, and adopting an entirely standalone approach by eliminating NgModule. Additionally, we delved into routing and lazy-loading within the standalone context.

The standalone APIs certainly live up to the hype, offering a modern and streamlined mental model. They enable providers to be declared at the application level, while components simply import required elements within their templates—effectively reducing boilerplate code. And remember, this is just the beginning! We can anticipate even more features to enhance the process and elevate Angular development, making the whole process even smoother and cooler. Until next time, au revoir!

© 2024 Newfire LLC,
45 Prospect St, Cambridge, MA 02139, USA

Privacy Policy
Amazon Consulting PartnerClutch