model() for signal-based 2-way data bindings

Angular 17.2 brought more features to the signal-based components approach, where almost everything in our components will become signals. We’ve already covered that inputs can be signals since Angular 17.1.

As an Angular developer, you’ve probably used ngModel in the past to create two-way data bindings. You might have created your own 2-way bindings using the approach covered in this tutorial, too.

All of that becomes even easier with Angular 17.2, as we can now do the following using the model function:

The above syntax means that we can pass a value to NameEntryComponent‘s age property as follows:

We can even have a 2-way binding (meaning something that acts as both an input and an output) by using the “banana in a box” syntax as follows:

This gets even better as we can specify that a value is required for that model input using model.required:

Since age and firstName are signals, we can update their values using set or update:

Where increaseAge is a method that uses an arrow function (as those aren’t supported in Angular templates):

You can see this example in action on Stackblitz here.

We can pass data to models using regular component properties:

But it gets even better as we can also use signals as-is with these 2-way bindings:

You can see this new example in action on Stackblitz here. Next week, I will cover 4 more functions available since Angular 17.2, all signal-based as well!

Angular 17: Simplifying style(s)

A major focus of the Angular team over the past two years has been to improve the developer experience by removing some of the common annoyances in the framework (better error messages, for instance). This is often referred to as “quality of life” improvements, and the Angular 17 feature I want to cover today is undoubtedly one of those.

Have you ever wondered why the component decorator always had a styleUrls or styles attribute even when 99.9999% of the time, we need one style URL or CSS style?

Well, I have good news for you! Angular 17 introduced a styleUrl attribute (singular) for a single style file:

And the other styles attribute has more flexibility as well as it now supports a single string or an array of strings, which means all options are available now:

A simple change that makes our life easier!

Dependency Injection: ElementRef

We use dependency injection daily to inject services in our Angular “objects,” such as components, directives, pipes, and other services.

Angular does offer other kinds of injectables, though, and one of those is the ElementRef. Its primary purpose is to give us direct access to the native DOM element of a component or a directive.

Here’s a code example (you can test it in Stackblitz here):

The above code changes the background color of the HTML element to red. It’s important to note that the above implementation is not ideal. We should use Angular data bindings for such a use case, which are a lot more readable (code example in Stackblitz here):

The Angular team doesn’t recommend using this API either:

So when should we use ElementRef? Just like markForCheck, it’s a last resort option, and we should use it when everything else fails. The only use case I would consider for ElementRef is when I have to interact with a non-Angular 3rd-party library, such as a chart or a map library, after carefully checking that Angular wrappers are not available on npm (or such wrappers do not pass my tests for good, reliable dependencies)

ChangeDetectorRef.markForCheck()

The following method call is either something you’re either very familiar with or something you’ve never heard about: ChangeDetectorRef.markForCheck()

If you’ve never heard about it, congratulations! It means you’ve been using Angular in a way that doesn’t require you to mess with change detection, and that’s an excellent thing.

On the other end of the spectrum, if you see that method in many places in your code, your architecture might have a problem. Why is that? As we covered before, there are two types of change detection: Default and onPush.

When we use onPush, we’re telling Angular: “This is a component that relies on @Input values to display its data, so don’t bother re-rendering that component when @Input do not change”.

The thing is, sometimes people start using onPush full of good intentions. Then, they start injecting services into their component, and when the data in those services change, the component does not re-render… So, instead of disabling ChangeDetection.OnPush (or figuring out a better way to keep their component as a presentation component), they go to StackOverflow, where people say: Just use ChangeDetectorRef.markForCheck(), and problem solved! Here’s a typical screenshot of such a response:

That’s because ChangeDetectorRef.markForCheck() does exactly that: It’s a way to tell Angular that something changed in our component, so Angular must check and re-render that component as soon as possible. As you can guess, this is meant to be an exception and not the rule.

The main reason why we would use ChangeDetectorRef.markForCheck() is if we integrate a non-Angular friendly 3rd party library that interacts with the DOM and changes values that Angular cannot see because Angular doesn’t manage that DOM. In other scenarios, we should avoid using it and think about a better architecture.

How to handle drag-and-drop with Angular?

Drag and drop features can dramatically improve the UX of a web application. Implementing drag and drop with Javascript seems intimidating, which is why we have the Angular CDK (Component Development Kit).

Let’s say we have a list of items we want to be able to reorder using drag and drop:

Thanks to the CDK drag-and-drop directives, such a task is easy. Here’s all the code needed to enable it:

The two directives cdkDropList and cdkDrag enable the drag-and-drop feature. We also need the following code in our component class:

The moveItemInArray function is a utility function from the CDK, so we don’t have to worry about that.

See the source code and try this example in action on Stackblitz here.

Increasing binding specificity

Let’s say we want to trigger an action when the user hits enter on their keyboard. We could do something like this, capturing the keydown event and then decide what to do based on the entered key:

While the above works, it’s pretty verbose. Instead, we can make our binding more specific by doing the following:

Now, that’s a lot more specific and less verbose. The same goes for class bindings. Here’s a lengthy example:

Since all we want to do is toggle the green class on or off based on a boolean expression, the following syntax is shorter and better:

You can see these examples in action on Stackblitz.

Angular 16.2: NgComponentOutlet input bindings

Another feature of Angular 16.2 is using input bindings with component outlets. You might not be familiar with ngComponentOutlet in the first place, so let’s explain what the directive does.

Let’s consider the following template syntax:

The above code would load a dynamic component into the ng-container. Assigning a new component type to componentTypeExpression would display it in that container. Here’s a basic example that shows a HelloComponent in the component outlet:

This can be helpful if you need to use different types of components and don’t want to (or can’t) use the router to make it happen.

The new feature of Angular 16.2 allows us to pass input values to that component. Let’s assume that HelloComponent looks like this:

We can bind a value to the name input as follows:

And that does the trick! You can see an example in action on Stackblitz here. inputs is an object that can have as many keys as needed. In my example, there is only one: name.

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.

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.