Recap of ng-conf 2024

Ng-conf 2024 wrapped up a few hours ago, and the event was filled with great news for Angular enthusiasts. First, the forecast for the following few versions looks very bright:

The Angular team went on to reveal more of these expected features by showing what they’re currently working on:

For the first time, the team also mentioned a future where Angular would run “without Zone.js and RxJs.” Though we knew that zoneless was in the plans for quite some time, it looks like the very positive reception of Angular Signals has pushed the team to envision removing RxJs as a dependency.

The surprising Wiz announcement

On that same topic of Angular Signals, the Angular team made a surprise announcement of their close collaboration with Wiz, a framework used internally at Google for most public apps that everyone knows about: Youtube, Google Photos, Drive, Gmail, etc. That collaboration started with signals, which are shared between Wix and Angular. The big announcement was that Angular Signals are now used in 100% of Youtube, which is quite impressive considering that some people were thinking that Angular was dead not too long ago.

On that same topic, the Angular team revealed their internal motto: build Angular for the next ten years and ensure that it is still an excellent choice ten years from now. Now that sunny forecast makes perfect sense!

Server-side rendering is all the rage

37% more apps have been using server-side rendering since the launch of Angular v17. It’s the topic I talked about at the conference and will be the topic of one of my upcoming free monthly online workshops. In the meantime, you can read my Angular server-side rendering tutorial here.

The great news is that the Angular team is working with Wiz on that topic as well to implement partial hydration soon, which will give us more options on server-side rendering and lazy-loading with defer.

Component authoring improvements

The Angular team is also working on improving the Angular component syntax by possibly removing a lot of boilerplate:

No more selector, no more imports array, no more standalone: true! Again, this is just an idea at this point, but let’s remember that signals were a similar idea not too long ago and are now a fully available API in the framework.

As a recap, there’s much to be excited about for us Angular developers. I will cover more updates in this newsletter over the next few weeks, as there is more to unpack from these two conference days.

You can watch a replay of the main keynote here on YouTube (powered by Angular Signals!)

Code challenge #3 solution

Before I dive into the solution of code challenge #3, I wanted to mention that my next FREE workshop is scheduled for April 4th. It will be all about getting into RxJs from scratch, and you can register here. I’ll start from the basics and move on to more advanced topics, such as subjects and operators, and you’ll be able to ask me questions as we go through the content and exercises together.

As always, it’s a free workshop, but your coffee donations are always welcome.

Let’s get into our code challenge solution. The goal was to “connect” two different dropdowns so that the selection of the first dropdown (a continent) would update the contents of the second dropdown (a list of countries for that continent):

The challenge is that countries depend on two different Observable data sources:

  1. An HTTP request to an API to get the list of all countries in the world
  2. The currently selected continent (a reactive form select dropdown)

In other words, we start with the following code:

Our goal is to combine data from both Observables into one result. Ideally, such a result would be updated whenever any of the Observable sources are updated (e.g., a new continent is selected by the user, or a new list of countries is available from the API).

As a result, we can use the withLatestFrom operator to make that “connection” between our two Observables:

Such an operator returns an array of all the latest values of our source Observables in the order in which they were declared. As a result, we can use the map operator to receive that array and turn it into something different:

Inside that operator, we decide to return a new array. The continent remains the same, and we filter the list of countries based on that continent:

FInally, we can use tap to run some side effects. In our case, we update the list of countries used by the dropdown, and we set the country dropdown value to the first country of that list to ensure that a country from a previous continent doesn’t remain selected:

And that’s it! You can see that code in action on Stackblitz here.

A few of you sent me their own solutions to the challenge, and a recurring theme was that most of you didn’t “connect” the Observables and just assumed that the list of countries would already be downloaded and available by the time the user selected a continent. Something along those lines, where this.countries would be received from the API before that code runs:

While the above code would work most of the time, if a user selects a continent before this.countries is defined, an error would be thrown and the corresponding subscription destroyed, which means the above code would not run anymore when the user selects a new value. As a result, using operators is safer.

Another trick in my solution is that I didn’t use any .subscribe in my code, which means I don’t have to unsubscribe, as I let the async pipe do so for me automatically:

That’s it for code challenge #3! Next week, I’ll send you updates from ng-conf in Salt Lake City. If you’re around, feel ree to come say hi!

Code challenge #3: RxJs Wizardry

It’s been a few months since our last code challenge, where the goal is to have you look at some code that isn’t working and make you improve your Angular skills along the way.

In this code challenge #3, we have two select dropdowns that have to work together so that when we select a continent, we can only see countries from that continent in the second dropdown. The current code isn’t working – Argentina isn’t in Europe, so this selection should not be possible:

You can fork that code from Stackblitz here. Feel free to email me your solution once you have one ready. I’ll send you a possible solution in next week’s newsletter.

In case you missed my previous code challenges, here they are, along with the link to their respective solutions:

viewChild() and contentChild() for signal-based queries

After introducing model() for signal-based 2-way bindings, let’s dive into viewChild() and contentChild() for signal-based queries.

In case you’re not familiar with Angular queries, they existed before Angular 17.2 as the @ViewChild and @ContentChild decorators.

ViewChild is a way to get a reference on an HTML within the current component’s template. For instance, if we need our Typescript to access the instance of a given child component, I can use @ViewChild like so:

The new syntax gives us the same feature as a Signal:

The same goes with ContentChild. The difference between ViewChild and ContentChild is that ViewChild looks for an element inside the component’s template, whereas ContentChild looks for an element projected by the parent component into the ng-content element of the current component using content projection. You can learn more about content projection with this tutorial:

The above code will look for an element with a template reference variable called test in the projected ng-content.

We can achieve the same feature with the new contentChild() function, which returns a Signal:

Differences between these new functions and the old decorators

These new functions bring a few extra interesting features. For instance, we can get rid of possibly undefined values by making a query required:

Decorator-based queries were satisfied once the component was fully initialized, which means that running side effects required the use of specific lifecycle methods:

With signal-based queries, we receive a signal so we can rely on computed() or effect() to run such side-effects:

This approach is less intrusive and requires knowing just one thing: Signals! No more lifecycle methods are needed. You can find these different examples in action on Stackblitz here.

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!

All about environment variables with Angular

Angular apps created with the Angular CLI used to have automatic environment creation built-in for new projects. While this automation was removed in recent years to simplify the learning curve of Angular, it is still possible to add support for different environments using the command:

ng generate environments

Before we generate such environments, it’s important to take a look at angular.json in the root folder of any Angular project, more specifically, the section called configurations in the build section:

This tells us that our project has two different configurations: production and development, and the default config for the ng build command is production.

Note that the serve section of angular.json has a similar config that extends the build targets and overrides the default configuration by making it development, which, as a result, is what we use when we run ng serve:

If we want to override the default configurations when running ng serve or ng build, we can do this by adding a configuration flag to the command and making it point to the proper configuration name, for instance:

ng serve --configuration=production

ng build --configuration=development

How do we plug environment variables into this?

When we run ng generate environments, the Angular CLI does two things:

It creates two new files in src/environments: environments.ts (the environment for production, which is also the default) and environment.development.ts for development.

angular.json gets updated with the following highlighted syntax:

As you can see, this instructs the builder to replace the production environment file with the development file when we use the --configuration=development flag.

Such environment files can contain any environment variables we want. For instance, say I want to define a SERVER_URL that is different for production and development. I can do that:

Now, to use such environment variable in my code, all I need to do is import it as follows:

Important: The import has to be from /environments/environment. Angular will pick the correct configuration based on the flag given to the build or serve command.

How do you create environments for QA, pre-prod, and more?

Now that we know that angular.json is the file where all this magic happens, if we want to create a qa environment, we can head to the configurations section of build, add a new configuration block with the name q (or anything you want, of course), and make the file replacement to a new file that I call environment.qa.ts:

Then, I can create that environment file under src/environments and add any variables I need in it:

And that’s it! We covered how to create environment variables for production and development and how to create other custom environments we might need.

5 tips for Strictly Typed Reactive Forms

Forms can be a very complex part of any web application, and today, I will cover some useful tips and tricks for using Reactive Forms with strict types.

  1. Use nonNullable: true to restore default values on reset

Form controls and groups are nullable by default in Angular, which means that the following example of FormControl value isn’t of type string:

Instead, if you try to read email.value, you’ll see that the type is string or null. Why would that be the case since we have a default value? That’s because the form can be reset with a <button type="reset"> on the form or by calling email.reset(), for instance.

The trick then is to make that control non nullable by adding the following option:

Now, when the form gets reset, email.value is equal to "test2@gmail.com", which is perfect in several different form scenarios, such as an edit form, where we don’t want to “lose” the previous values, but just “reset” to those values.

2. Using non-nullable form controls at scale

If we want to apply that same config to several form controls, we can put them in a FormGroup and pass that same nonNullable: true option to the FormGroup constructor instead of specifying it for every single control. But what if we use the FormBuilder service?

Then we can use the following syntax:

3. How to define the type of a FormControl with no default value?

If we do something like this:

We’re in trouble because Typescript will infer the type of password.value is null. Instead, we use generics and a union type to specify the expected type for that value, here string or null:

4. How to read the values of disabled form controls?

In complex forms, it is very common that some controls are enabled/disabled based on some condition. For instance, if the user selects the country “USA, ” we might enable a state dropdown with the US states in it and disable it for other countries.

When a control is disabled, its value doesn’t appear as part of the form group value. Here is an example where age is obviously equal to 21, but since that control is disabled, formGroup.value doesn’t return it:

The workaround here is to use formGroup.getRawValue(), which will return everything, including disabled control values:

You can see that example in action on Stackblitz here.

5. How to handle dynamic forms where we don’t know which controls will be present ahead of time?

In some cases, forms can be completely different depending on the type of user, country, etc. As a result, we don’t know whether a control will be present or not at runtime. We can define that uncertainty in Typescript using the optional operator ? when we define the type of our form:

In the above code, password is defined as optional in our interface, meaning that the compiler will not throw an error if we remove that control later.

If your form is 100% dynamic and handling proper types for each scenario would be a nightmare, you can use the more flexible FormRecord instead of FormGroup:

What’s new in Angular 17.1?

Angular 17.1 brings new features to the framework and goes one step further towards signal-based components with the introduction (as a developer preview) of Signal inputs.

What’s a Signal input?

Instead of using the @Input decorator, we can now use the input() function from @angular/core. This function creates an input value that is received as a signal. Here is an example:

Since name is a signal, it can be read like so:

There are several benefits to that approach. For instance, you can derive a value from that input without using ngOnInit or ngOnChanges. Instead, we can use computed:

You can see the above code in action on Stackblitz.

The new API also supports required inputs with the input.required function to ensure a value is passed to the component/directive:

Router

We can now pass information to a route using the info option as follows:

Or using the router service:

This info object is not persisted in the browser session history, unlike the state option, which can also be used to pass data to a route.

Application builder migration

If you still need to migrate to the new Vite-based application builder, you can do so with one simple CLI command now:

ng update @angular/cli --name=use-application-builderCode language: CSS (css)

Unit tests without a browser

Another exciting update is an addition to the ng test command so you can run your unit tests without opening a browser, which is perfect for continuous integration, for instance:

ng test --no-browsers

These are the most essential changes in Angular 17.1. I’d say it’s a pretty good release!

Need your input + some goodies

Welcome to the first edition of the Weekly Angular Newsletter! With this new weekly format comes the option to do things differently, such as more in-depth tutorials, code challenges, or audio/video content.

As a result, I would like to collect your input by filling out this form. That way, I’ll know what content works best for you, which topics you want me to cover, and in which format. I pre-selected a few options in the form, but feel free to submit completely different ideas if needed. Thanks a lot!

Free conference tickets, anyone?

I’ll speak at DeveloperWeek 2024 soon, in-person in the San Francisco Bay Area and Online a week later. The organizers allowed me to share free tickets with you so you can attend the conference in person or remotely. You can register here for free.

DeveloperWeek is a generalist developer conference with multiple tracks and topics: AI and machine learning, web, Dev Ops, APIs and micro-services, and more. My talk will be about Cypress, and you can also register for my free online workshop for similar content.

Finally, I’m also speaking at ng-conf 2024 this year! I have ONE $225 discount code to give away, and the first person to reply to this email will get the code so they can enjoy a discounted ticket to the best Angular conference in the world!

My talk at ng-conf will be a 2-hour workshop on server-side rendering with Angular! I hope to see many of you there. In the meantime, don’t forget to give me your input, claim a free conference ticket, and get ready for next week’s post!

Last post of the year!

Congratulations for making it to the end of year one of the Daily Angular Newsletter! This is the 260th entry of the year, which means I managed to publish something every single working day of the year. That was a lot of work, so In 2024, I’m moving to a weekly format, which means we get a new logo:

If you want to receive these weekly emails, you’re already in if you received my daily emails. If you were not receiving the daily emails, you can subscribe here to receive the weekly newsletter. If you want out, there is an unsubscribe link at the end of each email.

Last but not least, I want to thank you for reading these daily messages. Your feedback has been fantastic all year, so I signed up for a second year with a new cadence!

I also introduced links so you can support my work by buying me a coffee; several of you have used it this past month, which is excellent and is pushing me to create more free content, starting with another online workshop on component testing in January.

Happy New Year, and see you next week for the first edition of the Weekly Angular Newsletter!