Skip to main content

Complete SMS Application in Angular

Level: Advanced

ℹ️ What You'll Learn
  • Full SMS application architecture
  • Feature modules organization
  • Service layer design
  • Complete routing setup
  • Forms and validation
  • HTTP integration
  • Error handling

Project Structure

sms-portal/
├── src/
│ ├── app/
│ │ ├── core/
│ │ │ ├── auth.service.ts
│ │ │ ├── auth.guard.ts
│ │ │ └── http.interceptor.ts
│ │ ├── shared/
│ │ │ ├── navbar/
│ │ │ ├── footer/
│ │ │ └── pipes/
│ │ ├── features/
│ │ │ ├── students/
│ │ │ │ ├── components/
│ │ │ │ │ ├── student-list/
│ │ │ │ │ ├── student-detail/
│ │ │ │ │ └── student-form/
│ │ │ │ ├── services/
│ │ │ │ │ └── student.service.ts
│ │ │ │ └── student.routes.ts
│ │ │ ├── exams/
│ │ │ └── fees/
│ │ └── app.routes.ts
│ └── main.ts

App Routes

// app.routes.ts
export const routes: Routes = [
{ path: '', redirectTo: '/dashboard', pathMatch: 'full' },

{
path: 'login',
component: LoginComponent
},
{
path: 'dashboard',
component: DashboardComponent,
canActivate: [authGuard]
},
{
path: 'students',
canActivate: [authGuard],
loadChildren: () => import('./features/students/student.routes')
.then(m => m.STUDENT_ROUTES)
},
{
path: 'exams',
canActivate: [authGuard],
loadChildren: () => import('./features/exams/exam.routes')
.then(m => m.EXAM_ROUTES)
},
{
path: '**',
redirectTo: '/dashboard'
}
];

Student Feature Module

// features/students/student.routes.ts
export const STUDENT_ROUTES: Routes = [
{ path: '', component: StudentListComponent },
{ path: 'new', component: StudentFormComponent },
{ path: ':id', component: StudentDetailComponent },
{ path: ':id/edit', component: StudentFormComponent }
];

// features/students/services/student.service.ts
@Injectable({ providedIn: 'root' })
export class StudentService {
private apiUrl = `${environment.apiUrl}/students`;

constructor(private http: HttpClient) {}

getStudents(filters?: any) {
return this.http.get<Student[]>(this.apiUrl, { params: filters });
}

getStudent(id: number) {
return this.http.get<Student>(`${this.apiUrl}/${id}`);
}

createStudent(student: Omit<Student, 'id'>) {
return this.http.post<Student>(this.apiUrl, student);
}

updateStudent(id: number, student: Student) {
return this.http.put<Student>(`${this.apiUrl}/${id}`, student);
}

deleteStudent(id: number) {
return this.http.delete(`${this.apiUrl}/${id}`);
}
}

Student List Component

// features/students/components/student-list/student-list.component.ts
@Component({
selector: 'app-student-list',
templateUrl: './student-list.component.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class StudentListComponent implements OnInit {
students$: Observable<Student[]>;
loading$ = new BehaviorSubject(false);
error$ = new BehaviorSubject<string | null>(null);

constructor(
private service: StudentService,
private router: Router
) {
this.students$ = this.service.getStudents();
}

ngOnInit() {
this.loadStudents();
}

loadStudents() {
this.loading$.next(true);
this.service.getStudents()
.pipe(
finalize(() => this.loading$.next(false)),
catchError(error => {
this.error$.next('Failed to load students');
return of([]);
})
)
.subscribe();
}

deleteStudent(id: number) {
if (confirm('Delete student?')) {
this.service.deleteStudent(id)
.pipe(
tap(() => this.loadStudents())
)
.subscribe();
}
}

editStudent(id: number) {
this.router.navigate(['/students', id, 'edit']);
}

trackByStudentId(index: number, student: Student) {
return student.id;
}
}
<!-- student-list.component.html -->
<div class="container">
<div class="header">
<h1>Students</h1>
<button (click)="router.navigate(['/students/new'])">Add Student</button>
</div>

<div *ngIf="loading$ | async" class="loading">Loading...</div>

<div *ngIf="error$ | async as error" class="alert-error">
{{ error }}
</div>

<table *ngIf="students$ | async as students">
<thead>
<tr>
<th>Roll #</th>
<th>Name</th>
<th>Email</th>
<th>Class</th>
<th>Status</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let student of students; trackBy: trackByStudentId">
<td>{{ student.rollNumber }}</td>
<td>{{ student.name }}</td>
<td>{{ student.email }}</td>
<td>{{ student.className }}</td>
<td>
<span [ngClass]="'status-' + (student.status | lowercase)">
{{ student.status }}
</span>
</td>
<td>
<button (click)="editStudent(student.id)">Edit</button>
<button (click)="deleteStudent(student.id)">Delete</button>
</td>
</tr>
</tbody>
</table>
</div>

Student Form Component

export class StudentFormComponent implements OnInit, OnDestroy {
form: FormGroup;
loading = false;
error: string | null = null;
private destroy$ = new Subject<void>();

constructor(
private fb: FormBuilder,
private service: StudentService,
private route: ActivatedRoute,
private router: Router
) {
this.form = this.fb.group({
rollNumber: ['', [Validators.required, Validators.pattern(/^[0-9]+$/)]],
name: ['', [Validators.required, Validators.minLength(3)]],
email: ['', [Validators.required, Validators.email]],
className: ['10', Validators.required],
status: ['Active', Validators.required]
});
}

ngOnInit() {
// Check if editing
this.route.params
.pipe(
switchMap(params => {
if (params['id']) {
return this.service.getStudent(params['id']);
}
return of(null);
}),
takeUntil(this.destroy$)
)
.subscribe(student => {
if (student) {
this.form.patchValue(student);
}
});
}

onSubmit() {
if (this.form.invalid) return;

this.loading = true;
const id = this.route.snapshot.paramMap.get('id');
const request = id
? this.service.updateStudent(+id, this.form.value)
: this.service.createStudent(this.form.value);

request
.pipe(
tap(() => {
this.router.navigate(['/students']);
}),
catchError(error => {
this.error = error.message;
this.loading = false;
return of(null);
}),
takeUntil(this.destroy$)
)
.subscribe();
}

ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
}

Key Patterns Used

  • Feature modules (lazy loaded)
  • Service layer for API
  • Routing with guards
  • Reactive forms
  • Observables and pipes
  • Error handling
  • Loading states
  • Change detection optimization

Architecture Benefits

✓ Modular structure ✓ Separation of concerns ✓ Easy to test ✓ Scalable ✓ Maintainable ✓ Reusable services

SMS App Features

  • Student management (CRUD)
  • Form validation
  • Error handling
  • Loading states
  • Role-based access (guards)
  • API integration
  • List pagination
  • Search/filter

Key Takeaways

  • Feature modules = separate concerns
  • Services = reusable logic
  • Guards = route protection
  • Observables = async operations
  • Forms = user input
  • Error handling = user feedback
  • Testing = confidence in code
💡 Backend Developer Tip

This structure mirrors ASP.NET Core architecture: Core = infrastructure, Shared = common utilities, Features = business logic. Same principles, Angular implementation.

⚠️ Production Checklist
  • Authentication working
  • Error handling complete
  • Loading states implemented
  • Form validation working
  • Tests passing
  • Build optimized
  • Environment configs correct
  • CORS configured
  • API documentation done
  • Monitoring setup
🤖Use AI to Learn Faster

Use ChatGPT, Claude, or Copilot to go deeper on Complete Angular SMS. Try these prompts:

  • "How do feature modules improve scalability?"
  • "Why separate services from components?"
  • "What's complete SMS app workflow?"
  • "Quiz me on Angular architecture"

💡 Tip: After reading this article, paste your own code into AI and ask "What could go wrong here and why?" — fastest way to find edge cases and deepen understanding.

nexcoding.in