v19 launch sale ends in ... Get 25% off BUY NOW
3d cartoon hands holding a phone

Unlock full course by purchasing a membership

Lesson 3

An Introduction to Angular Template Syntax

STANDARD

An Introduction to Angular Template Syntax

We are going to look at some special things Angular brings to the template in just a moment. Before I get into that though, I think it’s useful to know about the APIs that each and every DOM element (that is, a single node in your HTML like <button>) have. These are features of DOM elements that exist regardless of Angular.

Although this will only be a brief look into how native JavaScript and the DOM work, it is helpful to be able to draw lines between when you are utilising features of Angular and when you are just using standard browser/JavaScript features. I would encourage you to continue expanding your knowledge of fundamental JavaScript concepts, as well as learning Angular itself.

Attributes, Properties, Events, and Methods

Let’s imagine we’ve grabbed a single node in an HTML template by using something like getElementById('myInput') in JavaScript. That node will have attributes, properties, methods and events.

NOTE: We would not typically grab a reference to an element in an Angular application using getElementById — there are Angular specific ways to do this. This is just a generic example of how these concepts apply outside of the Angular framework environment.

An attribute like id below can be used to supply an initial value to a property:

<input id="some-input" value="hello">

This attribute is used to set an initial value for the id property on the element. Attributes can only ever be strings.

What the browser will do is parse all of the HTML on the page, and it will add nodes to the DOM for each element. This node will be an object that has properties. In the case of our <input> element above, an HTMLInputElement object will be added to the DOM along with all of the properties that an HTMLInputElement object would have. Since we supplied an id attribute, when this object is created it will take the value we provided, which was some-input, and it will initialise the id property of the HTMLInputElement object to that value.

Whilst an attribute is just used to supply a simple initial value, a property of a DOM object is much more open to manipulation.

const myInput = document.getElementById('some-input');
console.log(myInput.value); // hello
myInput.value = "What's up?";
console.log(myInput.value); // What's up?
myInput.value = new Object(); // We also are not limited to just using strings

A method is a function that we can call on the element, like this:

myInput.addEventListener('input', doSomething);

By using the addEventListener method, we would now be listening for the input event on that input field and will call the doSomething function when that happens. This relies on the element’s ability to fire events.

As well as the input event, an element can also fire events like focus, blur, click and so on — elements can also fire custom events. Having a basic understanding of these native JavaScript concepts should help to shape your understanding of what Angular adds on top of that.

Once again, our goal is just to gain a basic understanding here and we will continue to learn these concepts throughout the course. Just throwing a ton of concepts at you all at once without practice isn’t going to be that helpful.

NOTE: As we discuss these syntax concepts, try them out in your own application. If you still have your my-app application in a working state you can use that, or you can create an entirely new application. You can just dump these examples directly into your root AppComponent, play around with them, and see what happens.

Property Binding

import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  template: ` <input [value]="firstName" /> `,
})
export class AppComponent {
  firstName = 'Josh';
}

This looks similar to the concepts we just discussed, but now we have these strange square brackets around the value attribute. Just like with an attribute initialising a property this will also initialise the value property of our input element.

However, there are two important differences now.

First, this will set the element’s value property to the expression firstName (which is defined in the class definition for the component as a member variable). Note that firstName is an expression, not a string. Without the use of the square brackets, firstName would be treated literally as the string “firstName” rather than whatever it is that firstName evaluates to (in this case: Josh). Surrounding the property with square brackets allows us to bind the property to an expression (most often a value from our class).

The second important difference is that the value of the property will be updated when firstName changes. Our firstName class member might initially be Josh and so the input will have a value of Josh. But later the firstName variable might change to Kathy, and when that happens the value property will be updated to Kathy. For now, you can just take this for granted. But this ties into Angular’s change detection system that we will discuss in detail in a later lesson.

NOTE: Angular also has a special way to make dealing with forms and inputs easier — we are not using that in this example, but we will later. You would not typically bind to an input element manually in Angular this way.

Binding an event to a method

import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  template: `<button (click)="handleClick($event)">click me</button>`,
})
export class AppComponent {
  handleClick(ev: MouseEvent) {
    console.log('I was clicked!');
    console.log(ev);
  }
}

We can place an event name inside of (parenthesis) to trigger a particular method in our class when that event is fired. In this case, when the click event is triggered on the button it will call our handleClick method that we defined in the class. We use the special $event parameter to pass in the details of the event to our method, but we don’t need to pass this in if we don’t want. You can replace click with any native or custom event you like.

This isn’t particularly relevant to this section, but notice that we are also manually specifying that our ev parameter will have a type of MouseEvent. Often TypeScript can implicitly tell what type something is, so we don’t need to manually define it. In this case, TypeScript does not know what type of parameter we might pass into the handleClick function. So, we manually tell TypeScript that we will be passing a MouseEvent to this method, which is the type of event that is triggered when the button is clicked.

In the beginning, it is going to be hard to know how to type certain things. If you are unsure, you can just give it a type of any:

  handleClick(ev: any) {
    console.log('I was clicked!');
    console.log(ev);
  }

This is not something you want to be doing long term, as it defeats the benefits of TypeScript. But, in the beginning, you might not want to waste time messing around with types when you have tons of other concepts to learn as well. So, just use any if you need to, and dedicate some time to applying proper types later.

Interpolations

import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  template: `<p>Hi, {{ name }}</p>`,
})
export class AppComponent {
  name = 'Josh';
}

This is very similar to the concept of binding to a property:

<input [value]="name" />

Except instead of binding to a property, we can render an expression anywhere in our template. In fact, an interpolation will even work for binding to a value instead of using the square bracket syntax:

<input value="{{name}}" />

BUT!… you should not do this for property binding. It may work in some cases, but the interpolation syntax {{ }} will render the expression as a string, whereas the square bracket syntax [ ] will bind the value to the property as whatever type it actually is. For example, if you were passing in a number, the {{ }} syntax will convert it to a string — which might not be what you want. So, in short, use [] for binding to properties, and use {{ }} for displaying values in your template.

Two Way Data Binding

So far, we have been looking at how to take a value from our class and display it in the template. You can think of this as one-way data binding. Let’s back up a little and re-consider this example of property binding from before:

import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  template: ` <input [value]="firstName" /> `,
})
export class AppComponent {
  firstName = 'Josh';
}

This works great because our input will use whatever value we define for firstName in the class…

But this is an input. When we put an <input> on the page we generally expect the user to change its value. What happens then? In the example above, when the user changes the <input> by typing into the field, the input they see will change. But, the value of firstName in the class will remain the same.

This is where Angular’s concept of two-way data binding comes into play. When we update the firstName variable in the class we want that change to be reflected in the template, and when the value is changed in the template we also want the value to be changed in the class. It should work both ways.

We can set up two-way data binding in Angular like this:

<input [value]="firstName" (input)="firstName = $event.target.value">

This is reasonably intuitive and utilises concepts we have already covered. We set the value with a property binding to update the value in the template, and then we listen for the input event to update the value of firstName in the class when it is triggered.

NOTE: You can access any class member variable you define in your class in your template as we are doing with firstName above.

It makes sense, but it is a bit awkward to set up. Fortunately, Angular provides an alternative syntax for us:

import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';

@Component({
  selector: 'app-root',
  template: ` <input [(ngModel)]="firstName" /> `,
  imports: [FormsModule],
})
export class AppComponent {
  firstName = 'Josh';
}

This is just a shortcut for the code we wrote above but is obviously a lot easier to write (they call this the “banana in a box” syntax). This ngModel comes from Angular’s FormsModule which provides a bunch of functionality related to creating forms.

NOTE: If you do not add FormsModule to the imports array the ngModel syntax will not work

This is one approach you can take to forms, and is generally referred to as template driven forms, but we won’t actually be making use of ngModel throughout this course. Instead, we will be making use of the ReactiveFormsModule which we will cover later.

Creating a Template Variable

<p #myParagraph></p>

This one is not used as often, but can come in useful in certain situations. The code above creates a local template variable that we can use to access the element it is attached to, so if I wanted to add some content to this paragraph I could do the following:

<button (click)="myParagraph.innerHTML = 'Once upon a time...'">Click me</button>

This example is pretty contrived, but the situations in which this concept can be useful are a bit more complex than I think we should get into right now.

Embedded Templates

This is not a feature you will use all that often yourself (we will actually use this for creating loading templates later), but it is the concept behind the structural directives provided by Angular that are used quite often. We will discuss those structural directives in just a moment, but in the hope of making them seem a little less magical, let’s briefly cover the concept of an embedded template:

<ng-template #myTemplate>
  <p>Hello there</p>
</ng-template>

Go ahead and add that to the example application in the template and see what happens.

You should find that nothing is displayed. The general idea here is that an <ng-template> allows us to define a chunk of the DOM, but Angular won’t actually render it. What we are doing is creating a sort of “prefabrication” or “stamp”, and we can stamp out that template dynamically. We might want to stamp it out multiple times, or we might want to display a template based on some condition (like if some data is loading in, for example).

To use this template, we can use the following syntax:

<ng-container *ngTemplateOutlet="myTemplate"></ng-container>

NOTE: Using ngTemplateOutlet will require adding it to the imports of your component:

import { Component } from '@angular/core';
import { NgTemplateOutlet } from '@angular/common';

@Component({
  selector: 'app-root',
  template: `
    <ng-template #myTemplate>
      <p>Hello there</p>
    </ng-template>

    <ng-container *ngTemplateOutlet="myTemplate"></ng-container>
  `,
  imports: [NgTemplateOutlet],
})
export class AppComponent {}

Notice that we are referring to the template variable myTemplate that we added to our <ng-template>. The <ng-container> used here is actually a separate concept — technically we could have just used a <div>.

Unlike the <div> approach, the <ng-container> approach is generally better because it won’t render an extra element in the DOM. The <ng-container> is useful in other circumstances as well and we will make use of this later. Basically, any time we need some container element to attach some behaviour to, but we don’t want to render an additional element in the DOM, we can use <ng-container>. In this particular case, it doesn’t actually matter, even if we did use a <div> the *ngTemplateOutlet won’t actually render the <div> in the DOM.

At this point, you should be able to see the <ng-template> being rendered out in the DOM. But you might be thinking this is all pretty weird and pointless at the moment… why bother with the template?

Well, one thing we could do now is stamp out multiple instances of this template if we want, e.g:

<ng-container *ngTemplateOutlet="myTemplate"></ng-container>
<ng-container *ngTemplateOutlet="myTemplate"></ng-container>
<ng-container *ngTemplateOutlet="myTemplate"></ng-container>

That’s kind of fun — we don’t need to re-add the whole template each time. Still, not super interesting. We could make this configurable though:

    <ng-template #myTemplate let-greeting="greeting">
      <p>{{ greeting }} there</p>
    </ng-template>

    <ng-container
      *ngTemplateOutlet="myTemplate; context: { greeting: 'Hi' }"
    ></ng-container>
    <ng-container
      *ngTemplateOutlet="myTemplate; context: { greeting: 'Hello' }"
    ></ng-container>
    <ng-container
      *ngTemplateOutlet="myTemplate; context: { greeting: 'Yo' }"
    ></ng-container>

Our template can take in a context. Here we are passing in a greeting and changing what we render out based on the greeting passed in.

You can do all sorts of clever stuff with this but to be honest, I don’t use this concept a whole lot. If I wanted some configurable/repeatable element like the above, I would generally just create a custom component. But I still think it is important to understand as a concept, since this is the basic idea behind the structural directives Angular provides us that are used a lot.

Directives

Before we get to structural directives it is useful to understand what a standard directive is and how we might use it. We will look at actually creating a directive later, but a key point to understand is that a directive defines a selector:

@Directive({
  selector: '[myDirective]'
})
export class MyDirective {}

NOTE: A directive can be standalone as well — just like a component. If it is not marked as standalone: false it will be standalone by default. Otherwise, it will need to be included as part of an @NgModule

A @Directive is like a @Component except that it does not have a template. Instead of having its own template, it attaches its behaviour to some other element that already exists. Like a little parasite. A helpful parasite.

The selector is defined using a CSS style selector, so our particular selector of [myDirective] will match any element that has a myDirective attribute. If we had made the selector .my-directive instead then it would match any element with a CSS class of my-directive.

As an example, we might add some code to this directive such that it will make whatever element it is attached to have a background colour of red. We could then do this in our template:

<my-component myDirective></my-component>

NOTE: Remember, to use a standalone directive you will need to add it to the imports array of the component you are using it in — otherwise Angular will not know that it exists

Since <my-component> has a myDirective attribute, our MyDirective directive will be attached to it, and it will make the component’s background colour red.

In short: directives are useful for modifying the behaviour of existing components/elements.

Structural Directives

<section *ngIf="showSection"></section>
<li *ngFor="let item of items">{{ item }}</li>

To use *ngIf and *ngFor you must import the CommonModule (you can also import NgIf and NgFor individually if you prefer):

import { CommonModule } from '@angular/common';
import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  template: ` <p *ngIf="shouldDisplay">hello</p> `,
  imports: [CommonModule],
})
export class AppComponent {
  shouldDisplay = true;
}

NOTE: There is a new syntax for handling control flow in Angular such as conditionally showing/hiding elements or looping over elements. The syntax above is the “old” way, but is still used by many Angular applications. The “old” syntax uses structural directives for the purpose of control flow, but structural directives can also be used in other situations as well. It’s still useful to understand structural directives, even if you are using the new control flow syntax.

Directives allow you to attach some behaviour to an existing element, and structural directives specifically allow you to affect the structure of the DOM. As we saw in the previous section, we can control if/when/how a pre-defined template should be rendered out into the DOM.

The two examples of structural directives above, *ngIf and *ngFor, are provided by Angular and they are used a lot. These will be directly replaced by the @if and @for syntax we will discuss in a moment, but you will still see this “old” syntax used a lot for a long time.

In the first example, we are conditionally showing the <section> based on whether or not the showSection expression evaluates to be true, and in the second example, we are looping over all items in the items array and creating a list item for all of them. If there were 5 items in the items array in our class, and let’s pretend this array had elements with the numbers 1 through to 5, then the following would be rendered out:

<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>

These specific examples are “structural directives” because they use the * syntax and modify the DOM with <ng-template> behind the scenes.

@-Syntax for Control Flow

This new @-syntax is a direct replacement for the concept of *ngIf and *ngFor. Unlike the structural directive approach, the @-syntax is baked in by default and does not need to be imported.

The examples from above would translate to:

@if (showSection) {
  <section></section>
}

and:

@for (item of items; track $index){
  <li>{{ item }}</li>
}

These do the exact same thing — the @if will conditionally display something, and the @for will loop over an array and render something for each item in the array.

One difference here is the use of this strange track $index statement. This is for performance reasons — Angular is better able to render items in a list if it has a unique identifier to track each item with.

Let’s say you were looping over an item object that had its own unique key:

items = [
  {id: '1', title: 'hello'},
  {id: '2', title: 'world'}
]

You could use the items id as the tracking mechanism:

@for (item of items; track item.id){
  <li>{{ item }}</li>
}

However, we might not always have a unique id to track by, and in this case we can just use the $index of the item in the array as the unique identifier.

This is necessary to use with the new syntax because this performance aspect is enforced by default. It is also possible to write a custom trackBy function for the old *ngFor syntax, but it is not enforced and it is harder to do.

A nicer thing about this new syntax is that it also makes else and else if conditions quite easy to implement:

@if (someCondition){
  <p>display this</p>
} @else if (someOtherCondition){
  <p>display this instead</p>
} @else {
  <p>last resort</p>
}

We have not discussed everything there is to know about this control flow syntax, but it is a good amount to get us started and we will cover more as we go.

Recap

We’ve covered a lot in this lesson, and again, I don’t expect all this to stick at once. We will continue revisiting these concepts throughout the course.

The best syntax for binding a variable to a property is...

If we want to bind a click event to some method we would do...

If we wanted to render out the result of 1 + 1 in the template, which of the following would work?

What is the correct way to set up two way data binding for a form control?

What is the correct way to set up a template variable?

Select the component that a directive with a selector of [myDirective] would apply to

The *ngIf structural directive or @if syntax will...

The *ngFor structural directive or @for syntax will...