DEV Community

Cover image for Local Component Template Variables with @let
Brian Treese
Brian Treese

Posted on • Edited on • Originally published at briantree.se

Local Component Template Variables with @let

In Angular, we can now create variables for reuse right within our component templates. Now that might seem odd but it’s actually pretty cool. If you’re like me, you may have a hard time understanding the benefits at first. So, in this example, I’ll show you how to create these template variables, and then I’ll show you several different possible use cases and benefits to help you better understand why you may want to use them in your projects.

The Anatomy of the @let Syntax

To create a template variable we need to add the @ symbol, followed by the word “let”. The variables set with this @let syntax are a lot like variables set using JavaScript’s let declaration. So next, we add one or more whitespaces followed by a variable name, and then one or more whitespaces again. Then, as with JavaScript variables, we set it using the equals sign followed by a valid JavaScript expression. This expression can be single or multi-line expression, and then we terminate the expression with a semicolon.

@let name = expression;
Enter fullscreen mode Exit fullscreen mode

So that’s how we create them, now let’s look at a few use cases.

A Simple String Variable Example

Let’s start with a basic example to see it in action. In our demo application's app header component, we’re going to take the brand name “Petpix” and set it to a variable.

To do this, let’s create the variable using the @let syntax, let’s call it “name”. Then, let’s set it to the string “Petpix”, and then we just need to add a semicolon. And to use this variable, in this case we can just string interpolate the value.

Before:

<h1>
    <svg ..></svg>
    Petpix
</h1>
Enter fullscreen mode Exit fullscreen mode

After:

<h1>
    <svg ..></svg>
    @let name = 'Petpix';
    {{ name }}
</h1>
Enter fullscreen mode Exit fullscreen mode

Now if we were to save we would see that nothing changed which is a good thing right? It means that this variable has been set correctly and is working just as we’d expect.

Now this is just a simple example to demonstrate how this syntax works. The only reason I could see to make a variable for something this simple is if this brand name were used in more than one place in this template. So, just keep that in mind.

Ok, how about a little bit more of an advanced example?

Simplifying Async Pipe Subscriptions With @let

This new syntax can help simplify usages of observables that use the async pipe. For example, if we open up our slider component template, we have a “selectedImage” observable used to provide the image description to the description form component.

slider.component.html

@if ($selectedImage | async; as image) {
    <app-description-form [description]="image.description"></app-description-form>
}
Enter fullscreen mode Exit fullscreen mode

This observable is updated every time we navigate to a new image. We’re using the async pipe here in order to easily update the view when the observable emits with a new value. And we are also using this same variable and async pipe to conditionally display the location the photo was taken in.

slider.component.html

@if ($selectedImage | async; as image) {
    @if (image.location) {
        <h3>Photo Location</h3>
        <address>
            {{ image.location.city }},
            {{ image.location.state }}
            {{ image.location.postalCode }}
        </address>
    }
}
Enter fullscreen mode Exit fullscreen mode

Now, in the latest versions of Angular, you may find yourself using observables with the async pipe much less often because we can easily convert them to signals with the new toSignal() function. And if you’re doing this, this example won’t really provide much value for you.

But for those of you who are still using this configuration, we can simplify it a little with the @let syntax.

Let’s add a new variable and we’ll add it outside of the “isAnonymous” condition. Let’s call it “image”, and let’s set it to our observable with the async pipe. Then let’s add the nullish coalescing operator and if it’s undefined, we’ll fallback to the first image in the list.

slider.component.html

@let image = ($selectedImage | async) ?? images[0];
Enter fullscreen mode Exit fullscreen mode

And now we can remove this condition wrapping the description form because we’ll always have an image and description with how we’ve set our variable up.

Before:

@if ($selectedImage | async; as image) {
    <app-description-form [description]="image.description"></app-description-form>
}
Enter fullscreen mode Exit fullscreen mode

After:

<app-description-form [description]="image.description"></app-description-form>
Enter fullscreen mode Exit fullscreen mode

We can also eliminate this condition wrapping the location since we have our new variable.

Before:

@if ($selectedImage | async; as image) {
    @if (image.location) {
        <h3>Photo Location</h3>
        <address>
            {{ image.location.city }},
            {{ image.location.state }}
            {{ image.location.postalCode }}
        </address>
    }
}
Enter fullscreen mode Exit fullscreen mode

After:

@if (image.location) {
    <h3>Photo Location</h3>
    <address>
        {{ image.location.city }},
        {{ image.location.state }}
        {{ image.location.postalCode }}
    </address>
}
Enter fullscreen mode Exit fullscreen mode

So that’s definitely better than it was before but you just may not really need this depending upon your code base. So let’s look at another example.

Simplifying Repetitive Complex Logic with @let

With @let we can clean up repetitive and complex template logic making things easier to read, understand, and maintain. Let’s look at our slider component template again.

Here we have a pretty long, ugly condition:

slider.component.html

@if (!isAnonymous && images.length > 1 && accountType === 'paid') {
    <nav>
        ...
    </nav>
}
Enter fullscreen mode Exit fullscreen mode

We check to make sure the user is not anonymous, we check that the list length is greater than one, and we check that the account type is “paid” before we show the buttons to navigate the gallery. Now, not only is this a long, ugly condition, this exact condition is used to conditionally display the description as well.

slider.component.html

@if (!isAnonymous && images.length > 1 && accountType === 'paid') {
    <app-description-form [description]="image.description"></app-description-form>
}
Enter fullscreen mode Exit fullscreen mode

Well, this is another great use for the @let syntax. Let’s make this condition a variable. Let’s add it at the top of the template. We’ll call it “advancedFeatures”.

slider.component.html

@let advancedFeatures = !isAnonymous && images.length > 1 && accountType === 'paid';
Enter fullscreen mode Exit fullscreen mode

Ok, now we just need to update both of those conditions to use the new variable.

Before:

@if (!isAnonymous && images.length > 1 && accountType === 'paid') {
    <nav>
        ...
    </nav>
}
...
@if (!isAnonymous && images.length > 1 && accountType === 'paid') {
    <app-description-form [description]="image.description"></app-description-form>
}
Enter fullscreen mode Exit fullscreen mode

After:

@if (advancedFeatures) {
    <nav>
        ...
    </nav>
}
...
@if (advancedFeatures) {
    <app-description-form [description]="image.description"></app-description-form>
}
Enter fullscreen mode Exit fullscreen mode

So, this can be a great way to clean up repetitive and complex logic in the template.

Creating a Dynamic Variable Based on a Form Control Value with @let

We can also do cool things like create dynamic variables based off things like the values of form controls. For example, let’s take a look at our description form component.

Here we have a textarea with its value bound to a description input:

description-form.component.html

<textarea
    ...
    [value]="description()"></textarea>
Enter fullscreen mode Exit fullscreen mode

Well, we can create a variable based on the value of this field. To do this, we first need to add a template reference variable on the textarea itself.

description-form.component.html

<textarea
    ...
    #textarea
    [value]="description()"></textarea>
Enter fullscreen mode Exit fullscreen mode

Then, let’s add a new variable called “descriptionVal”. We’ll set this variable accessing the value property off of the reference variable for the textarea. Then we can output the string interpolated value of this variable.

description-form.component.html

<p>
    @let descriptionVal = textarea.value;
    <strong>Value:</strong>
    {{ descriptionVal }}
</p>
Enter fullscreen mode Exit fullscreen mode

Ok, now let’s save and take look:

Example of the output of the variable created using the dynamic value of a textarea

Cool, now we can see the description value repeated out here. That’s nice right? But what’s even more cool is that we can type in this textarea and, when we do, we see the value change as we type.

Example of a dynamic variable based on the value of a textarea updating as we type in the textarea

Very cool. So that may come in handy for you in certain situations.

Signal Type Narrowing with @let

Something else that we can do is use the @let syntax for type narrowing on signal values.

What exactly does this mean? Well, Let’s take a look at an example.

Back in the example for our slider component, we actually already have a signal for the selected image that we can use instead of the observable. So, we can eliminate the image variable that we added earlier and instead set the description with our “selectedImage” signal.

Before:

@let image = ($selectedImage | async) ?? images[0];

@if (advancedFeatures) {
    <app-description-form [description]="image.description"></app-description-form>
}
Enter fullscreen mode Exit fullscreen mode

After:

@if (advancedFeatures) {
    <app-description-form [description]="selectedImage().description"></app-description-form>
}
Enter fullscreen mode Exit fullscreen mode

Now what we can do is create a “location” variable. We’ll set this variable based on the "selectedImage" signal instead, accessing the location object off of that signal. This allows us to simplify this address and also allows us to eliminate the conditional check on each of these as well.

Before:

@if (image.location) {
    <h3>Photo Location</h3>
    <address>
        {{ image.location?.city }},
        {{ image.location?.state }}
        {{ image.location?.postalCode }}
    </address>
}
Enter fullscreen mode Exit fullscreen mode

After:

@let location = selectedImage().location;

@if (location) {
    <h3>Photo Location</h3>
    <address>
        {{ location.city }},
        {{ location.state }}
        {{ location.postalCode }}
    </address>
}
Enter fullscreen mode Exit fullscreen mode

So overall, this syntax can really help simplify a lot of different things right within component template. And this is pretty handy, but there are a couple of things to be aware of.

Variables Using @let Syntax Can’t be Reassigned

First, unlike JavaScript variables set using let, these template variables are read-only and cannot be reassigned.

Now, they will update when the view changes like what we saw when the description input changed on our description form, but if we try to reassign the variable directly, we’ll get an error.

For example, if we try to redeclare the “advancedFeatures” variable in our slider component, we won’t be able to. Let’s go ahead and set it to false right after its original declaration.

slider.component.html

@let advancedFeatures = !isAnonymous && images.length > 1 && accountType === 'paid';
@let advancedFeatures = false;
Enter fullscreen mode Exit fullscreen mode

Now let’s save and see what happens:

Example of an error when trying to reassign a variable using the @let syntax

Well, we get an error letting us know that we can’t declare this variable because it already exists. So that’s something to be on the look out for.

Variables Using @let are Scoped

Another thing to be aware of is that variable scope matters. Variables defined with @let can only be accessed by the current view and its descendants. They cannot be access by any parent.

An easy way to see this is to add a variable within an @if block. Let’s add a test variable and then let’s try to access this variable outside of the scope of the @if block.

slider.component.html

@if (advancedFeatures) {
    @let testVar = 'test';
    ...
}
{{ testVar }}
Enter fullscreen mode Exit fullscreen mode

Now let’s save and see what happens.

Example of an error when trying to access a variable using the @let syntax outside of its scope

Well, we get an error when we try to do this because that variable is scoped within the @if block condition and is not accessible outside.

In Conclusion

The ability to add variables right within the template is pretty handy in some situations. Now, I’m sure that there are several of you out there who may not be impressed or may just dislike the idea of doing this altogether. And if you’re one of these folks, that’s fine just remember, it’s ok you don’t have to use it. Just know it’s there for you if you ever do need it.

I hope you found this tutorial helpful, and if you did, check out my YouTube channel for more tutorials about various topics and features within Angular.

Additional Resources

Want to See It in Action?

Check out the demo code and examples of these techniques in the in the Stackblitz example below. If you have any questions or thoughts, don’t hesitate to leave a comment.

Top comments (1)

Collapse
 
jangelodev profile image
João Angelo

Hi Brian Treese,
Top, very nice and helpful !
Thanks for sharing.