Show the live alert feed for the selected channel
Angular · Angular · advanced · greenfield
Wires the alerts panel to the live feed: the component watches the channelId route param, and on every change resets the list and subscribes to that channel's alert stream, prepending each incoming alert and keeping the latest 50. The param subscription is scoped with takeUntilDestroyed per our conventions. Tested by opening the ops channel and watching alerts stream in live.
An incident-response dashboard. Channels emit a steady stream of alerts (a busy channel does several per minute); on-call engineers hop between channels constantly during an incident. The panel component stays mounted across switches — routing only rewrites the param.
Requirements
- The alerts panel shows the live feed for the channel named in the route: `AlertFeedService.channel(id)` returns a long-lived, websocket-backed Observable that never completes and emits alerts for that one channel for as long as something is subscribed.
- Users switch channels from the sidebar without the panel being destroyed — only the `channelId` route param changes. On a switch, the list clears and from then on shows only alerts for the newly selected channel, newest first, capped at the latest 50.
- At any moment at most ONE channel subscription may be live: the previous channel's subscription must be torn down when the user switches, and whatever is live must be torn down when the component is destroyed.
- Use signals for the component state per the codebase conventions.
Files touched
- src/app/alerts/channel-alerts.component.ts
--- src/app/alerts/channel-alerts.component.ts
+import { Component, inject, signal } from '@angular/core';
+import { ActivatedRoute } from '@angular/router';
+import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
+import { AlertFeedService, AlertMessage } from './alert-feed.service';
+
+const MAX_ALERTS = 50;
+
+/**
+ * Live alert feed for the channel in the route. The sidebar switches
+ * channels by rewriting the :channelId param; the panel stays mounted.
+ */
+@Component({
+ selector: 'app-channel-alerts',
+ standalone: true,
+ template: `
+ <section class="alerts" aria-label="Channel alerts">
+ <h2>#{{ channelId() }}</h2>
+ @if (alerts().length === 0) {
+ <p class="alerts-empty">No alerts yet in this channel.</p>
+ } @else {
+ <ul class="alerts-list">
+ @for (alert of alerts(); track alert.id) {
+ <li class="alert-row" [class.critical]="alert.severity === 'critical'">
+ <span class="alert-text">{{ alert.text }}</span>
+ <time [attr.datetime]="alert.receivedAt">{{ alert.receivedAt }}</time>
+ </li>
+ }
+ </ul>
+ }