روش های اشتراک داده بین کامپوننت های انگولار | راهنمای کاربردی


نوشتن کامپوننتهای کوچک در اغلب موارد یک روش خوب برای مدیریت کد در انگولار (Angular) محسوب میشود. زمانی که شروع به نوشتن کامپوننتهای کوچک میکنید، باید روشهایی برای اشتراک داده بین کامپوننت های انگولار در نظر بگیرید. پیش از آن که به بررسی روشهای این اشتراک داده بین کامپوننتها بپردازیم، باید رابطه بین کامپوننتها را تعریف کنیم.
پنج روش برای اشتراک داده بین کامپوننتهای انگولار وجود دارد:
- از کامپوننت والد به فرزند
- از کامپوننت فرزند به والد
- اشتراک داده بین کامپوننتهای همنیا
- اشتراک داده با استفاده از مشخصه ViewChild
- اشتراک داده بین کامپوننتهای نامرتبط
در ادامه به بررسی و توضیح هر یک از این روشها خواهیم پرداخت.
از کامپوننت والد به فرزند
زمانی که ()Input@ را در کامپوننت فرزند تعریف کنید، مقدار مورد نظر را از کامپوننت مستر یا والد دریافت میکند.
پیش از اشتراک داده بین کامپوننتها ابتدا باید مدل دادههای خود را با استفاده از اینترفیسها تعریف کنیم. در کد زیر اینترفیس Product را با فیلدهای الزامی productID و productName برخی فیلدهای اختیاری تعریف کردهایم:
1export interface Product {
2 productID: number;
3 productName:string;
4 productRating?:number;
5 productComments?:string;
6
7}
اکنون اینترفیس Product را در کامپوننت والد خود ایمپورت میکنیم و سپس قالب (Tempate) را میسازیم. این قالب شامل یک شناسه محصول و یک نام محصول است. سپس نام محصول را با کامپوننت فرزند به اشتراک میگذاریم. زمانی که کامپوننت فرزند، نام محصول را بهروزرسانی کند، باید دادهها را به دوباره به کامپوننت والد بازگشت دهیم. بنابراین کد کامپوننت والد به صورت زیر است:
1import { Component, Output,EventEmitter,OnInit, ViewChild, ViewEncapsulation } from '@angular/core';
2import {Product} from './product';
3
4@Component({
5 selector: 'my-app',
6 template:`
7 <h1>Master Components</h1>
8<div class="row">
9 <div class="col-md-12"> </div>
10</div>
11<div class="row">
12 <div class="col-md-2">
13 <label class="cus-form-label">Product ID</label>
14 <input class="form-control minimal" id="pid"
15 [(ngModel)]="product.productID">
16 </div>
17 <div class="col-md-2">
18 <label class="cus-form-label">Product Name</label>
19 <input class="form-control minimal" id="name"
20 [(ngModel)]="product.productName">
21 </div>
22</div>
23<div class="row">
24 <div class="col-md-12"> </div>
25</div>
26<app-child [childToMaster]=product.productName (childToParent)="childToParent($event)">
27
28</app-child>
29 `,
30 styleUrls: [ './app.component.css' ]
31})
32export class AppComponent implements OnInit {
33 product:Product = {} as Product;
34
35
36 ngOnInit() {
37 this.product.productID= 21;
38 this.product.productName ="Netflix";
39 }
40
41 childToParent(name){
42 this.product.productName=name;
43 }
44
45}
کد کامپوننت فرزند نیز چنین است:
1import { Component, Input, Output,EventEmitter } from '@angular/core';
2import {Product} from './product';
3
4@Component({
5 selector: 'app-child',
6 template:`<h1>Child component</h1>
7<div class="row">
8 <div class="col-md-12"> </div>
9</div>
10<div class="row">
11 <div class="col-md-2">
12 <label class="cus-form-label">Name</label>
13 <input class="form-control minimal" id="name"
14 [(ngModel)]="masterName">
15 </div>
16 </div>
17 <div class="row">
18 <div class="col-md-12"> </div>
19 </div>
20 <div>{{masterName}}</div>
21 <div class="row">
22 <div class="col-md-12"> </div>
23 </div>
24 <button id="buttonids" class="float-right btn btn-primary" (click)="sendToParent(masterName)">
25 Send back to Parent component
26 </button>`,
27})
28export class AppChildComponent {
29 //getting value from parent to child
30 @Input('childToMaster') masterName: string;
31
32 @Output() childToParent = new EventEmitter<String>();
33
34
35
36 sendToParent(name){
37 //alert(name);
38 this.childToParent.emit(name);
39 }
40}
اینک دکوراتور زیر را در کامپوننت فرزند تعریف میکنیم. childToMaster نام مشخصهای است که میخواهیم از کامپوننت والد به اشتراک بگذاریم. دکوراتور Input به وضوح مشخص میسازد که کامپوننت فرزند انتظار مقدار را از کامپوننتهای والد دارد.
1@Input(‘childToMaster’) masterName: string;
در کامپوننت والد، مشخصه childToMaster را تعیین خواهیم کرد.
1<app-child [childToMaster]=product.productName (childToParent)=”childToParent($event)”></app-child>
اکنون مشخصه product.productName را با کامپوننت فرزند به اشتراک میگذاریم. زمانی که شروع به نوشتن productName بکنید، به صورت خودکار با کامپوننت فرزند به اشتراک گذاشته میشود.
کامپوننت فرزند به والد
برای اشتراک دادهها از فرزند به والد به دکوراتور Output نیاز داریم. در کامپوننت فرزند باید دکوراتور output را به صورت زیر تعریف کنیم:
1@Output() childToParent = new EventEmitter<String>();
اکنون باید این مشخصه را با کامپوننت والد به اشتراک بگذاریم. بنابراین اشتراک دادهها از کامپوننت فرزند به والد نیازمند ارسال یک رویداد با مقدار به کامپوننت والد است. این رویداد میتواند بر اساس یک نقطه تحریک (Trigger) ارسال شود. در این مثال ما این کار را با استفاده از رویداد کلیک شدن یک دکمه انجام میدهیم. رویداد sendToParent در زمان کلیک شدن دکمه فراخوانی میشود.
1sendToParent(name){
2 this.childToParent.emit(name);
3}
اکنون باید childtoParent را به مشخصه تگ child در کامپوننت والد مربوط سازیم.
1<app-child [childToMaster]=product.productName (childToParent)=”childToParent($event)”></app-chil
زمانی که این مقدار به دست کامپوننت والد برسد، آن را تعیین میکنیم:
1childToParent(name){
2 this.product.productName=name;
3}
اینک میتوانیم تغییر یافتن نام محصول را در کامپوننت والد ببینیم.
اشتراک داده بین کامپوننتهای همنیا
اشتراک داده بین همنیاها با استفاده از دو رویکرد که قبلاً توضیح دادیم، انجام میشود. یعنی ابتدا دادهها به وسیله دکوراتور Output از فرزند به سمت والد به اشتراک گذاشته میشوند و سپس از آن والد با استفاده از دکوراتور Input به همنیای دیگر ارسال میشوند. از این رو همنیاها میتوانند از طریق کامپوننتهای والد با همدیگر صحبت کنند.
اشتراک داده با استفاده از دکوراتور ViewChild
دکوراتور ViewChild به کامپوننت فرزند امکان میدهد که درون کامپوننت والد تزریق شود. از این رو، این دکوراتور قدرت زیادی دارد.
کامپوننتهای والد با استفاده از دکوراتور ViewChild میتوانند متدها و مشخصههای فرزند را کنترل کنند. بین ترتیب والد میتواند پس از رویداد view init به مشخصهها دسترسی داشته باشد. این بدان معنی است که باید یک قلاب چرخه عمری ngAfterViewInit را پیادهسازی کنیم تا بتوانیم مشخصهها را در کامپوننتهای والد دریافت کنیم.
1@ViewChild(AppChildComponent) child;
2constructor() { }
3ngAfterViewInit() {
4 this.product.productName=child.masterName; //<= This will set data
5}
در کد فوق ViewChild در کامپوننت فرزند تعریف شده است. ViewChild موجب میشود که در کامپوننت والد یک ارجاع به کامپوننت فرزند داشته باشیم. همچنین مقدار masterName فرزند را در productName والد تعیین میکند.
اشتراک دادهها بین کامپوننتهای نامرتبط
زمانی که هیچ ارتباطی بین کامپوننتها وجود نداشته باشد، نمیتوانیم دادهها را با استفاده از چهار روش که در بخش فوق اشاره کردیم به اشتراک بگذاریم. این موضوع زمانی اتفاق میافتد که کامپوننتها در ماژولهای متفاوتی باشند. سناریوهای دیگری وجود دارند که شما لیستی از محصولها دارید و روی یک محصول خاص کلیک میکنید و سپس به کامپوننتهای جزییات محصول هدایت میشوید. در این نوع سناریوها باید از سرویسهای داده برای اشتراک دادهها بین کامپوننتها استفاده کنید.

1import { Injectable } from '@angular/core';
2import { Subject, BehaviorSubject, Observable } from 'rxjs';
3import {Product} from './product';
4
5@Injectable()
6export class SharedDataService {
7 constructor(){}
8 //Using any
9 public editDataDetails: any = [];
10 public productData:Product={} as Product;
11 private messageSource = new BehaviorSubject(this.editDataDetails);
12 currentMessage = this.messageSource.asObservable();
13
14 changeMessage(message: string) {
15 this.messageSource.next(message)
16 }
17
18 //Using Interfaces
19 private productRecord: BehaviorSubject<Product> = new BehaviorSubject<Product>(this.productData);
20
21 public getProductRecord(): Observable<Product> {
22 return this.productRecord.asObservable();
23 }
24
25 public setProductRecord(product: Product): void {
26 this.productRecord.next(this.productData);
27 }
28
29}
برای ایجاد سرویس داده باید BehaviorSubject را تعریف کنیم. مقدار کنونی و آخرین مقدار را ذخیره میکند. به دلایل زیر همواره بهتر است از BehaviorSubject استفاده کنیم:
- به صورت خودکار آخرین مقدار را هر زمان که ثبت شود بهروزرسانی میکند.
- در زمان فراخوانی متد ()getValue همواره آخرین مقدار را بازگشت میدهد.
نیازی به فراخوانی بعدی نیست و کافی است متد set و get را برای دریافت مقدار مورد استفاده قرار دهیم.
در سرویس دادهها یک messageSource به عنوان BehaviorSubject ایجاد کردهایم. این messageSource یک مقدار editDataDetails با نوع any میپذیرد. امکان ساخت editDataDetails به عنوان نوع اینترفیس محصول نیز وجود دارد که رویه مناسبی تصور میشود. متد changeMessage نیز مقدار observable-ها را تعیین میکند.
1export class SharedDataService {
2 constructor(){}
3 //Using any
4 public editDataDetails: any = [];
5 public subject = new Subject<any>();
6 private messageSource = new BehaviorSubject(this.editDataDetails);
7 currentMessage = this.messageSource.asObservable();
8 changeMessage(message: string) {
9 this.messageSource.next(message)
10 }
11}
اکنون باید این سرویس را با کامپوننتها یعنی جایی که میخواهیم مقادیر را دریافت و تنظیم کنیم، به اشتراک بگذاریم. متد changeMessage را با مقادیر به عنوان پارامتر فراخوانی کنید تا مقدار مورد نظر تعیین شود. در کامپوننتی که میخواهید مقدار دریافت شود، آن را ثبت نام (subscribe) کنید. زمانی که ثبت نام کردید، همواره آخرین مقدار را در زمان هر گونه تغییر در کد دریافت میکنید.
1//Set value in component 1
2this.sharedDataService.changeMessage("message here");
3//Get value in component 2
4selectedMessage:any;
5ngOnInit() {
6 this.sharedDataService.currentMessage.subscribe(message => (this.selectedMessage= message)); //<= Always get current value!
7}
ایجاد متدهای Get و Set مشخصه و BehaviourSubject
در مثال قبل میتوانیم با استفاده از اینترفیس Product و ساخت متدهای getter و setter با متد زیر، عملکرد بهتری را شاهد باشیم.
1import { Injectable } from '@angular/core';
2import { Subject, BehaviorSubject, Observable,ReplaySubject } from 'rxjs';
3import {Product} from './product';
4@Injectable()
5export class SharedDataService {
6constructor(){}
7//Using Interfaces
8public productData:Product={} as Product;
9private productRecord: BehaviorSubject<Product> = new BehaviorSubject<Product>(this.productData);
10public getProductRecord(): Observable<Product> {
11return this.productRecord.asObservable();
12}
13public setProductRecord(product: Product): void {
14this.productRecord.next(this.productData);
15}
16}
اکنون از کامپوننتی که میخواهیم دادهها را تنظیم کنیم، عملیات set را اجرا میکنیم. با ثبت نام در متد get میتوانیم آخرین مقدار دادههای محصول را دریافت کنیم.
1//Set value in component 1
2var product:Product = {productID:234, productName:"Amazon"}
3this.sharedDataService.setProductRecord(product);
4//Get value in component 2
5selectedProduct: Product;
6ngOnInit() {
7 this.sharedDataService.getProductRecord().subscribe(p=> (this.selectedProduct= p)); //<= Always get current value!
8}
استفاده از ReplaySubject
در بسیاری از سناریوها به subject-ها برای یادآوری مقادیر قدیمی نیاز داریم. برای نمونه در زمان رتبهدهی به محصول باید دادهها را به موتور تحلیلی بدهیم تا بررسی کند سه محصول انتخابی آخر کاربر کدامها بوده است. برای دستیابی به این حالت میتوانیم ReplaySubjects را به صورت زیر ایجاد کنیم:
1private productRecord: ReplaySubject<Product> = new ReplaySubject<Product>();
ارسال دادهها بین کامپوننتها با استفاده از Angular Route
اگر از ناوبری روتر برای رفتن به کامپوننتهای دیگر استفاده میکنید، میتوانید آن را در متد navigate روتر ارسال کنید:
1//Component 1:
2constructor(private router: Router,private renderer: Renderer) {}
3this.router.navigate([‘product/productdetail’, { ‘productID’: 21}]);
در کد فوق، id محصول را در پارامترهای کوئری در زمان مسیریابی کامپوننت به جزییات محصول تنظیم میکنیم. سپس در کامپوننت جزییات آن id را در سازنده به دست میآوریم.
1//Component 2:
2constructor(private route: ActivatedRoute,private router: Router) {
3 route.params.subscribe(params => {
4 let data = params[‘productID’]
5 console.log(`Log the param data ${data}`);
6 });
7}
چنان که میبینید در کد فوق، در کامپوننت دوم، productID را به دست آوردهایم. از آنجا که این دادهها همواره از طریق URL ارسال میشوند، در معرض دید کاربر نهایی قرار دارند. بنابراین نباید اقدام به ارسال اطلاعات حساس بکنید. با این حال، همواره میتوانید از این روش برای ارسال فیلدهای کوچک و دادههای عادی استفاده کنید.
سخن پایانی
در این مقاله 5 روش برای اشتراک داده بین کامپوننتهای انگولار معرفی کردیم.
این که از کدام یک از این روشها استفاده میکنید، به رابطه بین کامپوننتها بستگی دارد. استفاده از هر یک از این رویکردها برخی مزیتها و معایب نسبت به انواع دیگر دارد.
به نظرم خیلی خوب توضیح داده شده. ممنون