Derinlemesine yazılım eğitimleri için kanalımı takip edebilirsiniz...

Angular – Gelişmiş Component Yapılanması | ViewChild, ViewChildren, QueryList

Merhaba,

Bu içeriğimizde Angular mimarisinde, DOM nesnelerine karşılık referans oluşturmak için kullanılan ViewChild ve ViewChildren dekoratörleriyle birlikte bu nesneleri koleksiyonel olarak elde etmemizi sağlayan QueryList decoratörünün işlevlerini inceliyor olacağız.

ViewChild

ViewChild dekoratörü, DOM’da ilk eşleştiği öğeyi temsil etmektedir. Şöyle ki;

import { AfterViewInit, Component, ElementRef, OnInit, ViewChild } from '@angular/core';

@Component({
  selector: 'app-home',
  template: `
  <input type="text" value="Merhaba" #txtInput>
  `
})
export class HomeComponent implements AfterViewInit {

  @ViewChild("txtInput", { static: false }) txtInput: ElementRef;

  ngAfterViewInit(): void {
    console.log(this.txtInput)
    const htmlInputElement: HTMLInputElement = this.txtInput.nativeElement;
    console.log(htmlInputElement.value)
  }

}

Dikkat ederseniz ‘input’ HTML element’ine ‘#txtInput’ template variable’ını atayarak işaretliyoruz ve ardından ViewChild dekoratörü ile ilgili değişkeni sorgulayarak yakalıyoruz. Tabi burada yakalanan HTML nesnesinin ElementRef türünden elde edildiğini ve içerisindeki ‘nativeElement’ property’sinin ise artık nesnenin özelliğine göre (HTMLInputElement) şekillendiğini bilmenizde fayda vardır.

Aynı şekilde ‘ViewChild’ dekoratörü ile selector üzerinden kullanılan component’ler de işaretlenebilmekte ve kullanılabilmektedir. Bunun için de aşağıdaki örneği inceleyebiliriz;

Child Component:

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

@Component({
  selector: 'app-child',
  template: `
  <h4>Child Component</h4>
  Geçerli sayı : {{count}}
  `
})
export class ChildComponent {

  count = 0;

  increment() {
    this.count++;
  }

  decrement() {
    this.count--;
  }

}

Home Component:

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

@Component({
  selector: 'app-home',
  template: `
  <button (click)="increment()">Increment</button>
  <button (click)="decrement()">Decrement</button>
  <p>Geçerli sayı : {{childComponent.count}}</p>
  <app-child></app-child>
  `
})
export class HomeComponent {

  @ViewChild(ChildComponent, { static: true }) childComponent: ChildComponent;

  increment() {
    this.childComponent.increment();
  }

  decrement() {
    this.childComponent.decrement();
  }

}

Yukarıdaki kod bloğunu incelerseniz eğer, ViewChild dekoratörü parametreye verilen ‘ChildComponent’ türünü direkt yakalamakta ve içerisindeki özellikleri kullanılabilir kılmaktadır.
Angular - Gelişmiş Component Yapılanması | ViewChild, ViewChildren, QueryList
Ya da isterseniz önceki örnekte olduğu gibi template variable üzerinden de işaretlemede bulunup ardından ViewChild ile referans edebilirsiniz.

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

@Component({
  selector: 'app-home',
  template: `
  <button (click)="increment()">Increment</button>
  <button (click)="decrement()">Decrement</button>
  <p>Geçerli sayı : {{childComponent.count}}</p>
  <app-child #childComponent></app-child>
  `
})
export class HomeComponent {

  @ViewChild("childComponent", { static: true }) childComponent: ChildComponent;

  increment() {
    this.childComponent.increment();
  }

  decrement() {
    this.childComponent.decrement();
  }

}

ViewChild static Parametresi

Yukarıda vermiş olduğumuz örneklerin kimin de false kimin de ise true olarak set ettiğimiz static parametresinin ne işe yaradığını muhtemelen merak etmişsinizdir. O halde hemen açıklayalım…

static parametresi, ViewChild ile referans edilen nesnenin sayfanın initialize edildiği andan beri mevcut olup olmadığını ifade eder. Bu parametreye false değeri verildiği taktide Angular’ın ilgili nesneyi sayfanın yüklenmesinden sonra arayacağını yani bir başka deyişle belirli koşullara göre bağımlı olacağını ifade etmiş oluyoruz. Bu durumda da ilgili nesneyi ancak ngAfterViewInit event’in de elde edebiliyoruz. Yok eğer true değerini verirseniz bu sefer de ilgili nesnenin sayfanın yüklenmesinin en başından beri mevcut olduğunu söylemiş oluyoruz. Haliyle ngOnInit event’i üzerinden erişim gösterebiliyoruz.

Şöyle ki;

export class HomeComponent implements OnInit, AfterViewInit {
  @ViewChild("childComponent", { static: true }) childComponent: ChildComponent;

  .
  .
  .

  ngOnInit(): void {
    console.log("OnInit", this.childComponent)
  }

  ngAfterViewInit(): void {
    console.log("AfterViewInit", this.childComponent)
  }

}

Yukarıdaki gibi static parametresini true ve false değerleriyle test edersek eğer ilgili event’lerin erişim durumunu aşağıdaki görsellerden gözlemleyebiliriz.

true false
Angular - Gelişmiş Component Yapılanması | ViewChild, ViewChildren, QueryList Angular - Gelişmiş Component Yapılanması | ViewChild, ViewChildren, QueryList
ViewChild – Multiple Instance

ViewChild ile referans etmek istediğimiz HTML yahut component farketmeksizin herhangi bir öğenin sayfa üzerinde birden fazla instance’ı varsa eğer bu durumda ilk nesne işaretlenecektir.

Şöyle ki;

.
.
.
  template: `
  <button (click)="increment()">Increment</button>
  <button (click)="decrement()">Decrement</button>
  <p>Geçerli sayı : {{childComponent.count}}</p>
  <app-child #childComponent></app-child>
  <app-child #childComponent></app-child>
  `
.
.
.

Yukarıdaki örnekte birden fazla ‘childComponent’ template variable’a karşılık gelen ‘ChildComponent’ tanımlaması bulunmaktadır. Önceki satırlarda ‘ViewChild’ ile sadece ilk component’in referans edileceği söylendiği üzere bu çalışma neticesinde aşağıdaki ekran görüntüsündeki gibi bir davranış söz konusu olacaktır.
Angular - Gelişmiş Component Yapılanması | ViewChild, ViewChildren, QueryList

ViewChild – Read Option

ViewChild ile referans edilen öğeler birden fazla türle ilişkilendirilerek daha efektif bir şekilde yönetilebilmektedir. Şöyle ki;

@Component({
  selector: 'app-home',
  template: `
  <input type="text" #txtInput [(ngModel)]="txt">
  `
})
export class HomeComponent implements OnInit {
  @ViewChild("txtInput", { static: true, read: ElementRef }) txtInput: ElementRef;

  txt: string;
  .
  .
  .
}

Yukarıda örnek amaçlı tanımlanmış olan input nesnesi ‘#txtInput’ template variable’ı ile işaretlendiği gibi biryandan da [(ngModel)] direktifini kullanarak two way data binding özelliğini kullanmaktadır. Yani bizler bu nesneyi bir element olduğu için ElementRef ile, [(ngModel)]‘ı kullandığı için NgModel ile veya bir view container olduğu için de ViewContainerRef türüyle ilişkilendirerek referans edebiliriz. Eğer ki bu nesneyi aşağıdaki gibi direkt ViewChild ile referans edersek default olarak ElementRef türünden elde ediyor olacağız.

 @ViewChild("txtInput", { static: true }) txtInput;

Eğer ilgili objeyi farklı türlerle elde etmek istiyorsanız aşağıdaki gibi read parametresini kullanarak bu işlemi gerçekleştirebilirsiniz.

  @ViewChild("txtInput", { static: true, read: ElementRef }) txtInput: ElementRef;
  @ViewChild("txtInput", { static: true, read: NgModel }) txtInput: NgModel;
  @ViewChild("txtInput", { static: true, read: ViewContainerRef }) txtInput: ViewContainerRef;

Angular’da her öğenin ElementRef ve ViewContainerRef ile ilişkilendirilebildiğini ve eğer ilgili öğe bir direktif yahut component ise o türlerle de referans edilebildiğini bilmenizde fayda vardır.

read parametresinin bir diğer işlevi ise child component’ten bir provider’ı inject etmemizi sağlamasıdır. Hemen örneklendirmemiz gerekirse eğer;

Child Component:

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

@Component({
  selector: 'app-child',
  template: `
  <h4>Child Component</h4>
  `,
  providers: [{ provide: 'Example Provider', useValue: 'Example Value' }]
})
export class ChildComponent {

}

Bakın, görüldüğü üzere child component’te ‘Example Provider’ isminde bir provider tanımlanmıştır.

Home Component:

import { Component, OnInit, ViewChild } from '@angular/core';

@Component({
  selector: 'app-home',
  template: `
  <app-child #child></app-child>
  `
})
export class HomeComponent implements OnInit {
  @ViewChild("child", { static: true, read: 'Example Provider' }) exampleProvider: string;

  txt: string;

  ngOnInit(): void {
    console.log("OnInit", this.exampleProvider)
  }
}

Home component’te ise ViewChild ile ‘child’ referans edilmekte lakin read değerine child component’te ki provider’ın adı verilerek esasında bu provider’ın değeri elde edilmiş olunmaktadır.Angular - Gelişmiş Component Yapılanması | ViewChild, ViewChildren, QueryList

ViewChildren

ViewChild, sayfada ilgili öğeden kaç tane olursa olsun eşleşecek olan ilk nesneyi, tek olarak referans etmektedir. ViewChildren ise verilen öğenin tümünü QueryList olarak elde etmemizi sağlayan bir dekoratördür.

import { AfterViewInit, Component, ElementRef, OnInit, QueryList, ViewChild, ViewChildren } from '@angular/core';

@Component({
  selector: 'app-home',
  template: `
  <app-child #child></app-child>
  <app-child #child></app-child>
  <app-child #child></app-child>
  <app-child #child></app-child>
  `
})
export class HomeComponent implements AfterViewInit {
  @ViewChildren("child") childs: QueryList<ElementRef>;

  ngAfterViewInit(): void {
    console.log("AfterViewInit", this.childs)
  }
}

Angular - Gelişmiş Component Yapılanması | ViewChild, ViewChildren, QueryListViewChildren, syntax’ı gereği static parametresi barındırmadığı için referans ettiği öğelere ngOnInit event’i içerisinden erişilememekte, sadece ngAfterViewInit event’inden erişilebilmektedir.

ViewChildren – Read Option

Nasıl ki ViewChild dekoratöründe read option’ı child component’in provider’ını inject etmemizi sağlamakta, benzer mantıkla ViewChildren dekoratörü de child component’lerin provider’larını inject ederek kümülatif olarak elde etmemize imkan tanımaktadır. Bunun için aşağıdaki örneği incelersek eğer;

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

@Component({
  selector: 'app-child',
  template: `
  <h4>Child Component</h4>
  `,
  providers: [{ provide: 'Example Provider', useValue: 'Example Value - Child' }]
})
export class ChildComponent {

}
@Component({
  selector: 'app-child2',
  template: ``,
  providers: [{ provide: 'Example Provider', useValue: 'Example Value - Child 2' }]
})
export class Child2Component {

}
@Component({
  selector: 'app-child3',
  template: ``,
  providers: [{ provide: 'Example Provider', useValue: 'Example Value - Child 3' }]
})
export class Child3Component {

}

Yukarıda görüldüğü üzere ‘Child’, ‘Child2’ ve ‘Child3’ component’leri tanımlanmış ve her component’te farklı value’lara karşılık gelen ‘Example Provider’ key’inde provider oluşturulmuştur. Şimdi bu provider’ları aşağıdaki gibi aynı template variable ile işaretleyerek ViewChildren dekoratörüyle QueryList olarak elde edebilir ve read option’ı ile de hepsinin provider değerlerini okuyarak elde edebiliriz.

import { AfterViewInit, Component, ElementRef, QueryList, ViewChildren } from '@angular/core';

@Component({
  selector: 'app-home',
  template: `
  <app-child #child></app-child>
  <app-child2 #child></app-child2>
  <app-child3 #child></app-child3>
  `
})
export class HomeComponent implements AfterViewInit {
  @ViewChildren("child", { read: "Example Provider" }) childs: QueryList<ElementRef>;

  ngAfterViewInit(): void {
    console.log("AfterViewInit", this.childs)
  }
}

Angular - Gelişmiş Component Yapılanması | ViewChild, ViewChildren, QueryList

QueryList

ViewChildren dekoratörü neticesinde QueryList türünden elde edilen öğelerin sayfaya eklenip kaldırıldığına dair bilgi edinebilmek için changes property’sine subscribe olarak süreci takipte bulunabiliriz.

Misal olarak ngIf direktifiyle ilgili öğelerin visible özelliğine aşağıdaki gibi kontrol getirebilir ve her değişiklik durumunda tetiklenecek olan changes property’sinden öğelerin son durumuna dair bilgi alabiliriz.

import { AfterViewInit, Component, ElementRef, QueryList, ViewChildren } from '@angular/core';

@Component({
  selector: 'app-home',
  template: `
  <h1 #element>A</h1>
  <h1 *ngIf="visible" #element>B</h1>
  <h1 *ngIf="visible" #element>C</h1>

  <button (click)="visibleComponents()">Show</button>
  `
})
export class HomeComponent implements AfterViewInit {
  @ViewChildren("element", {}) childs: QueryList<ElementRef>;

  visible: boolean;

  ngAfterViewInit(): void {
    console.log("AfterViewInit", this.childs)
    this.childs.changes.subscribe({
      next: (data) => {
        console.log(data)
      }
    });
  }

  visibleComponents() {
    this.visible = !this.visible;
  }
}

Angular - Gelişmiş Component Yapılanması | ViewChild, ViewChildren, QueryList

Directive İle İşaretlenmiş Öğeleri Elde Etme

Son olarak, herhangi bir direktif ile işaretlediğimiz öğeleri ‘ViewChild’ ile nasıl elde edebileceğimizi konuşmakta fayda görmekteyim. Bunun için aşağıdaki örnek çalışmayı inceleyebilirsiniz.

Custom Directive:

import { Directive, ElementRef } from '@angular/core';

@Directive({
  selector: '[appEx]'
})
export class ExDirective {

  constructor(public elementRef: ElementRef) { }

}

Yukarıdaki custom directive kodlarını incelerseniz eğer işaretleyeceği HTML öğesini karşılayabilmesi için access modifier’ı ‘public’ ve türü ‘ElementRef’ olan bir parametre barındırmaktadır.

Şimdi bu directive ile ‘ViewChild’ı birlikte aşağıdaki gibi kullanabiliriz.

import { Component, ElementRef, OnInit, ViewChild } from '@angular/core';
import { ExDirective } from '../ex.directive';

@Component({
  selector: 'app-home',
  template: `
  <h1 appEx>...</h1>
  `
})
export class HomeComponent implements OnInit {
  @ViewChild(ExDirective, { static: true }) ex: ExDirective;

  ngOnInit(): void {
    console.log(this.ex.elementRef)
  }
}

Angular - Gelişmiş Component Yapılanması | ViewChild, ViewChildren, QueryListDirektif ile yapılan bu manevrayı kullandığımız Angular’da Dynamic Component Loading başlıklı makalemizi ayrı bir örnek olması açısından kaynak olarak değerlendirebileceğinizi söylerek içeriğimizi burada noktalayalım derim 🙂

İlgilenenlerin faydalanması dileğiyle…
Sonraki yazılarımda görüşmek üzere…
İyi çalışmalar…

Bunlar da hoşunuza gidebilir...

Bir yanıt yazın

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