در پروژه‌های واقعی Angular، کامپوننت‌ها به‌صورت جداگانه کار نمی‌کنند؛ آن‌ها یک ساختار درختی می‌سازند و دائماً باید با یکدیگر ارتباط داشته باشند. در این جلسه یاد می‌گیریم چگونه داده را از والد به فرزند با @Input ارسال کنیم، چگونه فرزند با @Output به والد پیام بدهد، و در نهایت چطور با @ViewChild مستقیماً به عناصر DOM یا متدهای یک کامپوننت دیگر دسترسی پیدا کنیم. این سه ابزار پایه‌ای‌ترین و مهم‌ترین روش‌های ارتباط میان کامپوننت‌ها در Angular 17 هستند و در تمام بخش‌های بعدی پروژه‌ها کاربرد دارند.

در این جلسه می‌خواهیم مهم‌ترین بخش معماری Angular را یاد بگیریم:
ارتباط بین کامپوننت‌ها.

در Angular سه مدل ارتباط اصلی داریم:

  1. ارتباط Parent → Child (با @Input)
  2. ارتباط Child → Parent (با @Output)
  3. دسترسی مستقیم به یک کامپوننت یا DOM (با @ViewChild)

اگر این سه ابزار را خوب یاد بگیرید، می‌توانید پروژه‌های واقعی و پیشرفته بسازید.

چرا ارتباط کامپوننت‌ها مهم است؟

هر پروژه Angular به صورت درختی از کامپوننت‌ها ساخته می‌شود.
کامپوننت‌ها باید بتوانند به هم داده بدهند، رویداد بفرستند یا حتی یکدیگر را کنترل کنند.

برای همین Angular سه ابزار در اختیار ما گذاشته:

  • @Input() برای دریافت داده
  • @Output() برای ارسال رویداد
  • @ViewChild() برای دسترسی مستقیم

حالا جلوتر می‌رویم.

──────────────────────────

۱) ارتباط Parent → Child با @Input

تعریف ساده:

@Input یعنی:

«این مقدار را از بیرون (والد) دریافت کن»

مثال:

child.component.ts

@Component({
  selector: 'app-child',
  standalone: true,
  template: `<p>Message: {{ message }}</p>`
})
export class ChildComponent {
  @Input() message!: string;
}

parent.component.html

<app-child [message]="'Hello from Parent!'"></app-child>

در اینجا، Parent مقدار "Hello from Parent!" را به Child تزریق می‌کند.

──────────────────────────

قوانین مهم @Input

  • اسم ورودی باید با براکت [ ] ست شود.
  • هنگام تغییر مقدار، Angular داده را دوباره به Child می‌دهد.
  • مقدار Input همیشه یک‌طرفه است (Child نمی‌تواند آن را عوض کند)

──────────────────────────

۲) ارتباط Child → Parent با @Output

وقتی می‌خواهیم Child به Parent خبر بدهد (Event بفرستد) از Output استفاده می‌کنیم.

تعریف ساده:

@Output یعنی:

«من یک رویداد می‌فرستم، Parent می‌تواند گوش بدهد»

مثال:

child.component.ts

@Component({
  selector: 'app-child',
  standalone: true,
  template: `
    <button (click)="send()">Send Data</button>
  `
})
export class ChildComponent {
  @Output() messageEvent = new EventEmitter<string>();

  send() {
    this.messageEvent.emit('Message from Child');
  }
}

parent.component.html

<app-child (messageEvent)="handleMessage($event)"></app-child>

<p>Received: {{ receivedMessage }}</p>

parent.component.ts

receivedMessage = '';

handleMessage(data: string) {
  this.receivedMessage = data;
}

مقایسه سریع Input و Output

ابزارجهت ارتباطتوضیح
InputParent → Childارسال داده
OutputChild → Parentارسال رویداد/پیام

۳) دسترسی به DOM و کامپوننت‌ها با @ViewChild

@ViewChild زمانی کاربرد دارد که:

  • می‌خواهیم مستقیماً به یک عنصر DOM دسترسی داشته باشیم
  • یا می‌خواهیم متدهای یک کامپوننت دیگر را صدا بزنیم
  • یا می‌خواهیم مقدار Input/Output را از Parent کنترل کنیم

۳.۱ مثال: گرفتن یک دکمه از DOM

home.component.ts

@Component({
  selector: 'app-home',
  standalone: true,
  template: `
    <button #myBtn>Click Me</button>
  `
})
export class HomeComponent {
  @ViewChild('myBtn') button!: ElementRef<HTMLButtonElement>;

  ngAfterViewInit() {
    this.button.nativeElement.style.background = 'red';
  }
}

ElementRef دسترسی مستقیم به DOM می‌دهد
✔ این کار فقط باید در ngAfterViewInit انجام شود

──────────────────────────

۳.۲ مثال: دسترسی به یک کامپوننت دیگر

گاهی لازم می‌شود به متدی از Child دسترسی پیدا کنیم.

child.component.ts

@Component({
  selector: 'app-child',
  standalone: true,
  template: `Child ready!`
})
export class ChildComponent {
  sayHello() {
    console.log('Hello from child!');
  }
}

parent.component.ts

@Component({
  selector: 'app-parent',
  standalone: true,
  imports: [ChildComponent],
  template: `
    <app-child></app-child>
    <button (click)="callChild()">Call Child</button>
  `
})
export class ParentComponent {

  @ViewChild(ChildComponent) child!: ChildComponent;

  callChild() {
    this.child.sayHello();
  }
}

✔ Parent می‌تواند مستقیم متدهای Child را اجرا کند
✔ بدون Input/Output
✔ بسیار کاربردی در فرم‌ها، کامپوننت‌های UI و کنترل‌های پیچیده

──────────────────────────

خطاهای رایج ViewChild

۱. مقدار undefined در ngOnInit

چون ViewChild قبل از ngAfterViewInit مقداردهی نمی‌شود.

راه‌حل:

از ngAfterViewInit استفاده کنید.


۲. گرفتن چندین ViewChild

@ViewChildren('item') items!: QueryList<ElementRef>;

۳. خطای ExpressionChangedAfterItHasBeenChecked

اگر مقدار ViewChild را در هنگام Change Detection تغییر دهید.

✔ راه‌حل: استفاده از setTimeout() یا ChangeDetectorRef.detectChanges()

──────────────────────────

نکات مهم و Best Practices

  • از ViewChild فقط وقتی استفاده کنید که Input/Output جواب نمی‌دهد
  • دسترسی به DOM را به حداقل برسانید (بهتر است با Renderer2 باشد)
  • Input همیشه One-way binding است
  • Output همیشه Event-based است
  • ViewChild مناسب دسترسی کامپوننت ← کاملاً استاندارد
  • ViewChild برای دسترسی به DOM ← تا حد امکان کم

──────────────────────────

جمع‌بندی

در این جلسه یاد گرفتیم:

  • ارتباط Parent → Child با @Input
  • ارتباط Child → Parent با @Output
  • ارسال داده، رویداد، پیام بین کامپوننت‌ها
  • استفاده از EventEmitter
  • دسترسی مستقیم به DOM با ElementRef
  • دسترسی مستقیم به کامپوننت‌ها با ViewChild
  • نکات مهم در ngAfterViewInit
  • Best Practices ارتباط کامپوننت‌ها

این جلسه یکی از مهم‌ترین جلسات Angular است و پایه جلسات آینده (Forms، Services، RxJS) محسوب می‌شود.

دسترسی به ویدیوهای کامل دوره

برای مشاهده ویدیوهای کامل هر جلسه، دریافت تمرین‌ها، فایل‌ها و پشتیبانی، می‌توانید دوره کامل را تهیه کنید.