Hey guys, in this post
we are going to see how to create tabs dynamically in which each tab will
contain data from each component. There are many ways to create tab structure,
in this post we are going to focus on lazy-loaded component, which means all
components that will act as a tab will be loaded in lazily with help of
routing. Let’s do it step by step.
If you are not interested in lazy loading and you want tabs with simple selector please click here to visit post tabs with eager loading
If you are not interested in lazy loading and you want tabs with simple selector please click here to visit post tabs with eager loading
[at the bottom of the post
you will get the link to download the project]
[ If you are preparing for an interview please click here - angular interview questions and answers ]
[ If you are preparing for an interview please click here - angular interview questions and answers ]
At very first, install
angular material and angular cdk to support material.
D:\Angular-DemoApps\Angular 6 - Dynamic
tab implementation with Multiple component (Component as a tab, Eager
loading)> npm install --save @angular/material @angular/cdk
npm WARN @angular/animations@7.1.4
requires a peer of @angular/core@7.1.4 but none is installed. You must install
peer dependencies yourself.
npm WARN bootstrap@4.2.1 requires a peer
of jquery@1.9.1 - 3 but none is installed. You must install peer dependencies
yourself.
npm WARN bootstrap@4.2.1 requires a peer
of popper.js@^1.14.6 but none is installed. You must install peer dependencies
yourself.
npm WARN @angular/cdk@8.1.1 requires a
peer of @angular/core@^8.0.0 || ^9.0.0-0 but none is installed. You must
install peer dependencies yourself.
npm WARN @angular/cdk@8.1.1 requires a
peer of @angular/common@^8.0.0 || ^9.0.0-0 but none is installed. You must
install peer dependencies yourself.
npm WARN @angular/material@8.1.1
requires a peer of @angular/animations@^8.0.0 || ^9.0.0-0 but none is
installed. You must install peer dependencies yourself.
npm WARN @angular/material@8.1.1
requires a peer of @angular/core@^8.0.0 || ^9.0.0-0 but none is installed. You
must install peer dependencies yourself.
npm WARN @angular/material@8.1.1
requires a peer of @angular/common@^8.0.0 || ^9.0.0-0 but none is installed.
You must install peer dependencies yourself.
npm WARN @angular/material@8.1.1
requires a peer of @angular/forms@^8.0.0 || ^9.0.0-0 but none is installed. You
must install peer dependencies yourself.
npm WARN optional SKIPPING OPTIONAL
DEPENDENCY: fsevents@1.2.4 (node_modules\fsevents):
npm WARN notsup SKIPPING OPTIONAL
DEPENDENCY: Unsupported platform for fsevents@1.2.4: wanted
{"os":"darwin","arch":"any"} (current:
{"os":"win32","arch":"x64"})
+ @angular/cdk@8.1.1
+ @angular/material@8.1.1
updated 2 packages and audited 40183
packages in 19.079s
found 607 vulnerabilities (2 low, 5
moderate, 600 high)
run `npm audit fix` to fix them, or `npm audit` for details
PS D:\Angular-DemoApps\Angular 6 -
Dynamic tab implementation with Multiple component (Component as a tab, Eager
loading)>
After completing the installation we are going to use our previous login and registration pages(pages
that we use in all our posts), That will be loaded into tab or will act as a
tab.
Following are the login
and registration component firstpage and secondpage component respectively.
The only difference is we
are going to add module and routing module to each component to make them load
lazily.
first-page.component.html
<div
class="container">
<form
#frm="ngForm"
id="frm"><br><br>
<div
class="row">
<div
class="col-lg-4"></div>
<div
class="col-lg-6">
<h3>Login</h3>
<table>
<thead>
<th>
Login Name
</th>
<th>
<input
class="form-control"
type="text"
[(ngModel)]="user.userName"
name="userName"
required>
</th>
</thead>
<tbody>
<tr>
<th>
Password
</th>
<td><input
class="form-control"
type="password"
[(ngModel)]="user.password"
name="password"></td>
</tr>
<tr>
<td>
</td>
<td>
<button
class="btn
btn-primary">Login</button>
<button
class="btn
btn-light">Register Me</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</form>
</div>
first-page.component.ts
import
{ Component, OnInit,
HostListener, ViewChild
} from '@angular/core';
import
{ Router } from
'@angular/router';
import
{ User } from
'../user/user';
import
{ HomepageComponent
} from '../homepage/homepage.component';
@Component({
selector: 'app-first-page',
templateUrl: './first-page.component.html',
styleUrls: ['./first-page.component.css']
})
export
class FirstPageComponent
implements OnInit
{
user: User;
constructor(private
homePagereferance:
HomepageComponent)
{ }
ngOnInit() {
this.user
= new User();
}
}
first-page-routing.module.ts
import
{ NgModule } from
'@angular/core';
import
{ Routes, RouterModule
} from '@angular/router';
import
{ FirstPageComponent
} from './first-page.component';
const
routes: Routes
= [
{ path: '',
component: FirstPageComponent
}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export
class FirstPageRoutingModule
{ }
second-page.components.html
<div
class="container">
<div
class="row">
<div
class="col-lg-4"></div>
<div
class="col-lg-6">
<form
action=""
#frm="ngForm">
<h3>Registration</h3>
<table>
<tr>
<td>User
name</td>
<td><input
[(ngModel)]="user.userName"
class="form-control"
type="text"
name="userName"></td>
</tr>
<tr>
<td>password</td>
<td><input
[(ngModel)]="user.password"
class="form-control"
type="password"
name="password"></td>
</tr>
<tr>
<td>confirm
password</td>
<td><input
class="form-control"
type="password"
name="password"></td>
</tr>
<tr>
<td>email</td>
<td><input
[(ngModel)]="user.email"
class="form-control"
type="text"
name="email"></td>
</tr>
<tr>
<td>contact
No</td>
<td><input
[(ngModel)]="user.contactNo"
class="form-control"
type="text"
name="contactNo"></td>
</tr>
<tr>
<td>address</td>
<td><input
[(ngModel)]="user.address"
class="form-control"
type="text"
name="address"></td>
</tr>
<tr>
<td></td>
<td><button
class="btn
btn-light">Cancel</button> <button
class="btn
btn-primary">save</button></td>
</tr>
</table>
</form>
</div>
</div>
</div>
second-page.component.ts
import
{ Component, OnInit,
ViewChild } from
'@angular/core';
import
{ User } from
'../user/user';
@Component({
selector: 'app-second-page',
templateUrl: './second-page.component.html',
styleUrls: ['./second-page.component.css']
})
export
class SecondPageComponent
implements OnInit
{
user: User;
constructor() { }
ngOnInit() {
this.user
= new User();
}
}
second-page-routing.module.ts
import
{ NgModule } from
'@angular/core';
import
{ Routes, RouterModule
} from '@angular/router';
import
{ SecondPageComponent
} from './second-page.component';
const
routes: Routes
= [
{ path: '',
component: SecondPageComponent
}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export
class SecondPageRoutingModule
{ }
Above are the components
containing simple code to show login and registration page respectively, which
will act as a tab.
But these components cannot
act as a tab directly. Let’s make them capable to become a tab.
We are going to use
routing to show/load components, here we have a parent(homepage) component
which will be loaded at very first. Then we are going to use child routing,
that loads the component as a child of parent(homepage) component.
But where are the tabs…,
So we are going to maintain the navbar which will act as a tab and on click of
each navigation, child component will be loaded into router-outlet.
We have used two
router-outlet and hence two main routing files
1 App-routing to load home page
2 Homepage-routing where another component will be
loaded as a child
It will be very clear
when we will look at the code.
homepage.component.html
<div
class="container
no-gutters">
<div
class="row">
<div
class="col-lg-12">
<h2>Welcome
to dynamic tab with multiple component demo</h2>
</div>
<div
class="col-lg-12">
<a
class="btn
btn-outline-light" (click)="openLogin()">open
login
component in
tab</a>
<a
class="btn
btn-outline-light" (click)="openReg()">open
registeration
component tab</a>
<a
class="btn
btn-outline-light" (click)="duplicate()">
Duplicate</a>
</div>
</div>
<div
class="row
no-gutters">
<nav
mat-tab-nav-bar mainTabs
class="scroll
mainTabs">
<a
*ngFor="let
link of navLinks; let i = index" mat-tab-link
class="tab-font"
(click)="activate(i);"
[active]="activeLinkIndex
== i" title="{{link.label}}">
<span>{{link.label}}</span>
<span
(click)="closeTab(i)">
<i>X</i>
</span>
</a>
</nav>
</div>
<div
class="row
no-gutters">
<div
class="col-lg-12">
<router-outlet></router-outlet>
</div>
</div>
</div>
As
discussed, we have used a navigation bar to show the tab list and added title and
close button by ourselves, below that there is router-outlet to load the content of
each tab means load particular component belonging to that route.
Now
let’s check real business, here we have declared one array which will be used
for maintaining tab-list and managed our logic to add a tab, remove tab and
maintain the selected tab as well. Here on click of a tab, we are going to load the
component by using navigation method and, the list of components to load and
their paths are configured into homepage-routing file.
You can understand it easily by looking at
code.
homepage.component.ts
import
{ Component, OnInit
} from '@angular/core';
import
{ Router } from
'@angular/router';
@Component({
selector: 'app-homepage',
templateUrl: './homepage.component.html',
styleUrls: ['./homepage.component.css']
})
export
class HomepageComponent
implements OnInit
{
activeLinkIndex = -1;
navLinks: any[];
link: any;
counter: number
= 0;
constructor(private
router: Router)
{ }
ngOnInit() {
this.navLinks
= [];
}
openLogin() {
let tabTitle
= "First Page";
let tabId
= "firstpage";
let param
= undefined;
this.openTab(tabTitle,
tabId, undefined);
}
openReg() {
let tabTitle
= "Second Page";
let tabId
= "secondpage";
let param
= undefined;
this.openTab(tabTitle,
tabId, undefined);
}
duplicate() {
this.counter++;
let tabTitle
= "duplicate "
+ this.counter;
let tabId
= "firstpage";
let param
= "allow duplicate";
this.openTab(tabTitle,
tabId, param);
}
public openTab(heading:
String, route:
String, parameter:
any) {
console.log("openTab
in layout... IN");
var res;
var navFlag
= false;
var tabLength
= this.navLinks.length;
if (parameter
!= undefined && parameter
== "allow duplicate")
{
navFlag = false;
} else {
for (var
i = 0;
i < this.navLinks.length;
i++) {
if
(this.navLinks[i].link
== route) {
navFlag
= true;
if
(!this.navLinks[i].parameter
|| parameter == "allow
duplicate") {
this.router.navigate([this.navLinks[i].link]).then(nav
=> {
if
(nav) {
this.activeLinkIndex
= i;
}
});
this.activeLinkIndex
= i;
} else
{
this.router
.navigate([
this.navLinks[i].link,
this.navLinks[i].parameter
])
.then(nav
=> {
if
(nav) {
this.activeLinkIndex
= i;
}
});
this.activeLinkIndex
= i;
}
break;
} else
{
navFlag
= false;
}
}
}
if (navFlag
== false) {
this.navLinks.push({
label:
heading,
link:
route,
index:
tabLength,
parameter:
parameter
});
var i
= this.navLinks.length
- 1;
if (!this.navLinks[i].parameter
|| parameter == "allow
duplicate") {
this.router
.navigate([this.navLinks[tabLength].link])
.then(nav
=> {
navFlag
= true;
if
(nav) {
this.activeLinkIndex
= tabLength;
}
});
this.activeLinkIndex
= tabLength;
} else {
this.router
.navigate([
this.navLinks[tabLength].link,
this.navLinks[i].parameter
])
.then(nav
=> {
navFlag
= true;
if
(nav) {
this.activeLinkIndex
= tabLength;
}
});
this.activeLinkIndex
= tabLength;
}
}
console.log("openTab
in layout... OUT");
}
closeTab(index:
number) {
console.log("closeTab
in layout component... IN");
var maxTabIndex;
var res;
if (this.activeLinkIndex
== index) {
maxTabIndex = this.navLinks.length
- 1;
if (maxTabIndex
== 0) {
this.navLinks.splice(index,
1);
res
= this.router.navigate(["/"]);
} else if
(index < maxTabIndex)
{
this.navLinks.splice(index,
1);
res
= this.router
.navigate([this.navLinks[index].link])
.then(nav
=> {
if
(nav) {
this.activeLinkIndex
= index;
}
});
this.activeLinkIndex
= index;
} else if
(index == maxTabIndex)
{
this.navLinks.splice(index,
1);
maxTabIndex
= this.navLinks.length
- 1;
res
= this.router
.navigate([this.navLinks[maxTabIndex].link])
.then(nav
=> {
if
(nav) {
this.activeLinkIndex
= maxTabIndex;
}
});
this.activeLinkIndex
= maxTabIndex;
} else {
res
= this.router.navigate(["/"]);
}
} else if
(this.activeLinkIndex
> index) {
this.navLinks.splice(index,
1);
this.activeLinkIndex
= this.activeLinkIndex
- 1;
} else {
this.navLinks.splice(index,
1);
}
if (this.navLinks.length
<= 0) {
this.router.navigate(['homePage'])
}
console.log("closeTab
in layout component... OUT");
}
closeOtherTabs(index:
number) {
console.log("closeOtherTabs
in layout component... IN");
this.link
= this.navLinks[index];
this.navLinks
= [];
this.navLinks.push(this.link);
this.router.navigate([this.navLinks[0].link]).then(nav
=> {
if (nav)
{
this.activeLinkIndex
= 0;
}
});
this.activeLinkIndex
= 0;
this.link
= undefined;
console.log("closeOtherTabs
in layout component... OUT");
}
closeAllTabs() {
console.log("closeAllTabs
in layout component... IN");
this.activeLinkIndex
= -1;
this.router.navigate(["/"]);
this.navLinks
= [];
console.log("closeAllTabs
in layout component... OUT");
}
activate(index:
number) {
console.log("activate
in layout component... IN");
if (!this.navLinks[index].parameter)
{
this.router.navigate([this.navLinks[index].link]).then(nav
=> {
if
(nav) {
this.activeLinkIndex
= index;
}
});
this.activeLinkIndex
= index;
} else {
this.router
.navigate([
this.navLinks[index].link,
this.navLinks[index].parameter
])
.then(nav
=> {
if
(nav) {
this.activeLinkIndex
= index;
}
});
this.activeLinkIndex
= index;
}
console.log("activate
in layout component... OUT");
}
}
Homepage-routing.module.ts
import
{ NgModule } from
'@angular/core';
import
{ Routes, RouterModule
} from '@angular/router';
import
{ HomepageComponent
} from './homepage.component';
const
routes: Routes
= [
{
path: '',
component: HomepageComponent,
children:
[
{ path:
'firstpage', loadChildren:
'../first-page/first-page.module#FirstPageModule'
},
{ path:
'secondpage', loadChildren:
'../second-page/second-page.module#SecondPageModule'
}
]
},
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export
class HomepageRoutingModule
{ }
Here is the output
And that’s it,
we have successfully
created dynamic tab logic into our angular project.
Note: this is the logic
where we have used routing to load the components, so it will not save/maintain
the state of tab. You have to made some provision while switching the tab,
either you can ask user to save the data before switching the tab or you can
save the state of tab in to the local storage and restore when come back.
Hope you like it.
Here is the link to download the project
if you want to use eagerly loaded tabs and maintained state please check our prev post here -tab with maintained state
Comments
Post a Comment