Form doldurduğunuz bir sayfadan yanlışlıkla çıktığınızda doldurduğunuz tüm değerleri kaybedersiniz. Hele ki doldurulması gereken alan çok fazlaysa baya can sıkıcı bir durum olur. Böyle durumların önüne geçebilmek için Angular'ın canDeactivate özelliğinden yararlanabiliriz. Eğer form'da herhangi bir değişiklik varsa ve sayfa değiştirilmek isteniyorsa kullanıcıdan onay alarak bu işlemi yaptırabiliriz.


Basit bir örnek yapalım;

Filmlerin listelendiği ve film ekleme işlemi yapabildiğimiz bir projemiz olsun. Eklemeyi yaptığımız sayfada inputlardan herhangi birine bir şey yazılmışsa ve menüden başka sayfaya tıklanmışsa kullanıcıya confirm() ile uyarı verip, onaylarsa sayfa değişikliği gerçekleşsin, onaylamazsa doldurduğu hiçbir değerini kaybetmeden aynı sayfada kalsın.


1-) Projemizi oluşturalım;

ng new detect-unsaved-changes-in-forms


2-) Componentlerimizi oluşturalım ve route düzenlememizi yapalım;

ng g c components/home
ng g c components/movies
ng g c components/add-movie
ng g c components/categories
ng g c components/add-category


app-routing.module.ts;

const routes: Routes = [
  {
    path: '',
    component: HomeComponent,
    pathMatch: 'full'
  },
  {
    path: 'movies',
    component: MoviesComponent
  },
  {
    path: 'add-movie',
    component: AddMovieComponent
  },
  {
    path: 'categories',
    component: CategoriesComponent
  },
  {
    path: 'add-category',
    component: AddCategoryComponent
  }
]


3-) add-movie componentimiz için formu oluşturalım.

add-movie.component.ts;

import { FormGroup, FormBuilder, Validators } from '@angular/forms';

movieForm: FormGroup;

constructor(
    private fb: FormBuilder
) { }

ngOnInit() {
    this.movieForm = this.fb.group({
      name: ['', Validators.required],
      year: ['', Validators.required],
      image: ['', Validators.required]
    });
    this.movieForm.valueChanges.subscribe( e => this.isDirty = true );
}

addMovie() {
    console.log(this.movieForm.value);
}


add-movie.component.html;

<form [formGroup]="movieForm">
  <div class="form-group">
    <input type="text" formControlName="name">
    <label>Movie Name</label>
  </div>
  <div class="form-group">
    <input type="text" formControlName="year">
    <label>Year</label>
  </div>
  <div class="form-group">
    <input type="text" formControlName="image">
    <label>Image Link</label>
  </div>
  <button type="button" (click)="addMovie()">Add Movie</button>
</form>


4-) Şimdi DirtyComponent interface oluşturacağız. Bu interface'i add-movie componentimizde implement edeceğiz.

ng g models/dirty-component


dirty-component.ts;

import {Observable} from 'rxjs';


export declare interface DirtyComponent {
  canDeactivate: () => boolean | Observable<boolean>;
}


5-) AddMovieComponent içerisinde oluşturduğumuz interface'i kullanalım. Nasıl yapıyoruz bunu;


add-movie.component.ts i açıyoruz. export class AddMovieComponent implements OnInit kısmına bir virgül koyup DirtyComponent yazıyoruz (yukarıda import etmeyi de unutmuyoruz). Daha sonra bu interface'de şart koştuğumuz canDeactive methodunu ekliyoruz.


Tabi bu method içerisinden true ya da false değer döndürmemiz gerekiyor. Bunun içinde bir değişken tanımlayacağız isDirty olsun adı. İlk değerine de false vereceğiz. Ve canDeactivate methodu içerisinde bu değişkeni döndüreceğiz, yani return this.isDirty;


Daha sonra movieForm içerisinde bir değişiklik olursa isDirty değerini true yapacağız. Bu değişikliği de ReactiveForm'ların valueChanges özelliği ile. Yani şu şekilde değişikliği yakalayıp isDirty değerini true olarak değiştireceğiz;

this.movieForm.valueChanges.subscribe( e => this.isDirty = true );


Bunları yaptığımızda add-movie.component.ts dosyamız şu şekilde olacak;

import { DirtyComponent } from '../../models/dirty-component';

export class AddMovieComponent implements OnInit, DirtyComponent {


  movieForm: FormGroup;
  isDirty = false;


  constructor(
    private fb: FormBuilder
  ) { }


  ngOnInit() {
    this.movieForm = this.fb.group({
      name: ['', Validators.required],
      year: ['', Validators.required],
      image: ['', Validators.required]
    });
    this.movieForm.valueChanges.subscribe( e => this.isDirty = true );
  }


  canDeactivate() {
    return this.isDirty;
  }


  addMovie() {
    console.log(this.movieForm.value);
  }


}


6-) Eğer formda değişiklik var ve sayfadan çıkmak istiyorsak, kullanıcıdan onay aldıracağımız DirtyCheck guard'ı oluşturalım.

ng g guards/dirty-check

komutu çalıştırdıktan sonra gelen seçeneklerden canDeactivate'i seçelim, eğer bu seçenek yoksa canActivate'i seçebilirsiniz. İçeriğini değiştirip son haline kavuştururuz.


Bu guard içerisinde canDeactivate() methodunda DirtyComponent'in değerine göre confirm() (onay alma) çalıştıracağız.

Yani şöyle methodumuz şu şekilde olacak;

canDeactivate(
    component: DirtyComponent,
    next: ActivatedRouteSnapshot,
    state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
    if (component.canDeactivate()) {
      return confirm('There are changes you have made to the page. If you quit, you will lose your changes.');
    } else {
      return true;
    }
}


Tüm dosya ise şu şekilde olacak: dirty-check.guard.ts;

import { Injectable } from '@angular/core';
import { CanDeactivate, ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree } from '@angular/router';
import { Observable } from 'rxjs';
import { DirtyComponent } from '../models/dirty-component';


@Injectable({
  providedIn: 'root'
})
export class DirtyCheckGuard implements CanDeactivate<DirtyComponent> {


  canDeactivate(
    component: DirtyComponent,
    next: ActivatedRouteSnapshot,
    state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
    if (component.canDeactivate()) {
      return confirm('There are changes you have made to the page. If you quit, you will lose your changes.');
    } else {
      return true;
    }
  }


}


Buradaki olay şu;

Guard içerisindeki canDeactivate methoduna gelen DirtyComponent'in değeri true ise confirm ile bir uyarı verecek ve kullanıcı Tamam derse bu sayfaya gidecek, Hayır derse sayfadan çıkmamış olacak. Eğer false ise direkt o sayfaya gidebilecek.


Tabi şuan yukarıdaki olayların hiçbirini çalıştırmadık. Çalışabilmesi için son bir adımımız var. Routing içerisinde 'add-movie' rotasına bunu tanımlamak.


7-) AppRoutingModule'de tanımlamamızı yapalım;


Yukarıda yazdığımız işlemler hangi sayfalarımızda geçerli olsun istiyorsak routes içinde o elamana canDeactivate: [] tanımlamasını yapmamız gerekiyor. Yani şu şekilde;

{
    path: 'add-movie',
    component: AddMovieComponent
}

olan tanımlamayı şununla değiştireceğiz;

{
    path: 'add-movie',
    component: AddMovieComponent,
    canDeactivate: [DirtyCheckGuard]
}


Hepsi bu kadar. Basit bir şekilde anlatmaya çalıştım.

Yaptığımız örneğin canlı canlı test edebileceğiniz linkini de buraya bırakayım: https://stackblitz.com/edit/detect-unsaved-changes-in-forms


Umarım faydalı olur.