Takip et

Angular’da Reactive Formlar: Değişiklikleri Dinleme

Angular’da Reactive Formlar: Değişiklikleri Dinleme Giriş Angular, modern web uygulamaları geliştirmek için güçlü ve esnek bir platform suna

Angular’da Reactive Formlar: Değişiklikleri Dinleme

Giriş

Angular, modern web uygulamaları geliştirmek için güçlü ve esnek bir platform sunar. Bu platformun temel taşlarından biri de form yönetimidir. Angular, formları yönetmek için iki farklı yaklaşım sunar: Template-driven Forms ve Reactive Forms. Bu makalede, özellikle büyük ölçekli ve karmaşık formlar için tercih edilen, daha ölçeklenebilir ve test edilebilir bir yapıya sahip olan Reactive Forms üzerine odaklanacağız. Reactive Forms, form durumunu ve değişikliklerini programatik olarak yönetme imkanı sunar. Bu makalenin ana konusu ise, Reactive Forms yapısı içinde form alanlarındaki veya genel form durumundaki değişiklikleri nasıl dinleyeceğimiz ve bu değişikliklere nasıl tepki vereceğimizdir.

Formlardaki değişiklikleri dinlemek, kullanıcı deneyimini zenginleştirmek, veri bütünlüğünü sağlamak ve uygulamanın performansını optimize etmek için kritik öneme sahiptir. Örneğin, bir kullanıcının belirli bir alana giriş yapmasıyla başka bir alanın görünürlüğünü değiştirebilir, formun geçerliliğine göre bir gönder düğmesini etkinleştirebilir veya belirli bir gecikmeyle sunucuya otomatik arama istekleri gönderebilirsiniz. Tüm bu senaryolar, Reactive Forms’un sunduğu güçlü gözlemlenebilir (observable) mekanizmaları sayesinde kolayca uygulanabilir.

Bu makalede, FormControl, FormGroup ve FormArray gibi Reactive Forms yapılarını kullanarak valueChanges ve statusChanges gözlemlenebilirlerini derinlemesine inceleyeceğiz. Ayrıca, RxJS operatörlerini (örneğin debounceTime, distinctUntilChanged, switchMap) kullanarak bu değişiklikleri nasıl daha etkin bir şekilde yöneteceğimizi, performans sorunlarını nasıl çözeceğimizi ve çeşitli pratik senaryoları nasıl uygulayacağımızı öğreneceğiz. Son olarak, bellek sızıntılarını önlemek için abonelikleri doğru bir şekilde yönetmenin önemine değineceğiz.

Reactive Form Temelleri ve Değişiklik Dinleme Mekanizmaları

Angular Reactive Forms, form kontrollerini programatik olarak oluşturmanıza ve yönetmenize olanak tanır. Formun her bir parçası bir JavaScript sınıfı tarafından temsil edilir ve bu sınıflar formun değerini ve durumunu bir gözlemlenebilir akış (observable stream) olarak sunar.

FormControl, FormGroup, FormArray Yapıları

Reactive Forms’un üç temel yapı taşı vardır:
* FormControl: Tek bir form giriş alanını (input, select, textarea vb.) temsil eder. Değerini, geçerliliğini ve durumunu yönetir.
* FormGroup: Bir grup FormControl veya başka FormGroup örneklerini bir araya getirir. Genellikle bir formun tamamını veya mantıksal olarak ilişkili bir bölümünü temsil eder. Tüm alt kontrollerinin değerlerini ve geçerlilik durumunu toplar.
* FormArray: Dinamik olarak eklenip çıkarılabilen bir kontrol koleksiyonunu temsil eder. Örneğin, bir kullanıcının birden fazla e-posta adresi veya telefon numarası girebildiği senaryolarda kullanışlıdır.

Bu üç sınıfın her biri, formdaki değişiklikleri dinlemek için iki önemli gözlemlenebilir (observable) özellik sunar: valueChanges ve statusChanges.

valueChanges Observable’ı

valueChanges, bir FormControl, FormGroup veya FormArray‘ın değeri her değiştiğinde yeni bir değer yayan bir RxJS Observable’ıdır. Bu, kullanıcının form alanlarına her giriş yaptığında, programatik olarak bir değer atandığında veya form sıfırlandığında tetiklenir.

FormControl ile Kullanımı

Tek bir giriş alanının değerindeki değişiklikleri izlemek için FormControl‘un valueChanges özelliğini kullanırız.

import { Component, OnInit, OnDestroy } from '@angular/core';
import { FormControl } from '@angular/forms';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';

@Component({
  selector: 'app-search-input',
  template: 
    
    

Arama Terimi: {{ searchTerm }}

}) export class SearchInputComponent implements OnInit, OnDestroy { searchControl = new FormControl(''); searchTerm: string = ''; private destroy$ = new Subject(); ngOnInit() { this.searchControl.valueChanges .pipe( takeUntil(this.destroy$) // Bellek sızıntısını önlemek için ) .subscribe(value => { this.searchTerm = value; console.log('Arama terimi değişti:', value); // Burada arama işlemini tetikleyebilirsiniz }); } ngOnDestroy() { this.destroy$.next(); this.destroy$.complete(); } }

Yukarıdaki örnekte, searchControl adlı FormControl‘un değeri her değiştiğinde, subscribe bloğu içindeki kod çalışır. Bu sayede, kullanıcının yazdığı her karakterde searchTerm değişkeni güncellenir ve konsola loglanır.

FormGroup ile Kullanımı

Bir FormGroup‘un valueChanges özelliği, gruptaki herhangi bir kontrolün değeri değiştiğinde tetiklenir ve FormGroup‘un tüm değerlerini içeren bir nesne yayımlar. Bu, formun genel değerini izlemek için idealdir.

import { Component, OnInit, OnDestroy } from '@angular/core';
import { FormGroup, FormControl, Validators } from '@angular/forms';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';

@Component({
  selector: 'app-user-profile',
  template: 
    
{{ formValues | json }}


})
export class UserProfileComponent implements OnInit, OnDestroy {
userProfileForm = new FormGroup({
firstName: new FormControl('', Validators.required),
lastName: new FormControl('', Validators.required),
email: new FormControl('', [Validators.required, Validators.email])
});

formValues: any;
private destroy$ = new Subject();

ngOnInit() {
this.userProfileForm.valueChanges
.pipe(
takeUntil(this.destroy$)
)
.subscribe(values => {
this.formValues = values;
console.log('Form değerleri değişti:', values);
// Burada formun tamamının değerine göre işlemler yapabilirsiniz
});
}

ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
}

Bu örnekte, userProfileForm içindeki firstName, lastName veya email alanlarından herhangi biri değiştiğinde, userProfileForm.valueChanges tetiklenir ve tüm formun güncel değerini ({ firstName: '...', lastName: '...', email: '...' } şeklinde) yayımlar.

FormArray ile Kullanımı

FormArray‘ın valueChanges özelliği, dizideki herhangi bir kontrolün değeri değiştiğinde veya diziye yeni bir kontrol eklendiğinde/çıkarıldığında tetiklenir. Yayımlanan değer, dizideki tüm kontrollerin değerlerini içeren bir dizidir.

import { Component, OnInit, OnDestroy } from '@angular/core';
import { FormArray, FormControl, FormGroup, Validators, FormBuilder } from '@angular/forms';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';

@Component({
  selector: 'app-skill-list',
  template: 
    
{{ skillForm.value | json }}


})
export class SkillListComponent implements OnInit, OnDestroy {
skillForm: FormGroup;
private destroy$ = new Subject();

constructor(private fb: FormBuilder) {
this.skillForm = this.fb.group({
skills: this.fb.array([this.fb.control('', Validators.required)])
});
}

get skills() {
return this.skillForm.get('skills') as FormArray;
}

addSkill() {
this.skills.push(this.fb.control('', Validators.required));
}

removeSkill(index: number) {
this.skills.removeAt(index);
}

ngOnInit() {
this.skills.valueChanges
.pipe(
takeUntil(this.destroy$)
)
.subscribe(values => {
console.log('Yetenekler değişti:', values);
// Burada yetenekler dizisine göre işlemler yapabilirsiniz
});
}

ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
}

Bu örnekte, skills adlı FormArray‘e yeni bir yetenek eklendiğinde, var olan bir yetenek kaldırıldığında veya herhangi bir yetenek alanının değeri değiştiğinde skills.valueChanges tetiklenir ve güncel yetenekler dizisini yayımlar.

statusChanges Observable’ı

statusChanges, bir FormControl, FormGroup veya FormArray‘ın geçerlilik durumu (validation status) her değiştiğinde yeni bir durum yayan bir RxJS Observable’ıdır. Olası durumlar şunlardır:
* VALID: Kontrol veya grup geçerlidir.
* INVALID: Kontrol veya grup geçersizdir.
* PENDING: Asenkron doğrulama devam etmektedir.
* DISABLED: Kontrol veya grup devre dışıdır.

Kullanım Alanları ve Örnek Kod

statusChanges, formun genel geçerliliğini izlemek ve buna göre UI elementlerini (örneğin, bir gönder düğmesini etkinleştirmek veya devre dışı bırakmak) güncellemek için çok kullanışlıdır.

import { Component, OnInit, OnDestroy } from '@angular/core';
import { FormGroup, FormControl, Validators } from '@angular/forms';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';

@Component({
  selector: 'app-registration-form',
  template: 
    
Kullanıcı adı gerekli!
Şifre en az 6 karakter olmalı!

Form Durumu: {{ formStatus }}

}) export class RegistrationFormComponent implements OnInit, OnDestroy { registrationForm = new FormGroup({ username: new FormControl('', Validators.required), password: new FormControl('', [Validators.required, Validators.minLength(6)]) }); formStatus: string = ''; private destroy$ = new Subject(); ngOnInit() { this.registrationForm.statusChanges .pipe( takeUntil(this.destroy$) ) .subscribe(status => { this.formStatus = status; console.log('Form durumu değişti:', status); // Örneğin, form geçerliyse bir API çağrısı yapabilirsiniz }); } ngOnDestroy() { this.destroy$.next(); this.destroy$.complete(); } }

Bu örnekte, username veya password alanlarının geçerlilik durumu değiştiğinde (VALID, INVALID, PENDING gibi), registrationForm.statusChanges tetiklenir. Bu sayede formStatus değişkenini güncelleyebilir ve “Kaydol” düğmesini formun genel geçerlilik durumuna göre etkinleştirebilir veya devre dışı bırakabiliriz.

RxJS Operatörleri ile Değişiklikleri Yönetme

valueChanges ve statusChanges gözlemlenebilirleri, RxJS kütüphanesinin bir parçasıdır. Bu, bize RxJS’in zengin operatör setini kullanarak bu akışları dönüştürme, filtreleme ve yönetme yeteneği verir. Bu operatörler, performansı artırmak, gereksiz işlemleri engellemek ve daha karmaşık mantıkları uygulamak için kritik öneme sahiptir.

debounceTime

debounceTime operatörü, bir Observable’ın yayımladığı değerleri belirli bir süre boyunca bekletir ve yalnızca o süre içinde başka bir değer yayımlanmazsa son değeri iletir. Bu, özellikle kullanıcının hızlı bir şekilde giriş yaptığı arama kutuları gibi durumlarda, her tuş vuruşunda bir işlem (örneğin API çağrısı) tetiklemek yerine, kullanıcının yazmayı bitirmesini beklemek için çok faydalıdır. Performansı artırır ve sunucuya gereksiz istekleri azaltır.

import { Component, OnInit, OnDestroy } from '@angular/core';
import { FormControl } from '@angular/forms';
import { Subject } from 'rxjs';
import { debounceTime, distinctUntilChanged, takeUntil } from 'rxjs/operators';

@Component({
  selector: 'app-debounced-search',
  template: 
    
    

Son Arama: {{ lastSearchedTerm }}

}) export class DebouncedSearchComponent implements OnInit, OnDestroy { searchControl = new FormControl(''); lastSearchedTerm: string = ''; private destroy$ = new Subject(); ngOnInit() { this.searchControl.valueChanges .pipe( debounceTime(300), // 300 ms içinde başka bir değer gelmezse tetikle distinctUntilChanged(), // Sadece değer gerçekten değiştiyse tetikle takeUntil(this.destroy$) ) .subscribe(value => { this.lastSearchedTerm = value; console.log('Debounce edilmiş arama:', value); // Burada API'ye arama isteği gönderebilirsiniz }); } ngOnDestroy() { this.destroy$.next(); this.destroy$.complete(); } }

Bu örnekte, kullanıcı searchControl‘a her karakter yazdığında valueChanges tetiklenir, ancak debounceTime(300) sayesinde, kullanıcı yazmayı bıraktıktan 300 milisaniye sonra değer subscribe bloğuna ulaşır. distinctUntilChanged() ise, aynı değerin art arda gelmesini engeller (örneğin, “elma” yazıp geri silip tekrar “elma” yazmak gibi).

distinctUntilChanged

distinctUntilChanged operatörü, bir Observable’dan yayımlanan ardışık aynı değerleri filtreler. Yalnızca yeni yayımlanan değer bir önceki değerden farklıysa bu değeri iletir. Bu, bir değerin değiştiğini ancak aynı kaldığını bildiren gereksiz tetiklemeleri önlemek için kullanışlıdır.

Yukarıdaki debounceTime örneğinde olduğu gibi, distinctUntilChanged genellikle debounceTime ile birlikte kullanılır. Bu kombinasyon, özellikle arama kutuları veya otomatik kaydetme işlevleri için çok etkilidir.

filter, map, switchMap ve Diğerleri

RxJS operatörleri, Observable akışlarını manipüle etmek için çok güçlüdür.
* filter: Belirli bir koşulu karşılamayan değerleri akıştan çıkarır. Örneğin, sadece belirli bir uzunluğun üzerindeki arama terimlerini işlemek isteyebilirsiniz.
* map: Her değeri dönüştürür ve dönüştürülmüş değeri yayımlar. Örneğin, bir form grubunun değerini alıp sadece belirli alanlarını içeren yeni bir nesne döndürebilirsiniz.
* switchMap: İç içe geçmiş Observable’larla çalışırken kullanılır. Bir dış Observable’dan yeni bir değer geldiğinde, önceki iç Observable aboneliğini iptal eder ve yeni bir iç Observable’a abone olur. Bu, özellikle her form değişikliğinde bir API çağrısı yaparken önceki bekleyen çağrıları iptal etmek için kritik öneme sahiptir, böylece sadece en son API isteğinin sonucu kullanılır.

Örnek Senaryolar: filter ve switchMap ile Otomatik Arama

import { Component, OnInit, OnDestroy } from '@angular/core';
import { FormControl } from '@angular/forms';
import { Subject, of } from 'rxjs';
import { debounceTime, distinctUntilChanged, filter, switchMap, takeUntil } from 'rxjs/operators';

// Sahte bir API servisi
class SearchService {
  search(term: string) {
    console.log(API'ye istek gönderildi: ${term});
    return of(Arama sonuçları için: ${term}).pipe(debounceTime(500)); // API gecikmesini simüle et
  }
}

@Component({
  selector: 'app-advanced-search',
  template: 
    
    

Sonuç: {{ searchResult }}

, providers: [SearchService] }) export class AdvancedSearchComponent implements OnInit, OnDestroy { searchControl = new FormControl(''); searchResult: string = ''; private destroy$ = new Subject(); constructor(private searchService: SearchService) {} ngOnInit() { this.searchControl.valueChanges .pipe( debounceTime(400), // Kullanıcı yazmayı bıraktıktan 400ms bekle distinctUntilChanged(), // Sadece değer değiştiyse devam et filter(term => term.length >= 3 || term.length === 0), // Sadece 3+ karakter veya boş string için ara switchMap(term => { if (term.length === 0) { return of(''); // Boş arama için boş sonuç döndür } return this.searchService.search(term); // API çağrısı yap }), takeUntil(this.destroy$) ) .subscribe(result => { this.searchResult = result; console.log('Arama sonucu:', result); }); } ngOnDestroy() { this.destroy$.next(); this.destroy$.complete(); } }

Bu gelişmiş arama örneği, RxJS operatörlerinin gücünü göstermektedir:
1. debounceTime(400): Kullanıcı yazmayı bitirene kadar bekler.
2. distinctUntilChanged(): Aynı arama terimi için tekrar arama yapmayı engeller.
3. filter(term => term.length >= 3 || term.length === 0): Sadece 3 veya daha fazla karakter girildiğinde veya arama kutusu tamamen boşaltıldığında arama yapar. Bu, çok kısa terimler için gereksiz API çağrılarını önler.
4. switchMap(term => ...): Her yeni arama terimi geldiğinde, önceki bekleyen arama isteğini iptal eder ve searchService.search() tarafından döndürülen yeni Observable’a abone olur. Bu, kullanıcının hızlıca yazması durumunda eski, alakasız sonuçların gelmesini engeller.

Pratik Uygulamalar ve Senaryolar

Reactive Forms’un değişiklik dinleme yetenekleri, çeşitli pratik senaryolarda uygulamanızın davranışını dinamik olarak kontrol etmenizi sağlar.

Dinamik Alanları Yönetme

Bir form alanının değerine göre başka bir form alanının görünürlüğünü, etkinliğini veya geçerliliğini değiştirmek yaygın bir gereksinimdir.

Bir Alanın Değerine Göre Başka Bir Alanı Etkinleştirme/Devre Dışı Bırakma

Örneğin, “Fatura adresi teslimat adresiyle aynı mı?” şeklinde bir onay kutusu olduğunda, bu kutu işaretlendiğinde fatura adresi alanlarını devre dışı bırakmak isteyebilirsiniz.

import { Component, OnInit, OnDestroy } from '@angular/core';
import { FormGroup, FormControl, FormBuilder, Validators } from '@angular/forms';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';

@Component({
  selector: 'app-address-form',
  template: 
    

Teslimat Adresi

Fatura Adresi

Teslimat adresiyle aynı
{{ addressForm.value | json }}


})
export class AddressFormComponent implements OnInit, OnDestroy {
addressForm: FormGroup;
private destroy$ = new Subject();

constructor(private fb: FormBuilder) {
this.addressForm = this.fb.group({
deliveryAddress: ['', Validators.required],
sameAsDelivery: [false],
billingAddress: ['', Validators.required]
});
}

ngOnInit() {
this.addressForm.get('sameAsDelivery')?.valueChanges
.pipe(
takeUntil(this.destroy$)
)
.subscribe(sameAsDelivery => {
const billingAddressControl = this.addressForm.get('billingAddress');
if (sameAsDelivery) {
billingAddressControl?.disable();
billingAddressControl?.setValue(this.addressForm.get('deliveryAddress')?.value);
} else {
billingAddressControl?.enable();
billingAddressControl?.setValue(''); // Veya önceki değeri geri yükle
}
});

// Teslimat adresi değiştiğinde fatura adresini güncelle (eğer aynı ise)
this.addressForm.get('deliveryAddress')?.valueChanges
.pipe(
takeUntil(this.destroy$)
)
.subscribe(deliveryAddress => {
if (this.addressForm.get('sameAsDelivery')?.value) {
this.addressForm.get('billingAddress')?.setValue(deliveryAddress);
}
});
}

ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
}

Bu örnekte, sameAsDelivery onay kutusunun değeri değiştiğinde, billingAddress kontrolü etkinleştirilir veya devre dışı bırakılır ve değeri güncellenir. Ayrıca, deliveryAddress değiştiğinde ve sameAsDelivery işaretliyse, billingAddress otomatik olarak güncellenir.

Çapraz Alan Doğrulaması (Cross-Field Validation)

Birden fazla form alanının değerlerinin birbirine bağlı olduğu doğrulama senaryolarında, FormGroup‘un valueChanges özelliğini veya özel bir doğrulayıcıyı kullanabiliriz. Örneğin, “şifre” ve “şifre tekrarı” alanlarının eşleşmesi gibi.

import { Component, OnInit, OnDestroy } from '@angular/core';
import { FormGroup, FormControl, Validators, FormBuilder, AbstractControl, ValidatorFn } from '@angular/forms';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';

// Custom validator for password matching
function passwordMatchValidator(controlName: string, matchingControlName: string): ValidatorFn {
  return (formGroup: AbstractControl): { [key: string]: any } | null => {
    const control = formGroup.get(controlName);
    const matchingControl = formGroup.get(matchingControlName);

    if (!control || !matchingControl) {
      return null;
    }

    // set error on matchingControl if validation fails
    if (matchingControl.errors && !matchingControl.errors['passwordMismatch']) {
      return null;
    }

    if (control.value !== matchingControl.value) {
      matchingControl.setErrors({ passwordMismatch: true });
      return { passwordMismatch: true };
    } else {
      matchingControl.setErrors(null);
      return null;
    }
  };
}

@Component({
  selector: 'app-password-match',
  template: 
    
Şifre gerekli!
Şifreler eşleşmiyor!
}) export class PasswordMatchComponent implements OnInit, OnDestroy { passwordForm: FormGroup; private destroy$ = new Subject(); constructor(private fb: FormBuilder) { this.passwordForm = this.fb.group({ password: ['', Validators.required], confirmPassword: ['', Validators.required] }, { validators: passwordMatchValidator('password', 'confirmPassword') }); } ngOnInit() { // Şifre alanlarından biri değiştiğinde çapraz doğrulamayı tetiklemek için // FormGroup seviyesindeki validator zaten bunu yapar, ancak manuel tetikleme gerekirse: this.passwordForm.valueChanges .pipe( takeUntil(this.destroy$) ) .subscribe(() => { // Bu subscription, formdaki herhangi bir değer değiştiğinde formGroup'un validator'ının // tekrar çalışmasını sağlar. Genellikle FormGroup seviyesindeki validatorlar // değer değiştiğinde otomatik olarak çalışır, bu yüzden bu özel bir duruma gerek kalmayabilir. // Ancak, karmaşık bağımlılıklar varsa, manuel updateValueAndValidity() çağrısı gerekebilir. // this.passwordForm.get('confirmPassword')?.updateValueAndValidity({ emitEvent: false }); }); } ngOnDestroy() { this.destroy$.next(); this.destroy$.complete(); } }

Bu örnekte, passwordMatchValidator adlı özel bir doğrulayıcı, passwordForm‘a uygulanır. Bu doğrulayıcı, password ve confirmPassword alanlarının değerlerini karşılaştırır. Alanlardan biri değiştiğinde, FormGroup seviyesindeki doğrulayıcı tetiklenir ve şifrelerin eşleşip eşleşmediğini kontrol eder.

Sunucuya Otomatik Kaydetme veya Arama

Daha önce debounceTime ve switchMap ile gösterilen arama örneği, aynı zamanda otomatik kaydetme işlevleri için de kullanılabilir. Kullanıcı formdaki verileri değiştirdiğinde, belirli bir süre (örneğin 1-2 saniye) beklendikten sonra, form verilerini otomatik olarak sunucuya gönderebilirsiniz.

// Önceki AdvancedSearchComponent örneğindeki mantık, SaveService ile uyarlanabilir.
// searchService yerine saveService.save(formValue) çağrısı yapılabilir.

Form Durumunu İzleme ve Kaydetme

Formun dirty (kirli – yani kullanıcı tarafından değiştirilmiş), pristine (temiz – başlangıç değeriyle aynı), valid (geçerli) veya invalid (geçersiz) gibi durumlarını izlemek, kullanıcıya geri bildirim sağlamak veya formun kaydedilebilirliğini kontrol etmek için önemlidir. statusChanges ve valueChanges ile bu durumları kolayca takip edebilirsiniz.

// Önceki RegistrationFormComponent örneğindeki gibi, statusChanges ile formun geçerliliğini izleyebiliriz.
// Ayrıca, form.dirty veya form.pristine özelliklerini de kontrol edebiliriz.

Abonelikleri Yönetme ve Bellek Sızıntılarını Önleme

RxJS Observable’larına abone olduğunuzda, bu abonelikleri uygun bir şekilde yönetmek ve bileşen yok edildiğinde iptal etmek çok önemlidir. Aksi takdirde, abonelikler bellekte kalmaya devam eder ve bellek sızıntılarına yol açar, bu da uygulamanızın performansını olumsuz etkiler.

unsubscribe() Kullanımı

Her subscribe() çağrısı bir Subscription nesnesi döndürür. Bu nesnenin unsubscribe() metodunu çağırarak aboneliği manuel olarak iptal edebilirsiniz.

import { Component, OnInit, OnDestroy } from '@angular/core';
import { FormControl } from '@angular/forms';
import { Subscription } from 'rxjs';

@Component({
  selector: 'app-manual-unsubscribe',
  template: 
    
  
})
export class ManualUnsubscribeComponent implements OnInit, OnDestroy {
  myControl = new FormControl('');
  private mySubscription: Subscription | undefined;

  ngOnInit() {
    this.mySubscription = this.myControl.valueChanges.subscribe(value => {
      console.log('Değer:', value);
    });
  }

  ngOnDestroy() {
    if (this.mySubscription) {
      this.mySubscription.unsubscribe();
      console.log('Abonelik iptal edildi.');
    }
  }
}

Birden fazla aboneliğiniz varsa, bunları bir Subscription dizisinde toplayıp ngOnDestroy içinde döngü ile iptal edebilirsiniz.

takeUntil Operatörü

takeUntil operatörü, bir Observable’ın başka bir Observable değer yayımlayana kadar değerleri yayımlamasını sağlar. Bu, abonelikleri yönetmek için daha temiz ve RxJS idiomatik bir yoldur. Genellikle, bileşenin yok edildiğini belirten bir Subject (genellikle destroy$) ile birlikte kullanılır.

Yukarıdaki tüm örneklerimizde takeUntil(this.destroy$) kullanımını görebilirsiniz. Bu yöntem, her bir aboneliği ayrı ayrı yönetmek yerine tek bir Subject ile tüm abonelikleri otomatik olarak iptal etmenizi sağlar.

import { Component, OnInit, OnDestroy } from '@angular/core';
import { FormControl } from '@angular/forms';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';

@Component({
  selector: 'app-take-until-example',
  template: 
    
  
})
export class TakeUntilExampleComponent implements OnInit, OnDestroy {
  myControl = new FormControl('');
  private destroy$ = new Subject(); // Bileşenin yok edildiğini bildiren Subject

  ngOnInit() {
    this.myControl.valueChanges
      .pipe(
        takeUntil(this.destroy$) // destroy$ bir değer yayımladığında aboneliği iptal et
      )
      .subscribe(value => {
        console.log('Değer:', value);
      });
  }

  ngOnDestroy() {
    this.destroy$.next(); // destroy$ Subject'ine bir değer yayımlayarak takeUntil'i tetikle
    this.destroy$.complete(); // Subject'i tamamla
  }
}

async Pipe

async pipe, Angular şablonlarında Observable’ları doğrudan kullanmanıza olanak tanır. async pipe, Observable’a otomatik olarak abone olur ve bileşen yok edildiğinde aboneliği otomatik olarak iptal eder. Bu, şablon içinde tek bir değer gösterimi için çok kullanışlıdır ancak bir form kontrolünün valueChanges‘ını dinleyip buna göre karmaşık yan etkiler oluşturmak için genellikle yeterli değildir. Daha çok, Observable’dan gelen veriyi doğrudan DOM’a bağlamak için tercih edilir.

// Şablon içinde:
// 

Arama Terimi: {{ searchControl.valueChanges | async }}

Bu yöntem, yukarıda bahsettiğimiz karmaşık mantıklar (debounce, switchMap vb.) için doğrudan uygun olmasa da, basit değer gösterimleri için bellek yönetimini kolaylaştırır.

En İyi Uygulamalar ve İpuçları

* Abonelikleri Her Zaman Yönetin: Bellek sızıntılarını önlemek için takeUntil veya unsubscribe() kullanarak abonelikleri her zaman iptal edin.
* RxJS Operatörlerini Akıllıca Kullanın: debounceTime ve distinctUntilChanged gibi operatörleri, özellikle kullanıcı girişine dayalı API çağrıları yaparken performansı artırmak için kullanın.
* switchMap ile Yarış Koşullarını Önleyin: Bir önceki Observable’ın tamamlanmasından önce yeni bir Observable oluşturmanız gerektiğinde (örneğin arama veya kaydetme API çağrıları), switchMap kullanarak yarış koşullarını ve eski verilerin işlenmesini engelleyin.
* Ayrı Form Grupları Oluşturun: Büyük formları yönetmek için, formu mantıksal bölümlere ayırın ve her bölüm için ayrı FormGroup‘lar oluşturun. Bu, kodu daha okunabilir ve yönetilebilir hale getirir.
* Custom Validators Kullanın: Karmaşık doğrulama mantığı için özel doğrulayıcılar yazın. Çapraz alan doğrulaması için FormGroup seviyesinde doğrulayıcılar kullanın.
* Test Edilebilirliği Göz Önünde Bulundurun: Reactive Forms, programatik yapısı sayesinde test edilebilirliği artırır. Değişiklik dinleme mantığınızı ayrı fonksiyonlara ayırarak veya doğrudan RxJS akışlarını test ederek kolayca test edebilirsiniz.
* Performansı İzleyin: Aşırı sık tetiklenen valueChanges abonelikleri uygulamanızın performansını etkileyebilir. Geliştirici araçlarını kullanarak uygulamanızın performansını izleyin ve gerektiğinde debounceTime gibi operatörlerle optimizasyonlar yapın.

Sonuç

Angular Reactive Forms, form verilerini ve durumunu programatik olarak yönetmek için güçlü ve esnek bir yapı sunar. valueChanges ve statusChanges gözlemlenebilirleri, formdaki değişiklikleri dinlemek ve bu değişikliklere dinamik olarak tepki vermek için temel mekanizmaları sağlar. RxJS operatörlerini bu gözlemlenebilirlerle birleştirmek, form mantığını daha verimli, performanslı ve okunabilir hale getirir. debounceTime, distinctUntilChanged, filter ve switchMap gibi operatörler sayesinde, kullanıcı deneyimini iyileştiren ve uygulamanın kaynaklarını daha verimli kullanan karmaşık senaryoları kolayca uygulayabilirsiniz.

Bellek sızıntılarını önlemek için abonelik yönetiminin (özellikle takeUntil operatörüyle) kritik önemi unutulmamalıdır. Doğru uygulamalar ve en iyi pratiklerle, Angular Reactive Forms’u kullanarak hem güçlü hem de sürdürülebilir form çözümleri geliştirebilirsiniz. Bu makale, Angular Reactive Forms’ta değişiklikleri dinleme konusunda kapsamlı bir rehber sunarak, geliştiricilerin daha dinamik ve etkileşimli kullanıcı arayüzleri oluşturmalarına yardımcı olmayı amaçlamıştır.

Yorumlar
İçeriği beğendiniz mi? Bir tartışma başlatın veya görüşlerinizi paylaşın.
Yorum Yaz

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

E-posta Bülteni
Yazılım Topluluğuna Katılın
En son güncellemeleri, yaratıcı ipuçlarını ve özel kaynakları doğrudan e-posta kutunuza alın. Tasarım ve inovasyonun geleceğini birlikte keşfedelim.