How to turn an Angular app into standalone - Part I
Tags: angular, webdev, tutorial, typescript
Published: 2022-10-20 14:34:00

In this article, we will turn some components in an existing Angular app into standalone components, to see if this feature is worth the trouble. What else do they need to work properly? What scenarios are they best used for? How do they affect development experience? and Output bundles?

The claim:

> **standalone components** provide a simplified way to build Angular applications.

Does it? Let's see.

### Mantra

Watching the [video in Angular docs](https://angular.io/guide/standalone-components) standalone comes across as an app-wise architecture. We create standalone components, and route to them. Those components import other standalone components, and the party goes on. The root app component does not need to be standalone obviously, but it is possible.

Angular today provides a transition to the module-less world, we will investigate the following transitions:

-   Turning a common component into a standalone component
-   Turning a pipe and a directive in a library to standalone
-   Grouping multiple shared components in an importable non-modularized group (is it at all possible?)
-   Extracting a feature component to make it standalone and reusable outside its module
-   Creating a standalone routed component (is it worth it?)

Let's dig. Find the project we are building in [**StackBlitz**](https://stackblitz.com/edit/angular-standalone-app)

Turning an existing component into standalone
---------------------------------------------

According to the documentation, a stand alone component can still be referenced if it is in the Module, but not under `declarations` rather `imports`. So The following example, where we have a `toast` component in the root component.

> We built the toast service previously in [Catching and displaying UI errors with toast messages in Angular](https://garage.sekrab.com/posts/catching-and-displaying-ui-errors-with-toast-messages-in-angular)

The `app.module` looks like this

```ts
// app.module with no standalone
@NgModule({
	declarations: [
	  // the app root
	  AppComponent,
	  // ...
	  // our toast non standalone component
	  ToastPartialComponent
	],
	imports: [
	  // other needed modules
	  BrowserModule
	  CommonModule,
	]
})
export class AppModule { }
```

And in the `app.component` itself:

```ts
// app.component (root component)
@Component({
  selector: 'app-root',
  // add the gr-toast ToastPartialComponent in root
  template: `<gr-toast></gr-toast>
  <router-outlet></router-outlet>`
})
export class AppComponent {
}
```

Let's now adapt the `ToastPartialComponent` to be `standalone`, there are two modifications needed along with it. The first, is that the standalone component needs **its own imported dependencies**, so if we use `ngIf` we need to import `CommonModule`. It becomes like this

```ts
// lib/toast partial
@Component({
	selector: 'gr-toast',
	// make it standalone
	standalone: true
	// first, you need to import CommonModule explicitly
	imports: [CommonModule],
	template: `
		 some code that uses ngIf for example
	`
})
export class ToastPartialComponent {
}
```

The other change is to move the toast component from `declarations` to `imports`, like this:

```ts
// app.module now imports a standalone component
@NgModule({
	declarations: [
	  AppComponent,
	  //...
	],
	imports: [
	  // other needed modules
	  BrowserModule
	  CommonModule,
	  // add the standalone component here
	  ToastPartialComponent
	 ]
})
export class AppModule { }
```

According to Angular developers, this is a good place to **start transitioning slowly.** My take: there is no gain for root components, because we never need to reuse those components. So let's move on to something less root-y, and more leafy.

Standalone common components, pipes or directives
-------------------------------------------------

We shall try three examples in our application. A common partial component, a common pipe, and a common directive.

In a normal application with modules, we usually add our common components to a `SharedModule` and the pipes and directives into the same module, or in their own `LibModule` where all reusable library components sit. So, it initially may look something like this:

```ts
// An example of a shared module in the current app
@NgModule({
  imports: [
    CommonModule,
    // bring in the LibModule which contains
		//the pipes and directives if we need them
    LibModule
  ],
  declarations: [
    // add common components when not standalone
    StarsPartialComponent,
    PagerPartialComponent,
  ],
  exports: [
    StarsPartialComponent,
    PagerPartialComponent,
    CommonModule,
    LibModule,
  ],
})
export class SharedModule {}
```

Let's make a star-rating common component and change it into standalone.

### Turning a common partial component into standalone

To use the star-rating component today, it should be declared and exported in `SharedModule`, which is imported into our feature module, in our case it shall be the `ProjectRoutingModule`.

```ts
// routes/project.route: project routing module:
@NgModule({
  imports: [
    // to use a common component, import the shared module
    SharedModule,
    // ...
  ],
  declarations: [ProjectListComponent, ProjectViewComponent],
})
export class ProjectRoutingModule {}
```

Let's head to our stars component in the `components/common` folder, and turn it into a `standalone` component.

```ts
// common/star.partial.ts
@Component({
  selector: 'gr-stars',
  template: `...`
  // turn into a standalone, and now u can import it directly
  standalone: true,
	// ...
})
export class StarsPartialComponent implements OnInit {
 // ...
}
```

Now we have a couple of options.

-   **First option:** Remove the stars component from the `SharedModule` and import it directly into the `Project` module, or anywhere it needs to be used:

```ts
// first option, move the stars out of the shared module
// core/shared.module
@NgModule({
  imports: [
    // ...
  ],
  declarations: [
    // remove from declarations when it is standalone
    // StarsPartialComponent,
    //...
  ],
  exports: [
    // StarsPartialComponent,
    // ...
  ],
})
export class SharedModule {}
```

Then in the projects routing module, import it

```ts
// routes/project.route
@NgModule({
  imports: [
	  // ...
    // import standalone components
    StarsPartialComponent,
  ],
  declarations: [
		// ...
	],
})
export class ProjectRoutingModule {}

// also import it into any module you need it
```

-   **Second option:** Move the star component into the `imports` array of the `SharedModule` and keep importing the `SharedModule` as you used to. (We just need to **export** the common component if we do not need it anywhere else in the `SharedModule`.)

```ts
// second option: move stars to exports of shared module
@NgModule({
  imports: [ 
    // ...
    // add standalone only if you intend to reuse within shared component
    StarsPartialComponent,
  ],
  declarations: [
    // remove from declarations when it is standalone
    // StarsPartialComponent,
    // ...
  ],
  exports: [
    // keep exporting standalone
    StarsPartialComponent,
    // ...
  ],
})
export class SharedModule {}
```

-   **Third option**: get rid of `SharedModule` and turn it into an exported `const`

This third option is what Angular docs provide as an alternative to exporting multiple standalone components without a module. This rather looks like a hack, but it is aligned with the mantra of going module-free in your future apps. It looks like this:

```ts
// third option: replace shared module with a const
export const SHARED_COMPONENTS = [
	StarPartialComponent,
	PagerPartialComponent,
	// ... all standalone shared components
] as const;
```

Then in our targeted module, like `Project` module:

```ts
// import the const
@NgModule({
  imports: [
    // bring them in
    ...SHARED_COMPONENTS,
  ],
     // ...
})
export class ProjectRoutingModule {}
```

Next week, we will dive into turning the whole app into module-free, and get rid of the `Project` module as well.

### Importing library pipes and directives into existing modules

> This is the biggest win of the standalone feature

Now we are going to get rid of the `LibModule` and turn the directive and pipe in it into `standalone` pipes and directives. This is probably the biggest gain since we are used to grouping simple pipes and directives that are used often into a single module, in order to declare them. With `standalone` we no longer need to declare them. We simply import them whenever we need them. Let's dig

```ts
// in SharedModule stop referencing the LibModule
@NgModule({
 // ...
  exports: [
    // ...
    // standalone, no LibModule any more
    // LibModule,
  ],
})
export class SharedModule {}
```

In our StackBlitz project I created two components to test with: `LetDirective` and `CustomCurrencyPipe`, let's turn into `standalone`

```ts
// LetDirective
@Directive({
  selector: '[grLet]',
  // turn into standalone
  standalone: true
})
// ...

// CustomCurrencyPipe
@Pipe({
  name: 'grCurrency',
  // turn into standalone
  standalone: true
})
```

In our `Project` module, where we want to use those directives and pipes, we import them. How nice!

```ts
// project module now brings in the library items needed
@NgModule({
  imports: [
     // ...
    // bring in pipes and directives needed
    CustomCurrencyPipe,
    LetDirective
  ],
  // ...
})
export class ProjectRoutingModule {}
```

We can use the same trick of exported const, but having them individually is better. So from now on, having a group of pipes and directives and common components is no longer a headache. **Kudos Angular**.

Standalone common feature component
-----------------------------------

The other very useful scenario is "common feature component." Take for example our project on StackBlitz. The `Project` routing module uses a `ProjectCardPartialComponent`. In a large application where a project card needs to be shown in different modules, in the current module-based Angular architecture, we need to move the card into its own module, and import it whenever needed.

```ts
// The project module should contain the card and anything reusable
@NgModule({
  imports: [
    // bring in all modules the card uses
    RouterModule,
    // this one has the Star component
    ...SHARED_COMPONENTS,
    // standalone pipes
    CustomCurrencyPipe,
  ],
  // declare the card
  declarations: [ProjectCardPartialComponent],
  exports: [ProjectCardPartialComponent],
})
export class ProjectModule {}

// and the project route module would import it
const routes: Routes = [
 // ...
];

@NgModule({
  imports: [
    // bring in the project module whenever it needs to be used
    ProjectModule,
    //...
  ],
  // ...
})
export class ProjectRoutingModule {}
```

Let's turn the card component into a standalone, and import it into the project route module directly

```ts
// project route module
@NgModule({
  imports: [
    // ...
    // the project module no longer needed if the card is standalone
    // ProjectModule,
    // but we need the card standalone component instead
    ProjectCardPartialComponent
  ]
  // ...
})
export class ProjectRoutingModule {}
```

The project card itself needs a lot of modules to work. Here is how it looks after adaptation:

```ts
// ProjectCardPartialComponent
@Component({
   //...
  // to turn into standalone
  standalone: true,
  // we need to import everything needed for this component
  imports: [CommonModule, RouterModule,
  // add StarsPartialComponent, or ...SHARED_COMPONENTS, or SharedModule, all work
  StarsPartialComponent,
   // and bring in the currency pipe
   CustomCurrencyPipe],
})
export class ProjectCardPartialComponent {
  // ...
}
```

This is also sweet on the long run, it means less stray modules which serve no purpose other than containing reusable feature components.

Standalone routed component
---------------------------

The last scenario for today is a routed component that is fully standalone. Take for example our `/content/standalone` route. The component uses a pipe, a directive, and some if statement. And let's spice it up by adding other components, some standalone and others. Here is how the standalone component itself looks like:

```ts
@Component({
  templateUrl: './standalone.html',
   // routed standalone component
   standalone: true,
   // needs to import everything it needs, including other standalone components
   imports: [ RouterModule,
   // lets use the pager component from sharedmodule
   // this also has CommonModule
   SharedModule,
   // we can do this for the stars component
   ...SHARED_COMPONENTS,
   // and this for the pipe:
   CustomCurrencyPipe,
   // and also this (standalone components only)
   ProjectCardPartialComponent
  ],

})
export class ContentStandaloneComponent  {
  // ...
}
```

The HTML can have all of the following elements:

```html
<!-- the standalone.html content -->
<!-- CustomCurrencyPipe-->
<p>{{345.25 | grCurrency:'TRY'}}</p>
<!-- ngIf (CommonModule) -->
<div class="box" *ngIf="testMe">
   <h4>Standalone partial common component</h4>
  <gr-stars [rating]="2"></gr-stars>
</div>
<!-- a router link (RouterModule) -->
<a routerLink="/projects">Go to projects</a>
<!-- a non standalone component from SharedModule -->
<gr-pager [isLoadMore]="true"></gr-pager>
<!-- a standalone common feature component -->
<gr-project-card [project]="project"></gr-project-card>
```

The routing module that includes the standalone route, need not reference anything other than the route, no declarations, no modules:

```ts
// content.route
const routes: Routes = [
  //...
  {
    // this is a fully standalone component, it needs no imports in this module
    path: 'standalone',
    component: ContentStandaloneComponent
  }
];

@NgModule({
  imports: [
   // ... no reference to ContentStandaloneComponent
  ],
  declarations: [
    // ... no reference to ContentStandaloneComponent
   ],
})
export class ContentRoutingModule {}
```

Not including any reference to the `ContentStandaloneComponent` in the routing module, is the biggest win for this case. What we essentially did is move all dependencies from the module, to the component. Which means this component can be sitting anywhere in our app, and routed to from anywhere in the app. **This is flexible** and I can think of a couple of use cases where this would be really helpful. But on everyday app building, **it might not be such a great idea**. Modules give us a sense of organization and prevent mix-up between routes.

Conclusion
----------

We went through a quick exercise to change a current angular application into one that uses the `standalone` feature. Here are my takes thus far:

-   If you are developing for production: **WAIT**
-   Good candidates for adoption: directives and pipes organized in a library that you don't use often, or shared components and common feature components that are reusable
-   Not so good places to adopt: routed components, you are better off placing them in a controlled module, root components or components with too many dependencies that are usually passed in from the root module.

Building for SSR and running in browser does not seem to have changed a lot. The bundles have shifted their sizes around. While the `main` bundle dropped in size, the `common` lazy loaded chunk increased. Some other chunks increased in size, and others dropped. As for server side lazy chunks, they all seemed to increase in size. On a big application, I can see the drop of the `main` bundle as a win.

![Build standalone result](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7cvlzg276wz06aarxuz4.png)

Fully standalone app
--------------------

How about a fully standalone app? Given that we are still in development preview, and considering how messy it can get, is it worth it? Let's dig in. Next week. 😴

### RESOURCES

-   [Standalone Angular components docs](https://angular.io/guide/standalone-components)
-   [StackBlitz project](https://stackblitz.com/edit/angular-standalone-app)

{% embed https://garage.sekrab.com/posts/how-to-turn-an-angular-app-into-standalone-part-i %}