Skip to main content

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

  1. What is Angular?
  2. Components
  3. Services and Dependency Injection
  4. Signals (Reactive State Management)
  5. Routing
  6. Modules vs Standalone Components
  7. 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)
}
];
// 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:

  1. Project Architecture - See how these concepts work together in TimeLock
  2. Getting Started - Set up your development environment
  3. Code Structure - Explore the actual codebase