Angular Named Router Outlet + Popup Example
August 27, 2021
This page will walk through Angular named router outlet and popup example. Named outlet is used to perform a task in popup view. The named outlet will stay open even when we switch between pages in the application. Named outlet will close only when we close it. The component which needs to open in popup view cannot use same router outlet which other pages of application uses. Named outlet is that outlet which is given a name using name
attribute in <router-outlet>
tag. Router supports only one unnamed outlet per template, that is called primary outlet. Named outlet can be more than one per template. The component which needs to open in named outlet will configure its path with target outlet name in routing module using outlet
property of Route
interface. More than one named outlet can stay open together. Named outlet uses secondary routes to open a component. Secondary routes are independent of primary route. We can navigate to named outlet using RouterLink
directive as well as Router.navigate()
method. Named outlet will close only when we pass null
path to it. On this page we will provide how to create named outlet, configuring path and navigation. Find the complete example step-by-step.
Contents
Technologies Used
Find the technologies being used in our example.1. Angular 12.1.0
2. Node.js 12.14.1
3. NPM 7.20.3
Project Structure
Find the project structure of our demo application.angular-demo | |--src | | | |--app | | | | | |--animations | | | | | | | |--on-off.animation.ts | | | |--round-anticlock.animation.ts | | | |--fly-in-out.animation.ts | | | | | |--book.ts | | |--book.service.ts | | |--book.component.ts | | |--book.component.html | | |--addbook.component.ts | | |--addbook.component.html | | |--book-detail.component.ts | | |--book-detail.component.html | | |--book-update.component.ts | | |--book-update.component.html | | |--book-update.component.css | | |--app.component.ts | | |--app-routing.module.ts | | |--app.module.ts | | | |--main.ts | |--index.html | |--styles.css | |--node_modules |--package.json
Step-1: Creating Named Outlet
To create a named outlet,<router-outlet>
has attribute name
that is used as follows.
<router-outlet name="bookList"></router-outlet>
<router-outlet>
has outlet name as booklist
. We can create more than one named outlet with our primary outlet. Unnamed outlet is primary outlet.
<router-outlet></router-outlet> <router-outlet name="bookList"></router-outlet> <router-outlet name="bookPopup"></router-outlet>
bookList
and bookPopup
.
Step2: Creating Routes for Named Outlet
To open a route in a named outlet, we need to useoutlet
property of Route
interface. It is used as follows.
outlet: 'bookPopup'
bookPopup
is the name of named router outlet. Find the code snippet of routing module.
const routes: Routes = [ { path: 'book', component: BookComponent }, { path: 'add-book', component: AddBookComponent, outlet: 'bookPopup' }, { path: 'update-book/:book-id', component: BookUpdateComponent, outlet: 'bookPopup' }, { path: 'book-detail', component: BookDetailComponent, outlet: 'bookList' }, { path: '', redirectTo: '/book', pathMatch: 'full' } ];
1.
AddBookComponent
will open in bookPopup
named outlet.
2.
BookUpdateComponent
will open in bookPopup
named outlet.
3.
BookDetailComponent
will open in bookList
named outlet.
4.
BookComponent
will open in unnamed outlet i.e. primary outlet.
Step3: Navigate to Named Outlet
We can navigate to named outlet usingRouterLink
directive as well as Router.navigate()
method.
A. Using
RouterLink
directive 1. Path:
{ path: 'update-book', component: BookUpdateComponent, outlet: 'bookPopup' }
<a [routerLink]="[{ outlets: { bookPopup: ['update-book'] } }]" >
{ path: 'update-book/:book-id', component: BookUpdateComponent, outlet: 'bookPopup' }
<a [routerLink]="[{ outlets: { bookPopup: ['update-book', book.id] } }]" >
{ path: ':book-id', component: BookUpdateComponent, outlet: 'bookPopup' }
<a [routerLink]="[{ outlets: { bookPopup: [book.id] } }]" >
Router.navigate()
method1. Path:
{ path: 'update-book', component: BookUpdateComponent, outlet: 'bookPopup' }
this.router.navigate([{ outlets: { bookPopup: [ 'update-book' ] }}]);
{ path: 'update-book/:book-id', component: BookUpdateComponent, outlet: 'bookPopup' }
this.router.navigate([{ outlets: { bookPopup: [ 'update-book', book.id ] }}]);
{ path: ':book-id', component: BookUpdateComponent, outlet: 'bookPopup' }
this.router.navigate([{ outlets: { bookPopup: [ book.id ] }}]);
Step4: Close Named Outlet
Once a named outlet is opened for a route, it can be closed by passing route asnull
.
close() { this.router.navigate([{ outlets: { bookPopup: null }}]); }
null
is not any route, so the outlet will be closed. A named outlet works as a container in which we open our component corresponding to a route. If a component is opened using a route in a named outlet and when we navigate to another route which has same named outlet then the component corresponding to previous route will be removed and component corresponding to new route will open. When null
is passed as a path to named outlet, no component opens and hence popup view outlet is closed.
Secondary routes
Named outlet will open as secondary route within a (). Suppose we have opened a route with unnamed outlet and then we visit a route that will open in named outlet, we will observe the path on browser address bar that both path exists there. The named outlet path is called secondary routes and will open in () . Secondary routes will be appended with unnamed router outlet path. All the open routes using primary and named outlet will be activated when usingrouterLinkActive
property. Let us discuss secondary routes for some cases.
Case-1: Visit a path with named outlet
http://localhost:4200/book(bookPopup:add-book)
http://localhost:4200/book
(bookPopup:add-book)
bookPopup
and path add-book
. Find the print screen.

Case-2: Visit paths with two different named outlet.
http://localhost:4200/book(bookList:book-detail//bookPopup:add-book)

Case-3: Visit a path which contains parameter in named outlet.
http://localhost:4200/book(bookPopup:update-book/3)

Complete Example
app.component.tsimport { Component } from '@angular/core'; @Component({ selector: 'app-root', template: ` <nav [ngClass] = "'parent-menu'"> <ul> <li><a routerLink="/book" routerLinkActive="active">Book</a></li> <li><a [routerLink]="[{ outlets: { bookPopup: ['add-book'] } }]" routerLinkActive="active">Add Book</a></li> <li><a [routerLink]="[{ outlets: { bookList: ['book-detail'] } }]" routerLinkActive="active">Book Details</a></li> </ul> </nav> <router-outlet></router-outlet> <router-outlet name="bookList"></router-outlet> <router-outlet name="bookPopup"></router-outlet> ` }) export class AppComponent { }
import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { BookComponent } from './book.component'; import { AddBookComponent } from './addbook.component'; import { BookDetailComponent } from './book-detail.component'; import { BookUpdateComponent } from './book-update.component'; const routes: Routes = [ { path: 'book', component: BookComponent }, { path: 'add-book', component: AddBookComponent, outlet: 'bookPopup' }, { path: 'update-book/:book-id', component: BookUpdateComponent, outlet: 'bookPopup' }, { path: 'book-detail', component: BookDetailComponent, outlet: 'bookList' }, { path: '', redirectTo: '/book', pathMatch: 'full' } ]; @NgModule({ imports: [ RouterModule.forRoot(routes) ], exports: [ RouterModule ] }) export class AppRoutingModule{ }
import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { FormsModule } from '@angular/forms'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { AppComponent } from './app.component'; import { BookComponent } from './book.component'; import { AddBookComponent } from './addbook.component'; import { BookDetailComponent } from './book-detail.component'; import { BookUpdateComponent } from './book-update.component'; import { BookService } from './book.service'; import { AppRoutingModule } from './app-routing.module'; @NgModule({ imports: [ BrowserModule, FormsModule, AppRoutingModule, BrowserAnimationsModule ], declarations: [ AppComponent, BookComponent, AddBookComponent, BookDetailComponent, BookUpdateComponent ], providers: [ BookService ], bootstrap: [ AppComponent ] }) export class AppModule { }
export interface Book { id: number; name: string; author: string; state: string; }
import { Injectable } from '@angular/core'; import { Book } from './book'; const BOOKS: Book[] = [ { "id": 1, "name": "Java", "author": "Mahesh", "state": "off" }, { "id": 2, "name": "Angular", "author": "Mahesh", "state": "off" }, { "id": 3, "name": "Spring", "author": "Krishna", "state": "off" }, { "id": 4, "name": "Hibernate", "author": "Krishna", "state": "off" } ]; let booksPromise = Promise.resolve(BOOKS); @Injectable() export class BookService { getBooks() { return booksPromise; } addBook(book: Book) { return this.getBooks() .then(books => { let maxIndex = books.length - 1; let bookWithMaxIndex = books[maxIndex]; book.id = bookWithMaxIndex.id + 1; book.state = 'off'; books.push(book); return book; }); } getBook(id: number) { return this.getBooks() .then(books => books.find(book => book.id === id)); } resetBookState(book: Book) { return this.getBooks().then(books => { books.map(book => book.state = 'off'); book.state = 'on'; return books; }); } }
import { Component, OnInit } from '@angular/core'; import { Router } from '@angular/router'; import { BookService } from './book.service'; import { Book } from './book'; import { ON_OFF_ANIMATION } from './animations/on-off.animation'; @Component({ templateUrl: 'book.component.html', animations: [ ON_OFF_ANIMATION ] }) export class BookComponent implements OnInit { books = {} as Promise<Book[]>; constructor(private bookService: BookService, private router: Router) { } ngOnInit() { this.books = this.bookService.getBooks(); } edit(book: Book) { this.bookService.resetBookState(book).then(() => this.router.navigate([{ outlets: { bookPopup: ['update-book', book.id] } }]) ); } }
<h3>Click to Update Book</h3> <ul [ngClass]= "'sub-menu'"> <li *ngFor="let book of books | async" [@onOffTrigger] = "book.state" (click)="edit(book)"> {{book.id}}. {{book.name}} </li> </ul>
import { Component, OnInit } from '@angular/core'; import { Router, ActivatedRoute, Params } from '@angular/router'; import { switchMap } from 'rxjs/operators'; import { BookService } from './book.service'; import { Book } from './book'; @Component({ templateUrl: './book-update.component.html', styleUrls: ['./book-update.component.css'] }) export class BookUpdateComponent implements OnInit { book = {} as Book | undefined; constructor(private bookService: BookService, private router: Router, private route: ActivatedRoute) { } ngOnInit() { this.route.params.pipe( switchMap((params: Params) => this.bookService.getBook(+params['book-id'])) ).subscribe(book => this.book = book); } update() { this.router.navigate([{ outlets: { bookPopup: null } }]); } }
<div *ngIf="book"> <h3>Update Book for Id: {{book.id}}</h3> <p>Book Name: <input [(ngModel)]="book.name"> </p> <p>Book Author: <input [(ngModel)]="book.author"> </p> <p> <button type="button" (click)="update()">Update</button> </p> </div>
:host { position: absolute; background-color: #e0d7d5; top: 20%; left: 15%; border: 3px solid black; }
import { Component, HostBinding } from '@angular/core'; import { Router } from '@angular/router'; import { BookService } from './book.service'; import { Book } from './book'; import { ROUND_ANTICLOCK_ANIMATION } from './animations/round-anticlock.animation'; @Component({ templateUrl: './addbook.component.html', styles: [':host { position: absolute; top: 20%; left: 5%; border: 3px solid black; }'], animations: [ ROUND_ANTICLOCK_ANIMATION ] }) export class AddBookComponent { @HostBinding('@roundAntiClockTrigger') roundAntiClockTrigger = 'in'; book = {} as Book; constructor(private bookService: BookService, private router: Router) { } add() { this.bookService.addBook(this.book).then( () => this.router.navigate([{ outlets: { bookPopup: null } }]) ); } }
<h3>Add Book</h3> <p>Enter Name: <input [(ngModel)]="book.name"> </p> <p>Enter Author: <input [(ngModel)]="book.author"> </p> <p> <button type="button" (click)="add()">Add</button> </p>
import { Component, OnInit, HostBinding } from '@angular/core'; import { Router } from '@angular/router'; import { FLY_IN_OUT_ANIMATION } from './animations/fly-in-out.animation'; import { BookService } from './book.service'; import { Book } from './book'; @Component({ templateUrl: './book-detail.component.html', styles: [':host { position: absolute; top: 20%; left: 5%; border: 3px solid black; }'], animations: [ FLY_IN_OUT_ANIMATION ] }) export class BookDetailComponent implements OnInit { @HostBinding('@flyInOutTrigger') flyInOutTrigger = 'in'; books: Promise<Book[]>; constructor(private bookService: BookService, private router: Router) { this.books = this.bookService.getBooks(); } ngOnInit() { } close() { this.router.navigate([{ outlets: { bookList: null } }]); } }
<h3>Book Details</h3> <p *ngFor="let book of books | async"> <b>Id:</b> {{book.id}}, <b>Name:</b> {{book.name}}, <b>Author:</b> {{book.author}} </p> <button type="button" (click)="close()">CLOSE</button>
import { animate, state, style, transition, trigger } from '@angular/animations'; export const ON_OFF_ANIMATION = trigger('onOffTrigger', [ state('off', style({ backgroundColor: '#E5E7E9', color: '#1C2833', fontSize: '18px', transform: 'scale(1)' })), state('on', style({ backgroundColor: '#17202A', color: '#F0F3F4', fontSize: '19px', transform: 'scale(1.1)' })), transition('off => on', animate(100)), transition('on => off', animate(100)) ]);
import { animate, state, style, transition, trigger, sequence } from '@angular/animations'; export const ROUND_ANTICLOCK_ANIMATION = trigger('roundAntiClockTrigger', [ state('in', style({ backgroundColor: '#E5E7E9', color: '#1B2172' })), transition('void => *', sequence([ style({ transform: 'rotate(270deg)', opacity: 0, backgroundColor: '#0D6063' }), animate('0.6s ease-in-out') ])), transition('* => void',[ style({backgroundColor: '#0D6063'}), animate('0.6s ease-out', style({transform: 'rotate(-270deg)', opacity: 0})) ]) ]);
import { animate, state, style, transition, trigger, group } from '@angular/animations'; export const FLY_IN_OUT_ANIMATION = trigger('flyInOutTrigger', [ state('in', style({ backgroundColor: '#7BBEFC', color: '#080809', transform: 'translateX(0)', opacity: 1 })), transition(':enter', [ style({ backgroundColor: '#E3E8EC', transform: 'translateX(300%)', opacity: 0 }), group([ animate('0.5s 0.1s ease-in', style({ transform: 'translateX(0)', })), animate('0.3s 0.1s ease', style({ opacity: 1 })) ]) ]), transition(':leave', [ style({ backgroundColor: '#9DCEFC', }), group([ animate('0.5s ease-out', style({ transform: 'translateX(300%)' })), animate('0.3s 0.1s ease', style({ opacity: 0 })) ]) ]) ]);
.parent-menu ul { list-style-type: none; margin: 0; padding: 0; overflow: hidden; background-color: #333; } .parent-menu li { float: left; } .parent-menu li a { display: block; color: white; text-align: center; padding: 15px 15px; text-decoration: none; } .parent-menu li a:hover:not(.active) { background-color: #111; } .parent-menu .active{ background-color: #4CAF50; } .sub-menu ul { list-style-type: none; padding: 0; } .sub-menu li { display: block; width: 120px; line-height: 50px; padding: 0 10px; box-sizing: border-box; border-radius: 4px; margin: 10px; cursor: pointer; overflow: hidden; white-space: nowrap; }
Run Application
To run the demo application, find following steps.1. Download source code using download link given below on this page.
2. Use downloaded src in your Angular CLI application. To install Angular CLI, find the link.
3. Run ng serve using command prompt.
4. Access the URL http://localhost:4200
References
Routing & NavigationAngular Animations Example