Angular2 İle ReactiveX
Selamlar;
Bugün Reactive Extensionsların [RX] Angular2’de nasıl kullanılacağını hep beraber inceleyeceğiz. ReactiveX kısaca belirtilen diziler üzerinde asenkron ve event tabanlı işlemler yapmayı sağlayan bir kütüphanedir. En sevdiğim özelliği observer pattern kullanan, birden çok operasyonun asenkron olarak yapılmasını sağlayan birçok platformda kullanılabilen bir kütüpahane olmasıdır. Rx Observable’lar ile neler yapılabileceğini http://rxmarbles.com/ adresinden de inceleyebilirsiniz.
Bu bölümde RX kullanarak Angular2 ile çeşitli örnekler yapacağız. Böylece bize nasıl kolaylıklar sağlıyabileceğini hepberaber inceleyeceğiz.
Index.html: Index sayfasına eklenmesi gereken dosyalar aşağıda gösterilmiştir.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
<html> <head> <title>AngularJs 2 Öğren</title> <link rel="stylesheet" href="node_modules/bootstrap/dist/css/bootstrap.css"> <!-- 1. Load libraries --> <!-- IE required polyfills, in this exact order --> <script src="node_modules/es6-shim/es6-shim.min.js"></script> <script src="node_modules/systemjs/dist/system-polyfills.js"></script> <script src="node_modules/angular2/bundles/angular2-polyfills.js"></script> <script src="node_modules/systemjs/dist/system.src.js"></script> <script src="node_modules/rxjs/bundles/Rx.js"></script> <script src="node_modules/angular2/bundles/angular2.dev.js"></script> <script src="node_modules/angular2/bundles/http.dev.js"></script> <!-- 2. Configure SystemJS --> <script> System.config({ packages: { app: { format: 'register', defaultExtension: 'js' } } }); System.import('app/boot') .then(null, console.error.bind(console)); </script> <script> String.prototype.replaceAll = function(search, replacement) { var target = this; return target.replace(new RegExp(search, 'g'), replacement); }; </script> </head> <!-- 3. Sayfada Görünecek AngularJS2 Component --> <body> <my-app>Yükleniyor...</my-app> </body> </html> |
Örnek 1: Bir input alana veri girildiği zaman belli bir bekleme süresi sonunda ilgili textdeki boşluklar “-” karakteri ile değiştirilerek console’a yazılacaktır. Girilen string değerin bir çeşit Url’e dönüştürüldüğü düşünülebilir.
app.component.ts’e eklenen paketler:
- RX için eklenecek moduleler “{Observable} from ‘rxjs/Rx’” tüm paket yerine daha lightweight hali olan “{Observable} from ‘rxjs/Observable’” küçültülmüş paketi eklenmiştir. Bu paket angular2 takımı tarafından oluşturulmuştur.
- Form içindeki Html elementlere erişebilmek için “{ControlGroup ve FormBuilder}” moduleleri eklenmiştir.
- Ayrıca bende bazı komutlar hata verdiği için “rxjs/Rx” module de eklenmiştir. Normalde eklenmesine gerek olmamalıdır.
- “rxjs/add/operator/debounceTime” module’ü delay() yani bekleme işlemi yapılabilmesi için eklenmiştir.
- “rxjs/add/operator/filter” belirtilen source üzerinde filitreleme yapabilmek için eklenmiştir.
- template’de “[ngFormModel]” ile ilgili form “form” değişkenine atanmıştır. Böylece bu form’a ait elemanlara erişilebilecektir.
- input alanı “ngControl“, “search” olarak atanmıştır.
- AppComponent sınıfının constructor’ında => “search” input alanı bulunmuştur.
- Input alan her deger girilip silinmesinde aşağıda yazılan “search.valueChanges” event’i devreye girer. İlgili eventin sonuna eklnen “RX” methodlarından :
- .debounceTime(400) ile, input alana girilen her harf için işlem yapılmaması ve 400ms beklenilmesi sağlanmıştır.
- .map(Y=>(<string>Y).replaceAll(” “,”-“)) ile girilen text içinde ” ” karakteri “-” ile değiştirilmiştir. Burada “<string>Y” ile map(Y) nesnesi “string”‘e cast edilir.
- .subscribe ile en son “map()”‘lenen veri console basılır.
Index.html: Sayfasına String tipine “replaceAll()” methodu bir çeşit extension gibi “prototype” ile eklenmiştir. Bu konu ile ilgili detaylı javascript webinarımı izlemenizi tavsiye ederim. Böylece girilen tüm text’deki boşluklar “-” karakteri ile değiştirilebilecektir.
1 2 3 4 5 6 |
<script> String.prototype.replaceAll = function(search, replacement) { var target = this; return target.replace(new RegExp(search, 'g'), replacement); }; </script> |
app.component.ts:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
/// <reference path="../typings/tsd.d.ts" /> import {Component} from 'angular2/core'; import {ControlGroup,FormBuilder} from 'angular2/common'; //import {Observable} from 'rxjs/Rx'; import {Observable} from 'rxjs/Observable'; import 'rxjs/Rx'; import 'rxjs/add/operator/debounceTime' import 'rxjs/add/operator/filter' @Component({ selector: 'my-app', template: ` <form [ngFormModel]="form"> <input type="text" ngControl="search" placeholder="Search Artist"/> </form> ` }) export class AppComponent { form:ControlGroup; constructor(fb:FormBuilder) { this.form=fb.group({ search:[] }); //console.log(new Observable()); var search=this.form.find('search'); search.valueChanges .debounceTime(400) .map(Y=>(<string>Y).replaceAll(" ","-")) .subscribe(x=>console.log(x)); } } |
Örnek 2: Bu örnekte “Observable” ile nasıl bir [] dizi kümesinde gezilip “map()”‘lenip sonuca varılacağını inceleyeceğiz.
- Bulunulan tarihten 2 gün önce ve sonrası, yani toplam 5 günlük zaman “startDates[]” dizisine atanmıştır.
- Observable.fromArray(startDates) ile alınan tarih dizisi içinde gezileceği belirtilir.
- .map(date=>console.log(date)) ile içinde gezilen dizi console’a yazılır.
- .map() return [1,2,3] yapımıştır. Bu işlemde, map’den örnek olması amacı ile konudan bağımsız [1,2,3] dizisi dönülmüştür.
- .subscribe(x=>console.log(“x:”+x)) ile map()’den ne değer dönülür ise “subscribe”‘da o değerin alındığı, console’a yukarıdaki gibi basılarak gösterilmek istenmiştir.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
var startDates = []; var startDate = new Date(); for (var day = -2; day <= 2; day++) { var date = new Date ( startDate.getFullYear(), startDate.getMonth(), (startDate.getDate() + day)); startDates.push(date); } Observable .fromArray(startDates) .map(date=>{console.log(date); return [1,2,3]; }) .subscribe(x=>console.log("x:"+x)); console.log(Observable.of(1)); |
Örnek 3: Bu örnekde Observable ile nasıl sürekli belli bir zaman aralığında bir kendisinin “interval()” ile çağırılabileceği incelenmiştir.
- Observal.interval(1000) ile observal kendini her 1 sn’de bir çağırmaktadır.
- .map() ve .subscribe() ile ilgili yazı ve sayı dizisi console’a yukarıda görüldüğü gibi basılmaktadır.
1 2 3 4 5 6 7 |
var observable=Observable.interval(1000); observable .map(x=>{ console.log("Tekrardan çağır"); return [1,2,3] }) .subscribe(x=>console.log(x)); |
Örnek 4: Bu örnekde 2 farklı “Observable” nesnesi aynı MsSql’de olduğu gibi birleştirilip(union) alias verilmiştir.
- .forkJoin() methodu ile 2 observable birleştirilmiştir.
- .map() ile ilgili observable nesneleri “Object” bir nesneye “user” ve “tweets” propertysi olarak atanmıştır.
- .subscribe() methodu ile ilgili result “Object” console’a basılmıştır.
1 2 3 4 5 6 7 8 9 10 11 |
var userStream = Observable.of({ userId: 1, username: 'borsoft' }).delay(2000); var tweetsStream = Observable.of([1, 2, 3]).delay(1500); Observable .forkJoin(userStream, tweetsStream) .map(joined => new Object({ user: joined[0], tweets: joined[1] })) .subscribe(result => console.log(result)); |
Örnek 5: Bu örneğimizde Wikipedia’dan arama işlemini Angular2 ve Observable kullanarak başta basit yazılan bir örneği, sürekli geliştirip hepberaber inceleyeceğiz.
WikipediaService.ts: Öncelikle arama amaçlı aşağıdaki services yazılır. Amaç istenen “Term“‘e göre wikipedia’dan arama yapabilmektir. Bunun için “URLSearchParams” kullanılmış ve dönüş tipini Json’a convert etmek için “Jsonp” kütüphaneleri kullanılmıştır.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
import {Injectable} from 'angular2/core'; import {URLSearchParams, Jsonp} from 'angular2/http'; @Injectable() export class WikipediaService { constructor(private jsonp: Jsonp) { } search(term: string) { var search = new URLSearchParams() search.set('action', 'opensearch'); search.set('search', term); search.set('format', 'json'); return this.jsonp .get('http://en.wikipedia.org/w/api.php?callback=JSONP_CALLBACK', { search }) .map((response) => response.json()[1]); } } |
app.component.ts(1) : Ekelenecek tüm module’ler aşağıda gösterilmiştir. Kullanılacak RX modulleri ve Observable, JSONP_PROVİDERS: Wikipedia’dan çekilecek olan data Json tipindedir,
custom yazılan “WikipediaService”, Control directive’i ve Component module leridir.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
/// <reference path="../typings/tsd.d.ts" /> import {Component} from 'angular2/core'; import {Control} from 'angular2/common'; import {WikipediaService} from '../app/WikipediaService' import {JSONP_PROVIDERS} from 'angular2/http'; import {Observable} from 'rxjs/Observable'; import 'rxjs/add/operator/map'; import 'rxjs/add/operator/debounceTime'; import 'rxjs/add/operator/distinctUntilChanged'; import 'rxjs/add/operator/switchMap'; |
app.component.ts(2): Aşağıda arama yapılacak “input” alan tanımlanmıştır. Property binding olarak “[ngFormControl]=’term’” kullanılmıştır. Böylece AppComponent sınıfında ilgili html elemente “term” olarak ulaşılacaktır. Çekilen tüm data”items”‘a atanacaktır. “*ngFor” ile gezilip ekrana basılacaktır. İlgili JSONP ve WikipediaServices providerları yine burada sayfaya eklenmiştir.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
@Component({ selector: 'my-app', template: ` <div> <h2>Wikipedia Search</h2> <input type="text" [ngFormControl]="term"/> <ul> <li *ngFor="#item of items">{{item}}</li> </ul> </div> `, providers:[WikipediaService,JSONP_PROVIDERS] }) |
app.component.ts(3):
- AppComponent sınıfında çekilecek items: “Array<string>” olarak tanımlanmıştır. Serach yapılacak “term=new Control()” olarak tanımlanmıştır.
- constructor()’da yazılmış olan custom “WikipediaService” alınmıştır.
- “term.valueChanges” eventinde, yani input alana değer girilip çıkarıldığı zaman ilgili event tetiklenir.
- debounceTime(400): Her girilen harf için işlem yapılmaması için delay konmuştur. Yani belli bir süre bekleme yapılmıştır.
- distinctUntilChanged(): Önceden aranan bir term için tekrardan bir arama yapılmaması için bu method eklenmiştir.
- subscribe((term:string)=> Girilen term string’e cast edilir.
- this.wikipediaService.search(term).subscribe(items => this.items = items)) Wikipedia’da search edilen term sonuçları items dizisine aktarılır. Böylece ilgili dizi gezilerek arama sonuçları ekrana basılır.
1 2 3 4 5 6 7 8 9 10 11 |
export class AppComponent { items: Array<string>; term = new Control(); constructor(private wikipediaService: WikipediaService) { console.log(this.term); this.term.valueChanges .debounceTime(400) .distinctUntilChanged() .subscribe((term:string) => this.wikipediaService.search(term).subscribe(items => this.items = items)); } } |
app.component.ts(4) Geliştirme:
- “items: Observable<Array<string>>” Items nesnesi Observable bir array nesnesi olarak atanır.
- “WikipediaSearch” servisinden dönen değerler doğrudan “this.items =“‘a aktarılır.
- “switchMap” ile tüm dönen değerler async olarak items’a maplenir.
1 2 3 4 5 6 7 8 9 10 11 |
export class AppComponent { items: Observable<Array<string>>; term = new Control(); constructor(private wikipediaService: WikipediaService) { console.log(this.term); this.items =this.term.valueChanges .debounceTime(400) .distinctUntilChanged() .switchMap((term:string) => this.wikipediaService.search(term)); } } |
app.component.ts(5) Template Geliştirme: “switchMap” ile yapılan async mapleme sonucu “*ngFor” ile yapılan aramaya “| async” pipe’ı eklenerek asenkron bir şekilde ilgili items[] dizisinde gezilir ve arama sonuçları sayfaya basılır.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
@Component({ selector: 'my-app', template: ` <div> <h2>Wikipedia Search</h2> <input type="text" [ngFormControl]="term"/> <ul> <li *ngFor="#item of items | async">{{item}}</li> </ul> </div> `, providers:[WikipediaService,JSONP_PROVIDERS] }) |
app.component.ts Full:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
/// <reference path="../typings/tsd.d.ts" /> import {Component} from 'angular2/core'; import {Control} from 'angular2/common'; import {WikipediaService} from '../app/WikipediaService' import {JSONP_PROVIDERS} from 'angular2/http'; import {Observable} from 'rxjs/Observable'; import 'rxjs/add/operator/map'; import 'rxjs/add/operator/debounceTime'; import 'rxjs/add/operator/distinctUntilChanged'; import 'rxjs/add/operator/switchMap'; @Component({ selector: 'my-app', template: ` <div> <h2>Wikipedia Search</h2> <input type="text" [ngFormControl]="term"/> <ul> <li *ngFor="#item of items | async">{{item}}</li> </ul> </div> `, providers:[WikipediaService,JSONP_PROVIDERS] }) export class AppComponent { items: Observable<Array<string>>; term = new Control(); constructor(private wikipediaService: WikipediaService) { console.log(this.term); this.items =this.term.valueChanges .debounceTime(400) .distinctUntilChanged() .switchMap((term:string) => this.wikipediaService.search(term)); } } |
Source Code: http://www.borakasmer.com/projects/angular2.rar
Örnek 6: Bunu evde denemeyin:) Bu örnekde belirtilen bir text içindeki kelimeler aralarındaki boşluk karakterine göre sıra ile alınmakta ve harf harf bir input alana girilmektedir. Her kelimenin sonucunde Wikipedia’dan arama yapılmakta ve bulunun sonuç listesi ekrana basılmaktadır.
Örnek Url: http://angular2search.azurewebsites.net/
- Öncelikle ilgili paketler import ile yüklenir.
- Template kısımda aynı yukarıdaki örnek de olduğu gibi arama sonucunu tutan “items” async olarak gezilip ekrana basılmaktadır.
- randomInterval içinde gezilecek text’in length’i boyutunda bir dizi olarak atanır.
- .concatMap() tüm elemanları iterable tek bir diziye atılması sağlanır. Bu örnkte boşluk karakterine kadar olan tüm karakterleri tekbir diziye koymayı yani herbir kelimeyi almaya yarar.
- delay() belli bir zaman, uygulamayı bekletmeye yarar. Çekilen herbir kelimenin harflerinin, aynı typing yapılıyormuşçasına girilmesini sağlar.
- .zip() dizi elemanlarını birleştirip tek bir result’a döndürür.
- .scan() ile boşluk karakterine kadar ilgili karakterlerin uç uca toplanması sağlanmıştır.
- .share() ile boşluk karakteri gelene kadar işlemin devam etmesine, boşluk yada null dönülmesi durumunda yeni kelime için tüm işlemin başa dönülmesi sağlanmıştır.
- .map() ile herbir çekilen harf input alana atanır.
- .debounceTime() herbir işlem, yani arama arasına zaman konulmuştur.
- .distinctUntilChanged() aynı kelimenin aranmaması sağlanmıştır
- .switchMap() ilgili servis girilen harflerden oluşan “$term” yani kelime wikipedia’da aranır. Dönen liste “items[]” string dizisine atanir. Ve böylece çekilen arama sonucu ekrana basılmış olur.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
/// <reference path="../typings/tsd.d.ts" /> import {Component} from 'angular2/core'; import {Control} from 'angular2/common'; import {WikipediaService} from '../app/WikipediaService' import {JSONP_PROVIDERS} from 'angular2/http'; //import {Observable} from 'rxjs/Observable'; import {Observable} from 'rxjs/Rx'; import 'rxjs/add/operator/map'; import 'rxjs/add/operator/debounceTime'; import 'rxjs/add/operator/distinctUntilChanged'; import 'rxjs/add/operator/switchMap'; @Component({ selector: 'my-app', template: ` <div> <h2>Wikipedia Search</h2> <input type="text" [value]="term"/> <ul> <li *ngFor="#item of items | async">{{item}}</li> </ul> </div> `, providers: [WikipediaService, JSONP_PROVIDERS] }) export class AppComponent { text = `Bora has been writing code for as long as he can remember and has been getting paid to do it since 1996. .Net And Web technologies is his passion. And he is really interested in with Azure. He is working as a Senior Software Architect for Dogan Gazetecilik, which is the biggest media company in Turkey. In the past, he was working as a Development Team Leader for Medyanet Dogan Online, which is the most known adserver company in Turkey. And also he was working for StartTv which is the very popular tv channel portal as a Software Manager and Technical Leader. In fact, his specialty Crm. He worked so many years on crm content management system as a Team Leader for Linde. He had the opportunity to work on many different platforms. Such as research and development on telecommunication company for Turkcell, kiosk and electronic scales programming for Mental, dynamic web page framework algorithms for CitiBank and speech recognition.`; randomInterval$ = Observable.range(0, this.text.length) .concatMap(x => Observable.of(x) .delay(Math.random() * 500) ); term: string=""; term$ = Observable.from(this.text, function (x) { return x; }) .zip(this.randomInterval$, x => x) .scan((a, c) => (c === " ") ? "" : a + c) .share() .map((result: string) => this.term = result) items: Observable<Array<string>>; constructor(private wikipediaService: WikipediaService) { this.items = this.term$ .debounceTime(250) .distinctUntilChanged() .switchMap((term: string) => this.wikipediaService.search(term)) } } |
Source Code: https://github.com/borakasmer/Angular2SearchAtWikipedia
Son söz olarak ReactiveX yani rxjs kütüpahanesinin Angular2 ile ne kadar kullanışlı ve güçlü olduğunu; asenkron olarak servislerden nasıl yararlanılabileceğini ve diziler üzerinde nasıl işlmeler yapılabileceğini hep beraber gördük. Geldik bir makalenin daha sonuna. Yeni bir makalede görüşmek üzere hoşçakalın.
Kaynaklar : ng-book2 The Complete Book on AngularJS2 https://angular.io/, https://egghead.io, Mosh Hamedani(Angular 2 with TypeScript for Beginners The Pragmatic Guide) …
Hocam, tüm makalelerin çok iyi çok şey öğreniyorum.Lütfen yazılarına böyle devam et . Eline emeğine sağlık. Çok teşekkür ediyorum.
Böyle güzel yorumların için teşekkür ederim Erhan.
İşine yaradı ise ne mutlu bana. Yazıyorum vakit buldukça:)
İyi çalışmalar.
Tekrar merhaba Hocam,
Bir sorum olacak angular 2 ile alakalı. Sadece ilgili component te kullanmak için lazım olan bir javascript dosyasını component yüklendiğinde nasıl yükleyebilirim. İndex.html de eklemek istemiyorum çünkü her zaman lazım olmayacak.
Teşekkürler.
Selamlar Erhan,
Bu soruya Chat ortamında cevapladım. İsteyen ordan bakabilir :)
Hoşçakalın.