Angular Fundamentals
This guide introduces core Angular concepts using examples from the TimeLock project. If you're new to Angular, this is your starting point.
Table of Contents
- What is Angular?
- Components
- Services and Dependency Injection
- Signals (Reactive State Management)
- Routing
- Modules vs Standalone Components
- TypeScript Integration
What is Angular?
Angular is a TypeScript-based web application framework developed by Google. It provides:
- Component-based architecture - Build encapsulated components that manage their own state
- Dependency injection - Manage dependencies efficiently
- Reactive programming - Handle asynchronous data with RxJS and Signals
- Routing - Navigate between different views
- Forms - Handle user input with powerful form controls
- HTTP client - Communicate with backend services
Key Angular Concepts in TimeLock
TimeLock uses Angular 20 with modern patterns:
- Standalone components (no NgModules needed)
- Signals for reactive state management
- Lazy loading for performance
- Dependency injection for services
Components
Components are the building blocks of Angular applications. Each component consists of:
Component Structure
@Component({
selector: 'app-example', // HTML tag name
standalone: true, // Modern standalone component
imports: [CommonModule], // Dependencies
template: `<h1>{{title}}</h1>`, // HTML template
styleUrls: ['./example.css'] // Component styles
})
export class ExampleComponent {
title = 'Hello World'; // Component properties
}
Real Example from TimeLock
// From src/app/components/todo-item/todo-item.component.ts
@Component({
selector: 'app-todo-item',
standalone: true,
imports: [CommonModule, FormsModule, TaskEditorComponent],
templateUrl: './todo-item.component.html',
styleUrls: ['./todo-item.component.css']
})
export class TodoItemComponent {
@Input() todo!: Todo; // Input from parent
@Output() todoUpdated = new EventEmitter<Todo>(); // Output to parent
isEditing = signal(false); // Component state with signals
toggleComplete() {
// Component method
this.todoUpdated.emit({
...this.todo,
completed: !this.todo.completed
});
}
}
Component Communication
Parent to Child: Use @Input()
// Parent template
<app-todo-item [todo]="selectedTodo"></app-todo-item>
// Child component
@Input() todo!: Todo;
Child to Parent: Use @Output()
and EventEmitter
// Child component
@Output() todoUpdated = new EventEmitter<Todo>();
onUpdate() {
this.todoUpdated.emit(this.todo);
}
// Parent template
<app-todo-item (todoUpdated)="handleTodoUpdate($event)"></app-todo-item>
Services and Dependency Injection
Services handle business logic and data management. Angular's dependency injection system provides services to components.
Creating a Service
@Injectable({
providedIn: 'root' // Available application-wide
})
export class TodoService {
private todos = signal<Todo[]>([]);
addTodo(todo: Todo) {
this.todos.update(todos => [...todos, todo]);
}
getTodos() {
return this.todos.asReadonly();
}
}
Using Services in Components
export class TodoListComponent {
private todoService = inject(TodoService); // Modern injection syntax
// Or using constructor injection (traditional way)
constructor(private todoService: TodoService) {}
todos = this.todoService.getTodos(); // Access service data
}
TimeLock Service Examples
ProjectService - Manages projects
// Simplified example from src/app/services/project.service.ts
@Injectable({ providedIn: 'root' })
export class ProjectService {
private projectsSignal = signal<Project[]>([]);
projects = this.projectsSignal.asReadonly();
async addProject(projectData: Omit<Project, 'id'>): Promise<string> {
const newProject = { ...projectData, id: this.generateId() };
this.projectsSignal.update(projects => [...projects, newProject]);
await this.saveProjects();
return newProject.id;
}
}
Signals
Signals are Angular's modern reactive state management solution (introduced in Angular 16+).
Basic Signals
import { signal, computed } from '@angular/core';
export class ExampleComponent {
// Writable signal
count = signal(0);
// Computed signal (derived state)
doubleCount = computed(() => this.count() * 2);
// Read signal value
getCurrentCount() {
return this.count(); // Call like a function
}
// Update signal
increment() {
this.count.update(value => value + 1);
// Or set directly: this.count.set(5);
}
}
Signals in Templates
<!-- Display signal values -->
<p>Count: {{count()}}</p>
<p>Double: {{doubleCount()}}</p>
<!-- Use in event handlers -->
<button (click)="increment()">Increment</button>
TimeLock Signal Examples
// From TodoService
private todosSignal = signal<Todo[]>([]);
private filterSignal = signal<FilterType>('all');
// Computed signals for filtered data
activeTodos = computed(() =>
this.todosSignal().filter(todo => !todo.completed && !todo.archived)
);
completedTodos = computed(() =>
this.todosSignal().filter(todo => todo.completed)
);
filteredTodos = computed(() => {
const todos = this.todosSignal();
const filter = this.filterSignal();
switch (filter) {
case 'active': return todos.filter(t => !t.completed);
case 'completed': return todos.filter(t => t.completed);
default: return todos;
}
});
Routing
Angular Router enables navigation between different views/pages.
Route Configuration
// From src/app/app.routes.ts
export const routes: Routes = [
{ path: '', redirectTo: '/projects', pathMatch: 'full' },
{
path: 'projects',
loadComponent: () => import('./pages/projects/projects.component')
.then(m => m.ProjectsComponent)
},
{
path: 'project/:id',
loadComponent: () => import('./pages/project-detail/project-detail.component')
.then(m => m.ProjectDetailComponent)
}
];
Navigation
// In component
import { Router } from '@angular/router';
export class ExampleComponent {
private router = inject(Router);
navigateToProject(projectId: string) {
this.router.navigate(['/project', projectId]);
}
}
<!-- In template -->
<a routerLink="/projects">Projects</a>
<a [routerLink]="['/project', project.id]">{{project.name}}</a>
Route Parameters
// In component
import { ActivatedRoute } from '@angular/router';
export class ProjectDetailComponent {
private route = inject(ActivatedRoute);
ngOnInit() {
const projectId = this.route.snapshot.paramMap.get('id');
// Or reactive: this.route.paramMap.subscribe(params => ...)
}
}
Modules vs Standalone Components
TimeLock uses standalone components (Angular 14+), which is the modern approach.
Traditional NgModule Approach (Old)
@NgModule({
declarations: [AppComponent, TodoComponent],
imports: [BrowserModule, FormsModule],
providers: [TodoService],
bootstrap: [AppComponent]
})
export class AppModule { }
Standalone Components (Modern - Used in TimeLock)
@Component({
selector: 'app-todo',
standalone: true, // No module needed!
imports: [CommonModule, FormsModule], // Direct imports
template: `...`
})
export class TodoComponent { }
// Bootstrap directly
bootstrapApplication(App, {
providers: [
provideRouter(routes),
// other providers
]
});
Benefits of Standalone Components
- Simpler - No need to manage NgModules
- Better tree-shaking - Unused code is eliminated
- Lazy loading - Components can be loaded on-demand
- Easier testing - Less boilerplate
TypeScript Integration
Angular is built with TypeScript, providing strong typing and better developer experience.
Interface Definitions
// From src/app/models/todo.model.ts
export interface Todo {
id: string;
title: string;
description?: string; // Optional property
completed: boolean;
priority: 'low' | 'medium' | 'high'; // Union types
dueDate?: Date;
tags?: string[];
}
Type Safety in Components
export class TodoComponent {
@Input() todo!: Todo; // Strongly typed input
updateTodo(updates: Partial<Todo>) { // Partial type utility
// TypeScript ensures type safety
this.todo = { ...this.todo, ...updates };
}
}
Generic Types
// Generic service method
async loadData<T>(key: string): Promise<T[]> {
// Implementation
}
// Usage
const todos = await this.loadData<Todo>('todos');
const projects = await this.loadData<Project>('projects');
Next Steps
Now that you understand Angular fundamentals, continue with:
- Project Architecture - See how these concepts work together in TimeLock
- Getting Started - Set up your development environment
- Code Structure - Explore the actual codebase