https://www.buraksenyurt.com/Burak Selim Şenyurt - Angular2022-01-24T21:02:48+00:00Matematik Mühendisi Bir Bilgisayar Programcısının NotlarıBurak Selim SenyurtBlogEngine.Net Syndication Generatorhttps://www.buraksenyurt.com/opml.axdBurak Selim SenyurtMatematik Mühendisi Bir Bilgisayar Programcısının Notlarıtr-TRBurak Selim Şenyurt0.0000000.000000https://www.buraksenyurt.com/post/socket-io-yardimiyla-realtime-calisan-bir-angular-uygulamasi-gelistirmekSocket-IO Yardımıyla RealTime Çalışan Bir Angular Uygulaması Geliştirmek2019-08-08T15:17:00+00:00bsenyurt<p><img style="float: right;" src="https://www.buraksenyurt.com/image.axd?picture=/2019/09/29/someprimes.png" alt="" />Dünyanın aslen hukukçu olmasına rağmen en ünlü matematikçilerinden olan Fermat'nın<em>(1601-1665)</em> asal sayıları bulduğunu iddia ettiği denklemini bir diğer matematikçi Euler<em>(1707-1773)</em>, n=5 değeri için bozmuştur. Lakin matematikçilerin ve diğer pek çok kişinin asalları bulma tutkusu bitmemiştir. Bilim, felsefe ve müzikle haşırneşir olmayı seven Fransız rahibi Marin Mersenne<em>(1558-1648)</em> 2<em><sup>n</sup></em>-1 şeklindeki formülü ile ünlenmiştir. Formüldeki n değerinin asal sayı olarak kabul edildiği hallerde bulunan sayıların da asal olduğunun belirtildiği bir teorem söz konusudur<em>(</em><em>Bu formül ile bulunan bir sayının asal olup olmadığı Lucas-Lehmer testi ile kontrol edilebilir)</em></p>
<p>Nitekim mesele 1000-2000 arası asalları bulmakla ilgili değildir. En büyük asal değeri bulabilmektir. Çünkü n değeri büyüdükçe en büyük asalı bulmak da zorlaşır<em>(Nadir olan her zaman daha kıymetlidir)</em> Mersenne sayıları olarak adlandırılan bu asalların en kocamanı 2018 yılında elektrik mühendisi Jonathan Pace tarafından keşfedilmiştir. n = 82.589.933 değeri için bulunan 50nci Mersenne asalı tam 24.862.048 rakamdan oluşmaktadır<em>(Ocak 2019 itibariyle)</em> Dilerseniz 51nci Mersenne asalını bulmak için siz de katkıda bulunabilir hatta bulursanız küçük bir ödül bile alabilirsiniz. <a href="https://www.mersenne.org/" target="_blank">Şu adrese girip</a> GIMPS<em>(Great Internet Mersenne Prime Search)</em> sistemine gönüllü olarak katılmanız yeterli.</p>
<p>Lakin hangi formül olursa olsun çıkan sonucun asal sayı olacağının garantisi veya ispatı henüz yoktur<em>(May Be Prime!)</em> Hatta dünyadaki tüm asal sayılarının dizisini bize getirebilecek bir denklem de henüz mevcut değildir. Peki bugünkü konumuzun Mersenne asalları ile bir ilgisi var mı dersiniz? Bu cumartesi gecesi derlemesinin 29ncu çalışmaya ait olması haricinde pek yok ;)</p>
<p>Bilindiği üzere istemci-sunucu geliştirme modelinde gerçek zamanlı ve çift yönlü iletişim için WebSocket yaygın olarak kullanılan protokollerden birisi. Klasik HTTP request/response modelinden farklı olarak WebSocket protokolünde sunucu, istemcinin talep göndermesine gerek kalmadan mesaj gönderebiliyor. Chat uygulamaları, çok kullanıcılı gerçek zamanlı oyunlar, finansal bildirim yapan ticari programlar, online doküman yönetim sistemleri ve benzerleri WebSocket protokolünün kullanıldığı ideal ortamlar. Benim <a href="https://github.com/buraksenyurt/saturday-night-works/tree/master/No%2029%20-%20Real%20Time%20App%20with%20Angular" target="_blank">29 numaralı Saturday Night Works çalışması</a>ndaki amacım Socket.IO kütüphanesinden yararlanan bir Node sunucusu ile Angular'da yazılmış bir web uygulamasını WebSocket protokolü tabanında deneyimlemekti. Hazırsanız notlarımızı toparlamaya başlayalım.</p>
<p>Öncelikle örneğimizde neler yapacağımızdan bahsdelim. Kullanıcıların aynı doküman üzerinde ortaklaşa çalışabileceği bir örnek geliştirmeye çalışacağız. İstemciler yeni bir doküman başlatabilecek. Dokümanların tamamı tüm kullanıcılar tarafından görülebilecek ve yazılanlar her istemcinin penceresine yansıyacak. Bir nevi ortak dashboard üzerindeki post-it'lerin herkes tarafından düzenlenebildiği bir ortam gibi düşünebiliriz. Ben örneği her zaman olduğu gibi WestWorld<em>(Ubuntu 18.04, 64bit)</em> üzerinden denemeye çalışıyorum. Bu arada makinenizde node, npm, angular CLI'ın yüklü olduğunu varsayıyorum.</p>
<h2>Uygulamanın İnşası</h2>
<p>Uygulama iki önemli parçadan oluşuyor. Soket mesajlaşmasını yönetecek olan sunucu<em>(node.js tarafı)</em> ve istemci<em>(Angular tarafı) </em>Sunucu tarafının inşası için aşağıdaki terminal komutları ile işe başlayabiliriz.</p>
<pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false">mkdir docserver
cd docserver
mkdir src
npm init
npm install --save-dev express socket.io @types/socket.io
cd src
touch app.js</pre>
<p>Bize yardımcı olacak sunucu ve soket özellikleri için bir epxress ve socket.io paketlerini yüklüyoruz. app.js dosyasının içeriğini ise aşağıdaki gibi geliştirebiliriz.</p>
<pre class="brush:js;auto-links:false;toolbar:false" contenteditable="false">/*
sunucu özelliklerini kolayca kazandırmak için express modülünü kullanıyoruz.
WebSocket kullanımı içinse socket.io paketi dahil ediliyor
*/
const app = require('express')();
const http = require('http').Server(app);
const io = require('socket.io')(http);
const articles = {}; // Üzerinde çalışılacak yazıların tutulacağı repo. Canlı ortamlar için fiziki alan ele alınmalı.
/*
on metodları birer event listener'dır. İlk parametre olayın adı,
ikinci parametrede olay tetiklendiğinde çalışacak callback fonksiyonudur.
connection Socket.IO için tahsis edilmiş bir olaydır.
Burada soket haberleşmesi tesis edilir ve bağlı olan
istemciler için broadcasting başlatılır.
*/
io.on("connection", socket => {
/*
updateRoom metodu bağlı olan tüm istemcilerin aynı doküman üzerinde çalışmasını garanti etmek içindir.
İstemci bağlantı gerçekleştirip dokümanla çalışmak üzere bir odaya bağlanır(room).
Bağlı olan istemci bu odadayken başka bir dokümanla çalışmasına izin verilmez.
N sayıda istemci aynı odadayken aynı doküman üzerinde güncelleme yapabilir.
İstemci bir başka dokümanla çalışmak isterse bulunduğu odadan ayrılır ve yeni bir tanesine katılır.
Tabii Socket.IO ile n sayıda oda(room) ile çalışmak mümkündür. Ancak bu senaryoda istenmemektedir.
*/
let preId;
const updateRoom = currentId => {
socket.leave(preId);
socket.join(currentId);
preId = currentId;
};
/*
istemci get isimli bir olay yayınladığında çalışır.
istemci bir odaya gelen id ile dahil edilir.
sonrasında sunucu dokümanı istemciye yollar.
Bunun için ready isimli bir olay yayınlar ki istemci de bu olayı dinlemektedir.
*/
socket.on("get", id => {
console.log("get event id: " + id);
updateRoom(id);
socket.emit("ready", articles[id]);
});
/*
add yeni bir dokümanın eklenmesi için kullanılır.
istemci tarafından yayınlanan olayda payload olarak
dokümanın kendisi gelir.
io üzerinden yayınlanan warnEveryone isimli olay
istemcilerin tümünü yeni bir dokümanın eklendiği bilgisini vermek üzere tasarlanmıştır.
socket üzerinden yapılan olay bildirimi payload dokümanı ile birlikte sadece bağlı
olan istemci için geçerlidir.
socket ile io nesnelerinin emit kullanımları arasındaki farka dikkat edelim.
io.emit bağlı olan tüm istmecileri ilgilendirirken, socket.emit o anki olayın
sahibi bağlı olan istemciyi ilgilendirir.
*/
socket.on("add", payload => {
articles[payload.id] = payload;
updateRoom(payload.id);
console.log("add event " + payload.id);
io.emit("warnEveryone", Object.keys(articles));
socket.emit("ready", payload);
console.log(articles);
});
/*
İstemcilerin üzerinde çalıştıkları dokümanda yaptıkları herhangibir tuş darbesi
bu olayın tetiklenmesi ile ilgilidir.
Payload içeriğine göre odadaki doküman güncellenir ve
sadece bu doküman üzerinde çalışanların bilgilendirimesi sağlanır.
*/
socket.on("update", payload => {
//console.log("update event");
articles[payload.id] = payload;
socket.to(payload.id).emit("ready", payload);
});
// Tüm bağlı istemcileri template dizisindeki key değerleri için bilgilendir
io.emit("warnEveryone", Object.keys(articles));
});
http.listen(5004);
console.log("Ortak makale yazma platformu :P 5004 nolu porttan dinlemede...");</pre>
<p>Kodu içerisindeki yorumlar ile mümkün mertebe açıklamaya çalıştım. Buraya kadar her şey yolunda gittiyse istemci uygulamanın inşası ile devam edebiliriz.</p>
<h2>İstemcinin<em>(Angular tarafı)</em> İnşası</h2>
<p>Soket yöneticisi ile konuşacak olan istemciyi bir Angular uygulaması olarak geliştireceğiz. İşe aşağıdaki terminal komutları ile başlayabiliriz.</p>
<pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false">ng new authorApp --routing=false --style=css
cd authorApp
sudo npm install --save-dev ngx-socket-io
ng g class article
ng g component article-list
ng g component article
ng g service article</pre>
<p>İlk komutla authorApp isimli bir Angular uygulaması oluşturulur. Socket.IO ile Angular tarafında konuşmamızı sağlayacak ngx-socket-io paketi proje klasörü içindeyken npm yardımıyla yüklenir. Yine aynı klasörde article isimli sınıf, article-list ve article isimli bileşenler ve soket sunucusuyla iletişimde kullanacağımız article isimli servis oluşturulur <em>(g sonrasında gelen component ve service anahtar kelimeleri için c ve skısaltmaları da kullanılabilir)</em></p>
<p><img src="https://www.buraksenyurt.com/image.axd?picture=/2019/09/29/credit_1.png" alt="" /></p>
<p>Gelelim istemci tarafındaki kodlarımıza. Öncelikle app.module.ts dosyasında SocketIoModule ile ilgili bir kaç konfigurasyon ayarlaması yapalım. Böylece hangi sunucu ile web socket haberleşmesi yapılacağı tüm modüller için ayarlanmış olur.</p>
<pre class="brush:js;auto-links:false;toolbar:false" contenteditable="false">import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { ArticleListComponent } from './article-list/article-list.component';
import { ArticleComponent } from './article/article.component';
import { FormsModule } from '@angular/forms';
/*
Angular tarafından socket haberleşmesi için gerekli modül
bildirimleri. Web Socket sunucusunun adresi de konfigurasyon bilgisi olarak tanımlanmakta.
*/
import { SocketIoModule, SocketIoConfig } from 'ngx-socket-io';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
const config: SocketIoConfig = { url: 'http://localhost:5004' };
@NgModule({
declarations: [
AppComponent,
ArticleListComponent,
ArticleComponent
],
imports: [
BrowserModule,
// Üstte belirtilen url bilgisi ile birlikte socket modülünü hazır hale getirip içeri alıyoruz
SocketIoModule.forRoot(config),
BrowserAnimationsModule,
FormsModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
</pre>
<p>Odalardaki makaleleri temsil eden article.ts sınıfını da şöyle yazabiliriz.</p>
<pre class="brush:js;auto-links:false;toolbar:false" contenteditable="false">/*
Ortaklaşa çalışılacak dokümanı temsilen kullanılacak tip
*/
export class Article {
id: string;
content: string;
}</pre>
<p>Pek tabii asıl iş yükü proxy sınıfı görevi üstlenen article.service.ts içerisinde. Socket sunucusu ile haberleşecek ve arayüz tarafında kullanacağımız servis kodlarını aşağıdaki gibi geliştirebiliriz.</p>
<pre class="brush:js;auto-links:false;toolbar:false" contenteditable="false">import { Injectable } from '@angular/core';
import { Socket } from 'ngx-socket-io'; // Socket sunucusuna event fırlatıp yakalayacağımız için
import { Article } from '../app/article'; //Article tipini kullanacağımız için
@Injectable({
providedIn: 'root'
})
export class ArticleService {
/*
Socket sunucusundan yayınlanan ready ve warnEveryOne isimli olaylar için kullanacağımız özellikleri tanımlıyoruz.
Sunucu tüm istemcilere makale listesini string array olarak gönderirken warnEveryOne olayını yayınlamakta.
Doküman ekleme, güncelleme ve tek birisini çekme işlemlerine karşılık olarak da ready olayını yayınlıyordu.
fromEvent dönüşleri Observable tiptedir. Yani değişiklikler otomatik olarak abonelerine yansıyacaktır.
*/
currentArticle = this.socket.fromEvent<Article>('ready');
allOfThem = this.socket.fromEvent<string[]>('warnEveryone');
constructor(private socket: Socket) { } //Constructor injection ile Socket modülünü yükledik
/*
Boş bir doküman üretmek için kullanılıyor.
emit metodu add olayını tetiklemekte.
Sunucuya ikinci parametrede belirtilen içerik gönderiliyor.
emit metodlarındaki ilk parametrelerdeki olaylar sunucunun dinlediği olaylardır.
*/
add() {
let randomArticleName = Math.floor(Math.random() * 1000 + 1).toString();
this.socket.emit('add', {id: randomArticleName,content:'' });
// console.log(this.allOfThem.forEach(a=>console.log(a)));
}
/*
makale içeriğinin güncellenmesi halinde sunucu tarafına update olayı basılır
*/
update(article:Article){
this.socket.emit('update',article);
}
/*
id değerine göre bir makaleyi almak için get olayını fırlatıyor.
*/
get(id:string){
this.socket.emit('get',id);
}
}</pre>
<p>Ön yüz tarafında daha çok bileşenleri kodlayacağız. article ve article-list bileşenleri ana bileşen olan app içerisinde kullanılmaktalar. Tüm bu bileşenlere ait html ve typescript içeriklerini aşağıdaki gibi düzenleyebiliriz.</p>
<p>article-component.html</p>
<pre class="brush:html;auto-links:false;toolbar:false" contenteditable="false"><textarea [(ngModel)]='article.content' (keyup)='updateArticle()' placeholder='Haydi bir şeyler yazalım...'></textarea>
<!--textarea'yo ngModel niteliği ile arka plandaki article sınıfının content özelliğine bağlıyoruz
keyup olayı parmağımızı her tuştan çektiğimizde çalışacak ve bileşenin typescript tarafındaki updateArticle
metodunu çağıracak.
--></pre>
<p>article-component.ts</p>
<pre class="brush:js;auto-links:false;toolbar:false" contenteditable="false">import { Component, OnInit, OnDestroy } from '@angular/core';
import { ArticleService } from 'src/app/article.service';
import { Subscription } from 'rxjs';
import { Article } from 'src/app/article';
import { startWith } from 'rxjs/operators';
@Component({
selector: 'app-article',
templateUrl: './article.component.html',
styleUrls: ['./article.component.css']
})
export class ArticleComponent implements OnInit, OnDestroy {
article: Article;
private _subscription: Subscription;
/*
ArticleService, Constructor Injection ile içeriye alınır.
*/
constructor(private ArticleService: ArticleService) { }
/*
Bileşen initialize edilirken güncel makale için bir abonelik başlatılır.
Böylece gerek bu aboneliğin sahibinin değişiklikleri
gerek diğerlerinin değişiklikleri aynı makalede çalışan herkese yansır.
*/
ngOnInit() {
this._subscription = this.ArticleService.currentArticle.pipe(
startWith({ id: '', content: 'Var olan bir makaleyi seç ya da yeni bir tane oluştur' })
).subscribe(a => this.article = a);
}
// Bileşen ölürken üzerinde çalışan makalenin aboneliğinden çıkılır
ngOnDestroy() {
this._subscription.unsubscribe();
}
/*
Arayüzdeki keyup olayı ile bağlanmıştır
Yani tuştan parmak kaldırdıkça servise bir güncelleme olayı fırlatılır
ki bu tüm abonelerce alınır.
*/
updateArticle() {
this.ArticleService.update(this.article);
}
}</pre>
<p>article-list.component.html</p>
<pre class="brush:html;auto-links:false;toolbar:false" contenteditable="false"><div>
<button (click)='newArticle()'>Yeni makale başlat</button>
</div>
<div style="height: 100%;">
<span *ngFor='let a of articles | async' (click)='getArticle(a)'>
<b>{{a}}</b>
<br/>
</span>
</div>
<!--
*ngFor ile Typescript tarafındaki articles isimli diziyi dönüyoruz.
Her bir elemanı için {{a}} ile dizi elemanını basıyoruz ki bu içerde
rastgele üretilen dosya numarası oluyor.
click olayı tetiklendiğinde dosya numarasının içeriğini çeken getArticle metodu
çağırılıyor ki o da article-list.component.ts içerisinde yer alıyor.
button kontrolüne basıldığındaysa yine article-list.component.ts içerisindeki
newArticle metodu çağırılıyor.
--></pre>
<p>artcile-list.component.ts</p>
<pre class="brush:js;auto-links:false;toolbar:false" contenteditable="false">import { Component, OnInit, OnDestroy } from '@angular/core';
import { Observable, Subscription } from 'rxjs';
import { ArticleService } from 'src/app/article.service';
@Component({
selector: 'app-article-list',
templateUrl: './article-list.component.html',
styleUrls: ['./article-list.component.css']
})
/*
Bileşen, OnInit ve OnDestroy fonksiyonlarını implemente ediyor.
Yani bileşen oluşturulurken ve iade edilirken yaptığımız bağzı işlemler var.
Init'te güncel makale listesi için bir stream açılmakta ve o an üzerinde çalışılan
makale için bir abonelik başlatılmakta. Destroy metodunda ise üzerinde çalışılan makalenin aboneliğinden çıkılmakta.
articles değişkeni Observable tipinden bir string dizisi ve servisin allOfThem
özelliği ile ilişkilendirilip bir stream oluşması sağlanıyor.
Bileşen üzerinden socket sunucusuna fırlatılan olayların karşılığından fırlatılan olaylar,
Observable değişkenin güncel kalmasını sağlayacaktır.
*/
export class ArticleListComponent implements OnInit, OnDestroy {
articles: Observable<string[]>;
currentArticle: string;
private _subscription: Subscription;
constructor(private articleService: ArticleService) { }
ngOnInit() {
this.articles = this.articleService.allOfThem;
this._subscription = this.articleService.currentArticle.subscribe(a => this.currentArticle = a.id);
}
ngOnDestroy() {
this._subscription.unsubscribe();
}
// id değerine göre makale çekilmesi için gerekli sunucu olayını tetikler
getArticle(id: string) {
this.articleService.get(id);
}
// Yeni bir makale oluşturulması için gerekli olayı tetikler
newArticle() {
this.articleService.add();
}
}</pre>
<p>app.component.html <em>(HTML tablosunun üst tarafında article-list, alt satırında ise article bileşenlerini gösterecektir)</em></p>
<pre class="brush:html;auto-links:false;toolbar:false" contenteditable="false"><table>
<tr>
<td>
<app-article-list></app-article-list>
</td>
<td style="width: 200px;">
<app-article></app-article>
</td>
</tr>
</table></pre>
<p>app.component.ts</p>
<pre class="brush:js;auto-links:false;toolbar:false" contenteditable="false">import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'authorApp';
}</pre>
<p>Sunucu ve istemci tarafı uygulamalarımız artık hazır. Çalışma zamana geçip testlerimize başlayabiliriz.</p>
<h2>Çalışma Zamanı</h2>
<p>İstemcilerin dokümanlar üzerinde çalışmasını sağlamak için öncelikle node sunucusunu ayağa kaldırmamız gerekiyor. Bunu için</p>
<pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false">npm run start</pre>
<p>komutunu kullanabiliriz. İstemci tarafını çalıştırmak içinse,</p>
<pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false">ng serve</pre>
<p>terminal komutundan yararlanılabilir.</p>
<p>Servis localhost:4200 nolu port'tan ayağa kalkar. Bu zorunlu değildir ve isterseniz geliştirme ortamı için angular.json dosyasındaki serve kısmına yeni bir options elementi olarak port bilgisi ekleyebilirsiniz veya ng komutu ile --port anahtarını kullanabilirsiniz.</p>
<pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false">ng serve --port 4003</pre>
<p>gibi.</p>
<p>Örneği daha iyi anlamak için iki veya daha fazla istemci çalıştırmakta yarar var. Bir istemcide yeni bir sayfa açıp üzerinde yazarken diğer istemcide de aynı dosya numarası görünür ve değişiklikler karşılıklı olarak taraflara yansır. Yani Cenifır'ın 399 nolu dokümanda yaptığı değişikliği aynı dokümana bakan Brendon görebilir ve üstüne kendi değişikliklerini yazıp bunları Jenifer'ın görmesini sağlayabilir. Chat uygulaması gibisinden ama değil gibi...</p>
<p><img src="https://www.buraksenyurt.com/image.axd?picture=/2019/09/29/credit_2.png" alt="" /></p>
<p>Tasarım gerçekten çok kötü ancak amaç Socket.IO'nun Angular tarafında nasıl kullanılabileceğini anlamak olduğu için bir kaç fikir vermiş olmalı. En azından bana verdi ve aşağıda yazdığım maddelerdeki bilgileri öğrendiğimi ifade edebilirim.</p>
<h2>Ben Neler Öğrendim?</h2>
<ul>
<li>WebSocket protokolünün Node.js tarafında Socket.IO paketi yardımıyla nasıl kullanılabileceğini</li>
<li>emit ile bağlı istemciye ya da tüm istemcilere canlı yayının<em>(broadcasting)</em> nasıl yapılabileceğini</li>
<li>on, event olay dinleyicilerinin ne işe yaradığını</li>
<li>ng komutları ile proje oluşturulmasını, class, component ve service öğelerinin eklenmesini</li>
<li>Angular component'lerinin bir üst component içerisinde nasıl kullanılabileceğini</li>
<li>Bileşenlerin HTML tabanlı ön yüzünden, Typescript tarafındaki enstrümanlara<em>(metod, property vb)</em> nasıl ulaşılabileceğini</li>
</ul>
<p>Böylece geldik bir cumartesi gecesi çalışmasına ait derlemenin daha sonuna. Bu derlememizde Angular ile yazılmış istemcilerin Web Socket üzerinden bir birleriyle nasıl haberleşebileceğini incelemeye çalıştık. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.</p>2019-08-08T15:17:00+00:00.net.net coreangularsocket.ionqnodenode.jsangular clinpmtypescriptwebsocketsbroadcastingbsenyurtBilindiği üzere istemci-sunucu geliştirme modelinde gerçek zamanlı ve çift yönlü iletişim için WebSocket yaygın olarak kullanılan protokollerden birisi. Klasik HTTP request/response modelinden farklı olarak WebSocket protokolünde sunucu, istemcinin talep göndermesine gerek kalmadan mesaj gönderebiliyor. Chat uygulamaları, eş zamanlı oyunlar, finansal bildirim yapan ticari programlar, online doküman yönetim sistemleri ve benzerleri WebSocket protokolünün kullanıldığı ideal ortamlar. Benim 29 numaralı Saturday Night Works çalışmasındaki amacım Socket.IO kütüphanesinden yararlanan bir Node sunucusu ile Angular'da yazılmış bir web uygulamasını WebSocket protokolü tabanında deneyimlemekti. Hazırsanız notlarımızı derlemeye başlayalım.https://www.buraksenyurt.com/pingback.axdhttps://www.buraksenyurt.com/post.aspx?id=a0944841-09d6-4440-add4-62d3b38a94c02https://www.buraksenyurt.com/trackback.axd?id=a0944841-09d6-4440-add4-62d3b38a94c0https://www.buraksenyurt.com/post/socket-io-yardimiyla-realtime-calisan-bir-angular-uygulamasi-gelistirmek#commenthttps://www.buraksenyurt.com/syndication.axd?post=a0944841-09d6-4440-add4-62d3b38a94c0https://www.buraksenyurt.com/post/angular-ile-yazilmis-bir-web-uygulamasini-pwa-uyumlu-hale-getirmekAngular ile Yazılmış Bir Web Uygulamasını PWA Uyumlu Hale Getirmek2019-06-28T07:00:00+00:00bsenyurt<p><img style="float: right;" src="https://www.buraksenyurt.com/image.axd?picture=/2019/06/28/LesEclaireurs.png" alt="" />Geminin neredeyse tüm seyrüsefer sistemi ve radarı arka arkaya gelen alarm sinyalleri sonrası bozulmuştu. Güney pasifiği terk etmek üzere olan koca tekne en son Arjantin kıyılarına yakın seyrediyordu. Gecenin zifiri karanlığında ilerlerken kaptanın en büyük yol bulma ümitlerinden olan kuzey yıldızı bulutlarla kaplı gökyüzünden saatlerdir görülmüyordu.</p>
<p>Altı kişilik güverte mürettabı normal şartlarda geminin seyri için fazlasıyla yeterliydi. Yaklaşık yirmibin groston ağırlığındaki gemi son teknloji cihazlarla donatıldığı için az sayıda personel ile kıtalar arası seyahat edebiliyordu.</p>
<p>Ama şimdi kaptan ve yardımcısı dışındaki mürettebat geminin çeşitli noktalarına yayılmış, dürbünleriyle bir şeyler arıyordu. Korkutucu olan, yönü belirleyemezlerse kendilerini Antartika güzergahında bulabilecek olmalarıydı. Koca okyanusta bu derecede bir rota sapması işlerin daha da korkunç bir hal almasına neden olabilirdi.</p>
<p>Derken kıç tarafa giden denizcinin sesi duyuldu telsizden. Güverte umuttan daha da fazla bir hisle dolmuştu anında. Gemi, güçlü bir manevra ile geri dönüp ufukta belirmiş ve aralıklarla yanıp sönen ışığa doğru yöneldi. Kaptan mürettabatı tekrar güverteye davet ederken yardımcısına şöyle seslendi "Tanrıya şükür Eric. Sonunda Les Eclaireurs yüzünü gösterdi" </p>
<p>Les Eclaireurs...1920 yılında inşa edilen bu deniz feneri, Arjantinin en güney şehri olarak bilinen Ushuaia'nın yaklaşık 9.3 km doğusundadır. "Dünyanın Ucundaki Fener" olarak da bilinir. Turistlerin popüler uğrak noktası olan ve küçük bir ada üzerinde duran fener tarih boyunca bir çok denizci için yol gösterici olmuştur. Deniz feneri kelimesinin İngilizce karşılığı Lighthouse'dur ve bu kelime <a href="https://github.com/buraksenyurt/saturday-night-works" target="_blank">bir cumartesi gecesi çalışması</a>nda bana yol göstericilik yapmıştır. Öyleyse gelin derlememize başlayalım <em>(Biraz eski bir araştırma olsa da <a href="https://10mosttoday.com/10-most-famous-lighthouses-in-the-world/" target="_blank">dünyanın 10 ünlü feneri için şu yazıya</a> bakabilirsiniz. Ben özellikle İskoçya'daki Bell Rock deniz fenerinden çok etkilendim)</em></p>
<p>PWA<em>(Progressive Web App)</em> tipindeki uygulamalar özellikle mobil cihazlarda kullanılırken sanki AppStore veya PlayStore'dan indirilmiş native uygulamalarmış gibi görünürler. Ancak native uygulamalar gibi dükkandan indirilmezler ve bir web sunucusundan talep edilirler. Https desteği sunduklarından hat güvenlidir. Bağlı olan istemcilere push notification ile bildirimde bulunabilirler. Cihaz bağımsız olarak her tür form-factor'ü desteklerler. Bu uygulama modelinde Service Worker'lar iş başındadır ve sürekli taze kalınmasını sağlarlar. Düşük internet bağlantılarında veya internet olmayan ortamlarda çevrim dışı da çalışabilirler. URL üzerinden erişilen uygulamalar olduklarından kurulum ihtiyaçları yoktur.</p>
<p>Benim bu cumartesi gecesi çalışmasındaki amacım ise gayet basitti. Biraz yabancısı olduğum Angular ile basit bir web uygulaması yazmak ve bunu PWA uyumlu hale getirmek.</p>
<p>Peki bir web sayfasından gelen içeriğin PWA uyumluluğunu nasıl test edebiliriz? Bunun için Google'ın geliştirdiği ve Chrome üzerinde bulunan Lighthouse isimli uygulamadan yararlanabiliriz<em>(Ta taaaa...Hikayeyi bağladım işte)</em> F12 ile açılan Developer Tools'tan kolayca erişilebilen Lighthouse ile o anki sayfa için uyumluluk testleri yapabiliriz. Örneğin kendi blogum için bunu yaptığımda mobile cihazlardaki PWA uyumluluğunun %50 olarak çıktığını gördüm :/ Yarı yarıya uyumsuz. Bu nedir arkadaş ya?</p>
<p><img src="https://www.buraksenyurt.com/image.axd?picture=/2019/06/28/credit_1.png" alt="" /></p>
<p>Bakalım boş bir uygulama için bu durumu değiştirebilecek miyiz?</p>
<h2>Ön Hazırlıklar</h2>
<p>Angular ile ilgili işlemler için command-line interface<em>(CLI)</em> aracından yararlanabiliriz. Yoksa aşağıdaki ilk komutla kurmak lazım tabii. Angular CLI komut satırı bir çok konuda yardımcı olacaktır. Projenin oluşturulması, angular için yazılmış paketlerin kolayca eklenmesi vb...İkinci komutla projemizi hazır şablonla oluşturuyoruz. UI tarafında Material Design kullanmayı öğrenmeye çalışacağım. Bu nedenle proje klasörüne girdikten sonra <strong>ng add</strong> komutu ile material'ın angular sürümünü de projeye ilave etmemiz lazım <em>(Prebuilt tema seçimini Indigo/Pink olarak bıraktım)</em></p>
<pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false">sudo npm install -g @angular/cli
ng new quotesify
cd quotesify
ng add @angular/material</pre>
<h2>Kod Tarafı</h2>
<p>Kodları mümkün mertebe açıklamalarla desteklemeye çalıştım ancak genel hatları ile önyüz bileşenini değiştirdiğimiz, farklı bir adresle haberleşecek bir servis yazdığımızı ifade edebiliriz. İşe ilk olarak app.module.ts dosyasından başlayalım. app.module.ts dosyasında HTTP çağrılarını yapmamızı sağlayan HttpClientModule modülünü tanımlıyoruz. Böylece HttpClient, ana modüle bağlı tüm bileşen ve servislere enjekte edilebilir<em>(Evet burada da Dependency Injection var. O her yerde :P ) </em>Ayrıca UI tarafı kontrolleri için ilgili Material modüllerini de eklememiz lazım. Örnekte Toolbar, Card ve Button kontrollerine ait modülleri ele almaktayız. Kodda geçen diğer modüller zaten şablon ile birlikte gelmiş olanlar.</p>
<pre class="brush:js;auto-links:false;toolbar:false" contenteditable="false">import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
/*
UI tasarımında kullanacağımız Material bileşenlerine ait modül bildirimleri
*/
import { MatToolbarModule } from '@angular/material/toolbar';
import { MatCardModule } from '@angular/material/card';
import { MatButtonModule } from '@angular/material/button';
/*
HttpClientModule'ü burada import ettik.
Böylece HTTP çağrıları yapabilmemizi sağlayan
HttpClient nesnesini ana modüle bağlı olan
tüm componenetlere enjekte edebiliriz.
HttpClient'ı arayüze veri döndüren dummy bir API
servisine Get çağrısı yapmak için kullanacağız.
*/
import { HttpClientModule } from '@angular/common/http';
import { ServiceWorkerModule } from '@angular/service-worker';
import { environment } from '../environments/environment';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
NoopAnimationsModule,
HttpClientModule, //Buraya da eklemeyi unutmamak lazım
// Aşağıdakilerde Material modülleri için yapılan ilaveler
MatToolbarModule,
MatCardModule,
MatButtonModule,
// PWA güncellemesi sonrası eklenen Worker Service kaydının yapılması
ServiceWorkerModule.register('ngsw-worker.js', { enabled: environment.production })],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }</pre>
<p>Bu adımdan sonra <strong>ng g service dummy</strong> terminal komutunu kullanarak DummyService isimli bir servis sınıfı ekliyoruz. <a href="https://jsonplaceholder.typicode.com/posts" target="_blank">Şuradaki dummy servis</a> adresinden veri çekip sunmakla görevli bir modül esas itibariyle. Sunmak derken uygulama arayüzündeki bileşenleri besleyecek diyebiliriz.</p>
<pre class="brush:js;auto-links:false;toolbar:false" contenteditable="false">/*
Servisin görevi https://jsonplaceholder.typicode.com/posts adresinden
dummy veri çekmek ve bunu bir Observable nesne olarak sunmak.
*/
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http'; // HttpClient nesnesini içeriye constructor üzerinden enjekte edeceğiz
import { Observable } from 'rxjs'; //RxJS kütüphanesinden Observable tipini kullanıyoruz
/*
JSON servisinden dönen öğeleri ifade eden arayüz tanımı.
Post tipini temsilen bazı alanlar içeriyor.
*/
export interface Post {
userId: number;
id: number;
title: string;
body: string;
}
@Injectable({
providedIn: 'root'
})
/*
DummyService'i üretmek için komut satırından
ng g service dummy
komutunu kullandık
*/
export class DummyService {
// Constructor bazlı dependency injection
constructor(private httpClient: HttpClient) { }
/*
get metodu Observable tipte bir koleksiyon döndürür
*/
get(): Observable<Post[]> {
var url = "https://jsonplaceholder.typicode.com/posts";
/*
url ile belirtilen adrese get talebi gönderiyor
ve içeriğini Post dizisi olarak alıp
Observable nesnesiyle geriye dönüyoruz
*/
return <Observable<Post[]>>this.httpClient.get(url);
}
}</pre>
<p>Oluşturulan servisi app.component.ts dosyasında kullanabilmek içinse bir takım eklemeler yapmalıyız.</p>
<pre class="brush:js;auto-links:false;toolbar:false" contenteditable="false">import { Component, OnInit } from '@angular/core';
import {DummyService} from './dummy.service'; // yeni eklediğimiz servisi kullanacağımızı belirtiyoruz
import {Post} from './dummy.service'; //ki Post arayüz tipinide oradan export etmiştik
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
// AppComponent, OnInit metodunu uygulamalı
export class AppComponent implements OnInit {
title = 'Dummy Posts';
posts: Array<Post>; // çekilen Post verilerini saklamak için kullanacağımız dizi
constructor(private dummyService:DummyService){
}
/*
OnInit, Angular bileşeninin yaşam döngüsünde çalışan metodlardan birisi.
Component oluşturulurken devreye girip ilgili servisten veriyi çeken bir
işlevi yürütecek şekilde programlandı.
OnInit AppComponent bileşeni oluşurken bir seferliğine çağrılır.
*/
ngOnInit(){
/*
Constructor'dan enjekte edilen DummyService örneğini kullanarak
get metoduna başvuruyor ve Post dizisini çekiyoruz.
DummyService servisindeki get metodu Observable bir nesne döndürüyor.
Burada ona abone(subscribe) oluyoruz. Asenkron çalışma durumu söz konusu
olduğunda servis ilgili veriyi çektiğinde kendisine abone olanları da bilgilendirecektir.
Yani çekilen Post dizisindeki değişim(bu senaryoda servisten alınması) component'e
bildirilmiş olacaktır.
*/
this.dummyService.get().subscribe((data:Array<Post>)=>{
this.posts=data;
},(err)=>{
console.log(err);
});
}
}</pre>
<p>Önyüz bileşeni olarak src/app/app.component.html içeriği de tamamen değiştirildi. Material bileşenlerine yer verildi.</p>
<pre class="brush:html;auto-links:false;toolbar:false" contenteditable="false"><mat-toolbar>
<mat-toolbar-row>
<span>Some dummy posts from universe</span>
</mat-toolbar-row>
</mat-toolbar>
<main>
<mat-card *ngFor="let post of posts">
<mat-card-header>
<mat-card-title>{{post.title}}</mat-card-title>
</mat-card-header>
<mat-card-content>
{{post.body}}
</mat-card-content>
</mat-card>
</main></pre>
<p>Toolbar tipinde bir Navigation kontrolü, Post bilgilerini göstermek içinse Card kontrolünden yararlanıyoruz. UI, bağlı olduğu AppComponent içerisindeki posts dizisini kullanıyor. Tüm dizi elemanlarında gezmek içinse *ngFor komutundan yararlanılmakta. Bir özellik değerini arayüzde göstermek istediğimizde {{post.title}} benzeri notasyonlar kullandığımız da gözden kaçmamalı.</p>
<h2>PWA Uyumluluğu için Hazırlıklar</h2>
<p>Amacımız uygulamanın PWA uygunluğunu kontrol etmek olduğu için öncelikle onu canlı ortam için hazırlamalıyız<em>(Yani Production Build işlemini yapmamız gerekiyor)</em> Nitekim PWA özelliklerinin bir çoğu geliştirme ortamına dahil edilmemekte. Build işlemi için ng CLI aracını aşağıdaki gibi kullanabiliriz.</p>
<pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false">ng build --prod</pre>
<p><img src="https://www.buraksenyurt.com/image.axd?picture=/2019/06/28/credit_2.png" alt="" /></p>
<p>Uygulama dist klasörüne build edilmiş olur. Hizmete sunmak için http-server gibi bir araçtan yararlanılabilir. Eğer sistemde yüklü değilse npm ile kurmamız gerekir. İlk komutla bunu yapıyoruz. İkinci terminal komutuysa uygulamayı localhost üzerinden ayağa kaldırmakta.</p>
<pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false">sudo npm install -g http-server
cd dist
cd quotesify
http-server -o</pre>
<p>Bunun sonucu olarak 127.0.0.1:8080 veya 8081 portundan yayın yapılır ve uygulama açılır.</p>
<p><img src="https://www.buraksenyurt.com/image.axd?picture=/2019/06/28/credit_3.png" alt="" /></p>
<p>Uygulama çalıştıktan sonra F12 ile Audits kısmına gidip 'Run Audit' ile PWA testi başlatılırsa, Lighthouse bize aşağıdakine benzer sonuçlar verecektir<em>(Tabii sizin denediğiniz vakitlerde bu kurallar değişmiş olabilir. O nedenle bilgileri güncellemekte yarar var)</em></p>
<p><img src="https://www.buraksenyurt.com/image.axd?picture=/2019/06/28/credit_4.png" alt="" /></p>
<p>PWA uyumluluğu oldukça düşük ki bu zaten şu aşamada beklediğimiz bir şey. PWA uyumlu hale getirmek için neler yapılabilir bakalım.</p>
<h2>İhlal Edilen PWA Kriterleri</h2>
<p>Önce hangi kuralların ihlal edildiğinde ve bunların ne anlama geldiğine bir bakalım.</p>
<ul>
<li>Uygulamanın HTTPS desteği olmazsa olmazlardandır. Development tarafında sıkıntı olmasa da uygulamayı üretim ortamlarına aldığımızda sertifika tabanlı iletişim sağlanmalıdır.</li>
<li>Service Worker olmaması sebebiyle offline çalışma ve cache kabiliyetlerinin yanı sıra push notification kabiliyletleri de ortada yoktur. Service Worker, ağ proxy'si gibi bir görev üstlenir ve uygulamanın çektiği öğeler<em>(asset)</em> ile veriyi taleplerden<em>(requests)</em> yakalayıp önbelleğe alma operasyonlarında işe yarar.</li>
<li>Manifesto dosyasının bulunmayışı ki bu dosyada uygulama adı, kısa açıklaması, icon'lar ve diğer gerekli bilgiler yer alır. Ayrıca manifesto dosyası sayesinde add-to-home-screen ve splash screen özellikleri de etkinleşir.</li>
<li>Progressive Enhancment desteğinin olmaması da bir PWA ihlalidir. Uygulamanın çağırıldığı tarayıcıya göre ileri seviye özelliklerin kullanılabileceğinin ifade edilmesi beklenmektedir.</li>
</ul>
<h2>PWA Uyumluluğu için Yapılanlar</h2>
<p>Angular tarafında uygulamayı PWA uyumlu hale getirmek için aşağıdaki terminal komutunu çalıştırmak yeterlidir. <em>(Proje klasöründe çalıştırdığımıza dikkat edelim)</em></p>
<pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false">ng add @angular/pwa</pre>
<p>Komut çalıştırıldığında eksik olan manifesto ve service worker dosyaları eklenir. Ayrıca assets altındaki icon'ların form factor desteği açısından farklı boyutları oluşur.</p>
<p><img src="https://www.buraksenyurt.com/image.axd?picture=/2019/06/28/credit_5.png" alt="" /></p>
<p>Yeni bir dağıtım paketi çıktığımızda PWA için eklenen Service Worker ve manifesto dosyalarını da görebiliriz.</p>
<p><img src="https://www.buraksenyurt.com/image.axd?picture=/2019/06/28/credit_6.png" alt="" /></p>
<p>Tekrardan Lighthouse raporunu çektiğimizde aşağıdaki gibi %92lik bir karşılama oranı oluştuğunu görebiliriz. Fena değil ama eksik. Çünkü HTTPS desteğini göremedi.</p>
<p><img src="https://www.buraksenyurt.com/image.axd?picture=/2019/06/28/credit_7.png" alt="" /></p>
<p>Peki ya kalan HTTPS ihlalini development ortamında nasıl aşabiliriz? Aşabilir miyiz? Eğer buraya kadar gelebildiyseniz bir adım daha ilerleyebilirsiniz sevgili okur ;)</p>
<h2>Ben Neler Öğrendim?</h2>
<p><a href="https://github.com/buraksenyurt/saturday-night-works/tree/master/No%2028%20-%20PWA%20with%20Angular" target="_blank">Yirmisekiz numaralı bu cumartesi gecesi çalışması</a>nın da bana kattığı değerli bilgiler oldu elbette. Bunları kabaca aşağıdaki gibi sıralayabilirim.</p>
<ul>
<li>Angular CLI'ın<em>(command-line interface)</em> temel komutlarını</li>
<li>Component'lere servislerin nasıl enjekte edilebileceğini</li>
<li>Çok basit anlamda Material bileşenlerini arayüzde nasıl kullanabileceğimi</li>
<li>PWA tipindeki uygulamaların genel karakteristiklerini ve avantajlarını</li>
<li>PWA ihlallerinin kısaca ne anlama geldiklerini ve tespitinde Lighthouse'un nasıl kullanılabileceğini</li>
</ul>
<p>Böylece geldik bir maceramızın daha sonuna. Bu yazıda basit bir Angular uygulaması geliştirip bunu Progressive Web App modelinde yayınlanabilecek kıvama getirmeye çalıştık. Umarım sizler için de faydalı bir çalışma olmuştur. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.</p>2019-06-28T07:00:00+00:00angularangular climaterial designtypescriptjavascriptpwaprogressive web appgoogle chromeservice workerform factorcommand line interfaceclilighthousechrome developer toolsbsenyurtPWA(Progressive Web App) tipindeki uygulamalar özellikle mobil cihazlarda kullanılırken sanki AppStore veya PlayStore'dan indirilmiş native uygulamalarmış gibi görünürler. Ancak native uygulamalar gibi dükkandan indirilmezler ve bir web sunucusundan talep edilirler. Https desteği sunduklarından hat güvenlidir. Bağlı olan istemcilere push notification ile bildirimde bulunabilirler. Cihaz bağımsız olarak her tür form-factor'ü desteklerler. Bu uygulama modelinde Service Worker'lar iş başındadır ve sürekli taze kalınmasını sağlarlar. Düşük internet bağlantılarında veya internet olmayan ortamlarda çevrim dışı da çalışabilirler. URL üzerinden erişilen uygulamalar olduklarından kurulum ihtiyaçları yoktur.https://www.buraksenyurt.com/pingback.axdhttps://www.buraksenyurt.com/post.aspx?id=1afe44af-8b67-404a-b8c0-e6b55e2307f30https://www.buraksenyurt.com/trackback.axd?id=1afe44af-8b67-404a-b8c0-e6b55e2307f3https://www.buraksenyurt.com/post/angular-ile-yazilmis-bir-web-uygulamasini-pwa-uyumlu-hale-getirmek#commenthttps://www.buraksenyurt.com/syndication.axd?post=1afe44af-8b67-404a-b8c0-e6b55e2307f3https://www.buraksenyurt.com/post/cloud-firestore-ile-angular-kullanimiCloud Firestore ile Angular Kullanımı2019-05-24T09:00:00+00:00bsenyurt<p><img style="float: right;" src="https://www.buraksenyurt.com/image.axd?picture=/2019/04/32/magicjohson.png" alt="" />Earvin (Magic) Johnson. Michael Jordan'la geçen gençlik yıllarımın henüz başlarında rastladığım NBA'in ve Los Angles Lakers'ın 2.06lık unutulmaz oyun kurucusu. O dönemlerde yaptığı inanılmaz assistler ve oyun zekası hala aranır nitelikte. Aslında sadece oyun kurucu değil zaman zaman şutör gard ve uzun forvet pozisyonlarında da oynamıştır.</p>
<p>Lakers tarafından 1979 yılında birinci sırada draft edilen Johnson toplamda 5 NBA şampiyonluğu yaşamış efsanelerden birisi. <a href="https://stats.nba.com/player/77142/career/" target="_blank">NBA istatistiklerine göre</a> oynadığı 906 maçta 19.5 sayı ve 11.2 assist ortalamaları ile double double yapmıştır. Toplamda 10141 asist ile tüm zamanların en çok asist yapan 5nci oyuncusu durumunda. 32 numaralı formasıyla 12 sezon Lakers'da görev alan oyun kurucunun hayatını sevgili Murat Murathanoğlu'nun eşsiz anlatımıyla dinlemek isterseniz <a href="https://youtu.be/xyxIiLpNDu8" target="_blank">şöyle buyrun</a>. Onun <a href="https://github.com/buraksenyurt/saturday-night-works" target="_blank">Saturday-Night-Works</a> çalıştayımla olan tek ilgisi ise <a href="https://github.com/buraksenyurt/saturday-night-works/tree/master/No%2032%20-%20Angular%20with%20Firebase" target="_blank">forma numarası</a>. Hoş bir giriş olsun istedim de :[]</p>
<p>Gelelim derleyip toparladığım blog notlarıma.</p>
<p>Angular tarafına yavaş yavaş alışmaya başlamıştım. Yine de fazladan idman yapmaktan ve tekrar etmekten zarar gelmez diye düşünüp farklı örnekleri uygulamaya çalışıyordum. Bu sefer temel CRUD<em>(Create Read Update Delete)</em> operasyonlarını Cloud Firestore üzerinden icra ederken Angular'da koşmaya çalışmışım. Amaçlarımdan birisi servis tarafında Form kontrolü kullanabilmek. Örnekte ikinci el eşya satışı yapmak üzere kurgulanan basit bir web arayüzü söz konusu. Programı her zaman olduğu gibi WestWorld<em>(Ubuntu 18.04, 64bit)</em> üzerinde yazdım<em>.</em></p>
<blockquote>
<p>Google bilindiği üzere 2014 yılında bir bulut servis sağlayıcı olan Firebase'i satın almıştı. Sonrasında bu servisin Web ve Mobil tarafı için kullanılabilen Firestore isimli NoSQL tabanlı veri tabanını kullanıma açtı. Firestore, Realtime Database alternatifi olarak kullanıma sunuldu. Realtime Database'e göre bazı farklılıkları var. Örneğin sadece mobil değil web tarafı için de offline kullanım imkanı sağlıyor. Ölçekleme bulut sisteminde otomatik olarak yapılıyor. Realtime Database'e göre karmaşık sorgu performansının daha iyi olduğu belirtiliyor. Ücretlendirme politikası uygulamanın büyüklüğüne göre Realtime Database'e göre daha ekonomik olabiliyor. Dolayısıyla mobil tarafta ilerleyen Startup projelerinin MVP modelleri için ideal bir çözüm gibi duruyor.</p>
</blockquote>
<h2>İlk Hazırlıklar</h2>
<p>Tabii konumuz esas itibariyle Angular deneyimini arttırmak. Cloud Firestore bu noktada bir veri sağlayıcısı rolünü üstlenecek. İşe Angular projesini oluşturarak başlayabiliriz. Bir Angular projesini kolayca oluşturmanın en etkili yolu bildiğiniz üzere CLI<em>(Command-Line Interface)</em> aracından yararlanmak. Dolayısıyla sistemimizde Angular CLI yüklü olmalı. Eğer yüklü değilse aşağıdaki ilk terminal komutunu bu amaçla kullanabiliriz.</p>
<pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false">sudo npm install -g @angular/cli
ng new quick-auction
npm i --save bootstrap firebase @angular/fire
cd speed-sell
ng g c products
ng g c product-list
ng g s shared/products</pre>
<p>Takip eden komutlara gelirsek...</p>
<p><strong>ng new</strong> ile quick-action isimli yeni bir Angular projesi oluşturmaktayız<em>(Sorulan sorularda Routing seçeneğine No dedim ve Style olarak CSS'i seçili bıraktım. Ancak bunun yerine bootstrap kullanacağız)</em> <strong>npm i</strong> ile başlayan komutlarda stil için <strong>bootstrap</strong>, Google'ın Cloud Firestore tarafı ile konuşabilmek içinde <strong>firebase</strong> ve anglular'ın firebase ile konuşabilmesi içinse <strong>@angular/fire</strong> paketlerini ekliyoruz. <strong>ng g</strong> ile başlayan komutlarda iki bileşen<em>(component)</em> ve her iki bileşen için ortaklaşa kullanılacak bir servis nesnesi oluşturuyoruz. Bu servis temel olarak firestore veri tabanı ile olan iletişim görevlerini üstlenecek.</p>
<h2>Firebase Tarafı<em>(Cloud Firestore)</em></h2>
<p>Google Cloud tarafında yapacağımız bazı hazırlıklar var. Firebase tarafında yeni bir proje açıp içerisinde test amaçlı bir Firestore veri tabanı oluşturacağız. Öncelikle b<a href="https://console.firebase.google.com/" target="_blank">u adresten</a> Firebase Console'a gidelim ve örnek bir proje üretelim. Ben aşağıdaki ekran görüntüsüneki gibi quict-auctions-project isimli bir uygulama oluşturdum<em>(Esasen quick demek istemiştim ama dikkatsizlik olsa gerek quict demişim, olsun. Özgün bir isim olmuş :P )</em></p>
<p><img src="https://www.buraksenyurt.com/image.axd?picture=/2019/04/32/credit_1.png" alt="" /></p>
<p>Sonrasında Database menüsünden veya kocaman turuncu kutucuk içerisindeki Cloud Firestorm bölümünden hareket ederek yeni bir veri tabanı oluşturalım. Aşağıdaki ekran görüntüsünde olduğu gibi veri tabanını Test modunda açabiliriz.</p>
<p><img src="https://www.buraksenyurt.com/image.axd?picture=/2019/04/32/credit_2.png" alt="" /></p>
<p>Şimdi Angular uyguaması ile Firebase servis tarafını tanıştırmalıyız. Project Overview kısmından hareket ederek</p>
<p><img src="https://www.buraksenyurt.com/image.axd?picture=/2019/04/32/credit_3.png" alt="" /></p>
<p>kırmızı kutucuktaki düğmeye basalım. Gerekli ortam değişkenleri otomatik olarak üretilecektir. Karşımıza gelen ekrandaki config içeriğini uygulamanın environment.ts dosyası içerisine almamız yeterli.</p>
<p><img src="https://www.buraksenyurt.com/image.axd?picture=/2019/04/32/credit_4.png" alt="" /></p>
<h2>Kod Tarafı</h2>
<p>Gelelim kod tarafında yaptığımız değişikliklere. Arayüz tarafını daha şık hale getirmek için bootstrap kullanıyoruz. Bu nedenle angular.json dosyasındaki style elementini değiştirdik.</p>
<pre class="brush:xml;auto-links:false;toolbar:false" contenteditable="false">"styles": [
"node_modules/bootstrap/dist/css/bootstrap.min.css",
"src/styles.css"
],</pre>
<p>Uygulama, satılacak ürünlerin yönetimi ilgili iki bileşen kullanıyor. Hatırlayacağınız üzere bunları terminalden üretmiştik<em>(products ve product-list)</em> Birisi tipik listeleme diğeri ise ekleme işlemi için kullanılacak. Bu bileşenlere ait HTML ve Typescript kodlarını aşağıdaki gibi geliştirebiliriz. Kodların anlaşılması adına mümkün mertebe yorum satırları kullandım.</p>
<p>products.component.ts</p>
<pre class="brush:js;auto-links:false;toolbar:false" contenteditable="false">import { Component, OnInit } from '@angular/core';
import { ProductsService } from '../shared/products.service'; // ProductsService modülünü bildiriyoruz
@Component({
selector: 'app-products',
templateUrl: './products.component.html',
styleUrls: ['./products.component.css']
})
export class ProductsComponent implements OnInit {
constructor(private productsService: ProductsService) { } // Constructor injection ile ProductsService tipini içeriye alıyoruz
auctions = []; // açık artırma verilerini tutacağımız array
ngOnInit() {
}
// Bileşendeki button'a basıldığında (click) niteliğine atanan olay bildirimi nedeniyle bu metod çalışacaktır
onSubmit() {
let formData = this.productsService.productForm.value; // aslında servis tarafındaki form kontrolü bileşenle ilişkilendirildiğinden girilen değerler oraya da yansır
console.log(formData); // F12 ile tarayıcı Console penceresinden bu çıktıya bakabiliriz
this.productsService.addProduct(formData);
}
}</pre>
<p>products.component.html</p>
<pre class="brush:html;auto-links:false;toolbar:false" contenteditable="false"><form [formGroup]="this.productsService.productForm">
<div class="form-group">
<label for="lblTitle">Tanıtım başlığı</label>
<input type="text" formControlName="title" class="form-control" id="txtTitle" placeholder="Tanıtım başlığını giriniz">
</div>
<div class="form-group">
<label for="lblSummary">Açıklaması</label>
<input type="text" formControlName="summary" class="form-control" id="txtSummary" placeholder="Ne satıyorsunuz az biraz bilgi...">
</div>
<div class="form-group">
<label for="lblPrice">Fiyat</label>
<input type="number" formControlName="price" class="form-control" id="txtPrice" placeholder="10">
</div>
<div class="form-group form-check">
<input type="checkbox" formControlName="bargain" class="form-check-input" id="chkBargain">
<label class="form-check-label" for="chkBargain">Pazarlık olur mu?</label>
</div>
<button class="btn btn-primary" (click)="onSubmit()">Yolla</button>
</form>
<!--
form elementindeki [formGroup] niteliğine dikkat edelim. Buraya atanan değer,
bileşene enjekte edilen ProductsService nesnesine ait form özelliğidir.
Servis tipinin productForm değişkenindeki alanlar bu bileşen üzerindeki kontrollere
formControlName niteliği yardımıyla bağlanırlar.
--></pre>
<p>product-list.component.ts</p>
<pre class="brush:js;auto-links:false;toolbar:false" contenteditable="false">import { Component, OnInit } from '@angular/core';
import { ProductsService } from '../shared/products.service'; // ProductService modülünü bildiriyoruz
@Component({
selector: 'app-product-list',
templateUrl: './product-list.component.html',
styleUrls: ['./product-list.component.css']
})
export class ProductListComponent implements OnInit {
constructor(private productService: ProductsService) { } // servisi constructor üzerinden enjekte ettik
allProducts; // Firestore koleksiyonundaki tüm dokümanları temsil edecen değişkenimiz
ngOnInit() {
/*
Bileşen initialize aşamasındayken servisten tüm ürünleri çekiyoruz.
Subscribe metoduyla da servisin getProducts metodundan dönen sonuç kümesini,
allProducts isimli değişkene bağlıyoruz ki bunu bileşenin ön yüzü kullanıyor
*/
this.productService
.getProducts()
.subscribe(res => this.allProducts = res);
}
/*
Bir ürünü silmek için kullandığımız metod.
Servis tarafındaki deleteProduct çağrılıyor.
Parametre olarak o anki product içeriği gönderilmekte
*/
delete = p => this.productService.deleteProduct(p).then(r => {
//alert('silindi');
});
/*
Ürünün sadece bargain özelliğini update eden bir metod
olarak düşünelim. Senaryoda pazarlık payı olup olmadığını belirten
checkbox'ın durumunu güncelletiyoruz
*/
// Güncelleme örneği (fiyatı 10 birim arttırıyoruz)
increasePrice = p => this.productService.updateProduct(p, 10);
// Güncelleme örneği (fiyatı 10 birim düşürüyoruz)
decreasePrice = p => this.productService.updateProduct(p, -10);
}</pre>
<p>product-list.component.html</p>
<pre class="brush:html;auto-links:false;toolbar:false" contenteditable="false"><table class="table">
<thead class="thead-dark">
<tr>
<th>Açıklama</th>
<th>Başlık</th>
<th>Fiyat</th>
<th>Pazarlık?</th>
<th></th>
</tr>
</thead>
<tbody>
<tr *ngFor="let product of allProducts">
<td>{{product.payload.doc.data().summary}}</td>
<td>{{product.payload.doc.data().title}}</td>
<td>{{product.payload.doc.data().price}}</td>
<td>
{{product.payload.doc.data().bargain?'Var':'Yok'}}
</td>
<td>
<div class="btn-group-sm">
<button class="primary btn-default btn-block" (click)="increasePrice(product)">+</button>
<button class="primary btn-default btn-block" (click)="decreasePrice(product)">-</button>
<button class="primary btn-danger btn-block" (click)="delete(product)">Sil</button>
</div>
</td>
</tr>
</tbody>
</table>
<!--
Klasik bir Grid tasarımı söz konusu.
*ngFor ile bileşenin init metodunda doldurulan allProducts dizisini dönüyoruz.
Firestore'dan gelen her bir dokümanın elemanlarına ulaşmak için,
payload.doc.data().[özellik adı] notasyonunu kullandık.
Sil başlıklı button'a basıldığında bileşendeki delete metodunu çağrılmış oluyor.
Checkbox kontrolünün click olayında bileşendeki güncelleme metodunu ve dolayısıyla
servis tarafındaki versiyonunu çağırmış oluyoruz.
--></pre>
<p>CRUD operasyonları her iki bileşen içinde ortaklaşa kullanılabilecek fonksiyonellikler. Bu nedenle Shared klasörü altında konuşlandırdığımız products.service.ts isimli bir tip mevcut.</p>
<pre class="brush:js;auto-links:false;toolbar:false" contenteditable="false">import { Injectable } from '@angular/core';
import { FormControl, FormGroup } from "@angular/forms"; // FormGroup ve FormControl tiplerini kullanabilmek için eklemeliyiz
import { AngularFirestore } from "@angular/fire/firestore"; // Firestore tarafı ile konuşmamızı sağlayacak modül. Servisini constructor'da enjekte ediyoruz
@Injectable({
providedIn: 'root'
})
export class ProductsService {
constructor(private firestore: AngularFirestore) { }
/*
Yeni bir FormGroup nesnesi örnekliyoruz.
title, summary, price ve online isimli FormControl nesneleri içeriyor.
bu özelliklere atanan değerleri Firebase tarafına yazacağız.
element adları arayüz tarafında da birebir kullanılacaklar
*/
productForm = new FormGroup({
title: new FormControl(''),
summary: new FormControl(''),
price: new FormControl(100),
bargain: new FormControl(false),
})
/*
Firestore veritabanına yeni bir Product verisi eklemek için kullanılan servis metodu.
collection ile Firestore tarafındaki koleksiyonu işaret ediyoruz.
Gelen json içeriği products isimli koleksiyona yazılıyor.
*/
addProduct(p) {
return new Promise<any>((resolve, reject) => {
this.firestore.collection("products").add(p).
then(res => { }, err => reject(err));
});
}
getProducts() {
/*
Firestore veri tabanındaki products koleksiyonu içerisinde yer alan tüm dokümanları alıyoruz.
snapshotChanges çağrısı değişikliklerin kontrol altında olmasını sağlar.
Bizim değişiklikleri yakalayıp güncellemeler yapmamıza gerek kalmaz.
*/
return this.firestore.collection("products").snapshotChanges();
}
// silme işlemini üstlenen servis metodumuz
deleteProduct(p) {
return this.firestore
.collection("products")
.doc(p.payload.doc.id) // firestrore tarafındaki id bilgisini kullanacak.
.delete();
}
// Güncelleme operasyonu. rate değişkenine gelen değere göre price değerini değiştiriyoruz
updateProduct(p, rate) {
// Önce üzerinde çalışılan veriyi alalım.
var prd=p.payload.doc.data();
if(prd.price==10 && rate<0) // fiyatı sıfırın altına indirmek istemeyiz çünkü
return;
// Üst limit kontrolü de konulabilir belki
// fiyat arttırımı veya azaltımı uygunsa yeni değeri alıyoruz ve firestore üzerinden güncelleme yapıyoruz
var newPrice=prd.price+rate;
return this.firestore
.collection("products")
.doc(p.payload.doc.id)
.set({ price: newPrice }, { merge: true });
// merge özelliğine atanan true değeri, tüm entity değerlerinin güncellenmesi yerine sadece metoda ilk parametre ile gelenlerin ele alınmasını söyler.
}
}</pre>
<p>Eklediğimiz bileşenleri kullandığımız yer app nesnesi. Angular tarafındaki ana bileşenimiz olarak düşünebiliriz. Dolayısıyla modül bildirimleri ve bileşenlerin HTML yerleşimleri için app.module.ts ve app.component.html dosyalarını da kodlamamız gerekiyor.</p>
<p>app.module.ts</p>
<pre class="brush:js;auto-links:false;toolbar:false" contenteditable="false">import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { environment } from "src/environments/environment"; //environment.ts içerisindeki firebaseConfig sekmesinin anlaşılabilmesi için gerekli modül
import { AngularFireModule } from "@angular/fire";
import { AngularFirestoreModule } from "@angular/fire/firestore";
import { AppComponent } from './app.component';
import { ProductsComponent } from './products/products.component';
import { ProductListComponent } from './product-list/product-list.component';
import { ProductsService } from './shared/products.service'; // Tüm bileşenlerde kullanabilmek için ProductsService modülünü bildirip alttaki providers özelliğine de ekledik
import {ReactiveFormsModule} from '@angular/forms'; // Service tarafında FormControl ve FormGroup modüllerini kullanabilmek için bildirdik ve aşağıdaki import kısmında ekledik
@NgModule({
declarations: [
AppComponent,
ProductsComponent,
ProductListComponent
],
imports: [
BrowserModule,
ReactiveFormsModule,
AngularFireModule.initializeApp(environment.firebaseConfig), // AngularFireModule' ü environment.ts içerisindeki firebaseConfig ayarları ile başlatmış olduk
AngularFirestoreModule
],
providers: [ProductsService],
bootstrap: [AppComponent]
})
export class AppModule { }</pre>
<p>ve app.component.html</p>
<pre class="brush:html;auto-links:false;toolbar:false" contenteditable="false"><!--
Uygulama hizmete alındığında render edilecek olan ana bileşenimiz.
ng g c komutları ile oluşturduğumuz products ve product-list bileşenlerini bootstrap grid sistemini kullanarak ekrana yerleştiriyoruz.
class niteliklerinde kullandığımzı değerler ile ortamı biraz renklendirmeye çalıştım
-->
<div class="container px-lg-5">
<div class="row">
<h1>Hızlı Satış?</h1>
</div>
<div class="row border border-primary rounded">
<div class="col py-3 px-lg-3 col-md-12">
<app-products></app-products>
</div>
</div>
<div class="row border border-dark rounded">
<div class="col py-3 px-lg-3 col-md-12">
<app-product-list></app-product-list>
</div>
</div>
</div></pre>
<p>Son olarak Firestore tarafı için gerekli apiKey, databaseUrl, senderId, projectId gibi bilgilerin environment.ts dosyasına eklenmesi lazım ki çalışma zamanında kullanılabilsinler. Bu bilgileri Google Cloud tarafı otomatik olarak üretmişti hatırlayacağınız üzere.</p>
<pre class="brush:js;auto-links:false;toolbar:false" contenteditable="false">export const environment = {
production: false,
// Firebase'in orada oluşturduğumuz projemiz için bize verdiği ayarlar
firebaseConfig: {
apiKey: ".....", //Burası sizin projenizin Api Key değeri olmalı
authDomain: "quict-auctions-project.firebaseapp.com",
databaseURL: "https://quict-auctions-project.firebaseio.com",
projectId: "quict-auctions-project",
storageBucket: "quict-auctions-project.appspot.com",
messagingSenderId: "....." // Bu da sizin projeniz için verilen senderId değeri olmalı
},
};</pre>
<p>Hepsi bu kadar :) Artık uygulamayı çalıştırıp sonuçlarına bakabiliriz.</p>
<h2>Çalışma Zamanı</h2>
<p>Uygulamayı çalıştırmak için terminalden</p>
<pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false">ng serve</pre>
<p>komutunu vermemiz yeterli. 4200 numaralı port üzerinden web arayüzüne erişebiliriz. WestWorld testlerinde benim aldığım örnek bir ekran görüntüsünü aşağıda bulabilirsiniz<em>(Hani ispatı olsun da sonra çalışmıyor bu filan demeyelim)</em></p>
<p><img src="https://www.buraksenyurt.com/image.axd?picture=/2019/04/32/credit_5.png" alt="" /></p>
<p>Örneğin ilgi çekici yanlarından birisi, önyüz ve Firestore taraflarının eş zamanlı olarak güncel kalabilmeleridir. Firestore web konsolunda eriştiğimiz dokümanlarda yapacağımız değişiklikler anında önyüz tarafına push edilir, önyüzde yaptığımız değişiklikler de benzer şekilde Firestore tarafına yansır. Bunu denemenizi öneriririm. + ve - düğmeleri ile güncel fiyat bilgisini arttırma veya azaltma işlemlerini yapabiliriz. Sil düğmesi tahmin edileceği üzere satışa çıkarttığımız ürünü repository'den kaldırmak içindir. Güncelleme oparasyonunu sadece fiyat ayarlamaları için yaptık lakin ürün bilgilerinin düzenlenmesi ihtiyacı da var. Bunu uygulamaya nasıl ekleyebiliriz bir düşünün ;)</p>
<h2>Ben Neler Öğrendim</h2>
<p>Bu örnek çalışma ile Angular bilgilerimi biraz daha pekiştirmiş ama daha da önemlisi veri kaynağı olarak Google Cloud Platform'un bir ürününü kullanmış oldum. Genel hatlarıyla öğrendiklerimi şöyle özetleyebilirim.</p>
<ul>
<li>Bir component üzerindeki element değerlerinin formControlName niteliği yardımıyla servis tarafındaki FormControl nesnelerine bağlanabileceğini</li>
<li>Firebase üzerinde Cloud Firestore veri tabanının nasıl oluşturulabileceğini</li>
<li>Uygulamanın Firebase tarafı ile haberleşebilmesi için gerekli konfigurasyon ayarlarının nereye konulması gerektiğini ve nasıl çağırılabildiğini</li>
<li>Cloud Firestore ve önyüzün birbirlerinin değişikliklerini anında görebildiklerini</li>
<li>Bileşenlerdeki kontrollere olay metodlarının nasıl bağlanabileceğini</li>
<li>Firestore paketinin temel CRUD<em>(Create Read Update Delete)</em> komutlarını</li>
</ul>
<p>Böylece geldik bir maceranın daha sonuna. <a href="https://github.com/buraksenyurt/saturday-night-works/tree/master/No%2032%20-%20Angular%20with%20Firebase" target="_blank">32 numaralı Saturday-Night-Works çalışmasının kodlarına buradan ulaşabilirsiniz</a>. Yeni bir gözden geçirme yazısında buluşuncaya dek hepinize mutlu günler dilerim.</p>2019-05-24T09:00:00+00:00firebasefirestoregcpgoogle cloud platformangulartypescriptcloud firestorenosqlrealtime databasebsenyurtAngular tarafına yavaş yavaş alışmaya başladım. Yine de fazladan idman yapmaktan ve tekrar etmekten zarar gelmez. Bu sefer temel CRUD(Create Read Update Delete) operasyonlarını Firebase Firestore üzerinden icra ederken Angular'da koşmaya çalışacağım. Amaçlarımdan birisi de servis tarafında Form kontrolü kullanabilmek. Örneği her zaman olduğu gibi WestWorld(Ubuntu 18.04, 64bit) üzerinde yazacağım(ahch-to sistemine geçmeden önce oradaki son örneklerim diyebilirim)https://www.buraksenyurt.com/pingback.axdhttps://www.buraksenyurt.com/post.aspx?id=29306b57-8733-40c6-9fde-7e5dc7b18db22https://www.buraksenyurt.com/trackback.axd?id=29306b57-8733-40c6-9fde-7e5dc7b18db2https://www.buraksenyurt.com/post/cloud-firestore-ile-angular-kullanimi#commenthttps://www.buraksenyurt.com/syndication.axd?post=29306b57-8733-40c6-9fde-7e5dc7b18db2https://www.buraksenyurt.com/post/angular-ile-basit-bir-gorevler-listesi-uygulamasi-yazmakAngular ile Basit Bir Görevler Listesi Uygulaması Yazmak2019-05-08T06:00:00+00:00bsenyurt<p><img style="float: right;" src="https://www.buraksenyurt.com/image.axd?picture=/2019/04/09/albertsword.png" alt="" />Bazen ne kadar basit olursa olsun üşenmeden bir örneğin üstüne gitmek gerekiyor. Çünkü çok basit örneklerle çalışıyor olsak bile gözümüzden kaçan önemli detaylar olabilir. Günümüzde kullanmakta olduğumuz pek çok geliştirme çatısı, belli ürünlere yönelik hazır şablonları kolayca üretebileceğimiz komut setleri sunmakta.</p>
<p>Boilerplate olarak da ifade edebileceğimiz bu enstrümanlar sayesinde bir anda işler halde karşımıza çıkan uygulamalarla karşılaşıyoruz. Ancak ürüne hakim olabilmek, rahatça sağını solunu bükebilmek için hazır gelen şablonları bile kurcalamak gerekiyor. Benim <a href="https://github.com/buraksenyurt/saturday-night-works" target="_blank">Saturday-Night-Works birinci fazında</a> sıklıkla icra ettiğim bir eğitim süreci bu. Angular, Blazor, React ve benzeri konu başlıklarında hazır hello world şablonları ile sıklıkla karşılaştım. Onları eğip bükerek daha çok şey öğrenmeye çalıştım. Sonuçta tecrübe etmediğimiz sürece bilgi dağarcığımız genişleyemez, yanılıyor muyum? Öyleyse gelin 09 numaralı çalışmayı kayıt altına alalım.</p>
<p><a href="https://angular.io/" target="_blank">Angular</a> ürünü web, mobil ve masaüstü uygulamalar geliştirmek için kullanılan Javascript tabanlı açık kaynak bir web çatısı olarak karşımıza çıkıyor. Uzun zamandır hayatımızda olan ve endüstüriyel anlamda kendisini kanıtlamış bir ürün. Pek tabii sıklıkla Vue ve React ile karşılaştırıldığına da şahit oluyoruz. Ben Saturday-Night-Works çalışmaları kapsamında herbiriyle ilgili en temel seviyede örnekler geliştirmeye de çalıştım. Nitekim bırakın bunları birbirleriyle karşılaştırmayı, gözü kapalı Hello World uygulamaları nasıl yazılır bile bilmiyordum.</p>
<p>Hali hazırda çalıştığım şirketteki yeni nesil uygulamalarda ağırlıklı olarak Vue.js kullanılıyor olsa da yeni özellikler eklemek için var olan öğelere bakıyorduk. Dolayısıyla Angular tarafında sıcak kalmaya çalışmak adına basit bir örnekle başlamak yerinde bir karardı. Bende böyle yapmışım. Örnekte kendime bir görev listesi oluşturuyorum. Sadece yeni giriş ve silme fonksiyonu olsa da bir şeyler öğrendim diyebilirim<em>("ToDo List" en yaygın Hello World örnekleri arasında yer alıyor)</em> Uygulamayı her zaman ki gibi Visual Studio Code ile WestWorld<em>(Ubuntu 18.04, 64bit)</em> üzerinde icra etmekteyim.</p>
<h2>Ön Gereklilikler</h2>
<p>Tabii işin başında bize bir takım alet edevatlar gerekiyor. node ve npm sistemde olması gerekenler. WestWorld'de bu araçlar zaten var<em>(Yani sizin sisteminizde yoksa edinmelisiniz)</em> npm'i Angular için Command Line Interface<em>(CLI)</em> aracını yüklemek maksadıyla kullanıyoruz. Kurulum için gerekli terminal komutu şöyle, </p>
<pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false">sudo npm install -g @angular/cli</pre>
<p>Angular CLI ile projeyi oluşturmak oldukça basit. Önyüz tarafının görselliğini arttırmak adına Bootstrap kullanabiliriz. Tabii öncelikle ilgili bootstrap paketlerini sisteme dahil etmemiz gerekiyor. Bunu bower yöneticisinden yararlanarak aşağıdaki terminal komutu ile yapabiliriz.</p>
<pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false">bower i bootstrap</pre>
<blockquote>
<p>Angular projesini oluşturduktan sonra bootstrap'in CSS dosyalarını assets/css altına alıp orayı referans etmeyi tercih ettim<em>(index.html sayfasına bakın) </em>Lakin Bootstrap için CDN adreslerini de pekala kullanabiliriz.</p>
</blockquote>
<h2>Angular Uygulamasının Oluşturulması</h2>
<p>Angular uygulamasını hazır şablonundan üretmek oldukça kolay ve sıklıkla tercih edilen yollardan birisi. Tek yapmamız gereken terminalden ng new komunutunu çalıştırmak. new sonrası gelen parametre tahmin edileceği üzere uygulamamızın adı olacak.</p>
<pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false">ng new life-pbi-app</pre>
<p>ng new sonrası oluşan proje içerisinde çok fazla dosya bulunacaktır. Şu haliyle de uygulamayı çalıştırıp sonuçlarını görebiliriz ama başta da belirttiğim üzere biraz eğip bükmek lazım. Benim yaptığım değişiklikler son derece basit. Sonuçta tek bir arayüzüm olacak ki bu index.html. Önyüzde gösterilecek bileşenimiz ise yine şablon ile hazır olarak gelen app.component. Ona ait HTML içeriğini örnek için aşağıdaki gibi değiştirebiliriz.</p>
<p>app.component.html</p>
<pre class="brush:html;auto-links:false;toolbar:false" contenteditable="false"><div class="container">
<form>
<div class="form-group">
<h1 class="text-center text-success">Çalışma Planım...</h1>
<p>Burada 1 haftalık kişisel görev planlarıma yer vermekteyim. Mesela <i>"bu hafta 10 km yürüyüş yapacağım"</i></p>
<div class="card input-group-prebend">
<div class="card-body">
<input type="text" #job class="form-control" placeholder="Salı günü 100 faul atışı çalışacağım..." name="job"
ngModel>
<!-- addJob metodundaki job nesnesi üst kontroldeki #job niteliğidir.
value özelliğine giderek girilen bilgiyi addJob metoduna göndermiş oluyoruz. -->
<input type="button" class="btn btn-info" (click)="addJob(job.value)" value="Ekle" />
</div>
</div>
<!-- ngFor ile jobs dizisinde dolaşıyoruz ve her bir eleman için
card stilinde birer div oluşturulmasını sağlıyoruz
-->
<div *ngFor="let job of jobs" class="card">
<div class="card-body">
<div class="row">
<div class="col-sm-10">
{{job}} <!-- dizideki görevin bilgisini yazdırıyoruz-->
</div>
<div class="col-sm-2">
<!-- Silme işlemi için removeJob fonksiyonu çağrılıyor.
Parametre ise dizinin o anki elemanı-->
<input type="button" class="btn btn-primary" (click)="removeJob(job)" value="Çıkart" />
</div>
</div>
</div>
</div>
</div>
</form>
</div>
<!-- addJob, removeJob metodları ile jos dizisi app.component.ts dosyası içerisinde yer alıyor --></pre>
<p>component içerisinde basit bir form grubu var. İçinde iki adet bileşen gövdesi bulunuyor. Üst taraf yeni görev girmek için kullanılan kısım. Ekle başlıklı düğmeye basıldığındaysa Typescript tarafındaki addJob metodu çağırılıyor. Parametre olarak job isimli text kontrolünün içeriği gönderilmekte.</p>
<p>Alt tarafta yer alan gövde içindeyse bir for döngüsünden yararlanılarak tüm görev listenin basıldığı satırlar bulunuyor. Çıkart başlıklı düğmeye basıldığında devreye giren removeJob fonksiyonu parametre olarak döngünün o anki Job nesne örneğini almakta ki bunu silme işlemi için kullanıyoruz. Burada aslında güncelleme içinde bir şeyler yapmak gerekiyor. Ne var ki çalışma sırasında bunu atlamışım. Kuvvetle muhtemel üşendiğim içindir. Siz güncelleme için ayrı bir bileşene yönlendirmeyi deneyebilirsiniz<em>(ki ben ilerleyen safhalarda Firebase ile ilgili bir kullanımı da denemişim. Magic Johnson numaralı örnek. Onu da bir ara bloğa kayıt altına almalıyım)</em></p>
<p>app.component.ts <em>(typescript tabanlı bileşenimiz)</em> </p>
<pre class="brush:js;auto-links:false;toolbar:false" contenteditable="false">import { Component } from '@angular/core';
import { isJsObject } from '@angular/core/src/change_detection/change_detection_util';
@Component({
selector: 'app-root',
templateUrl: './app.component.html', //Bu Typescript dosyasının hangi html ile ilişkili olduğu belirtiliyor
styleUrls: ['./app.component.css']
})
export class AppComponent {
jobs = []; //görev listesinin tutulacağı dizi
// yeni bir job eklemek için
addJob(value) {
if (value !== "") {
this.jobs.push(value)
// console.log(this.jobs) // Tarayıcı console penceresine log düşürebiliriz
} else {
alert('Bir görev girmelisin... ;)')
}
}
// bir görevi listeden çıkartmak için
removeJob(job) {
for (let i = 0; i <= this.jobs.length; i++) {
if (job == this.jobs[i]) {
this.jobs.splice(i, 1)
}
}
}
}</pre>
<p>Bileşenin Typescript tabanlı arka tarafı görev ekleme ve silme operasyonlarını içermekte. app-root ile ilişkilendirilmiş durumda<em>(Bu, index.html sayfasındaki yerleşim için önemli bir bilgi)</em> Örneğin basit olması amacıyla görev listesi uygulama çalıştığı sürece bellekte duran bir diziyi kullanıyor. Elbette bunu farklı bir veri kaynağına bağlayabiliriz. Mesela Azure Cosmos DB veya SQLite gibi veri kaynaklarının kullanılması tercih edilebilir. Son olarak ilgili bileşenin gösterildiği index.html içeriği de aşağıdaki gibi değiştirilebilir. </p>
<pre class="brush:html;auto-links:false;toolbar:false" contenteditable="false"><!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Kişisel PBI Listem</title>
<base href="https://www.buraksenyurt.com/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
<link rel="stylesheet" href="assets/css/bootstrap.min.css" />
</head>
<body>
<app-root>Az sabır. Yükleniyor daaa!</app-root>
</body>
</html></pre>
<h2>Çalışma zamanı</h2>
<p>Uygulamayı çalıştırmak için aşağıdaki terminal komutunu vermek yeterli.</p>
<pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false">ng server</pre>
<p>Buna göre http://localhost:4200 adresine talep gönderirsek uygulamamıza ulaşabiliriz<em>(URL bilgisi javascript dosyalarından birisinde de parametrik olarak bulunuyor. Geliştirme ortamı için değiştirmek isteyebilirsiniz diye söylüyorum ;) )</em> Uygulamanın çalışma zamanına ait örnek bir görüntüde şöyle.</p>
<p><img src="https://www.buraksenyurt.com/image.axd?picture=/2019/04/09/credit_1.png" alt="" /></p>
<h2>Ben Neler Öğrendim?</h2>
<p><a href="https://github.com/buraksenyurt/saturday-night-works" target="_blank">Saturday-Night-Works</a> birinci fazındaki ilk acemilik uygulamalarımdan birisi olan <a href="https://github.com/buraksenyurt/saturday-night-works/tree/master/No%2009%20-%20ToDo%20App%20with%20Angular" target="_blank">09 numaralı örneğin</a> de bana kattığı bir takım şeyler oldu tabii. Bunları genişletirsek aşağıdaki gibi listeleyebilirim.</p>
<ul>
<li>Typescript ile HTML tarafındaki Angular yapılarının nasıl anlaştığını</li>
<li>Bootstrap'i bir Angular projesinde nasıl kullanabileceğimi</li>
<li>component üzerindeki button kontrollerinden Typescript olaylarının nasıl tetiklendiğini</li>
<li>Temel ng terminal komutlarını</li>
</ul>
<p>Böylece geldik bir maceramızın daha sonuna. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.</p>2019-05-08T06:00:00+00:00angulartypescriptjavascripthtmlbootstrap.net coreubuntuhello worldnpmbsenyurtBu çalışmadaki temel amacım Angular ile basit bir Hello World uygulaması oluşturmak. Güncel Angular bilgim oldukça düşük olduğu için bu tip bir çalışma içerisine girdim diyebilirim. Nitekim şirketteki projelerde Vue.js kullanılıyor ancak ben Angular tarafını da öğrenmek istiyorum. Çalışmayı uzun zamandır yaptığım gibi yine WestWorld (Ubuntu 18.04, 64bit) üzerinde icra etmekteyim.https://www.buraksenyurt.com/pingback.axdhttps://www.buraksenyurt.com/post.aspx?id=ff3d9f6d-cb7a-40a0-a445-efaa15a6ef7c2https://www.buraksenyurt.com/trackback.axd?id=ff3d9f6d-cb7a-40a0-a445-efaa15a6ef7chttps://www.buraksenyurt.com/post/angular-ile-basit-bir-gorevler-listesi-uygulamasi-yazmak#commenthttps://www.buraksenyurt.com/syndication.axd?post=ff3d9f6d-cb7a-40a0-a445-efaa15a6ef7chttps://www.buraksenyurt.com/post/Angular-ile-Basit-Bir-Tahmin-Oyunu-YazmakAngular ile Basit Bir Tahmin Oyunu Yazmak2019-05-01T17:00:00+00:00bsenyurt<p><img style="float: right;" src="https://www.buraksenyurt.com/image.axd?picture=/2019/04/30/com64k.jpg" alt="" />Commodore 64 sahibi olduğum günlerde beni çok etkileyen bir Futbol oyunu vardı. Üstelik yerli malıydı. Görsel bir arabirimi yoktu. Komut satırından size sorulan sorulara verdiğiniz cevaplara göre Türkiye birinci futbol liginde maçlar yapıyordunuz. Açılışta takımınızı ve rakibinizi seçtikten sonra yazı tura sorusu ile başlıyordu her şey. Kazandıysanız da "top mu, kale mi" sorusuyla devam ediyordu. Maçın süresi ilerledikçe komut satırından sorular gelmeye devam ediyordu. "Rakip ceza sahasının gerisinde şut çekti. Kaleciniz ne yapacak?" Ve seçenekler geliyordu. "Plonjon, out'a çelme vs" Yapılan seçime göre gol yiyebilir, topu çelebilir veya tutabilirdiniz. İsmini bir türlü hatırlayamadığım ama komut satırından olsa bile beni saatlerce monitör başına kitleyen bir oyundu. Zaten o devrin Commodore 64 oyunlarındaki yaratıcılık, programlama kabiliyetleri bir başkaydı. Bu düşünceler ışığında günlerden bir gün Angular tarafı ile ilgili <a href="https://github.com/buraksenyurt/saturday-night-works" target="_blank">saturday-night-works çalışmalarımı</a> yapmaktayken bende basit ama bana keyif verecek bir oyun yazayım istedim.</p>
<p>Esasında Angular tarafında çok deneyimli değildim. Eksiğim çoktu. Onu daha iyi tanımak için bol bol örnek yapmam gerekiyordu. Bilgilerimi pekiştirmek için farklı öğretileri uygulamaya devam ediyordum. Bu kez temelleri basit şekilde anlamak adına bir şehir tahmin oyunu yazmaya karar verdim. Uygulama havanın rastsal durumuna göre kullanıcısına bir soru soracak ve hangi şehirde olduğunun bulmasını isteyecek. Kabaca şu aşağıdaki cümleye benzer bir düşünce ile yola çıktığımı söyleyebilirim.</p>
<p>"Merhaba Burak. Bugün hava oldukça 'güneşli' ve ben kendimi bir yere ışınladım. Neresi olduğunu tahmin edebilir misin?"</p>
<p>'güneşli' yazan kısım rastgele gelecek bir kelime. Yağmurlu olabilir, sisli olabilir vb...Buna göre uygun şehirlerden rastgele birisine gidecek bilgisayar. Biz de bunu tahmin etmeye çalışacağız. Tabii tahmini kolaylaştırmak için minik bir ipucu vereceğiz. Baş harfini söyleyeceğiz<em>(ki siz bunu daha da zenginleştirebilirsiniz. Tahmin sayısını tutup belli bir oranda hak tanıyabilir, tahmin edemedikçce daha fazla harf çıkarttırabilirsiniz)</em></p>
<p>Öyleyse vakit kaybetmeden işe koyulalım değil mi? Ben örneği artık sonbaharını yaşamakta olan WestWorld <em>(Ubuntu 18.04, 64bit)</em> üzerinde geliştirdim.</p>
<h2>Ön Gereksinimler ve Kurulumlar</h2>
<p>Sisteminizde angular CLI yüklü olursa iyi olur. Komut satırından angular projesi başlatmak için işimizi oldukça kolaylaştıracaktır. Sonrasında boilerplate etkisi ile uygulamayı oluşturabiliriz. Arayüzün şık görünmesini sağlamak için <em>(ben ne kadar şıklaştırabilirsem artık :D )</em> bootstrap'i tercih edebiliriz. Aşağıdaki terminal komutları gerekli yükleme işlemlerini yapacaktır. İlk komutla angular CLI aracını yüklerken, ikinci komutla yeni bir angular projesi oluşturuyoruz. Son terminal komutuyla da bootstrap'i projemize dahil ediyoruz. Hepsi Node Package Manager yardımıyla gerçekleştirilmekte.</p>
<pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false">sudo npm install -g @angular/cli
ng new where-am-i --inlineTemplate
cd where-am-i
npm install bootstrap --save</pre>
<h2>Yapılan Değişiklikler</h2>
<p>Uygulama kodlarında değişiklik yaptığım çok az yer var. Malum boilerplate etkisi ile zaten hazır bir proje şablonu üretilmiş durumda. Biz temel olarak bir bileşen oluşturup bunu ana sayfada kullanıyoruz. </p>
<p>Bootstrap'i kullanabilmek için proje klasöründeki angular.json dosyasındaki styles elementine ilave bir bildirim yaptık. Buna ek olarak src/app klasöründeki app.component.html dosyasını aşağıdaki gibi değiştirdik <em>(Size yardımcı olacak bilgiler kodların yorum satırlarında yer alıyor. Direkt copy-paste yapmadan önce okuyun)</em></p>
<pre class="brush:html;auto-links:false;toolbar:false" contenteditable="false"><!--
bootstrap css stilleri ile donattığımız basit bir arayüzümüz var.
app.component sınıfındaki property'lere erişmek için {{propertyName}} notasyonu kullanılıyor.
Yine bileşen üzerinde bir metod çağrısı yapmak ve bunu bir kontrol olayı ile ilişkilendirmek için
(eventName)="method name" şeklinde bir notasyon kullanılıyor.
Angular direktiflerinde *ngIf komutunu kullanarak tahmine göre bir HTML elementinin gösterilmesi sağlanıyor.
-->
<div class="container">
<h2>Bil bakalım hangi şehre gittim? :)</h2>
<div class="card bg-light mb-3">
<div class="card-body">
<p class="card-text">Bugün hava <b>{{currentWeather}}</b> ve ben ... şehrine gittim.</p>
</div>
</div>
<div>
<p>
<button class="btn btn-primary btn-sm" (click)="fullThrottle()">Hey Scotty. Beni yeniden
ışınla</button>
</p>
</div>
<div>
<label>Tahminin nedir?</label>
<input (input)="playersGuess=$event.target.value" type="text" />
<button class="btn btn-primary btn-sm" (click)="checkMyGuess()">Dene</button>
</div>
<div>
<p *ngIf="guessIsCorrect" class="alert alert-success">Bravo! Yakaladın beni</p>
<p *ngIf="!guessIsCorrect" class="alert alert-warning">Tüh. Tekrar dener misin?</p>
<p class="text-info">İşte sana bir ipucu. {{hint}}</p>
</div>
</div></pre>
<p>Son olarak src/app klasöründeki app.component.ts typescript dosyasındaki bileşen sınıfının değiştirildiğini ifade edebilirim.</p>
<pre class="brush:js;auto-links:false;toolbar:false" contenteditable="false">import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'Şimdi Hangi Şehirdeyim?';
currentWeather: string; // Güncel hava durumu bilgisini tutan property
computersLocation: string; //Bilgisayarın yerini tutacak property
playersGuess: string; // Oyuncunun tahminini tutacak property
guessIsCorrect: boolean; // Tahminin doğru olup olmadığını tuttuğumuz property
hint:string; // Tahmini kolaylaştırmak için verdiğimiz ipucunu tutan property
// Örnek veri dizileri.
// TODO: Daha uygun bir key-value dizisi bulunabilir mi?
airConditions = ['güneşli', 'yağmurlu', 'karlı', 'sisli'];
cities = [
['Barcelona', 'Madrid', 'Lima', 'Rio', 'Miami', 'Sydney', 'Antalya'],
['Prag', 'Paris', 'Tokyo', 'Dublin', 'Londra', 'Pekin'],
['Moskova', 'Montreal', 'Boston', 'Ağrı'],
['London', 'Glasgow', 'Mexico City', 'Frankfurt', 'İstanbul']
];
/*
Uygulama button bağımsız ilk başlatıldığında da hava tahmini yapılsın ve şehir tutulsun.
*/
constructor() {
this.hint = "";
this.computersLocation="";
this.currentWeather="";
this.fullThrottle();
}
/*
Bilgisayar için rastgele hava durumu üreten fonksiyon
Random fonksiyonundan yararlanıp uygun aralıklarda rastgele sayı üretir
ve buna göre rastgele bir şehir tutar.
*/
fullThrottle() {
// hava durumlarını tutan dizinin boyutuna göre rastgele sayı ürettik
var rnd1 = Math.floor((Math.random() * this.airConditions.length));
// rastgele bir hava durumu bilgisi aldık
this.currentWeather = this.airConditions[rnd1];
// şehirlerin tutulduğu dizide, hava durumu bilgisine uyan (örnekte indeks sırası) dizinin uzunluğunu aldık
var arrayLength = this.cities[rnd1].length;
// uzunluğuna göre rastgele bir sayı ürettik
var rnd2 = Math.floor((Math.random() * arrayLength));
// üretilen rastgele sayıya göre diziden bir şehir adı aldık
this.computersLocation = this.cities[rnd1][rnd2];
this.hint="Baş harfi "+this.computersLocation[0];
console.log(this.computersLocation); // Şşşşttt. Kimseye söylemeyin. F12'ye basınca ışınlanan şehri görebilirsiniz.
}
/*
Oyuncunun tahminini kontrol eden fonksiyon
*/
checkMyGuess() {
if (this.playersGuess == this.computersLocation)
this.guessIsCorrect = true;
else
this.guessIsCorrect = false;
}
}</pre>
<h2>Çalışma Zamanı</h2>
<p>Uygulamayı çalıştırmak için terminalden aşağıdaki komutu vermek yeterlidir.</p>
<pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false">ng serve</pre>
<p>Çalışma zamanına ait örnek ekran görüntülerimiz ise aşağıdakine benzer olacaktır. Mesela bir tahmin yaptık ve sonucu bulamadıysak şuna benzer bir sonuçla karşılaşırız.</p>
<p><img src="https://www.buraksenyurt.com/image.axd?picture=/2019/04/30/credit_1.png" alt="" /></p>
<p>Ama sonucu bilirsek de şöyle bir ekranla karşılaşırız.</p>
<p><img src="https://www.buraksenyurt.com/image.axd?picture=/2019/04/30/credit_2.png" alt="" /></p>
<h2>Ben Neler Öğrendim</h2>
<p>Pek tabii bu antrenmanla da bir çok şey öğrendim. Aklımda kaldığı kadarıyla onları şöyle özetleyebilirim.</p>
<ul>
<li>Component bileşeni ile HTML arayüzünü, sınıf özellikleri üzerinden nasıl konuşturabileceğimi</li>
<li>Bootstrap temel elementlerini Angular bileşenlerinde nasıl kullanabileceğimi</li>
<li>ng serve komutu ile uygulamayı çalıştırdıktan sonra, bileşen ve arayüzde yapılan değişikliklerin, save sonrası uygulamayı tekrardan çalıştırmaya gerek kalmadan çalışma zamanına yansıtıldığını</li>
<li>Component arayüzünden, Typescript tarafındaki metodların bir olaya bağlı olarak nasıl tetiklenebileceklerini</li>
</ul>
<p>Böylece geldik bir maceramızın daha sonuna. <a href="https://github.com/buraksenyurt/saturday-night-works" target="_blank">Saturday-Night-Works'ün 30 numaralı projesi</a>ne ait blog notlarımı da tamamlamış oldum. Ben bu maceralar sırasında güzel şeyler araştırıyor ve öğreniyorum. Size de böyle bir macerayı tavsiye ederim. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.</p>2019-05-01T17:00:00+00:00angulartypescripthtmljavascriptc#componentvs codeubuntubootstrapangular clinpmnode package managerbsenyurtAngular tarafını yavaş yavaş tanımaya başlıyorum. Ancak bilgilerimi pekiştirmek için farklı öğretileri uygulamaya devam etmem gerekiyor. Bu kez temelleri basit şekilde anlamak adına bir şehir tahmin oyunu yazmaya karar verdim. Uygulama havanın rastsal durumuna göre kullanıcısına bir soru soracak ve hangi şehirde olduğunu bulmasını isteyecek. Kabaca şu aşağıdaki cümleye benzer bir düşünce ile yola çıktım.https://www.buraksenyurt.com/pingback.axdhttps://www.buraksenyurt.com/post.aspx?id=b5bfd36c-4de7-4670-9a22-4232e72c63310https://www.buraksenyurt.com/trackback.axd?id=b5bfd36c-4de7-4670-9a22-4232e72c6331https://www.buraksenyurt.com/post/Angular-ile-Basit-Bir-Tahmin-Oyunu-Yazmak#commenthttps://www.buraksenyurt.com/syndication.axd?post=b5bfd36c-4de7-4670-9a22-4232e72c6331