Angular 6’da Kullanıcı Giriş Ekranında Undo Redo İşlemler

Selamlar,

Bu makalede, Angular 6 ile yazılacak bir iş başvuru web uygulamasında, UNDO – REDO işlemler nasıl yapılır, hep beraber inceleyeceğiz. Bu sistemin sadece Angular için değil, aynı zamanda farklı teknolojiler için de kolaylıkla implemente edilebileceği akıllardan çıkarılmamalıdır.

Bilgisayarda en çok sevdiğim şey, genelde yapılan bir hatanın kolayca geri alınabilmesidir. Örneğin tıpta ya da İnşaat Müh., böyle bir şey söz konusu bile değildir. Bu makalede, her yapılan değişiklikten sonra ilgili model, bir dizide tutulacaktır. Daha sonra istenen durumda, ilgli dizi içerisinde ileri geri hareket edilerek, geçerli indexdeki model alınacaktır. Böyle bir açıklama ile, akıllara gelen ilk design pattern “Memento”‘dan başka bir şey değildir. Memento Design Pattern hakkında daha detaylı bilgiye buradan erişebilirsiniz.

Alttaki komut ile Angular 6 projesi oluşturulur.

Bu örnekte bir iş bavurusu formu oluşturulacaktır.

Model/Person.ts: Başvuru yapacak kişinin view model’i aşağıdaki gibidir.

src/app/app.component.ts:

  • “model: Person” : Sayfada kullanılacak model tipi “Person”‘dır.
  • “tr” : Sayfada PrimeNG kullanılmıştır. Takvimde [locale]=”tr” attribute’ü ile kullanılacak dil tanımlanmıştır.
  • “isModelChange” : Input alanlardan ayrılındığında yani (blur)’da, datanın değişip değişmediği kontrol edilmişitr.
  • “changedModel” :  Değişen datanın, UserControl(navigate.component)’a gönderilerek, container dizisinde saklanacağı “person” model değişkenidir.
  • “constructor(public service: cloneService)” : Sayfa yüklenirken kullanılacak servis, “dependency injection” ile constructor’da sayfaya eklenir.
  • “public ngOnInit() { }”: İlgili method, Angular tarafında bir page load methodu gibidir. Sayfa ilk yüklenirken, örnek amaçlı dummy data oluşturulur ve “changedModel“, yani değişen data değişkenine atanır. Bu da “navigate.component” UserController’ında bir history listesine eklenir.
  • “this.tr= { }” : PrimeNGkütüphanesi ile kullanılacak takvim yani “<p-calendar” controlünün türkçe dil desteği buradaki tanımlama ile yapılır.
  • “checkModel()” : İlgili modelde değişiklik olmuş ise, bir sonraki adımda anlatılacak service ile değişen model’in deep copy’si alınır.
    • isModelChange: View Model’de değişiklik olup olmadığına bakar.
    • isCheck: Bazı html elementlerde model’in değişip değişmediğine bakılmasına gerek yoktur. Yazının devamında detaylıca anlatılacaktır.
  • getCurrentData(): Undo, Redo ile gelinen geçerli index’deki Person data, ilgili model’e atanır.

npm install –save lodash” Yandaki komut ile Lodash projeye yüklenir. Peki neden mi? Az sonra :)

cloneService.ts: Amac, bir “Person[]” dizinin deep copy’sini almaktır. Yani referance değerleri tamamen farklı, yeni bir eşleniğini oluşturmaktır. Bu işlemin yapılabileceği 3. party kütüphanelerden biri de Lodash’dir. Peki neden? Çünkü değişime uğrayan herbir model, kendine özgüdür. Kopyalanan ve ilgili diziye atılan herbir modelin, var olan sistemden bağımsız olmaması durumunda, hali hazırda çalışılan model üzerindeki değişimlerin tümü, bu kopyalanan modelleri etkileyecekti. İlgili deep copy işlemi projenin farklı birçok yerinde yapıldığı için, kod kalabalığından kurtulma amaçlı servis haline getilirilmiştir. Ayrıca Generic tipte bir değişken beklediği için, kendisine gönderilen tüm data modelleri clonelayabilmektedir.

app.component.html: Örnek amaçlı başvuru formunun girildiği ekrandır. Css olarak bootstrap kullanılmıştır. Kullanılan Html elementler PrimeNG’ye aittir.

  • <navigate-console [modelPerson]=”changedModel” (getdata)=”getCurrentData($event)”></navigate-console>” : Tüm sayfalarda kullanılacak, Undo-Redo işlemlerinin yapıldığı UserControl’dür. İlerde detaylıca incelenecektir.
  • “<input id=”float-input” type=”text” size=”30″ pInputText [(ngModel)]=”model.Name” (ngModelChange)=”isModelChange=true” (blur)=”checkModel()”>”: İsmin yazıldığı input alandır.
    • [(ngModel)]=”model.Name” : “User.Name” alanına 2 yönlü şekilde bağlanılmıştır.
    • (ngModelChange)=”isModelChange=true” : İlgili Name property’si değiştiği zaman, bu değişken’e true değeri atılır. Bu şekilde değişen data Undo-Redo’da, değişen data listesine atanır.
    • (blur)=”checkModel()” : Input alanlarda, her tuşa basıldığında değişen data history listesine atılmaması amacı ile, input alandan sadece ayrılınca(blur), değişen ilgili model, “checkModel()” methoduna gönderilip servis yardımı ile clone’u alınır.
  • “<input type=”checkbox” [(ngModel)]=”model.IsMale” (ngModelChange)=”checkModel(true)”/>” : Cinsiyet checkBox’ı [(ngModel)] ile “IsMale” propertysine bağlanmıştır. (ngModelChange) eventinde, kısaca checkbox tıklandığı zaman, model değişir ve ilgili event tetiklenir. “checkModel(true)” methodu çağrılırken “IsCheck=true” atanarak, modelin değişip değişmediğine bakılmadan doğrudan servis yardımı ile clone’u alınır.

  • “<p-calendar [(ngModel)]=”model.BirthDate” (ngModelChange)=”checkModel(true)” [inline]=”true” showOtherMonths=”true” yearNavigator=”true” yearRange=”1960:2020″ monthNavigator=”true” showTime=”true” hourFormat=”24″ showButtonBar=”true” [locale]=”tr”></p-calendar>”: Bu component, PrimeNG açık kaynak kodlu Angular kütüphanesine aittir.
    • Not: PrimeNG kurmak için yandaki komutların çalıştırılması gerekmektedir. “npm install primeng –save“, “npm install primeicons –save“. PrimeNG Angular, React gibi birçok javascript kütüphanesine component yazan bir türk firmasıdır. Hakkında daha detaylı bilgiye  buradan erişebilirsiniz.
    • “[(ngModel)]=”model.BirthDate”” :BirthDate alanına 2 yönlü bind işlemi yapılır.
    • “(ngModelChange)=”checkModel(true)””: Yeni bir tarih seçildiğinde geçerli model değiştirilmiş olunur. Bu durumda model değişmiş mi diye bakılmadan “checkModel()” methodu ile “deep copy”‘si alınır. Diğer propertyler’i PrimeNG sayfasından incelenebilir.
  • “<p-slider [(ngModel)]=”model.Salary” [range]=”true” [step]=”1000″ [min]=”1000″ [max]=”20000″ (onSlideEnd)=”checkModel(true)”></p-slider>” : Bu da gene PrimeNG’ye ait bir controllerdır. Amaç belli bir aralıkta maaş değeri seçmektir.
    • “[(ngModel)]=”model.Salary”” : Salary property’sine bağlanmıştır.
    • “(onSlideEnd)=”checkModel(true)”” : Herbir slide yani yana kaydırma hareketinden ziyade, kaydırma işlemi bittikten sonra “checkModel()” methodu çağrılıp, değişen model’in service yardımı ile clone()’lanmaktadır.

Şimdi sıra geldi esas işin yani, Undo-Redo işlemlerinin yapıldığı navigate.component.ts‘e.

navigate.component.ts: İstenen herhangi bir sayfaya rahatlıkla implemente edilebilmesi amacı ile, UserControl olarak oluşturulmuştur. Memento Design Patterninin kullanıldığı yapıda, ileri geri button durumları, geçerli Index numarası ve değişen model’in tüm listesi bu component üzerinde bulunmaktadır. Böylece hangi sayfa üzerine konursa konsun, ileri geri button aktifliği, bir önceki veya bir sonraki kayda erişim, ilgili sayfa üzerindeki model geçmişi gibi yapıların tamamı bulunduğu sayfadan bağımsız bir controller üzerinde bulunacak ve böylece kod kalabalığından da kaçınılacaktır.

<navigate-console [modelPerson]=”changedModel” (getdata)=”getCurrentData($event)”></navigate-console>
  • Constructor’da : KeyValueDiffers ve cloneService providerları dependency injection ile sınıfa dahil edilmişlerdir.
    • KeyValueDiffers: Service’e @input olarak gelen complex tiplerin, mesela Person modelinin herhangi bir property’sinin değişip değişmediğinin anlaşılması için kullanılır. Klasik @input, property olarak tanımlandığında, basit string veya number gibi tiplerin değişimi algılanabilir.
    • cloneService: İlgili user controller’a gelen modelin deep copy’sinin alınması için kullanılmıştır.
  • “container: Person[] = []” : Değişen model history’nin saklanacağı dizidir.

  • “currentIndex: number = 0” : Dizi içerisinde bulunulan Index.
    • Not: En büyük indexli eleman, dizideki ilk elemandır. Yukarıda görüldüğü gibi, son elemanın Index’i 0’dır.
  • “@Input() modelPerson: Person”: User Control’a değişken olarak bulunduğu sayfadan aktarılan değişen person model’i.
  • “ngDoCheck()” : Gönderilen model’in değişip değişmediğine bakılır ve değişmiş ise person history dizisine(container)’a atılır.
  • “@Output() getdata” : Undo veya Redo işleminden sonra, gelinen o andaki indexdeki model ile birlikte tetiklenen event’dir. Böylece User Control’ün bulunduğu sayfadaki model, bu seçilen model ile değiştirilir.
  • “undoClick()” : Bir önceki yapılan işleme dönülür.
    • “var getIndex = this.container.length – 1 – this.currentIndex” : currentIndex bir arttırılarak, tersten Person[]container’da, bir gerideki değişen eski modelin indexi alınır.
    • “var data = this.service.cloneModel(this.container[getIndex])” : User Control’ün bulunduğu sayfaya gönderilecek bir önceki modelin, deep copy’si alınır.
      • “this.getdata.emit(data)” : getdata event’i, clone alınan data ile tetiklenir. Bu da app.component sayfasındaki “getCurrentData()” methodunu, ilgili data ile çağırmakta ve bir önceki kayıt, var olan person model ile değiştirilmektedir. Bu şekilde ilgili modele 2 way binding ile bağlı olan PrimeNG controllerlar, artık bir önceki yapılan değişikliği göstermektedirler.
  • “redoClick()” : Bir sonraki yapılan işleme gidilir.
    • “this.currentIndex–” : Index 1 azaltılır.
    • “var getIndex = this.container.length – 1 – this.currentIndex” : Tersten Person[]container’da, bir sonraki kaydın indexi alınır.
    • “var data = this.service.cloneModel(this.container[getIndex])”: User Control’ün bulunduğu sayfaya gönderilecek bir sonraki modelin, deep copy’si alınır.
      • “this.getdata.emit(data)”: getdata event’i, clone alınan data ile tetiklenir. Bu da app.component sayfasındaki “getCurrentData()” methodunu, ilgili data ile çağırmakta ve bir sonraki kayıt, var olan person model ile değiştirilmektedir. Bu şekilde ilgili modele 2 way binding ile bağlı olan PrimeNG controllerlar, artık bir sonraki yapılan değişikliği göstermektedirler.
  • “pushContainer()” : Amaç, değişime uğruyan modeli history amaçlı Person[] container’a atmaktır.
    • “if (this.currentIndex > 0 && this.currentIndex < this.container.length – 1) {” : Undo -Redo adımlarında gezinirken, klasik sistemlerden bağımsız olarak, araya yeni bir değişen Person model’i Insert edilebilmektedir. Yani 1,2,3,4 indexli bir dizide 2 ile 3. index arasında iken, yeni bir Person model konulup, 3=>4 ve 4=>5 olarak ötelenir.
    • “this.container.push(per)”: Son indexde duruluyor ise, değişen person kaydı listeye doğrudan atılır.
  • “public canUndo(): boolean {” : Undo(Geri) butonunun aktifliğini belirlenir.
    • “return (this.container.length < 2 || this.currentIndex >= this.container.length – 1)”: Eğer 1’den fazla kayıt yok ise, ya da son index yani ilk kayıtta bulunuluyor ise, Geriye Dönüş buttonu(Undo) pasif hale getirilir.
  • “public canRedo(): boolean {” : Redo(İleri) butonunun aktifliğini belirlenir.
    • “return this.currentIndex<=0” : Eğer son kayıtta ise, İleri buttonu(Redo) pasif hale getirilir.

navigate.component.html: Tüm sayfalarda kullanılabilecek, datanın bir önceki veya bir sonraki halinin alınabileceği UserControl.

  • Undo (Geri) buttonu “canUndo()” methodu ile aktiflik propery’si tanımlanmıştır.
  • Undo (click)  eventinde bir önceki kayda gidilecek, yukarıda anlatılan “undoClick()” methodu çalıştırılır.
  • Redo (ileri) buttonu “canRedo()” methodu ile aktiflik propery’si tanımlanmıştır.
  • Redo (click)  eventinde bir sonraki kayda gidilecek, yukarıda anlatılan “redoClick()” methodu çalıştırılır.

Geldik bir makalenin daha sonuna. Bu makalede memento design pattern kullanılarak, işlem yapılan data üzerinde geri ve ileri  hareket edilmiş ve tarihçesi tutulmuştur. Bu işlem Angular 6 üzerinde yapılsa da, farklı bir yapı üzerinde de kolaylıkla uygulanabilirdi. İlgili Undo-Redo buttonları, User Control haline getirilip tüm bussines tek bir yerde tutulduğu için,  kullanıldığı sayfalara minimum kod yükü getirmiştir. Ayrıca değişen model’in clone’u, ayrı bir serviste generic tipte bir parametre kullanılarak alındığı için, tüm model ve sayfalara hızlıca implemente edilebilmiştir. Bu sistemde, modelde oluşan her değişik durumunda, üzerinde çalışılan modelin tamamı bir container list’e [] atılmıştır. Siz bu projeyi performans anlamında bir adım daha öteye götürerek, ilgili modelde sadece değişen property’i bir container list []’e tutabilirsiniz. Böylece ilgili container listesinin, client’ın rem’inde daha az  yer kaplamasını ve daha hızlı çalışmasını sağlamış olursunuz. Tabi bu durumda, undo redo işlemlerinde değişen propertylerin, var olan modele implemente edilmesi gerekmektedir.

Yeni bir makalede görüşmek üzere hoşçakalın.

Source Code : https://github.com/borakasmer/UndoRedoOperationsonAngular6

Herkes Görsün:

Bunlar da hoşunuza gidebilir...

5 Cevaplar

  1. KERİM dedi ki:

    Teşekkürler hocam güzel bir yazı

  2. Tagi dedi ki:

    İşte bu !
    Teşekkürler

  3. Selim dedi ki:

    Merhaba bora bey, yazı serileriniz için çok teşekkür ederim. Anlattığınız konudan bağımsız bir sorum olacak. 5-6 user type barındıran sistemim için angular tarafında sayfalarda gösterilmesi gereken alanlar, toollar farklı olabiliyor. Bunu basit bir şekilde sayfalara anguların if directivleriyle çözüyorum,fakat günün sonunda sadece bir sayfa için bile onlarca if directivi ile karşılaşabiliuorum. Bu probleme daha iyi bir yaklaşımınız var mıdır?
    Şimdiden teşekkür ederim.

  4. ismail dedi ki:

    Merhaba,
    Teşekkürler video için. Veritabanından gelen datayı dinamik olarak forum oluşturan bir video yapabilir misiniz ? Örneğin; veritabanında Adı(textbox) , cinsiyeti (checkbox), resmi (photo) , uyrugu (combobox) şeklinde veri olduğunu düşünelim. Html bu dataya göre oluşacak ve gerekli insert update işlemlerini yapacak. Bu konuda kısa da olsa video yapabilirseniz çok sevinirim. Şimdiden teşekkürler.

borsoft için bir cevap yazın Cevabı iptal et

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