ngIfngFor: display a listHttpClient and async PipeprovideRouter: router config and lazy loadingdefault export!In this short "step by step" tutorial you will learn how to create your first multi-view Angular 15 application that uses standalone components
The only requirements you need to make this exercise is NodeJS version 16 or above
Open your terminal and use the following command to create an Angular project:
npx -p @angular/cli@latest ng new angular15-demo -s -t -S
When asked, choose the following options:
NoCSSThe @latest option will install the latest framework version.
If you want to create a project with a specific version you can also use @x.x.x (i.e. @angular/cli@15.0.0)
The previous command will generate a "classic" Angular application that uses ngModules.
However we can now avoid to use modules in Angular 14+ projects and use (standalone) components only.
So open the main.ts file and replace all the content with the following in order to bootstrap your application with AppComponent instead AppModule:
main.tsimport {bootstrapApplication} from "@angular/platform-browser";
import {AppComponent} from "./app/app.component";
bootstrapApplication(AppComponent)
.catch(err => console.log(err))
AppComponentYour application still won't work because you have to set the AppComponent as standalone:
app.component.tsimport { Component } from '@angular/core';
@Component({
selector: 'app-root',
standalone: true, // <==
template: `
<div>Hello Angular 15</div>
`,
})
export class AppComponent {}
You can now run the application to check if it works:
npm start
Open your browser and you should be able to see the result (just the Hello Angular 15 message) on localhost:4200 url.
You can now delete the app.module.ts file from your project.
ngIfNow we want to use the *ngIf directive to toggle the visibility of a <div> element when a button is clicked.
In the previous Angular versions this directive was available importing the CommonModule in any module that needed to use it but, since we have deleted AppModule, you won't be able to use it anymore.
So your component needs to import into it:
app.component.tsimport { Component } from '@angular/core';
import { NgIf } from "@angular/common";
@Component({
selector: 'app-root',
standalone: true,
template: `
<button (click)="visible = !visible">Toggle</button>
<div *ngIf="visible">Hello Angular 15</div>
`,
imports: [
NgIf // you can also import CommonModule instead
],
})
export class AppComponent {
visible = false
}
You can also import CommonModule instead a specific directive. For example instead of:
app.component.tsimport { NgIf } from '@angular/common';
// ...
imports: [
NgIf
],
you can import the CommonModule that also contain all the other built-in directives, such as ngFor, ngClass and so on and all the pipes (json, date, async ...):
app.component.tsimport { CommonModule } from '@angular/common';
// ...
imports: [
CommonModule
],
ngFor: display a listLet's try to display a list of users getting them from an hard coded array.
As you can see from the script below, you also need to import the NgFor directive now:
app.component.tsimport { Component } from '@angular/core';
import { NgFor, NgIf } from "@angular/common";
@Component({
selector: 'app-root',
standalone: true,
template: `
<button (click)="visible = !visible">Toggle</button>
<div *ngIf="visible">Hello Angular 15</div>
<li *ngFor="let user of users">{{user}}</li>
`,
imports: [
NgIf, NgFor // or import CommonModule
],
})
export class AppComponent {
visible = false;
users = ['Fabio', 'Lorenzo', 'Silvia']
}
HttpClient and async PipeNow we want to fetch data from a REST API by using the HttpClient service.
We are usually used to importing HttpClientModule in AppModule to make it available everywhere but, since we don't have it anymore, we can now import it directly in the component :
app.component.tsimport { Component } from '@angular/core';
import { AsyncPipe, NgFor } from "@angular/common";
import { HttpClient, HttpClientModule } from "@angular/common/http"; // <==
@Component({
selector: 'app-root',
standalone: true,
template: `
<li *ngFor="let user of users$ | async">{{user.name}}</li>
`,
imports: [
HttpClientModule, // <==
NgFor,
AsyncPipe
],
})
export class AppComponent {
users$ = this.http.get<any[]>('https://jsonplaceholder.typicode.com/users') // <==
constructor(private http: HttpClient) {} // <==
}
However I usually prefer to import it in main.ts by using the provideHttpClient function as shown below in order to make all the HttpClient features globally available:
main.tsimport { bootstrapApplication } from "@angular/platform-browser";
import { AppComponent } from "./app/app.component";
import { provideHttpClient } from "@angular/common/http";
bootstrapApplication(AppComponent, {
providers: [
provideHttpClient() // <==
]
})
.catch(err => console.log(err))
Of course you can now remove HttpClientModule from app.component.ts
provideRouter: router config and lazy loadingNow our goal is the creation of a multipage application using Angular Router. First we create the pages/routes, which are standalone components:
ng g component --standalone --flat pages/home
ng g component --standalone --flat pages/catalog
ng g component --standalone --flat pages/contacts
I use the --flat option to avoid the creation of a folder for each component:
Now we can update main.ts to define the router rules: each url path specifies what component it should load.
main.tsimport {bootstrapApplication} from "@angular/platform-browser";
import {AppComponent} from "./app/app.component";
import {provideRouter} from "@angular/router";
import {provideHttpClient} from "@angular/common/http";
bootstrapApplication(AppComponent, {
providers: [
provideHttpClient(),
provideRouter([
{path: 'home', loadComponent: () => import('./app/pages/home.component').then(c => c.HomeComponent)},
{path: 'catalog', loadComponent: () => import('./app/pages/catalog.component').then(c => c.CatalogComponent)},
{path: 'contacts', loadComponent: () => import('./app/pages/contacts.component').then(c => c.ContactsComponent)},
{path: '', redirectTo: 'home', pathMatch: 'full'}
])
]
})
.catch(err => console.log(err))
The loadComponent property allow us to use dynamic imports and apply the lazy loading technique.
Now we can update AppComponent to create a simple navigation bar.
Of course we may also create a standalone component but it's not important for understanding the concept.
app.component.tsimport { Component } from '@angular/core';
import { RouterLink, RouterOutlet } from "@angular/router";
@Component({
selector: 'app-root',
standalone: true,
template: `
<button routerLink="home">home</button>
<button routerLink="catalog">catalog</button>
<button routerLink="contacts">contacts</button>
<router-outlet></router-outlet>
`,
imports: [
RouterOutlet,
RouterLink
],
})
export class AppComponent {}
We have to import RouterOutlet and RouterLink directives since we use them in the template
Now you can navigate the application clicking buttons:
Open your browser Dev Tools -> Network to see how the application is now splitted in several chunks:
default export!To simplify the router configuration we can also export our components using default:
export default class HomeComponent {
// ...
}
export default class CatalogComponent {
// ...
}
export default class ContactsComponent {
// ...
}
So we can now update the router rules as shown below:
main.tsimport {bootstrapApplication} from "@angular/platform-browser";
import {AppComponent} from "./app/app.component";
import {provideRouter} from "@angular/router";
import {provideHttpClient} from "@angular/common/http";
bootstrapApplication(AppComponent, {
providers: [
provideHttpClient(),
provideRouter([
// OLD
// {path: 'home', loadComponent: () => import('./app/pages/home.component').then(c => c.HomeComponent)},
// NEW
{path: 'home', loadComponent: () => import('./app/pages/home.component')},
{path: 'catalog', loadComponent: () => import('./app/pages/catalog.component')},
{path: 'contacts', loadComponent: () => import('./app/pages/contacts.component')},
{path: '', redirectTo: 'home', pathMatch: 'full'}
])
]
})
.catch(err => console.log(err))
