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

شاید برای شما هم پیش اومده باشه که تو پروژه انگولاری خودتون لازم باشه که یکسری داده بین دو تا کامپوننت اشتراک بزارید ( یا همون پاس بدید به  یک کامپوننت دیگه) برای اینکه بتونید اینکار رو انجام بدید 4 تا روش وجود داره (حداقل) که با هم این چهار روش رو یادمیگیرم

هر دو کامپوننت میتونن دو نوع رابطه با هم داشته باشند ،

1- رابطه پدر و فرزند

2- رابطه هم سطح (یا همون هیچ رابطه ای)

1- پدر به فرزند با استفاده از Input :

دکوراتو @Input به شما اجازه میده که یکسری داده رو از کامپوننت والد به کامپوننت فرزند پاس بدید

این روش متداول ترین روشی که برامون اتفاق می افته ، فرض کنید یک مقدار دارید که میخواهید اون رو در یک textbox در کامپوننت فرزند نمایش بدید ، برای اینکار باید به شیوه زیر عمل کنید:

کامپوننت والد:

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

@Component({
  selector: 'app-parent',
  template: `
    <app-child [childMessage]="مقداری کی میخواهیم پاس بدیم"></app-child>
  `,
  styleUrls: ['./parent.component.css']
})
export class ParentComponent{
  parentMessage = "message from parent"
  constructor() { }
}
خب همونطور که تو خط پنجم قطعه کد بالا مشاهده می کنید ما تو کامپوننت والدمون ()، از کامپوننت فرزند () استفاده کردیم، چیزی که تو استفاده از کامپوننت فرزند باید بهش دقت کنید اون property هستش که در کامپوننت فزند نوشته شده بنام
childMessage​

در ادامه کامپوننت فرزند رو با قطعه کد زیر داریم:

کامپوننت فرزند:
import { Component, Input } from '@angular/core';

@Component({
  selector: 'app-child',
  template: `
      Say {{ message }}
  `,
  styleUrls: ['./child.component.css']
})
export class ChildComponent {

  @Input() childMessage: string;

  constructor() { }

}
 
خب در این بخش هم اگر توجه کنید با توسط دکوراتور @Input یک متغیر تعریف کردیم
 
 
  @Input() childMessage: string;
 خب حالا با اجرای پروژه ما هر مقداری رو در ChildMessage در هنگام استفاده از کامپوننت فرزند بزاریم ، به اون مقدار تو خود کامپوننت دسترسی داریم
 
2- فزرند به پدر با استفاده از ViewChild
ViewChild به شما این اجازه رو میده تا یک کامپوننت رو Inject کنید توی یک کامپوننت دیگه تا با این روش بتونید به متغیرهاش و همچنین متدهاش دسترسی داشته باشید
فقط باید توجه داشته باشید که برای Implement کردن کامپوننت والد حتما باید از هوک AfterViewInit  توی کامپوننت والد استفاده کنید تا بتونید به فرزند دسترسی داشته باشید
 
کامپوننت والد
import { Component, ViewChild, AfterViewInit } from '@angular/core';
import { ChildComponent } from "../child/child.component";

@Component({
  selector: 'app-parent',
  template: `
    Message: {{ message }}
    <app-child></app-child>
  `,
  styleUrls: ['./parent.component.css']
})
export class ParentComponent implements AfterViewInit {

  @ViewChild(ChildComponent) child;

  constructor() { }

  message:string;

  ngAfterViewInit() {
    this.message = this.child.message
  }
}
حتما دقت داشته باشید که باید برای Implement کردن کامپوننت والد مثل زیر عمل کنید
export class ParentComponent implements AfterViewInit {
و به طبع این موضوع
ngAfterViewInit() {
    this.message = this.child.message
  }​

کامپوننت فرزند

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

@Component({
  selector: 'app-child',
  template: `
  `,
  styleUrls: ['./child.component.css']
})
export class ChildComponent {

  message = 'مقدار موردنظر';

  constructor() { }

}
 
3- فرزند به والد با استفاده از @OutPut و EventEmitter
 
کامپوننت والد
import { Component } from '@angular/core';

@Component({
  selector: 'app-parent',
  template: `
    Message: {{message}}
    <app-child (messageEvent)="receiveMessage($event)"></app-child>
  `,
  styleUrls: ['./parent.component.css']
})
export class ParentComponent {

  constructor() { }

  message:string;

  receiveMessage($event) {
    this.message = $event
  }
}
کامپوننت فرزند
mport { Component, Output, EventEmitter } from '@angular/core';

@Component({
  selector: 'app-child',
  template: `
      <button (click)="sendMessage()">Send Message</button>
  `,
  styleUrls: ['./child.component.css']
})
export class ChildComponent {

  message: string = "سلام دنیا"

  @Output() messageEvent = new EventEmitter<string>();

  constructor() { }

  sendMessage() {
    this.messageEvent.emit(this.message)
  }
}
خب ، شیوه کار در این بخش شاید یکمی پیچیده باشه ، ابتدا از کامپوننت فرزند شروع میکنیم:
 
خب اول توی TEMPLATE  کامپوننتمون یک دکمه قرار دادیم که با فشردن اون دکمه یک متد از کامپ.ننت فرزند اجرا میشه
<button (click)="sendMessage()">Send Message</button>
وقتی روی دکمه کلیک کنیم انگولار متد sendMessage() موجود تو کامپوننت فرزند رو اجرا میکنه :
sendMessage() {
    this.messageEvent.emit(this.message)
  }

خب تا اینجا که مشکلی نیست ، اصل قضیه از ایمجا به بعد شروع میشه،

ما یک متغیر هم از نوع string تو کامپوننت فرزند تعریف کردیم و بهش مقدار "سلام دنیا" ی معروف رو دادیم
بعد اومدیم با استفاده از دکوراتور @OutPut و یک Event Emitter یک متغیر تعریف کردیم
  @Output() messageEvent = new EventEmitter<string>();

خب وقتی که متد sendMessage() اجرا میشه مقدار موجود در متغیر message رو در messageEvent قرار میده و اون رو emit میکنه

خب از اینجا به بعد کار ما میریم سراغ کامپوننت والد . تو template کامپوننت واد هم ما اومدیم و کامپوننت فرزند رو استفاده کردیم ، و گفتیم که هروقت messageEvent توی کامپوننت فرزند emit شد (یعنی همون دکمه ی تو کامپوننت فرزند کلیک شد) ،
<app-child (messageEvent)="receiveMessage($event)"></app-child>

مقدار موجود در messageEvent رو بگیر و متد receiveMessage رو صدا بزن و مقدار رو بهش پاس بده. اینطوری مقدار موجود در message کامپوننت فرزند به message موجود در کامپوننت والد ارسال میشه.

4- اشتراک بین دو کامپوننت هم سطح:
خب اگر کامپوننتهای ما هیچ رابطه پدر و فرزندی نداشتند ما میتونیم از یک سرویس مشترک بین این دو استفاده کنیم .
به این شکل که یک سرویس که در هر دو کامپوننت استفاده میشه (اگر هیچ سرویس مشترکی نداشتند ، مجبوریم یک سرویس مشترک بین این دو کامپوننت ایجاد کنیم) رو برای کارمون انتخاب میکنیم.
نکته: منظور از سرویس مشترک ، سرویسی است که در هردو کامپوننت تزریق شده باشد
نکته2: برای راحتی کار خودتون بهتره یک سرویس مخصوص اینکار داشته باشید و از سرویسهای دیگه استفاده نکنید
 
خب شیوه کار به این صورت هست که ابتدا ما یک سرویس ایجاد میکنیم و این سرویس رو تو هر دو کامپوننت هم سطحمون inject میکنیم.
 
سرویس مشترک
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';

@Injectable()
export class DataService {

  private messageSource = new BehaviorSubject('default message');
  currentMessage = this.messageSource.asObservable();

  constructor() { }

  changeMessage(message: string) {
    this.messageSource.next(message)
  }

}

کامپوننت ول:

import { Component, OnInit } from '@angular/core';
import { DataService } from "../data.service";

@Component({
  selector: 'app-parent',
  template: `
    {{message}}
  `,
  styleUrls: ['./sibling.component.css']
})
export class ParentComponent implements OnInit {

  message:string;

  constructor(private data: DataService) { }

  ngOnInit() {
    this.data.currentMessage.subscribe(message => this.message = message)
  }

}

در کامپوننت اول با دستور زیر مقدار موجود در سرویس رو میخونیم

this.data.currentMessage.subscribe(message => this.message = message)

 

کامپوننت دوم:

import { Component, OnInit } from '@angular/core';
import { DataService } from "../data.service";

@Component({
  selector: 'app-sibling',
  template: `
    {{message}}
    <button (click)="newMessage()">New Message</button>
  `,
  styleUrls: ['./sibling.component.css']
})
export class SiblingComponent implements OnInit {

  message:string;

  constructor(private data: DataService) { }

  ngOnInit() {
    this.data.currentMessage.subscribe(message => this.message = message)
  }

  newMessage() {
    this.data.changeMessage("Hello from Sibling")
  }

}
در کامپوننت دوم یکبار در موقع init شدن کامپوننت مقدار رو از سرویس مشترک میخوانیم
ngOnInit() {
    this.data.currentMessage.subscribe(message => this.message = message)
  }

خب از اینجا به بعد هرموقع که متد newMessage() موجود در کامپوننت دوم اجرا بشه ، مقدار موجود در متغیر message ارسال میشه به متد curretMessage موجود در سرویس مشترکمون و مقادیر میره تو سرویس مشترکمون قرار میگیره  و چون از Observable تو سرویس مون استفاده کردیم بلافاصله مقدار ارسال شده به سرویس در کامپوننت اول هم دریافت میشه.