Saturday, August 16, 2025
0 comments

Master Angular 20 Advanced Component Communication: A Beginner’s Guide to @Input , @Output , ViewChild, ContentChild, and Storage with a FreeAPI Demo

 





Introduction to Advanced Component CommunicationIn Angular, components often need to share data or trigger actions across parent-child hierarchies. Angular provides several tools for this:
  • @Input
    ()
    : Passes data from a parent component to a child component.
  • @Output
    () and EventEmitter
    : Emits events from a child to a parent.
  • ViewChild: Accesses a child component’s properties or methods.
  • ContentChild: Accesses content projected into a component.
  • Local/Session Storage: Persists data in the browser for state management.
This module extends your project by enabling communication between AdminComponent and UserComponent, using storage to persist user selections.
@Input
() (Parent → Child)
The
@Input
()
decorator allows a parent component to pass data to a child component via a property binding.
Example
typescript
// src/app/components/child/child.component.ts
import { Component, Input } from '@angular/core';

@Component({
  selector: 'app-child',
  template: `<p>User: {{ userName }}</p>`,
  standalone: true
})
export class ChildComponent {
  @Input() userName: string = '';
}
typescript
// src/app/components/parent/parent.component.ts
import { Component } from '@angular/core';
import { ChildComponent } from '../child/child.component';

@Component({
  selector: 'app-parent',
  template: `<app-child [userName]="parentUser"></app-child>`,
  standalone: true,
  imports: [ChildComponent]
})
export class ParentComponent {
  parentUser = 'Alice';
}
Explanation:
  • @Input() userName: Binds the userName property to a value passed from the parent.
  • [userName]="parentUser": Passes parentUser from the parent to the child’s userName.

@Output
() & EventEmitter (Child → Parent)
The
@Output
()
decorator with EventEmitter allows a child component to emit events to its parent, enabling child-to-parent communication.
Example
typescript
// src/app/components/child/child.component.ts
import { Component, Output, EventEmitter } from '@angular/core';

@Component({
  selector: 'app-child',
  template: `<button (click)="sendMessage()">Send Message</button>`,
  standalone: true
})
export class ChildComponent {
  @Output() messageEvent = new EventEmitter<string>();

  sendMessage() {
    this.messageEvent.emit('Hello from child!');
  }
}
typescript
// src/app/components/parent/parent.component.ts
import { Component } from '@angular/core';
import { ChildComponent } from '../child/child.component';

@Component({
  selector: 'app-parent',
  template: `
    <app-child (messageEvent)="receiveMessage($event)"></app-child>
    <p>Message: {{ message }}</p>
  `,
  standalone: true,
  imports: [ChildComponent]
})
export class ParentComponent {
  message = '';

  receiveMessage(message: string) {
    this.message = message;
  }
}
Explanation:
  • @Output() messageEvent: Declares an event emitter.
  • emit(): Sends data to the parent.
  • (messageEvent)="receiveMessage($event)": Binds the parent’s method to the child’s event.

ViewChild & ContentChildViewChildViewChild allows a parent component to access a child component’s properties or methods directly.Example
typescript
// src/app/components/child/child.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-child',
  template: `<p>Child Component</p>`,
  standalone: true
})
export class ChildComponent {
  childMessage = 'Hello from child!';

  showMessage() {
    return this.childMessage;
  }
}
typescript
// src/app/components/parent/parent.component.ts
import { Component, ViewChild, AfterViewInit } from '@angular/core';
import { ChildComponent } from '../child/child.component';

@Component({
  selector: 'app-parent',
  template: `
    <app-child #childRef></app-child>
    <button (click)="callChildMethod()">Call Child</button>
    <p>{{ message }}</p>
  `,
  standalone: true,
  imports: [ChildComponent]
})
export class ParentComponent implements AfterViewInit {
  @ViewChild('childRef') child!: ChildComponent;
  message = '';

  ngAfterViewInit() {
    this.message = this.child.childMessage;
  }

  callChildMethod() {
    this.message = this.child.showMessage();
  }
}
Explanation:
  • @ViewChild('childRef'): References the child component via a template variable.
  • ngAfterViewInit: Ensures the child is available before accessing it.
ContentChildContentChild accesses content projected into a component using <ng-content>.Example
typescript
// src/app/components/child/child.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-child',
  template: `<p>Projected Content</p>`,
  standalone: true
})
export class ChildComponent {
  content = 'Projected content';
}
typescript
// src/app/components/parent/parent.component.ts
import { Component, ContentChild, AfterContentInit } from '@angular/core';
import { ChildComponent } from '../child/child.component';

@Component({
  selector: 'app-parent',
  template: `
    <ng-content></ng-content>
    <p>{{ content }}</p>
  `,
  standalone: true
})
export class ParentComponent implements AfterContentInit {
  @ContentChild(ChildComponent) child!: ChildComponent;
  content = '';

  ngAfterContentInit() {
    this.content = this.child?.content || '';
  }
}
Explanation:
  • @ContentChild: Accesses a projected child component.
  • ngAfterContentInit: Ensures projected content is available.

Local Storage & Session Storage IntegrationLocal storage and session storage allow data persistence in the browser:
  • Local Storage: Persists until explicitly cleared.
  • Session Storage: Persists until the browser tab is closed.
Example
typescript
// src/app/services/storage.service.ts
import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class StorageService {
  setLocalStorage(key: string, value: any) {
    localStorage.setItem(key, JSON.stringify(value));
  }

  getLocalStorage(key: string) {
    const data = localStorage.getItem(key);
    return data ? JSON.parse(data) : null;
  }

  setSessionStorage(key: string, value: any) {
    sessionStorage.setItem(key, JSON.stringify(value));
  }

  getSessionStorage(key: string) {
    const data = sessionStorage.getItem(key);
    return data ? JSON.parse(data) : null;
  }
}
Explanation:
  • JSON.stringify and JSON.parse handle object serialization.
  • Use localStorage for persistent data, sessionStorage for temporary data.

Hands-On Project: Pass User Info from Admin to User and Persist in Local StorageLet’s extend your angular-routing-demo project to:
  1. Pass selected user info from AdminComponent to UserComponent using @Input() and @Output().
  2. Use ViewChild to access a child component’s method.
  3. Persist the selected user in local storage using a new StorageService.
  4. Fetch users from freeapi.miniprojectideas.com (from Module 7).
Step 1: Set Up the ProjectEnsure the project is set up (from Module 7):
bash
ng new angular-routing-demo
cd angular-routing-demo
ng g component components/user
ng g component components/admin
ng g component components/user-details
ng g component components/not-found
ng g component components/admin-dashboard
ng g component components/admin-settings
ng g component components/register
ng g component components/login
ng g component components/user-info
ng g pipe pipes/filter-by-role
ng g service services/user
ng g service services/storage
Step 2: Create StorageService
typescript
// src/app/services/storage.service.ts
import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class StorageService {
  setLocalStorage(key: string, value: any) {
    localStorage.setItem(key, JSON.stringify(value));
  }

  getLocalStorage(key: string) {
    const data = localStorage.getItem(key);
    return data ? JSON.parse(data) : null;
  }

  clearLocalStorage(key: string) {
    localStorage.removeItem(key);
  }
}
Step 3: Update User Model
typescript
// src/app/models/user.model.ts
export interface User {
  id: number;
  userName: string;
  emailId: string;
  role: string;
}
Step 4: Update app.routes.tsAdd the UserInfoComponent to the User route:
typescript
// src/app/app.routes.ts
import { Routes } from '@angular/router';
import { UserComponent } from './components/user/user.component';
import { AdminComponent } from './components/admin/admin.component';
import { UserDetailsComponent } from './components/user-details/user-details.component';
import { NotFoundComponent } from './components/not-found/not-found.component';
import { AdminDashboardComponent } from './components/admin-dashboard/admin-dashboard.component';
import { AdminSettingsComponent } from './components/admin-settings/admin-settings.component';
import { RegisterComponent } from './components/register/register.component';
import { LoginComponent } from './components/login/login.component';

export const routes: Routes = [
  {
    path: 'user',
    component: UserComponent,
    children: [
      { path: ':id', component: UserDetailsComponent }
    ]
  },
  { path: 'register', component: RegisterComponent },
  { path: 'login', component: LoginComponent },
  {
    path: 'admin',
    component: AdminComponent,
    children: [
      { path: 'dashboard', component: AdminDashboardComponent },
      { path: 'settings', component: AdminSettingsComponent },
      { path: '', redirectTo: 'dashboard', pathMatch: 'full' }
    ]
  },
  { path: '', redirectTo: '/user', pathMatch: 'full' },
  { path: '**', component: NotFoundComponent }
];
Step 5: Update main.ts
typescript
// src/main.ts
import { bootstrapApplication } from '@angular/platform-browser';
import { provideRouter } from '@angular/router';
import { provideHttpClient } from '@angular/common/http';
import { AppComponent } from './app/app.component';
import { routes } from './app/app.routes';

bootstrapApplication(AppComponent, {
  providers: [provideRouter(routes), provideHttpClient()]
}).catch(err => console.error(err));
Step 6: Update AppComponent
typescript
// src/app/app.component.ts
import { Component, OnInit } from '@angular/core';
import { Router, NavigationStart, NavigationEnd, RouterOutlet, RouterLink } from '@angular/router';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
  standalone: true,
  imports: [RouterOutlet, RouterLink]
})
export class AppComponent implements OnInit {
  constructor(private router: Router) {}

  ngOnInit() {
    this.router.events.subscribe(event => {
      if (event instanceof NavigationStart) {
        console.log('Navigation started:', event.url);
      } else if (event instanceof NavigationEnd) {
        console.log('Navigation ended:', event.url);
      }
    });
  }
}
typescript
// src/app/app.component.html
nav:
  a: routerLink="/user" routerLinkActive="active": User Dashboard
  a: routerLink="/register" routerLinkActive="active": Register
  a: routerLink="/login" routerLinkActive="active": Login
  a: routerLink="/admin" routerLinkActive="active": Admin Panel
router-outlet
css
// src/app/app.component.css
nav { margin: 20px; }
a { margin-right: 10px; text-decoration: none; }
.active { font-weight: bold; color: #007bff; }
Step 7: Create UserInfoComponentThis child component displays user info passed from AdminComponent and emits events.
typescript
// src/app/components/user-info/user-info.component.ts
import { Component, Input, Output, EventEmitter } from '@angular/core';
import { User } from '../../models/user.model';
import { StorageService } from '../../services/storage.service';

@Component({
  selector: 'app-user-info',
  template: `
    div: *ngIf="user":
      p: Selected User: {{ user.userName }} ({{ user.role }})
      button: (click)="clearSelection()": Clear Selection
  `,
  standalone: true
})
export class UserInfoComponent {
  @Input() user: User | null = null;
  @Output() clearUser = new EventEmitter<void>();

  constructor(private storageService: StorageService) {}

  clearSelection() {
    this.storageService.clearLocalStorage('selectedUser');
    this.clearUser.emit();
  }
}
Step 8: Update UserService
typescript
// src/app/services/user.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, of, throwError } from 'rxjs';
import { catchError, finalize } from 'rxjs/operators';
import { User } from '../models/user.model';

@Injectable({
  providedIn: 'root'
})
export class UserService {
  private apiUrl = 'https://freeapi.miniprojectideas.com/api/User/GetAllUsers';
  private localUsers: User[] = [
    { id: 1, userName: 'Alice', emailId: 'alice@example.com', role: 'admin' },
    { id: 2, userName: 'Bob', emailId: 'bob@example.com', role: 'user' },
    { id: 3, userName: 'Charlie', emailId: 'charlie@example.com', role: 'admin' },
    { id: 4, userName: 'David', emailId: 'david@example.com', role: 'user' }
  ];
  private admins = [
    { username: 'admin1', password: 'admin123' },
    { username: 'admin2', password: 'admin456' }
  ];

  constructor(private http: HttpClient) {}

  getUsers(): Observable<User[]> {
    return this.http.get<User[]>(this.apiUrl).pipe(
      catchError(error => {
        console.error('Error fetching users from API:', error);
        return of(this.localUsers);
      })
    );
  }

  getUserById(id: number): Observable<User | undefined> {
    return this.http.get<User>(`${this.apiUrl}/${id}`).pipe(
      catchError(error => {
        console.error('Error fetching user by ID:', error);
        const localUser = this.localUsers.find(user => user.id === id);
        return of(localUser);
      })
    );
  }

  addUser(user: User): Observable<any> {
    return this.http.post(this.apiUrl, user).pipe(
      catchError(error => {
        console.error('Error adding user:', error);
        this.localUsers.push(user);
        return of(user);
      })
    );
  }

  validateAdmin(username: string, password: string): boolean {
    return this.admins.some(admin => admin.username === username && admin.password === password);
  }
}
Step 9: Update AdminComponentAdd functionality to select a user and pass it to UserComponent.
typescript
// src/app/components/admin/admin.component.ts
import { Component, OnInit, OnDestroy } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { RouterOutlet, RouterLink, Router } from '@angular/router';
import { Observable, of } from 'rxjs';
import { catchError, finalize } from 'rxjs/operators';
import { FilterByRolePipe } from '../../pipes/filter-by-role.pipe';
import { UserService } from '../../services/user.service';
import { StorageService } from '../../services/storage.service';
import { User } from '../../models/user.model';

@Component({
  selector: 'app-admin',
  templateUrl: './admin.component.html',
  styleUrls: ['./admin.component.css'],
  standalone: true,
  imports: [FormsModule, RouterOutlet, RouterLink, FilterByRolePipe]
})
export class AdminComponent implements OnInit, OnDestroy {
  users$: Observable<User[]>;
  filterRole = 'admin';
  isHighlighted = false;
  isLoading = false;
  error: string | null = null;
  selectedUser: User | null = null;

  constructor(
    private userService: UserService,
    private storageService: StorageService,
    private router: Router
  ) {}

  ngOnInit() {
    this.isLoading = true;
    this.users$ = this.userService.getUsers().pipe(
      catchError(err => {
        this.error = 'Failed to load users: ' + err.message;
        this.isLoading = false;
        return of([]);
      }),
      finalize(() => this.isLoading = false)
    );
    const storedUser = this.storageService.getLocalStorage('selectedUser');
    if (storedUser) {
      this.selectedUser = storedUser;
    }
  }

  ngOnDestroy() {
    console.log('AdminComponent: Destroyed');
  }

  toggleHighlight() {
    this.isHighlighted = !this.isHighlighted;
  }

  setFilterRole(role: string) {
    this.filterRole = role;
  }

  logSearch(value: string) {
    console.log('Search:', value);
  }

  selectUser(user: User) {
    this.selectedUser = user;
    this.storageService.setLocalStorage('selectedUser', user);
    this.router.navigate(['/user']);
  }

  clearSelectedUser() {
    this.selectedUser = null;
    this.storageService.clearLocalStorage('selectedUser');
  }
}
typescript
// src/app/components/admin/admin.component.html
h2: Admin Panel
nav:
  a: routerLink="dashboard" routerLinkActive="active": Dashboard
  a: routerLink="settings" routerLinkActive="active": Settings
router-outlet
h3: Admin Users List
div: *ngIf="error" class="error": {{ error }}
div: *ngIf="isLoading" class="loading": Loading users...
div: *ngIf="users$ | async as users; else loading":
  div:
    label: Filter by Role: 
    select: [(ngModel)]="filterRole" (change)="setFilterRole(filterRole)":
      option: value="all": All
      option: value="admin": Admin
      option: value="user": User
  button: (click)="toggleHighlight()": Toggle Highlight
  ul:
    li: *ngFor="let user of users | filterByRole:filterRole" [ngClass]="{'highlight': isHighlighted && user.role === 'admin'}":
      {{ user.userName | uppercase }} ({{ user.role }}) - {{ user.emailId }}
      button: (click)="selectUser(user)": Select
  div: *ngIf="selectedUser":
    p: Selected: {{ selectedUser.userName }}
    button: (click)="clearSelectedUser()": Clear Selection
input: #searchInput placeholder="Search users"
button: (click)="logSearch(searchInput.value)": Search
ng-template: #loading: Loading users...
css
// src/app/components/admin/admin.component.css
ul { list-style-type: none; padding: 0; }
li { padding: 10px; margin: 5px 0; border: 1px solid #ccc; }
.highlight { background-color: #e0f7fa; }
nav { margin: 10px 0; }
a { margin-right: 10px; text-decoration: none; }
.active { font-weight: bold; color: #007bff; }
.loading { text-align: center; color: #007bff; }
.error { text-align: center; color: red; }
button { margin-left: 10px; }
Step 10: Update UserComponentAdd UserInfoComponent as a child and use ViewChild.
typescript
// src/app/components/user/user.component.ts
import { Component, OnInit, ViewChild, AfterViewInit } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { ActivatedRoute, Router, RouterLink } from '@angular/router';
import { Observable, of } from 'rxjs';
import { catchError, finalize } from 'rxjs/operators';
import { UserService } from '../../services/user.service';
import { StorageService } from '../../services/storage.service';
import { UserInfoComponent } from '../user-info/user-info.component';
import { User } from '../../models/user.model';

@Component({
  selector: 'app-user',
  templateUrl: './user.component.html',
  styleUrls: ['./user.component.css'],
  standalone: true,
  imports: [FormsModule, RouterLink, UserInfoComponent]
})
export class UserComponent implements OnInit, AfterViewInit {
  searchQuery = '';
  users$: Observable<User[]>;
  isLoading = false;
  error: string | null = null;
  selectedUser: User | null = null;
  @ViewChild(UserInfoComponent) userInfo!: UserInfoComponent;

  constructor(
    private route: ActivatedRoute,
    private router: Router,
    private userService: UserService,
    private storageService: StorageService
  ) {}

  ngOnInit() {
    this.isLoading = true;
    this.users$ = this.userService.getUsers().pipe(
      catchError(err => {
        this.error = 'Failed to load users: ' + err.message;
        this.isLoading = false;
        return of([]);
      }),
      finalize(() => this.isLoading = false)
    );
    this.route.queryParamMap.subscribe(params => {
      this.searchQuery = params.get('search') || '';
    });
    const storedUser = this.storageService.getLocalStorage('selectedUser');
    if (storedUser) {
      this.selectedUser = storedUser;
    }
  }

  ngAfterViewInit() {
    if (this.userInfo && this.selectedUser) {
      console.log('UserInfoComponent accessed via ViewChild:', this.userInfo.user);
    }
  }

  updateQuery() {
    this.router.navigate(['/user'], { queryParams: { search: this.searchQuery } });
  }

  handleClearUser() {
    this.selectedUser = null;
    this.storageService.clearLocalStorage('selectedUser');
  }
}
typescript
// src/app/components/user/user.component.html
h2: User Dashboard
div: *ngIf="error" class="error": {{ error }}
div: *ngIf="isLoading" class="loading": Loading users...
div:
  label: Search users: 
  input: [(ngModel)]="searchQuery" placeholder="Search users"
  button: (click)="updateQuery()": Search
p: Search Query: {{ searchQuery }}
app-user-info: [user]="selectedUser" (clearUser)="handleClearUser()"
router-outlet
ul: *ngIf="users$ | async as users; else loading":
  li: *ngFor="let user of users":
    a: routerLink="/user/{{ user.id }}": {{ user.userName }} ({{ user.role }}) - {{ user.emailId }}
ng-template: #loading: Loading users...
css
// src/app/components/user/user.component.css
ul { list-style-type: none; padding: 0; }
li { padding: 10px; margin: 5px 0; border: 1px solid #ccc; }
.loading { text-align: center; color: #007bff; }
.error { text-align: center; color: red; }
Step 11: Update UserDetailsComponent
typescript
// src/app/components/user-details/user-details.component.ts
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, RouterLink } from '@angular/router';
import { Observable, of } from 'rxjs';
import { catchError, finalize } from 'rxjs/operators';
import { UserService } from '../../services/user.service';
import { User } from '../../models/user.model';

@Component({
  selector: 'app-user-details',
  templateUrl: './user-details.component.html',
  styleUrls: ['./user-details.component.css'],
  standalone: true,
  imports: [RouterLink]
})
export class UserDetailsComponent implements OnInit {
  user$: Observable<User | undefined>;
  isLoading = false;
  error: string | null = null;

  constructor(
    private route: ActivatedRoute,
    private userService: UserService
  ) {}

  ngOnInit() {
    this.isLoading = true;
    this.route.paramMap.subscribe(params => {
      const id = Number(params.get('id'));
      this.user$ = this.userService.getUserById(id).pipe(
        catchError(err => {
          this.error = 'Failed to load user: ' + err.message;
          this.isLoading = false;
          return of(undefined);
        }),
        finalize(() => this.isLoading = false)
      );
    });
  }
}
typescript
// src/app/components/user-details/user-details.component.html
h2: User Details
div: *ngIf="error" class="error": {{ error }}
div: *ngIf="isLoading" class="loading": Loading user...
div: *ngIf="user$ | async as user; else noUser":
  p: Name: {{ user.userName }}
  p: Email: {{ user.emailId }}
  p: Role: {{ user.role | uppercase }}
p: else noUser: User not found
p: Back to User Dashboard
css
// src/app/components/user-details/user-details.component.css
.loading { text-align: center; color: #007bff; }
.error { text-align: center; color: red; }
Step 12: Update RegisterComponent
typescript
// src/app/components/register/register.component.ts
import { Component } from '@angular/core';
import { ReactiveFormsModule, FormGroup, FormControl, Validators } from '@angular/forms';
import { Router, RouterLink } from '@angular/router';
import { UserService } from '../../services/user.service';
import { noSpacesValidator } from '../../validators/no-spaces.validator';
import { Observable } from 'rxjs';

@Component({
  selector: 'app-register',
  templateUrl: './register.component.html',
  styleUrls: ['./register.component.css'],
  standalone: true,
  imports: [ReactiveFormsModule, RouterLink]
})
export class RegisterComponent {
  registerForm = new FormGroup({
    userName: new FormControl('', [Validators.required, Validators.minLength(3), noSpacesValidator()]),
    emailId: new FormControl('', [Validators.required, Validators.email]),
    role: new FormControl('user', [Validators.required])
  });
  isLoading = false;
  error: string | null = null;

  constructor(private userService: UserService, private router: Router) {}

  onSubmit() {
    if (this.registerForm.valid) {
      this.isLoading = true;
      const formValue = this.registerForm.value;
      const newUser = {
        id: this.userService.getUsers().length + 1,
        userName: formValue.userName || '',
        emailId: formValue.emailId || '',
        role: formValue.role || 'user'
      };
      this.userService.addUser(newUser).subscribe({
        next: () => {
          this.registerForm.reset();
          this.router.navigate(['/user']);
        },
        error: err => {
          this.error = 'Failed to register user: ' + err.message;
          this.isLoading = false;
        },
        complete: () => {
          this.isLoading = false;
        }
      });
    }
  }
}
typescript
// src/app/components/register/register.component.html
h2: User Registration
div: *ngIf="error" class="error": {{ error }}
div: *ngIf="isLoading" class="loading": Registering user...
form: [formGroup]="registerForm" (ngSubmit)="onSubmit()":
  div:
    label: Username: 
    input: formControlName="userName"
    div: *ngIf="registerForm.get('userName')?.hasError('required') && registerForm.get('userName')?.touched":
      Username is required
    div: *ngIf="registerForm.get('userName')?.hasError('minlength') && registerForm.get('userName')?.touched":
      Username must be at least 3 characters
    div: *ngIf="registerForm.get('userName')?.hasError('noSpaces') && registerForm.get('userName')?.touched":
      Username cannot contain spaces
  div:
    label: Email: 
    input: formControlName="emailId"
    div: *ngIf="registerForm.get('emailId')?.hasError('required') && registerForm.get('emailId')?.touched":
      Email is required
    div: *ngIf="registerForm.get('emailId')?.hasError('email') && registerForm.get('emailId')?.touched":
      Invalid email format
  div:
    label: Role: 
    select: formControlName="role":
      option: value="user": User
      option: value="admin": Admin
  button: type="submit" [disabled]="registerForm.invalid || isLoading": Register
p: Back to User Dashboard
css
// src/app/components/register/register.component.css
form { margin: 20px; padding: 20px; border: 1px solid #ccc; }
input, select { padding: 5px; margin: 5px; width: 200px; }
div { margin-bottom: 10px; }
.error { color: red; font-size: 0.9em; }
.loading { text-align: center; color: #007bff; }
Step 13: Update LoginComponent
typescript
// src/app/components/login/login.component.ts
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { Router, RouterLink } from '@angular/router';
import { UserService } from '../../services/user.service';

@Component({
  selector: 'app-login',
  templateUrl: './login.component.html',
  styleUrls: ['./login.component.css'],
  standalone: true,
  imports: [FormsModule, RouterLink]
})
export class LoginComponent {
  username = '';
  password = '';
  errorMessage = '';

  constructor(private userService: UserService, private router: Router) {}

  onSubmit() {
    if (this.userService.validateAdmin(this.username, this.password)) {
      this.router.navigate(['/admin']);
    } else {
      this.errorMessage = 'Invalid username or password';
    }
  }
}
typescript
// src/app/components/login/login.component.html
h2: Admin Login
form: (ngSubmit)="onSubmit()":
  div:
    label: Username: 
    input: [(ngModel)]="username" name="username" required
    div: *ngIf="form.username?.errors?.['required'] && form.username?.touched": Username is required
  div:
    label: Password: 
    input: [(ngModel)]="password" name="password" type="password" required
    div: *ngIf="form.password?.errors?.['required'] && form.password?.touched": Password is required
  div: *ngIf="errorMessage" class="error": {{ errorMessage }}
  button: type="submit" [disabled]="!form.valid": Login
p: Back to User Dashboard
css
// src/app/components/login/login.component.css
form { margin: 20px; padding: 20px; border: 1px solid #ccc; }
input { padding: 5px; margin: 5px; width: 200px; }
div { margin-bottom: 10px; }
.error { color: red; font-size: 0.9em; }
Step 14: Update Child Components
typescript
// src/app/components/admin-dashboard/admin-dashboard.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-admin-dashboard',
  template: `
    h3: Admin Dashboard
    p: Welcome to the Admin Dashboard!
  `,
  standalone: true
})
export class AdminDashboardComponent {}
typescript
// src/app/components/admin-settings/admin-settings.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-admin-settings',
  template: `
    h3: Admin Settings
    p: Configure settings here.
  `,
  standalone: true
})
export class AdminSettingsComponent {}
Step 15: Update NotFoundComponent
typescript
// src/app/components/not-found/not-found.component.ts
import { Component } from '@angular/core';
import { RouterLink } from '@angular/router';

@Component({
  selector: 'app-not-found',
  template: `
    h2: 404 - Page Not Found
    p: Go back to User Dashboard
  `,
  standalone: true,
  imports: [RouterLink]
})
export class NotFoundComponent {}
Step 16: Update FilterByRolePipe
typescript
// src/app/pipes/filter-by-role.pipe.ts
import { Pipe, PipeTransform } from '@angular/core';
import { User } from '../models/user.model';

@Pipe({
  name: 'filterByRole',
  standalone: true
})
export class FilterByRolePipe implements PipeTransform {
  transform(users: User[], role: string): User[] {
    if (!users || role === 'all') return users;
    return users.filter(user => user.role === role);
  }
}
Step 17: Update no-spaces.validator.ts
typescript
// src/app/validators/no-spaces.validator.ts
import { AbstractControl, ValidatorFn } from '@angular/forms';

export function noSpacesValidator(): ValidatorFn {
  return (control: AbstractControl): { [key: string]: any } | null => {
    const hasSpaces = control.value?.includes(' ');
    return hasSpaces ? { noSpaces: true } : null;
  };
}
Step 18: Test the Application
  1. Run ng serve and visit http://localhost:4200.
  2. Test features:
    • Admin Panel (/admin):
      • Fetch users from freeapi.miniprojectideas.com.
      • Select a user (click “Select”) and verify it’s saved in local storage (check browser DevTools > Application > Local Storage).
      • Navigate to /user and confirm the selected user appears in UserInfoComponent.
      • Clear the selection and verify it’s removed from local storage.
    • User Dashboard (/user):
      • View the selected user in UserInfoComponent.
      • Clear the selection and verify it updates.
    • Register (/register):
      • Add a new user and verify it appears in User and Admin lists.
    • Login (/login):
      • Log in with admin1/admin123.
    • User Details (/user/:id):
      • View user details.
    • 404 Page (/invalid):
      • Confirm the Not Found page appears.

Troubleshooting Common ErrorsTS2305: Module '"./app/app"' has no exported member 'App'Cause: Incorrect import in main.ts.Fix:
typescript
// src/main.ts
import { bootstrapApplication } from '@angular/platform-browser';
import { provideRouter, provideHttpClient } from '@angular/router';
import { AppComponent } from './app/app.component';
import { routes } from './app/app.routes';

bootstrapApplication(AppComponent, {
  providers: [provideRouter(routes), provideHttpClient()]
}).catch(err => console.error(err));
Steps:
  1. Ensure AppComponent is exported in app.component.ts.
  2. Verify file paths.
  3. Run ng serve.
ViewChild UndefinedCause: Accessing ViewChild before the view is initialized.Fix:
  • Use ngAfterViewInit to access ViewChild properties.
  • Ensure the child component is present in the template.
Local Storage Data Not PersistingCause: Incorrect serialization or key mismatch.Fix:
  • Verify JSON.stringify and JSON.parse usage in StorageService.
  • Check the key (selectedUser) is consistent across components.

Best Practices for Component Communication
  • Use
    @Input
    /
    @Output
    for Simple Communication
    : Ideal for parent-child data passing.
  • Leverage Services for Complex State: Use services for app-wide state management.
  • Access ViewChild/ContentChild Sparingly: Prefer @Input/@Output for cleaner code.
  • Type Safety: Use interfaces for @Input and @Output data.
  • Storage Security: Sanitize data before storing in local/session storage to prevent XSS attacks.

0 comments:

Featured Post

Master Angular 20 Basics: A Complete Beginner’s Guide with Examples and Best Practices

Welcome to the complete Angular 20 learning roadmap ! This series takes you step by step from basics to intermediate concepts , with hands...

Subscribe

 
Toggle Footer
Top