The latest version of Angular introduces a significant enhancement known as the new Control Flow syntax, revolutionizing the way we handle component templates. This update marks a departure from older directives such as *ngIf/else, ngSwitch, and *ngFor. In essence, it streamlines the template syntax, providing a more intuitive and efficient approach to managing conditional and repetitive elements within Angular applications.
The familiar structural directive *ngIf and the convention of utilizing ng-template for other conditions are standard practices for seasoned Angular developers. However, with the advent of the new Control Flow syntax, these traditional methods become obsolete. Notably, there’s no longer a requirement to import the CommonModule to leverage these features, simplifying the setup process significantly.
Let’s delve into a practical example to illustrate the effectiveness of this updated syntax and its impact on our development workflow.
Table of Contents
Angular Control Flow Syntax for if/else Conditions
Ok, here in our component we have this player object consisting of some data about NBA player LeBron James.
app.component.ts
protected player: Player = {
name: 'LeBron James',
games: 1421,
points: 38652,
fieldGoalPercentage: 0.505,
threePointPercentage: 0.345,
imageName: 'lebron-james'
};
In the realm of real-world application development, data retrieval often involves making API requests, where the response may either contain relevant data or return it as undefined. As conscientious developers, we must handle this variability in our Angular templates with careful consideration. In this specific scenario, our objective is to exhibit our player component solely when valid player data is available. To achieve this, we employ conditional logic.
Enter the new Angular syntax, reminiscent of other popular templating languages such as PHP and Razor, which simplifies this process. It begins with the ‘@’ symbol, followed by parentheses encapsulating our condition. In this instance, our condition checks for the truthiness of the player value. Subsequently, we encapsulate the conditional item—our player component—within the corresponding block. This block includes the player input, effectively integrating the conditional rendering seamlessly into our Angular application.
app.component.html
@if (player) {
<app-player [player]="player"></app-player>
}
Right now, of course, it’s showing because we have our data hard-coded.
So let’s go clear it out.
protected player?: Player;
Upon saving the changes, we observe that the component is correctly removed from view when data is absent.
To display a message in the absence of data, we introduce an ‘else’ statement into our template. This is achieved by adding another ‘@’ symbol, followed by the keyword ‘else’, and enclosing a new set of curly braces where we define our message content.
Upon saving these modifications, our message is now visibly displayed, indicating the absence of data.
In summary, this is the new syntax that supplants the old *ngIf/else directive, offering a more streamlined approach to conditional rendering in Angular.
Angular Control Flow Syntax for Switch Statements
Let’s explore the new syntax for switch statements in Angular. Unlike before, there’s no longer a need for a wrapping element or ng-container when utilizing the ngSwitch directive and its cases. Consider the following example: we have a select control offering options to choose between LeBron, Kareem, or the default player.
Examining our data, we find three player objects: player one (LeBron), player two (Kareem), and player three (Karl Malone).
protected player1: Player = {
name: 'LeBron James',
games: 1421,
points: 38652,
fieldGoalPercentage: 0.505,
threePointPercentage: 0.345,
imageName: 'lebron-james'
};
protected player2: Player = {
name: 'Kareem Abdul-Jabbar',
games: 1560,
points: 38387,
fieldGoalPercentage: 0.559,
threePointPercentage: 0.056,
imageName: 'kareem-abdul-jabbar'
};
protected player3: Player = {
name: 'Karl Malone',
games: 1476,
points: 36928,
fieldGoalPercentage: 0.516,
threePointPercentage: 0.274,
imageName: 'karl-malone'
};
Our goal in the template is to dynamically display the corresponding player based on the selected value from our form control, which can be either LeBron, Kareem, or Default.
protected options = new FormControl<'LeBron' | 'Kareem' | 'Default'>('Default', { nonNullable: true });
To achieve this, we start by introducing the ‘@’ symbol followed by the word “switch” in our template. Within parentheses, we specify the value we are switching on, which, in this case, is the value of our options control.
@switch (options.value) {}
For each case, we utilize the ‘@’ symbol, followed by the word “case” and the specific value (e.g., LeBron, Kareem) within parentheses. Within the corresponding curly braces, we include our player component with the appropriate player input. We repeat this process for each player, adjusting the case value and player input accordingly.
@switch (options.value) {
@case ('LeBron') {
<app-player [player]="player1"></app-player>
}
}
Additionally, we add a default case where we display the player component with the data for Karl Malone, as he is the default player in our example.
@switch (options.value) {
@case ('LeBron') {
<app-player [player]="player1"></app-player>
}
@case ('Kareem') {
<app-player [player]="player2"></app-player>
}
}
Upon saving these changes, we see Karl Malone displayed initially, as he is the default player. When we switch to LeBron or Kareem, the view updates accordingly, demonstrating the dynamic nature of the ngSwitch directive in Angular.
@switch (options.value) {
@case ('LeBron') {
<app-player [player]="player1"></app-player>
}
@case ('Kareem') {
<app-player [player]="player2"></app-player>
}
@default {
<app-player [player]="player3"></app-player>
}
}
In conclusion, this new switch syntax simplifies the process of implementing switch statements in Angular templates.
Angular Control Flow Syntax for For Loops
In Angular, when implementing for loops using the *ngFor directive, a container element or ng-container was previously required. Additionally, developers had to define a variable to iterate over a list of options, often necessitating the use of a trackby function for performance optimization. While the process remains conceptually similar, there are nuances in the latest approach.
Consider an example where we have a larger list of players, each with various attributes.
players.ts
export const players = [
{
name: 'LeBron James',
games: 1421,
points: 38652,
fieldGoalPercentage: 0.505,
threePointPercentage: 0.345,
imageName: 'lebron-james'
},
{
name: 'Kareem Abdul-Jabbar',
games: 1560,
points: 38387,
fieldGoalPercentage: 0.559,
threePointPercentage: 0.056,
imageName: 'kareem-abdul-jabbar'
},
{
name: 'Karl Malone',
games: 1476,
points: 36928,
fieldGoalPercentage: 0.516,
threePointPercentage: 0.274,
imageName: 'karl-malone'
},
{
name: 'Kobe Bryant',
games: 1346,
points: 33643,
fieldGoalPercentage: 0.447,
threePointPercentage: 0.329,
imageName: 'kobe-bryant'
},
{
name: 'Michael Jordan',
games: 1072,
points: 32292,
fieldGoalPercentage: 0.497,
threePointPercentage: 0.327,
imageName: 'michael-jordan'
},
{
name: 'Dirk Nowitzki',
games: 1522,
points: 31560,
fieldGoalPercentage: 0.471,
threePointPercentage: 0.38,
imageName: 'dirk-nowitzki'
},
{
name: 'Wilt Chamberlain',
games: 1045,
points: 31419,
fieldGoalPercentage: 0.54,
threePointPercentage: 0,
imageName: 'wilt-chamberlain'
},
{
name: 'Shaquille O\'Neal',
games: 1207,
points: 28596,
fieldGoalPercentage: 0.582,
threePointPercentage: 0.045,
imageName: 'shaquille-oneal'
},
{
name: 'Carmelo Anthony',
games: 1260,
points: 28289,
fieldGoalPercentage: 0.447,
threePointPercentage: 0.355,
imageName: 'carmelo-anthony'
},
{
name: 'Moses Malone',
games: 1329,
points: 27409,
fieldGoalPercentage: 0.491,
threePointPercentage: 0.10,
imageName: 'moses-malone'
},
{
name: 'Elvin Hayes',
games: 1303,
points: 27313,
fieldGoalPercentage: 0.452,
threePointPercentage: 0.147,
imageName: 'elvin-hayes'
}
];
To display these players in the template using a for loop, we utilize the new syntax. Beginning with the ‘@’ symbol, we introduce the keyword “for” followed by parentheses. Within these parentheses, we define our iteration variable, in this case, named “player”. Similar to the previous *ngFor syntax, we use the “of” keyword to iterate over our list of data, here referred to as players.
@for (player of players) {
}
To enhance performance and avoid unnecessary re-renders, we introduce a trackby function. Following a semicolon, we specify the keyword “track” and provide a unique value for tracking, such as an id. In our case, we’ll use the player’s name as the identifier.
@for (player of players; track player.name) {
}
Ok, all that’s left now is to add our player component and pass it our player data.
And now when we save, here’s our list of players.
Angular For Loop Empty Template
In an Angular application, when dealing with an empty list and the need to display a message, a straightforward approach can be adopted. Let’s consider a scenario where our list is emptied, and we aim to display a message in such cases.
To begin, we can simulate an empty list by clearing out our list data. Upon saving, this would result in a blank screen, which is not ideal. To rectify this, we can modify our template to include a provision for an empty list scenario within our for loop. Inside this block, we can include the message we wish to display.
Upon saving these changes, our message is now visible, providing a clear indication to the user that the list is currently empty. This approach ensures a seamless user experience and demonstrates the flexibility of Angular in handling various scenarios.
@for (player of players) {
...
}
@empty {
<p>There are no players to display at this time</p>
}
Angular For Loop Additional Properties
For those familiar with the old *ngFor syntax, you’ll be pleased to know that all the previous properties still exist in the new Angular syntax, and they are added in a similar manner.
- index
- first
- last
- even
- odd
- count
To demonstrate, let’s consider adding functionality to display the count of items in our list before the first item. We achieve this by introducing a condition for the first item and then displaying a message with the count interpolated into the string.
Upon saving these changes, we can observe the message with the count displayed before all items, enhancing the user experience with informative content.
@for (player of players;
track player.name;
let index = $index;
let first = $first;
let last = $last;
let even = $even;
let odd = $odd;
let count = $count) {
...
}
Additionally, suppose we want to display the index of each list item next to the player’s name. In that case, we can pass the item index to the player component. Internally, the component adjusts the index by adding one and displays this value before the player’s name, providing a numbered list effect.
@if (first) {
<strong>There are {{ count }} players in the list</strong>
}
Furthermore, we can introduce zebra striping to our list by binding a class, ‘even’, to all even-numbered list items. This simple addition visually distinguishes these items from the rest.
Upon saving, we can see that all even-numbered items now appear slightly darker, improving the visual appeal of our list.
Lastly, we can add a border to highlight the first and last list items by binding a ‘first’ and ‘last’ class to these variables. This addition adds a visual cue to the first and last items in the list.
<app-player
...
[class.even]="even"
...>
</app-player>
Upon scrolling to the bottom of the list, we can observe that the last item now has a blue border, indicating its position within the list.
<app-player
...
[class.first]="first"
[class.last]="last"
...>
</app-player
In summary, the new *ngFor syntax in Angular retains the familiar functionality of the old syntax while streamlining the implementation process. This change reduces the need for additional elements such as ng-containers and ng-templates, as well as eliminating the requirement for additional imports, resulting in a more straightforward and efficient development experience.
The Angular Control Flow Migration Schematic
For developers concerned about transitioning to Angular’s new control flow syntax within an existing codebase, rest assured that the migration process is designed to be straightforward. With the migration schematic, updating existing *ngIf, ngSwitch, and *ngFor directives should be a seamless process, requiring minimal effort on your part.
ng g @angular/core:control-flow-migration
By leveraging the migration schematic, Angular handles the updates to these directives, ensuring that your codebase remains up-to-date with the latest syntax without much manual intervention.
Want to See It in Action?
Explore the demonstration code and examples showcasing these techniques in action through the StackBlitz demo below. Whether you’re looking to gain a better understanding or have specific questions, feel free to leave a comment for further clarification or discussion. Your feedback is valuable, and I’m here to help.