Share logic and state across components — injectable services, the inject function, and signal-based services that work like a built-in store.
Why: logic that more than one component needs — logging, auth, talking to APIs — lives in a service: a plain class with @Injectable. providedIn: 'root' means the whole app shares ONE instance (a singleton), and inject() asks Angular for it — you never write new yourself. This is dependency injection, the pattern at the heart of Angular.
import { Component, Injectable, inject } from '@angular/core'
// providedIn: 'root' = one shared instance for the whole app
@Injectable({ providedIn: 'root' })
export class Logger {
log(message: string) {
console.log('[app]', message)
}
}
@Component({
selector: 'app-save-button',
template: `<button (click)="save()">Save</button>`,
})
export class SaveButton {
// inject() asks Angular for the shared instance — you never `new` it
private logger = inject(Logger)
save() {
this.logger.log('saved at ' + Date.now())
}
}Why: when two siblings need the same data, the state moves to their closest common parent — one owns it, both receive it. The child gets the value as an input and reports changes up with an output. This is the first tool to reach for; most "state management" problems dissolve right here.
import { Component, input, output, signal } from '@angular/core'
@Component({
selector: 'app-filter-box',
template: `
<input [value]="value()" (input)="onInput($event)" />
`,
})
export class FilterBox {
value = input.required<string>()
changed = output<string>() // reports changes up
onInput(e: Event) {
this.changed.emit((e.target as HTMLInputElement).value)
}
}
@Component({
selector: 'app-search',
imports: [FilterBox],
template: `
<!-- Both siblings need query, so the parent owns it -->
<app-filter-box [value]="query()" (changed)="query.set($event)" />
<p>Results for: {{ query() || '(everything)' }}</p>
`,
})
export class Search {
query = signal('')
}