Angular Component’lerinin Haberleşmesi

ismail kaşan
8 min readJun 10, 2024

--

Angular Component’lerinin Haberleşmesi

Angular güçlü bir SPA(single page application) framework’üdür. Çünkü Angular ile geliştirilen web sayfaları her defasında yüklenmeden, Angular’ın sahip olduğu özellikler sayesinde, sayfadaki elementler manipule edilerek arayüzler güncellenir. Bu desteği sağlayan en önemli yapısı component’lerdir. Çünkü component’ler sürekli bir birleri ile haberleşerek sayfayı sürekli güncel tutarlar. İşte bu haberleşme yöntemlerinden en güçlüsü binding işlemidir. Binding işlemlerinin ne olduklarını daha önceki makalede anlatmıştım. Buradan bakabilirsiniz.

Angular uygulamaları genellikle birden fazla component içerir. Bu component’lerin birbiriyle sürekli etkileşim halindedir. Bu etkileşim, veri iletişimi, olayları yönetme veya component’ler arasında bilgi paylaşımını içerebilir. Angular’da component’ler arası iletişim, farklı yöntemler kullanılarak gerçekleştirilir. Bu da uygulamanın performansını, kullanılabilirliğini ve genel etkinliğini büyük ölçüde etkiler. Bu makalede, Angular component’ler arasındaki haberleşme konseptini anlamak için kullanılan farklı teknikleri ve bu tekniklerin nasıl uygulandığını anlatmaya çalışacağım.

Parent’tan Child’a Veri Göndermek.

Daha önceki binding makalesinde az da olsa bu konuya değinmiştim. Angular’da ‘data-bound property’ dediğimiz özel property’ler vardır. Bunlardan en çok kullanılan ve sık sık karşımıza çıkanları ‘@Input’ ve ‘@Output’ dur. Bir parent’ten child’e veri göndermek istediğimizde bu ‘@Input’ ‘data-bound property’sinden faydalanırız. Child component’in içerisinde ‘@Input’ property’si tanımlanır. Parent’in template’inde child component’i çağırırken bu oluşturulan property’e(child component içerisinde tanımlanan ‘@Input property’) bind işlemi ile veri gönderilir. Örnek;

/* child.component.ts */
import { Component, Input } from '@angular/core';

@Component({
selector: 'app-child',
template: `
<p>My master is {{masterName}}.</p>
`
})
export class ChildComponent {
@Input('master') masterName = ''; // data-bound @Input() property
}
/* parent.component.ts */
import { Component } from '@angular/core';

@Component({
selector: 'app-parent',
template: `
<app-child [master]="master"></app-child> //property binding ile veri gönderiliyor.
`
})
export class ParentComponent {
master = 'Ismail';
}

Setter ile ‘@Input()’ Property’sini Kontrol Etmek

set name() ile parent’ten gelen bir veriye bazı özel işlemler uygulanmak istenebilir. Örneğin aşağıdaki örnekte adı ‘name’ olan ‘@Input’ özelliğinin alacağı verideki boşlukları atmak ve boş değeri varsayılan metinle değiştirmek için set name() içerisinde kontroller yapılmıştır.

/* child.component.ts */
import { Component, Input } from '@angular/core';

@Component({
selector: 'app-child',
template: `
<p>{{name}}.</p>
`
})
export class ChildComponent {
// data-bound @Input() property with a setter and a getter
get name(): string { return this._name; }
set name(name: string) {
this._name = (name && name.trim()) || '<no name set>';
}
private _name = '';
}
/* parent.component.ts */
import { Component } from '@angular/core';

@Component({
selector: 'app-parent',
template: ` //property binding ile veri gönderiliyor.
<app-child *ngFor="let name of names" [name]="name"></app-child>`
})
export class ParentComponent {
// Displays 'Dr. IQ', '<no name set>', 'Bombasto'
names = ['Dr. IQ', ' ', ' Bombasto '];
}

ngOnChanges() ile ‘@Input()’ Property’sinin Değişimlerini Takip Etmek

Daha öncede bahsettiğimiz gibi, Angular component’teki her bir ‘data-bound property’ değeri değiştiğinde ‘detect-changes’ algoritması ile değişiklikleri tarar. Etkilenen component’lerin ‘lifecycle-hook’ metodlarından ilgili olanları tetikler. ‘ngOnChanges()’ metodu her bir ‘data-bound property’ değeri değiştiğinde tetiklenen bir metotur. Angular Component Nedir? başlıklı makalede detaylarına bakabilirisiniz. Bu metot sayesinde property’in değişimleri sürekli kontrol edilebilir. Aşağıdaki örnekte parent component’te her butona basıldığında minor ve major değerleri değiştirilerek child component’e gönderilecektir. Child component’teki ‘ngOnChanges(changes: SimpleChanges)’ metodu tetiklenecektir. ‘ngOnChanges(changes: SimpleChanges)’ metodu ‘SimpleChanges’ tipindeki parametre ile bu property’lerin değişimlerini tutmaktadır.

/* version-child.component.ts */

import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';

@Component({
selector: 'app-version-child',
template: `
<h3>Version {{major}}.{{minor}}</h3>
<h4>Change log:</h4>
<ul>
<li *ngFor="let change of changeLog">{{change}}</li>
</ul>
`
})
export class VersionChildComponent implements OnChanges {
@Input() major = 0;
@Input() minor = 0;
changeLog: string[] = [];

ngOnChanges(changes: SimpleChanges) {
const log: string[] = [];
for (const propName in changes) {
const changedProp = changes[propName]; // propperty'ler tek tek kontrol edilir.
const to = JSON.stringify(changedProp.currentValue);
if (changedProp.isFirstChange()) {
log.push(`Initial value of ${propName} set to ${to}`);
} else {
const from = JSON.stringify(changedProp.previousValue);
log.push(`${propName} changed from ${from} to ${to}`);
}
}
this.changeLog.push(log.join(', '));
}
}
/* version-parent.component.ts */

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

@Component({
selector: 'app-version-parent',
template: `
<h2>Source code version</h2>
<button type="button" (click)="newMinor()">New minor version</button>
<button type="button" (click)="newMajor()">New major version</button>
<app-version-child [major]="major" [minor]="minor"></app-version-child>
`
})
export class VersionParentComponent {
major = 1;
minor = 23;

newMinor() {
this.minor++;
}

newMajor() {
this.major++;
this.minor = 0;
}
}

Child’tan Parent’a Veri Göndermek.

Bir child component’ten parent component’e veri göndermek istediğimizde ‘@Output‘data-bound property’ den faydalanırız. Child component’in içerisinde ‘@Output’ property ile bir ‘EventEmitter’ tanımlanır. Parent component template’inde child component’i çağırırken bu oluşturulan property’e(child component içerisinde tanımlanan ‘@Output’ property’) event bind işlemi ile bir metoda bağlanır. Örnek;

/* child.component.ts */
import { Component, Output } from '@angular/core';

@Component({
selector: 'app-child',
template: `
<p>My master is {{masterName}}.</p>
<button type="button" (click)="vote(true)">Agree</button>
`
})
export class ChildComponent {
@Input('master') masterName = ''; // data-bound @Input() property
@Output() voted = new EventEmitter<boolean>(); // data-bound @Output() property

vote(agreed: boolean) {
this.voted.emit(agreed); // emit() ile bir event gönderilir
}
}
/* parent.component.ts */
import { Component } from '@angular/core';

@Component({
selector: 'app-parent',
template: `
<!-- event binding ile event metoda bağlanıyor. -->
<app-child [master]="master" (voted)="onVoted($event)"></app-child >
`
})
export class ParentComponent {
master = 'Ismail';
agreed = 0;
disagreed = 0;

onVoted(agreed: boolean) { // yakalanan event ile işlem yapılıyor.
if (agreed) {
this.agreed++;
} else {
this.disagreed++;
}
}
}

Bir Local Değişken(template-reference) Sayesinde Parent’in Child İle Haberleşmesi

Parent component bir child component’in property’lerini okumak veya metodlarını tetiklemek için ‘data-bound property’lerle haberleşmek zorunda değildir. Kullanılacak senaryoya göre ‘template-reference’ dediğimiz özellik sayesinde bu işlemleri yapabiliyoruz. ‘template-reference’ bir component’in özelliklerine erişmeyi sağlar. Fakat bu erişim sadece template düzeyindedir. Erişmeye çalışan component’in sınıfından oluşturulmuş nesnesi erişemez. Yani HTML template’inde erişilebiliyorken, ts dosyasında erişilemez. Örnek;

/* child.component.ts */
import { Component} from '@angular/core';

@Component({
selector: 'app-child',
template: `
<p>I am a child.</p>
`
})
export class ChildComponent {
start() { conosle.log('start')}
stop() { conosle.log('stop') }
}
/* parent.component.ts */

/* #child ile oluşturulan local variable bütün
parent template'inde erişilebilir olacaktır. Bu template-referance sayesinde
start() ve stop() metotları template üzerinden tetiklenebilecektir. */

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

@Component({
selector: 'app-parent',
template: `
<p>I am Parent.</p>
<button type="button" (click)="child.start()">Start</button>
<button type="button" (click)="chil.stop()">Stop</button>
<app-child #child></app-child >
`
})
export class ParentComponent {
}

Bu ‘template-reference’ ile tamplate üzerinde erişilebiliyorken, sınıf üzerinden erişmek için ne gerekiyor? Hadi ona bakalım!

Parent’in Sınıf Düzeyinde Child İle Haberleşmesi

Bir parent component’in sınıf düzeyinde bir child component’e erişmesi için, child component’in parent component sınıfına injekte edilmesi gerekmektedir. Bunun içinde ‘@ViewChild()’ decorator’ü kullanılır. ‘@ViewChild()’ ile Inject edilen child içi işlemler genelde parent’ta ‘ngAfterViewInit()’ ‘lifecycle-hook’ metodu tetiklendikten sonra yapılır. Çünkü ancak o zaman child component hazır hale gelecektir. Örnek;

/* child.component.ts */
import { Component} from '@angular/core';

@Component({
selector: 'app-child',
template: `
<p>I am a child.</p>
`
})
export class ChildComponent {
start() { conosle.log('start')}
stop() { conosle.log('stop') }
}
/* parent.component.ts */

import { Component, ViewChild } from '@angular/core';
import { ChildComponent } from './child.component';

@Component({
selector: 'app-parent',
template: `
<p>I am Parent.</p>
<button type="button" (click)="start()">Start</button>
<button type="button" (click)="stop()">Stop</button>
<app-child></app-child >
`
})
export class ParentComponent {
@ViewChild(ChildComponent) // child @ViewChild ile inject ediliyor.
private childComponent!: ChildComponent;

start() { this.childComponent.start(); } // childComponent üzerinden tetikleniyor.
stop() { this.childComponent.stop(); }
}

İki Component’in Bir Servis Üzerinden Haberleşmesi

Aslında şuana kadar birbirleri ile ilişkili olan iki component’in nasıl haberleştiklerine baktık. Genelde bunlar ‘parent-child’ ilişkisi içerisindeler. Fakat iki component’in birbiri ile haberlşebilmesi için‘parent-child’ ilişkisi içinde olmasına gerek yok. Eğer gerek olsaydı işler o zaman çok karışırdı. Çünkü, Angular kodları derlenip tarayıcı tarafından yorumlandığında, component’ler belli bir hiyeraşik ağaç yapısı ile DOM’a eklenir. Aşağıdaki görselde her bir harfin bir Angular component’i olduğunu varsayalım. R bizim root dediğimiz app component olsun. Hemen altında 4 tane child olan P, A, X ve Y component’leri vardır. Aynı mantıkla P’nin child component’i C’dir. Bu mantıkla bütün componentler ilişki içerisindedir.

Eğer bütün haberleşmeyi ‘parent-child’ ilişkisi üzerinden yürütecek olsaydık işler çok karışacaktır. Tabiri caizse kuzen olan veyahut uzaktan akraba olan cmponent’lerin haberleşebilmesi için ta en tepeye kadar çıkmak zorunda kalınacaktı. Yani B component’inden tetiklenen bir olay, C’yi etkileyip C’nin yeniden render eldimesini gerektiriyorsa, sırasıyla B -> Y -> R -> P -> C component’lerini de etkileyecekti. Bu yüzden bütün bu component’ler ‘detect-changes’ algoritması ile taranacak ve buna bağlı olarak bütün bu component’lerin ‘lifecycle-hook’ metodları tetiklenecekti. Tamamen verimsiz ve kontrolü zor olan bir durum çıkacaktı ortaya.

İşte SPA uygulamalarında bu tarz genel problemlere Redux Problems denir. Ama ben bunu anlatmayacağım. Çünkü başlı başına ayrı bir konu. Bu problemleri çözmek için frameworkler kendi çözümlerini sunan kütüphaneler ve yaklaşımlar üretiyorlar. Angular’da da bunun için geliştirilmiş epey bir kütüphane mevcuttur. Benim bildiklerim NgRx ve NGXS var merak edenler bakabilir.

Bu uzun izahattan sonra, Angular projelerinin vazgeçilmez kütüphanelerinden biri olan RxJs ile 2 farklı component’i nasıl haberleştirildiğine bakalım.

Tanım olarak RxJS, olay ve veri kaynaklarını abone olunabilir (subscribable) nesnelere dönüştürüp, bunlar üzerinde operatörler yardımıyla dönüşümler gerçekleştirebildiğiniz, gözlemleyenler (observer) aracılığıyla sonucu tüketebildiğiniz JavaScript’le yazılmış bir reaktif programlama (reactive programming) kütüphanesidir. Şimdilik RxJS’in detayına girmeyeceğim. Sadece RxJs kütüphanesinde Subject nesnesinin bilgi yayını yaptığını bilelim yeter. Aşağıdaki örnekte kod aralarına yorumları ekledim.

/* mission.service.ts*/

import { Injectable } from '@angular/core';
import { Subject } from 'rxjs'; // rxjs kütüphanesi import edilir

@Injectable()
export class MissionService {

// Bilgi yayını yapan 2 subject tanımlanır
private missionAnnouncedSource = new Subject<string>();
private missionConfirmedSource = new Subject<string>();

// Bu bilgi yayını yapan kaynaklar akışa çevrilir
missionAnnounced$ = this.missionAnnouncedSource.asObservable();
missionConfirmed$ = this.missionConfirmedSource.asObservable();

// Servis içindeki bu iki metod akışa veri gönderir.
announceMission(mission: string) {
this.missionAnnouncedSource.next(mission);
}

confirmMission(astronaut: string) {
this.missionConfirmedSource.next(astronaut);
}
}
/* mission-control.component.ts */

import { Component } from '@angular/core';
import { MissionService } from './mission.service'; // servis import edilir

@Component({
selector: 'app-mission-control',
template: `
<h2>Mission Control</h2>
<button type="button" (click)="announce()">Announce mission</button>
`,
providers: [MissionService]
})
export class MissionControlComponent {

// abonelik tanımlanır.
subscription: Subscription;

// mission service inject edilir.
constructor(private missionService: MissionService) {

/* servis içindeki missionConfirmed$ akışına subscribe ile abone olunur.
Akışta her veri geldiğinde burada yakalanıp işlenir. */

this.subscription = missionService.missionConfirmed$.subscribe(
data=> {
console.log(data);
});
}

announce() {
this.missionService.announceMission('Go to the Moon!');
}

ngOnDestroy() {
//component yok edildiğinde abonelik sonlandırılır.
this.subscription.unsubscribe();
}
}
/* astronaut.component.ts */

import { Component, OnDestroy } from '@angular/core';
import { MissionService } from './mission.service';
import { Subscription } from 'rxjs';

@Component({
selector: 'app-astronaut',
template: `
<p>
I am astronaut!
<button type="button" (click)="confirm()"> Confirm </button>
</p>
`
})
export class AstronautComponent implements OnDestroy {
// abonelik tanımlanır.
subscription: Subscription;

// mission service inject edilir.
constructor(private missionService: MissionService) {

/* servis içindeki missionAnnounced$ akışına subscribe ile abone olunur.
Akışta her veri geldiğinde burada yakalanıp işlenir. */

this.subscription = missionService.missionAnnounced$.subscribe(
mission => {
console.log(mission);
});
}

confirm() {
this.missionService.confirmMission('astronaut');
}

ngOnDestroy() {
//component yok edildiğinde abonelik sonlandırılır.
this.subscription.unsubscribe();
}
}

Bu makalede, Angular component’lerinin haberleşme yöntemlerini inceledik ve farklı senaryolarda nasıl kullanılabileceklerini öğrendik. Component’ler arasındaki veri iletişimi, olayların yönetimi ve genel uygulama akışı açısından kritik bir rol oynar. Angular’ın sağladığı çeşitli haberleşme mekanizmalarını kullanarak, uygulamalarımızı daha esnek, ölçeklenebilir ve bakımı daha kolay hale getirebiliriz. Her bir haberleşme yöntemi, belirli bir senaryoya en uygun olanı seçmemizi sağlar, böylece Angular uygulamalarımızı daha verimli bir şekilde geliştirebiliriz. Görüldüğü gibi Angular güçlü özelliklere sahip olmasına rağmen, topluluklar tarafından da açık kaynaklı kütüphaneler ile de desteklenmektedir.

Bir yazının daha sonuna geldik. Yazıyı, yeni başlayanlar veya İngilizcesi yeterli olmayanlar için mümkün olduğunca anlaşılır bir şekilde yazmaya çalıştım. Umarım faydalı olmuştur. Aklınıza takılan herhangi bir soruyu çekinmeden yorumlarda sorabilirsiniz.

Yaralanılan Kaynaklar:

--

--

ismail kaşan
ismail kaşan

Written by ismail kaşan

I am a full stack developer since 2016.

No responses yet