Skip to content

Key Concepts

Components are the building blocks of Angular applications. Each component encapsulates:

  • Template — The HTML structure
  • Class — The logic written in TypeScript
  • Styles — CSS/SCSS for the component
  • Metadata — The @Component decorator
@Component({
selector: 'app-greeting',
template: `<h1>Hello, {{ name }}!</h1>`,
styles: [`h1 { color: blue; }`]
})
export class GreetingComponent {
name = 'Angular';
}

Modern Angular recommends standalone components, which don’t require NgModules:

@Component({
selector: 'app-button',
standalone: true,
imports: [CommonModule],
templateUrl: './button.component.html'
})
export class ButtonComponent {
// No NgModule needed!
}

Services are reusable classes that provide functionality across your application.

import { Injectable } from '@angular/core';
@Injectable({ providedIn: 'root' })
export class DataService {
getData(): string {
return 'Hello from service!';
}
}
@Component({
selector: 'app-display',
standalone: true,
template: `<p>{{ message }}</p>`
})
export class DisplayComponent {
message: string;
constructor(private dataService: DataService) {
this.message = dataService.getData();
}
}

Key Points:

  • @Injectable() decorator makes a class injectable
  • providedIn: 'root' provides the service globally
  • The constructor parameter is automatically injected
  • Services promote code reuse and testability

Angular’s DI container automatically provides dependencies to components and services.

constructor(private http: HttpClient, private route: ActivatedRoute) {}
export class MyComponent {
private http = inject(HttpClient);
}

Directives modify the behavior or appearance of DOM elements.

<!-- ngIf: Conditional rendering -->
<div *ngIf="isVisible">Visible!</div>
<!-- ngFor: Loops -->
<li *ngFor="let item of items">{{ item.name }}</li>
<!-- ngSwitch: Multiple conditions -->
<div [ngSwitch]="color">
<div *ngSwitchCase="'red'">Red selected</div>
<div *ngSwitchCase="'blue'">Blue selected</div>
<div *ngSwitchDefault>Other color</div>
</div>
<!-- ngClass: Dynamic CSS classes -->
<div [ngClass]="{ 'active': isActive, 'disabled': isDisabled }">
Content
</div>
<!-- ngStyle: Dynamic inline styles -->
<div [ngStyle]="{ 'color': textColor, 'font-size.px': fontSize }">
Styled text
</div>
<!-- Built-in directives -->
<input [(ngModel)]="name" />
<input [disabled]="isDisabled" />
@Directive({
selector: '[appHighlight]',
standalone: true
})
export class HighlightDirective {
constructor(private el: ElementRef) {
this.el.nativeElement.style.backgroundColor = 'yellow';
}
}

Usage:

<p appHighlight>This text is highlighted</p>

Angular provides multiple ways to bind data between component and template.

<p>{{ message }}</p>
<p>{{ user.name.toUpperCase() }}</p>
<p>{{ 5 + 5 }}</p>
<img [src]="imageUrl" />
<button [disabled]="isDisabled">Click me</button>
<div [class.active]="isActive">Active</div>
<button (click)="handleClick()">Click</button>
<input (keyup.enter)="onEnter($event)" />
<div (mouseover)="onHover()">Hover me</div>
<input [(ngModel)]="name" />
<!-- Equivalent to: <input [value]="name" (input)="name = $event.target.value" /> -->

Pipes transform data for display in templates.

<!-- Date pipe -->
<p>{{ today | date: 'short' }}</p>
<!-- Currency pipe -->
<p>{{ price | currency: 'USD' }}</p>
<!-- Uppercase/Lowercase -->
<p>{{ text | uppercase }}</p>
<p>{{ text | lowercase }}</p>
<!-- Decimal pipe -->
<p>{{ value | number: '1.2-2' }}</p>
<!-- Percent pipe -->
<p>{{ score | percent }}</p>
<!-- Json pipe (debugging) -->
<pre>{{ data | json }}</pre>
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'reverse',
standalone: true
})
export class ReversePipe implements PipeTransform {
transform(value: string): string {
return value.split('').reverse().join('');
}
}

Usage:

<p>{{ 'hello' | reverse }}</p> <!-- Output: olleh -->

Angular provides two approaches to forms.

import { ReactiveFormsModule, FormBuilder } from '@angular/forms';
@Component({
selector: 'app-form',
standalone: true,
imports: [ReactiveFormsModule],
template: `
<form [formGroup]="form" (ngSubmit)="onSubmit()">
<input formControlName="name" />
<input formControlName="email" />
<button type="submit">Submit</button>
</form>
`
})
export class FormComponent {
form: FormGroup;
constructor(private fb: FormBuilder) {
this.form = this.fb.group({
name: ['', Validators.required],
email: ['', [Validators.required, Validators.email]]
});
}
onSubmit() {
console.log(this.form.value);
}
}
<form (ngSubmit)="onSubmit(form)" #form="ngForm">
<input name="name" ngModel required />
<input name="email" ngModel required email />
<button type="submit" [disabled]="!form.valid">Submit</button>
</form>

Route between different views in your application.

export const routes: Routes = [
{ path: '', redirectTo: '/home', pathMatch: 'full' },
{ path: 'home', component: HomeComponent },
{ path: 'about', component: AboutComponent },
{ path: 'user/:id', component: UserDetailComponent },
{ path: '**', component: NotFoundComponent }
];
constructor(private router: Router) {}
navigate() {
this.router.navigate(['/user', 123]);
}
<a routerLink="/home">Home</a>
<a [routerLink]="['/user', userId]">User Profile</a>

Components have a lifecycle Angular manages.

export class MyComponent implements OnInit, OnDestroy {
// Called after component initialization
ngOnInit() {
// Fetch data, initialize properties
}
// Called when component is destroyed
ngOnDestroy() {
// Clean up resources
}
// Called when input property changes
ngOnChanges(changes: SimpleChanges) {
// React to input changes
}
// Called after view initialization
ngAfterViewInit() {
// Access DOM elements
}
}

Angular tracks when component data changes and updates the view.

@Component({
selector: 'app-optimized',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class OptimizedComponent {
// Only checks when inputs change or events occur
}

Communicate with backend APIs.

import { HttpClient } from '@angular/common/http';
@Injectable({ providedIn: 'root' })
export class ApiService {
constructor(private http: HttpClient) {}
getUsers() {
return this.http.get<User[]>('/api/users');
}
createUser(user: User) {
return this.http.post<User>('/api/users', user);
}
updateUser(id: number, user: User) {
return this.http.put<User>(`/api/users/${id}`, user);
}
deleteUser(id: number) {
return this.http.delete(`/api/users/${id}`);
}
}
export class UserListComponent {
users$ = this.api.getUsers();
constructor(private api: ApiService) {}
// In template: {{ users$ | async }}
}
ConceptPurposeExample
ComponentsBuild UI blocks@Component() class
ServicesReusable logicFetch data, calculations
DirectivesModify DOM/behavior*ngIf, [ngClass]
Data BindingConnect template & logic{{ }}, (), []
PipesTransform data{{ date | date }}
FormsUser inputReactive or template-driven
RoutingNavigationRoutes, RouterLink
LifecycleComponent statesOnInit, OnDestroy
HTTPAPI communicationHttpClient