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.

All ng-conf 2023 videos available for free

I talked about ng-conf 2023 quite a bit back in June and just wanted to let you know that all videos from the conference are now available online for FREE. All you have to do is enter your email address here.

Here are my top 5 recommendations from the conference:

Enjoy! The first 4 talks are about 20 minutes long, and the last one is 35 minutes long.

Using setters with @Input()

Following our theme of lifecycle methods with ngOnChanges and ngOnInit, I want to give you another interesting trick to be notified when an @Input value changes.

We’re already familiar with that syntax:

But what if instead of applying @Input on a class property, we used it on a setter:

The input works the same as before, with the advantage of running several instructions when a new value is set. All of that without using ngOnChanges. This approach is practical if you have several side-effects to trigger depending on which input changes, which would be tedious to handle with multiple conditionals:

The setter approach brings more clarity and leaves less room for mistakes:

ngOnInit lifecycle hook

A few days ago, we talked about the ngOnChanges lifecycle hook. We saw that every time an @Input() changes, Angular will trigger ngOnChanges. ngOnInit is similar but runs only once; after all @Input() receive their initial value. The official documentation puts it like this:

The above definition might surprise you because there is a widespread belief that the purpose of ngOnInit is to run all initialization tasks, even though the class constructor runs before ngOnInit, as explained a few months ago in this entry: ngOnInit vs. constructor in Angular.

As a result, using ngOnInit is only 100% justified when you want to run some code that needs the value(s) of @Input() properties of your component/directive. In other scenarios, you can use a constructor to run your initialization code sooner.

How to create custom pipes?

Earlier this month, we covered how to create custom directives. Today, let’s tackle how to create custom pipes. Pipes help format data such as strings, dates, numbers, etc. The first thing to do is to use the Angular CLI:

ng generate pipe [name]

or ng g p [name]

It’s also possible to generate standalone pipes with the --standalone option:

ng generate pipe [name] --standalone

This creates a class with a single method transform to implement:

By default, the types are all set to unknown, as Angular CLI cannot guess what kind of data we want to format. As we know what we want to format and how, as well as how many arguments our pipe can accept, we can change that signature to make it more meaningful as follows:

Most pipes have optional parameters, so it’s always a good idea to have a default value for each, as I did in this example with displayPrefix = true.

Assuming we change the name of the pipe in the decorator to personName, such a pipe can safely be used as follows:

Or, if we want to turn off the prefixes, we can use our optional parameter and set its value to false:

Displaying an overlay with Angular

We covered how to display a dialog in two lines of code earlier. Today, I want to illustrate how we can show an overlay on any portion of the screen using the Angular CDK.

First, it’s important to mention that Angular CDK is NOT part of Angular Material, and you can use the CDK independently from any component library.

To install the CDK: npm install @angular/cdk

Then, you would import the OverlayModule wherever you need it (here in a standalone component):

This module allows you to inject an Overlay service that can be used to create an overlay and display a component in it:

By default, overlays have a transparent background, so I added a panelClass for my overlay and then gave it a background color – Note that you also need to import the CSS styles from the CDK in your global CSS file:

There are plenty of configuration options, and if you want to see a different approach, look at this tutorial for a dropdown built with an overlay. The source code for my basic example is here on Stackblitz.

ngOnChanges lifecycle hook

ngOnChanges is an Angular lifecycle hook that is called whenever one or more of the bound input properties of a component changes. This hook helps update the component’s state or view in response to changes to the input properties.

To use it, implement the OnChanges interface as follows:

The above ngOnChanges method will run every time the parent component updates the value of name. We can get more information about what changed using the parameter of that method, an object that maps each changed property name to an SimpleChange object. The SimpleChange object contains the following properties:

  • previousValue: The value of the property before it changed.
  • currentValue: The value of the property after it changed.
  • firstChange: boolean value, true if it’s the first change made to that input, false otherwise.

As an example, the above code would output that object to the console as follows:

You can find that example in action on Stackblitz. The most common real-life example of using ngOnChanges is to be notified when an object ID changes so the component can request data for that ID using a service that makes an HTTP request, for instance.

Best practices for using visibility modifiers

Yesterday, we talked about Typescript visibility modifiers in the context of Angular classes. Today, I want to focus on best practices and common scenarios that involve such visibility modifiers.

First, it’s very common to have Angular services that use RxJs Subjects. Subjects are somewhat dangerous because they can be used to emit data to all subscribers. As a result, it makes sense to “hide” a subject by making it private and then expose an Observable out of it as follows:

Note that we don’t need to make data$ public, because any field that isn’t private or protected is public by default. Using readonly enforces that other components/services cannot assign a new value to data$. As a result, the above code is safe: The business logic that decides when and how to emit data is “hidden” in our service (where it belongs), and yet the rest of the application can be notified when the data changes without being able to break that process by accident.

Of course, the same idea applies to Signals (see my best practices for Signals)

Another option suggested in that post is to define a getter method that returns the read-only object:

This is equivalent to using readonly. The value can’t be changed because we define a getter and no setter.

Let’s complete our best practices list from yesterday:

  1. Make every member private by default
  2. If the member is needed in the template of a component, make it protected
  3. If the member is meant to be fully public, go with public
  4. If the member should be accessible but not modifiable, use readonly or a single getter with no setter

Typescript visibility modifiers

Angular is mostly about Typescript classes, and Typescript classes have modifiers that alter the visibility of class members: public, protected, private, and readonly. Here is what you need to know about them.

Everything is public by default

If you don’t use a modifier on a class member, it is public by default. Any other class can see and use that member and change its value:

private means that the member isn’t visible outside of the class (including a component’s HTML template)

private is a way to enforce that other classes cannot access a member of your class. Used in a component, this indicates that we do not want that property to be used in the component’s HTML template. In a service, this suggests that we don’t want other components/services to see that member:

protected is in-between public and private. It makes the member accessible in a component’s template without making it fully public.

In the following example, our component property date is invisible from other Angular code in our app, but our HTML template can use it:

Best practice – My recommendation

If you want to stick to simple rules that make sense and are the safest, here’s what you can do:

  1. Make every member private by default
  2. If the member is needed in the template of a component, make it protected
  3. If the member is meant to be fully public, go with public

Tomorrow, I’ll add a couple more suggestions by introducing the readonly modifier. Stay tuned!