| آلية الجلب الشبكي (`Mechanism`) | عدد الطلبات المرسلة للخادم | تأثيرها على سرعة وأداء التطبيق |
|---|---|---|
| بدون TransferState | يُرسل طلب شبكي على الخادم ثم يُعاد إرسال نفس الطلب على المتصفح مجدداً | هدر في موارد الشبكة وتكرار غير مبرر للاتصالات |
| باستخدام TransferState | يُرسل الطلب على الخادم وتُنقل النتائج للمتصفح مباشرة دون طلب جديد | فوري، فائق السرعة، ويوفر استهلاك البيانات بشكل جذري |
عند تشغيل التطبيق بنظام العرض على الخادم (SSR)، قد يقوم الخادم بإرسال طلب شبكي لجلب البيانات ورسم الصفحة، ولكن عند انتقال الصفحة إلى المتصفح (Hydration)، قد يعيد التطبيق إرسال نفس الطلب الشبكي مرة أخرى بلا داعٍ. يتيح لك أنجولر حل هذه المشكلة الجوهرية بذكاء مذهل عبر TransferState API التي تقم بنفض وجلب البيانات من الخادم وتمريرها جاهزة للمتصفح دون إعادة الاتصال بالخادم.
يتم تعريف مفتاح تخزين فريد باستخدام makeStateKey لحفظ واسترجاع البيانات المنقولة بين البيئتين.
عندما يبدأ المتصفح في استلام الصفحة، يكتشف وجود البيانات المخزنة مسبقاً في حالة النقل فيستغلها فوراً بدلاً من إعادة جلبها.
| 1 | import { Injectable, PLATFORM_ID, inject } from '@angular/core'; |
| 2 | import { TransferState, makeStateKey } from '@angular/core'; |
| 3 | import { HttpClient } from '@angular/common/http'; |
| 4 | import { isPlatformServer } from '@angular/common'; |
| 5 | import { Observable, of, tap } from 'rxjs'; |
| 6 | |
| 7 | const USERS_KEY = makeStateKey<any[]>('cached_users_data'); |
| 8 | |
| 9 | @Injectable({ |
| 10 | providedIn: 'root' |
| 11 | }) |
| 12 | export class DataTransferService { |
| 13 | private http = inject(HttpClient); |
| 14 | private transferState = inject(TransferState); |
| 15 | private platformId = inject(PLATFORM_ID); |
| 16 | |
| 17 | getUsers(): Observable<any[]> { |
| 18 | // 1. التحقق مما إذا كانت البيانات مخزنة مسبقاً في حالة النقل |
| 19 | if (this.transferState.hasKey(USERS_KEY)) { |
| 20 | const cachedData = this.transferState.get(USERS_KEY, []); |
| 21 | this.transferState.remove(USERS_KEY); // إزالة المفتاح بعد الاستخدام الأول |
| 22 | console.log('[TransferState] تم استرجاع البيانات المنقولة من الخادم للمتصفح بنجاح دون طلب جديد.'); |
| 23 | return of(cachedData); |
| 24 | } |
| 25 | |
| 26 | // 2. إذا لم تكن موجودة، يتم جلبها عبر الشبكة |
| 27 | return this.http.get<any[]>('https://jsonplaceholder.typicode.com/users').pipe( |
| 28 | tap(data => { |
| 29 | // إذا كنا على الخادم، نقوم بتخزين البيانات في نقل الحالة لتمريرها للمتصفح |
| 30 | if (isPlatformServer(this.platformId)) { |
| 31 | this.transferState.set(USERS_KEY, data); |
| 32 | console.log('[TransferState] تم حفظ البيانات على الخادم لتصديرها للمتصفح.'); |
| 33 | } |
| 34 | }) |
| 35 | ); |
| 36 | } |
| 37 | } |
| 1 | import { Component, OnInit, inject } from '@angular/core'; |
| 2 | import { CommonModule } from '@angular/common'; |
| 3 | import { DataTransferService } from './data-transfer.service'; |
| 4 | |
| 5 | @Component({ |
| 6 | selector: 'app-users-display', |
| 7 | standalone: true, |
| 8 | imports: [CommonModule], |
| 9 | template: ` |
| 10 | export const x = 1; |
| 11 | <div class="card"> |
| 12 | <h3>قائمة المستخدمين المستلمة عبر TransferState</h3> |
| 13 | <ul> |
| 14 | <li *ngFor="let user of users">{{ user.name }}</li> |
| 15 | </ul> |
| 16 | </div> |
| 17 | `, |
| 18 | styles: [` |
| 19 | .card { background: #090d16; color: #fff; padding: 24px; border-radius: 8px; border: 1px solid #1e293b; } |
| 20 | ul { color: #94a3b8; } |
| 21 | li { color: #38bdf8; margin: 4px 0; } |
| 22 | `] |
| 23 | }) |
| 24 | export class UsersDisplayComponent implements OnInit { |
| 25 | private dataService = inject(DataTransferService); |
| 26 | users: any[] = []; |
| 27 | |
| 28 | ngOnInit(): void { |
| 29 | this.dataService.getUsers().subscribe(data => { |
| 30 | this.users = data; |
| 31 | }); |
| 32 | } |
| 33 | } |
⚠️ تحذير
تذكر أن نسيان إزالة أو إدارة مفتاح الحالة قد يتسبب في استهلاك ذاكرة غير ضروري أو بقاء بيانات قديمة! احرص دائماً على تصميم مفاتيح الحالة بذكاء وإدارتها عبر الخدمات المركزية.