اگر به دنبال یادگیری آموزش Angular هستید، درک مفاهیم State Management و RxJS در Angular یکی از مهمترین مراحل در تسلط بر این فریمورک قدرتمند است. Angular با ابزارهایی مانند RxJS و NgRx امکان مدیریت وضعیت (State) و جریان داده را در برنامههای پیچیده فراهم میکند. این مقاله به شما کمک میکند تا با این مفاهیم از سطح مبتدی تا پیشرفته آشنا شوید و بتوانید آنها را در پروژههای واقعی پیادهسازی کنید.
RxJS: مبنای مدیریت جریان داده
1. مفهوم Observables و Operators
RxJS یک کتابخانهی قدرتمند در Angular است که ابزارهای جامعی برای مدیریت جریانهای داده (Streams) فراهم میکند. این جریانها میتوانند شامل رویدادهای کاربر، دادههای سرور، یا حتی دادههای پویا درون برنامه باشند. RxJS از طریق Observables و Operators این امکان را فراهم میکند که این دادهها را بهصورت مؤثر پردازش، ترکیب و فیلتر کنید.
Observables
Observable هستهی اصلی RxJS است. یک Observable یک منبع داده است که مقادیر را در طول زمان منتشر میکند و مشترکین (Subscribers) میتوانند این مقادیر را دریافت کنند.
ویژگیهای اصلی Observables:
غیرهمگام و پویا: دادهها میتوانند بهصورت آنی، با تأخیر یا مکرراً ارسال شوند.
کنترلپذیری بالا: میتوانید جریان داده را شروع، متوقف و حتی دوباره شروع کنید.
تعامل آسان با دیگر بخشهای برنامه: Observables به راحتی با دیگر ساختارها مثل Promises و توابع Callbacks ترکیب میشوند.
مثال ساده ایجاد یک Observable:
import { Observable } from 'rxjs';
const myObservable = new Observable(observer => {
observer.next('Hello'); // ارسال داده به مشترک
observer.next('World');
observer.complete(); // پایان جریان
});
myObservable.subscribe({
next: value => console.log(value), // مدیریت داده
complete: () => console.log('Done') // پایان مشترک
});
خروجی:
Hello World Done
Operators
Operators ابزارهایی هستند که جریانهای داده را پردازش میکنند. RxJS دارای بیش از 100 Operator برای کاربردهای مختلف است.
انواع Operators:
عملگرهای تبدیلی (Transformation):
map: مقادیر را در جریان تغییر میدهد.
scan: مشابه reduce در آرایهها.
عملگرهای فیلترینگ (Filtering):
filter: جریان را بر اساس شرطی خاص فیلتر میکند.
take: فقط تعداد مشخصی از مقادیر را میگیرد.
عملگرهای ترکیبی (Combination):
merge: چند جریان را ترکیب میکند.
concat: جریانها را به ترتیب ترکیب میکند.
عملگرهای زمانی (Time-based):
debounceTime: کنترل فرکانس انتشار مقادیر.
delay: تأخیر در انتشار مقادیر.
مثال عملی Observables و Operators
در مثال زیر، اعداد زوج از یک جریان انتخاب شده و سپس هر عدد 10 برابر میشود:
import { of } from 'rxjs';
import { filter, map } from 'rxjs/operators';
const numbers$ = of(1, 2, 3, 4, 5); // جریان اعداد
numbers$
.pipe(
filter(value => value % 2 === 0), // فقط مقادیر زوج
map(value => value * 10) // هر مقدار را 10 برابر کن
)
.subscribe(result => console.log(result)); // خروجی: 20, 40
2. مدیریت دادههای استریمشده
یکی از چالشهای بزرگ در برنامهنویسی مدرن، مدیریت دادههایی است که بهصورت غیرهمگام و پویا تولید میشوند. RxJS با استفاده از Observables و Operators این فرآیند را ساده و قابلکنترل میکند.
کاربردهای عملی مدیریت دادههای استریمشده
ورودی کاربر: در برنامههایی که نیازمند مدیریت رویدادهای مکرر کاربر (مثل تایپ کردن یا کلیک کردن) هستند، RxJS ابزارهای قدرتمندی ارائه میدهد.
مثال: مدیریت تایپ در کادر جستجو در این مثال، دادههای واردشده توسط کاربر تنها پس از متوقف شدن تایپ به مدت 300 میلیثانیه پردازش میشوند:
import { fromEvent } from 'rxjs';
import { debounceTime, map } from 'rxjs/operators';
// انتخاب کادر جستجو
const searchBox = document.getElementById('search-box');
const input$ = fromEvent(searchBox, 'input');
input$
.pipe(
debounceTime(300), // انتظار 300 میلیثانیه
map(event => event.target.value) // استخراج مقدار وارد شده
)
.subscribe(value => console.log(value)); // نمایش مقدار وارد شده
ویژگیهای کد:
fromEvent: یک Observable از رویداد DOM میسازد.
debounceTime: مقادیر را کنترل کرده و تنها پس از گذشت زمان مشخص، مقدار جدید را منتشر میکند.
map: مقدار ورودی (value) را از رویداد استخراج میکند.
ترکیب جریانهای داده: در برخی سناریوها، ممکن است بخواهید دادههای چندین منبع را با هم ترکیب کنید. برای مثال، رویدادهای ورودی کاربر را با پاسخ سرور ترکیب کنید.
مثال: ترکیب جریانها
import { of, combineLatest } from 'rxjs';
const userInput$ = of('User Input');
const serverResponse$ = of('Server Response');
combineLatest([userInput$, serverResponse$])
.subscribe(([input, response]) => console.log(`${input} - ${response}`));
// خروجی: User Input - Server Response
RxJS یکی از ابزارهای کلیدی در Angular است که مدیریت جریان دادهها و رویدادهای پویا را آسان میکند. با استفاده از Observables برای تعریف جریانهای داده و Operators برای پردازش آنها، میتوانید برنامههای پیشرفته، کارآمد و واکنشگرا بسازید. برای ادامه مسیر یادگیری، توصیه میشود مستندات رسمی RxJS و منابع آموزشی آنلاین را بررسی کنید.
State Management با NgRx: مفهوم Store، Actions و Reducers
NgRx یکی از محبوبترین کتابخانهها برای مدیریت وضعیت (State Management) در Angular است که بر اساس معماری Redux توسعه داده شده است. NgRx به شما امکان میدهد تا وضعیت برنامه را بهصورت سراسری مدیریت کنید و از قابلیتهای قدرتمند جریان دادهها (Streams) بهره ببرید. در این بخش به تفصیل سه مفهوم اصلی NgRx یعنی Store، Actions و Reducers را بررسی میکنیم.
1.Store
Store مرکزی برای ذخیره و مدیریت وضعیت (State) برنامه است. این وضعیت شامل تمامی دادهها و مقادیر مرتبط با برنامه میشود که در یک مکان متمرکز نگهداری میشوند.
ویژگیهای Store:
منبع واحد حقیقت: تمام وضعیت برنامه در Store نگهداری میشود، بنابراین نیاز به مدیریت وضعیت در چندین مکان مختلف از بین میرود.
دسترسی سراسری: کامپوننتهای مختلف برنامه میتوانند وضعیت را از Store دریافت کرده و تغییرات موردنیاز را اعمال کنند.
غیرقابل تغییر (Immutable): وضعیت در Store بهصورت غیرقابل تغییر است. هر تغییر منجر به ایجاد نسخهی جدیدی از وضعیت میشود.
مثال:
تصور کنید که برنامه شما دارای یک State برای تعداد کلیکهای یک دکمه است:
export interface AppState {
counter: number;
}
export const initialState: AppState = {
counter: 0,
};
این ساختار وضعیت در Store نگهداری خواهد شد.
2. Actions
Actions رویدادهایی هستند که قصد دارند وضعیت را تغییر دهند. این رویدادها به Store اعلام میکنند که چه تغییری باید اعمال شود.
ویژگیهای Actions:
ساختار ساده: یک Action معمولاً شامل یک نوع (type) و بار اطلاعاتی (payload) است.
رویدادمحور: Actions به Store ارسال میشوند تا فرآیند تغییر وضعیت آغاز شود.
توضیحدهنده تغییرات: Actions مشخص میکنند که چه اتفاقی افتاده است و چه تغییری باید اعمال شود.
مثال:
تعریف Actions برای افزایش و کاهش مقدار شمارنده:
import { createAction } from '@ngrx/store';
export const increment = createAction('[Counter] Increment');
export const decrement = createAction('[Counter] Decrement');
export const reset = createAction('[Counter] Reset');
در اینجا، [Counter] یک دستهبندی است که نشان میدهد این Action مربوط به چه بخشی از برنامه است.
3. Reducers
Reducers توابعی هستند که وظیفه دارند وضعیت جدید را بر اساس Action دریافتی ایجاد کنند. این توابع وضعیت فعلی و Action را دریافت کرده و وضعیت جدید را بازمیگردانند.
ویژگیهای Reducers:
توابع خالص (Pure Functions): Reducers هیچ وابستگی به متغیرهای خارجی ندارند و نتیجهی یکسانی را برای ورودیهای یکسان تولید میکنند.
غیرقابل تغییر بودن وضعیت: Reducers وضعیت موجود را تغییر نمیدهند؛ بلکه نسخهی جدیدی از وضعیت را بازمیگردانند.
مثال:
تعریف Reducer برای شمارنده:
import { createReducer, on } from '@ngrx/store';
import { increment, decrement, reset } from './actions';
import { AppState, initialState } from './state';
export const counterReducer = createReducer(
initialState,
on(increment, state => ({ counter: state.counter + 1 })), // افزایش مقدار
on(decrement, state => ({ counter: state.counter - 1 })), // کاهش مقدار
on(reset, state => ({ counter: 0 })) // تنظیم مقدار به صفر
);
نحوه تعامل این اجزا با هم
برای درک بهتر، روند کار NgRx را بهصورت گامبهگام توضیح میدهیم:
ایجاد Action: وقتی کاربر روی دکمهی افزایش کلیک میکند، یک Action مانند increment تولید میشود.
Dispatch Action: این Action به Store ارسال میشود.
Reducer فراخوانی میشود: Store Action را به Reducer مرتبط میفرستد.
ایجاد وضعیت جدید: Reducer وضعیت جدید را ایجاد و به Store برمیگرداند.
بهروزرسانی UI: کامپوننتهایی که به وضعیت Store متصل هستند، تغییرات را دریافت کرده و بهروزرسانی میشوند.
مثال کاربردی
گام 1: تعریف State و Store
export interface AppState {
counter: number;
}
export const initialState: AppState = {
counter: 0,
};
گام 2: تعریف Actions
import { createAction } from '@ngrx/store';
export const increment = createAction('[Counter] Increment');
export const decrement = createAction('[Counter] Decrement');
export const reset = createAction('[Counter] Reset');
گام 3: تعریف Reducer
import { createReducer, on } from '@ngrx/store';
import { increment, decrement, reset } from './actions';
import { AppState, initialState } from './state';
export const counterReducer = createReducer(
initialState,
on(increment, state => ({ counter: state.counter + 1 })),
on(decrement, state => ({ counter: state.counter - 1 })),
on(reset, state => ({ counter: 0 }))
);
گام 4: ثبت Store در ماژول
import { NgModule } from '@angular/core';
import { StoreModule } from '@ngrx/store';
import { counterReducer } from './reducer';
@NgModule({
declarations: [],
imports: [
StoreModule.forRoot({ counter: counterReducer })
],
bootstrap: []
})
export class AppModule { }
گام 5: استفاده از Store در کامپوننت
import { Component } from '@angular/core';
import { Store } from '@ngrx/store';
import { increment, decrement, reset } from './actions';
@Component({
selector: 'app-counter',
template: `
<h1>{{ counter$ | async }}</h1>
<button (click)="increment()">افزایش</button>
<button (click)="decrement()">کاهش</button>
<button (click)="reset()">تنظیم مجدد</button>
`
})
export class CounterComponent {
counter$ = this.store.select(state => state.counter);
constructor(private store: Store<{ counter: number }>) {}
increment() {
this.store.dispatch(increment());
}
decrement() {
this.store.dispatch(decrement());
}
reset() {
this.store.dispatch(reset());
}
}
استفاده از NgRx برای مدیریت State
NgRx ابزاری بسیار مفید برای مدیریت وضعیت (State) در برنامههای Angular است. این کتابخانه با استفاده از اصول معماری Redux، جریان یکطرفه دادهها را فراهم کرده و مدیریت تغییرات در برنامه را سادهتر و قابل پیشبینیتر میکند. در این بخش، مراحل عملی استفاده از NgRx برای مدیریت State با جزئیات بیشتری توضیح داده میشود.
1. نصب NgRx
برای استفاده از NgRx، ابتدا باید کتابخانهی موردنیاز را نصب کنید. این کار با دستور زیر انجام میشود:
npm install @ngrx/store
2. تعریف State
State نشاندهندهی وضعیت جاری برنامه است. برای مثال، اگر برنامهی شما شامل یک شمارنده باشد، وضعیت ممکن است فقط یک عدد باشد که مقدار شمارنده را نشان میدهد.
تعریف State:
// state.ts
export interface AppState {
counter: number; // وضعیت شامل مقدار شمارنده است
}
export const initialState: AppState = {
counter: 0, // مقدار اولیه شمارنده
};
3. تعریف Actions
Actions رویدادهایی هستند که قصد تغییر وضعیت را دارند. هر Action باید نوع (type) و در صورت نیاز بار اطلاعاتی (payload) مشخصی داشته باشد.
تعریف Actions:
// actions.ts
import { createAction } from '@ngrx/store';
// Action برای افزایش مقدار شمارنده
export const increment = createAction('[Counter] Increment');
// Action برای کاهش مقدار شمارنده
export const decrement = createAction('[Counter] Decrement');
4. تعریف Reducer
Reducers توابعی هستند که وضعیت (State) را بر اساس Action تغییر میدهند. این توابع وضعیت فعلی و Action را بهعنوان ورودی دریافت کرده و وضعیت جدیدی تولید میکنند.
تعریف Reducer:
// reducer.ts
import { createReducer, on } from '@ngrx/store';
import { increment, decrement } from './actions';
import { AppState, initialState } from './state';
export const counterReducer = createReducer(
initialState,
on(increment, state => ({ counter: state.counter + 1 })), // افزایش مقدار
on(decrement, state => ({ counter: state.counter - 1 })) // کاهش مقدار
);
5. ثبت Store در ماژول اصلی برنامه
پس از تعریف State، Actions و Reducer، باید Store را در ماژول اصلی Angular ثبت کنید. این کار باعث میشود Store در تمامی بخشهای برنامه قابل دسترسی باشد.
ثبت Store:
// app.module.ts
import { NgModule } from '@angular/core';
import { StoreModule } from '@ngrx/store';
import { counterReducer } from './reducer';
@NgModule({
declarations: [],
imports: [
StoreModule.forRoot({ counter: counterReducer }) // ثبت Reducer در Store
],
bootstrap: []
})
export class AppModule { }
6. استفاده از Store در کامپوننتها
در کامپوننتها میتوانید به Store دسترسی داشته باشید و با استفاده از آن وضعیت را بخوانید یا تغییر دهید.
تعریف کامپوننت:
// counter.component.ts
import { Component } from '@angular/core';
import { Store } from '@ngrx/store';
import { AppState } from './state';
import { increment, decrement } from './actions';
@Component({
selector: 'app-counter',
template: `
<h1>{{ counter$ | async }}</h1> <!-- نمایش مقدار شمارنده -->
<button (click)="increment()">افزایش</button> <!-- دکمه افزایش -->
<button (click)="decrement()">کاهش</button> <!-- دکمه کاهش -->
`
})
export class CounterComponent {
counter$ = this.store.select(state => state.counter); // انتخاب مقدار شمارنده از Store
constructor(private store: Store<AppState>) {}
increment() {
this.store.dispatch(increment()); // ارسال Action افزایش
}
decrement() {
this.store.dispatch(decrement()); // ارسال Action کاهش
}
}
چرا از NgRx استفاده کنیم؟
سازماندهی بهتر: NgRx وضعیت را بهصورت متمرکز مدیریت میکند و از پراکندگی کد جلوگیری میکند.
پیشبینیپذیری تغییرات: با استفاده از Actions و Reducers، تمامی تغییرات وضعیت برنامه قابل پیشبینی و ردیابی هستند.
قابلیت اشکالزدایی بالا: میتوانید جریان Actions را مشاهده کرده و تغییرات وضعیت را مرحلهبهمرحله بررسی کنید.
مقیاسپذیری: برای پروژههای بزرگ، NgRx یک راهحل عالی برای مدیریت وضعیت است.
چند نکته عملی:
اشتراک وضعیت (State Sharing): اگر چندین کامپوننت به یک وضعیت وابسته باشند، NgRx به اشتراکگذاری وضعیت بین آنها کمک میکند.
const counter$ = this.store.select(state => state.counter);
پردازش دادهها: اگر نیاز دارید دادهها قبل از نمایش پردازش شوند، میتوانید از RxJS Operators استفاده کنید.
const doubledCounter$ = this.store.select(state => state.counter).pipe( map(counter => counter * 2) // دو برابر کردن مقدار شمارنده );
استفاده از NgRx برای مدیریت State در Angular، برنامههای شما را ساختارمندتر و قابل پیشبینیتر میکند. این کتابخانه با استفاده از مفاهیمی مثل Store، Actions و Reducers فرآیند مدیریت وضعیت را ساده کرده و به توسعهدهندگان امکان ساخت برنامههای بزرگ و پیچیده را میدهد.
نتیجهگیری
در این مقاله، به بررسی جامع مفاهیم State Management و RxJS در Angular پرداختیم و نشان دادیم که چگونه ابزارهای قدرتمند RxJS و NgRx میتوانند مدیریت وضعیت و جریان دادهها را در برنامههای Angular ساده و موثر کنند. RxJS با استفاده از Observables و Operators امکان مدیریت جریانهای پویا و غیرهمگام را فراهم میکند، درحالیکه NgRx با استفاده از Store، Actions و Reducers ساختاری قابل پیشبینی و مقیاسپذیر برای مدیریت وضعیت برنامه ارائه میدهد.
تسلط بر State Management و RxJS در Angular به شما این امکان را میدهد که برنامههای پیچیده را بهسادگی مدیریت کنید و تجربهای بهینه و روان برای کاربران ایجاد کنید. اگرچه این مفاهیم در ابتدا ممکن است پیچیده به نظر برسند، با مطالعه و تمرین مداوم میتوانید از قدرت و انعطافپذیری آنها در پروژههای خود بهرهمند شوید. برای یادگیری بیشتر، منابع معرفیشده در این مقاله میتوانند راهنمایی ارزشمند باشند.
