יישומים מודרניים גדלים לעתים קרובות להכיל עשרות או אפילו מאות רכיבים, שירותים ומודולים.כפי שהמורכבות עולה, כך גם האתגר של ניהול תקשורת בין חלקים שונים של היישום. Direct Parent-childFLT:0 / FLT 1 מחייבs להיות unwieldy, ושירותים המחזיקים במדינה משותפת יכולים להפוך במהירות למורכב.

מאמר זה מספק צלילה עמוקה ליצירת אוטובוס אירועים חד-ממדי ב- Angular באמצעות RxJS. אנו נכסה את הבסיס התיאורטי של דפוס ה-oneton, פרטי יישום שלב אחר שלב, תרחישים של שימוש בעולם האמיתי, ושיקולים מתקדמים כגון בטיחות מסוג, ניהול זיכרון ובדיקה.אם אתה בונה פרויקט חדש או מארגן בסיס קוד מורשת, דפוס זה יהיה לך להעצים יישומים, נקי יותר.

הבנת תבנית ה- Singleton ב-Angular

דפוס הטון הוא אחד דפוסי העיצוב הנפוצים ביותר בהנדסה תוכנה.כוונות הליבה שלה היא להגביל את הרגעה של מחלקה לאובייקט אחד בדיוק ולספק נקודת גישה גלובלית של אובייקט זה. in Angular, Singletons הם בדרך כלל מיושמים באמצעות שירותים הניתנים ברמת השורש.

כיצד Anngular Enforces Singletons

מערכת ההזרקה התלות של אנגולרי (DI) היא היררכית.כאשר שירות הוכרז עם FLT:2 ב-FLT 3 עיצוב, Angular מבטיח כי אותו מקרה של השירות משותף בכל היישום.

import { Injectable } from '@angular/core';

@Injectable({
 providedIn: 'root'
})
export class MySingletonService {
 // This instance is shared application-wide
}

שימוש ב-FLT:5 לא רק מבטיח מקרה אחד, אלא גם תומך בשקיקה עץ, אם השירות לעולם לא מוזרק, ניתן להסירו מהחבילה הסופית, צמצום טביעת הרגל של היישום.

למה לבודד אוטובוס?

אוטובוס אירועים הוא בעצם מרכז הודעה: רכיבים פולטים אירועים, ורכיבים אחרים או שירותים להירשם לאירועים אלה.עבור מודל תקשורת זה לעבוד באופן אמין, כל המשתתפים חייבים להתייחס לאותו מקרה אוטובוס אירוע. A Singleton מבטיח זאת.אם כל רכיב יוצר אוטובוס אירועים פרטי משלו, אירועים יהיו מבודדים ותקשורת ישבור.

עיצוב אוטובוס אירועים מבוקש

הבסיס של אוטובוס האירוע שלנו הוא RxJS, ספרייה רבת עוצמה לתכנות תגובתיות. Angular עצמו מבוסס במידה רבה על RxJS, מה שהופך אותו לבחירה טבעית עבור בניית שירותים מונעים על ידי אירועים.

בחירת הסוג הנכון

RxJS מספק מספר סוגים של נושאים:

  • (ב) ⁇ 0 ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇
  • (ב) ⁇ :0(BehaviorSubjectFLT:1) דורש ערך ראשוני ומחזר את הערך האחרון למנויים חדשים.
  • (ב) [15] ,ReplaySubjectFLT:1 - Replays a configurable number of Past to late מנויים.
  • (ב) ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇

עבור אוטובוס אירוע קלאסי (אירועים חד-פעמיים שלא צריך להיות replayed), מנוף פשוט (FLT 7) הוא הבחירה המתאימה ביותר.שימוש ב-FLT:8 היה אומר כי האירוע הוא מצבן, אשר בדרך כלל אינו המקרה עבור אירועי יישום כגון "משתמש רשום" או "נתונים מרעננות" או "מענן" אם אתה צריך מנויים מאוחר לקבל את האירוע האחרון, LTF:9 עם גודל של 1 גנר" אך הוא יכול להיחשב פחות נפוץ.

יצירת אוטובוס מסוג-Safe Event

כדי למנוע שגיאות בזמן ריצה, מומלץ להגדיר את הצורה של אירועים שהאוטובוס יכול לפלט.You יכול ליצור ממשק מפת אירועים המאגד שמות אירועים אפשריים וסוגי שכר קשורים.

export interface AppEvent {
 name: string;
 data: unknown;
}

export interface EventMap {
 'user:login': { userId: string; token: string };
 'user:logout': void;
 'data:updated': { entity: string; id: string };
 'notification:new': { message: string; type: 'success' | 'error' };
}

באמצעות סוגים ממופים, אתה יכול ליצור אוטובוס אירועים בטוח מסוג זה המאפשר רק פולטים ו subscribing לאירועים ידועים.

import { Injectable } from '@angular/core';
import { Subject, Observable } from 'rxjs';
import { filter, map } from 'rxjs/operators';
import { EventMap } from './event-map';

@Injectable({
 providedIn: 'root'
})
export class TypedEventBusService {
 private subject = new Subject<{ name: keyof EventMap; data: EventMap[keyof EventMap] }>();

 emit<K extends keyof EventMap>(eventName: K, data: EventMap[K]): void {
 this.subject.next({ name: eventName, data });
 }

 on<K extends keyof EventMap>(eventName: K): Observable<EventMap[K]> {
 return this.subject.asObservable().pipe(
 filter((event) => event.name === eventName),
 map((event) => event.data as EventMap[K])
 );
 }
}

גישה זו מספקת בטיחות מבעוד מועד: אינכם יכולים לגרור אירוע עם סוג המטען הלא נכון, ומנויים מקבלים נתונים מתואמים כראוי.זה מפחית באופן דרמטי את הסבירות של שגיאות במשרה חלקית שנגרמו על ידי מבנים לא תואמים.

שירות אוטובוסים יחידטון

עם התיאוריה במקום, בואו נבנה שירות אוטובוסים מעשי, גמיש, אירוע.

יישום בסיסי

התחל עם שירות פשוט המשתמש גנרית 12:12 גרסה זו היא לא מטיפוס אבל קל להבין.

import { Injectable } from '@angular/core';
import { Subject, Observable } from 'rxjs';
import { filter, map } from 'rxjs/operators';

export interface BusEvent {
 name: string;
 data: any;
}

@Injectable({
 providedIn: 'root'
})
export class EventBusService {
 private bus = new Subject<BusEvent>();

 emit(eventName: string, data?: any): void {
 this.bus.next({ name: eventName, data });
 }

 on(eventName: string): Observable<any> {
 return this.bus.asObservable().pipe(
 filter(event => event.name === eventName),
 map(event => event.data)
 );
 }
}

(הופנה מהדף ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇

תגית: Error Handling

ביישום ייצור, ייתכן שתרצה לטפל שגיאות בחסד, אתה יכול להשתמש ב- 18FLT אשר תומך גם הודעות שגיאה, או שאתה יכול להוסיף נושא נפרד לשגיאות.

private errorSubject = new Subject<Error>();

emitError(error: Error): void {
 this.errorSubject.next(error);
}

למען הפשטות, אנו נמשיך את אוטובוס האירוע המרכזי המתמקד באירועים בלבד, אך ניתן להרחיב אותו כנדרש.

ניהול מנויים

אחת הטעויות הנפוצות ביותר עם מנויים אובססיביים היא לשכוח לא לרשום.אם רכיב מנוי לאוטובוס האירוע אבל נהרס ללא רישום, המנוי נשאר פעיל ועשוי לגרום לדלפות זיכרון או תופעות לוואי לא רצויות.

import { Component, OnInit, OnDestroy } from '@angular/core';
import { Subscription } from 'rxjs';
import { EventBusService } from './event-bus.service';

@Component({
 selector: 'app-example',
 template: `<div>Listening for events...</div>`
})
export class ExampleComponent implements OnInit, OnDestroy {
 private subscription: Subscription;

 constructor(private eventBus: EventBusService) {}

 ngOnInit(): void {
 this.subscription = this.eventBus.on('data:updated').subscribe(data => {
 console.log('Data updated:', data);
 });
 }

 ngOnDestroy(): void {
 if (this.subscription) {
 this.subscription.unsubscribe();
 }
 }
}

ניתן להשתמש גם בדפוסי ה- 22 של ההרחבה כדי לבטל את המנויים באופן אוטומטי כאשר מרכיב נהרס.

שימוש ב- Event Bus ב Components

עכשיו כשיש לנו אוטובוס אירועים חזק, בואו נראה אותו בפעולה עם תרחיש תקשורת מציאותי.

דוגמה: מערכת זיהוי

דמיינו יישום עם רכיב ראש (שם מוצגים הודעות) ורכיב הגדרות שבו המשתמש יכול לעורר הודעה.מרכיבים אלה אינם קשורים בעץ הרכיב, כך שאירועים מסורתיים של FLT:23 לא יעבדו בקלות.

(ב) ⁇ ⁇ ⁇ ⁇ ⁇ ⁇

import { Component } from '@angular/core';
import { EventBusService } from '../services/event-bus.service';

@Component({
 selector: 'app-notification-publisher',
 template: `
 <button (click)="sendNotification()">
 Trigger Notification
 </button>
 `
})
export class NotificationPublisherComponent {
 constructor(private eventBus: EventBusService) {}

 sendNotification(): void {
 this.eventBus.emit('notification:new', {
 message: 'Your settings have been saved.',
 type: 'success'
 });
 }
}

(ב) ⁇ ⁇ ⁇ ⁇ ⁇

import { Component, OnInit, OnDestroy } from '@angular/core';
import { Subscription } from 'rxjs';
import { EventBusService } from '../services/event-bus.service';

@Component({
 selector: 'app-notification-display',
 template: `
 <div *ngIf="lastNotification" class="notification" [ngClass]="lastNotification.type">
 {{ lastNotification.message }}
 </div>
 `
})
export class NotificationDisplayComponent implements OnInit, OnDestroy {
 lastNotification: { message: string; type: string } | null = null;
 private subscription: Subscription;

 constructor(private eventBus: EventBusService) {}

 ngOnInit(): void {
 this.subscription = this.eventBus.on('notification:new').subscribe(data => {
 this.lastNotification = data;
 });
 }

 ngOnDestroy(): void {
 if (this.subscription) {
 this.subscription.unsubscribe();
 }
 }
}

בהגדרה זו, שני המרכיבים לעולם לא לייבא אחד את השני, הם תלויים רק ב-FLT:26 ההפיכה המשוחררת הזו הופכת את הקוד לקל יותר לשמור, לבחון ולייצר מחדש.

שימוש ב- Event Bus בשירותים

שירותים יכולים גם להירשם לאירועים ולהגיב בהתאם, לדוגמה, שירות אימות יכול להקשיב לאירוע של FLT:27 כדי לנקות אסימונים.

import { Injectable, OnDestroy } from '@angular/core';
import { Subscription } from 'rxjs';
import { EventBusService } from './event-bus.service';

@Injectable({ providedIn: 'root' })
export class AuthService implements OnDestroy {
 private subscription: Subscription;

 constructor(private eventBus: EventBusService) {
 this.subscription = this.eventBus.on('user:logout').subscribe(() => {
 this.clearSession();
 });
 }

 clearSession(): void {
 // Clear tokens, navigate to login, etc.
 }

 ngOnDestroy(): void {
 this.subscription?.unsubscribe();
 }
}

שימו לב כי למרות שהשירות הוא סינגלטון, הוא עדיין מיישום את המנויים כאשר היישום נסגר (למשל, במהלך Angular Universal SSR או בבדיקה).

שיקולים מתקדמים

בעוד אוטובוס אירוע חדטון הוא חזק, יש להשתמש בו בחשיבה.כאן הם נושאים מתקדמים שכל מפתח צריך להבין.

ביצועים ודה-קפיצה

אם האירועים מפוטרים בתדר גבוה (למשל, במהלך תנועת עכברים או זרימת נתונים בזמן אמת), לשקול שימוש ב-FLT:30 או FLT:31 מפעילי רכיבים מנויים.

להימנע מזיכרון

כבר הדגישנו את החשיבות של אי-קיום, אך הוא חוזר על עצמו.האוטובוס הבלתי-אפשרי הוא אינסופי: הוא לעולם לא שלם עד שהנושא לא יוחסן באופן ידני או שהיישום נהרס.כל מרכיב ששכח ל unsubscribe יגרום לדלפה זיכרון.ביישומים גדולים, דליפות כאלה יכולות לצבור ולהרוס ביצועים לאורך זמן.

private destroy$ = new Subject<void>();

ngOnInit(): void {
 this.eventBus.on('data:updated')
 .pipe(takeUntil(this.destroy$))
 .subscribe(data => { ... });
}

ngOnDestroy(): void {
 this.destroy$.next();
 this.destroy$.complete();
}

להתווכח ולהתגל

מערכות מונחות אירועים יכולות להיות מאתגרות כי זרימת האירועים אינה גלויה מיד.אפשר להוסיף כניסה לאוטובוס האירוע עצמו.לדוגמה, תוכל להכניס כל אירוע הנפלט במצב פיתוח:

emit(eventName: string, data?: any): void {
 if (environment.production === false) {
 console.debug(`[EventBus] Emitting: "${eventName}"`, data);
 }
 this.bus.next({ name: eventName, data });
}

תוספת פשוטה זו יכולה לחסוך שעות של זמן מבועת.

יחידת מבחן אוטובוס האירוע

בדיקת האוטובוס עצמו היא פשוטה: אתה יכול להירשם לאירוע, פולט אותו, ולבדוק כי המנוי מקבל את הנתונים הנכונים.

import { TestBed } from '@angular/core/testing';
import { EventBusService } from './event-bus.service';

describe('EventBusService', () => {
 let service: EventBusService;

 beforeEach(() => {
 TestBed.configureTestingModule({});
 service = TestBed.inject(EventBusService);
 });

 it('should emit and receive events', (done) => {
 const testData = { foo: 'bar' };
 service.on('test:event').subscribe(data => {
 expect(data).toEqual(testData);
 done();
 });
 service.emit('test:event', testData);
 });

 it('should not deliver events to subscribers of different names', () => {
 const spy = jasmine.createSpy();
 service.on('other:event').subscribe(spy);
 service.emit('test:event', {});
 expect(spy).not.toHaveBeenCalled();
 });
});

כאשר בוחנים רכיבים המשתמשים באוטובוס האירוע, מזריקים מרגל או לעג ל-FLT:36 כדי לוודא כי הרכיב פולט אירועים נכונים או מגיב לאירועים כראוי.

יתרונות ומכשולים פוטנציאליים

יתרונות

  • (ב) ,0) תקשורת מבוססת-ד' (FLT:1) - קומפרסים ושירותים יכולים לתקשר ללא התייחסות ישירה אחד לשני, מה שהופך את בסיס הקוד ליותר מודולרי.
  • מקור האמת:0 (הופנה מהדף אמתFLT:1) – אוטובוס אירועים בוטו מבטיח שכל האירועים זורמים דרך ערוץ אחד, מפשטים את המעבר וההתקוע.
  • (ב) ,0) ,נבדקת זיכרון טביעת רגל 1 (במקום ליצור מספר רב של פולטים, יש לך בדיוק מקרה אחד, אשר מועיל במיוחד ביישומים גדולים.
  • (FLT:0) תגובתי של NatureBuildFLT:1 - Leveraging RxJS מאפשר למפעילים חזקים (Debounce, משלבים את Latest וכו ') כדי להתמודד עם זרימת אירועים מורכבים.
  • (ב) כי האוטובוס האירוע הוא שירות בלתי ניתן להפליג אותו בקלות במבחנים.

מלכודות פוטנציאליות

  • (FLT:0)OveruseveFLT:1) - החל על אוטובוס אירועים עולמי עבור כל חלק של תקשורת יכול להפוך את הנתונים לזרום קשה יותר כדי לעקוב, המוביל "אירועים ספוגים" להשתמש בו בעיקר עבור חששות חתכים (לאנופים, מדינה, הגדרות גלובליות) ולא לתקשורת הורים-ילד שגרתית.
  • (ב) ללא בטיחות מסוג ללא ציות טירפיטלף 1 (אם אתה מדלג על הגישה המדומה, אתה מאבד את הבטיחות של זמן ייצור ולהגדיל את הסיכון לתאונות דרכים.
  • (ב) ויקרא י"ד: "ה' אלקים" (בראשית כ"ד, כד) "וַיָּבוּ" (שם כ"ד, כ"ד).
  • (ב) ,0) ,הרחבה בפרשת אירועים פרימבייט (Terphalph) 1:1 – שלא כמו שיחה ישירה של שיטה, מקור האירוע אינו ברור מהאוטובוס עצמו.
  • (ב) ,0) אירועים מרתקים (FLT:1) - אירועים שגורמים לאירועים אחרים יכולים ליצור לולאות אינסופיות אם לא נועדו בקפידה.

אפשרויות ל-oneton Event Bus

בעוד אוטובוס אירוע הטון הוא כלי רב עוצמה, אין זו הדרך היחידה לנהל תקשורת בין-משותף באנגולרי.

  • (FLT:0) ניהול המדינה Libraries (NgRx, Akita, NGXS)teauFLT:1 - אלה מספקים דפוס מובנה, דמוי Redux לניהול מדינת יישום.הם בזבזניים עבור פרויקטים קטנים אבל מצוין עבור יישומים גדולים, מורכבים.
  • שירות משותף עם התנהגות של SubjectFIRLT:1 (במקום אוטובוס אירוע כללי, אתה יכול ליצור שירותים ייעודיים לחשוף מדינה מסוימת כמו observables.זה יותר בטוח סוג ומונע את "המערב הפרוע" של שמות אירועים שרירותיים.
  • (ב) (ב) ,0) תקשורת-לבנטית (בקיצור: ⁇ ) , (ב) , (ב[[1924]]) ,[[1924]]]], ו[[1924]],]] ו[[1924]],]]
  • (FLT:0) ,הסתה עם שאילתאות או ארצ'ר 1) - ניתן להשיג תקשורת בין-קרב באמצעות נתוני נתיב.

אוטובוס האירוע הבודד מאיר כאשר אתה צריך הפיכה חופשית ללא ספרייה מלאה לניהול המדינה.זה קל, קל ליישם, גמיש - אבל זה דורש משמעת.

מסקנה

יישום דפוס יחידטון עבור אוטובוס אירוע בכל יישום ב Angular הוא טכניקה מוכחת לפשט תקשורת על פני מרכיבים ושירותים מעודפים באופן רופף. על ידי מינוף מערכת ההזרקה התלות של Angular ונהרות החזקות של RxJS, אתה יכול ליצור מרכז מרכזי, בטוח סוג זה הופך את היישום שלך יותר יציב.

כיסו את התיאוריה מאחורי סינגלים, עברנו דרך בניית אוטובוס אירועים חזק עם יישום בסיסי וסוג, הפגינו שימוש חוצה-שותף, ודנו בנושאים מתקדמים כגון פיזור, בדיקות וניהול זיכרון.התחלים העיקריים הם: לספק את השירות ברמת השורש, להשתמש ב-FLT:39 לאירוע תמיד, לא סופר, ולטפל באירועים שלך.

(הופנה מהדף ה-Sunton Event Bus Pattern שבו הוא הגיוני, אך להימנע מלהשתמש בו בשילוב עם הפרקטיקה הטובה ביותר של Angular - כגון מודולריות, צורות תגובתיות, והפרדה חכמה / אם תת-מרכיב - זה יעזור לך לבנות יישומים נקיים יותר, מקצועיים יותר.לקריאה נוספת, להתייעץ עם ה-FLT:0Angular Document on Singleton ServicesLTFLT, The F2xer: