Angular SSR ile DOM'a güvenli bir şekilde erişme

Cihan Monako
Gerald Monako

Angular, geçtiğimiz yıl boyunca geliştiricilerin Önemli Web Verileri'ni iyileştirmelerine ve son kullanıcılara mükemmel bir deneyim sunmalarına yardımcı olmak için hidrasyon ve ertelenebilir görünümler gibi birçok yeni özellik elde etti. Bu işlevi destekleyen, akış ve kısmi sıvı alma gibi sunucu tarafı oluşturma ile ilgili ek özelliklerle ilgili araştırmalar da devam etmektedir.

Ne yazık ki, uygulamanızın veya kitaplığınızın bu yeni ve yakında kullanıma sunulacak tüm özelliklerden tam olarak yararlanmasını engelleyebilecek tek bir kalıp vardır: Temel DOM yapısının manuel olarak değiştirilmesi. Angular, bir bileşenin sunucu tarafından serileştirilmesinden tarayıcıda kullanılabilir hale gelene kadar DOM yapısının tutarlı kalmasını gerektirir. Hidrasyondan önce, bu özelliklerin çalışmasını engelleyen tutarsızlıklar ortaya çıkmadan önce düğümleri manuel olarak DOM'ye eklemek, taşımak veya kaldırmak için ElementRef, Renderer2 veya DOM API'lerini kullanmak.

Ancak tüm manuel DOM manipülasyonları ve erişimi sorunlu değildir ve bazen gerekli olabilir. DOM'yi güvenli bir şekilde kullanmanın anahtarı, DOM'a yönelik ihtiyacınızı mümkün olduğunca en aza indirmek ve ardından kullanımınızı mümkün olduğunca ertelemektir. Aşağıdaki yönergeler, bunu nasıl başaracağınızı ve Angular'ın yeni ve gelecekte sunduğu tüm özelliklerden tam anlamıyla yararlanabilecek, gerçekten evrensel ve geleceğe hazır Angular bileşenlerini nasıl oluşturabileceğinizi açıklamaktadır.

Manuel DOM manipülasyonundan kaçınma

Manuel DOM manipülasyonunun neden olduğu sorunlardan kaçınmanın en iyi yolu, doğal olarak mümkün olduğunda bundan tamamen kaçınmaktır. Angular, DOM'un çoğu özelliğini değiştirebilen yerleşik API'lere ve kalıplara sahiptir. Doğrudan DOM'a erişmek yerine bunları kullanmayı tercih etmelisiniz.

Bir bileşenin kendi DOM öğesini değiştirme

Bir bileşen veya yönerge yazarken host öğesini (bileşen veya yönergenin selector'ıyla eşleşen DOM öğesi) değiştirmeniz ve örneğin, bir sarmalayıcı öğesini hedeflemek veya kullanıma sunmak yerine bir sınıf, stil veya özellik eklemek üzere değiştirmeniz gerekebilir. Temel DOM öğesini değiştirmek için yalnızca ElementRef öğesine erişmek cazip gelebilir. Bunun yerine, değerleri bir ifadeye bildiri olarak bağlamak için ana makine bağlamalarını kullanmanız gerekir:

@Component({
  selector: 'my-component',
  template: `...`,
  host: {
    '[class.foo]': 'true'
  },
})
export class MyComponent {
  /* ... */
}

HTML'de veri bağlama konusunda olduğu gibi, örneğin, özelliklere ve stillere bağlama işlemini gerçekleştirebilir ve 'true''yi, Angular'ın değeri gerektiğinde otomatik olarak eklemek veya kaldırmak için kullanacağı farklı bir ifadeyle değiştirebilirsiniz.

Bazı durumlarda, anahtarın dinamik olarak hesaplanması gerekir. Bir değer grubu veya eşleme döndüren bir sinyale veya işleve de bağlanabilirsiniz:

@Component({
  selector: 'my-component',
  template: `...`,
  host: {
    '[class.foo]': 'true',
    '[class]': 'classes()'
  },
})
export class MyComponent {
  size = signal('large');
  classes = computed(() => {
    return [`size-${this.size()}`];
  });
}

Daha karmaşık uygulamalarda, ExpressionChangedAfterItHasBeenCheckedError kullanımından kaçınmak için manuel DOM işleme yöntemini kullanmak cazip gelebilir. Bunun yerine, değeri önceki örnekte olduğu gibi bir sinyale bağlayabilirsiniz. Bu işlem gerektiğinde yapılabilir ve kod tabanınızın tamamında sinyalleri benimsemeniz gerekmez.

Şablonun dışındaki DOM öğelerini değiştirme

DOM'yi, diğer üst veya alt bileşenlere ait olanlar gibi normalde erişilemeyen öğelere erişmek için kullanmayı deneyebilirsiniz. Ancak, bu yöntem hataya açıktır, kapsüllemeyi ihlal eder ve gelecekte bu bileşenlerin değiştirilmesini veya yükseltilmesini zorlaştırır.

Bunun yerine, bileşeniniz diğer tüm bileşenleri bir siyah kutu olarak değerlendirmelidir. Diğer bileşenlerin (aynı uygulamada veya kitaplıkta bile olsa) ne zaman ve nerede bileşeninizin davranışı veya görünümüyle etkileşim kurması ya da özelleştirmesi gerekebileceğini değerlendirin ve bunun için güvenli ve belgelenmiş bir yol sunun. Basit @Input ve @Output özellikleri yeterli olmadığında bir alt ağacın bir alt ağacın kullanımına sunulması için hiyerarşik bağımlılık ekleme gibi özellikleri kullanın.

Geçmişte, kalıcı iletişim kutuları veya ipuçları gibi özelliklerin uygulanması için <body> veya başka bir barındırma öğesinin sonuna bir öğe eklemek ve ardından içeriği buraya taşımak ya da yansıtmak yaygındı. Ancak bu günlerde şablonunuzda basit bir <dialog> öğesi oluşturabilirsiniz:

@Component({
  selector: 'my-component',
  template: `<dialog #dialog>Hello World</dialog>`,
})
export class MyComponent {
  @ViewChild('dialog') dialogRef!: ElementRef;

  constructor() {
    afterNextRender(() => {
      this.dialogRef.nativeElement.showModal();
    });
  }
}

Manuel DOM manipülasyonunu erteleme

Doğrudan DOM manipülasyonunuzu ve erişimi mümkün olduğunca en aza indirmek için önceki yönergeleri kullandıktan sonra, kaçınılması zor bir miktar kalabilirsiniz. Bu gibi durumlarda bu tarihi mümkün olduğunca uzun bir süre ertelemek gerekir. afterRender ve afterNextRender geri çağırmaları, Angular herhangi bir değişiklik olup olmadığını kontrol ettikten ve bunları DOM'ye kaydettikten sonra yalnızca tarayıcıda çalıştığı için bunu yapmanın mükemmel bir yoludur.

Yalnızca tarayıcı JavaScript'i çalıştırma

Bazı durumlarda yalnızca tarayıcıda çalışan bir kitaplığınız veya API'niz olur (örneğin, grafik kitaplığı, bazı IntersectionObserver kullanımı vb.). Tarayıcıda çalışıp çalışmadığınızı koşullu olarak kontrol etmek veya sunucuda davranışı saptırmak yerine sadece afterNextRender kullanabilirsiniz:

@Component({
  /* ... */
})
export class MyComponent {
  @ViewChild('chart') chartRef: ElementRef;
  myChart: MyChart|null = null;
  
  constructor() {
    afterNextRender(() => {
      this.myChart = new MyChart(this.chartRef.nativeElement);
    });
  }
}

Özel düzen oluştur

Bazen hedef tarayıcılarınızın henüz desteklemediği bazı düzenleri (ör. ipucu konumlandırmak) gerçekleştirmek için DOM'yi okumanız veya yazmanız gerekebilir. afterRender, DOM'un tutarlı bir durumda olduğundan emin olabileceğiniz için bunun için mükemmel bir seçimdir. afterRender ve afterNextRender, phase değerini EarlyRead, Read veya Write olarak kabul eder. DOM düzeni yazıldıktan sonra okunduğunda, tarayıcı düzeni eşzamanlı olarak yeniden hesaplamaya zorlar. Bu durum performansı ciddi ölçüde etkileyebilir (bkz. düzen bozma). Bu nedenle, mantığınızı dikkatli bir şekilde doğru aşamalara bölmeniz önemlidir.

Örneğin, sayfadaki başka bir öğeye göre ipucu görüntülemek isteyen bir ipucu bileşeni muhtemelen iki aşama kullanır. EarlyRead aşaması ilk olarak öğelerin boyutunu ve konumunu elde etmek için kullanılır:

afterRender(() => {
    targetRect = targetEl.getBoundingClientRect();
    tooltipRect = tooltipEl.getBoundingClientRect();
  }, { phase: AfterRenderPhase.EarlyRead },
);

Ardından Write aşamasında, ipucunu yeniden konumlandırmak için daha önce okunan değer kullanılır:

afterRender(() => {
    tooltipEl.style.setProperty('left', `${targetRect.left + targetRect.width / 2 - tooltipRect.width / 2}px`);
    tooltipEl.style.setProperty('top', `${targetRect.bottom - 4}px`);
  }, { phase: AfterRenderPhase.Write },
);

Mantıklarımızı doğru aşamalara bölen Angular, DOM manipülasyonunu uygulamadaki diğer tüm bileşenlerde etkin bir şekilde toplu olarak yapabiliyor ve böylece minimum performans etkisi yapabiliyor.

Sonuç

Kullanıcılarınıza mükemmel bir deneyim sunmanızı kolaylaştırmak amacıyla, Angular sunucu tarafında oluşturma işlevinde yeni ve heyecan verici pek çok yeni ve heyecan verici iyileştirme yakında kullanıma sunulacaktır. Önceki ipuçlarının, uygulamalarınızda ve kitaplıklarınızda bunlardan en iyi şekilde yararlanmanıza yardımcı olacağını umuyoruz.