
Syncfusion Angular Tab
- 214 installs
- Updated August 4, 2026
- syncfusion/angular-ui-components-skills
Use syncfusion-angular-tab for development tasks
About
syncfusion-angular-tab: A skill for development. This provides functionality for development workflows.
- syncfusion-angular-tab
Syncfusion Angular Tab by the numbers
- 214 all-time installs (skills.sh)
- +12 installs in the week ending Aug 5, 2026 (Skillselion tracking)
- Ranked #1,879 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/syncfusion/angular-ui-components-skills --skill syncfusion-angular-tabAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 214 |
|---|---|
| Last updated | August 4, 2026 |
| Repository | syncfusion/angular-ui-components-skills ↗ |
What it does
Use syncfusion-angular-tab for development tasks
Files
Implementing Syncfusion Angular Tabs
The Syncfusion Angular Tab component provides a flexible, responsive way to organize and display content in a tabbed interface. Tabs allow users to switch between multiple content panels, making it ideal for multi-step workflows, settings panels, navigation structures, and organized data displays.
When to Use This Skill
- Building tabbed navigation: Organize related content into separate tabs with easy switching
- Multi-step workflows: Create wizard-like experiences or step-by-step processes
- Responsive layouts: Adapt tab display for mobile, tablet, and desktop screens (different orientations)
- Dynamic content management: Add, remove, reorder, or drag-drop tabs at runtime
- Complex applications: Integrate Grids, Charts, Calendars, Forms within tabs
- State preservation: Save and restore user selections across browser sessions
- Localized applications: Support multiple languages with localized UI text
- Multiple orientations: Vertical or horizontal tab headers based on layout needs
- Large data sets: Load tabs from remote APIs or databases
- Customized UI: Style headers, icons, animations, overflow modes, and content areas
- User preferences: Remember last selected tab with state persistence
Navigation Guide
Getting Started
📄 Read: references/getting-started.md
- Package installation and setup
- Angular CLI configuration (standalone vs module)
- CSS imports and theme setup
- Three rendering methods: JSON items, ng-template, HTML elements
- Basic usage examples and minimal setup
Content Rendering Modes
📄 Read: references/rendering-modes.md
- On Demand (default): Load active tab content, preserve state
- Dynamic: Memory-optimized, single content in DOM
- Init: All content upfront, access between tabs
- Performance tradeoffs and use case selection
- Code examples for each mode
Content Management & Data
📄 Read: references/content-management.md
- DataSource binding and data-driven tabs
- Dynamic tab addition/removal (.addTab(), .removeTab())
- AJAX and server-side content loading
- Show/hide tabs dynamically
- Reactive forms within tabs (FormGroup, FormControl)
- Content reuse with TemplateRef
Tab Selection & Navigation
📄 Read: references/selection-navigation.md
- Tab selection events (selecting, selected)
- Programmatic tab selection (.select() method)
- Determining user vs programmatic selection (isInteracted)
- Keyboard navigation (Tab key, arrow keys, Enter/Space)
- Click handlers and selection callbacks
Customization & Styling
📄 Read: references/customization-styling.md
- Header styles and CSS classes (fill, background, accent)
- Icon positioning and icon CSS customization
- Content height management and auto-sizing
- Scroll step configuration
- Animation control and custom animations
- CSS customization guide for advanced styling
- Responsive header display
Drag and Drop Reordering
📄 Read: references/drag-and-drop-reordering.md
- Enable drag and drop with
allowDragAndDropproperty - Configure drag area with
dragAreaproperty - Handle
onDragStart,dragging,draggedevents - Prevent dragging/dropping specific tabs
- Reorder active tab in popup overflow mode
- Drag items between multiple Tab components
- Drag Tab items to external components (TreeView, etc.)
Localization and Orientation
📄 Read: references/localization-and-orientation.md
- Localize UI text using
localeproperty and L10n class - Header placement options (Top, Bottom, Left, Right)
- Overflow modes (Scrollable vs Popup)
scrollStepconfiguration for scroll speed- Responsive orientation changes based on screen size
- Multi-language support with custom translations
State Persistence and Data Binding
📄 Read: references/state-persistence-and-data-binding.md
- Enable persistence with
enablePersistenceproperty - Automatic state saving to browser localStorage
- Binding tabs to data sources (arrays, APIs, DataManager)
- Loading tab data from remote servers (OData, HTTP)
- Load tab content through POST requests
- Combined persistence with dynamic data binding
Advanced Scenarios
📄 Read: references/advanced-scenarios.md
- Nested tabs (multiple levels of tabbed content)
- Collapsible tabs with accordion-like behavior
- Wizard pattern implementation (step validation)
- State persistence with LocalStorage/SessionStorage
- Component initialization from events
- Edge cases and best practices
Responsive & Adaptive Behavior
📄 Read: references/responsive-adaptive.md
- Adaptive rendering for constrained spaces
- Overflow modes: Scrollable and Popup
- Responsive tab display on mobile/desktop
- Orientation changes (horizontal/vertical)
- Prevent swipe selection on touch devices
- Width and height constraints
Troubleshooting & Best Practices
📄 Read: references/troubleshooting.md
- Common implementation issues and solutions
- Performance optimization techniques
- Content rendering gotchas
- State management pitfalls
- Accessibility and keyboard support
- Animation performance
- Migration from EJ1 to EJ2
Quick Start Example
Basic Tab with multiple content panels:
import { Component } from '@angular/core';
import { TabModule } from '@syncfusion/ej2-angular-navigations';
@Component({
imports: [TabModule],
standalone: true,
selector: 'app-root',
template: `
<ejs-tab id="element">
<e-tabitems>
<e-tabitem [header]="headerText[0]" [content]="content0"></e-tabitem>
<e-tabitem [header]="headerText[1]" [content]="content1"></e-tabitem>
<e-tabitem [header]="headerText[2]" [content]="content2"></e-tabitem>
</e-tabitems>
</ejs-tab>
`
})
export class AppComponent {
public headerText: object[] = [
{ text: 'Home' },
{ text: 'Settings' },
{ text: 'Profile' }
];
public content0 = 'Welcome to the home tab with your dashboard content.';
public content1 = 'Configure application settings and preferences here.';
public content2 = 'View and edit your user profile information.';
}Common Patterns
Pattern 1: Dynamic Tab Creation
// Add new tab at specific index
const newTab = {
header: { text: 'New Tab' },
content: 'New content here'
};
this.tabObj.addTab([newTab], 2);Pattern 2: Selection with Event Handling
// Handle tab selection changes
onTabSelected(args: SelectEventArgs) {
console.log('Selected tab index:', args.selectedIndex);
// Refresh data for selected tab
this.loadDataForTab(args.selectedIndex);
}Pattern 3: Responsive Tab Header with Icons
public headerText: object[] = [
{ text: 'Dashboard', iconCss: 'e-icons e-home' },
{ text: 'Products', iconCss: 'e-icons e-shopping-cart' },
{ text: 'Orders', iconCss: 'e-icons e-receipt' }
];Pattern 4: Content Rendering Mode Selection
// Use Dynamic mode for memory optimization
<ejs-tab
loadOn="Dynamic" // Only active content in DOM
heightAdjustMode="Auto">
</ejs-tab>Pattern 5: Keyboard Navigation
// Tab component has built-in keyboard support
// Tab key - focus next header
// Arrow keys - navigate between tabs
// Enter/Space - activate tab
// Users can switch tabs without mouseKey Properties & Methods
Common Properties:
items- Tab item collectionselectedIndex- Currently active tab indexheightAdjustMode- How content height is managedloadOn- Content rendering mode (Default, Dynamic, Init)overflowMode- Handling overflow (Scrollable, Popup)animation- Animation configurationallowDragAndDrop- Enable/disable drag-drop (boolean)dragArea- CSS selector for drag boundaryreorderActiveTab- Reorder active from popup (boolean)headerPlacement- Header position (Top, Bottom, Left, Right)enablePersistence- Save/restore state (boolean)locale- Language/culture code (e.g., 'en-US', 'fr-FR')scrollStep- Pixels to scroll per click
Key Methods:
.select(index)- Programmatically select tab.addTab(items, index)- Add new tabs.removeTab(index)- Remove tab.hideTab(index, hide)- Hide/show tab (hide=true to hide, false to show).refresh()- Refresh tab layout.dataBind()- Update data binding
Important Events:
selecting- Before tab selectionselected- After tab selectiononDragStart- Before drag begins (cancellable)dragging- During drag operationdragged- After drop completes (cancellable)created- Tab component initializeddestroyed- Tab component destroyed
Related Skills
- Implementing Buttons - For button styling in tab headers
- Implementing Forms - For reactive forms within tabs
- Implementing Grids - For data display in tabs
---
Next Step: Based on your needs, read the appropriate reference file from the navigation guide above.
Advanced Scenarios
Table of Contents
Nested Tabs
Create multiple levels of tabbed content:
Basic Nested Tabs
import { Component } from '@angular/core';
import { TabModule } from '@syncfusion/ej2-angular-navigations';
@Component({
imports: [TabModule],
standalone: true,
selector: 'app-root',
template: `
<ejs-tab id="parent-tab">
<e-tabitems>
<!-- Parent Tab 1 with nested tabs -->
<e-tabitem [header]="{ text: 'Home' }">
<ng-template #content>
<ejs-tab id="nested-tab-1">
<e-tabitems>
<e-tabitem [header]="{ text: 'Overview' }"
content="Home overview content"></e-tabitem>
<e-tabitem [header]="{ text: 'Details' }"
content="Home details content"></e-tabitem>
</e-tabitems>
</ejs-tab>
</ng-template>
</e-tabitem>
<!-- Parent Tab 2 with nested tabs -->
<e-tabitem [header]="{ text: 'Settings' }">
<ng-template #content>
<ejs-tab id="nested-tab-2">
<e-tabitems>
<e-tabitem [header]="{ text: 'General' }"
content="General settings"></e-tabitem>
<e-tabitem [header]="{ text: 'Advanced' }"
content="Advanced settings"></e-tabitem>
<e-tabitem [header]="{ text: 'Security' }"
content="Security settings"></e-tabitem>
</e-tabitems>
</ejs-tab>
</ng-template>
</e-tabitem>
<!-- Parent Tab 3 -->
<e-tabitem [header]="{ text: 'About' }" content="About content"></e-tabitem>
</e-tabitems>
</ejs-tab>
`
})
export class AppComponent { }Three-Level Nesting
<ejs-tab id="level-1">
<e-tabitems>
<e-tabitem [header]="{ text: 'Products' }">
<ng-template #content>
<!-- Level 2 -->
<ejs-tab id="level-2">
<e-tabitems>
<e-tabitem [header]="{ text: 'Electronics' }">
<ng-template #content>
<!-- Level 3 -->
<ejs-tab id="level-3">
<e-tabitems>
<e-tabitem [header]="{ text: 'Phones' }"
content="Phones content"></e-tabitem>
<e-tabitem [header]="{ text: 'Tablets' }"
content="Tablets content"></e-tabitem>
</e-tabitems>
</ejs-tab>
</ng-template>
</e-tabitem>
<e-tabitem [header]="{ text: 'Clothing' }"
content="Clothing content"></e-tabitem>
</e-tabitems>
</ejs-tab>
</ng-template>
</e-tabitem>
</e-tabitems>
</ejs-tab>Dynamic Nested Tab Initialization
import { Component, ViewChild } from '@angular/core';
import { TabModule, TabComponent, SelectEventArgs } from '@syncfusion/ej2-angular-navigations';
@Component({
imports: [TabModule],
standalone: true,
selector: 'app-root',
template: `
<ejs-tab #parentTab (selected)="onParentTabSelected($event)">
<e-tabitems>
<e-tabitem [header]="{ text: 'Documents' }">
<ng-template #content>
<ejs-tab #nestedTab1 id="nested-1"></ejs-tab>
</ng-template>
</e-tabitem>
<e-tabitem [header]="{ text: 'Images' }">
<ng-template #content>
<ejs-tab #nestedTab2 id="nested-2"></ejs-tab>
</ng-template>
</e-tabitem>
</e-tabitems>
</ejs-tab>
`
})
export class AppComponent {
@ViewChild('parentTab') parentTab?: TabComponent;
@ViewChild('nestedTab1') nestedTab1?: TabComponent;
@ViewChild('nestedTab2') nestedTab2?: TabComponent;
onParentTabSelected(args: SelectEventArgs) {
// Initialize nested tabs on demand
if (args.selectedIndex === 0) {
this.initializeDocumentTabs();
} else if (args.selectedIndex === 1) {
this.initializeImageTabs();
}
}
private initializeDocumentTabs() {
if (this.nestedTab1) {
(this.nestedTab1 as TabComponent).items = [
{ header: { text: 'Word' }, content: 'Word documents' },
{ header: { text: 'PDF' }, content: 'PDF documents' },
{ header: { text: 'Excel' }, content: 'Excel files' }
];
}
}
private initializeImageTabs() {
if (this.nestedTab2) {
(this.nestedTab2 as TabComponent).items = [
{ header: { text: 'JPG' }, content: 'JPG images' },
{ header: { text: 'PNG' }, content: 'PNG images' },
{ header: { text: 'GIF' }, content: 'GIF images' }
];
}
}
}Collapsible Tabs
Create accordion-like collapse/expand functionality:
Collapsible Tab Implementation
import { Component, ViewChild } from '@angular/core';
import { TabModule, TabComponent, SelectEventArgs } from '@syncfusion/ej2-angular-navigations';
import { CommonModule } from '@angular/common';
@Component({
imports: [TabModule, CommonModule],
standalone: true,
selector: 'app-root',
template: `
<style>
.collapsed { display: none; }
</style>
<ejs-tab #tabObj (created)="onTabCreated()" (selected)="onTabSelected($event)">
<e-tabitems>
<e-tabitem [header]="{ text: 'Section 1' }">
<ng-template #content>
<div class="content">Content for section 1</div>
</ng-template>
</e-tabitem>
<e-tabitem [header]="{ text: 'Section 2' }">
<ng-template #content>
<div class="content">Content for section 2</div>
</ng-template>
</e-tabitem>
<e-tabitem [header]="{ text: 'Section 3' }">
<ng-template #content>
<div class="content">Content for section 3</div>
</ng-template>
</e-tabitem>
</e-tabitems>
</ejs-tab>
`
})
export class AppComponent {
@ViewChild('tabObj') tabObj?: TabComponent;
private expandedIndex = 0;
onTabCreated() {
// Initially collapse all except first
const contentElements = document.querySelectorAll('.e-content .e-item');
contentElements.forEach((elem, index) => {
if (index !== this.expandedIndex) {
elem.classList.add('collapsed');
}
});
}
onTabSelected(args: SelectEventArgs) {
const contentElements = document.querySelectorAll('.e-content .e-item');
// Collapse previously expanded
if (this.expandedIndex < contentElements.length) {
contentElements[this.expandedIndex].classList.add('collapsed');
}
// Expand selected
contentElements[args.selectedIndex!].classList.remove('collapsed');
this.expandedIndex = args.selectedIndex!;
}
}Smooth Collapse/Expand with Animation
.e-content .e-item {
max-height: 500px;
overflow: hidden;
transition: max-height 0.3s ease-out;
}
.e-content .e-item.collapsed {
max-height: 0;
overflow: hidden;
}Wizard Pattern
Create multi-step wizard using tabs:
Basic Wizard
import { Component, ViewChild } from '@angular/core';
import { TabModule, TabComponent, SelectingEventArgs } from '@syncfusion/ej2-angular-navigations';
import { FormBuilder, FormGroup, Validators, ReactiveFormsModule } from '@angular/forms';
import { CommonModule } from '@angular/common';
@Component({
imports: [TabModule, ReactiveFormsModule, CommonModule],
standalone: true,
selector: 'app-root',
template: `
<div style="margin: 20px;">
<ejs-tab #wizardTab (selecting)="onTabSelecting($event)">
<e-tabitems>
<!-- Step 1: Personal Information -->
<e-tabitem [header]="{ text: 'Step 1: Personal' }">
<ng-template #content>
<form [formGroup]="step1Form" style="padding: 20px;">
<div style="margin: 10px 0;">
<label>Name:</label>
<input type="text" formControlName="name" />
<span *ngIf="step1Form.get('name')?.hasError('required')"
style="color: red;">Name required</span>
</div>
<div style="margin: 10px 0;">
<label>Email:</label>
<input type="email" formControlName="email" />
<span *ngIf="step1Form.get('email')?.hasError('email')"
style="color: red;">Valid email required</span>
</div>
</form>
</ng-template>
</e-tabitem>
<!-- Step 2: Address -->
<e-tabitem [header]="{ text: 'Step 2: Address' }">
<ng-template #content>
<form [formGroup]="step2Form" style="padding: 20px;">
<div style="margin: 10px 0;">
<label>Street:</label>
<input type="text" formControlName="street" />
</div>
<div style="margin: 10px 0;">
<label>City:</label>
<input type="text" formControlName="city" />
</div>
</form>
</ng-template>
</e-tabitem>
<!-- Step 3: Confirmation -->
<e-tabitem [header]="{ text: 'Step 3: Confirm' }">
<ng-template #content>
<div style="padding: 20px;">
<h3>Confirm Your Information</h3>
<p><strong>Name:</strong> {{ step1Form.get('name')?.value }}</p>
<p><strong>Email:</strong> {{ step1Form.get('email')?.value }}</p>
<p><strong>Address:</strong> {{ step2Form.get('street')?.value }},
{{ step2Form.get('city')?.value }}</p>
<button (click)="submitWizard()">Submit</button>
</div>
</ng-template>
</e-tabitem>
</e-tabitems>
</ejs-tab>
<div style="margin: 20px;">
<button (click)="previousStep()" [disabled]="currentStep === 0">Previous</button>
<button (click)="nextStep()" [disabled]="currentStep === 2">Next</button>
</div>
</div>
`
})
export class AppComponent {
@ViewChild('wizardTab') wizardTab?: TabComponent;
public currentStep = 0;
public step1Form!: FormGroup;
public step2Form!: FormGroup;
constructor(private fb: FormBuilder) {
this.step1Form = this.fb.group({
name: ['', Validators.required],
email: ['', [Validators.required, Validators.email]]
});
this.step2Form = this.fb.group({
street: ['', Validators.required],
city: ['', Validators.required]
});
}
onTabSelecting(args: SelectingEventArgs) {
// Validate current step before allowing navigation
if (args.selectedIndex! > this.currentStep) {
if (!this.validateCurrentStep()) {
args.cancel = true;
}
}
this.currentStep = args.selectedIndex!;
}
private validateCurrentStep(): boolean {
if (this.currentStep === 0) {
return this.step1Form.valid;
} else if (this.currentStep === 1) {
return this.step2Form.valid;
}
return true;
}
previousStep() {
if (this.currentStep > 0) {
(this.wizardTab as TabComponent).select(this.currentStep - 1);
}
}
nextStep() {
if (this.validateCurrentStep() && this.currentStep < 2) {
(this.wizardTab as TabComponent).select(this.currentStep + 1);
}
}
submitWizard() {
if (this.step1Form.valid && this.step2Form.valid) {
const data = {
personal: this.step1Form.value,
address: this.step2Form.value
};
console.log('Wizard submission:', data);
// Send to server
}
}
}State Persistence
Save and restore tab state across sessions:
LocalStorage Persistence
import { Component, ViewChild, OnInit } from '@angular/core';
import { TabModule, TabComponent, SelectEventArgs } from '@syncfusion/ej2-angular-navigations';
@Component({
imports: [TabModule],
standalone: true,
selector: 'app-root',
template: `
<ejs-tab #tabObj (selected)="onTabSelected($event)">
<e-tabitems>
<e-tabitem [header]="{ text: 'Tab 1' }" content="Content 1"></e-tabitem>
<e-tabitem [header]="{ text: 'Tab 2' }" content="Content 2"></e-tabitem>
<e-tabitem [header]="{ text: 'Tab 3' }" content="Content 3"></e-tabitem>
</e-tabitems>
</ejs-tab>
`
})
export class AppComponent implements OnInit {
@ViewChild('tabObj') tabObj?: TabComponent;
ngOnInit() {
this.restoreTabState();
}
onTabSelected(args: SelectEventArgs) {
// Save selected index
localStorage.setItem('selectedTabIndex', args.selectedIndex!.toString());
}
private restoreTabState() {
const savedIndex = localStorage.getItem('selectedTabIndex');
if (savedIndex) {
setTimeout(() => {
(this.tabObj as TabComponent).select(parseInt(savedIndex, 10));
}, 0);
}
}
}SessionStorage with Full State
import { Component, OnInit, ViewChild } from '@angular/core';
import { TabModule, TabComponent, SelectEventArgs } from '@syncfusion/ej2-angular-navigations';
interface TabState {
selectedIndex: number;
tabs: object[];
timestamp: number;
}
@Component({
imports: [TabModule],
standalone: true,
selector: 'app-root',
template: `
<ejs-tab #tabObj (selected)="onTabSelected($event)">
<e-tabitems>
<e-tabitem [header]="{ text: 'Favorites' }"></e-tabitem>
<e-tabitem [header]="{ text: 'Recent' }"></e-tabitem>
<e-tabitem [header]="{ text: 'Archived' }"></e-tabitem>
</e-tabitems>
</ejs-tab>
`
})
export class AppComponent implements OnInit {
@ViewChild('tabObj') tabObj?: TabComponent;
ngOnInit() {
this.restoreState();
}
onTabSelected(args: SelectEventArgs) {
this.saveState();
}
private saveState() {
const tab = this.tabObj as TabComponent;
const state: TabState = {
selectedIndex: tab.selectedIndex!,
tabs: tab.items || [],
timestamp: Date.now()
};
sessionStorage.setItem('tabState', JSON.stringify(state));
}
private restoreState() {
const saved = sessionStorage.getItem('tabState');
if (saved) {
const state: TabState = JSON.parse(saved);
// Check if state is not too old (1 hour)
if (Date.now() - state.timestamp < 3600000) {
setTimeout(() => {
const tab = this.tabObj as TabComponent;
if (state.tabs.length > 0) {
tab.items = state.tabs;
}
tab.select(state.selectedIndex);
}, 0);
}
}
}
}Component Integration
Integrate complex Angular components inside tabs:
Grid Inside Tab
import { Component } from '@angular/core';
import { TabModule } from '@syncfusion/ej2-angular-navigations';
import { GridAllModule } from '@syncfusion/ej2-angular-grids';
@Component({
imports: [TabModule, GridAllModule],
standalone: true,
selector: 'app-root',
template: `
<ejs-tab>
<e-tabitems>
<e-tabitem [header]="{ text: 'Orders' }">
<ng-template #content>
<ejs-grid id="grid" [dataSource]="orderData">
<e-columns>
<e-column field="OrderID" headerText="Order ID" width="150"></e-column>
<e-column field="CustomerName" headerText="Customer Name" width="150"></e-column>
<e-column field="TotalAmount" headerText="Total" width="120"></e-column>
</e-columns>
</ejs-grid>
</ng-template>
</e-tabitem>
</e-tabitems>
</ejs-tab>
`
})
export class AppComponent {
public orderData: object[] = [
{ OrderID: 10248, CustomerName: 'John', TotalAmount: 500 },
{ OrderID: 10249, CustomerName: 'Jane', TotalAmount: 750 },
{ OrderID: 10250, CustomerName: 'Bob', TotalAmount: 600 }
];
}Form and Calendar Integration
import { Component } from '@angular/core';
import { TabModule } from '@syncfusion/ej2-angular-navigations';
import { CalendarAllModule } from '@syncfusion/ej2-angular-calendars';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
@Component({
imports: [TabModule, CalendarAllModule, FormsModule, ReactiveFormsModule],
standalone: true,
selector: 'app-root',
template: `
<ejs-tab>
<e-tabitems>
<e-tabitem [header]="{ text: 'Event Registration' }">
<ng-template #content>
<form style="padding: 20px;">
<div>
<label>Name:</label>
<input type="text" />
</div>
<div>
<label>Event Date:</label>
<ejs-calendar [(value)]="selectedDate"></ejs-calendar>
</div>
</form>
</ng-template>
</e-tabitem>
</e-tabitems>
</ejs-tab>
`
})
export class AppComponent {
public selectedDate: Date = new Date();
}Best Practices for Advanced Scenarios
1. Nested Tabs: Limit nesting to 2-3 levels for usability 2. Wizard: Validate each step before proceeding 3. State Persistence: Use appropriate storage (localStorage vs sessionStorage) 4. Complex Components: Load on demand (Dynamic mode) to improve performance 5. Memory Management: Unsubscribe from observables in ngOnDestroy 6. Accessibility: Maintain proper focus management in nested structures
Content Management & Data Integration
Table of Contents
- Overview
- DataSource Binding
- Dynamic Tab Operations
- AJAX Content Loading
- Reactive Forms in Tabs
- Content Reuse with TemplateRef
- Show/Hide Tabs
Overview
The Tab component provides multiple ways to manage and load content dynamically:
- Bind from data sources
- Add/remove tabs at runtime
- Load content via AJAX/server
- Use reactive forms with tabs
- Reuse templates efficiently
- Show/hide tabs programmatically
DataSource Binding
Bind tab items directly from an array of objects:
Basic DataSource Example
import { Component } from '@angular/core';
import { TabModule } from '@syncfusion/ej2-angular-navigations';
@Component({
imports: [TabModule],
standalone: true,
selector: 'app-root',
template: `
<ejs-tab [items]="tabItems"></ejs-tab>
`
})
export class AppComponent {
public tabItems: object[] = [
{
header: { text: 'Home' },
content: 'Welcome to home tab'
},
{
header: { text: 'Settings' },
content: 'Application settings and preferences'
},
{
header: { text: 'About' },
content: 'Information about this application'
}
];
}DataSource with Icons
public tabItems: object[] = [
{
header: { text: 'Dashboard', iconCss: 'e-icons e-home' },
content: 'Dashboard content here'
},
{
header: { text: 'Products', iconCss: 'e-icons e-shopping-cart' },
content: 'Products content here'
},
{
header: { text: 'Orders', iconCss: 'e-icons e-receipt' },
content: 'Orders content here'
}
];Dynamic DataSource from API
import { HttpClient } from '@angular/common/http';
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-root',
imports: [TabModule, HttpClientModule],
standalone: true,
template: `<ejs-tab [items]="tabItems"></ejs-tab>`
})
export class AppComponent implements OnInit {
public tabItems: object[] = [];
constructor(private http: HttpClient) { }
ngOnInit() {
// Load tabs from API
this.http.get<object[]>('/api/tabs').subscribe(data => {
this.tabItems = data;
});
}
}Dynamic Tab Operations
Add Tabs at Runtime
The addTab() method adds new tabs at specified index:
import { Component, ViewChild } from '@angular/core';
import { TabModule, TabComponent } from '@syncfusion/ej2-angular-navigations';
import { FormsModule } from '@angular/forms';
@Component({
imports: [TabModule, FormsModule],
standalone: true,
selector: 'app-root',
template: `
<div style="margin: 20px;">
<button (click)="addTab()">Add Tab</button>
<button (click)="removeTab()">Remove Last Tab</button>
</div>
<ejs-tab #tabObj>
<e-tabitems>
<e-tabitem [header]="{ text: 'Tab 1' }" content="Content 1"></e-tabitem>
<e-tabitem [header]="{ text: 'Tab 2' }" content="Content 2"></e-tabitem>
</e-tabitems>
</ejs-tab>
`
})
export class AppComponent {
@ViewChild('tabObj') tabObj?: TabComponent;
private tabCount = 2;
addTab() {
this.tabCount++;
const newTab = {
header: { text: `Tab ${this.tabCount}` },
content: `Content for tab ${this.tabCount}`
};
// Add at end (last index)
const lastIndex = this.tabObj!.items!.length;
this.tabObj!.addTab([newTab], lastIndex);
}
removeTab() {
const lastIndex = this.tabObj!.items!.length - 1;
if (lastIndex >= 0) {
this.tabObj!.removeTab(lastIndex);
this.tabCount--;
}
}
}Add with Position
// Add at specific index
const newTab = {
header: { text: 'New Tab' },
content: 'New content'
};
const insertIndex = 1; // Insert at position 1
this.tabObj.addTab([newTab], insertIndex);
// Add multiple tabs at once
const newTabs = [
{ header: { text: 'Tab A' }, content: 'Content A' },
{ header: { text: 'Tab B' }, content: 'Content B' }
];
this.tabObj.addTab(newTabs, 2);Remove Tabs
// Remove specific tab by index
this.tabObj.removeTab(1);
// Remove multiple tabs
for (let i = 0; i < 3; i++) {
this.tabObj.removeTab(0); // Always remove first
}
// Remove all tabs except first
while (this.tabObj.items!.length > 1) {
this.tabObj.removeTab(1);
}AJAX Content Loading
Load tab content dynamically from server:
Basic AJAX Loading
import { HttpClient } from '@angular/common/http';
import { Component, OnInit } from '@angular/core';
import { TabModule } from '@syncfusion/ej2-angular-navigations';
@Component({
imports: [TabModule],
standalone: true,
selector: 'app-root',
template: `
<ejs-tab id="element">
<e-tabitems>
<e-tabitem [header]="{ text: 'Home' }">
<ng-template #content>{{ homeContent }}</ng-template>
</e-tabitem>
<e-tabitem [header]="{ text: 'About' }">
<ng-template #content>{{ aboutContent }}</ng-template>
</e-tabitem>
<e-tabitem [header]="{ text: 'Contact' }">
<ng-template #content>{{ contactContent }}</ng-template>
</e-tabitem>
</e-tabitems>
</ejs-tab>
`
})
export class AppComponent implements OnInit {
public homeContent = '';
public aboutContent = '';
public contactContent = '';
constructor(private http: HttpClient) { }
ngOnInit() {
// Load content from server
this.http.get('/api/content/home', { responseType: 'text' })
.subscribe(data => this.homeContent = data);
this.http.get('/api/content/about', { responseType: 'text' })
.subscribe(data => this.aboutContent = data);
this.http.get('/api/content/contact', { responseType: 'text' })
.subscribe(data => this.contactContent = data);
}
}Load on Tab Selection
Load content only when tab is selected:
import { Component, ViewChild } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { TabModule, TabComponent, SelectEventArgs } from '@syncfusion/ej2-angular-navigations';
@Component({
imports: [TabModule],
standalone: true,
selector: 'app-root',
template: `
<ejs-tab #tabObj (selected)="onTabSelected($event)">
<e-tabitems>
<e-tabitem [header]="{ text: 'Products' }">
<ng-template #content>
{{ productContent || 'Loading...' }}
</ng-template>
</e-tabitem>
<e-tabitem [header]="{ text: 'Orders' }">
<ng-template #content>
{{ orderContent || 'Loading...' }}
</ng-template>
</e-tabitem>
</e-tabitems>
</ejs-tab>
`
})
export class AppComponent {
@ViewChild('tabObj') tabObj?: TabComponent;
public productContent = '';
public orderContent = '';
private loadedTabs = new Set<number>();
constructor(private http: HttpClient) { }
onTabSelected(args: SelectEventArgs) {
const index = args.selectedIndex!;
// Load only if not already loaded (lazy load)
if (!this.loadedTabs.has(index)) {
if (index === 0) {
this.http.get('/api/products', { responseType: 'text' })
.subscribe(data => {
this.productContent = data;
this.loadedTabs.add(0);
});
} else if (index === 1) {
this.http.get('/api/orders', { responseType: 'text' })
.subscribe(data => {
this.orderContent = data;
this.loadedTabs.add(1);
});
}
}
}
}Reactive Forms in Tabs
Integrate Angular Reactive Forms with tabs:
Complete Form Example
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators, ReactiveFormsModule } from '@angular/forms';
import { TabModule } from '@syncfusion/ej2-angular-navigations';
import { CommonModule } from '@angular/common';
@Component({
imports: [TabModule, ReactiveFormsModule, CommonModule],
standalone: true,
selector: 'app-root',
template: `
<ejs-tab>
<e-tabitems>
<e-tabitem [header]="{ text: 'Personal Info' }">
<ng-template #content>
<form [formGroup]="personalForm">
<div>
<label>First Name:</label>
<input type="text" formControlName="firstName" />
</div>
<div>
<label>Last Name:</label>
<input type="text" formControlName="lastName" />
</div>
<div>
<label>Email:</label>
<input type="email" formControlName="email" />
</div>
</form>
</ng-template>
</e-tabitem>
<e-tabitem [header]="{ text: 'Address' }">
<ng-template #content>
<form [formGroup]="addressForm">
<div>
<label>Street:</label>
<input type="text" formControlName="street" />
</div>
<div>
<label>City:</label>
<input type="text" formControlName="city" />
</div>
<div>
<label>Country:</label>
<input type="text" formControlName="country" />
</div>
</form>
</ng-template>
</e-tabitem>
<e-tabitem [header]="{ text: 'Review' }">
<ng-template #content>
<div>
<h3>Review Your Information</h3>
<p><strong>Name:</strong> {{ personalForm.get('firstName')?.value }}
{{ personalForm.get('lastName')?.value }}</p>
<p><strong>Email:</strong> {{ personalForm.get('email')?.value }}</p>
<p><strong>Address:</strong> {{ addressForm.get('street')?.value }},
{{ addressForm.get('city')?.value }}, {{ addressForm.get('country')?.value }}</p>
<button (click)="submitForm()">Submit</button>
</div>
</ng-template>
</e-tabitem>
</e-tabitems>
</ejs-tab>
`
})
export class AppComponent implements OnInit {
public personalForm!: FormGroup;
public addressForm!: FormGroup;
constructor(private fb: FormBuilder) { }
ngOnInit() {
this.personalForm = this.fb.group({
firstName: ['', Validators.required],
lastName: ['', Validators.required],
email: ['', [Validators.required, Validators.email]]
});
this.addressForm = this.fb.group({
street: ['', Validators.required],
city: ['', Validators.required],
country: ['', Validators.required]
});
}
submitForm() {
if (this.personalForm.valid && this.addressForm.valid) {
const formData = {
personal: this.personalForm.value,
address: this.addressForm.value
};
console.log('Submit:', formData);
// Send to server
}
}
}Content Reuse with TemplateRef
Efficiently reuse content templates across multiple tabs:
import { Component, TemplateRef, ViewChild } from '@angular/core';
import { TabModule, TabComponent } from '@syncfusion/ej2-angular-navigations';
import { CommonModule } from '@angular/common';
@Component({
imports: [TabModule, CommonModule],
standalone: true,
selector: 'app-root',
template: `
<!-- Reusable content templates -->
<ng-template #gridContent let-data>
<div>
<p>Grid showing data type: {{ data.type }}</p>
<!-- Actual grid component would go here -->
</div>
</ng-template>
<div style="margin: 20px;">
<button (click)="addGridTab()">Add Grid Tab</button>
<button (click)="addChartTab()">Add Chart Tab</button>
</div>
<ejs-tab #tabObj></ejs-tab>
`
})
export class AppComponent {
@ViewChild('tabObj') tabObj?: TabComponent;
@ViewChild('gridContent') gridTemplate?: TemplateRef<any>;
private tabCount = 0;
addGridTab() {
this.tabCount++;
const newTab = {
header: { text: `Grid ${this.tabCount}` },
content: this.gridTemplate // Reuse template
};
this.tabObj!.addTab([newTab], this.tabObj!.items!.length);
}
addChartTab() {
this.tabCount++;
const newTab = {
header: { text: `Chart ${this.tabCount}` },
content: '<div>Chart content</div>'
};
this.tabObj!.addTab([newTab], this.tabObj!.items!.length);
}
}Show/Hide Tabs
Dynamically show and hide tabs:
Hide Tab Example
import { Component, ViewChild } from '@angular/core';
import { TabModule, TabComponent } from '@syncfusion/ej2-angular-navigations';
@Component({
imports: [TabModule],
standalone: true,
selector: 'app-root',
template: `
<div style="margin: 20px;">
<button (click)="toggleTabVisibility(0, true)">Hide Tab 1</button>
<button (click)="toggleTabVisibility(1, true)">Hide Tab 2</button>
<button (click)="toggleTabVisibility(0, false)">Show Tab 1</button>
<button (click)="toggleTabVisibility(1, false)">Show Tab 2</button>
</div>
<ejs-tab #tabObj>
<e-tabitems>
<e-tabitem [header]="{ text: 'Admin' }" content="Admin content"></e-tabitem>
<e-tabitem [header]="{ text: 'Settings' }" content="Settings content"></e-tabitem>
<e-tabitem [header]="{ text: 'User Profile' }" content="Profile content"></e-tabitem>
</e-tabitems>
</ejs-tab>
`
})
export class AppComponent {
@ViewChild('tabObj') tabObj?: TabComponent;
// hideTab(index, true) - Hide tab
// hideTab(index, false) - Show tab
toggleTabVisibility(index: number, hide: boolean) {
(this.tabObj as TabComponent).hideTab(index, hide);
}
}Conditional Tab Visibility
// Show/hide based on user permissions
export class AppComponent {
@ViewChild('tabObj') tabObj?: TabComponent;
public isAdmin = false;
ngOnInit() {
this.loadUserRole().then(role => {
this.isAdmin = role === 'admin';
if (!this.isAdmin) {
// Hide admin tabs for non-admin users
// hideTab(index, true) hides the tab
(this.tabObj as TabComponent).hideTab(0, true); // Admin tab
(this.tabObj as TabComponent).hideTab(4, true); // Settings tab
}
});
}
private loadUserRole() {
// Load from API or auth service
return Promise.resolve('user');
}
}Key Takeaways
- DataSource Binding: Simple array-based approach for static and API-driven tabs
- Dynamic Operations: Add/remove tabs efficiently at runtime
- AJAX Loading: Load content from server on demand for performance
- Reactive Forms: Build complex multi-step forms within tabs
- TemplateRef: Reuse templates to reduce code duplication
- Show/Hide: Control visibility based on user role or conditions
Customization & Styling
Table of Contents
- Header Styles
- Icon Customization
- Content Height Management
- Scroll Configuration
- Animations
- Advanced CSS Customization
Header Styles
Predefined Header Style Classes
Tab component provides built-in header style classes:
import { Component, ViewChild } from '@angular/core';
import { TabModule, TabComponent } from '@syncfusion/ej2-angular-navigations';
import { DropDownListModule, ChangeEventArgs } from '@syncfusion/ej2-angular-dropdowns';
@Component({
imports: [TabModule, DropDownListModule],
standalone: true,
selector: 'app-root',
template: `
<div style="margin: 20px;">
<label>Header Style:</label>
<ejs-dropdownlist
[dataSource]="styleOptions"
[fields]="fields"
[value]="'default'"
(change)="onStyleChange($event)">
</ejs-dropdownlist>
</div>
<ejs-tab #tabObj [selectedIndex]="0">
<e-tabitems>
<e-tabitem [header]="{ text: 'Twitter' }" content="Twitter content"></e-tabitem>
<e-tabitem [header]="{ text: 'Facebook' }" content="Facebook content"></e-tabitem>
<e-tabitem [header]="{ text: 'WhatsApp' }" content="WhatsApp content"></e-tabitem>
</e-tabitems>
</ejs-tab>
`
})
export class AppComponent {
@ViewChild('tabObj') tabObj?: TabComponent;
public styleOptions: object[] = [
{ text: 'Default', style: 'default' },
{ text: 'Fill', style: 'fill' },
{ text: 'Background', style: 'background' },
{ text: 'Accent', style: 'accent' }
];
public fields = { text: 'text', value: 'style' };
onStyleChange(args: ChangeEventArgs) {
const style = args.value as string;
const tabElement = (this.tabObj as TabComponent).element;
// Remove all style classes
tabElement.classList.remove('e-fill', 'e-background', 'e-accent');
// Apply selected style
if (style === 'fill') {
tabElement.classList.add('e-fill');
} else if (style === 'background') {
tabElement.classList.add('e-background');
} else if (style === 'accent') {
tabElement.classList.add('e-background', 'e-accent');
}
}
}Style Definitions
| Class | Description |
|---|---|
.e-fill | Selected tab has solid fill background |
.e-background | All headers have background with selected border |
.e-background.e-accent | Background style with accent color for selected tab |
| (none) | Default - underline for selected tab |
Custom Header Styling
/* Custom header styles */
.custom-tabs .e-tab-header {
background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
border-radius: 4px 4px 0 0;
}
.custom-tabs .e-tab-wrap {
color: white;
font-weight: 500;
padding: 12px 20px;
}
.custom-tabs .e-tab-wrap:hover {
background-color: rgba(255, 255, 255, 0.1);
}
.custom-tabs .e-tab-wrap.e-active {
background-color: rgba(255, 255, 255, 0.25);
border-bottom: 3px solid white;
}Apply with:
<ejs-tab cssClass="custom-tabs">
<!-- content -->
</ejs-tab>Icon Customization
Icon Positioning
Control where icons appear relative to text:
import { Component, ViewChild } from '@angular/core';
import { TabModule, TabComponent } from '@syncfusion/ej2-angular-navigations';
import { DropDownListModule, ChangeEventArgs } from '@syncfusion/ej2-angular-dropdowns';
@Component({
imports: [TabModule, DropDownListModule],
standalone: true,
selector: 'app-root',
template: `
<div style="margin: 20px;">
<label>Icon Position:</label>
<ejs-dropdownlist
[dataSource]="positions"
[value]="'left'"
(change)="onPositionChange($event)">
</ejs-dropdownlist>
</div>
<ejs-tab #tabObj>
<e-tabitems>
<e-tabitem [header]="headerText[0]"></e-tabitem>
<e-tabitem [header]="headerText[1]"></e-tabitem>
<e-tabitem [header]="headerText[2]"></e-tabitem>
</e-tabitems>
</ejs-tab>
`
})
export class AppComponent {
@ViewChild('tabObj') tabObj?: TabComponent;
public headerText: object[] = [
{ text: 'Twitter', iconCss: 'e-icons e-twitter' },
{ text: 'Facebook', iconCss: 'e-icons e-facebook' },
{ text: 'WhatsApp', iconCss: 'e-icons e-whatsapp' }
];
public positions = ['left', 'right', 'top', 'bottom'];
onPositionChange(args: ChangeEventArgs) {
const position = args.value as string;
// Update icon position for all headers
const items = (this.tabObj as TabComponent).items;
items?.forEach(item => {
(item as any).header.iconPosition = position;
});
(this.tabObj as TabComponent).refresh();
}
}Icon Positions
| Position | Description |
|---|---|
left | Icon appears to left of text (default) |
right | Icon appears to right of text |
top | Icon appears above text |
bottom | Icon appears below text |
Icon CSS Classes
Use Syncfusion icon library:
public headerText: object[] = [
{ text: 'Dashboard', iconCss: 'e-icons e-home' },
{ text: 'Settings', iconCss: 'e-icons e-settings' },
{ text: 'Search', iconCss: 'e-icons e-search' },
{ text: 'File', iconCss: 'e-icons e-file' },
{ text: 'Cart', iconCss: 'e-icons e-shopping-cart' }
];Custom Icons
// Using Font Awesome or custom icon fonts
public headerText: object[] = [
{ text: 'Home', iconCss: 'fas fa-home' },
{ text: 'User', iconCss: 'fas fa-user' },
{ text: 'Settings', iconCss: 'fas fa-cog' }
];Content Height Management
Height Adjust Modes
import { Component } from '@angular/core';
import { TabModule } from '@syncfusion/ej2-angular-navigations';
@Component({
imports: [TabModule],
standalone: true,
selector: 'app-root',
template: `
<!-- Auto-adjust height to content -->
<ejs-tab heightAdjustMode="Auto" style="height: auto;">
<e-tabitems>
<e-tabitem [header]="{ text: 'Short' }" content="Brief content"></e-tabitem>
<e-tabitem [header]="{ text: 'Long' }" [content]="longContent"></e-tabitem>
</e-tabitems>
</ejs-tab>
<!-- Fill available space -->
<ejs-tab heightAdjustMode="Fill" style="height: 400px;">
<e-tabitems>
<e-tabitem [header]="{ text: 'Tab 1' }" content="Content 1"></e-tabitem>
<e-tabitem [header]="{ text: 'Tab 2' }" content="Content 2"></e-tabitem>
</e-tabitems>
</ejs-tab>
<!-- Content height only (header excluded) -->
<ejs-tab heightAdjustMode="Content" style="height: 300px;">
<e-tabitems>
<e-tabitem [header]="{ text: 'Tab 1' }" content="Content 1"></e-tabitem>
<e-tabitem [header]="{ text: 'Tab 2' }" content="Content 2"></e-tabitem>
</e-tabitems>
</ejs-tab>
`
})
export class AppComponent {
public longContent = `
This is a long content with multiple lines.
Line 2: Lorem ipsum dolor sit amet.
Line 3: Consectetur adipiscing elit.
Line 4: Sed do eiusmod tempor incididunt.
Line 5: Ut labore et dolore magna aliqua.
`;
}Height Modes
| Mode | Behavior |
|---|---|
Auto | Height adjusts to content, no scrolling needed |
Fill | Fills container height, content scrolls if needed |
Content | Only content area gets height constraint |
Scroll Configuration
Scroll Step Size
Configure how much each scroll arrow moves:
import { Component } from '@angular/core';
import { TabModule } from '@syncfusion/ej2-angular-navigations';
import { FormsModule } from '@angular/forms';
@Component({
imports: [TabModule, FormsModule],
standalone: true,
selector: 'app-root',
template: `
<div style="margin: 20px;">
<label>Scroll Step:
<input type="number" [(ngModel)]="scrollStep" (change)="updateScrollStep()"
style="width: 60px;">
</label>
</div>
<ejs-tab id="scrollTab">
<e-tabitems>
<e-tabitem [header]="{ text: 'Tab 1' }" content="Content 1"></e-tabitem>
<e-tabitem [header]="{ text: 'Tab 2' }" content="Content 2"></e-tabitem>
<e-tabitem [header]="{ text: 'Tab 3' }" content="Content 3"></e-tabitem>
<e-tabitem [header]="{ text: 'Tab 4' }" content="Content 4"></e-tabitem>
<e-tabitem [header]="{ text: 'Tab 5' }" content="Content 5"></e-tabitem>
<e-tabitem [header]="{ text: 'Tab 6' }" content="Content 6"></e-tabitem>
<e-tabitem [header]="{ text: 'Tab 7' }" content="Content 7"></e-tabitem>
</e-tabitems>
</ejs-tab>
`
})
export class AppComponent {
public scrollStep = 60;
updateScrollStep() {
const tabElement = document.getElementById('scrollTab') as any;
if (tabElement && tabElement.ej2_instances) {
tabElement.ej2_instances[0].scrollStep = this.scrollStep;
}
}
}Animations
Enable/Disable Animations
import { Component } from '@angular/core';
import { TabModule } from '@syncfusion/ej2-angular-navigations';
@Component({
imports: [TabModule],
standalone: true,
selector: 'app-root',
template: `
<!-- Animations disabled -->
<ejs-tab [animation]="{ previous: { effect: 'None' }, next: { effect: 'None' } }">
<e-tabitems>
<e-tabitem [header]="{ text: 'No Animation' }" content="Fast switching"></e-tabitem>
<e-tabitem [header]="{ text: 'Tab 2' }" content="Content 2"></e-tabitem>
</e-tabitems>
</ejs-tab>
<!-- Default animations -->
<ejs-tab>
<e-tabitems>
<e-tabitem [header]="{ text: 'Default Animation' }" content="Smooth transition"></e-tabitem>
<e-tabitem [header]="{ text: 'Tab 2' }" content="Content 2"></e-tabitem>
</e-tabitems>
</ejs-tab>
`
})
export class AppComponent { }Custom Animations
// Slide animation
<ejs-tab [animation]="{ previous: { effect: 'SlideLeftOut' }, next: { effect: 'SlideRightIn' } }">
<!-- tabs -->
</ejs-tab>
// Fade animation
<ejs-tab [animation]="{ previous: { effect: 'FadeOut' }, next: { effect: 'FadeIn' } }">
<!-- tabs -->
</ejs-tab>
// Custom timing
<ejs-tab [animation]="{
previous: { effect: 'SlideLeftOut', duration: 500 },
next: { effect: 'SlideRightIn', duration: 500 }
}">
<!-- tabs -->
</ejs-tab>Advanced CSS Customization
Complete Customization Example
/* Main tab container */
.e-tab {
border: 2px solid #2196F3;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
/* Tab header area */
.e-tab .e-tab-header {
background: linear-gradient(90deg, #2196F3 0%, #1976D2 100%);
border-radius: 8px 8px 0 0;
padding: 8px;
}
/* Header items */
.e-tab .e-tab-header .e-toolbar-item {
margin: 0 4px;
}
/* Header text styling */
.e-tab .e-tab-wrap {
color: white;
font-weight: 500;
padding: 12px 16px;
border-radius: 4px;
transition: all 0.3s ease;
}
/* Hover effect */
.e-tab .e-tab-wrap:hover {
background-color: rgba(255, 255, 255, 0.1);
transform: translateY(-2px);
}
/* Active tab */
.e-tab .e-tab-wrap.e-active {
background-color: rgba(255, 255, 255, 0.25);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
border-bottom: 3px solid white;
}
/* Header icon */
.e-tab .e-tab-header .e-toolbar-item .e-tab-icon {
margin-right: 8px;
color: white;
}
/* Content area */
.e-tab .e-content {
background: #f5f5f5;
padding: 20px;
border-radius: 0 0 8px 8px;
min-height: 200px;
}
/* Content item */
.e-tab .e-content .e-item {
color: #333;
font-size: 14px;
line-height: 1.6;
}
/* Disabled state */
.e-tab .e-tab-wrap.e-disabled {
color: #ccc;
cursor: not-allowed;
background: #f0f0f0;
}Dark Theme Customization
/* Dark mode tabs */
.e-tab.dark-theme {
background: #1e1e1e;
border-color: #333;
}
.e-tab.dark-theme .e-tab-header {
background: #2d2d2d;
}
.e-tab.dark-theme .e-tab-wrap {
color: #e0e0e0;
}
.e-tab.dark-theme .e-tab-wrap:hover {
background: #3d3d3d;
}
.e-tab.dark-theme .e-tab-wrap.e-active {
background: #4d4d4d;
border-bottom-color: #bb86fc;
}
.e-tab.dark-theme .e-content {
background: #1e1e1e;
color: #e0e0e0;
}Use with:
<ejs-tab cssClass="dark-theme">
<!-- tabs -->
</ejs-tab>Responsive Header Styles
/* Mobile - stack vertically */
@media (max-width: 600px) {
.e-tab .e-tab-header {
flex-direction: column;
}
.e-tab .e-tab-wrap {
padding: 8px 12px;
font-size: 12px;
}
.e-tab .e-tab-header .e-toolbar-item .e-tab-icon {
margin-right: 4px;
}
}
/* Tablet - adjust padding */
@media (max-width: 900px) {
.e-tab .e-tab-wrap {
padding: 10px 14px;
}
}Performance Tips
- Use
heightAdjustMode="Content"to prevent layout thrashing - Minimize CSS calculations with simpler selectors
- Use CSS classes instead of inline styles
- Apply animations sparingly for better performance
- Use
effect: 'None'for large data sets
Drag and Drop Reordering in Angular Tab Component
Table of Contents
1. Enable Drag and Drop 2. Drag Area Configuration 3. Drag and Drop Events 4. Prevent Dragging or Dropping 5. Reorder Active Tab 6. Drag Between Multiple Tabs 7. Drag to External Components
---
Enable Drag and Drop
The Tab component provides built-in drag and drop functionality to reorder tab items dynamically. Enable this feature using the `allowDragAndDrop` property.
Property: allowDragAndDrop
- Type: boolean
- Default: false
- Description: Enables users to drag and drop tab items within the component
Basic Example
import { Component } from '@angular/core';
import { TabModule } from '@syncfusion/ej2-angular-navigations';
@Component({
imports: [TabModule],
standalone: true,
selector: 'app-root',
template: `
<ejs-tab id="draggableTab" [allowDragAndDrop]="true">
<e-tabitems>
<e-tabitem [header]="headerText[0]" [content]="content0"></e-tabitem>
<e-tabitem [header]="headerText[1]" [content]="content1"></e-tabitem>
<e-tabitem [header]="headerText[2]" [content]="content2"></e-tabitem>
</e-tabitems>
</ejs-tab>
`
})
export class AppComponent {
public headerText: object[] = [
{ text: 'Tab 1' },
{ text: 'Tab 2' },
{ text: 'Tab 3' }
];
public content0 = 'First tab content - drag to reorder';
public content1 = 'Second tab content - drag to reorder';
public content2 = 'Third tab content - drag to reorder';
}Result: Users can now drag tab headers to reorder them. The tab order updates dynamically.
---
Drag Area Configuration
Use the `dragArea` property to define the boundary within which tabs can be dragged. This prevents tabs from being moved outside the defined area.
Property: dragArea
- Type: string
- Default: null (entire document)
- Description: CSS selector for the drag boundary container
Example with Drag Area
import { Component } from '@angular/core';
import { TabModule } from '@syncfusion/ej2-angular-navigations';
@Component({
imports: [TabModule],
standalone: true,
selector: 'app-root',
template: `
<div id="tabcontainer">
<ejs-tab
id="draggableTab"
[allowDragAndDrop]="true"
dragArea="#tabcontainer">
<e-tabitems>
<e-tabitem [header]="headerText[0]" [content]="content0"></e-tabitem>
<e-tabitem [header]="headerText[1]" [content]="content1"></e-tabitem>
<e-tabitem [header]="headerText[2]" [content]="content2"></e-tabitem>
<e-tabitem [header]="headerText[3]" [content]="content3"></e-tabitem>
</e-tabitems>
</ejs-tab>
</div>
`,
styles: [`
#tabcontainer {
padding: 20px;
border: 2px solid #ccc;
border-radius: 4px;
}
`]
})
export class AppComponent {
public headerText: object[] = [
{ text: 'India' },
{ text: 'Australia' },
{ text: 'USA' },
{ text: 'France' }
];
public content0 = 'India officially the Republic of India, is a country in South Asia...';
public content1 = 'Australia, officially the Commonwealth of Australia, is a country...';
public content2 = 'The United States of America (USA or U.S.A.)...';
public content3 = 'France, officially the French Republic...';
}Result: Tab items can only be dragged within the #tabcontainer div. If dragged outside, the drag is cancelled.
---
Drag and Drop Events
The Tab component provides three events in the drag and drop lifecycle:
| Event | Type | Description | Cancellable |
|---|---|---|---|
| `onDragStart` | DragEventArgs | Fires before dragging begins | Yes |
| `dragging` | DragEventArgs | Fires while dragging is in progress | No |
| `dragged` | DragEventArgs | Fires after drop is completed | Yes |
Event Sequence Example
import { Component, ViewChild } from '@angular/core';
import { TabModule, TabComponent, DragEventArgs } from '@syncfusion/ej2-angular-navigations';
@Component({
imports: [TabModule],
standalone: true,
selector: 'app-root',
template: `
<div>
<p>Log: {{ dragLog }}</p>
<ejs-tab
id="draggableTab"
[allowDragAndDrop]="true"
(onDragStart)="onDragStart($event)"
(dragging)="onDragging($event)"
(dragged)="onDragged($event)">
<e-tabitems>
<e-tabitem [header]="headerText[0]" [content]="content0"></e-tabitem>
<e-tabitem [header]="headerText[1]" [content]="content1"></e-tabitem>
</e-tabitems>
</ejs-tab>
</div>
`
})
export class AppComponent {
@ViewChild('tabObj') tabObj?: TabComponent;
public dragLog = '';
public headerText: object[] = [
{ text: 'Tab 1' },
{ text: 'Tab 2' }
];
public content0 = 'First tab content';
public content1 = 'Second tab content';
onDragStart(args: DragEventArgs): void {
this.dragLog = `Started dragging from index: ${args.index}`;
}
onDragging(args: DragEventArgs): void {
// Update UI during drag (avoid expensive operations here)
}
onDragged(args: DragEventArgs): void {
this.dragLog = `Dropped at new position. Cancel: ${args.cancel}`;
}
}---
Prevent Dragging or Dropping
Use the onDragStart and dragged events to prevent dragging specific tabs or dropping at certain locations.
Prevent Dragging Specific Tabs
import { Component } from '@angular/core';
import { TabModule, DragEventArgs } from '@syncfusion/ej2-angular-navigations';
@Component({
imports: [TabModule],
standalone: true,
selector: 'app-root',
template: `
<ejs-tab
id="draggableTab"
[allowDragAndDrop]="true"
(onDragStart)="onDragStart($event)">
<e-tabitems>
<e-tabitem [header]="headerText[0]" [content]="content0"></e-tabitem>
<e-tabitem [header]="headerText[1]" [content]="content1"></e-tabitem>
<e-tabitem [header]="headerText[2]" [content]="content2"></e-tabitem>
</e-tabitems>
</ejs-tab>
`
})
export class AppComponent {
public headerText: object[] = [
{ text: 'Important' },
{ text: 'Tab 2' },
{ text: 'Tab 3' }
];
public content0 = 'This tab cannot be moved';
public content1 = 'This tab can be moved';
public content2 = 'This tab can be moved';
onDragStart(args: DragEventArgs): void {
// Prevent dragging the first tab (index 0)
if (args.index === 0) {
args.cancel = true;
console.log('Dragging disabled for Important tab');
}
}
}Prevent Dropping at Specific Positions
import { Component } from '@angular/core';
import { TabModule, DragEventArgs } from '@syncfusion/ej2-angular-navigations';
@Component({
imports: [TabModule],
standalone: true,
selector: 'app-root',
template: `
<ejs-tab
id="draggableTab"
[allowDragAndDrop]="true"
(dragged)="onDragged($event)">
<e-tabitems>
<e-tabitem [header]="headerText[0]" [content]="content0"></e-tabitem>
<e-tabitem [header]="headerText[1]" [content]="content1"></e-tabitem>
<e-tabitem [header]="headerText[2]" [content]="content2"></e-tabitem>
</e-tabitems>
</ejs-tab>
`
})
export class AppComponent {
public headerText: object[] = [
{ text: 'First' },
{ text: 'Second' },
{ text: 'Third' }
];
public content0 = 'First tab content';
public content1 = 'Second tab content';
public content2 = 'Third tab content';
onDragged(args: DragEventArgs): void {
// Prevent dropping as first tab
if (args.index === 0) {
args.cancel = true;
console.log('Cannot drop as first tab');
}
}
}---
Reorder Active Tab
Use the `reorderActiveTab` property to control whether the active tab is reordered when selected from the overflow popup.
Property: reorderActiveTab
- Type: boolean
- Default: true
- Description: When true, active tab moves to the front when selected from popup overflow
- Use Case: When overflow mode is "Popup", prevents active tab from being repositioned
Example with Overflow Mode
import { Component } from '@angular/core';
import { TabModule } from '@syncfusion/ej2-angular-navigations';
@Component({
imports: [TabModule],
standalone: true,
selector: 'app-root',
template: `
<ejs-tab
id="element"
overflowMode="Popup"
heightAdjustMode="Auto"
[reorderActiveTab]="false">
<e-tabitems>
<e-tabitem [header]="headerText[0]" [content]="content0"></e-tabitem>
<e-tabitem [header]="headerText[1]" [content]="content1"></e-tabitem>
<e-tabitem [header]="headerText[2]" [content]="content2"></e-tabitem>
<e-tabitem [header]="headerText[3]" [content]="content3"></e-tabitem>
<e-tabitem [header]="headerText[4]" [content]="content4"></e-tabitem>
</e-tabitems>
</ejs-tab>
`,
styles: [`
:host ::ng-deep .e-tab {
width: 300px;
}
`]
})
export class AppComponent {
public headerText: object[] = [
{ text: 'Home' },
{ text: 'About' },
{ text: 'Services' },
{ text: 'Contact' },
{ text: 'FAQ' }
];
public content0 = 'Home content';
public content1 = 'About content';
public content2 = 'Services content';
public content3 = 'Contact content';
public content4 = 'FAQ content';
}Behavior Difference:
- `reorderActiveTab = true` (default): When you select a tab from the popup, it moves to the visible area
- `reorderActiveTab = false`: When you select a tab from the popup, it remains in the popup (active state shown in popup)
---
Drag Between Multiple Tabs
Allow users to drag tab items between two separate Tab components. This requires manual implementation using addTab() and removeTab() methods.
import { Component, ViewChild } from '@angular/core';
import { TabModule, TabComponent, DragEventArgs, TabItemModel, HeaderModel } from '@syncfusion/ej2-angular-navigations';
import { isNullOrUndefined } from '@syncfusion/ej2-base';
@Component({
imports: [TabModule],
standalone: true,
selector: 'app-root',
template: `
<div id="tabparent">
<h3>Tab 1 (Drag items from here)</h3>
<ejs-tab
#firstTabObj
id="firstTab"
heightAdjustMode="Auto"
[allowDragAndDrop]="true"
dragArea="#tabparent"
(onDragStart)="firstTabDragStart($event)"
(dragged)="firstTabDragStop($event)">
<e-tabitems>
<e-tabitem [header]="headerText[0]" [content]="content0"></e-tabitem>
<e-tabitem [header]="headerText[1]" [content]="content1"></e-tabitem>
<e-tabitem [header]="headerText[2]" [content]="content2"></e-tabitem>
</e-tabitems>
</ejs-tab>
<h3 style="margin-top: 30px;">Tab 2 (Drag items to here)</h3>
<ejs-tab
#secondTabObj
id="secondTab"
heightAdjustMode="Auto"
[allowDragAndDrop]="true"
dragArea="#tabparent"
(onDragStart)="secondTabDragStart($event)"
(dragged)="secondTabDragStop($event)">
<e-tabitems>
<e-tabitem [header]="headerText[3]" [content]="content3"></e-tabitem>
<e-tabitem [header]="headerText[4]" [content]="content4"></e-tabitem>
</e-tabitems>
</ejs-tab>
</div>
`,
styles: [`
#tabparent {
padding: 20px;
border: 2px dashed #ccc;
border-radius: 4px;
}
`]
})
export class AppComponent {
@ViewChild('firstTabObj') firstTabObj?: TabComponent;
@ViewChild('secondTabObj') secondTabObj?: TabComponent;
public headerText: object[] = [
{ text: 'Item 1' },
{ text: 'Item 2' },
{ text: 'Item 3' },
{ text: 'Item 4' },
{ text: 'Item 5' }
];
public content0 = 'Content for Item 1';
public content1 = 'Content for Item 2';
public content2 = 'Content for Item 3';
public content3 = 'Content for Item 4';
public content4 = 'Content for Item 5';
private dragItemIndex?: number;
private dragItemContainer?: HTMLElement;
private draggedItems: TabItemModel[] = [];
firstTabDragStart(args: DragEventArgs): void {
// Store dragged item for later removal
this.draggedItems = [(this.firstTabObj as TabComponent).items[args.index]];
args.draggedItem.style.visibility = 'hidden';
this.dragItemContainer = args.draggedItem.closest('.e-tab') as HTMLElement;
}
firstTabDragStop(args: DragEventArgs): void {
if (!isNullOrUndefined((args.target as HTMLElement).closest('.e-tab')) &&
!(this.dragItemContainer as HTMLElement).isSameNode(args.target.closest('.e-tab'))) {
args.cancel = true;
const tabElement = args.target.closest('.e-tab') as HTMLElement;
const dropItem = args.target.closest('.e-toolbar-item') as HTMLElement;
if (tabElement && dropItem) {
// Calculate drop index
const toolbarItems = Array.from(
(this.secondTabObj as TabComponent).element.querySelector('.e-toolbar-items')?.children || []
).filter(el => el.classList.contains('e-toolbar-item'));
const dropItemIndex = toolbarItems.indexOf(dropItem);
// Add to second tab
(this.secondTabObj as TabComponent).addTab(this.draggedItems, dropItemIndex);
// Remove from first tab
const firstToolbarItems = Array.from(
(this.firstTabObj as TabComponent).element.querySelector('.e-toolbar-items')?.children || []
).filter(el => el.classList.contains('e-toolbar-item'));
const dragIndex = firstToolbarItems.indexOf(args.draggedItem);
(this.firstTabObj as TabComponent).removeTab(dragIndex);
}
}
args.draggedItem.style.visibility = 'visible';
}
secondTabDragStart(args: DragEventArgs): void {
this.draggedItems = [(this.secondTabObj as TabComponent).items[args.index]];
args.draggedItem.style.visibility = 'hidden';
this.dragItemContainer = args.draggedItem.closest('.e-tab') as HTMLElement;
}
secondTabDragStop(args: DragEventArgs): void {
if (!isNullOrUndefined((args.target as HTMLElement).closest('.e-tab')) &&
!(this.dragItemContainer as HTMLElement).isSameNode(args.target.closest('.e-tab'))) {
args.cancel = true;
const tabElement = args.target.closest('.e-tab') as HTMLElement;
const dropItem = args.target.closest('.e-toolbar-item') as HTMLElement;
if (tabElement && dropItem) {
const toolbarItems = Array.from(
(this.firstTabObj as TabComponent).element.querySelector('.e-toolbar-items')?.children || []
).filter(el => el.classList.contains('e-toolbar-item'));
const dropItemIndex = toolbarItems.indexOf(dropItem);
(this.firstTabObj as TabComponent).addTab(this.draggedItems, dropItemIndex);
const secondToolbarItems = Array.from(
(this.secondTabObj as TabComponent).element.querySelector('.e-toolbar-items')?.children || []
).filter(el => el.classList.contains('e-toolbar-item'));
const dragIndex = secondToolbarItems.indexOf(args.draggedItem);
(this.secondTabObj as TabComponent).removeTab(dragIndex);
}
}
args.draggedItem.style.visibility = 'visible';
}
}---
Drag to External Components
Tab items can be dragged to external components like TreeView by using the dragged event and component methods.
Key Concepts:
- Use the
draggedevent to detect drop on external element - Cancel the default Tab drag behavior with
args.cancel = true - Use target component's add/remove methods (e.g.,
treeView.addNodes(),tab.removeTab())
Example: Drag to TreeView
import { Component, ViewChild } from '@angular/core';
import { TabModule, TabComponent, DragEventArgs, TabItemModel } from '@syncfusion/ej2-angular-navigations';
import { TreeViewModule, TreeViewComponent, HeaderModel } from '@syncfusion/ej2-angular-navigations';
import { isNullOrUndefined } from '@syncfusion/ej2-base';
@Component({
imports: [TabModule, TreeViewModule],
standalone: true,
selector: 'app-root',
template: `
<div id="tabparent">
<h3>Drag tabs to TreeView below</h3>
<ejs-tab
#tabObj
id="draggableTab"
heightAdjustMode="Auto"
[allowDragAndDrop]="true"
dragArea="#tabparent"
(created)="onTabCreate()"
(dragged)="tabDragStop($event)">
<e-tabitems>
<e-tabitem [header]="headerText[0]" [content]="content0"></e-tabitem>
<e-tabitem [header]="headerText[1]" [content]="content1"></e-tabitem>
<e-tabitem [header]="headerText[2]" [content]="content2"></e-tabitem>
</e-tabitems>
</ejs-tab>
<h3 style="margin-top: 20px;">TreeView (Drop target)</h3>
<ejs-treeview
#treeObj
id="draggableTreeview"
[fields]="field"
cssClass="treeview-external-drop">
</ejs-treeview>
</div>
`
})
export class AppComponent {
@ViewChild('tabObj') tabObj?: TabComponent;
@ViewChild('treeObj') treeObj?: TreeViewComponent;
public headerText: object[] = [
{ text: 'Item 1' },
{ text: 'Item 2' },
{ text: 'Item 3' }
];
public content0 = 'Drag me to TreeView';
public content1 = 'Drag me to TreeView';
public content2 = 'Drag me to TreeView';
public field: object = {
dataSource: [
{ text: 'DroppedItems', id: 'parent', expanded: true }
],
id: 'id',
text: 'text',
child: 'child'
};
private nodeCounter = 0;
onTabCreate(): void {
const tabElement = document.getElementById('draggableTab');
if (!isNullOrUndefined(tabElement)) {
(this.tabObj as TabComponent).element.children[0].classList.add('e-droppable');
}
}
tabDragStop(args: DragEventArgs): void {
const toolbarItems = Array.from(
(this.tabObj as TabComponent).element.querySelector('.e-toolbar-items')?.children || []
).filter(el => el.classList.contains('e-toolbar-item'));
const dragTabIndex = toolbarItems.indexOf(args.draggedItem);
const dragItem = (this.tabObj as TabComponent).items[dragTabIndex];
const dropNode = (args.target as HTMLElement).closest('#draggableTreeview .e-list-item');
if (dropNode && !(args.target as HTMLElement).closest('#draggableTab .e-toolbar-item')) {
args.cancel = true;
// Create new TreeView node from Tab item
const newNode = [{
id: `node_${this.nodeCounter++}`,
text: (dragItem.header as HeaderModel).text as string
}];
// Remove from Tab
(this.tabObj as TabComponent).removeTab(dragTabIndex);
// Add to TreeView
(this.treeObj as TreeViewComponent).addNodes(newNode, 'parent');
}
}
}---
Best Practices
✅ Do:
- Set appropriate
dragAreato constrain drag operations - Provide visual feedback during drag operations
- Use
onDragStartto validate which tabs can be dragged - Clean up visibility styles after drag completion
❌ Don't:
- Leave hidden tabs invisible after failed drag operations
- Perform expensive operations in the
draggingevent (fired continuously) - Allow dragging tabs outside the intended container without validation
- Create duplicate tabs when transferring between containers
---
Related Properties
| Property | Type | Purpose |
|---|---|---|
allowDragAndDrop | boolean | Enable/disable drag-drop |
dragArea | string | CSS selector for drag boundary |
reorderActiveTab | boolean | Reorder active tab from popup |
items | TabItemModel[] | Tab collection |
Related Events
| Event | Fires | Cancellable | Use For |
|---|---|---|---|
onDragStart | Before drag begins | Yes | Prevent specific tabs from dragging |
dragging | During drag | No | Visual feedback (avoid expensive ops) |
dragged | After drop | Yes | Custom drop logic or validation |
Getting Started with Angular Tabs
Table of Contents
Installation
Prerequisites
- Node.js and npm installed
- Angular 12+ (this guide uses Angular 21 standalone components)
Create a New Angular Application
Use Angular CLI to set up a new project:
npm install -g @angular/cli
ng new syncfusion-angular-appDuring creation, you'll be prompted to choose:
- Stylesheet format (CSS, SCSS, Less)
- Server-side rendering (SSR)
- AI tool integration
Navigate to the project directory:
cd syncfusion-angular-appInstall Syncfusion Tab Package
Add the Syncfusion navigations package to your application:
npm install @syncfusion/ej2-angular-navigations --saveFor Angular versions below 12, use the ngcc (Angular compatibility compiler) package:
npm install @syncfusion/ej2-angular-navigations@ngcc --saveCSS Setup
Add the required CSS imports to your global stylesheet src/styles.css:
@import '../node_modules/@syncfusion/ej2-base/styles/material3.css';
@import '../node_modules/@syncfusion/ej2-buttons/styles/material3.css';
@import '../node_modules/@syncfusion/ej2-popups/styles/material3.css';
@import '../node_modules/@syncfusion/ej2-navigations/styles/material3.css';Available themes: material3, bootstrap, bootstrap4, bootstrap5, fabric, high-contrast, tailwind, fluent, fluent2
Rendering Methods
Method 1: JSON Items Collection
Define tabs using a JSON array with header and content properties:
import { Component } from '@angular/core';
import { TabModule } from '@syncfusion/ej2-angular-navigations';
@Component({
imports: [TabModule],
standalone: true,
selector: 'app-root',
template: `
<ejs-tab id="element">
<e-tabitems>
<e-tabitem [header]="headerText[0]" [content]="content0"></e-tabitem>
<e-tabitem [header]="headerText[1]" [content]="content1"></e-tabitem>
<e-tabitem [header]="headerText[2]" [content]="content2"></e-tabitem>
</e-tabitems>
</ejs-tab>
`
})
export class AppComponent {
public headerText: object[] = [
{ text: 'Twitter' },
{ text: 'Facebook' },
{ text: 'WhatsApp' }
];
public content0 = 'Twitter is an online social networking service...';
public content1 = 'Facebook is an online social networking service...';
public content2 = 'WhatsApp Messenger is a proprietary cross-platform...';
}Advantages:
- Simple data-driven approach
- Easy to bind from arrays or API responses
- Quick to implement
When to use:
- String content or simple HTML
- Binding from data sources
- Dynamic tab creation
Method 2: ng-template Approach
Use Angular templates for header and content:
@Component({
imports: [TabModule],
standalone: true,
selector: 'app-root',
template: `
<ejs-tab id="element">
<e-tabitems>
<e-tabitem>
<ng-template #headerText>
<div>Twitter</div>
</ng-template>
<ng-template #content>
Twitter is an online social networking service that enables users
to send and read short 140-character messages called "tweets".
</ng-template>
</e-tabitem>
<e-tabitem>
<ng-template #headerText>
<div>Facebook</div>
</ng-template>
<ng-template #content>
Facebook is an online social networking service headquartered
in Menlo Park, California.
</ng-template>
</e-tabitem>
</e-tabitems>
</ejs-tab>
`
})
export class AppComponent { }Advantages:
- Full control over header and content HTML
- Can include complex markup
- Supports nested templates
When to use:
- Complex HTML content
- Custom header styling
- Component integration
Method 3: HTML Elements Structure
Use HTML wrapper elements with specific CSS classes:
@Component({
imports: [TabModule],
standalone: true,
selector: 'app-root',
template: `
<ejs-tab id="element">
<div class="e-tab-header">
<div>Twitter</div>
<div>Facebook</div>
<div>WhatsApp</div>
</div>
<div class="e-content">
<div>
Twitter is an online social networking service that enables users
to send and read short 140-character messages called "tweets".
</div>
<div>
Facebook is an online social networking service headquartered
in Menlo Park, California.
</div>
<div>
WhatsApp Messenger is a proprietary cross-platform instant messaging
client for smartphones.
</div>
</div>
</ejs-tab>
`
})
export class AppComponent { }Key CSS Classes:
e-tab-header- Wrapper for all headerse-content- Wrapper for all content panels- Individual divs within each wrapper become items
Advantages:
- Direct HTML control
- No template syntax needed
- Simpler for static content
When to use:
- Static HTML content
- Server-rendered content
- Simple tab structures
Basic Setup
Minimal Tab implementation:
import { Component } from '@angular/core';
import { TabModule } from '@syncfusion/ej2-angular-navigations';
@Component({
imports: [TabModule],
standalone: true,
selector: 'app-root',
template: `
<ejs-tab>
<e-tabitems>
<e-tabitem [header]="{ text: 'Home' }" content="Welcome home!"></e-tabitem>
<e-tabitem [header]="{ text: 'About' }" content="About this app"></e-tabitem>
<e-tabitem [header]="{ text: 'Contact' }" content="Contact us"></e-tabitem>
</e-tabitems>
</ejs-tab>
`
})
export class AppComponent { }Run Your Application
npm startThe app will start on http://localhost:4200 by default. You should see your tabs rendered.
Module vs Standalone
Standalone Architecture (Recommended - Angular 21+):
@Component({
imports: [TabModule], // Direct module import
standalone: true,
selector: 'app-root',
template: `...`
})Module-based Architecture (Angular <21):
@NgModule({
imports: [TabModule],
declarations: [AppComponent],
bootstrap: [AppComponent]
})
export class AppModule { }Next Steps
- Explore different content rendering modes (On Demand, Dynamic, Init)
- Learn about tab selection and event handling
- Customize headers and styling
- Add animations and effects
- Implement dynamic tab operations (add, remove, show, hide)
Localization and Orientation in Angular Tab Component
Table of Contents
1. Localization 2. Header Placement (Orientation) 3. Overflow Modes 4. Responsive Orientation Changes
---
Localization
The Tab component supports localization for UI elements like the close button tooltip text. Use the `locale` property and the L10n class to define translations for different cultures.
Property: locale
- Type: string
- Default: 'en-US'
- Description: Sets the culture/language for Tab component text
Localizable Strings:
| Locale Key | Default Value | Purpose |
|---|---|---|
closeButtonTitle | "Close" | Tooltip text for close button |
Basic Localization Example
import { Component, OnInit } from '@angular/core';
import { TabModule, TabComponent } from '@syncfusion/ej2-angular-navigations';
import { L10n } from '@syncfusion/ej2-base';
@Component({
imports: [TabModule],
standalone: true,
selector: 'app-root',
template: `
<ejs-tab
id="element"
[locale]="locale"
[showCloseButton]="true">
<e-tabitems>
<e-tabitem [header]="headerText[0]" [content]="content0"></e-tabitem>
<e-tabitem [header]="headerText[1]" [content]="content1"></e-tabitem>
</e-tabitems>
</ejs-tab>
`
})
export class AppComponent implements OnInit {
public locale = 'en-US';
public headerText: object[] = [
{ text: 'Home' },
{ text: 'About' }
];
public content0 = 'Welcome to the home tab';
public content1 = 'About our application';
ngOnInit(): void {
// Set default English localization
L10n.load({
'en-US': {
'tab': {
'closeButtonTitle': 'Close'
}
}
});
}
}French Localization Example
import { Component, OnInit } from '@angular/core';
import { TabModule } from '@syncfusion/ej2-angular-navigations';
import { L10n } from '@syncfusion/ej2-base';
@Component({
imports: [TabModule],
standalone: true,
selector: 'app-root',
template: `
<div>
<button (click)="changeLocale('en-US')">English</button>
<button (click)="changeLocale('fr-BE')">Français</button>
<button (click)="changeLocale('de-DE')">Deutsch</button>
<hr />
<ejs-tab
id="element"
[locale]="currentLocale"
[showCloseButton]="true">
<e-tabitems>
<e-tabitem [header]="headerText[0]" [content]="content0"></e-tabitem>
<e-tabitem [header]="headerText[1]" [content]="content1"></e-tabitem>
<e-tabitem [header]="headerText[2]" [content]="content2"></e-tabitem>
</e-tabitems>
</ejs-tab>
</div>
`
})
export class AppComponent implements OnInit {
public currentLocale = 'en-US';
public headerText: object[] = [
{ text: 'Twitter' },
{ text: 'Facebook' },
{ text: 'WhatsApp' }
];
public content0 = 'Twitter is an online social networking service...';
public content1 = 'Facebook is an online social networking service...';
public content2 = 'WhatsApp Messenger is a messaging client...';
ngOnInit(): void {
// Load multiple language translations
L10n.load({
'en-US': {
'tab': {
'closeButtonTitle': 'Close'
}
},
'fr-BE': {
'tab': {
'closeButtonTitle': 'Fermer' // French
}
},
'de-DE': {
'tab': {
'closeButtonTitle': 'Schließen' // German
}
}
});
}
changeLocale(locale: string): void {
this.currentLocale = locale;
}
}Result: Close button tooltip changes based on selected language:
- English: "Close"
- French: "Fermer"
- German: "Schließen"
Custom Localization
import { Component, OnInit } from '@angular/core';
import { TabModule } from '@syncfusion/ej2-angular-navigations';
import { L10n } from '@syncfusion/ej2-base';
@Component({
imports: [TabModule],
standalone: true,
selector: 'app-root',
template: `
<ejs-tab
id="element"
locale="es-ES"
[showCloseButton]="true">
<e-tabitems>
<e-tabitem [header]="headerText[0]" [content]="content0"></e-tabitem>
</e-tabitems>
</ejs-tab>
`
})
export class AppComponent implements OnInit {
public headerText: object[] = [
{ text: 'Pestaña 1' }
];
public content0 = 'Contenido en español';
ngOnInit(): void {
// Register Spanish translation
L10n.load({
'es-ES': {
'tab': {
'closeButtonTitle': 'Cerrar' // Spanish: Close
}
}
});
}
}---
Header Placement (Orientation)
The Tab component allows placing headers at different positions using the `headerPlacement` property. This supports both horizontal and vertical layouts.
Property: headerPlacement
- Type: string (HeaderPosition enum)
- Default: 'Top'
- Options: 'Top', 'Bottom', 'Left', 'Right'
| Position | Layout | Content Position | Use Case |
|---|---|---|---|
| Top | Horizontal | Below headers | Standard navigation, most common |
| Bottom | Horizontal | Above headers | Footer-like navigation |
| Left | Vertical | Right of headers | Sidebar navigation |
| Right | Vertical | Left of headers | Right sidebar navigation |
Top Placement (Default)
import { Component } from '@angular/core';
import { TabModule } from '@syncfusion/ej2-angular-navigations';
@Component({
imports: [TabModule],
standalone: true,
selector: 'app-root',
template: `
<ejs-tab
id="element"
headerPlacement="Top">
<e-tabitems>
<e-tabitem [header]="headerText[0]" [content]="content0"></e-tabitem>
<e-tabitem [header]="headerText[1]" [content]="content1"></e-tabitem>
</e-tabitems>
</ejs-tab>
`
})
export class AppComponent {
public headerText: object[] = [
{ text: 'Home' },
{ text: 'About' }
];
public content0 = 'Home content displayed below';
public content1 = 'About content displayed below';
}Bottom Placement
<ejs-tab headerPlacement="Bottom">
<e-tabitems>
<e-tabitem [header]="headerText[0]" [content]="content0"></e-tabitem>
<e-tabitem [header]="headerText[1]" [content]="content1"></e-tabitem>
</e-tabitems>
</ejs-tab>Result: Content appears above tab headers (useful for footer navigation)
Left Placement (Vertical)
import { Component } from '@angular/core';
import { TabModule } from '@syncfusion/ej2-angular-navigations';
@Component({
imports: [TabModule],
standalone: true,
selector: 'app-root',
template: `
<ejs-tab
id="element"
headerPlacement="Left"
height="300px">
<e-tabitems>
<e-tabitem [header]="headerText[0]" [content]="content0"></e-tabitem>
<e-tabitem [header]="headerText[1]" [content]="content1"></e-tabitem>
<e-tabitem [header]="headerText[2]" [content]="content2"></e-tabitem>
</e-tabitems>
</ejs-tab>
`,
styles: [`
:host ::ng-deep .e-tab {
width: 100%;
}
`]
})
export class AppComponent {
public headerText: object[] = [
{ text: 'Dashboard' },
{ text: 'Settings' },
{ text: 'Profile' }
];
public content0 = 'Dashboard content on the right';
public content1 = 'Settings content on the right';
public content2 = 'Profile content on the right';
}Result: Headers displayed vertically on left side, content on right
Right Placement (Vertical)
<ejs-tab
headerPlacement="Right"
height="300px">
<e-tabitems>
<e-tabitem [header]="headerText[0]" [content]="content0"></e-tabitem>
<e-tabitem [header]="headerText[1]" [content]="content1"></e-tabitem>
</e-tabitems>
</ejs-tab>Result: Headers on right side, content on left
Dynamic Orientation Switching
import { Component, ViewChild } from '@angular/core';
import { TabModule, TabComponent } from '@syncfusion/ej2-angular-navigations';
import { DropDownListModule } from '@syncfusion/ej2-angular-dropdowns';
@Component({
imports: [TabModule, DropDownListModule],
standalone: true,
selector: 'app-root',
template: `
<div>
<label>Header Placement:</label>
<select [(ngModel)]="selectedPlacement" (change)="changePlacement()">
<option value="Top">Top</option>
<option value="Bottom">Bottom</option>
<option value="Left">Left</option>
<option value="Right">Right</option>
</select>
<hr />
<ejs-tab
#tabObj
id="element"
[headerPlacement]="selectedPlacement"
height="250px">
<e-tabitems>
<e-tabitem [header]="headerText[0]" [content]="content0"></e-tabitem>
<e-tabitem [header]="headerText[1]" [content]="content1"></e-tabitem>
<e-tabitem [header]="headerText[2]" [content]="content2"></e-tabitem>
</e-tabitems>
</ejs-tab>
</div>
`
})
export class AppComponent {
@ViewChild('tabObj') tabObj?: TabComponent;
public selectedPlacement = 'Top';
public headerText: object[] = [
{ text: 'Tab 1' },
{ text: 'Tab 2' },
{ text: 'Tab 3' }
];
public content0 = 'Content for Tab 1';
public content1 = 'Content for Tab 2';
public content2 = 'Content for Tab 3';
changePlacement(): void {
if (this.tabObj) {
this.tabObj.headerPlacement = this.selectedPlacement as any;
this.tabObj.refresh();
}
}
}---
Overflow Modes
When tab headers exceed available width, use the `overflowMode` property to handle overflow.
Property: overflowMode
- Type: string (OverflowMode enum)
- Default: 'Scrollable'
- Options: 'Scrollable', 'Popup'
| Mode | Behavior | Best For |
|---|---|---|
| Scrollable | Arrow buttons for left/right scrolling | Large number of tabs |
| Popup | Hidden tabs in dropdown menu | Limited space, few tabs at a time |
Scrollable Mode (Default)
import { Component } from '@angular/core';
import { TabModule } from '@syncfusion/ej2-angular-navigations';
@Component({
imports: [TabModule],
standalone: true,
selector: 'app-root',
template: `
<ejs-tab
id="element"
overflowMode="Scrollable"
width="300px">
<e-tabitems>
<e-tabitem [header]="headerText[0]" [content]="content0"></e-tabitem>
<e-tabitem [header]="headerText[1]" [content]="content1"></e-tabitem>
<e-tabitem [header]="headerText[2]" [content]="content2"></e-tabitem>
<e-tabitem [header]="headerText[3]" [content]="content3"></e-tabitem>
<e-tabitem [header]="headerText[4]" [content]="content4"></e-tabitem>
</e-tabitems>
</ejs-tab>
`
})
export class AppComponent {
public headerText: object[] = [
{ text: 'Home' },
{ text: 'Products' },
{ text: 'About' },
{ text: 'Contact' },
{ text: 'FAQ' }
];
public content0 = 'Home content';
public content1 = 'Products content';
public content2 = 'About content';
public content3 = 'Contact content';
public content4 = 'FAQ content';
}Result: Left/right arrow buttons appear to scroll through tabs
Popup Mode
import { Component } from '@angular/core';
import { TabModule } from '@syncfusion/ej2-angular-navigations';
@Component({
imports: [TabModule],
standalone: true,
selector: 'app-root',
template: `
<ejs-tab
id="element"
overflowMode="Popup"
width="300px">
<e-tabitems>
<e-tabitem [header]="headerText[0]" [content]="content0"></e-tabitem>
<e-tabitem [header]="headerText[1]" [content]="content1"></e-tabitem>
<e-tabitem [header]="headerText[2]" [content]="content2"></e-tabitem>
<e-tabitem [header]="headerText[3]" [content]="content3"></e-tabitem>
<e-tabitem [header]="headerText[4]" [content]="content4"></e-tabitem>
</e-tabitems>
</ejs-tab>
`
})
export class AppComponent {
public headerText: object[] = [
{ text: 'Home' },
{ text: 'Products' },
{ text: 'About' },
{ text: 'Contact' },
{ text: 'FAQ' }
];
public content0 = 'Home content';
public content1 = 'Products content';
public content2 = 'About content';
public content3 = 'Contact content';
public content4 = 'FAQ content';
}Result: Dropdown menu shows hidden tabs
Configurable Scroll Step
Use `scrollStep` to control how many pixels the tab header scrolls when clicking arrow buttons.
import { Component } from '@angular/core';
import { TabModule } from '@syncfusion/ej2-angular-navigations';
@Component({
imports: [TabModule],
standalone: true,
selector: 'app-root',
template: `
<ejs-tab
id="element"
overflowMode="Scrollable"
[scrollStep]="50"
width="300px">
<e-tabitems>
<e-tabitem
*ngFor="let item of headerText"
[header]="item"
[content]="'Content for ' + item.text">
</e-tabitem>
</e-tabitems>
</ejs-tab>
`
})
export class AppComponent {
public headerText: object[] = Array.from({ length: 10 }, (_, i) => ({
text: `Tab ${i + 1}`
}));
}Property: scrollStep
- Type: number
- Default: 0 (auto-calculated)
- Unit: pixels
---
Responsive Orientation Changes
Automatically change orientation based on screen size using @HostListener or window.matchMedia.
import { Component, ViewChild, HostListener } from '@angular/core';
import { TabModule, TabComponent } from '@syncfusion/ej2-angular-navigations';
@Component({
imports: [TabModule],
standalone: true,
selector: 'app-root',
template: `
<div>
<p>Current layout: {{ currentLayout }}</p>
<ejs-tab
#tabObj
id="element"
[headerPlacement]="headerPlacement"
height="300px">
<e-tabitems>
<e-tabitem [header]="headerText[0]" [content]="content0"></e-tabitem>
<e-tabitem [header]="headerText[1]" [content]="content1"></e-tabitem>
<e-tabitem [header]="headerText[2]" [content]="content2"></e-tabitem>
</e-tabitems>
</ejs-tab>
</div>
`
})
export class AppComponent {
@ViewChild('tabObj') tabObj?: TabComponent;
public headerPlacement = 'Top';
public currentLayout = 'Desktop (Headers Top)';
public headerText: object[] = [
{ text: 'Tab 1' },
{ text: 'Tab 2' },
{ text: 'Tab 3' }
];
public content0 = 'Content 1';
public content1 = 'Content 2';
public content2 = 'Content 3';
constructor() {
this.checkScreenSize();
}
@HostListener('window:resize', ['$event'])
onResize(event: Event): void {
this.checkScreenSize();
}
private checkScreenSize(): void {
const width = window.innerWidth;
if (width < 768) {
// Mobile: Left orientation
this.headerPlacement = 'Left';
this.currentLayout = 'Mobile (Headers Left)';
} else if (width < 1024) {
// Tablet: Top orientation
this.headerPlacement = 'Top';
this.currentLayout = 'Tablet (Headers Top)';
} else {
// Desktop: Top orientation
this.headerPlacement = 'Top';
this.currentLayout = 'Desktop (Headers Top)';
}
if (this.tabObj) {
this.tabObj.headerPlacement = this.headerPlacement as any;
this.tabObj.refresh();
}
}
}---
Best Practices
✅ Do:
- Use
localewithL10n.load()for multi-language support - Change
headerPlacementbased on available space - Use
Scrollablemode for many tabs,Popupfor limited space - Test localization with different character sets and RTL languages
❌ Don't:
- Hardcode localization strings in templates
- Change
headerPlacementtoo frequently (impacts performance) - Mix multiple overflow modes in single Tab
- Forget to register localization before setting locale property
---
Related Properties
| Property | Type | Purpose |
|---|---|---|
locale | string | Sets UI language and culture |
headerPlacement | string | Header position (Top/Bottom/Left/Right) |
overflowMode | string | Overflow handling (Scrollable/Popup) |
scrollStep | number | Pixels to scroll per click |
reorderActiveTab | boolean | Reorder from popup |
Localization Locale Codes
Common locale codes:
en-US- English (United States)en-GB- English (United Kingdom)fr-FR- French (France)fr-BE- French (Belgium)de-DE- German (Germany)es-ES- Spanish (Spain)it-IT- Italian (Italy)pt-BR- Portuguese (Brazil)ja-JP- Japanese (Japan)zh-CN- Chinese (Simplified)
Content Rendering Modes
Table of Contents
- Overview
- On Demand (Default)
- Dynamic Mode
- Init Mode
- Comparison & Selection Guide
- Performance Considerations
Overview
The Tab component supports three content rendering modes that balance performance, memory usage, and state preservation. Choosing the right mode depends on your application's needs:
- Number of tabs (few vs many)
- Content complexity (simple text vs components)
- State persistence (must preserve vs not needed)
- Memory constraints (mobile vs desktop)
On Demand (Default)
How It Works
Only the content of the currently selected tab is initially loaded in the DOM. When users switch tabs, content is rendered on demand. Once rendered, content stays in the DOM to preserve state.
Configuration
// Default behavior - no configuration needed
<ejs-tab id="element">
<e-tabitems>
<e-tabitem [header]="headerText[0]">
<ng-template #content>
<ejs-calendar></ejs-calendar>
</ng-template>
</e-tabitem>
<e-tabitem [header]="headerText[1]">
<ng-template #content>
<ejs-schedule width="100%" height="650px">
<e-views>
<e-view option="Day"></e-view>
</e-views>
</ejs-schedule>
</ng-template>
</e-tabitem>
</e-tabitems>
</ejs-tab>Behavior
1. Tab 1 (Calendar) renders on initial load 2. Tab 2 (Scheduler) is NOT in DOM initially 3. When user clicks Tab 2, Scheduler is rendered and added to DOM 4. Both components remain in DOM - clicking back shows Calendar immediately
Advantages
- Balanced performance: Faster initial load than Init mode
- State preservation: Tab content maintains scroll position, form values, component state
- Memory efficient: Only active + accessed tabs in DOM
- Smooth UX: No re-rendering when switching to previously viewed tab
Disadvantages
- Initial rendering: First tab access takes longer than already-rendered tabs
- Memory growth: DOM grows as users access more tabs
- Component overhead: Previously loaded components remain in memory
Best For
- Few to moderate tabs (3-15): Good balance of performance and UX
- Complex components: Preserve state between tab switches
- Business applications: Users need to switch back and forth
- Mobile apps: Not too many tabs, good memory/performance balance
Dynamic Mode
How It Works
Only the currently active tab's content is in the DOM. When users switch tabs, the active content is replaced with the new tab's content. Previous tab content is removed from the DOM.
Configuration
<ejs-tab
id="element"
loadOn="Dynamic"
heightAdjustMode="Auto">
<e-tabitems>
<e-tabitem [header]="headerText[0]">
<ng-template #content>
<app-login-form></app-login-form>
</ng-template>
</e-tabitem>
<e-tabitem [header]="headerText[1]">
<ng-template #content>
<ejs-grid [dataSource]="gridData"></ejs-grid>
</ng-template>
</e-tabitem>
</e-tabitems>
</ejs-tab>Behavior
1. Tab 1 (Login) renders on load 2. User clicks Tab 2 (Grid) 3. Tab 1 content removed from DOM 4. Tab 2 content added to DOM 5. User clicks Tab 1 again 6. Tab 2 content removed, Tab 1 re-rendered from scratch
Advantages
- Optimal memory usage: Only one tab's content in DOM at any time
- Fastest initial load: Minimal initial DOM
- Large tab sets: Scale well with many tabs (20+)
- Lightweight: No accumulation of DOM nodes
Disadvantages
- No state preservation: Tab re-renders on each visit (form loses input, scroll resets)
- Slower switching: Each tab switch triggers re-render
- UX friction: Stateful components reset every visit
- Component re-initialization: Event handlers, subscriptions re-bind
Best For
- Many tabs (15+): Memory-constrained or performance-critical
- Stateless content: Tabs display reference data, not user edits
- Mobile devices: Limited memory, prioritize performance
- Single-view workflows: Users typically visit each tab once
- Heavy components: Each tab is computationally expensive
Example: When Dynamic Works Well
// Read-only data display - state doesn't matter
<ejs-tab loadOn="Dynamic">
<e-tabitems>
<e-tabitem [header]="{ text: 'Products' }">
<ng-template #content>
<ejs-grid [dataSource]="products"></ejs-grid>
</ng-template>
</e-tabitem>
<e-tabitem [header]="{ text: 'Orders' }">
<ng-template #content>
<ejs-grid [dataSource]="orders"></ejs-grid>
</ng-template>
</e-tabitem>
<e-tabitem [header]="{ text: 'Customers' }">
<ng-template #content>
<ejs-grid [dataSource]="customers"></ejs-grid>
</ng-template>
</e-tabitem>
</e-tabitems>
</ejs-tab>Init Mode
How It Works
All tab content is rendered on initial component load and stays in the DOM. No lazy loading or on-demand rendering occurs.
Configuration
<ejs-tab
id="element"
loadOn="Init"
heightAdjustMode="Auto">
<e-tabitems>
<e-tabitem [header]="{ text: 'Dashboard' }">
<ng-template #content>
<app-dashboard></app-dashboard>
</ng-template>
</e-tabitem>
<e-tabitem [header]="{ text: 'Analytics' }">
<ng-template #content>
<app-analytics></app-analytics>
</ng-template>
</e-tabitem>
<e-tabitem [header]="{ text: 'Reports' }">
<ng-template #content>
<app-reports></app-reports>
</ng-template>
</e-tabitem>
</e-tabitems>
</ejs-tab>Behavior
1. Component renders all 3 tabs (Dashboard, Analytics, Reports) immediately 2. All content in DOM before user interaction 3. User clicks between tabs instantly - no rendering 4. Switching is instant because content already exists 5. Can access component references from other tabs
Advantages
- Instant switching: Fastest tab navigation (no re-render)
- State preservation: Full state across all tabs
- Component references: Access any tab's components programmatically
- Predictable UX: No lag when switching
Disadvantages
- Slow initial load: All tabs rendered upfront, slower first paint
- High memory usage: All components in DOM simultaneously
- Poor scaling: Breaks with many tabs (10+)
- Resource intensive: Every tab's API calls, subscriptions load immediately
Best For
- Few tabs (3-7): Small number manageable upfront
- Simple content: Light components, quick to render
- Dashboard-like apps: All panes typically visible eventually
- Stateful workflows: Need immediate state access
- Desktop apps: Memory not a constraint
Comparison & Selection Guide
| Aspect | On Demand | Dynamic | Init |
|---|---|---|---|
| Initial Load Speed | Medium | Fast | Slow |
| Tab Switching Speed | Very Fast (cached) | Slow | Very Fast |
| Memory Usage | Medium | Minimal | High |
| State Preservation | Yes | No | Yes |
| Ideal Tab Count | 3-15 | 15+ | 3-7 |
| Form Data Retention | Yes | No | Yes |
| Component Reuse | Partial | Full | Full |
Decision Tree
Question: How many tabs do you have?
├─ 3-7 tabs AND simple content
│ └─→ Use Init Mode (fast switching)
├─ 3-15 tabs AND need state preservation
│ └─→ Use On Demand (balanced)
└─ 15+ tabs OR memory constrained
└─→ Use Dynamic (optimized)
Question: Do users need to preserve their edits between tab switches?
├─ Yes → Use On Demand or Init
└─ No → Use Dynamic
Question: Is initial page load speed critical?
├─ Yes → Use On Demand or Dynamic
└─ No → Use InitPerformance Considerations
Measuring Performance
// Monitor tab rendering performance
export class AppComponent implements OnInit {
@ViewChild('tabObj') tabObj?: TabComponent;
ngOnInit() {
// Measure time to first tab render
console.time('tab-render');
// ... tab initialization
console.timeEnd('tab-render');
}
onTabSelected(args: SelectEventArgs) {
// Measure tab switching time
console.time('switch-' + args.selectedIndex);
// ... tab switch complete
console.timeEnd('switch-' + args.selectedIndex);
}
}Memory Profiling
Use Chrome DevTools to profile memory usage: 1. Open DevTools → Memory tab 2. Take heap snapshot before and after tab operations 3. Compare memory growth across modes 4. Identify memory leaks in components
Best Practices
- On Demand: Default choice for most applications
- Dynamic: Only if profiling shows memory issues
- Init: Only if performance profiling shows fast enough
- Profile first: Test with realistic content before choosing
- Mobile focus: Use Dynamic or On Demand on mobile
- Desktop focus: On Demand or Init acceptable
- Monitor: Track performance metrics in production
Switching Between Modes
You can change rendering mode at runtime:
export class AppComponent {
@ViewChild('tabObj') tabObj?: TabComponent;
switchMode(mode: 'Dynamic' | 'Init' | 'Default') {
(this.tabObj as TabComponent).loadOn = mode;
(this.tabObj as TabComponent).refresh();
}
}However, switching modes causes all content to be re-rendered, so do this sparingly.
Responsive & Adaptive Behavior
Table of Contents
Adaptive Rendering
The Tab component automatically adapts to constrained space:
Basic Adaptive Tabs
import { Component } from '@angular/core';
import { TabModule } from '@syncfusion/ej2-angular-navigations';
@Component({
imports: [TabModule],
standalone: true,
selector: 'app-root',
template: `
<!-- Adaptive tabs with constrained width -->
<div style="width: 400px; border: 1px solid #ccc;">
<ejs-tab>
<e-tabitems>
<e-tabitem [header]="{ text: 'Dashboard' }" content="Dashboard"></e-tabitem>
<e-tabitem [header]="{ text: 'Products' }" content="Products"></e-tabitem>
<e-tabitem [header]="{ text: 'Services' }" content="Services"></e-tabitem>
<e-tabitem [header]="{ text: 'Support' }" content="Support"></e-tabitem>
<e-tabitem [header]="{ text: 'Settings' }" content="Settings"></e-tabitem>
<e-tabitem [header]="{ text: 'About' }" content="About"></e-tabitem>
</e-tabitems>
</ejs-tab>
</div>
`
})
export class AppComponent { }Width Constraint
<ejs-tab [width]="'400px'" [height]="'300px'">
<e-tabitems>
<!-- tabs -->
</e-tabitems>
</ejs-tab>Overflow Modes
Handle tabs that don't fit in available space:
Scrollable Mode
Scroll arrows appear when tabs exceed container width:
import { Component } from '@angular/core';
import { TabModule } from '@syncfusion/ej2-angular-navigations';
@Component({
imports: [TabModule],
standalone: true,
selector: 'app-root',
template: `
<!-- Many tabs with scroll arrows -->
<ejs-tab [overflowMode]="'Scrollable'" style="width: 500px;">
<e-tabitems>
<e-tabitem [header]="{ text: 'Tab 1' }" content="Content 1"></e-tabitem>
<e-tabitem [header]="{ text: 'Tab 2' }" content="Content 2"></e-tabitem>
<e-tabitem [header]="{ text: 'Tab 3' }" content="Content 3"></e-tabitem>
<e-tabitem [header]="{ text: 'Tab 4' }" content="Content 4"></e-tabitem>
<e-tabitem [header]="{ text: 'Tab 5' }" content="Content 5"></e-tabitem>
<e-tabitem [header]="{ text: 'Tab 6' }" content="Content 6"></e-tabitem>
<e-tabitem [header]="{ text: 'Tab 7' }" content="Content 7"></e-tabitem>
<e-tabitem [header]="{ text: 'Tab 8' }" content="Content 8"></e-tabitem>
</e-tabitems>
</ejs-tab>
`
})
export class AppComponent { }Features:
- Left/right arrow buttons to scroll through tabs
- Smooth scrolling
- Keyboard navigation still works
- Active tab stays visible
Popup Mode
Overflowing tabs appear in a dropdown menu:
import { Component } from '@angular/core';
import { TabModule } from '@syncfusion/ej2-angular-navigations';
@Component({
imports: [TabModule],
standalone: true,
selector: 'app-root',
template: `
<!-- Overflow tabs in popup menu -->
<ejs-tab [overflowMode]="'Popup'" style="width: 400px;">
<e-tabitems>
<e-tabitem [header]="{ text: 'Home' }" content="Home content"></e-tabitem>
<e-tabitem [header]="{ text: 'Features' }" content="Features content"></e-tabitem>
<e-tabitem [header]="{ text: 'Pricing' }" content="Pricing content"></e-tabitem>
<e-tabitem [header]="{ text: 'Blog' }" content="Blog content"></e-tabitem>
<e-tabitem [header]="{ text: 'Support' }" content="Support content"></e-tabitem>
<e-tabitem [header]="{ text: 'Contact' }" content="Contact content"></e-tabitem>
<e-tabitem [header]="{ text: 'About' }" content="About content"></e-tabitem>
</e-tabitems>
</ejs-tab>
`
})
export class AppComponent { }Features:
- Overflowing tabs hidden by default
- Click dropdown arrow to see all tabs
- Click tab to select from popup
- Responsive to width changes
Choosing Between Modes
| Scenario | Mode | Reason |
|---|---|---|
| Users need to see all tabs | Scrollable | Preserves visibility |
| Many tabs (10+) | Popup | Saves space |
| Few tabs (5-7) | Default | No scrolling needed |
| Mobile | Popup | Better mobile UX |
Responsive Tab Display
Mobile-First Responsive Tabs
import { Component, HostListener } from '@angular/core';
import { TabModule } from '@syncfusion/ej2-angular-navigations';
import { CommonModule } from '@angular/common';
@Component({
imports: [TabModule, CommonModule],
standalone: true,
selector: 'app-root',
template: `
<!-- Responsive tab container -->
<div [ngStyle]="{ 'width': containerWidth, 'padding': containerPadding }">
<ejs-tab [overflowMode]="overflowMode">
<e-tabitems>
<e-tabitem [header]="{ text: 'Mobile' }" content="Mobile optimized"></e-tabitem>
<e-tabitem [header]="{ text: 'Tablet' }" content="Tablet view"></e-tabitem>
<e-tabitem [header]="{ text: 'Desktop' }" content="Desktop view"></e-tabitem>
</e-tabitems>
</ejs-tab>
</div>
`
})
export class AppComponent {
public containerWidth = '100%';
public containerPadding = '16px';
public overflowMode = 'Popup';
@HostListener('window:resize', ['$event'])
onResize(event: Event) {
const width = window.innerWidth;
if (width < 600) {
// Mobile
this.containerWidth = '100%';
this.containerPadding = '8px';
this.overflowMode = 'Popup';
} else if (width < 1024) {
// Tablet
this.containerWidth = '100%';
this.containerPadding = '12px';
this.overflowMode = 'Popup';
} else {
// Desktop
this.containerWidth = '90%';
this.containerPadding = '20px';
this.overflowMode = 'Scrollable';
}
}
}CSS Media Queries for Responsive Design
/* Mobile: < 600px */
@media (max-width: 600px) {
.e-tab {
width: 100%;
}
.e-tab .e-tab-wrap {
font-size: 12px;
padding: 8px 10px;
}
.e-tab .e-tab-header .e-toolbar-item .e-tab-icon {
margin-right: 4px;
}
.e-tab .e-content {
padding: 10px;
font-size: 13px;
}
}
/* Tablet: 600px - 1024px */
@media (min-width: 600px) and (max-width: 1024px) {
.e-tab .e-tab-wrap {
font-size: 13px;
padding: 10px 14px;
}
.e-tab .e-content {
padding: 15px;
font-size: 14px;
}
}
/* Desktop: > 1024px */
@media (min-width: 1024px) {
.e-tab .e-tab-wrap {
font-size: 14px;
padding: 12px 18px;
}
.e-tab .e-content {
padding: 20px;
font-size: 14px;
line-height: 1.6;
}
}Orientation
Tab component supports horizontal and vertical layouts:
Horizontal Orientation (Default)
<ejs-tab [orientation]="'Horizontal'">
<e-tabitems>
<e-tabitem [header]="{ text: 'Left' }" content="Content 1"></e-tabitem>
<e-tabitem [header]="{ text: 'Center' }" content="Content 2"></e-tabitem>
<e-tabitem [header]="{ text: 'Right' }" content="Content 3"></e-tabitem>
</e-tabitems>
</ejs-tab>Vertical Orientation
import { Component } from '@angular/core';
import { TabModule } from '@syncfusion/ej2-angular-navigations';
@Component({
imports: [TabModule],
standalone: true,
selector: 'app-root',
template: `
<!-- Headers on left, content on right -->
<ejs-tab [orientation]="'Vertical'" style="display: flex; height: 400px;">
<e-tabitems>
<e-tabitem [header]="{ text: 'Overview' }" content="Overview content"></e-tabitem>
<e-tabitem [header]="{ text: 'Details' }" content="Detailed information"></e-tabitem>
<e-tabitem [header]="{ text: 'Settings' }" content="Configuration options"></e-tabitem>
</e-tabitems>
</ejs-tab>
`
})
export class AppComponent { }Orientation Change on Resize
import { Component, HostListener, ViewChild } from '@angular/core';
import { TabModule, TabComponent } from '@syncfusion/ej2-angular-navigations';
@Component({
imports: [TabModule],
standalone: true,
selector: 'app-root',
template: `
<ejs-tab #tabObj [orientation]="tabOrientation">
<e-tabitems>
<e-tabitem [header]="{ text: 'Tab 1' }" content="Content 1"></e-tabitem>
<e-tabitem [header]="{ text: 'Tab 2' }" content="Content 2"></e-tabitem>
</e-tabitems>
</ejs-tab>
`
})
export class AppComponent {
@ViewChild('tabObj') tabObj?: TabComponent;
public tabOrientation = 'Horizontal';
@HostListener('window:resize', ['$event'])
onResize(event: Event) {
const width = window.innerWidth;
if (width < 800) {
// Switch to horizontal on mobile
this.tabOrientation = 'Horizontal';
} else {
// Use vertical on desktop
this.tabOrientation = 'Vertical';
}
(this.tabObj as TabComponent).refresh();
}
}Touch & Swipe Control
Use the `swipeMode` property to control swipe-based navigation on touch devices.
Prevent Swipe Selection
Disable swipe-based navigation using the swipeMode property:
import { Component } from '@angular/core';
import { TabModule } from '@syncfusion/ej2-angular-navigations';
@Component({
imports: [TabModule],
standalone: true,
selector: 'app-root',
template: `
<!-- Swipe navigation disabled -->
<ejs-tab swipeMode="None">
<e-tabitems>
<e-tabitem [header]="{ text: 'Tap to select' }" content="Content 1"></e-tabitem>
<e-tabitem [header]="{ text: 'No swiping' }" content="Content 2"></e-tabitem>
</e-tabitems>
</ejs-tab>
`
})
export class AppComponent { }SwipeMode Options:
- Both (default) - Swipe with touch and mouse
- Touch - Swipe with touch only
- Mouse - Swipe with mouse only
- None - Disable all swipe navigation
Custom Touch Handler
import { Component, ViewChild, HostListener } from '@angular/core';
import { TabModule, TabComponent } from '@syncfusion/ej2-angular-navigations';
@Component({
imports: [TabModule],
standalone: true,
selector: 'app-root',
template: `
<ejs-tab #tabObj [allowDragAndDrop]="false">
<e-tabitems>
<e-tabitem [header]="{ text: 'Swipe Enabled' }" content="Content 1"></e-tabitem>
<e-tabitem [header]="{ text: 'Tab 2' }" content="Content 2"></e-tabitem>
<e-tabitem [header]="{ text: 'Tab 3' }" content="Content 3"></e-tabitem>
</e-tabitems>
</ejs-tab>
`
})
export class AppComponent {
@ViewChild('tabObj') tabObj?: TabComponent;
private touchStartX = 0;
private touchStartY = 0;
@HostListener('touchstart', ['$event'])
onTouchStart(event: TouchEvent) {
this.touchStartX = event.touches[0].clientX;
this.touchStartY = event.touches[0].clientY;
}
@HostListener('touchend', ['$event'])
onTouchEnd(event: TouchEvent) {
const touchEndX = event.changedTouches[0].clientX;
const touchEndY = event.changedTouches[0].clientY;
const deltaX = this.touchStartX - touchEndX;
const deltaY = this.touchStartY - touchEndY;
// Only swipe if horizontal movement > vertical movement
if (Math.abs(deltaX) > Math.abs(deltaY) && Math.abs(deltaX) > 50) {
const tab = this.tabObj as TabComponent;
const currentIndex = tab.selectedIndex!;
const totalTabs = tab.items!.length;
if (deltaX > 0) {
// Swipe left - next tab
tab.select((currentIndex + 1) % totalTabs);
} else {
// Swipe right - previous tab
tab.select(currentIndex === 0 ? totalTabs - 1 : currentIndex - 1);
}
}
}
}Best Practices
- Mobile First: Design for mobile, then enhance for larger screens
- Overflow Mode: Use Popup for mobile, Scrollable for desktop
- Touch Friendly: Ensure tabs are at least 44x44px for touch
- Orientation: Provide horizontal for small screens, vertical for wide screens
- Performance: Use Dynamic rendering mode for many responsive tabs
- Testing: Test on actual devices and various screen sizes
- Accessibility: Maintain proper focus and keyboard navigation on all sizes
Performance Tips
- Lazy load content in responsive tabs (use Dynamic or On Demand modes)
- Minimize re-renders during window resize (debounce resize event)
- Use
mediaqueries over JavaScript when possible - Profile on mobile devices, not just desktop browsers
- Test with realistic content (not empty tabs)