Angular 4 – Route Guards CanDeactivate

Merhaba,

Bir önceki Angular 4 – Route Guards CanActivate başlıklı makalemde Angular uygulamalarında Route Guards yapısından söz ederek CanActivate yaklaşımını değerlendirmiştik. CanActivate yaklaşımı, kullanıcıyı yönlendirirken detaylı kontrol mekanizması sağlamaktadır. Hatta ilgili makalemde bu konuyla alakalı oturum açma senaryosunu örnek olarak vermiş, bir sayfaya erişim sağlamaya çalışan kullanıcı eğer oturumu girmemişse login sayfasına yönlendirmiştik.

Bu içeriğimizde ise Route Guards yapısında CanDeactivate yaklaşımını değerlendireceğiz. Bu yaklaşımın kısa ve öz tarifi, kullanıldığı sayfadan başka sayfaya geçiş kontrolü getirmesidir. Tabi bu kontrolü bizler belirli koşullara bağlayarak hareket edeceğiz.

CanDeactivate için sıca sıcağına bir örnek vermek gerekirse eğer kullanıcılarla etkileşime giren form yapılarının bulunduğu sayfaları örnek olarak verebiliriz. Mesela şirketinizin iletişim formuna dakikalarca tüm duygu ve düşüncesini yansıtan kullanıcının klavyede yanlış bir tuşa basmasından ya da fareyle hiç hesaplamadığı tarayıcının çıkış(X) butonuna tıklamasından doğacak olan zayiat kullanıcıda zaafiyete sebep olacaktır ve aynı emek ve gayreti birdaha yaşatma zarureti muhtemelen vazgeçmeyle sonuçlacaktır.

İşte bizlerin web geliştiricisi olarak bu tarz durumlara karşı kullanıcı merkezli önlemler almamız gerekmekte ve form nesnelerine girilen herhangi bir değer varsa o değerler post edilmediği sürece başka sayfaya geçişi engellememiz gerekmektedir.

Ahanda böyle durumlarda Route Guards yapısının CanDeactivate yaklaşımı ihtiyacımızı yeterince karşılık verebilecektir… 🙂

Şimdi bahsettiğimiz örnek mantıkta olayı pratikleştirelim ve gerçek bir senaryo üzerinden CanDeactivate yaklaşımının nasıl uygulandığını ele alalım.

Diyelim ki şirketinizin sitesinde “İletişim” ve “Ürünler” sayfası olsun.

export const AppRouter: Route[] = [
  { path: "", redirectTo: "/urunler", pathMatch: "full" },
  { path: "urunler", component: UrunlerComponent },
  { path: "iletisim", component: IletisimComponent }
];

Hemen iletişim componentinde gerekli form çalışmasını inşa edelim.

<form [formGroup]="frmIletisim" (ngSubmit)="MesajIlet(frmIletisim.value)">
  <div>
    Adınız
    <br>
    <input placeholder="Adınız..." type="text" formControlName="txtAd">
  </div>
  <div>
    Soyadınız
    <br>
    <input placeholder="Soyadınız..." type="text" formControlName="txtSoyad">
  </div>
  <div>
    Mesajınız
    <br>
    <textarea placeholder="Mesajınız..." formControlName="txtMesaj" rows="5"></textarea>
  </div>
  <div>
    <button>Mesajı Gönder</button>
  </div>
</form>
import { Component, OnInit } from '@angular/core';
import { FormGroup, FormControl } from "@angular/forms";
@Component({
  selector: 'app-iletisim',
  templateUrl: './iletisim.component.html',
  styleUrls: ['./iletisim.component.css']
})
export class IletisimComponent implements OnInit {
  frmIletisim;
  constructor() { }
  ngOnInit() {
    this.frmIletisim = new FormGroup({
      txtAd: new FormControl(),
      txtSoyad: new FormControl(),
      txtMesaj: new FormControl()
    });
  }
  MesajIlet(data) {
    alert(data.txtAd + " " + data.txtSoyad + " " + data.txtMesaj);
  }
}

Evet artık formumuz hazır. Şöyle bir göz atarsak eğer formumuza veri girildiği vakit olası sayfa değişikliği yahut tarayıcı kapatma durumlarında ilgili veri göz ardı edilmekte ve yanlışlıkla yapılan aksiyon neyse gerçekleştirilmektedir.
Angular 4 - Route Guards CanDeactivate

Şimdi Route Guards CanDeactivate kontrolünü sağlayabilmek için “FormKontrol” isminde bir sınıf oluşturalım.

import { CanDeactivate } from "@angular/router";
import { IletisimComponent } from "./iletisim/iletisim.component";
import { Observable } from "rxjs/Observable";

export class FormKontrol implements CanDeactivate<IletisimComponent> {
    canDeactivate(component: IletisimComponent): boolean | Observable<boolean> {
        alert("Başka sayfaya geçiş yasak...");
        return false;
    }
}

Görüldüğü üzere sınıfımızı oluşturduk. “CanDeactivate” interface’ini hangi componente özel kullanacaksak o componenti(IletisimComponent) Generic olacak şekilde belirterek sınıfa implement ediyoruz ve bu işlem sonucunda “canDeactivate” metodunu uygulamış oluyoruz. Bu metot geriye boolean tipte değer dönen bir fonksiyondur. Belirtilen component üzerinde(IletisimComponent) farklı bir sayfaya geçiş talebi geldiğinde ilgili metot true değeri döndürürse geçiş sağlanacak yok eğer false değeri döndürürse geçiş engellenmiş olacaktır.

Peki componentimiz bu sınıfın CanDeactivate sınıfı olup olmadığını nereden bilecek? diye sorabilirsiniz. Nasıl ki önceki yazımızda CanActivate’e özel sınıfı route yapılanmasında “canActivate” özelliğinde belirttiysek, benzer şekilde bu sınıfıda route yapılanmasında “canDeactivate” özelliğinde belirteceğiz.

export const AppRouter: Route[] = [
  { path: "", redirectTo: "/urunler", pathMatch: "full" },
  { path: "urunler", component: UrunlerComponent },
  { path: "iletisim", component: IletisimComponent, canDeactivate: [FormKontrol] }
];

Vee tabi bu işlemden sonra ilgili sınıfı ana modülümüzde provider özelliğine eklememiz gerekecektir. Ben zihinlerde hiçbir karışıklığa mahal vermemek için tüm “app.module” dosyasının içeriğini aşağıya alıyorum.

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { Route, RouterModule } from "@angular/router";
import { ReactiveFormsModule, FormControl } from "@angular/forms";
import { AppComponent } from './app.component';
import { UrunlerComponent } from './urunler/urunler.component';
import { IletisimComponent } from './iletisim/iletisim.component';
import { FormKontrol } from './form-kontrol';
export const AppRouter: Route[] = [
  { path: "", redirectTo: "/urunler", pathMatch: "full" },
  { path: "urunler", component: UrunlerComponent },
  { path: "iletisim", component: IletisimComponent, canDeactivate: [FormKontrol] }
];
@NgModule({
  declarations: [
    AppComponent,
    UrunlerComponent,
    IletisimComponent
  ],
  imports: [
    BrowserModule,
    RouterModule.forRoot(AppRouter),
    ReactiveFormsModule
  ],
  providers: [FormKontrol],
  bootstrap: [AppComponent]
})
export class AppModule { }

Evet… Şimdi uygulamamızı kaydedip, çalıştırdığımızda aşağıdaki gibi bir sıkıntıyla karşılaşacağız.
Angular 4 - Route Guards CanDeactivate
Görüldüğü üzere form doluda olsa boşta olsa başka bir sayfaya geçiş izni verilmemektedir. Bizler sadece form doluyken bu kontrolü sağlamak istiyoruz. Mantıklı olan formda herhangi bir değer yoksa başka sayfaya geçiş yapılabilmesidir. Bu handikaptan kurtulmak için yazmış olduğumuz “FormKontrol” sınıfına göz atmalıyız. Evet, burada dikkat ederseniz “canDeactivate” metodunda false değeri döndüğü için her durumda “IletisimComponent” sayfasından başka sayfalara geçiş izni verilmeyecektir.

Burada componentin durumuna göre bir değerlendirme olmadığı için bir mantık hatası söz konusudur. Yapılması gereken “IletisimComponent” sınıfında form durumu değerlendirilmeli ve bu duruma göre “FormKontrol” sınıfında çalışma gerçekleştirilmelidir.

<form  [formGroup]="frmIletisim" (ngSubmit)="MesajIlet(frmIletisim.value)">
  <div>
    Adınız
    <br>
    <input (change)="txtAd=$event.target.value;" placeholder="Adınız..." type="text" formControlName="txtAd">
  </div>
  <div>
    Soyadınız
    <br>
    <input (change)="txtSoyad=$event.target.value;" placeholder="Soyadınız..." type="text" formControlName="txtSoyad">
  </div>
  <div>
    Mesajınız
    <br>
    <textarea (change)="txtMesaj=$event.target.value;" placeholder="Mesajınız..." formControlName="txtMesaj" rows="5"></textarea>
  </div>
  <div>
    <button>Mesajı Gönder</button>
  </div>
</form>

Tüm form elemanlarına change eventı eklenmiştir ve o anki değerleri elde edilerek componentteki değişkenlere aktarılmıştır.

import { Component, OnInit } from '@angular/core';
import { FormGroup, FormControl } from "@angular/forms";
@Component({
  selector: 'app-iletisim',
  templateUrl: './iletisim.component.html',
  styleUrls: ['./iletisim.component.css']
})
export class IletisimComponent implements OnInit {
  txtAd: string = "";
  txtSoyad: string = "";
  txtMesaj: string = "";
  frmIletisim;
  FormKontrol(): boolean {
    if (this.txtAd.length > 0 || this.txtSoyad.length > 0 || this.txtMesaj.length > 0)
      return false;
    return true;
  }
  constructor() { }
  ngOnInit() {
    this.frmIletisim = new FormGroup({
      txtAd: new FormControl(),
      txtSoyad: new FormControl(),
      txtMesaj: new FormControl()
    });
  }
  MesajIlet(data) {
    alert(data.txtAd + " " + data.txtSoyad + " " + data.txtMesaj);
  }
}

Yukarıdaki kod bloğunu incelerseniz eğer “IletisimComponent” sınıfında “FormKontrol” metodu ile form değerleri kontrol edilmekte ve duruma göre geriye boolean değer döndürülmektedir. İşte bu fonksiyonu “FormKontrol” sınıfımızda aşağıdaki gibi kullanabiliriz;

import { CanDeactivate } from "@angular/router";
import { IletisimComponent } from "./iletisim/iletisim.component";
import { Observable } from "rxjs/Observable";

export class FormKontrol implements CanDeactivate<IletisimComponent> {
    canDeactivate(component: IletisimComponent): boolean | Observable<boolean> {
        if (!component.FormKontrol()) {
            alert("Başka sayfaya geçiş yasak...");
            return false;
        }
        return true;
    }
}

Görüldüğü üzere “FormKontrol” sınıfındaki “canDeactivate” fonksiyonuna tanımladığımız “component” parametresi bizlere üzerinde çalıştığımız componenti getirmektedir. Dolayısıyla ilgili componentimizde tanımladığımız “FormKontrol” isimli metoda erişebilmekte ve metodun değerlendirmesine göre sayfa geçiş izni verilmekte yahut verilmemektedir.
Angular 4 - Route Guards CanDeactivate

Gördüğünüz üzere amacımıza uygun işlevsellikte bir çalışma gerçekleştirmiş bulunmaktayız.

Peki hoca, ben başka bir componentte de “FormKontrol” sınıfını kullanmak istesem nasıl olacak? diye sorabilirsiniz ki güzel bir soru… “FormKontrol” sınıfı “IletisimComponent” sınıfına uygun bir şekilde tasarlandığı için ilgili component dışında başka bir componentte çalışmayacaktır. Dolayısıyla şu anki vaziyette “FormKontrol” sınıfını başka bir component için kullanamayacağız. Yapmamız gereken ya her bir component için farklı bir “FormKontrol” sınıfına benzer sınıf oluşturmak (tabi ki de böyle bir yöntemi tercih etmeyeceğiz, ayıptır 🙂 ) ya da OOP’nin nimetlerinden faydalanıp oluşturduğumuz “FormKontrol” sınıfını daha evrensel bir hale getireceğiz. Haliyle bizler ikinci yöntemi tercih edecek ve “FormKontrol” sınıfını “IletisimComponent” sınıfına olan bağımlılıktan kurtaracak, bir nevi Dependency Injection yaparak bağımsız bir sınıf haline getireceğiz.

Bu işlem için aşağıdaki kod bloğunu inceleyiniz.

import { CanDeactivate } from "@angular/router";
import { IletisimComponent } from "./iletisim/iletisim.component";
import { Observable } from "rxjs/Observable";

export interface IFormKontrol {
    FormKontrol(): boolean | Observable<boolean>;
}

export class FormKontrol implements CanDeactivate<IFormKontrol> {
    canDeactivate(component: IFormKontrol): boolean | Observable<boolean> {
        if (!component.FormKontrol()) {
            alert("Başka sayfaya geçiş yasak...");
            return false;
        }
        return true;
    }
}

Gördüğünüz gibi “IFormKontrol” isminde bir interface oluşturularak içerisine geriye boolean tipte değer dönen “FormKontrol” isminde bir metot tanımlanmıştır. “FormKontrol” sınıfımızda ise implement edilen “CanDeactivate” interface’inde oluşturduğumuz “IFormKontrol” interface’i Generic olarak belirtilmiştir. Dolayısıyla “FormKontrol” sınıfı tüm “IFormKontrol” sınıfını uygulayan componentler için CanDeactivate kontrolü görebilecektir.

import { Component, OnInit } from '@angular/core';
import { FormGroup, FormControl } from "@angular/forms";
import { IFormKontrol } from '../form-kontrol';

@Component({
  selector: 'app-iletisim',
  templateUrl: './iletisim.component.html',
  styleUrls: ['./iletisim.component.css']
})
export class IletisimComponent implements OnInit, IFormKontrol {
  txtAd: string = "";
  txtSoyad: string = "";
  txtMesaj: string = "";
  frmIletisim;
  FormKontrol(): boolean {
    if (this.txtAd.length > 0 || this.txtSoyad.length > 0 || this.txtMesaj.length > 0)
      return false;
    return true;
  }
  constructor() { }
  ngOnInit() {
    this.frmIletisim = new FormGroup({
      txtAd: new FormControl(),
      txtSoyad: new FormControl(),
      txtMesaj: new FormControl()
    });
  }
  MesajIlet(data) {
    alert(data.txtAd + " " + data.txtSoyad + " " + data.txtMesaj);
  }
}

“IletisimComponent” dışında başka bir component üzerinde denersek eğer;

import { Component, OnInit } from '@angular/core';
import { IFormKontrol } from '../form-kontrol';
@Component({
  selector: 'app-yorum',
  templateUrl: './yorum.component.html',
  styleUrls: ['./yorum.component.css']
})
export class YorumComponent implements OnInit, IFormKontrol {
  constructor() { }
  txtYorum: string = "";
  FormKontrol() {
    if (this.txtYorum.length > 0)
      return false;
    return true;
  }
  ngOnInit() {
  }
}
<input type="text" [(ngModel)]="txtYorum"/>
<button>Gönder</button>
export const AppRouter: Route[] = [
  { path: "", redirectTo: "/urunler", pathMatch: "full" },
  { path: "urunler", component: UrunlerComponent },
  { path: "iletisim", component: IletisimComponent, canDeactivate: [FormKontrol] },
  { path: "yorum", component: YorumComponent, canDeactivate: [FormKontrol] }
];

Gerekli alanlara yeni componentimizin adresinide verdikten sonra aşağıdaki gibi “FormKontrol” sınıfımızın bir componente bağımsız olacak şekilde çalıştığını görebilirsiniz.
Angular 4 - Route Guards CanDeactivate

Evet, Bu makalemiz neticesinde Angular 4 ile Route Guards yapılarınıda incelemiş olduk.

Umarım faydalı bir yazı olmuştur.

Sonraki yazılarımızda görüşmek üzere…
İyi çalışmalar…

Bunlar da hoşunuza gidebilir...

2 Cevaplar

  1. Mustafa dedi ki:

    Güzel açıklama.
    Maximilian Schwarzmüller udemy kursundan dinleyip anlamamıştım.
    Ama burada her şey pekişti.
    Teşekkürler 🙂

Bir cevap yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

*