Merhabalar arkadaşlar. Bu yazıda sizlere son zamanlardaki yeni öğrenme ve uğraş alanım olan Angular 2’nin web servis işlemleri üzerindeki kullanımını anlatmaya çalışacağım. Angular 2 ile bu yazıda GET ve POST işlemini örnekleyeceğiz.

Bu yazıda Angular 2 tarafında yazdığım proje için kaynak kodlara şu adresten ulaşabilirsiniz: https://github.com/ilkgunel/AngularAndRestUsageSample

Öncelikle özet olarak olayın web service tarafına bakalım. Benim MySQL veri tabanında Formula1 adında bir veri tabanım var ve içinde Drivers tablosu var.Drivers tablosu şu şekilde:

Bu tabloya göre Angular 2 çalışmak için RestEasy kütüphanesi ile bir web servis yazdım. Bu web servis tablodaki tüm kayıtları sunabilen, id bilgisine göre bir driver’ı gösteren ve tablo üzerinde güncelleme yapabilen bir web servis. Örneğin http://localhost:8080/RestfulWebServiceTutorials/webservice/formula1/driverList adresi şu resimdeki gibi JSON formatında bize tüm driver’ların listesini sunar:

http://localhost:8080/RestfulWebServiceTutorials/webservice/formula1/getViaId/{id} adresi ise gelen id bilgisine göre bir driver gösterecek. Şu şekilde:

Şimdi biz Angular 2 ile bu servis üzerinden driver listesi sunacağız, tekil driver bilgisini göstereceğiz, driver bilgisini güncelleyeceğiz. Başlayalım 💪 💪

index.html

Hedeflediğimiz GET ve POST işlemleri bir sayfa üzerinden yürüyecek ve burada bu iş index.html üzerinden olacak.

<html>
  <head>
    <title>Angular 2 QuickStart</title>
    <meta charset="UTF-8">
    <base href="/">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="styles.css">

    <!-- Polyfill(s) for older browsers -->
    <script src="node_modules/core-js/client/shim.min.js"></script>

    <script src="node_modules/zone.js/dist/zone.js"></script>
    <script src="node_modules/reflect-metadata/Reflect.js"></script>
    <script src="node_modules/systemjs/dist/system.src.js"></script>

    <script src="systemjs.config.js"></script>
    <script>
      System.import('app').catch(function(err){ console.error(err); });
    </script>
  </head>

  <body>
    <my-app>Loading...</my-app>
  </body>
</html>

Index sayfası üzerinde dikkatinizi iki noktaya çekmek istiyorum. Birincisi System.import(‘app’) ifadesi ile biz bu sayfaya Angular’ın çalışması için gerekli bileşenleri yüklemiş olduk. Bu yükleme işlemi systemjs.config.js dosyası içindeki tanımlamalar sayesinde gerçekleşiyor. İkinci nokta ise body tag’inin içi. Buradaki my-app app.component.ts dosyası içinde tanımlı bir component. System.import(‘app’) satırı sayesinde app.component.ts’i özel olarak tanıtmaya gerek kalmaksızın içindeki component’i çağırıp kullanabildik.

app.component.ts

import { Component } from '@angular/core';
import { DriverService } from './driver.service';


@Component({
  selector: 'my-app',
  template: `
  <h1>  </h1>
  <router-outlet>
  `,
  providers: [DriverService]
})
export class AppComponent {
  title:string = 'Formula 1 Drivers';
}

*.component.ts şeklindeki isim kalıbına sahip dosyalar içinde bileşen tanımlarımız gerçekleştiriliyor. Bu bileşen tanımlarını html sayfamız içinde çağırıp kullanabiliyoruz. app.component.ts dosyamız içerisinde

  • selector ifadesi bu bileşenin tanımlayacısı konumundadır. Yani html içerisinde ben bu bileşeni kullanmak istersem selector özelliğinde tanımlı değer ile ulaşıyorum.
  • template kısmında ise bu bileşen çağırıldığı sayfa içerisinde yer alacak şablonu tanımlıyoruz. title kısmı sabit olarak AppComponent sınıfından gelecek. router-outlet yazan kısımda ise ifade edilmek istenen bu componenet’in içerğinin URL path’ine göre gelecek bileşenin template’i olacağıdır. Az sonra app.routes.ts dosyasını inceleyeceğiz ve oradaki tanımları inceleyince burası daha iyi oturacak.
  • Web servisten GET ve POST işlemlerini yapacak metotları barındıran DriverService sınıfını da burada app bileşenine kayda geçiriyoruz.

app.routes.ts

import { Routes, RouterModule } from '@angular/router';

import { DriverListComponent } from './driver-list.component';
import { DriverDetailsComponent } from './driver-details.component';

const routes: Routes = [
  {
    path: 'drivers',
    component: DriverListComponent,
  },
  {
    path: 'drivers/:driverId',
    component: DriverDetailsComponent
  },
  {
    path: '',
    redirectTo: '/drivers',
    pathMatch: 'full'
  },
];

export const routing = RouterModule.forRoot(routes);

app.routes.ts dosyası içerisinde hangi path’te hangi hangi component’in render edileceği bilgisi yer alıyor.

  • import { Routes, RouterModule } from ‘@angular/router’; satırı ile Angular’ın yönlendirici kütüphanesini dahil ediyoruz.
  • import { DriverListComponent } from ‘./driver-list.component’; satırı ile sürücüleri listeleyecek componenet’i dahil ediyoruz.
  • import { DriverDetailsComponent } from ‘./driver-details.component’; satırı ile sürücü bilgilerini gösterecek component’i dahil ediyoruz.

Kodun devam eden kısmında Routes tipinde ve routes isminde bir sabit tanımlıyoruz ve içerisinde hangi path bilgisinde hangi component’in render edileceği bilgisini veriyoruz.

  • path: ‘drivers’, component: DriverListComponent ifadesi ile drivers path’inde DriverListComponent render edilecek,
  • path: ‘drivers/:driverId’, component: DriverDetailsComponent ifadesi ile DriverDetailsComponent render edilecek,
  • path: ‘‘,redirectTo: ‘/drivers’, pathMatch: ‘full’ ifadesi ile de varsayılan path’in drivers olacağını söylüyoruz.

app.module.ts

import { NgModule }      from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';

import { routing } from './app.routes';

import { AppComponent }  from './app.component';
import { DriverListComponent } from './driver-list.component';
import { DriverDetailsComponent } from './driver-details.component';

@NgModule({
  imports: [ BrowserModule, routing, FormsModule, HttpModule],
  declarations: [ AppComponent, DriverListComponent, DriverDetailsComponent],
  bootstrap: [ AppComponent ]
})
export class AppModule { }

app.module.ts dosyamız bizim proje içerisinde kullandığımız ve kendi tanımladığımız modülleri tanııttığımız dosya oluyor. Tek tek burada import’ları yapıp @NgModule ile işaretli ifade içinde imports kısmına Angular’dan gelenleri, declarations kısmına kendi tanımladığımız componentleri yazıyoruz. Burada export class AppModule { } bu modülü bir sınıf şeklinde export ediyoruz ve diğer bileşenler içinde import etmek üzere hazır hale getiriyoruz.

main.ts

import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

import { AppModule } from './app.module';

platformBrowserDynamic().bootstrapModule(AppModule); 

main.ts dosyamız aslında bizim uygulamamızın hangi modülden başlayacağını bildirdiğimiz dosyamızdır.

  • import { platformBrowserDynamic } from ‘@angular/platform-browser-dynamic’; satırı ile uygulamanız hangi modülden başlayacğaını tanımlayacağımız nesneyi import ettik.
  • import { AppModule } from ‘./app.module’; satırı ile AppModule sınıfını import ettik.
  • platformBrowserDynamic().bootstrapModule(AppModule); satırı ile az önce import ettiğimiz AppModule sınıfının uygulama başlatılırken yüklenecek modül olduğunu söylüyoruz.

driver.ts

export interface Driver{
    driverId: number;
    driverName: string;
    driverSurname: string;
    driverCountry: string;
    driverTeam: string;
}

driver.ts dosyamız bizim Java, C# gibi dillerdeki pure sınıflara karşılık gelen bir dosya. Bu dosya içerisinde sadece kullanacağımız objenin özelliklerini tanımlıyoruz.

driver.service.ts

Şimdi bu blog yazısını yazmamızın asıl sebep noktasıne geldik. Web servise get ve post isteğinde bulunacak service sınıfımızı yazacağız.

import { Injectable } from '@angular/core';
import { Http, Response, Headers} from '@angular/http';
import { Observable } from 'rxjs/Rx';
import { Driver } from './driver';
import 'rxjs/add/operator/map';

@Injectable()
export class DriverService{
  private baseUrl: string = 'http://localhost:8080/RestfulWebServiceTutorials/webservice/formula1';

  constructor(private http : Http){
  }

  getAll(){
    return this.http.get(`${this.baseUrl}/driverList/`).map((response:Response)=>response.json());
  }

  get(driverId: number): Observable<Driver> {
    let driver$ = this.http
      .get(`${this.baseUrl}/getViaId/${driverId}`, {headers: this.getHeaders()})
      .map(mapDriver);
      return driver$;
  }

  save(driver: Driver) : Observable<Response>{

    return this.http
      .post(`${this.baseUrl}/updateDriver`, JSON.stringify(driver), {headers: this.getHeaders()});
  }

  private getHeaders(){
    let headers = new Headers();
    headers.append('Accept', 'application/json');
    headers.append('Content-Type', 'application/json');
    return headers;
  }
}

function toDriver(r:any): Driver{
  let driver = <Driver>({
    driverId: r.driverId,
    driverName: r.driverName,
    driverSurname: r.driverSurname,
    driverCountry: r.driverCountry,
    driverTeam: r.driverTeam
  });
  console.log('Parsed driver:', driver);
  return driver;
}

function mapDriver(response:Response): Driver{
  return toDriver(response.json());
}

function handleError (error: any) {
  let errorMsg = error.message || `Yikes! There was was a problem with our hyperdrive device and we couldn't retrieve your data!`
  console.error(errorMsg);
  return Observable.throw(errorMsg);
}
  • import satırları ile işlemler için gerekli Angular sınıflarını dahil ediyoruz. Bizim için önemli satır import { Http, Response, Headers} from ‘@angular/http’; satırıdır. HTTP GET ve POST isteği yapacağımız için Angular’ın Http sınıfından faydalanacağız. Onu dahil ediyoruz.
  • import ‘rxjs/add/operator/map’; satırını response’u JSON veri tipine veri almak için kullanmak için ve map’leme yapabilmek için dahil ediyoruz.
  • @Injectable() olarak işaretlenen sınıflar diğer sınıflara kayıt edilebiliyorlar. Biz de bu sınıfı app.component.ts altına almıştık.
  • Sınıf içerisinde bir baseUrl tanımlıyoruz. Bu baseUrl webservisimizin kök path’ini belirtir. GET ve POST işlemlerinde kullanılacak path’ler ise bu baseUrl’e eklemeler yapılarak kullanılırlar.
  • Yine C#, Java gibi OOP dillerine benzer şekilde burada da constructor’ımız var. Bu constructor içerisinde sınıfımıza Http tipinde bir nesne dahil ediyoruz.
  • getAll() metodu tüm sürücüleri listeleyen web servis metodunu çağıracak ve kayıtları döndürecek sınıftır. Metot içerisinde http://localhost:8080/RestfulWebServiceTutorials/webservice/formula1/driverList adresine bir get isteği atılıyor, dönen cevap map metodu ile JSON’a map ediliyor.
  • get(driverId: number) metodu ise gelecek sürücü id bilgisine göre değer döndüren web servis metodunu çağıracak metottur. Parametre olarak driverId bilgisi web servise yapılan istek path’ine ekliyoruz ve isteğimizi yapıp map metodunun içinde mapDriver() metodunu çağırıyoruz. mapDriver() metodu gelen response’u JSON formatında toDriver metoduna veriyor ve toDriver metodu da JSON’dan bir driver nesnesi oluşturup döndürüyor.
  • save metodu da web servis’in update metodunu tetikleyen path’i çağırıyor ve kendine gelen Driver nesnesini JSON.stringify(driver) ifadesi ile JSON’a çevirip postluyor.
  • getHeaders metodu Headers sınıfı tipinden bir nesne oluşturuyor. HTTP GET ve POST isteklerinde header bilgileri mühimdir, sunucuların sorunsuz veri alışverişi yapabilmesi için header bilgilerinin uyuşması gerekmektedir. Bu yüzden biz Accept header’ı ile application/json tipinde veri kabul ettiğimizi ve Content-Type header’ı ile de application/json formatında veri gönderdiğimizi söylüyoruz.
  • en sondaki handleError() metodu da meydana gelen bir hatayı browser console’una basacak.

driver-list.component.ts

import { Component, OnInit } from '@angular/core';
import { Driver } from './driver';
import { DriverService } from './driver.service';

@Component({
  selector: 'driver-list',
  template: `
  <section>
    <section *ngIf="isLoading && !errorMessage">
    Loading our hyperdrives!!! Retrieving data...
    </section>
      <ul>
        <!-- this is the new syntax for ng-repeat -->
        <li *ngFor="let driver of drivers">
            <a href="#" [routerLink]="['/drivers', driver.driverId]">
           
          </a>
        </li>
      </ul>
      <section *ngIf="errorMessage">
        
      </section>
  </section>
  `
})
export class DriverListComponent implements OnInit{
  drivers: Driver[] = [];
  errorMessage: string = '';
  isLoading: boolean = true;

  constructor(private driverService : DriverService){ }

  ngOnInit(){
    this.driverService
      .getAll()
      .subscribe(
          d => this.drivers = d,
          e => this.errorMessage = e,
          () => this.isLoading = false);
  }
}
  • driver-list.component.ts dosyamız sürücü listesini gösterecek bileşenimizdir. driver-list bilgisi ile selector bilgisini tanımlıyoruz. template kısmında da bu bileşenin render edildiğinde ortaya çıkacak şablonu tanımlıyoruz. Şablon içerisinde Angular’ın *ngFor yapısı ile drivers dizisi içinde döndürüyoruz ve her bir driver’ı bir linke bağlıyoruz.
  • DriverListComponent sınıfının gövdesi içinde Driver[] tipinde drivers nesnemizi tanımlıyoruz. Constructor içerisinde bu sınıfa DriverService tipinde driverService nesnesi koyuyoruz.
  • ngOnInit() metodunda ise bu component oluşturulurken yani initialization aşamsında yapılacak işlemleri tanımlıyoruz. ngOnInit() metodunu kullanmak için de sınfımızı OnInit’den implement ediyoruz. ngOnInit() içerisinde driverService nesnesi üzerinden DriverService sınıfı içindeki getAll() metodunu çağırıyoruz ve dönen cevap içindeki değerleri drivers dizisine ekliyoruz.

driver-details.component.ts

import { Component, OnInit, OnDestroy } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { Response } from '@angular/http';

import { Driver } from './driver';
import { DriverService } from './driver.service';

@Component({
  selector: 'driver-details',
  templateUrl: 'app/driver-details.component.html'
})
export class DriverDetailsComponent implements OnInit, OnDestroy {
    driver: Driver;
    sub: any;
    teams: string[] = ['Scuderia Ferrari', 'McLaren', 'Mercedes AMG Petronas','Toyota'];

    constructor(private driverService: DriverService,
                private route: ActivatedRoute,
                private router: Router){
    }

    ngOnInit(){
        this.sub = this.route.params.subscribe(params => {
          let driverId = Number.parseInt(params['driverId']);
          console.log('getting person with id: ', driverId);
          this.driverService
            .get(driverId)
            .subscribe(p => this.driver = p);
        });
    }

    ngOnDestroy(){
        this.sub.unsubscribe();
    }

    gotoDriversList(){
        let link = ['/drivers'];
        this.router.navigate(link);
    }

    saveDriverDetails(){
      let link = ['/drivers'];
      this.driverService
          .save(this.driver)
          .subscribe(
            (r: Response) => {this.router.navigate(link);}
          );
    }
}

driver-details.component.ts dosyamız sürücü detaylarının gösterileceği sayfa çağırılınca render edilecek bileşenimizi tanımladığımız dosyamız.

  • driver-details ile selector tanımımızı gerçekleştiriyoruz.
  • templateUrl ile bir html dosyasını şablon olarak geçiriyoruz. driver-list bileşeni içinde şablon’umuzu direk HTML’i ts dosyası içine yazarak tanımlamıştık. İstersek o şablonu bir HTML dosyasına kaydedip o şekilde de çağırabilirdik.
  • DriverDetailsComponent sınıfının gövdesi içinde öncelikle constructor’da driverService, route ve router nesnelerimizi tanımlıyoruz. app.routes.ts dosyası içinde hatırlayacağınız gibi DriverDetailsComponent bileşeni drivers/:driverId path’i için tanımlanmıştı. ngOnInit() içerisinde de bu gelen driverId bilgisi ile driverService nesnesi üzerinden web servise GET isteği yapılıyor ve o id’ye sahip sürücü getirilip subscribe ile sınıfa ait nesne olan driver’a atanıyor.
  • gotoDriversList() metodu değişikliklerden vazgeçip sürücü listesine döndürüyor bizi. Bu işlemi de constructor içerisinden sınıfa inject edilmiş router nesnesi ile yapıyor. this.router.navigate(link); ifadesi ile gotoDriversList() metodu çağırıldığında drivers path’ine yönleneceğiz ve dolayısı ile DriverListComponent render edilecek.

driver-details.component.html

<!-- new syntax for ng-if -->
<section *ngIf="driver">
  <section>
    <h2>You selected:  </h2>
    <h3>Description</h3>
    <p>
       
    </p>
  </section>
  <section>
    <form (ngSubmit)="saveDriverDetails()" #driverForm="ngForm">
        <div>
            <label for="driverName">Name: </label>
            <input type="text" name="driverName" required [(ngModel)]="driver.driverName" #name="ngModel">
            <div [hidden]="name.valid || name.pristine" class="error">
                Name is required my good sir/lady!
            </div>
      </div>
      <div>
        <label for="driverSurname">Surname: </label>
        <input type="text" name="driverSurname" [(ngModel)]="driver.driverSurname">
      </div>
      <div>
        <label for="driverCountry">Country: </label>
        <input type="text" name="driverCountry" [(ngModel)]="driver.driverCountry">
      </div>
      <div>
        <label for="driverTeam">Team:</label>
        <select name="driverTeam" [(ngModel)]="driver.driverTeam">
          <option *ngFor="let team of teams" [value]="team"></option>
        </select>
      </div>
      <button type="submit" [disabled]="!driverForm.form.valid">Save</button>
    </form>
  </section>

  <button (click)="gotoDriversList()">Back to drivers list</button>
<section>

driver-details.component.html dosyamız bizim DriverDetailsComponent bileşenimiz için templateUrl kısmında tanımladığımız şablon dosyamızdı. Bu şablon dosya içerisinde DriverDetailsComponent’den gelen driver nesnesi üzerinden ilgili özellikler alınıp input’lar [(ngModel)] ifadeleri ile dolduruluyor. Save butonu bu formu DriverDetailsComponent içinde tanımlı olan saveDriverDetails() metoduna gönderirken, Back to drivers list butonu da bizi sürücü listesine döndürecek.

Ekran Çıktıları

Web servisi yazdığım sunucuyu başlatıp akabinde komut satırı üzerinden bu projenin olduğu klasöre girip npm install ve npm start komutlarını veriyorum.

Proje açıldığında karşıma gelen ekranda veritabanında kayıtlı olan tüm sürücüleri görebiliyorum:

Şimdi ben ilgili linkler arasından Mika Salo’ya tıklıyorum ve Mika Salo’ya ait bilgiler listeleniyor. URL’e de dikkat edelim, Mika Salo’nun id bilgisi ile diğer bilgileri getiriliyor.

Bilgileri şu şekilde değiştiriyorum ve Save butonuna tıklıyorum:

Mika Salo’nun yerinde girdiğim bilgileri görüyorum.

Veri tabanına gidip baktğımda da bilgilerin gerçekten kalıcı şekilde değiştiğini görüyorum.

Bu yazıda anlatacaklarım bu kadar arkadaşlar. Yeni hevesim olan Angular 2’nin web servis ile kullanımını öğrendikten sonra bu şekilde sizinle de paylaşmak istedim. Umarım güzel bir yazı olmuştur.

Gelecek yazıda görüşene kadar sağlıcakla kalın arkadaşlar.

Selam ve Sevgilerimle