Enterprise Angular

Standalone Components


The Goal

The standalone component architecture in Angular refers to the practice of encapsulating different parts of a web application into self-contained, reusable components.

These components can be developed, tested, and maintained independently, offering several advantages:

Modularity: Standalone components promote modularity in Angular applications. Each component focuses on a specific piece of functionality or UI element. This modular approach makes it easier to understand, maintain, and scale the application since developers can work on individual components without affecting others.

Reusability: Components can be reused across different parts of the application or even in other projects. This reusability reduces development time and effort, as developers can leverage existing components rather than reinventing the wheel. It also ensures consistency across the application by using the same components for similar functionalities.

Encapsulation: Components in Angular encapsulate their own logic, styles, and templates. This encapsulation prevents the leakage of styles or logic to other parts of the application, reducing the risk of conflicts and making it easier to manage dependencies.

Testability: Standalone components are easier to test since they represent isolated units of functionality. Developers can write unit tests for each component to ensure that it behaves as expected under different scenarios. This testing approach improves the overall quality and reliability of the application.

Scalability: As applications grow, standalone components help maintain a structured and organized codebase. Developers can add new features or modify existing ones by creating new components or extending existing ones, without having to overhaul the entire application architecture.

Parallel Development: Since components are independent entities, multiple developers can work on different components simultaneously without stepping on each other's toes. This parallel development approach accelerates the development process and fosters collaboration within the development team.

Ease of Maintenance: With a modular architecture based on standalone components, maintenance becomes more manageable. If a component needs to be updated or fixed, developers can focus solely on that component without affecting other parts of the application. This targeted approach simplifies debugging and troubleshooting.

Clearer Code Structure: Adopting a standalone component architecture leads to a clearer and more organized code structure. The separation of concerns between different components makes the codebase easier to navigate and understand, even for developers who are new to the project.

Overall, the standalone component architecture in Angular offers numerous benefits in terms of modularity, reusability, encapsulation, testability, scalability, parallel development, maintenance, and code structure. These advantages contribute to building robust, maintainable, and efficient web applications.

The Code

Here are a few isolated examples of what common elements of an Angular application would look like using a standalone approach.

Pipes

This code block defines an Angular pipe named CapitalisePipe that capitalizes the input string passed to it. Here's a breakdown of what each part of the code does:

@Pipe({ name: 'capitalise', standalone: true }): This is a decorator used to define an Angular pipe. It takes an object as an argument with properties that configure the behavior of the pipe. In this case:

name: 'capitalise': Specifies the name of the pipe, which will be used to reference it in Angular templates. standalone: true: Indicates that this pipe is standalone, meaning it doesn't rely on Angular's internal infrastructure. This can improve performance by allowing the pipe to be executed independently of Angular's change detection mechanism. export class CapitalisePipe implements PipeTransform { ... }: This declares a TypeScript class named CapitalisePipe, which implements the PipeTransform interface. Angular pipes must implement the PipeTransform interface, which requires the implementation of a transform method. The transform method accepts an input value (in this case, a string) and optionally additional parameters, and it returns the transformed value (also a string in this case).

transform(word: string): string { ... }: This is the transform method of the CapitalisePipe class. It takes a single argument word of type string, which represents the input value to be transformed. Inside the method, the toLocaleUpperCase() function is called on the word string, which converts all characters to uppercase according to the locale rules of the environment. The transformed string is then returned.

@Pipe({
  name: 'capitalize',
  standalone: true,
})
export class CapitalizePipe implements PipeTransform {
  transform(word: string): string {
    return word.toLocaleUpperCase();
  }
}

We can then leverage the CapitalizePipe by declaring our component as standalone and then using the pipe in our template.

@Component({
  selector: 'app-hello',
  template: `Hello { { name | capitalize } }`,
  standalone: true,
  imports: [CapitalisePipe],
})
class AppComponent {}

Directives

@Directive({
  selector: '[example-directive]',
  standalone: true,
})
class ExampleDirective {}
@Component({
  selector: 'app-hello',
  template: ` <div example-directive>Hello { { name | capitalise } }</div>`,
  standalone: true,
  imports: [CapitalisePipe, ExampleDirective],
})
class StandaloneComponent {}

Interop

You can import NgModules directly into standalone components.

Standalone components can also be imported into existing NgModules-based contexts.

Lazy Loading

This was cumbersome.

const routes: Routes = [
  {
    path: 'one',
    loadChildren: () =>
      import('./module-one/moduleone.module').then((m) => m.ModuleOneModule),
  },
];

This is much easier!

export const ROUTES: Route[] = [
  {
    path: 'lazy-hello',
    loadComponent: () =>
      import('./app-hello').then((m) => m.StandaloneComponent),
  },
];

Bootstrapping

import { bootstrapApplication } from '@angular/platform-browser';
import { PhotoAppComponent } from './app/photo.app.component';

bootstrapApplication(PhotoAppComponent);

Dependency injection.

bootstrapApplication(PhotoAppComponent, {
  providers: [
    {
      provide: BACKEND_URL,
      useValue: 'https://photoapp.looknongmodules.com/api',
    },
    provideRouter([
      /* app routes */
    ]),
    // ...
  ],
});
bootstrapApplication(PhotoAppComponent, {
  providers: [
    {
      provide: BACKEND_URL,
      useValue: 'https://photoapp.looknongmodules.com/api',
    },
    { provide: PhotosService, useClass: PhotosService },
    // ...
  ],
});
// In the main application:
export const ROUTES: Route[] = [
  {
    path: 'admin',
    loadChildren: () =>
      import('./admin/routes').then((mod) => mod.ADMIN_ROUTES),
  },
  // ...
];

// In admin/routes.ts:
export const ADMIN_ROUTES: Route[] = [
  { path: 'home', component: AdminHomeComponent },
  { path: 'users', component: AdminUsersComponent },
  // ...
];
export const ROUTES: Route[] = [
  {
    path: 'admin',
    providers: [AdminService, { provide: ADMIN_API_KEY, useValue: '12345' }],
    children: [
      { path: 'users', component: AdminUsersComponent },
      { path: 'teams', component: AdminTeamsComponent },
    ],
  },
  // ... other application routes that don't
  //     have access to ADMIN_API_KEY or AdminService.
];

Standalone Interceptors

bootstrapApplication(AppComponent, {
  providers: [provideHttpClient(withInterceptors([authInterceptor]))],
});
import { HttpInterceptorFn } from '@angular/common/http';

export const authInterceptor: HttpInterceptorFn = (req, next) => {
    req = req.clone({
        headers
    }

    return next(req)
};
bootstrapApplication(AppComponent, {
  providers: [
    provideHttpClient(withInterceptorsFromDi()),
    {
      provide: HTTP_INTERCEPTORS,
      useClass: AuthInterceptor,
      multi: true,
      withRequestsMadeViaParent(),
    },
  ],
});

Resources

Getting started with standalone components Migrate an existing Angular project to standalone

The Refurbished HttpClient in Angular 15 – Standalone APIs and Functional Interceptors

Challenges

Pending.

< Angular Home