Angular'da MQTT kullanmanın iki iyi yolu vardır. ngx-mqtt, MQTT.js'i bir Angular modülüne sarar ve RxJS observable'ları döndüren MqttService.observe() metodunu sunar; mesajları bir bileşene getirmenin en hızlı yolu budur. Ya da MQTT.js etrafında küçük, inject edilebilir bir servisi kendiniz yazarsınız: yaklaşık altmış satır, client sürümü, MQTT 5 seçenekleri ve bağlantı durumu üzerinde tam kontrol sağlar. İkisi de güvenli WebSocket (wss://) üzerinden çalışır, çünkü tarayıcılar ham TCP bağlantısı açamaz.
Bu eğitim iki yolu da gösteriyor, sonucu signal'li standalone bir bileşene bağlıyor ve gerçek bir broker'a karşı test ediyor.
Bağlantı ayarları
| Ayar | Değer |
|---|---|
| URL | wss://broker.connectmq.com:8084/mqtt |
| Host / port / path | broker.connectmq.com / 8084 / /mqtt |
| Protokol | MQTT 3.1.1 veya 5 |
| Kimlik bilgileri | yalnızca arayüzün topic'lerine erişimi olan bir tarayıcı hesabı |
HTTPS üzerinden sunulan bir Angular uygulaması wss:// kullanmak zorundadır; ws:// mixed content olarak engellenir. WebSocket üzerinden MQTT transport'u ayrıntılı olarak açıklar.
Seçenek 1: ngx-mqtt
npm i ngx-mqttngx-mqtt, Angular'dan bağımsız olarak geliştirilir ve kendi MQTT.js bağımlılığını sabitler. Kurmadan önce peer dependency'lerinin Angular sürümünüzle uyumlu olduğunu kontrol edin; kurulum hatalarının en yaygın kaynağı budur.
Standalone bir uygulamanın application config'ine bir kez kaydedin:
import { ApplicationConfig, importProvidersFrom } from '@angular/core';
import { IMqttServiceOptions, MqttModule } from 'ngx-mqtt';
const MQTT_OPTIONS: IMqttServiceOptions = {
hostname: 'broker.connectmq.com',
port: 8084,
protocol: 'wss',
path: '/mqtt',
username: 'username',
password: 'password',
};
export const appConfig: ApplicationConfig = {
providers: [importProvidersFrom(MqttModule.forRoot(MQTT_OPTIONS))],
};Ardından mesaja ihtiyaç duyduğunuz her yerde MqttService'i inject edin:
import { Component, inject } from '@angular/core';
import { toSignal } from '@angular/core/rxjs-interop';
import { IMqttMessage, MqttService } from 'ngx-mqtt';
import { map } from 'rxjs';
@Component({
selector: 'app-status',
template: `<p>Last status: {{ status() }}</p>
<button (click)="ping()">Ping</button>`,
})
export class StatusComponent {
private readonly mqtt = inject(MqttService);
readonly status = toSignal(
this.mqtt.observe('testmqtt/angular/status').pipe(map((m: IMqttMessage) => m.payload.toString())),
{ initialValue: '—' },
);
ping() {
this.mqtt.unsafePublish('testmqtt/angular/cmd/ping', String(Date.now()), { qos: 1 });
}
}observe(), aynı filtrenin tüm gözlemcileri arasında broker'daki tek bir aboneliği paylaşır ve sonuncusu ayrıldığında aboneliği iptal eder. publish(), broker mesajı onayladığında tamamlanan bir observable döndürür; unsafePublish() ise gönderip unutur.
Seçenek 2: kendi MQTT.js servisiniz
Güncel MQTT.js sürümünü, MQTT 5 property'lerini ya da sadece daha az bağımlılık istiyorsanız, elle yazılmış bir servis oldukça basittir:
npm i mqttimport { Injectable, OnDestroy, PLATFORM_ID, inject } from '@angular/core';
import { isPlatformBrowser } from '@angular/common';
import mqtt, { type MqttClient } from 'mqtt';
import { BehaviorSubject, Observable, Subject, filter, map } from 'rxjs';
import { environment } from '../environments/environment';
export type MqttState = 'idle' | 'connecting' | 'connected' | 'reconnecting' | 'offline' | 'error';
export interface MqttMessage<T> { topic: string; data: T }
export function matches(topicFilter: string, topic: string): boolean {
const f = topicFilter.split('/');
const t = topic.split('/');
for (let i = 0; i < f.length; i++) {
if (f[i] === '#') return true;
if (i >= t.length || (f[i] !== '+' && f[i] !== t[i])) return false;
}
return f.length === t.length;
}
@Injectable({ providedIn: 'root' })
export class MqttClientService implements OnDestroy {
readonly state$ = new BehaviorSubject<MqttState>('idle');
private readonly incoming$ = new Subject<{ topic: string; payload: Uint8Array }>();
private readonly refCounts = new Map<string, number>();
private client: MqttClient | null = null;
constructor() {
if (!isPlatformBrowser(inject(PLATFORM_ID))) return; // SSR sırasında MQTT yok
this.state$.next('connecting');
this.client = mqtt.connect(environment.mqtt.url, {
username: environment.mqtt.username,
password: environment.mqtt.password,
clientId: `ng-${crypto.randomUUID().slice(0, 8)}`,
reconnectPeriod: 2000,
connectTimeout: 10_000,
});
this.client.on('connect', () => this.state$.next('connected'));
this.client.on('reconnect', () => this.state$.next('reconnecting'));
this.client.on('offline', () => this.state$.next('offline'));
this.client.on('error', () => this.state$.next('error'));
this.client.on('message', (topic, payload) => this.incoming$.next({ topic, payload }));
}
observe<T = unknown>(topicFilter: string): Observable<MqttMessage<T>> {
return new Observable<MqttMessage<T>>((subscriber) => {
this.retain(topicFilter);
const sub = this.incoming$
.pipe(
filter((m) => matches(topicFilter, m.topic)),
map((m) => {
const text = new TextDecoder().decode(m.payload);
let data: T;
try { data = JSON.parse(text) as T; } catch { data = text as T; }
return { topic: m.topic, data };
}),
)
.subscribe(subscriber);
return () => {
sub.unsubscribe();
this.release(topicFilter);
};
});
}
async publish(topic: string, message: string, retain = false): Promise<void> {
await this.client?.publishAsync(topic, message, { qos: 1, retain });
}
ngOnDestroy(): void {
this.client?.end(true);
}
private retain(topicFilter: string) {
const n = this.refCounts.get(topicFilter) ?? 0;
if (n === 0) this.client?.subscribe(topicFilter, { qos: 1 });
this.refCounts.set(topicFilter, n + 1);
}
private release(topicFilter: string) {
const n = (this.refCounts.get(topicFilter) ?? 1) - 1;
if (n === 0) {
this.refCounts.delete(topicFilter);
this.client?.unsubscribe(topicFilter);
} else {
this.refCounts.set(topicFilter, n);
}
}
}Tasarım kararları:
- Uygulama başına bir client.
providedIn: 'root'servisi singleton yapar; böylece tüm bileşenler tek bir WebSocket'i paylaşır. - Soğuk, referans sayımlı observable'lar. Bir bileşen abone olana kadar broker'da hiçbir şeye abone olunmaz; son bileşen ayrıldığında topic aboneliği iptal edilir. MQTT.js, yeniden bağlandıktan sonra aktif topic'lere kendiliğinden tekrar abone olur.
- SSR için güvenli. Sunucuda servis
idlekalır; böylece Angular SSR veya prerendering asla soket açmaz. - Wildcard'lar client tarafında eşleştirilir. Birden fazla filtre eşleşse bile broker her mesajı bir kez gönderir;
matches()onu doğru gözlemcilere yönlendirir. Filtrelerinizi topic matcher ile deneyin.
Signal'li bir bileşen
import { Component, inject } from '@angular/core';
import { DecimalPipe } from '@angular/common';
import { toSignal } from '@angular/core/rxjs-interop';
import { auditTime, map, scan } from 'rxjs';
import { MqttClientService } from './mqtt-client.service';
interface Reading { t: number; h?: number }
@Component({
selector: 'app-sensors',
imports: [DecimalPipe],
template: `
<p>Broker: {{ state() }}</p>
<table>
@for (row of rows(); track row.device) {
<tr><td>{{ row.device }}</td><td>{{ row.t | number: '1.1-1' }} °C</td></tr>
}
</table>
<button [disabled]="state() !== 'connected'" (click)="ping()">Ping</button>
`,
})
export class SensorsComponent {
private readonly mqtt = inject(MqttClientService);
readonly state = toSignal(this.mqtt.state$, { requireSync: true });
readonly rows = toSignal(
this.mqtt.observe<Reading>('testmqtt/angular/sensors/+').pipe(
scan((acc, m) => ({ ...acc, [m.topic.split('/').pop()!]: m.data.t }), {} as Record<string, number>),
auditTime(100),
map((latest) => Object.entries(latest).map(([device, t]) => ({ device, t }))),
),
{ initialValue: [] },
);
ping() {
this.mqtt.publish('testmqtt/angular/cmd/ping', String(Date.now()));
}
}toSignal() bileşenin injection context'inde abone olur ve bileşen yok edildiğinde aboneliği otomatik olarak bitirir: ne ngOnDestroy ne takeUntil gerekir. scan cihaz başına son değeri tutar; auditTime(100) ise mesajlar ne kadar hızlı gelirse gelsin arayüz güncellemelerini saniyede ona sınırlar ve yoğun topic'lerde change detection'ı ucuz tutar. (Angular 19'dan önceki sürümlerde decorator'a standalone: true ekleyin.)
Template'lerde observable tercih ediyorsanız observe(), async pipe ile veya bir serviste takeUntilDestroyed() ile de aynı şekilde çalışır.
Test
Elle uçtan uca kontrol
ng serveçalıştırın ve bileşeninBroker: connectedgösterdiğini doğrulayın.errorgösteriyorsa tarayıcı konsolunu açın:Connection refused: Not authorizedkimlik bilgilerine veya ACL'lere; başarısız bir WebSocket handshake'i ise URL'ye,8084portuna veya/mqttpath'ine işaret eder.- Online MQTT client'ı açın, aynı broker'a bağlanın ve
testmqtt/angular/sensors/room-1topic'ine{"t": 23.4}publish edin. Satır 100 ms içinde görünür. testmqtt/angular/sensors/room-2topic'ine publish edin ve iki satırın da göründüğünü kontrol edin.- Online client'ta
testmqtt/angular/#topic'ine abone olun ve publish işlemini doğrulamak için Ping'e tıklayın. - Bileşenden başka bir route'a geçin ve debugger'da veya broker logunda topic aboneliğinin iptal edildiğini doğrulayın.
Angular uygulamanıza test ölçümleri publish edin
Unit test'ler
Bileşenlerin bir broker'a ihtiyacı olmamalıdır. Servisi bir Subject ile desteklenen bir stub ile değiştirin:
import { TestBed } from '@angular/core/testing';
import { BehaviorSubject, Subject } from 'rxjs';
import { MqttClientService, MqttMessage } from './mqtt-client.service';
import { SensorsComponent } from './sensors.component';
it('renders the latest reading per device', async () => {
const messages = new Subject<MqttMessage<{ t: number }>>();
await TestBed.configureTestingModule({
imports: [SensorsComponent],
providers: [{
provide: MqttClientService,
useValue: { state$: new BehaviorSubject('connected'), observe: () => messages, publish: async () => {} },
}],
}).compileComponents();
const fixture = TestBed.createComponent(SensorsComponent);
fixture.detectChanges();
messages.next({ topic: 'testmqtt/angular/sensors/room-1', data: { t: 21 } });
await new Promise((r) => setTimeout(r, 150)); // auditTime'ı bekle
fixture.detectChanges();
expect(fixture.nativeElement.textContent).toContain('room-1');
});Servisin kendisini, matches() ve yeniden bağlanma davranışı dahil, küçük bir entegrasyon test paketinde gerçek bir broker'a karşı test edin. Jest ile test rehberi Angular test runner'larına da uygulanabilecek kalıpları gösterir; TypeScript ile MQTT ise payload'ları uçtan uca tiplemeyi anlatır.
Sorun giderme
| Belirti | Neden | Çözüm |
|---|---|---|
npm i ngx-mqtt peer dependency çakışmasıyla başarısız oluyor | Kütüphane henüz Angular sürümünüz için güncellenmemiş | Seçenek 2'deki MQTT.js servisini kullanın |
| SSR build'i takılıyor veya zaman aşımına uğruyor | Client sunucuda bağlanıyor | isPlatformBrowser veya afterNextRender() ile koruyun |
| Yoğun topic'lerde arayüz yavaşlıyor | Her mesajda change detection | auditTime veya throttleTime ile sınırlayın |
| Sekmeler birbirinin bağlantısını koparıyor | Her yerde aynı clientId | Her sekme için rastgele bir client ID üretin |
Diğer hata mesajları için MQTT.js hataları yazısına bakın.
Angular ve MQTT doğal olarak uyum sağlar, çünkü ikisi de akışlarla düşünür: bağlantının sahibi tek bir singleton servis, bileşenler observable veya signal tüketir ve temizlik bileşen ortadan kalktığında yapılır. Angular sürümünüzü destekliyorsa ngx-mqtt ile başlayın, daha fazla kontrole ihtiyaç duyduğunuzda MQTT.js servisine geçin ve yayına almadan önce her topic'i ikinci bir client ile doğrulayın.
Sıkça sorulan sorular
Angular'da ngx-mqtt mi yoksa doğrudan MQTT.js mi kullanmalıyım?
ngx-mqtt en hızlı başlangıçtır: tek bir forRoot() çağrısı ve RxJS observable döndüren bir observe() metodu. MQTT.js etrafında küçük bir servis ise en güncel MQTT.js'i, MQTT 5 seçeneklerini ve tam kontrolü verir; Angular sürümlerine ayak uyduran bir wrapper'a bağımlı kalmazsınız.
Angular bileşenlerinde MQTT observable'larından unsubscribe olmam gerekir mi?
Evet, ya da bunu sizin yerinize yapan bir yöntem kullanın. toSignal(), async pipe ve takeUntilDestroyed() bileşen yok edildiğinde aboneliği bitirir. Referans sayımlı bir observe() metoduyla, kimse dinlemediğinde topic aboneliği broker'da da iptal edilir.
Uygulamam MQTT kullanınca Angular SSR neden takılıyor veya hata veriyor?
MQTT client'ı sunucuda bir WebSocket açmaya çalışır ve render'ın bitmesini engeller. Yalnızca tarayıcıda bağlanın: bağlanmadan önce isPlatformBrowser(PLATFORM_ID) kontrol edin ya da bağlantıyı afterNextRender() içinde başlatın.



