Skip to content

How to Create Standalone Components in Angular

Angular has become one of the most popular frameworks to build web applications today.

One of the key features of the framework is its component-based architecture, which allows best practices like modularity and reusability. Each Angular component consists of a template, a TypeScript class and metadata.

In this blog post, we will dive deeper into standalone components, and we will explore the anatomy of an application based on them.

For the demo application, we will create a card-like component which can be used to render blog posts in a web application.

Prerequisites

You'll need to have installed the following tools in your local environment:

  • The latest LTS version of Node.js is recommended.
  • Either NPM or Yarn as a package manager.
  • The Angular CLI tool(Command-line interface for Angular).

Initialize the Project

Let's create a project from scratch using the Angular CLI tool:

ng new demo-angular-standalone-components --routing --prefix corp --style css --skip-tests

This command will initialize a base project using some configuration options:

  • --routing. It will create a routing module.
  • --prefix corp. It defines a prefix to be applied to the selectors for created components(corp in this case). The default value is app.
  • --style css. The file extension for the styling files.
  • --skip-tests. Disable the generation of testing files for the new project.

If you pay attention to the generated files and directories, you'll see an initial project structure including the main application module and component:

|- src/
    |- app/
        |- app.module.ts
        |- app-routing.module.ts
        |- app.component.ts

Creating Standalone Components

First, let's create the custom button to be used as part of the Card component later. Run the following command on your terminal:

ng generate component button --inline-template --standalone

It will create the files for the component. The --standalone option will generate the component as standalone.

Let's update the button.component.ts file using the content below.

import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';

@Component({
  selector: 'corp-button',
  standalone: true,
  imports: [CommonModule],
  template: `
    <button class="corp-button">
      <ng-content></ng-content>
    </button>
  `,
  styleUrls: ['./button.component.css']
})
export class ButtonComponent {

}

Pay attention to this component since it's marked as standalone: true.

Starting with Angular v15: components, directives, and pipes can be marked as standalone by using the flag standalone.

When a class is marked as standalone, it does not need to be declared as part of an NgModule. Otherwise, the Angular compiler will report an error.

Also, imports can be used to reference the dependencies.

The imports property specifies the standalone component's template dependencies — those directives, components, and pipes that can be used within its template. Standalone components can import other standalone components, directives, and pipes as well as existing NgModules.

Next, let's create the following components: card-title, card-content, card-actions and card.

This can be done at once using the next commands.

ng generate component card-title --inline-template --standalone
ng generate component card-content --inline-template --standalone
ng generate component card-actions --inline-template --standalone
ng generate component card --inline-template --standalone

On card-title.component.ts file, update the content as follows:

//card-title.component.ts

import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';

@Component({
  selector: 'corp-card-title',
  standalone: true,
  imports: [CommonModule],
  template: `
    <h4>
        <ng-content></ng-content>
    </h4>
  `,
  styleUrls: ['./card-title.component.css']
})
export class CardTitleComponent {

}

Next, update the card-content.component.ts file:

//card-content.component.ts

import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';

@Component({
  selector: 'corp-card-content',
  standalone: true,
  imports: [CommonModule],
  template: `
    <p class="corp-card-content">
      <ng-content></ng-content>
    </p>
  `,
  styleUrls: ['./card-content.component.css']
})
export class CardContentComponent {

}

The card-actions.component.ts file should have the content below:

// card-actions.component.ts

import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';

@Component({
  selector: 'corp-card-actions',
  standalone: true,
  imports: [CommonModule],
  template: `
    <div class="corp-card-actions">
      <ng-content></ng-content>
    </div>
  `,
  styleUrls: ['./card-actions.component.css']
})
export class CardActionsComponent {

}

Finally, the card.component.ts file should be defined as follows:

//card.component.ts

import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';

@Component({
  selector: 'corp-card',
  standalone: true,
  imports: [CommonModule],
  template: `
    <div class="corp-card">
        <ng-content></ng-content>
    </div>
  `,
  styleUrls: ['./card.component.css']
})
export class CardComponent {

}

Using Standalone Components

Once all Standalone components are created, we can use them without the need to define an NgModule.

Let's update the app.component.ts file as follows:


// app.component.ts

import { Component } from '@angular/core';
import { ButtonComponent } from './button/button.component';
import { CardComponent } from './card/card.component';
import { CardTitleComponent } from './card-title/card-title.component';
import { CardContentComponent } from './card-content/card-content.component';
import { CardActionsComponent } from './card-actions/card-actions.component';

@Component({
  selector: 'corp-root',
  standalone: true,
  imports: [
    ButtonComponent,
    CardComponent,
    CardTitleComponent,
    CardContentComponent,
    CardActionsComponent
  ],
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'demo-angular-standalone-components';
}

Again, this Angular component is set as standalone: true and the imports section specifies the other components created as dependencies. Then, we can update the app.component.html file and use all the standalone components.

<!-- app.component.html -->

<h2>Latest Posts</h2>

<corp-card>
  <corp-card-title>Introduction to Angular</corp-card-title>
  <corp-card-content>
    Angular is a component-based framework for building scalable web applications.
  </corp-card-content>
  <corp-card-actions>
    <corp-button>View</corp-button>
  </corp-card-actions>
</corp-card>

<corp-card>
  <corp-card-title>Introduction to TypeScript</corp-card-title>
  <corp-card-content>
    TypeScript is a strongly typed programming language that builds on JavaScript, providing better tooling at any scale.
  </corp-card-content>
  <corp-card-actions>
    <corp-button>View</corp-button>
  </corp-card-actions>
</corp-card>

This may not work yet, since we need to configure the Angular application and get rid of the autogenerated module defined under the app.module.ts file.

Bootstrapping the Application

Angular provides a new API to use a standalone component as the application's root component.

Let's update the main.ts file with the following content:

// main.ts
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';

bootstrapApplication(AppComponent);

As you can see, we have removed the previous bootstrapModule call and the bootstrapApplication function will render the standalone component as the application's root component. You can find more information about it here.

Live Demo and Source Code

Want to play around with this code? Just open the Stackblitz editor or the preview mode in fullscreen.

Conclusion

We’re now ready to use and configure the Angular components as standalone and provide a simplified way to build Angular applications from scratch. Do not forget that you can mark directives and pipes as standalone: true. If you love Angular as much as we do, you can start building today using one of our starter.dev kits or by looking at their related showcases for more examples.

This Dot Labs is a development consultancy that is trusted by top industry companies, including Stripe, Xero, Wikimedia, Docusign, and Twilio. This Dot takes a hands-on approach by providing tailored development strategies to help you approach your most pressing challenges with clarity and confidence. Whether it's bridging the gap between business and technology or modernizing legacy systems, you’ll find a breadth of experience and knowledge you need. Check out how This Dot Labs can empower your tech journey.

You might also like

A Guide to (Typed) Reactive Forms in Angular - Part II (Building Dynamic Superforms) cover image

A Guide to (Typed) Reactive Forms in Angular - Part II (Building Dynamic Superforms)

In the first blog post of the series, we learned about Angular reactive forms and the data structures behind them. When developing real-world applications, however, you often need to leverage dynamic forms, as writing boilerplate for every form and its specific cases can be tedious and time-consuming. In certain situations, it may even be necessary to retrieve information from an API to construct the forms. In this post, we will go over a convenient abstraction we can create to build dynamic and adaptable forms without repeating boilerplate. The trick is to create a "view model" for our data and use a service to transform that data into a reactive form. I was first introduced to this approach by my friend and former teammate Thomas Duft, and I've been using it ever since. The approach outlined in the linked article worked great with untyped forms, but since now we can get our forms strictly typed, we'll want to upgrade it. And here is where it gets a bit tricky. Remember how I mentioned you shouldn't predeclare your form groups earlier? If you want to recursively create a form from a config, you just have to. And it's a dynamic form, so you cannot easily type it. To solve the issue, I devised a trick inspired by a "Super Form" suggested by Bobby Galli. Assuming we will have interfaces defined for our data, using this approach, we can create dynamic type-safe forms. First, we'll create types for our form config: `TypeScript // this will be our ViewModel for configuring a FormGroup export class FormSection | FormField | (FormSection | FormField)[]; } = any > { public title?: string; public fields: T; constructor(section: { title?: string; fields: T; }) { this.title = section.title; this.fields = section.fields; } } // Let's define some editor types we'll be using in the templates later export type FormEditor = | 'textInput' | 'passwordInput' | 'textarea' | 'checkbox' | 'select'; // And this will be a ViewModel for our FormControls export class FormField { public value: T; public editor: FormEditor; public validators: Validators; public label: string; public required: boolean; public options?: T[]; constructor(field: { value: T; editor: FormEditor; validators: Validators; label: string; required: boolean; options?: T[]; }) { this.value = field.value; this.editor = field.editor; this.validators = field.validators; this.label = field.label; this.required = field.required; this.options = field.options; } } ` And then we'll create some type mappings: `TypeScript // We will use this type mapping to properly declare our form group export type ControlsOf> = { [K in keyof T]: T[K] extends Array ? FormArray> : T[K] extends Record ? FormGroup> : FormControl; }; // We will use this type mapping to type our form config export type ConfigOf = { [K in keyof T]: T[K] extends (infer U)[] ? U extends Record ? FormSection>[] : FormField[] : T[K] extends Record ? FormSection> : FormField; }; ` And now we can use our types in a service that will take care of creating nested dynamic forms: `TypeScript import { Injectable } from '@angular/core'; import { AbstractControl, FormArray, FormControl, FormGroup, } from '@angular/forms'; import { ConfigOf, ControlsOf, FormField, FormSection } from './forms.model'; @Injectable({ providedIn: 'root', }) export class FormsService { public createFormGroup>( section: FormSection> ): FormGroup> { // we need to create an empty FormGroup first, so we can add FormControls recursively const group = new FormGroup({}); Object.keys(section.fields).forEach((key: any) => { const field = section.fields[key]; if (Array.isArray(field)) { group.addControl(key, this.createFormArray(field)); } else { if (field instanceof FormSection) { group.addControl(key, this.createFormGroup(field)); } else { group.addControl(key, new FormControl(field.value, field.validators)); } } }); // and we need to cast the group to the correct type before returning return group as unknown as FormGroup>; } public createFormArray>( fields: unknown[] ): FormArray> { const array: FormArray> = new FormArray( [] ) as unknown as FormArray>; fields.forEach((field) => { if (field instanceof FormSection) { array.push(this.createFormGroup(field)); } else { const { value, validators } = field as FormField; array.push(new FormControl(value, validators)); } }); return array as unknown as FormArray>; } } ` And that's it. Now we can use our FormService` to create forms. Let's say we have the following User model: `TypeScript export type User = { email: string; name: string; } ` We can create a form for this user from config in the following way: `TypeScript const userFormConfig = new FormSection>({ title: 'User Form', fields: { email: new FormField({ value: '', validators: [Validators.required, Validators.email], label: 'Email', editor: 'textInput', required: true, }), name: new FormField({ value: '', validators: [Validators.required], label: 'Name', editor: 'textInput', required: true, }) } }); const userForm = this.formsService.createFormGroup(userFormConfig); ` If we would check the type of userForm.value` now, we would see that it's correctly inferred as: `TypeScript Partial ` Outputting the Dynamic Forms To display the dynamic forms, we can write a simple component that takes the FormSection` or `FormField` as an `Input()` along with our `FormGroup` and displays the form recursively. We can use a setter to assign either field` or `section` property when the view model is passed into the component, so we can conveniently use them in our template. Our form component's TypeScript code will look something like this: `TypeScript import { Component, Input } from '@angular/core'; import { FormField, FormSection } from '../forms.model'; import { FormArray, FormGroup } from '@angular/forms'; @Component({ selector: 'app-form', templateUrl: './form.component.html', styleUrls: ['./form.component.scss'], }) export class FormComponent { private fieldConfig?: FormField; private sectionConfig?: FormSection; private arrayConfig?: (FormSection | FormField)[]; private sectionFieldsArray?: [string, FormField][]; @Input() public set config( config: | FormField | FormSection | (FormSection | FormField)[] ) { this.fieldConfig = config instanceof FormField ? config : undefined; this.arrayConfig = Array.isArray(config) ? config : undefined; this.sectionConfig = config instanceof FormSection ? config : undefined; this.sectionFieldsArray = Object.entries(this.sectionConfig?.fields || {}); } public get sectionFields(): [string, FormField][] { return this.sectionFieldsArray || []; } public get field(): FormField | undefined { return this.fieldConfig; } public get section(): FormSection | undefined { return this.sectionConfig; } public get array(): (FormSection | FormField)[] | undefined { return this.arrayConfig; } ngAfterViewInit() { console.log(this.arrayConfig); } @Input() public key!: string; @Input() public group!: FormGroup; public get sectionGroup(): FormGroup { return this.group.get(this.key) as FormGroup; } public get formArray(): FormArray { return this.group.get(this.key) as FormArray; } } ` And our template will reference a new form component for each section field in case we have passed in a FormSection` and it will have a switch case to display the correct control in case a `FormField` has been passed in: `HTML {{ field.label }} {{ section.title }} ` That way, we can display the whole form just by referencing one component, such as `HTML ` Check out an example on StackBlitz. In the next (and last) post of the series, we will learn about building custom Form Controls....

A Guide to (Typed) Reactive Forms in Angular - Part I (The Basics) cover image

A Guide to (Typed) Reactive Forms in Angular - Part I (The Basics)

When building a simple form with Angular, such as a login form, you might choose a template-driven approach, which is defined through directives in the template and requires minimal boilerplate. A barebone login form using a template-driven approach could look like the following: `HTML E-mail Password Login! ` `TypeScript // login.component.ts import { Component } from '@angular/core'; @Component({ selector: 'app-login', templateUrl: './login.component.html' }) export class LoginComponent implements OnInit { public credentials = { email: '', password: '' }; constructor(public myAuthenticationService: MyAuthenticationService) { } } ` However, when working on a user input-heavy application requiring complex validation, dynamic fields, or a variety of different forms, the template-driven approach may prove insufficient. This is where reactive forms** come into play. Reactive forms employ a reactive approach, in which the form is defined using a set of form controls and form groups. Form data and validation logic are managed in the component class, which updates the view as the user interacts with the form fields. This approach requires more boilerplate but offers greater explicitness and flexibility. In this three-part blog series, we will dive into the reactive forms data structures, learn how to build dynamic super forms, and how to create custom form controls. In this first post, we will familiarize ourselves with the three data structures from the @angular/forms` module: FormControl The FormControl` class in Angular represents a single form control element, such as an input, select, or textarea. It is used to track the value, validation status, and user interactions of a single form control. To create an instance of a form control, the `FormControl` class has a constructor that takes an optional initial value, which sets the starting value of the form control. Additionally, the class has various methods and properties that allow you to interact with and control the form control, such as setting its value, disabling it, or subscribing to value changes. As of Angular version 14, the FormControl` class has been updated to include support for **typed reactive forms** - a feature the Angular community has been wanting for a while. This means that it is now a generic class that allows you to specify the type of value that the form control will work with using the type parameter ``. By default, `TValue` is set to any, so if you don't specify a type, the form control will function as an untyped control. If you have ever updated your Angular project with ng cli` to version 14 or above, you could have also seen an `UntypedFormControl` class. The reason for having a `UntypedFormControl` class is to support incremental migration to typed forms. It also allows you to enable types for your form controls after automatic migration. Here is an example of how you may initialize a FormControl` in your component. `TypeScript import { FormControl } from '@angular/forms'; const nameControl = new FormControl("John Doe"); ` Our form control, in this case, will work with string` values and have a default value of "John Doe". If you want to see the full implementation of the FormControl` class, you can check out the Angular docs! FormGroup A FormGroup` is a class used to group several `FormControl` instances together. It allows you to create complex forms by organizing multiple form controls into a single object. The `FormGroup` class also provides a way to track the overall validation status of the group of form controls, as well as the value of the group as a whole. A FormGroup` instance can be created by passing in a collection of `FormControl` instances as the group's controls. The group's controls can be accessed by their names, just like the controls in the group. As an example, we can rewrite the login form presented earlier to use reactive forms and group our two form controls together in a FormGroup` instance: `TypeScript // login.component.ts import { FormControl, FormGroup, Validators } from '@angular/forms'; @Component({ selector: 'app-login', templateUrl: './login.component.html' }) export class LoginComponent implements OnInit { public form = new FormGroup({ email: new FormControl('', [Validators.required, Validators.email]), password: new FormControl('', [Validators.required]), }); constructor(public myAuthenticationService: MyAuthenticationService) { } public login() { // if you hover over "email" and "password" in your IDE, you should see their type is inferred console.log({ email: this.form.value.email, password: this.form.value.password }); this.myAuthenticationService.login(this.form.value); } } ` `HTML E-mail Password Login! ` Notice we have to specify a formGroup` and a `formControlName` to map the markup to our reactive form. You could also use a `formControl` directive instead of `formControlName`, and provide the `FormControl` instance directly. FormArray As the name suggests, similar to FormGroup`, a `FormArray` is a class used to group form controls, but is used to group them in a collection rather than a group. In most cases, you will default to using a FormGroup` but a `FormArray` may come in handy when you find yourself in a highly dynamic situation where you don't know the number of form controls and their names up front. One use case where it makes sense to resort to using FormArray` is when you allow users to add to a list and define some values inside of that list. Let's take a TODO app as an example: `TypeScript import { Component } from '@angular/core'; import { FormArray, FormControl, FormGroup } from '@angular/forms'; @Component({ selector: 'app-todo-list', template: Add TODO , }) export class TodoListComponent { public todos = new FormArray>([]); public todoForm = new FormGroup({ todos: this.todos, }); addTodo() { this.todoForm.controls['todos'].push(new FormControl('')); } } ` In both of the examples provided, we instantiate FormGroup directly. However, some developers prefer to pre-declare the form group and assign it within the ngOnInit method. This is usually done as follows: `TypeScript // login.component.ts import { FormControl, FormGroup, Validators } from '@angular/forms'; @Component({ selector: 'app-login', templateUrl: './login.component.html' }) export class LoginComponent implements OnInit { // predeclare the form group public form: FormGroup; constructor(public myAuthenticationService: MyAuthenticationService) { } ngOnInit() { // assign in ngOnInit this.form = new FormGroup({ email: new FormControl('', [Validators.required, Validators.email]), password: new FormControl('', [Validators.required]), }); } public login() { // no type inference :( console.log(this.form.value.email); } } ` If you try the above example in your IDE, you'll notice that the type of this.form.value` is no longer inferred, and you won't get autocompletion for methods such as `patchValue`. This is because the FormGroup type defaults to `FormGroup`. To get the right types, you can either assign the form group directly or explicitly declare the generics like so: `TypeScript public form: FormGroup, password: FormControl, }>; ` However, explicitly typing all your forms like this can be inconvenient and I would advise you to avoid pre-declaring your FormGroups` if you can help it. In the next blog post, we will learn a way to construct dynamic super forms with minimal boilerplate....

How to Update the Application Title based on Routing Changes in Angular cover image

How to Update the Application Title based on Routing Changes in Angular

Have you tried to update the document's title of your application? Maybe you're thinking that applying interpolation should be enough: `html {{myCustomTitleVariable}} ` That solution is not going to work since the ` element is outside of the scope of the Angular application. In fact, the root component of your app is within `` tag, and the title is part of the `` element. Luckily, Angular provides the Title service with the methods to read the current title of the application, and a setTitle(title)` to update that value. However, what happens if you need to update the title on routing changes? Also, you may consider updating it on certain components for Analytics purposes. In this blog post, I'll explain step-by-step how to create a custom Title service to have full control over the title of the current HTML document for your application. Project Setup Prerequisites You'll need to have installed the following tools in your local environment: - Node.js**. Preferably the latest LTS version. - A package manager**. You can use either NPM or Yarn. This tutorial will use NPM. Creating the Angular Project Let's assume we'll need to build an application with the following routes as requirements: `txt /home |- Renders a home component /products |- Renders a list of products /products/ |- Renders a product detail based on its Identifier The app redirects to /home path by default ` Now, let's create the project from scratch using the Angular CLI tool. `bash ng new angular-update-title --routing --prefix corp --style css --skip-tests ` This command will initialize a base project using some configuration options: - --routing`. It will create a routing module. - --prefix corp`. It defines a prefix to be applied to the selectors for created components(`corp` in this case). The default value is `app`. - --style css`. The file extension for the styling files. - --skip-tests`. it avoids the generations of the `.spec.ts` files, which are used for testing Creating the Modules and Components Once we got the initial structure of the app, we'll continue running the following commands to create a separate module for /home` and `/products`, which are the main paths of the project: `bash ng generate module home --routing ng generate component home/home ng generate module products --routing ng generate component products/products ng generate component products/product-detail ` The `--routing` flag can be using also along with `ng generate module` to create a routing configuration file for that module. Creating the Title Service Similar to the previous section, we will create a shared` module to hold the `Title` service. Both can be generated with the following commands: `bash ng generate module shared --module app ng generate service shared/services/title ` The `--module app` flag is used to "link" the brand new module to the pre-existing `app.module.ts` file. The Routing Configuration Open the app-routing.module.ts` file, and create the initial routes. `ts // app-routing.module.ts const routes: Routes = [ { path: '', pathMatch: 'full', redirectTo: 'home' }, { path: 'home', component: HomeComponent, data: { pageTitle: 'Home' } }, { path: 'products', loadChildren: () => import('./products/products.module').then(m => m.ProductsModule) } ]; ` By default, the application will redirect to the `home` path. When the router loads the `home` path, a `HomeComponent` will be rendered. The `products` path will be loaded using the _lazy loading_ feature. Pay attention to the data provided to the home` path. It contains the configured title through `pageTitle` string. Next, open the products-routing.module.ts` file to enable an additional configuration to load the _Products_ and the _Product Detail_ page. `ts // products-routing.module.ts const routes: Routes = [ { path: '', component: ProductsComponent, children: [ { path: ':id', component: ProductDetailComponent, } ], data: { pageTitle: 'Products' } }, ]; ` The router will render the `ProductsComponent` by default when the path matches to `/products`. This route also defines custom data to be rendered as titles later. When the path also adds an Id on `/products/:id`, the router will render the `ProductDetailComponent`. The Title Service Implementation It's time to implement the custom Title Service for our application. `ts // title.service.ts import { Injectable } from '@angular/core'; import { Title } from '@angular/platform-browser'; import { ActivatedRoute, NavigationEnd, Router } from '@angular/router'; import { BehaviorSubject, merge, Observable } from 'rxjs'; import { filter, map, tap } from 'rxjs/operators'; const DEFAULTTITLE = 'Corp'; @Injectable({ providedIn: 'root' }) export class TitleService { title$ = new BehaviorSubject(DEFAULTTITLE); private titleRoute$: Observable = this.router.events.pipe( filter((event) => event instanceof NavigationEnd), map(() => this.getPageTitle(this.activatedRoute.firstChild)) ); private titleState$ = merge(this.title$, this.titleRoute$).pipe( filter((title) => title !== undefined), tap((title) => { this.titleService.setTitle(${DEFAULT_TITLE} - ${title}`); }) ); constructor( private router: Router, private activatedRoute: ActivatedRoute, private titleService: Title ) { this.titleState$.subscribe(); } private getPageTitle( activatedRoute: ActivatedRoute | null ): string | undefined { while (activatedRoute) { if (activatedRoute.firstChild) { activatedRoute = activatedRoute.firstChild; } else if ( activatedRoute.snapshot.data && activatedRoute.snapshot.data['pageTitle'] ) { return activatedRoute.snapshot.data['pageTitle'] as string; } else { return undefined; } } return undefined; } } ` The above service implementation could be understood in just a few steps. First, we'll need to make sure to inject the `Router`, `ActivatedRoute` and `Title` services in the constructor. The `title$` attribute contains the initial value for the title("Corp"), which will be emitted through a _BehaviorSubject_. The `titleRoute$` is an Observable ready to emit any `pageTitle` value defined in the current route. It may use the parent's _pageTitle_ otherwise. The `titleState$` is an Observable ready to _listen_ to either `title$` or `titleRoute$` values. In case incoming value is defined, it will call the Angular Title service to perform the update. The `getPageTitle` method will be in charge of obtaining the `pageTitle` of the current route if it is defined or the title of the parent otherwise. Injecting the Title Service One easy way to apply the custom Title Service in the whole application is by updating the app.module.ts` file and injecting it into the constructor. `ts // app.module.ts export class AppModule { constructor(public titleService: TitleService) {} } ` In that way, once the default component gets rendered, the title will be displayed as Corp - Home`. If you click on Go to Products_ link, then a redirection will be performed and the Title service will be invoked again to display `Corp - Products` at this time. However, we may need to render a different title according to the product detail. In this case, we'll show Corp - Product Detail - :id` where the `Id` matches with the current route parameter. `ts // product-detail.component.ts import { Component, OnDestroy, OnInit } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; import { map, Subscription, tap } from 'rxjs'; import { TitleService } from 'src/app/shared/services/title.service'; @Component({ selector: 'corp-product-detail', templateUrl: './product-detail.component.html', styleUrls: ['./product-detail.component.css'], }) export class ProductDetailComponent implements OnInit, OnDestroy { protected subscription = new Subscription(); productId$ = this.route.params.pipe(map((params) => params['id'])); constructor( private route: ActivatedRoute, private titleService: TitleService ) {} ngOnInit(): void { const productIdSubscription = this.productId$ .pipe( tap((id) => this.titleService.title$.next(Product Detail - ${id}`)) ) .subscribe(); this.subscription.add(productIdSubscription); } ngOnDestroy(): void { this.subscription.unsubscribe(); } } ` Let's explain the implementation of this component: The constructor injects the `ActivatedRoute` and the custom `TitleService`. The `productId$` is the _Observable_ which is going to emit the `Id` parameter every time it changes in the URL. Once the component gets initialized, we'll need to _subscribe_ to the `productId$` _Observable_ and then emit a new value for the title after creating a new string using the `id`. That's possible through the `titleService.title$.next()` method. When the component gets _destroyed_, we'll need to _unsubscribe_ from the `productIdSubscription`. We're ready to go! Every time you select a product, the ProductDetail` component will be rendered, and the title will be updated accordingly. Live Demo and Source Code Want to play around with the final application? Just open the following link in your browser: https://luixaviles.github.io/angular-update-title. Find the complete angular project in this GitHub repository: angular-update-title-service. Do not forget to give it a star ⭐️, and play around with the code. Feel free to reach out on Twitter if you have any questions. Follow me on GitHub to see more about my work....

Testing a Fastify app with the NodeJS test runner cover image

Testing a Fastify app with the NodeJS test runner

Introduction Node.js has shipped a built-in test runner for a couple of major versions. Since its release I haven’t heard much about it so I decided to try it out on a simple Fastify API server application that I was working on. It turns out, it’s pretty good! It’s also really nice to start testing a node application without dealing with the hassle of installing some additional dependencies and managing more configurations. Since it’s got my stamp of approval, why not write a post about it? In this post, we will hit the highlights of the testing API and write some basic but real-life tests for an API server. This server will be built with Fastify, a plugin-centric API framework. They have some good documentation on testing that should make this pretty easy. We’ll also add a SQL driver for the plugin we will test. Setup Let's set up our simple API server by creating a new project, adding our dependencies, and creating some files. Ensure you’re running node v20 or greater (Test runner is a stable API as of the 20 major releases) Overview `index.js` - node entry that initializes our Fastify app and listens for incoming http requests on port 3001 `app.js` - this file exports a function that creates and returns our Fastify application instance `sql-plugin.js` - a Fastify plugin that sets up and connects to a SQL driver and makes it available on our app instance Application Code A simple first test For our first test we will just test our servers index route. If you recall from the app.js` code above, our index route returns a 501 response for “not implemented”. In this test, we're using the createApp` function to create a new instance of our Fastify app, and then using the `inject` method from the Fastify API to make a request to the `/` route. We import our test utilities directly from the node. Notice we can pass async functions to our test to use async/await. Node’s assert API has been around for a long time, this is what we are using to make our test assertions. To run this test, we can use the following command: By default the Node.js test runner uses the TAP reporter. You can configure it using other reporters or even create your own custom reporters for it to use. Testing our SQL plugin Next, let's take a look at how to test our Fastify Postgres plugin. This one is a bit more involved and gives us an opportunity to use more of the test runner features. In this example, we are using a feature called Subtests. This simply means when nested tests inside of a top-level test. In our top-level test call, we get a test parameter t` that we call methods on in our nested test structure. In this example, we use `t.beforeEach` to create a new Fastify app instance for each test, and call the `test` method to register our nested tests. Along with `beforeEach` the other methods you might expect are also available: `afterEach`, `before`, `after`. Since we don’t want to connect to our Postgres database in our tests, we are using the available Mocking API to mock out the client. This was the API that I was most excited to see included in the Node Test Runner. After the basics, you almost always need to mock some functions, methods, or libraries in your tests. After trying this feature, it works easily and as expected, I was confident that I could get pretty far testing with the new Node.js core API’s. Since my plugin only uses the end method of the Postgres driver, it’s the only method I provide a mock function for. Our second test confirms that it gets called when our Fastify server is shutting down. Additional features A lot of other features that are common in other popular testing frameworks are also available. Test styles and methods Along with our basic test` based tests we used for our Fastify plugins - `test` also includes `skip`, `todo`, and `only` methods. They are for what you would expect based on the names, skipping or only running certain tests, and work-in-progress tests. If you prefer, you also have the option of using the describe` → `it` test syntax. They both come with the same methods as `test` and I think it really comes down to a matter of personal preference. Test coverage This might be the deal breaker for some since this feature is still experimental. As popular as test coverage reporting is, I expect this API to be finalized and become stable in an upcoming version. Since this isn’t something that’s being shipped for the end user though, I say go for it. What’s the worst that could happen really? Other CLI flags —watch` - https://nodejs.org/dist/latest-v20.x/docs/api/cli.html#--watch —test-name-pattern` - https://nodejs.org/dist/latest-v20.x/docs/api/cli.html#--test-name-pattern TypeScript support You can use a loader like you would for a regular node application to execute TypeScript files. Some popular examples are tsx` and `ts-node`. In practice, I found that this currently doesn’t work well since the test runner only looks for JS file types. After digging in I found that they added support to locate your test files via a glob string but it won’t be available until the next major version release. Conclusion The built-in test runner is a lot more comprehensive than I expected it to be. I was able to easily write some real-world tests for my application. If you don’t mind some of the features like coverage reporting being experimental, you can get pretty far without installing any additional dependencies. The biggest deal breaker on many projects at this point, in my opinion, is the lack of straightforward TypeScript support. This is the test command that I ended up with in my application: I’ll be honest, I stole this from a GitHub issue thread and I don’t know exactly how it works (but it does). If TypeScript is a requirement, maybe stick with Jest or Vitest for now 🙂...