Creating dynamic, responsive user interfaces is at the heart of modern web development. Angular’s *ngIf directive stands as one of the most powerful and frequently used tools for conditional rendering, allowing developers to show or hide elements based on specific conditions. Whether you’re building a simple toggle button or complex multi-step forms, mastering *ngIf is essential for creating intuitive user experiences.
This comprehensive guide will transform your understanding of Angular’s conditional rendering capabilities, from basic syntax to advanced optimization techniques that will make your applications faster and more maintainable.
What Is Conditional Rendering and Why It Matters
Conditional rendering is the practice of displaying or hiding UI elements based on application state, user interactions, or data conditions. This fundamental concept enables developers to create dynamic interfaces that respond intelligently to changing circumstances.
Why conditional rendering is crucial for modern web applications:
- Enhanced user experience by showing relevant content at the right time
- Improved performance by rendering only necessary elements
- Better accessibility through context-aware interface states
- Simplified user workflows with step-by-step guidance
- Reduced cognitive load by hiding irrelevant information
Consider an e-commerce application where product details, shipping options, and payment forms appear conditionally based on user selections. Without proper conditional rendering, users would face overwhelming interfaces filled with irrelevant options.
Overview of *ngIf in Angular for Dynamic UI
Angular’s *ngIf directive provides a declarative approach to conditional rendering that integrates seamlessly with the framework’s change detection system. Unlike traditional DOM manipulation, *ngIf works at the template level, offering better performance and cleaner code organization.
*Key advantages of ngIf over manual DOM manipulation:
- Automatic change detection ensures UI stays synchronized with data
- Type safety through TypeScript integration
- Built-in performance optimizations with OnPush change detection
- Testability through Angular’s testing utilities
- Accessibility support with proper ARIA attribute handling
Understanding the Basics of *ngIf
What Is *ngIf in Angular?
*ngIf is a structural directive in Angular that conditionally includes or excludes a template fragment from the DOM based on a boolean expression. When the expression evaluates to true, the element and its children are rendered; when false, they’re completely removed from the DOM.
// Component property
export class AppComponent {
showWelcomeMessage = true;
userLoggedIn = false;
itemCount = 0;
}
<!-- Basic *ngIf usage -->
<div *ngIf="showWelcomeMessage">
<h2>Welcome to our application!</h2>
<p>We're glad you're here.</p>
</div>
How *ngIf Works Behind the Scenes in the DOM
Understanding *ngIf’s internal mechanics helps developers make informed decisions about when and how to use it effectively.
DOM Manipulation Process:
- Expression Evaluation: Angular evaluates the *ngIf expression during change detection
- DOM Insertion/Removal: Elements are completely added or removed from the DOM tree
- Component Lifecycle: Child components are created/destroyed with their parent elements
- Memory Management: Removed elements are garbage collected, freeing memory
<!-- Before: Element exists in DOM -->
<div *ngIf="true">This content is rendered</div>
<!-- After: Element completely removed from DOM -->
<div *ngIf="false">This content doesn't exist</div>
This approach differs significantly from CSS-based hiding (like display: none
), where elements remain in the DOM but are visually hidden.
Basic Syntax and Usage
The Simplest Example of *ngIf in Action
Start with straightforward boolean conditions to understand *ngIf’s fundamental behavior:
export class BasicExampleComponent {
isVisible = true;
hasPermission = false;
loading = true;
toggleVisibility(): void {
this.isVisible = !this.isVisible;
}
simulateLogin(): void {
this.loading = true;
setTimeout(() => {
this.hasPermission = true;
this.loading = false;
}, 2000);
}
}
<!-- Simple boolean conditions -->
<button (click)="toggleVisibility()">Toggle Content</button>
<div *ngIf="isVisible">
<h3>This content can be toggled on and off</h3>
<p>Click the button above to see the effect.</p>
</div>
<div *ngIf="loading">
<p>Loading user permissions...</p>
</div>
<div *ngIf="hasPermission">
<h4>Welcome, authorized user!</h4>
<p>You have access to premium features.</p>
</div>
Binding Boolean Values to *ngIf
*ngIf accepts any expression that can be evaluated as truthy or falsy, providing flexibility in conditional logic:
export class ConditionalComponent {
user = {
name: 'John Doe',
email: '[email protected]',
role: 'admin',
isActive: true
};
products: any[] = [];
errorMessage = '';
selectedCategory = 'electronics';
// Method returning boolean
hasAdminRights(): boolean {
return this.user.role === 'admin' && this.user.isActive;
}
// Getter for reactive conditions
get hasProducts(): boolean {
return this.products.length > 0;
}
}
<!-- Different types of boolean expressions -->
<div *ngIf="user.isActive">User is currently active</div>
<div *ngIf="hasAdminRights()">
<h4>Admin Panel</h4>
<p>Administrative functions available here.</p>
</div>
<div *ngIf="hasProducts">
<h4>Product Catalog</h4>
<p>Showing {{ products.length }} products</p>
</div>
<div *ngIf="!errorMessage">
<p>All systems operational</p>
</div>
<!-- Comparing values -->
<div *ngIf="selectedCategory === 'electronics'">
<h4>Electronics Department</h4>
<p>Browse our latest tech products</p>
</div>
Using *ngIf with Else Conditions
Introducing the else Template Reference
Angular’s *ngIf supports else conditions through template references, enabling clean either/or logic without duplicating conditional expressions:
<!-- Basic if/else structure -->
<div *ngIf="userLoggedIn; else loginPrompt">
<h3>Welcome back, {{ user.name }}!</h3>
<button (click)="logout()">Logout</button>
</div>
<ng-template #loginPrompt>
<h3>Please log in to continue</h3>
<button (click)="showLogin()">Login</button>
</ng-template>
Creating Fallback UIs Using *ngIf and Else
Implement comprehensive fallback patterns for better user experience:
export class FallbackComponent {
dataLoading = true;
data: any[] = [];
error: string | null = null;
async loadData(): Promise<void> {
try {
this.dataLoading = true;
this.error = null;
// Simulate API call
const response = await this.dataService.fetchData();
this.data = response;
} catch (err) {
this.error = 'Failed to load data. Please try again.';
this.data = [];
} finally {
this.dataLoading = false;
}
}
}
<!-- Comprehensive loading/error/success states -->
<div *ngIf="dataLoading; else dataContent">
<div class="loading-spinner">
<p>Loading your data...</p>
</div>
</div>
<ng-template #dataContent>
<div *ngIf="error; else successContent">
<div class="error-message">
<h4>Oops! Something went wrong</h4>
<p>{{ error }}</p>
<button (click)="loadData()">Try Again</button>
</div>
</div>
</ng-template>
<ng-template #successContent>
<div *ngIf="data.length > 0; else emptyState">
<h4>Your Data ({{ data.length }} items)</h4>
<ul>
<li *ngFor="let item of data">{{ item.name }}</li>
</ul>
</div>
</ng-template>
<ng-template #emptyState>
<div class="empty-state">
<h4>No data available</h4>
<p>Start by adding some items to see them here.</p>
<button (click)="openAddDialog()">Add Item</button>
</div>
</ng-template>
Combining *ngIf with Then and Else Templates
The Complete Syntax: *ngIf with then/else
For complex conditional logic, use the full then/else syntax to maintain clean, readable templates:
<!-- Complete then/else syntax -->
<div *ngIf="complexCondition; then successTemplate; else failureTemplate"></div>
<ng-template #successTemplate>
<div class="success-panel">
<h4>Operation Successful</h4>
<p>Your request has been processed successfully.</p>
</div>
</ng-template>
<ng-template #failureTemplate>
<div class="failure-panel">
<h4>Operation Failed</h4>
<p>Please check your input and try again.</p>
</div>
</ng-template>
Structuring Complex UI Logic with Template References
Organize complex conditional layouts using multiple template references:
export class DashboardComponent {
userRole: 'admin' | 'user' | 'guest' = 'user';
isSubscribed = false;
trialExpired = false;
get canAccessPremium(): boolean {
return this.userRole === 'admin' || (this.isSubscribed && !this.trialExpired);
}
get showUpgradePrompt(): boolean {
return this.userRole === 'user' && !this.isSubscribed;
}
}
<!-- Complex dashboard logic with multiple conditions -->
<div class="dashboard-container">
<div *ngIf="userRole === 'admin'; then adminDashboard;
else (canAccessPremium ? premiumDashboard :
(showUpgradePrompt ? upgradePrompt : basicDashboard))">
</div>
</div>
<ng-template #adminDashboard>
<div class="admin-panel">
<h2>Administrator Dashboard</h2>
<div class="admin-controls">
<button>Manage Users</button>
<button>System Settings</button>
<button>Analytics</button>
</div>
</div>
</ng-template>
<ng-template #premiumDashboard>
<div class="premium-panel">
<h2>Premium Dashboard</h2>
<div class="premium-features">
<button>Advanced Analytics</button>
<button>Priority Support</button>
<button>Custom Reports</button>
</div>
</div>
</ng-template>
<ng-template #upgradePrompt>
<div class="upgrade-panel">
<h2>Upgrade to Premium</h2>
<p>Unlock advanced features with our premium subscription.</p>
<button class="upgrade-btn">Upgrade Now</button>
</div>
</ng-template>
<ng-template #basicDashboard>
<div class="basic-panel">
<h2>Basic Dashboard</h2>
<p>Welcome! Explore our basic features.</p>
<div class="basic-features">
<button>View Profile</button>
<button>Basic Reports</button>
</div>
</div>
</ng-template>
Using *ngIf with Multiple Conditions
Logical Operators Inside *ngIf Expressions
Combine multiple conditions using JavaScript logical operators for sophisticated conditional rendering:
export class MultiConditionComponent {
user = {
age: 25,
hasLicense: true,
country: 'US',
creditScore: 750
};
weather = {
temperature: 72,
isRaining: false,
windSpeed: 5
};
inventory = {
inStock: true,
quantity: 50,
reservedItems: 10
};
}
<!-- AND conditions -->
<div *ngIf="user.age >= 18 && user.hasLicense">
<h4>Driving Eligibility Confirmed</h4>
<p>You're eligible to rent a vehicle.</p>
</div>
<!-- OR conditions -->
<div *ngIf="user.country === 'US' || user.country === 'CA'">
<p>Free shipping available in your region!</p>
</div>
<!-- Complex nested conditions -->
<div *ngIf="(user.age >= 21 && user.creditScore > 700) || user.country === 'premium_region'">
<div class="premium-offer">
<h4>Exclusive Premium Offer</h4>
<p>You qualify for our best rates!</p>
</div>
</div>
<!-- Weather-based conditional content -->
<div *ngIf="weather.temperature > 70 && !weather.isRaining && weather.windSpeed < 10">
<div class="outdoor-activity-suggestion">
<h4>Perfect Weather for Outdoor Activities!</h4>
<p>Consider visiting our outdoor venues today.</p>
</div>
</div>
<!-- Inventory availability with multiple checks -->
<div *ngIf="inventory.inStock && (inventory.quantity - inventory.reservedItems) > 5">
<button class="add-to-cart">Add to Cart</button>
<p>{{ inventory.quantity - inventory.reservedItems }} items available</p>
</div>
Best Practices for Readable Conditional Statements
Keep complex conditions maintainable by extracting logic into component methods or getters:
export class ReadableConditionsComponent {
user = { age: 25, hasLicense: true, isVerified: true };
subscription = { isPremium: true, isActive: true, daysLeft: 15 };
preferences = { showAdvanced: true, language: 'en' };
// Extract complex conditions into readable methods
canRentVehicle(): boolean {
return this.user.age >= 18 &&
this.user.hasLicense &&
this.user.isVerified;
}
hasActivePremium(): boolean {
return this.subscription.isPremium &&
this.subscription.isActive &&
this.subscription.daysLeft > 0;
}
shouldShowAdvancedFeatures(): boolean {
return this.hasActivePremium() &&
this.preferences.showAdvanced &&
this.preferences.language === 'en';
}
// Use getters for reactive conditions
get isEligibleForOffer(): boolean {
return this.canRentVehicle() &&
this.hasActivePremium() &&
this.user.age >= 25;
}
}
<!-- Clean, readable template with descriptive method names -->
<div *ngIf="canRentVehicle()">
<h4>Vehicle Rental Available</h4>
<p>You meet all requirements for vehicle rental.</p>
</div>
<div *ngIf="hasActivePremium()">
<div class="premium-content">
<h4>Premium Features</h4>
<p>Enjoy exclusive premium benefits.</p>
</div>
</div>
<div *ngIf="shouldShowAdvancedFeatures()">
<div class="advanced-panel">
<h4>Advanced Settings</h4>
<button>Configure Advanced Options</button>
</div>
</div>
<div *ngIf="isEligibleForOffer">
<div class="special-offer">
<h4>Special Offer Just for You!</h4>
<p>Save 25% on your next rental.</p>
</div>
</div>
Difference Between *ngIf and [hidden]
When to Use *ngIf vs [hidden]
Understanding the fundamental differences between *ngIf and [hidden] is crucial for making informed architectural decisions:
Aspect | *ngIf | [hidden] |
DOM Presence | Removes/adds elements completely | Elements remain in DOM |
Performance | Better for infrequent changes | Better for frequent toggles |
Memory Usage | Lower memory footprint | Higher memory usage |
Component Lifecycle | Triggers ngOnInit/ngOnDestroy | No lifecycle events |
CSS Animations | Requires special handling | Works with standard CSS transitions |
Form State | Resets form values | Preserves form state |
Performance Considerations and DOM Impacts
Choose the right approach based on your specific use case:
export class PerformanceComparisonComponent {
// For expensive components that change rarely
showComplexChart = false;
// For simple toggles that happen frequently
showTooltip = false;
// For form sections that should preserve state
showOptionalFields = false;
// Heavy component with complex initialization
loadChartData(): void {
// Expensive operation - use *ngIf to avoid unnecessary work
this.showComplexChart = true;
}
// Simple state toggle
toggleTooltip(): void {
// Frequent operation - [hidden] might be better
this.showTooltip = !this.showTooltip;
}
}
<!-- Use *ngIf for expensive components -->
<div *ngIf="showComplexChart">
<expensive-chart-component
[data]="chartData"
(dataProcessed)="onChartReady()">
</expensive-chart-component>
</div>
<!-- Use [hidden] for frequent toggles of simple content -->
<div class="tooltip" [hidden]="!showTooltip">
<p>This tooltip toggles frequently without DOM manipulation overhead.</p>
</div>
<!-- Use [hidden] when you need to preserve form state -->
<form>
<div class="basic-fields">
<input [(ngModel)]="name" placeholder="Name" name="name">
<input [(ngModel)]="email" placeholder="Email" name="email">
</div>
<div class="optional-fields" [hidden]="!showOptionalFields">
<input [(ngModel)]="phone" placeholder="Phone" name="phone">
<input [(ngModel)]="company" placeholder="Company" name="company">
<!-- Form values preserved when hidden/shown -->
</div>
<button type="button" (click)="showOptionalFields = !showOptionalFields">
{{ showOptionalFields ? 'Hide' : 'Show' }} Optional Fields
</button>
</form>
Performance Guidelines:
-
*Use ngIf when:
- Components have expensive initialization
- Content changes infrequently
- Memory optimization is important
- You want to trigger component lifecycle events
-
Use [hidden] when:
- Toggling happens frequently
- You need to preserve component/form state
- Content is lightweight
- You’re using CSS animations
Best Practices for Clean and Maintainable Templates
Keeping Templates Readable with Dedicated Variables
Extract complex conditional logic into component properties for improved readability and testability:
export class CleanTemplateComponent {
user: User | null = null;
permissions: Permission[] = [];
currentDate = new Date();
// Dedicated computed properties for template conditions
get isAuthenticated(): boolean {
return this.user !== null;
}
get hasAdminRole(): boolean {
return this.user?.role === 'admin';
}
get canEditContent(): boolean {
return this.isAuthenticated &&
(this.hasAdminRole || this.permissions.includes('EDIT_CONTENT'));
}
get canDeleteContent(): boolean {
return this.isAuthenticated &&
this.hasAdminRole &&
this.permissions.includes('DELETE_CONTENT');
}
get isBusinessHours(): boolean {
const hour = this.currentDate.getHours();
return hour >= 9 && hour <= 17;
}
get showMaintenanceNotice(): boolean {
return !this.isBusinessHours && this.hasAdminRole;
}
}
<!-- Clean, self-documenting template -->
<nav *ngIf="isAuthenticated">
<ul>
<li><a routerLink="/dashboard">Dashboard</a></li>
<li *ngIf="canEditContent"><a routerLink="/edit">Edit Content</a></li>
<li *ngIf="hasAdminRole"><a routerLink="/admin">Admin Panel</a></li>
</ul>
</nav>
<div *ngIf="!isAuthenticated">
<login-component></login-component>
</div>
<div *ngIf="showMaintenanceNotice">
<maintenance-notice></maintenance-notice>
</div>
<main *ngIf="isAuthenticated">
<content-editor *ngIf="canEditContent"></content-editor>
<delete-button *ngIf="canDeleteContent"></delete-button>
</main>
Avoiding Logic Overload in the HTML
Prevent templates from becoming cluttered with complex expressions:
// ❌ Bad: Complex logic in template
/*
<div *ngIf="user && user.subscription && user.subscription.plan === 'premium' &&
user.subscription.expiryDate > currentDate &&
user.preferences.showAdvanced &&
features.advancedAnalytics.enabled">
<!-- content -->
</div>
*/
// ✅ Good: Extract to component logic
export class LogicExtractionComponent {
user: User | null = null;
currentDate = new Date();
features = { advancedAnalytics: { enabled: true } };
get shouldShowAdvancedAnalytics(): boolean {
if (!this.user?.subscription) return false;
const { plan, expiryDate } = this.user.subscription;
const hasValidSubscription = plan === 'premium' && expiryDate > this.currentDate;
const userWantsAdvanced = this.user.preferences?.showAdvanced;
const featureEnabled = this.features.advancedAnalytics.enabled;
return hasValidSubscription && userWantsAdvanced && featureEnabled;
}
// Break down complex conditions into smaller, testable methods
private hasValidPremiumSubscription(): boolean {
return this.user?.subscription?.plan === 'premium' &&
this.user.subscription.expiryDate > this.currentDate;
}
private userPreferencesAllowAdvanced(): boolean {
return this.user?.preferences?.showAdvanced === true;
}
private advancedFeaturesEnabled(): boolean {
return this.features.advancedAnalytics.enabled;
}
}
<!-- Clean template with descriptive condition names -->
<div *ngIf="shouldShowAdvancedAnalytics">
<advanced-analytics-component></advanced-analytics-component>
</div>
Using *ngIf with Angular Pipes
Displaying Conditional Content with Pipe-Transformed Values
Combine Angular pipes with *ngIf for powerful data transformation and conditional rendering:
export class PipeConditionComponent {
prices = [29.99, 149.50, 999.99];
lastUpdate = new Date('2023-12-01T10:30:00');
searchTerm = '';
products = [
{ name: 'Laptop', category: 'Electronics', price: 999.99 },
{ name: 'Book', category: 'Education', price: 29.99 },
{ name: 'Headphones', category: 'Electronics', price: 149.50 }
];
user = {
preferences: {
currency: 'USD',
language: 'en-US'
}
};
}
<!-- Using pipes in *ngIf conditions -->
<div *ngIf="(prices | max) > 100">
<h4>Premium Products Available</h4>
<p>Highest price: {{ prices | max | currency:'USD':'symbol':'1.2-2' }}</p>
</div>
<div *ngIf="(lastUpdate | date:'short') !== (currentDate | date:'short')">
<div class="update-notice">
<p>Data last updated: {{ lastUpdate | date:'medium' }}</p>
<button (click)="refreshData()">Refresh</button>
</div>
</div>
<!-- Conditional rendering based on filtered results -->
<div *ngIf="(products | filter:searchTerm).length > 0; else noResults">
<h4>Search Results ({{ (products | filter:searchTerm).length }})</h4>
<div *ngFor="let product of products | filter:searchTerm">
<div class="product-card">
<h5>{{ product.name }}</h5>
<p>{{ product.price | currency:user.preferences.currency }}</p>
</div>
</div>
</div>
<ng-template #noResults>
<div class="no-results">
<h4>No products found</h4>
<p>Try adjusting your search term: "{{ searchTerm }}"</p>
</div>
</ng-template>
<!-- Async pipe with conditional rendering -->
<div *ngIf="userPermissions$ | async as permissions">
<div *ngIf="permissions.canViewReports">
<h4>Available Reports</h4>
<report-dashboard [permissions]="permissions"></report-dashboard>
</div>
</div>
Custom Pipes + *ngIf: A Powerful Combination
Create custom pipes that work seamlessly with conditional rendering:
// Custom pipe for conditional logic
@Pipe({ name: 'hasPermission' })
export class HasPermissionPipe implements PipeTransform {
transform(user: User | null, permission: string): boolean {
return user?.permissions?.includes(permission) || false;
}
}
@Pipe({ name: 'isExpired' })
export class IsExpiredPipe implements PipeTransform {
transform(date: Date | string): boolean {
const expiryDate = new Date(date);
return expiryDate < new Date();
}
}
@Pipe({ name: 'isEmpty' })
export class IsEmptyPipe implements PipeTransform {
transform(value: any[] | string | null | undefined): boolean {
if (!value) return true;
if (Array.isArray(value)) return value.length === 0;
if (typeof value === 'string') return value.trim().length === 0;
return false;
}
}
<!-- Using custom pipes with *ngIf -->
<div *ngIf="user | hasPermission:'ADMIN'">
<admin-panel></admin-panel>
</div>
<div *ngIf="!(user.subscription.endDate | isExpired)">
<premium-features></premium-features>
</div>
<div *ngIf="searchResults | isEmpty">
<empty-state message="No search results found"></empty-state>
</div>
<!-- Combining multiple custom pipes -->
<div *ngIf="(user | hasPermission:'VIEW_ANALYTICS') && !(analyticsData | isEmpty)">
<analytics-dashboard [data]="analyticsData"></analytics-dashboard>
</div>
Nested *ngIf Statements
Rendering Deeply Nested UI Blocks Conditionally
Handle complex nested conditions while maintaining code readability:
export class NestedConditionComponent {
user = {
isLoggedIn: true,
profile: {
isComplete: true,
hasAvatar: false
},
subscription: {
isActive: true,
plan: 'premium',
features: {
analytics: true,
reports: true,
api: false
}
}
};
applicationState = {
isMaintenanceMode: false,
featuresEnabled: {
userProfiles: true,
subscriptions: true,
analytics: true
}
};
}
<!-- Nested conditions with proper structure -->
<div *ngIf="!applicationState.isMaintenanceMode">
<div *ngIf="user.isLoggedIn">
<div class="user-dashboard">
<h2>Welcome back!</h2>
<div *ngIf="user.profile.isComplete">
<div class="profile-section">
<h3>Profile Information</h3>
<div *ngIf="!user.profile.hasAvatar">
<div class="avatar-prompt">
<p>Complete your profile by adding an avatar</p>
<button>Upload Avatar</button>
</div>
</div>
<div *ngIf="user.profile.hasAvatar">
<img [src]="user.profile.avatarUrl" alt="User Avatar">
</div>
</div>
<div *ngIf="user.subscription.isActive">
<div class="subscription-features">
<h3>Your {{ user.subscription.plan | titlecase }} Features</h3>
<div *ngIf="user.subscription.features.analytics && applicationState.featuresEnabled.analytics">
<analytics-widget></analytics-widget>
</div>
<div *ngIf="user.subscription.features.reports">
<reports-section></reports-section>
</div>
<div *ngIf="user.subscription.features.api">
<api-management></api-management>
</div>
</div>
</div>
</div>
<div *ngIf="!user.profile.isComplete">
<profile-completion-wizard></profile-completion-wizard>
</div>
</div>
</div>
<div *ngIf="!user.isLoggedIn">
<login-form></login-form>
</div>
</div>
<div *ngIf="applicationState.isMaintenanceMode">
<maintenance-page></maintenance-page>
</div>
Avoiding Over-Nesting and Code Smells
Refactor deeply nested conditions using template references and component extraction:
// ✅ Better approach: Extract complex nested logic
export class RefactoredNestedComponent {
user = { /* ... */ };
applicationState = { /* ... */ };
get showUserDashboard(): boolean {
return !this.applicationState.isMaintenanceMode && this.user.isLoggedIn;
}
get showProfileCompletion(): boolean {
return this.showUserDashboard && !this.user.profile.isComplete;
}
get showSubscriptionFeatures(): boolean {
return this.showUserDashboard &&
this.user.profile.isComplete &&
this.user.subscription.isActive;
}
}
<!-- Cleaner template with extracted logic -->
<div *ngIf="applicationState.isMaintenanceMode; else normalOperation">
<maintenance-page></maintenance-page>
</div>
<ng-template #normalOperation>
<div *ngIf="user.isLoggedIn; else loginPage">
<user-dashboard
[user]="user"
[showCompletion]="showProfileCompletion"
[showFeatures]="showSubscriptionFeatures">
</user-dashboard>
</div>
</ng-template>
<ng-template #loginPage>
<login-form></login-form>
</ng-template>
*ngIf with Structural Directives
Combining *ngIf with *ngFor Safely
When combining structural directives, use proper syntax to avoid conflicts:
export class StructuralDirectiveComponent {
categories = [
{
id: 1,
name: 'Electronics',
isActive: true,
products: [
{ id: 101, name: 'Laptop', inStock: true, price: 999 },
{ id: 102, name: 'Phone', inStock: false, price: 699 },
{ id: 103, name: 'Tablet', inStock: true, price: 399 }
]
},
{
id: 2,
name: 'Books',
isActive: false,
products: [
{ id: 201, name: 'Fiction Novel', inStock: true, price: 15 },
{ id: 202, name: 'Technical Guide', inStock: true, price: 45 }
]
},
{
id: 3,
name: 'Clothing',
isActive: true,
products: []
}
];
showOutOfStock = false;
userRole = 'customer'; // 'customer' | 'admin'
}
<!-- ❌ Wrong: Multiple structural directives on same element -->
<!-- <div *ngFor="let category of categories" *ngIf="category.isActive"> -->
<!-- ✅ Correct: Use ng-container for multiple structural directives -->
<ng-container *ngFor="let category of categories">
<div *ngIf="category.isActive" class="category-section">
<h3>{{ category.name }}</h3>
<ng-container *ngFor="let product of category.products">
<div *ngIf="product.inStock || showOutOfStock" class="product-card">
<h4>{{ product.name }}</h4>
<p>Price: {{ product.price | currency }}</p>
<span *ngIf="!product.inStock" class="out-of-stock">Out of Stock</span>
<button *ngIf="product.inStock" class="add-to-cart">Add to Cart</button>
</div>
</ng-container>
<div *ngIf="category.products.length === 0" class="empty-category">
<p>No products in this category yet.</p>
<button *ngIf="userRole === 'admin'" class="add-product">Add Product</button>
</div>
</div>
</ng-container>
<!-- Alternative approach using template references -->
<div *ngFor="let category of categories">
<ng-container [ngTemplateOutlet]="category.isActive ? activeCategory : null"
[ngTemplateOutletContext]="{ category: category }">
</ng-container>
</div>
<ng-template #activeCategory let-category="category">
<div class="category-section">
<h3>{{ category.name }}</h3>
<div class="products-grid">
<div *ngFor="let product of category.products; trackBy: trackByProductId"
[ngClass]="{ 'out-of-stock': !product.inStock }">
<product-card
[product]="product"
[showOutOfStock]="showOutOfStock">
</product-card>
</div>
</div>
</div>
</ng-template>
Structuring Loops with Conditional Content
Optimize list rendering with conditional elements using trackBy and smart filtering:
export class OptimizedListComponent {
items = [
{ id: 1, name: 'Item 1', status: 'active', priority: 'high' },
{ id: 2, name: 'Item 2', status: 'inactive', priority: 'low' },
{ id: 3, name: 'Item 3', status: 'pending', priority: 'medium' },
{ id: 4, name: 'Item 4', status: 'active', priority: 'high' }
];
filters = {
showInactive: false,
showOnlyHighPriority: false,
searchTerm: ''
};
// Optimize with getter for filtered items
get filteredItems() {
return this.items.filter(item => {
if (!this.filters.showInactive && item.status === 'inactive') {
return false;
}
if (this.filters.showOnlyHighPriority && item.priority !== 'high') {
return false;
}
if (this.filters.searchTerm &&
!item.name.toLowerCase().includes(this.filters.searchTerm.toLowerCase())) {
return false;
}
return true;
});
}
// TrackBy function for performance
trackByItemId(index: number, item: any): number {
return item.id;
}
// Method to get status-specific styling
getStatusClass(status: string): string {
return `status-${status}`;
}
}
<!-- Optimized list with conditional rendering -->
<div class="filter-controls">
<label>
<input type="checkbox" [(ngModel)]="filters.showInactive">
Show Inactive Items
</label>
<label>
<input type="checkbox" [(ngModel)]="filters.showOnlyHighPriority">
High Priority Only
</label>
<input type="text"
[(ngModel)]="filters.searchTerm"
placeholder="Search items...">
</div>
<div *ngIf="filteredItems.length > 0; else noItemsFound">
<h3>Items ({{ filteredItems.length }})</h3>
<div class="items-list">
<div *ngFor="let item of filteredItems; trackBy: trackByItemId"
class="item-card"
[ngClass]="getStatusClass(item.status)">
<h4>{{ item.name }}</h4>
<div class="item-metadata">
<span class="status">Status: {{ item.status | titlecase }}</span>
<span class="priority"
[ngClass]="'priority-' + item.priority">
Priority: {{ item.priority | titlecase }}
</span>
</div>
<div class="item-actions">
<button *ngIf="item.status === 'pending'"
class="approve-btn">
Approve
</button>
<button *ngIf="item.status === 'active'"
class="deactivate-btn">
Deactivate
</button>
<button *ngIf="item.priority === 'high'"
class="urgent-action">
Handle Urgent
</button>
</div>
</div>
</div>
</div>
<ng-template #noItemsFound>
<div class="no-items">
<h4>No items match your current filters</h4>
<p>Try adjusting your search criteria or filters.</p>
<button (click)="clearFilters()">Clear All Filters</button>
</div>
</ng-template>
Component Interaction with *ngIf
How Component State Affects Conditional Rendering
Manage component state changes that trigger conditional rendering updates:
export class ComponentInteractionExample {
parentData = {
isLoading: false,
hasError: false,
userData: null as any,
permissions: [] as string[]
};
childComponentConfig = {
showAdvanced: false,
enableEdit: false,
theme: 'light'
};
async loadUserData(userId: string): Promise<void> {
this.parentData.isLoading = true;
this.parentData.hasError = false;
try {
const userData = await this.userService.getUser(userId);
const permissions = await this.permissionService.getUserPermissions(userId);
this.parentData.userData = userData;
this.parentData.permissions = permissions;
// Update child configuration based on loaded data
this.updateChildConfig(userData, permissions);
} catch (error) {
this.parentData.hasError = true;
console.error('Failed to load user data:', error);
} finally {
this.parentData.isLoading = false;
}
}
private updateChildConfig(userData: any, permissions: string[]): void {
this.childComponentConfig = {
showAdvanced: permissions.includes('VIEW_ADVANCED'),
enableEdit: permissions.includes('EDIT_PROFILE'),
theme: userData.preferences?.theme || 'light'
};
}
onChildEvent(eventData: any): void {
// Handle events from child components
if (eventData.type === 'profile_updated') {
this.loadUserData(eventData.userId);
}
}
}
<!-- Parent component template with conditional child rendering -->
<div class="user-management-container">
<!-- Loading state -->
<div *ngIf="parentData.isLoading" class="loading-container">
<loading-spinner></loading-spinner>
<p>Loading user information...</p>
</div>
<!-- Error state -->
<div *ngIf="parentData.hasError && !parentData.isLoading" class="error-container">
<error-message
message="Failed to load user data"
[showRetry]="true"
(retry)="loadUserData(currentUserId)">
</error-message>
</div>
<!-- Success state with conditional child components -->
<div *ngIf="!parentData.isLoading && !parentData.hasError && parentData.userData">
<!-- Always show basic profile -->
<user-profile
[userData]="parentData.userData"
[editable]="childComponentConfig.enableEdit"
[theme]="childComponentConfig.theme"
(profileUpdated)="onChildEvent($event)">
</user-profile>
<!-- Conditional advanced settings -->
<advanced-settings
*ngIf="childComponentConfig.showAdvanced"
[userData]="parentData.userData"
[permissions]="parentData.permissions"
(settingsChanged)="onChildEvent($event)">
</advanced-settings>
<!-- Conditional admin panel -->
<admin-panel
*ngIf="parentData.permissions.includes('ADMIN_ACCESS')"
[targetUser]="parentData.userData"
(adminAction)="onChildEvent($event)">
</admin-panel>
<!-- Conditional edit mode -->
<profile-editor
*ngIf="childComponentConfig.enableEdit && editMode"
[userData]="parentData.userData"
(saveProfile)="onChildEvent($event)"
(cancelEdit)="editMode = false">
</profile-editor>
</div>
</div>
Using @Input and *ngIf Together
Create reusable components that handle conditional rendering based on input properties:
// Reusable notification component
@Component({
selector: 'app-notification',
template: `
<div *ngIf="shouldShow"
class="notification"
[ngClass]="notificationClasses">
<div class="notification-content">
<div *ngIf="title" class="notification-title">
{{ title }}
</div>
<div class="notification-message">
{{ message }}
</div>
<div *ngIf="showActions" class="notification-actions">
<button *ngIf="showDismiss"
(click)="dismiss()"
class="dismiss-btn">
{{ dismissText }}
</button>
<button *ngIf="actionText"
(click)="performAction()"
class="action-btn">
{{ actionText }}
</button>
</div>
</div>
<button *ngIf="showCloseButton"
(click)="close()"
class="close-btn">
×
</button>
</div>
`
})
export class NotificationComponent {
@Input() message = '';
@Input() title = '';
@Input() type: 'success' | 'error' | 'warning' | 'info' = 'info';
@Input() autoHide = false;
@Input() autoHideDelay = 5000;
@Input() showDismiss = true;
@Input() dismissText = 'Dismiss';
@Input() actionText = '';
@Input() showCloseButton = true;
@Input() persistent = false;
@Output() dismissed = new EventEmitter<void>();
@Output() actionClicked = new EventEmitter<void>();
@Output() closed = new EventEmitter<void>();
private autoHideTimer?: number;
ngOnInit(): void {
if (this.autoHide && !this.persistent) {
this.autoHideTimer = window.setTimeout(() => {
this.dismiss();
}, this.autoHideDelay);
}
}
ngOnDestroy(): void {
if (this.autoHideTimer) {
clearTimeout(this.autoHideTimer);
}
}
get shouldShow(): boolean {
return this.message.length > 0;
}
get showActions(): boolean {
return this.showDismiss || this.actionText.length > 0;
}
get notificationClasses(): { [key: string]: boolean } {
return {
[`notification-${this.type}`]: true,
'notification-persistent': this.persistent,
'notification-with-actions': this.showActions
};
}
dismiss(): void {
this.dismissed.emit();
}
performAction(): void {
this.actionClicked.emit();
}
close(): void {
this.closed.emit();
}
}
<!-- Parent component using the notification component -->
<div class="app-container">
<!-- Different notification scenarios -->
<app-notification
*ngIf="showSuccessMessage"
type="success"
title="Success!"
message="Your profile has been updated successfully."
[autoHide]="true"
[autoHideDelay]="3000"
(dismissed)="showSuccessMessage = false">
</app-notification>
<app-notification
*ngIf="errorMessage"
type="error"
title="Error Occurred"
[message]="errorMessage"
actionText="Retry"
[persistent]="true"
(actionClicked)="retryOperation()"
(dismissed)="errorMessage = ''">
</app-notification>
<app-notification
*ngIf="showWelcomeMessage && !hasSeenWelcome"
type="info"
title="Welcome!"
message="Take a tour of our new features."
actionText="Start Tour"
dismissText="Maybe Later"
[autoHide]="false"
(actionClicked)="startTour()"
(dismissed)="hasSeenWelcome = true">
</app-notification>
<!-- Conditional form validation notifications -->
<app-notification
*ngIf="formErrors.length > 0"
type="warning"
title="Form Validation Issues"
[message]="getFormErrorMessage()"
[showDismiss]="false"
[showCloseButton]="false">
</app-notification>
</div>
*ngIf with Forms and User Input
Conditionally Showing Form Fields Based on User Choices
Create dynamic forms that adapt to user selections:
export class DynamicFormComponent {
registrationForm = {
userType: '', // 'individual' | 'business'
accountType: '', // 'basic' | 'premium'
paymentMethod: '', // 'credit' | 'paypal' | 'bank'
country: '',
hasExperience: false,
experienceLevel: '', // 'beginner' | 'intermediate' | 'expert'
companySize: '', // 'small' | 'medium' | 'large'
industry: '',
requiresSupport: false,
supportType: [] as string[]
};
formValidation = {
showUserTypeError: false,
showPaymentError: false,
showExperienceError: false
};
countries = [
{ code: 'US', name: 'United States', requiresTax: true },
{ code: 'CA', name: 'Canada', requiresTax: true },
{ code: 'UK', name: 'United Kingdom', requiresTax: false },
{ code: 'DE', name: 'Germany', requiresTax: true }
];
industries = [
'Technology', 'Healthcare', 'Finance', 'Education', 'Retail', 'Manufacturing'
];
get selectedCountry() {
return this.countries.find(c => c.code === this.registrationForm.country);
}
get showBusinessFields(): boolean {
return this.registrationForm.userType === 'business';
}
get showPremiumFeatures(): boolean {
return this.registrationForm.accountType === 'premium';
}
get showPaymentFields(): boolean {
return this.registrationForm.accountType !== '';
}
get showTaxFields(): boolean {
return this.selectedCountry?.requiresTax || false;
}
get showExperienceFields(): boolean {
return this.registrationForm.hasExperience;
}
onUserTypeChange(): void {
// Reset dependent fields when user type changes
this.registrationForm.companySize = '';
this.registrationForm.industry = '';
this.formValidation.showUserTypeError = false;
}
onAccountTypeChange(): void {
// Reset payment method when account type changes
this.registrationForm.paymentMethod = '';
this.formValidation.showPaymentError = false;
}
}
<!-- Dynamic registration form -->
<form class="registration-form" #registrationForm="ngForm">
<!-- User Type Selection -->
<div class="form-section">
<h3>Account Type</h3>
<div class="radio-group">
<label>
<input type="radio"
name="userType"
value="individual"
[(ngModel)]="registrationForm.userType"
(change)="onUserTypeChange()">
Individual Account
</label>
<label>
<input type="radio"
name="userType"
value="business"
[(ngModel)]="registrationForm.userType"
(change)="onUserTypeChange()">
Business Account
</label>
</div>
</div>
<!-- Business-specific fields -->
<div *ngIf="showBusinessFields" class="form-section business-fields">
<h4>Business Information</h4>
<div class="form-group">
<label for="companySize">Company Size</label>
<select id="companySize"
name="companySize"
[(ngModel)]="registrationForm.companySize"
required>
<option value="">Select company size</option>
<option value="small">Small (1-50 employees)</option>
<option value="medium">Medium (51-200 employees)</option>
<option value="large">Large (200+ employees)</option>
</select>
</div>
<div class="form-group">
<label for="industry">Industry</label>
<select id="industry"
name="industry"
[(ngModel)]="registrationForm.industry"
required>
<option value="">Select industry</option>
<option *ngFor="let industry of industries" [value]="industry">
{{ industry }}
</option>
</select>
</div>
</div>
<!-- Account Level Selection -->
<div class="form-section">
<h3>Subscription Plan</h3>
<div class="radio-group">
<label>
<input type="radio"
name="accountType"
value="basic"
[(ngModel)]="registrationForm.accountType"
(change)="onAccountTypeChange()">
Basic (Free)
</label>
<label>
<input type="radio"
name="accountType"
value="premium"
[(ngModel)]="registrationForm.accountType"
(change)="onAccountTypeChange()">
Premium ($29/month)
</label>
</div>
</div>
<!-- Premium features preview -->
<div *ngIf="showPremiumFeatures" class="premium-features">
<h4>Premium Features Included:</h4>
<ul>
<li>Advanced analytics and reporting</li>
<li>Priority customer support</li>
<li>API access and integrations</li>
<li *ngIf="showBusinessFields">Team collaboration tools</li>
</ul>
</div>
<!-- Payment Information -->
<div *ngIf="showPaymentFields" class="form-section payment-section">
<h3>Payment Information</h3>
<div class="form-group">
<label for="country">Country</label>
<select id="country"
name="country"
[(ngModel)]="registrationForm.country"
required>
<option value="">Select country</option>
<option *ngFor="let country of countries" [value]="country.code">
{{ country.name }}
</option>
</select>
</div>
<!-- Tax information for applicable countries -->
<div *ngIf="showTaxFields" class="tax-info">
<p class="info-notice">
<strong>Note:</strong> Tax will be calculated and added at checkout based on your location.
</p>
</div>
<!-- Payment method selection -->
<div class="form-group">
<label>Payment Method</label>
<div class="radio-group">
<label>
<input type="radio"
name="paymentMethod"
value="credit"
[(ngModel)]="registrationForm.paymentMethod">
Credit Card
</label>
<label>
<input type="radio"
name="paymentMethod"
value="paypal"
[(ngModel)]="registrationForm.paymentMethod">
PayPal
</label>
<label>
<input type="radio"
name="paymentMethod"
value="bank"
[(ngModel)]="registrationForm.paymentMethod">
Bank Transfer
</label>
</div>
</div>
<!-- Credit card fields -->
<div *ngIf="registrationForm.paymentMethod === 'credit'" class="credit-card-fields">
<div class="form-row">
<div class="form-group">
<label for="cardNumber">Card Number</label>
<input type="text"
id="cardNumber"
name="cardNumber"
placeholder="1234 5678 9012 3456"
required>
</div>
<div class="form-group">
<label for="expiryDate">Expiry Date</label>
<input type="text"
id="expiryDate"
name="expiryDate"
placeholder="MM/YY"
required>
</div>
</div>
</div>
<!-- PayPal notice -->
<div *ngIf="registrationForm.paymentMethod === 'paypal'" class="paypal-notice">
<p>You will be redirected to PayPal to complete your payment securely.</p>
</div>
<!-- Bank transfer instructions -->
<div *ngIf="registrationForm.paymentMethod === 'bank'" class="bank-transfer-info">
<p>Bank transfer details will be provided after registration.</p>
<p><em>Note: Account activation may take 1-2 business days.</em></p>
</div>
</div>
<!-- Experience Level Section -->
<div class="form-section">
<div class="form-group">
<label>
<input type="checkbox"
name="hasExperience"
[(ngModel)]="registrationForm.hasExperience">
I have previous experience with similar platforms
</label>
</div>
<div *ngIf="showExperienceFields" class="experience-details">
<div class="form-group">
<label for="experienceLevel">Experience Level</label>
<select id="experienceLevel"
name="experienceLevel"
[(ngModel)]="registrationForm.experienceLevel">
<option value="">Select experience level</option>
<option value="beginner">Beginner (Less than 1 year)</option>
<option value="intermediate">Intermediate (1-3 years)</option>
<option value="expert">Expert (3+ years)</option>
</select>
</div>
</div>
</div>
<!-- Support Options -->
<div class="form-section">
<div class="form-group">
<label>
<input type="checkbox"
name="requiresSupport"
[(ngModel)]="registrationForm.requiresSupport">
I would like additional support during onboarding
</label>
</div>
<div *ngIf="registrationForm.requiresSupport" class="support-options">
<h4>Support Options</h4>
<div class="checkbox-group">
<label>
<input type="checkbox"
value="phone"
[(ngModel)]="registrationForm.supportType"
name="supportType">
Phone Support
</label>
<label>
<input type="checkbox"
value="email"
[(ngModel)]="registrationForm.supportType"
name="supportType">
Email Support
</label>
<label>
<input type="checkbox"
value="training"
[(ngModel)]="registrationForm.supportType"
name="supportType">
Training Sessions
</label>
</div>
</div>
</div>
<!-- Form Actions -->
<div class="form-actions">
<button type="submit"
class="primary-btn"
[disabled]="!registrationForm.form.valid">
Complete Registration
</button>
<button type="button"
class="secondary-btn"
(click)="resetForm()">
Reset Form
</button>
</div>
</form>
Validating Input-Dependent Sections with *ngIf
Implement dynamic validation that responds to conditional form sections:
export class FormValidationComponent {
formData = {
email: '',
password: '',
confirmPassword: '',
agreeToTerms: false,
newsletterSubscription: false,
marketingEmails: false
};
validationState = {
emailValid: false,
passwordValid: false,
passwordsMatch: false,
termsAccepted: false,
showPasswordRequirements: false,
showEmailVerification: false
};
emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
passwordPattern = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/;
validateEmail(): void {
this.validationState.emailValid = this.emailPattern.test(this.formData.email);
this.validationState.showEmailVerification = this.validationState.emailValid;
}
validatePassword(): void {
this.validationState.passwordValid = this.passwordPattern.test(this.formData.password);
this.validationState.showPasswordRequirements =
this.formData.password.length > 0 && !this.validationState.passwordValid;
this.checkPasswordsMatch();
}
checkPasswordsMatch(): void {
this.validationState.passwordsMatch =
this.formData.password === this.formData.confirmPassword &&
this.formData.confirmPassword.length > 0;
}
validateTerms(): void {
this.validationState.termsAccepted = this.formData.agreeToTerms;
}
get canSubmitForm(): boolean {
return this.validationState.emailValid &&
this.validationState.passwordValid &&
this.validationState.passwordsMatch &&
this.validationState.termsAccepted;
}
}
<!-- Form with dynamic validation -->
<form class="validation-form" #validationForm="ngForm">
<!-- Email Field with Validation -->
<div class="form-group">
<label for="email">Email Address</label>
<input type="email"
id="email"
name="email"
[(ngModel)]="formData.email"
(blur)="validateEmail()"
(input)="validateEmail()"
class="form-control"
[class.invalid]="formData.email && !validationState.emailValid"
[class.valid]="validationState.emailValid">
<div *ngIf="formData.email && !validationState.emailValid" class="error-message">
Please enter a valid email address.
</div>
<div *ngIf="validationState.showEmailVerification" class="success-message">
<span class="check-icon">✓</span> Valid email format
</div>
</div>
<!-- Password Field with Requirements -->
<div class="form-group">
<label for="password">Password</label>
<input type="password"
id="password"
name="password"
[(ngModel)]="formData.password"
(input)="validatePassword()"
class="form-control"
[class.invalid]="formData.password && !validationState.passwordValid"
[class.valid]="validationState.passwordValid">
<div *ngIf="validationState.showPasswordRequirements" class="password-requirements">
<h5>Password Requirements:</h5>
<ul>
<li [class.met]="formData.password.length >= 8">
At least 8 characters
</li>
<li [class.met]="/[a-z]/.test(formData.password)">
One lowercase letter
</li>
<li [class.met]="/[A-Z]/.test(formData.password)">
One uppercase letter
</li>
<li [class.met]="/\d/.test(formData.password)">
One number
</li>
<li [class.met]="/[@$!%*?&]/.test(formData.password)">
One special character (@$!%*?&)
</li>
</ul>
</div>
</div>
<!-- Confirm Password Field -->
<div class="form-group" *ngIf="validationState.passwordValid">
<label for="confirmPassword">Confirm Password</label>
<input type="password"
id="confirmPassword"
name="confirmPassword"
[(ngModel)]="formData.confirmPassword"
(input)="checkPasswordsMatch()"
class="form-control"
[class.invalid]="formData.confirmPassword && !validationState.passwordsMatch"
[class.valid]="validationState.passwordsMatch">
<div *ngIf="formData.confirmPassword && !validationState.passwordsMatch" class="error-message">
Passwords do not match.
</div>
<div *ngIf="validationState.passwordsMatch" class="success-message">
<span class="check-icon">✓</span> Passwords match
</div>
</div>
<!-- Terms and Conditions -->
<div class="form-group checkbox-group">
<label class="checkbox-label">
<input type="checkbox"
name="agreeToTerms"
[(ngModel)]="formData.agreeToTerms"
(change)="validateTerms()"
required>
<span class="checkmark"></span>
I agree to the <a href="/terms" target="_blank">Terms and Conditions</a>
</label>
<div *ngIf="!validationState.termsAccepted && formData.agreeToTerms === false" class="error-message">
You must agree to the terms and conditions to proceed.
</div>
</div>
<!-- Optional Newsletter Subscription -->
<div *ngIf="validationState.emailValid" class="form-group checkbox-group">
<label class="checkbox-label">
<input type="checkbox"
name="newsletterSubscription"
[(ngModel)]="formData.newsletterSubscription">
<span class="checkmark"></span>
Subscribe to our newsletter for updates and tips
</label>
<!-- Marketing emails option (only if newsletter is selected) -->
<div *ngIf="formData.newsletterSubscription" class="nested-option">
<label class="checkbox-label">
<input type="checkbox"
name="marketingEmails"
[(ngModel)]="formData.marketingEmails">
<span class="checkmark"></span>
Also receive marketing emails about new products and offers
</label>
</div>
</div>
<!-- Form Summary (only show when form is nearly complete) -->
<div *ngIf="validationState.emailValid && validationState.passwordValid" class="form-summary">
<h4>Registration Summary</h4>
<ul>
<li>Email: {{ formData.email }}</li>
<li>Newsletter: {{ formData.newsletterSubscription ? 'Yes' : 'No' }}</li>
<li *ngIf="formData.newsletterSubscription">
Marketing emails: {{ formData.marketingEmails ? 'Yes' : 'No' }}
</li>
</ul>
</div>
<!-- Submit Button with Dynamic State -->
<div class="form-actions">
<button type="submit"
class="submit-btn"
[disabled]="!canSubmitForm"
[class.ready]="canSubmitForm">
{{ canSubmitForm ? 'Create Account' : 'Complete Required Fields' }}
</button>
<!-- Progress Indicator -->
<div class="form-progress">
<div class="progress-item" [class.completed]="validationState.emailValid">
<span class="progress-icon">{{ validationState.emailValid ? '✓' : '1' }}</span>
Valid Email
</div>
<div class="progress-item" [class.completed]="validationState.passwordValid">
<span class="progress-icon">{{ validationState.passwordValid ? '✓' : '2' }}</span>
Strong Password
</div>
<div class="progress-item" [class.completed]="validationState.passwordsMatch">
<span class="progress-icon">{{ validationState.passwordsMatch ? '✓' : '3' }}</span>
Confirm Password
</div>
<div class="progress-item" [class.completed]="validationState.termsAccepted">
<span class="progress-icon">{{ validationState.termsAccepted ? '✓' : '4' }}</span>
Accept Terms
</div>
</div>
</div>
</form>
Common Mistakes and Debugging Tips
Why *ngIf Isn’t Working: Common Pitfalls
Identify and resolve frequent *ngIf issues that developers encounter:
export class CommonMistakesComponent {
// ❌ Common Mistake 1: Incorrect property binding
showElement = 'true'; // String instead of boolean
// ❌ Common Mistake 2: Undefined property access
user: any; // user is undefined initially
// ❌ Common Mistake 3: Async data without proper handling
data$ = this.dataService.getData(); // Observable not handled properly
// ❌ Common Mistake 4: Complex expressions that should be methods
items: any[] = [];
selectedCategory = '';
// ✅ Correct approaches
showElementCorrect = true; // Boolean value
userCorrect: User | null = null; // Properly typed with null check
// ✅ Proper async handling
dataCorrect$ = this.dataService.getData();
// ✅ Extract complex logic to getters/methods
get filteredItems(): any[] {
return this.items.filter(item =>
this.selectedCategory ? item.category === this.selectedCategory : true
);
}
get hasFilteredItems(): boolean {
return this.filteredItems.length > 0;
}
}
<!-- ❌ Common Mistakes -->
<!-- Mistake 1: String comparison (always truthy) -->
<!-- <div *ngIf="showElement">This always shows because 'true' is truthy</div> -->
<!-- Mistake 2: Accessing properties of undefined objects -->
<!-- <div *ngIf="user.isActive">This will cause runtime error if user is undefined</div> -->
<!-- Mistake 3: Not handling async data -->
<!-- <div *ngIf="data$.hasResults">This won't work without async pipe</div> -->
<!-- Mistake 4: Complex expressions in template -->
<!-- <div *ngIf="items.filter(i => selectedCategory ? i.category === selectedCategory : true).length > 0">
Complex logic makes template hard to read
</div> -->
<!-- ✅ Corrected Approaches -->
<!-- Fix 1: Use proper boolean values -->
<div *ngIf="showElementCorrect">This shows when true</div>
<!-- Fix 2: Safe navigation operator -->
<div *ngIf="userCorrect?.isActive">Safe property access</div>
<!-- Alternative: Explicit null check -->
<div *ngIf="userCorrect && userCorrect.isActive">Explicit null check</div>
<!-- Fix 3: Proper async data handling -->
<div *ngIf="dataCorrect$ | async as data">
<div *ngIf="data.hasResults">Show results: {{ data.results.length }}</div>
</div>
<!-- Fix 4: Use computed properties -->
<div *ngIf="hasFilteredItems">
Found {{ filteredItems.length }} items
</div>
Debugging with Angular DevTools and Console Logs
Implement debugging strategies for *ngIf issues:
export class DebuggingComponent {
debugMode = environment.production ? false : true;
user: User | null = null;
permissions: string[] = [];
// Debug helper methods
debugLog(message: string, data?: any): void {
if (this.debugMode) {
console.log(`[DEBUG] ${message}`, data || '');
}
}
debugCondition(conditionName: string, result: boolean, data?: any): boolean {
if (this.debugMode) {
console.log(`[CONDITION] ${conditionName}: ${result}`, data || '');
}
return result;
}
// Debugging getters with logging
get isUserAuthenticated(): boolean {
const result = this.user !== null;
return this.debugCondition('isUserAuthenticated', result, { user: this.user });
}
get hasAdminPermissions(): boolean {
const result = this.permissions.includes('ADMIN');
return this.debugCondition('hasAdminPermissions', result, {
permissions: this.permissions
});
}
get canAccessAdminPanel(): boolean {
const authenticated = this.isUserAuthenticated;
const hasPermissions = this.hasAdminPermissions;
const result = authenticated && hasPermissions;
return this.debugCondition('canAccessAdminPanel', result, {
authenticated,
hasPermissions,
breakdown: {
user: this.user,
permissions: this.permissions
}
});
}
// Method to manually trigger condition checks
debugAllConditions(): void {
if (!this.debugMode) return;
console.group('🔍 Debugging *ngIf Conditions');
console.log('Component State:', {
user: this.user,
permissions: this.permissions
});
const conditions = [
{ name: 'isUserAuthenticated', value: this.isUserAuthenticated },
{ name: 'hasAdminPermissions', value: this.hasAdminPermissions },
{ name: 'canAccessAdminPanel', value: this.canAccessAdminPanel }
];
conditions.forEach(condition => {
console.log(`${condition.value ? '✅' : '❌'} ${condition.name}: ${condition.value}`);
});
console.groupEnd();
}
}
<!-- Debugging template with conditional logging -->
<div class="debug-panel" *ngIf="debugMode">
<h4>Debug Information</h4>
<button (click)="debugAllConditions()">Check All Conditions</button>
<div class="debug-info">
<p>User: {{ user | json }}</p>
<p>Permissions: {{ permissions | json }}</p>
</div>
</div>
<!-- Main content with debugging -->
<div *ngIf="isUserAuthenticated; else notAuthenticated" class="authenticated-content">
<h2>Welcome, {{ user?.name || 'User' }}!</h2>
<div *ngIf="canAccessAdminPanel; else noAdminAccess" class="admin-section">
<admin-panel></admin-panel>
</div>
<ng-template #noAdminAccess>
<div class="access-denied">
<p>Admin access denied</p>
<div *ngIf="debugMode" class="debug-details">
<p>Debug: hasAdminPermissions = {{ hasAdminPermissions }}</p>
<p>Required permission: ADMIN</p>
<p>Current permissions: {{ permissions.join(', ') || 'None' }}</p>
</div>
</div>
</ng-template>
</div>
<ng-template #notAuthenticated>
<div class="login-prompt">
<p>Please log in to access this content</p>
<div *ngIf="debugMode" class="debug-details">
<p>Debug: user object = {{ user | json }}</p>
</div>
</div>
</ng-template>
<!-- Debugging helper for tracking changes -->
<div *ngIf="debugMode" class="change-tracker">
<h5>Condition Tracker</h5>
<div class="condition-status">
<span [class.active]="isUserAuthenticated">🔒 Authenticated</span>
<span [class.active]="hasAdminPermissions">👑 Admin</span>
<span [class.active]="canAccessAdminPanel">🎛️ Panel Access</span>
</div>
</div>
Browser DevTools Debugging Tips:
// Add to component for debugging in browser console
export class DebuggingComponent {
constructor() {
// Make component accessible in browser console (development only)
if (!environment.production) {
(window as any).debugComponent = this;
}
}
}
// In browser console, you can now access:
// debugComponent.isUserAuthenticated
// debugComponent.canAccessAdminPanel
// debugComponent.debugAllConditions()
Performance Optimization Tips
Lazy Loading Content with *ngIf
Implement performance-optimized conditional rendering for heavy components:
export class LazyLoadingComponent {
// Lazy loading flags
chartLoaded = false;
tableLoaded = false;
mapLoaded = false;
// Performance tracking
loadTimes = {
chart: 0,
table: 0,
map: 0
};
// Intersection Observer for lazy loading
private observer?: IntersectionObserver;
ngOnInit(): void {
this.setupIntersectionObserver();
}
ngOnDestroy(): void {
if (this.observer) {
this.observer.disconnect();
}
}
private setupIntersectionObserver(): void {
this.observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const targetId = entry.target.getAttribute('data-lazy-id');
this.loadContent(targetId);
}
});
}, {
threshold: 0.1, // Load when 10% visible
rootMargin: '50px' // Load 50px before coming into view
});
}
loadContent(contentId: string | null): void {
const startTime = performance.now();
switch (contentId) {
case 'chart':
if (!this.chartLoaded) {
this.chartLoaded = true;
this.loadTimes.chart = performance.now() - startTime;
}
break;
case 'table':
if (!this.tableLoaded) {
this.tableLoaded = true;
this.loadTimes.table = performance.now() - startTime;
}
break;
case 'map':
if (!this.mapLoaded) {
this.mapLoaded = true;
this.loadTimes.map = performance.now() - startTime;
}
break;
}
}
// Manual loading triggers
loadChart(): void {
this.loadContent('chart');
}
loadTable(): void {
this.loadContent('table');
}
loadMap(): void {
this.loadContent('map');
}
}
<!-- Lazy loading with intersection observer -->
<div class="dashboard-container">
<!-- Immediately visible content -->
<div class="dashboard-header">
<h1>Analytics Dashboard</h1>
<p>Performance metrics and insights</p>
</div>
<!-- Lazy loaded chart section -->
<div class="chart-section" data-lazy-id="chart" #chartContainer>
<h3>Performance Chart</h3>
<div *ngIf="!chartLoaded" class="loading-placeholder">
<div class="skeleton-loader chart-skeleton"></div>
<button (click)="loadChart()" class="load-trigger">
Load Chart
</button>
</div>
<div *ngIf="chartLoaded">
<performance-chart
[data]="chartData"
[options]="chartOptions">
</performance-chart>
<small>Loaded in {{ loadTimes.chart.toFixed(2) }}ms</small>
</div>
</div>
<!-- Lazy loaded data table -->
<div class="table-section" data-lazy-id="table" #tableContainer>
<h3>Data Table</h3>
<div *ngIf="!tableLoaded" class="loading-placeholder">
<div class="skeleton-loader table-skeleton"></div>
<button (click)="loadTable()" class="load-trigger">
Load Table
</button>
</div>
<div *ngIf="tableLoaded">
<data-table
[data]="tableData"
[columns]="tableColumns"
[pagination]="true">
</data-table>
</div>
</div>
<!-- Lazy loaded map component -->
<div class="map-section" data-lazy-id="map" #mapContainer>
<h3>Geographic View</h3>
<div *ngIf="!mapLoaded" class="loading-placeholder">
<div class="skeleton-loader map-skeleton"></div>
<button (click)="loadMap()" class="load-trigger">
Load Map
</button>
</div>
<div *ngIf="mapLoaded">
<geographic-map
[markers]="mapMarkers"
[options]="mapOptions">
</geographic-map>
</div>
</div>
</div>
Using TrackBy and Smart Change Detection
Optimize *ngIf performance with change detection strategies:
export class OptimizedChangeDetectionComponent {
// Use OnPush change detection for better performance
changeDetection = ChangeDetectionStrategy.OnPush;
items$ = this.dataService.getItems().pipe(
// Cache results to prevent unnecessary re-renders
shareReplay(1)
);
// Memoized computations
private memoizedFilters = new Map<string, any[]>();
// TrackBy functions for optimal list rendering
trackByItemId = (index: number, item: any): number => item.id;
trackByIndex = (index: number, item: any): number => index;
constructor(
private dataService: DataService,
private cdr: ChangeDetectorRef
) {}
// Optimized filtering with memoization
getFilteredItems(items: any[], filter: string): any[] {
const cacheKey = `${filter}-${items.length}`;
if (this.memoizedFilters.has(cacheKey)) {
return this.memoizedFilters.get(cacheKey)!;
}
const filtered = items.filter(item =>
filter ? item.category === filter : true
);
this.memoizedFilters.set(cacheKey, filtered);
return filtered;
}
// Manual change detection trigger
refreshData(): void {
this.memoizedFilters.clear();
this.cdr.markForCheck();
}
// Optimized condition checking
shouldShowItem(item: any): boolean {
// Use simple conditions that Angular can optimize
return item.isVisible && item.isActive;
}
}
<!-- Optimized template with trackBy and async pipe -->
<div class="optimized-list">
<!-- Use async pipe to handle observables efficiently -->
<div *ngIf="items$ | async as items">
<!-- Optimize list rendering with trackBy -->
<div class="items-container">
<div *ngFor="let item of getFilteredItems(items, selectedFilter);
trackBy: trackByItemId"
class="item-wrapper">
<!-- Use simple conditions for better performance -->
<div *ngIf="shouldShowItem(item)" class="item-content">
<h4>{{ item.name }}</h4>
<p>{{ item.description }}</p>
<!-- Conditional actions with minimal logic -->
<div class="item-actions">
<button *ngIf="item.canEdit" (click)="editItem(item)">
Edit
</button>
<button *ngIf="item.canDelete" (click)="deleteItem(item)">
Delete
</button>
</div>
</div>
<!-- Loading state for individual items -->
<div *ngIf="!shouldShowItem(item)" class="item-placeholder">
<div class="skeleton-loader"></div>
</div>
</div>
</div>
<!-- Empty state -->
<div *ngIf="items.length === 0" class="empty-state">
<h4>No items available</h4>
<button (click)="refreshData()">Refresh</button>
</div>
</div>
<!-- Loading state for entire list -->
<div *ngIf="!(items$ | async)" class="list-loading">
<div class="loading-spinner"></div>
<p>Loading items...</p>
</div>
</div>
Advanced Use Cases
Animating Elements with *ngIf and Angular Animations
Create smooth transitions for conditional content using Angular Animations:
import { trigger, state, style, transition, animate } from '@angular/animations';
@Component({
selector: 'app-animated-content',
template: `
<div class="animated-container">
<button (click)="toggleContent()" class="toggle-btn">
{{ showContent ? 'Hide' : 'Show' }} Content
</button>
<div *ngIf="showContent"
@slideInOut
class="animated-content">
<h3>Animated Content</h3>
<p>This content slides in and out smoothly.</p>
</div>
<div *ngIf="showModal"
@fadeInOut
class="modal-overlay"
(click)="closeModal()">
<div class="modal-content" (click)="$event.stopPropagation()">
<h4>Animated Modal</h4>
<p>This modal fades in and out with backdrop.</p>
<button (click)="closeModal()">Close</button>
</div>
</div>
</div>
`,
animations: [
trigger('slideInOut', [
transition(':enter', [
style({ transform: 'translateX(-100%)', opacity: 0 }),
animate('300ms ease-in',
style({ transform: 'translateX(0%)', opacity: 1 })
)
]),
transition(':leave', [
animate('300ms ease-out',
style({ transform: 'translateX(-100%)', opacity: 0 })
)
])
]),
trigger('fadeInOut', [
transition(':enter', [
style({ opacity: 0 }),
animate('200ms ease-in', style({ opacity: 1 }))
]),
transition(':leave', [
animate('200ms ease-out', style({ opacity: 0 }))
])
])
]
})
export class AnimatedContentComponent {
showContent = false;
showModal = false;
toggleContent(): void {
this.showContent = !this.showContent;
}
openModal(): void {
this.showModal = true;
}
closeModal(): void {
this.showModal = false;
}
}
Dynamically Rendering Components with *ngIf and ngComponentOutlet
Implement dynamic component rendering based on conditions:
export class DynamicComponentExample {
currentView: 'chart' | 'table' | 'map' | null = null;
// Component references for dynamic rendering
chartComponent = ChartComponent;
tableComponent = TableComponent;
mapComponent = MapComponent;
// Data for dynamic components
componentData = {
chart: { type: 'line', data: [1, 2, 3, 4, 5] },
table: { columns: ['Name', 'Value'], rows: [] },
map: { center: { lat: 40.7128, lng: -74.0060 }, zoom: 10 }
};
// Component inputs
get currentComponent() {
switch (this.currentView) {
case 'chart': return this.chartComponent;
case 'table': return this.tableComponent;
case 'map': return this.mapComponent;
default: return null;
}
}
get currentComponentInputs() {
if (!this.currentView) return {};
return this.componentData[this.currentView];
}
switchView(view: 'chart' | 'table' | 'map'): void {
this.currentView = view;
}
}
<!-- Dynamic component rendering -->
<div class="dynamic-component-container">
<!-- View selector -->
<div class="view-selector">
<button (click)="switchView('chart')"
[class.active]="currentView === 'chart'">
Chart View
</button>
<button (click)="switchView('table')"
[class.active]="currentView === 'table'">
Table View
</button>
<button (click)="switchView('map')"
[class.active]="currentView === 'map'">
Map View
</button>
</div>
<!-- Dynamic component outlet -->
<div class="component-outlet">
<div *ngIf="currentComponent" class="dynamic-wrapper">
<ng-container *ngComponentOutlet="currentComponent;
inputs: currentComponentInputs">
</ng-container>
</div>
<div *ngIf="!currentComponent" class="no-selection">
<h4>Select a view to display content</h4>
<p>Choose from Chart, Table, or Map views above.</p>
</div>
</div>
</div>
Conclusion
Recap: Making Your UI Smarter with *ngIf
Angular’s *ngIf directive transforms static templates into intelligent, responsive user interfaces that adapt to changing application states. Throughout this comprehensive guide, we’ve explored the full spectrum of *ngIf capabilities, from basic boolean conditions to advanced performance optimization techniques.
*Key takeaways for mastering ngIf:
- Understand the fundamentals: *ngIf completely removes or adds elements to the DOM, unlike CSS-based hiding
- Choose the right approach: Use *ngIf for expensive components and [hidden] for frequent toggles
- Implement clean architecture: Extract complex conditions into component methods for better maintainability
- Optimize performance: Leverage lazy loading, change detection strategies, and TrackBy functions
- Handle edge cases: Implement proper null checks, async data handling, and error states
- Debug effectively: Use Angular DevTools and console logging to troubleshoot conditional rendering issues
Best practices for production applications:
- Keep templates readable by extracting complex logic to component properties
- Use type-safe conditions with proper TypeScript typing
- Implement comprehensive error handling for edge cases
- Optimize performance with OnPush change detection and memoization
- Test thoroughly with unit tests covering all conditional branches
- Document complex conditions for team maintainability
Next Steps: Mastering Other Structural Directives in Angular
Building on your *ngIf expertise, explore these advanced Angular concepts to create even more sophisticated applications:
Related Structural Directives:
- *ngFor: Master list rendering with filtering, sorting, and performance optimization
- *ngSwitch: Implement complex conditional layouts with multiple branches
- Custom structural directives: Create reusable conditional logic for your specific use cases
Advanced Angular Patterns:
- Reactive Forms: Build dynamic forms that respond to user input with conditional validation
- State Management: Implement NgRx or Akita for complex application state handling
- Animation API: Create smooth transitions and micro-interactions that enhance user experience
- Change Detection: Master OnPush strategies and async patterns for optimal performance
Performance and Architecture:
- Lazy Loading: Implement route-based and component-based lazy loading strategies
- Progressive Web Apps: Build offline-capable applications with smart caching
- Micro-frontends: Architect scalable applications with modular component systems
The conditional rendering skills you’ve developed with *ngIf provide a solid foundation for these advanced topics. Continue practicing with real-world scenarios, and you’ll build applications that not only function flawlessly but also provide exceptional user experiences through intelligent, responsive interfaces.
For more advanced Angular tutorials and best practices, explore the official Angular documentation and consider diving deeper into performance optimization techniques that will make your applications stand out in today’s competitive web landscape.