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:
Explanation:
Explanation:
ViewChild & ContentChildViewChildViewChild allows a parent component to access a child component’s properties or methods directly.ExampleExplanation:Explanation:
Local Storage & Session Storage IntegrationLocal storage and session storage allow data persistence in the browser:Explanation:
Hands-On Project: Pass User Info from Admin to User and Persist in Local StorageLet’s extend your angular-routing-demo project to:Step 2: Create StorageServiceStep 3: Update User ModelStep 4: Update app.routes.tsAdd the UserInfoComponent to the User route:Step 5: Update main.tsStep 6: Update AppComponentStep 7: Create UserInfoComponentThis child component displays user info passed from AdminComponent and emits events.Step 8: Update UserServiceStep 9: Update AdminComponentAdd functionality to select a user and pass it to UserComponent.Step 10: Update UserComponentAdd UserInfoComponent as a child and use ViewChild.Step 11: Update UserDetailsComponentStep 12: Update RegisterComponentStep 13: Update LoginComponentStep 14: Update Child ComponentsStep 15: Update NotFoundComponentStep 16: Update FilterByRolePipeStep 17: Update no-spaces.validator.tsStep 18: Test the Application
Troubleshooting Common ErrorsTS2305: Module '"./app/app"' has no exported member 'App'Cause: Incorrect import in main.ts.Fix:Steps:
Best Practices for Component Communication
- @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.
@Input
() (Parent → Child)The @Input
() decorator allows a parent component to pass data to a child component via a property binding.Exampletypescript
// 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';
}
- @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.Exampletypescript
// 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;
}
}
- @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();
}
}
- @ViewChild('childRef'): References the child component via a template variable.
- ngAfterViewInit: Ensures the child is available before accessing it.
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 || '';
}
}
- @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.
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;
}
}
- 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:
- Pass selected user info from AdminComponent to UserComponent using @Input() and @Output().
- Use ViewChild to access a child component’s method.
- Persist the selected user in local storage using a new StorageService.
- Fetch users from freeapi.miniprojectideas.com (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
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);
}
}
typescript
// src/app/models/user.model.ts
export interface User {
id: number;
userName: string;
emailId: string;
role: string;
}
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 }
];
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));
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; }
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();
}
}
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);
}
}
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; }
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; }
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; }
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; }
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; }
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 {}
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 {}
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);
}
}
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;
};
}
- Run ng serve and visit http://localhost:4200.
- 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.
- Admin Panel (/admin):
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));
- Ensure AppComponent is exported in app.component.ts.
- Verify file paths.
- Run ng serve.
- Use ngAfterViewInit to access ViewChild properties.
- Ensure the child component is present in the template.
- Verify JSON.stringify and JSON.parse usage in StorageService.
- Check the key (selectedUser) is consistent across components.
Best Practices for Component Communication
- Use @Input/@Outputfor 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:
Post a Comment