Angular 17: Signals, Server-side rendering, and Standalone

We’re not done with Angular 17 updates! Here are a few more updates on Signals, Standalone, and Server-side rendering.

Signals

Signals have graduated from developer preview and are now officially part of the framework, except the effect function that is still being tweaked by the Angular team. A big step for Angular Signals.

Standalone

With Angular 17, a new Angular app is generated with standalone components by default. You can upgrade your Angular CLI to test that out, and if you don’t want to use standalone by default and your new apps, this feature can be turned off in angular.json with the following config:

   "projects": {
    "my-app": {
      "schematics": {
        "@schematics/angular:component": {
          "standalone": false
        }
     Code language: JavaScript (javascript)

Server-side rendering

With Angular 17, you can create an app that uses server-side rendering with a single command:

ng new --ssr

If you want to add server-side rendering to an existing app, you can run:

ng add @angular/ssr

It’s also possible to pre-render an Angular app using:

ng build --prerender

That way, you don’t need Node.js hosting and can use static, pre-rendered HTML files. I’ll explore this more in the newsletter before the end of the year.

On that same topic of server-side rendering, hydration graduated developer preview, which means an app rendered on the server side won’t rebuild its entire DOM in the browser. Instead, Angular will take over that DOM and re-hydrate it with up-to-date data as needed.

What you need to know about ngModules

Angular modules (ngModules) are a source of confusion for Angular developers. This short guide will clarify what they are and how to think about ngModules in general.

Why ngModules?

Angular uses a Typescript compiler and a template compiler that turns our HTML templates into Javascript instructions to render the DOM in a browser.

The Typescript compiler knows how to compile our Typescript code (all our classes) because all dependencies have to be imported in these Typescript files:

Compiling the HTML template for the above component is a different task because *ngIf is not imported anywhere in our Typescript code. Yet, the Angular compiler must know about all directive/component/pipe dependencies of a component’s template to turn all that HTML into DOM instructions.

The solution to that problem was the introduction of ngModules. ngModules expose features meant to be used by the template compiler and does so in a “global” way, in the sense that importing an ngModule once is enough to enable all its code for all components within that module.

That’s why we rarely think twice about using ngFor or ngIf or any pipe: They are all part of CommonModule, which is automatically imported into the AppModule by default:

Do I need to create ngModules in my app?

Most likely, no. The Angular team introduced standalone components as an alternative to ngModules. A standalone component does not need such modules because all its dependencies are listed in the Typescript code itself:

There were only two reasons why you’d need to create your own ngModules in the past:

That’s it. Both of these problems are solved by standalone components, which can be lazily loaded and already bring all their dependencies along, so no ngModule is needed.

What about core, shared, and feature modules?

Those were parts of guidelines created for the sake of consistency within Angular applications. But you don’t need these modules for your application to work. You can still organize your code neatly in folders and sub-folders and not use ngModules. You can even have tidy, short import statements without having ngModules.

Usually, the more ngModules you create, the more work and problems you’re likely to encounter (circular dependencies, anyone?), which is why the Angular team introduced standalone components and already migrated all its internal directives and pipes to standalone. In the long run, ngModules will likely disappear.

Standalone Application and Router config

With this daily post, let’s get back to the new Angular standalone features. So far, we have seen how to create standalone components, add dependencies, and lazy-loading of standalone components.

Since the primary goal of standalone components is to have less NgModules, what about creating an Angular application with no NgModule at all?

Enter the bootstrapApplication function (from @angular/platform-browser). All it needs is the root standalone AppComponent as a parameter:

And that’s it. No AppModule needed. Now you’re probably wondering: How to add some router config?

There’s a provideRouter function for that, along with several router utility functions to configure preloading, guards, error handling, and more:

Last but not least, if you need to use services from a third-party module and don’t want to import the other features of that module (components/pipes/directives), you can also do the following:

As you can see, standalone components have paved the way for many new function-based features (instead of classes/services), and we’ll see even more of those in the coming posts.

Lazy-loading standalone components

Now that we’ve covered both standalone components and lazy-loading Angular modules, we can introduce the concept of lazy-loading a standalone component, which wasn’t possible before Angular 14.

From a syntax standpoint, the only difference is that we use loadComponent instead of loadChildren. Everything else remains the same in terms of route configuration:

Now, one of the benefits of lazy-loading a module is that it can have its own routing config, thus lazy-loading multiple components for different routes at once.

The good news is that we can also lazy-load multiple standalone components. All it takes is creating a specific routing file that we point loadChildren to, like so:

One last cool thing to share today: Along with the above syntax, Angular now supports default exports in Typescript with both loadChildren and loadComponent.

This means that the previous verbose syntax:

loadComponent: () => import('./admin/panel.component').then(mod => mod.AdminPanelComponent)},

Can now become:

loadComponent: () => import('./admin/panel.component')

This works if that component is the default export in its file, meaning that the class declaration looks like this:

export default class PanelComponent

The same applies to loadChildren if the array of routes (or NgModule) is the default export in its file. You can see an example in Stackblitz here.

Adding dependencies to standalone components

Now that we’ve introduced standalone components, you might have tested them and quickly realized that if you start using directives such as *ngIf, or other components, your code doesn’t compile anymore.

That’s because those template dependencies (used only in the HTML template of your component) are not imported in Typescript (yet), so Angular cannot compile your templates. This doesn’t happen when we use modules because our components are declared there. We also import CommonModule by default, which contains all of the primary directives and pipes of the Angular framework.

If we want to import all these features into our standalone components, we can use the import property in the decorator as follows:

And if you want to import just one feature instead of an entire module, you can do that too – but only if that feature is declared as standalone:

This means that the Angular team has modified all Angular directives and pipes to be available as standalone features (see the ngIf source code here as an example).

Of course, we can still import entire modules if needed or list individual template dependencies, which means that all pipes, directives, and components should be listed individually in the imports array:

You can see an example on Stackblitz here.

What are standalone components?

Since Angular 14, any Angular feature (component, pipe, or directive) can be created as “standalone.” This week, we will dive into what standalone components are, what feature they bring to the table, and how to use them.

One quick note before we start: Whenever you see the words “standalone components,” it really means “standalone components/pipes/directives.” Perhaps we should call those “standalone features,” but I’ll stick with the naming convention used by the Angular team so far, so I don’t stand… alone.

What’s a standalone component?

A standalone component is a component that doesn’t belong to any NgModule. It can be imported on its own and used as-is.

For instance, in the past, we might have a ButtonComponent and a ButtonDirective in a ButtonModule (just like Angular Material does). This means that if we want to use ButtonComponent, we have to import the ButtonModule in our AppModule or feature module. This will make both ButtonComponent and ButtonDirective available for use in our app, even if you just use one of those features and don’t need the other.

Standalone components are different. They can be imported in a module just like other modules get imported in the array of imports:

Importing only the features we need is always better for performance, as the build output will be smaller than if we import an entire module of dependencies. So that would be benefit number one of standalone components.

How to create a standalone component?

With the Angular CLI: ng generate component Button --standalone

We can also make an existing component standalone by adding the standalone: true property in the @Component decorator like so:

You can see that example in action here on Stackblitz.