NodeJS, MongoDB ve Angular 8 ile Full Stack Proje Oluşturmak
Selamlar,
Bu makalede, NodeJs ile oluşturduğumuz servisler ile MongoDB’den çektiğimiz kayıtları, Angular 8 bir sayfada göstereceğiz.
Bu uygulama, Mac OsX (Catalina 10.15) ortamında kodlanacaktır. Öncelikle makinanızda Homebrew yok ise, bash’e aşağıdaki komutun yazılması ile kurulur.
1 |
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" |
- # Eğer va ise Homebrew paket yöneticisini aşağıdaki komut ile update edelim.
1 |
brew update |
- # Homebrew’e MongoDB yükleme komutunu verelim.
1 |
brew install mongodb |
- # root dizininde /data/db klasörü oluşturulur (Yönetici olarak) : Aşağıda görülen “username” kısmına, kendi kullanıcı adınızı giriniz(Örnekler: borakasmer). MongoDb’de ilgili db yolu, default olarak bu izin verilen ve tanımlanan path’dir.
- mongod : “data/db” eğer standart root altına tanımlanmış ve ilgili user için yetki verilmiş ise, monogoDb bu komut ile ayağa kaldırılır.
1 2 3 4 5 |
sudo mkdir data (root'da) cd data sudo mkdir db sudo chown "username" data sudo chown "username" data/db |
- NOT: [CATALINA] Mac’de, en son gelen CATALINA işletim sistemi kurulu ise, root’a herhangi bir folder yaratılamaz. Çünkü artık readonly’dir :) Bu projede, “data/db” klasörü, “/Users/borakasmer” altına taşınmış ve aşağıdaki komut ile “mongod” komutu çağrılmıştır. “–dpath” ile, mongoDB’nin çalışacağı root dışındaki database’in yolu tanımlanmıştır.
- sudo mongod –dbpath /Users/borakasmer/data/db
1.Yol :
Eğer her seferinde yukarıdaki komut yerine “mongod” komutu yazılarak işlem yapılması istenir ise:
- “nano ~/.bash_profile” : Komutu ile profile dosyası açılır.
- “alias mongod=”echo <YourPassword> | sudo -S mongod –dbpath <YourPath>” : İçerisine aşağıdaki komut eklenir. <YourPassword> yerine makina açılış şifreniz ve <YourPath> yerine “data/db” folder’ının taşındığı path yazılır. Örneğin bu projede ‘/Users/borokasmer/data/db’ yazılmıştır.
- Ctrl + O
- Enter
2.Yol (Tercihen): İlk yol, yukarıda görüldüğü gibi Password’ün “.bash_profile”‘a yazılmasından güvenlik açığına neden olmaktadır. Bu yol için, Furkan Kalkan‘a ayrıca teşekkür ederim.
1-) .bash_profile: Yukarıda eklenen satır, aşağıdaki gibi değiştirilir: Görüldüğü üzere, “sudo” ile çalıştırılan “mongod” komutu için gereken “Password” kaldırılmıştır.
1 |
alias mongod="sudo mongod --dbpath /Users/borakasmer/data/db" |
2-) Geldik “mongod” komutu yazıldığında şifrenin istenmemesinin sağlanmasına. Öncelikle “mongod” komutunun gerçek path’i, aşağıdaki komut ile bulunur: İçinde “bin/mongod” geçen path alınır.
1 |
find / -name 'mongod' |
3-) Şimdi sıra geldi “mongod” komutu için, “borakasmer” user’ına “NOPASSWD” yetkisinin verilmesine. Kendi username’inizi bulmak için, aşağıda görüldüğü gibi “whoami” komutunu kullanabilirsiniz.
Password isteğinin sudo mongod komutu için kaldırılması amacı ile, “sudoers” dosyası aşağıdaki komut ile “visudo” kullanılarak değiştirilir.
1 |
sudo visudo -f /etc/sudoers |
- Aşağıda görüldüğü gibi “# root and users in group wheel can run anything on any machine as any user”‘ açıklamasının olduğu yere, klavyenin aşağı ok tuşuna basılarak gelinir.
- Not: Ekranda en aşağıda gözüktüğüne bakmayın.
- Cursur’u, yazılacak boş alana getirip “A” tuşuna basılır.
- Sonra “ESC” tuşuna basılarak, visudo’nun “INSERT” komutunun girilmesine hazır hale getirilir.
- “borakasmer ALL=(root) NOPASSWD: /usr/local/Cellar/mongodb-community/4.2.0/bin/mongod” komutu yazılarak, borakasmer’in sudo ile çalışan mongod komutu için, şifre istememesi sağlanır.
- Satırın sonunda “:” tuşuna basılır.
- Sizden bir komut isteyecektir. Çıkış için “wa” komutu yazılır.
- “Enter” tuşuna basılarak, sudoers dosyası kaydedilip çıkılır.
Böylece “mongod” komutu yazıldığında, aslında ==>”sudo mongod –dbpath /Users/borakasmer/data/db” komutu şifre girilmeden çalıştırılmış olunur.
Makinanızda NodeJs yok ise, buradaki link’den indirebilirsiniz.
Bu projede IDE olara Visual Studio Code(1.29.1) kullanılmıştır.
MongoDB’yi ayağa kaldırma: Terminalde, aşağıdaki komut yazılarak mongoDB ayağa kaldırılır. Default port’u: “localhost:27017”‘dir.
1 |
mongoDB |
Aşağıdaki komut yazılarak mongo Shell’e geçilir.
1 |
mongo |
Amaç: MongoDb’de, örnek amaçlı document yaratmak.
users.js: Elimizde, örneğin aşağıda görülen kayıt gibi onlarca datanın bulunduğu bir users.js dosyası olsun. İlgili dosya, bu link‘den indirilebilir.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
{ "gender": "female", "name": { "title": "miss", "first": "mary", "last": "jones" }, "email": "mary.jones56@example.com", "username": "crazypeacock512" }, { "gender": "male", "name": { "title": "mr", "first": "alan", "last": "walters" }, "email": "alan.walters29@example.com", "username": "crazytiger134" }, |
- Adım : People DB oluşturmak: Mongo Shell ortamında, aşağıdaki komut ile ilgili user.js’deki tüm kayıtlar, MongoDB altında People adında bir database ve onun altında Users collection’ının içine doldurulur. Her şeyi tek komut ile yapmak, paha biçilemez :)
1 |
mongoimport --db people --collection users --jsonArray users.js |
Bakalım mongoDB çalışıyor mu ?
- “use people” : People database’i kullanılacak anlamına gelmektedir.
- “db.users.findOne()”: users tablosundaki(dökümanındaki), ilk kayıt getirilir.
- “db.drobDatabase()” : Oluşturulan People DB’si, silinir. Siz silmeseniz de olur. Spor amaçlı, silinip tekrar oluşturulmuştur.
- “mongoimport –db people –collection users –jsonArray users.js” : Komutu ile mongoDB’ye People DB’si tekrardan oluşturulmuştur.
- “db.users.find()” : Tüm user datası getirilmiştir.
Sık sorulan sorulardan biri de MongoDB için Mac’de veya PC’de hangi Visual Manager Tool’un kullanıldığıdır. Ben Mac’de ve Pc’de de aynı uygulamayı kullanıyorum. Bu örnekte Robomongo kullanılmıştır. Yeni adı Robo 3T olmuştur. İndirme link’i ve ekran görüntüsü aşağıdaki gibidir. Tamamen ücretsizdir :)
https://robomongo.org/download
Öncelikle, eğer makinanızda “Express” global olarak kurulu değil ise, aşağıdaki komut ile sadece proje içerisine eklenir. Neden bu uygulamada “Express” tercih ettim de “NestJs” tercih etmedim. Çünkü bana çok daha basit ve yalın geldi. Ama çok daha kapsamlı projelerde “NestJs” tercih edilebilir.
1 |
npm install express --save |
Not: Express kütüphanesini global olarak makinanıza kurmak isterseniz, aşağıdaki komut satırını yazmanız gerekmektedir.
1 |
npm install -g express |
service.js(1): NodeJs’i test amaçlı yazılan function, aşağıdaki gibidir:
- “var express = require(“express”)” : Express kütüphanesi tanımlanır.
- “app.get(‘/’, function (req, res) {” : Ana root’da direk ==>”http://localhost:9480/” çağrılır ise bu function çalışır.
- “res.send(“Selam Millet @coderBora”)” : Geriye result olarak, yukarıdaki mesajı dönecektir.
- “app.listen(9480)” : NodeJs’de default port atamak, bu kadar basittir :)
1 2 3 4 5 6 7 8 |
var express = require("express"); var app = express(); app.get('/', function (req, res) { res.send("Selam Millet @coderBora"); }) app.listen(9480); |
Yukarıdaki kod, aşağıda görüldüğü gibi Debug segmesinden Environment, Node.js seçilerek çalıştırılabilir.
Şimdi gelin NodeJs’i mongoDB’ye bağlayan ve tüm kaydı bir list olarak dönen kodu yazalım.
NodeJs’de mongoDB’ye bağlanmak için, bu örnekte mongoose kullanılmıştır. Aşağıdaki komut ile ilgili kütüphane indirilir.
1 |
npm i mongoose |
Users.js‘den gelen örnek data, aşağıdaki gibidir:
1 2 3 4 5 6 7 8 9 10 |
{ "gender": "female", "name": { "title": "miss", "first": "mary", "last": "jones" }, "email": "mary.jones56@example.com", "username": "crazypeacock512" } |
service.js(2): Aşağıda görüldüğü gibi MongoDB üzerinde, people DB’sine bağlanılmıştır.
- “var mongoose = require(“mongoose”)” : mongoose kütüphanesi projeye eklenir.
- “mongoose.connect(‘mongodb://localhost/people’)” : MongoDB‘de ==> People database’ine bağlanılır.
- “var humanSchema = new mongoose.Schema({” : Bu bizim view modelimizdir.
- * “var Human = mongoose.model(‘Human’, humanSchema,’users’)” : Burada Human nesnesine, tanımlı ‘humanSchema” view modeli ile mongoDB’deki ‘users’ collection’ı eşleştirilerek atanmıştır.
- * “app.get(“/people”, function (req, res) {” : NodeJs’de routing bu kadar basittir :) “/people” keyword’ü ile, ana root’un sonuna people yazılarak, ilgili function’a erişilir.
- “Human.find(function (err,doc){” Tüm kayıdın çekildiği komut, mongoose kütüphanesinde find()‘dır.
- “res.send(doc)” : Gelen tüm kayıt, geri dönülür.
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 |
var express = require("express"); var mongoose = require("mongoose"); var app = express(); mongoose.connect('mongodb://localhost/people') var humanSchema = new mongoose.Schema({ gender:String, email:String, name :{ title: String, first: String, last: String } }); var Human = mongoose.model('Human', humanSchema,'users'); app.get("/people", function (req, res) { Human.find(function (err,doc){ res.send(doc); }) }) app.listen(9480); |
Sonuç : Yukarıdaki servisin çıktısının bir kısmı, aşağıdaki gibidir.
Gelin şimdi özel bir kolon ekleyelim. Kısacası mongodb’den dönmeyen bir kolonu, araya girip biz ekleyelim. Name alanındaki, “first” ve “last” alanlarını birleştirip “fullName” kolonuna ekleyelim.
- “humanSchema” : İlgili modele “fullName” property’si eklenmiştir.
- “doc.forEach(function (item)” : Servisden tüm kayıtlar çekildikten sonra, içinde gezilir.
- “item.fullName=item._doc.name.first+ ‘ ‘ + item._doc.name.last : fullName alanına, name objesinin first ve last propertyleri birleştirilerek atanır.
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 |
var express = require("express"); var mongoose = require("mongoose"); var app = express(); app.use(cors()); mongoose.connect('mongodb://localhost/people', { useNewUrlParser: true, useUnifiedTopology: true }) var humanSchema = new mongoose.Schema({ gender:String, email:String, name :{ title: String, first: String, last: String }, fullName: String }); var Human = mongoose.model('Human', humanSchema,'users'); app.get("/people", function (req, res) { //res.send("Günaydın Millet"); Human.find(function (err,doc){ doc.forEach(function (item) { item.fullName=item._doc.name.first+ ' ' + item._doc.name.last; }); res.send(doc); }) }) app.listen(9480); |
Sonuç : Aşağıda görüldüğü gibi, DB’de olmayan fullName alanı, first ve last alanları birleştirilerek doldurulmuştur.
- “var page” : Bulunulan sayfa parametre olarak gönderilir.
- “rowCount” : Bir sayfadaki toplam satır sayısı.
- “skip() – var skip = page * rowCount” : Seçilen sayfaya göre toplam kayıt sayısı bulunur ve o eleman sayısı kadar atlanıp, sayfada gösterilir.
- “.limit(rowCount)” : Çekilecek kayıt sayısı belirlenir.
- “.exec()” İçine konan function çalıştırılır. Aşağıdaki örnekte isim(first) ve soy(last) ismi birleştirip fullName alanına atanmıştır.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
app.get("/getpeoplebyPaging/:page/:count", function (req, res) { var page = parseInt(req.params.page) - 1; var rowCount = parseInt(req.params.count); var skip = page * rowCount; Human.find() .skip(skip) .limit(rowCount) .exec( function (err, doc) { if (doc != null) { doc.forEach(function (item) { item.fullName = item._doc.name.first + ' ' + item._doc.name.last; }); } res.send(doc); } ) }) |
Şimdi gelin isterseniz username ve last(soyadı) bilgisine göre filitre yapan Nodejs functionlarını yazalım. 2 farklı parametre alma şekli, bu functionlarda gösterilmiştir.
getpeopleByUsername: Aranan kişinin, kullanıcı adına göre filitrelendiği methoddur. Burada ilgili parametre “/:name” şeklinde karşılanmaktadır.
- “var query = { username: req.params.name }” : Username’e göre filitrelemenin yapıldığı satırdır. Gelen parametre req.params.name şeklinde alınmaktadır.
getpeopleByLastname : Aranan kişinin, soyadına göre filitrelendiği methoddur. Burada ilgili parametre url parameter’dır. Url tarafından “?lastname=ryan” şeklinde çağrılmaktadır.
- “var query = { “name.last”: req.query.lastname };” : Soyad’a göre filitrelemenin yapıldığı satırdır. Gelen parametre, req.query.lastname şeklinde alınmaktadır.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
app.get("/getpeopleByUsername/:name", function (req, res) { var query = { username: req.params.name }; Human.find(query,function (err, doc) { doc.forEach(function (item) { item.fullName = item._doc.name.first + ' ' + item._doc.name.last; }); res.send(doc); }) }) app.get("/getpeopleByLastname", function (req, res) { var query = { "name.last": req.query.lastname }; Human.find(query, function (err, doc) { doc.forEach(function (item) { item.fullName = item._doc.name.first + ' ' + item._doc.name.last; }); res.send(doc); }) }) |
last(Soyada)’a Göre Örnek Sorgu :
1 |
http://localhost:9480/getpeopleByLastname?lastname=ryan |
Username’e Göre Örnek Sorgu :
1 |
http://localhost:9480/getpeopleByUsername/goldenkoala804 |
Şimdi sıra geldi Document’a Insert yapan functiona:
body-parser : Öncelikle request.body’ye gelen sınıfın (human) parse edilip alınabilmesi için, ilgili kütüphanenin aşağıdaki gibi uygulamaya eklenmesi gerekmektedir.
1 |
npm install body-parser |
body-parse, projeye aşağıdaki gibi implemente edilir.
1 2 3 4 5 6 7 |
var bodyParser = require('body-parser'); app.use(bodyParser.urlencoded({ extended: false })); app.use(bodyParser.json()); |
Insert MongoDB insertPeople(): Aşağıda görüldüğü gibi, asenkron Insert bir function yazılmıştır.
- Kaydetma amaçlı gönderilen Human model ==> body-parse sayesinde, req.body şeklinde alınır.
- “var person = new Human(req.body)” : Kaydedilecek yeni person document oluşturulur.
- “var result = await person.save()” : Gönderilen data mongoDB’ye kaydedilir.
Not : “res.send(result)” şeklinde başarılı olduğuna dair geri dönüş, ilerde bize sorun çıkarıcaktır. İlgili geri dönüş angular 8 tarafında hata olarak alhılanacaktır. Bunun önüne geçmek için “return res.status(200).json({ status: “succesfully delete” })” şeklinde bir geri dönüş yapılabilir.
1 2 3 4 5 6 7 8 9 10 11 |
app.post('/insertPeople', async (req, res) =>{ //console.log("req.body : " + req.body.username); //console.log("req.body.last : " + req.body.name.last); try { var person = new Human(req.body); var result = await person.save(); res.send(result); } catch (error) { res.status(500).send(error); } }) |
Postman üzerinden NodeJs insertPeople() function’ına, aşağıdaki gibi yeni bir document post edilir.
Update MongoDB : updatePeople(): Aşağıda görüldüğü gibi asenkron Update bir function yazılmıştır.
- “var updatePerson = new Human(req.body)” : Servise gelen güncel data, updatePerson modele alınır.
- “const person = await Human.findOne({ username: updatePerson.username })” : Güncellenecek olan document, unique “username” ile bulunur.
- await person.updateOne(updatePerson) : Asenkron olarak güncelleme işlemi yapılır.
1 2 3 4 5 6 7 8 9 10 11 |
app.post('/updatePeople', async (req, res) => { //console.log("req.username : " + req.body.username); try { var updatePerson = new Human(req.body); const person = await Human.findOne({ username: updatePerson.username }); await person.updateOne(updatePerson); return res.send("succesfully saved"); } catch (error) { res.status(500).send(error); } }) |
Postman üzerinden NodeJs updatePeople() function’ına, aşağıdaki gibi güncellenecek document post edilir.
Delete MongoDB : deletePeople(): Aşağıda görüldüğü gibi, Delete bir function yazılmıştır.
- “Human.findOneAndRemove({ username: deletePerson.username })” : Silinecek document, username’e göre bulunup silinmiştir.
- “.then(function (doc) {” : Silinecek document’in bulunup bulunmadığı, doc parametresinden anlaşılmıştır.
- “if(doc) return res.status(200).json({ status: “succesfully delete” })” : İlgili document’in bulunduğu ve doğru şekilde silindiği anlamına gelir.
- “else return res.send(“no document found!”)” : Böyle bir document’ın bulunamaması durumunda, ilgili mesaj dönülür.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
app.post('/deletePeople', (req, res) => { console.log("req.username : " + req.body.username); try { var deletePerson = new Human(req.body); Human.findOneAndRemove({ username: deletePerson.username }) .then(function (doc) { if(doc) //return res.send("succesfully delete"); return res.status(200).json({ status: "succesfully delete" }); else return res.send("no document found!"); }).catch(function (error) { throw error; }); } catch (error) { res.status(500).send(error); } }) |
Postman üzerinden, NodeJs deletePeople() function’ına, aşağıdaki gibi silinecek kayıt post edilir.
Postman üzerinden NodeJs deletePeople() function’ına, aşağıdaki gibi olmayan (borsoft12) username’li bir kayıt post edilerek silinmeye çalışılmış ve “no documnet found!” hatası alınmıştır.
1-Arama : getpeopleStartsWith() Şimdi sıra geldi aranan kelime ile başlayan isimlere ait kayıtların getirilmesine. Aşağıda görüldüğü gibi regex kullanılmıştır. Aranan kelimenin başına “^” konması ile aranan kelime ile başlıyan tüm kayıtlar getirilmiştir. Örnek ==> ismi name.first “^ar” ile başlıyan kayıtlar getirilir.
1 2 3 4 5 6 7 8 9 10 |
app.get("/getpeopleStartsWith/:name", function (req, res) { //res.send("Günaydın Millet"); var query = { "name.first": { $regex: '^'+req.params.name } }; Human.find(query, function (err, doc) { doc.forEach(function (item) { item.fullName = item._doc.name.first + ' ' + item._doc.name.last; }); res.send(doc); }) }) |
1 |
http://localhost:9480/getpeopleStartsWith/ar |
2-Arama, Sıralama, Limit, Atalama : getpeopleContainsOrderTopWith() Bu functionda da “name.first”, yani isminin içinde aranan kelime geçen (name), belli bir sayıdaki (top) tüm kayıtlar, ilk kayıt atlanmak şartı (skip(1)) ile isme göre tersten sıralanarak (sort) getirilmiştir. Aşağıda görüldüğü gibi regex kullanılmıştır. Örnek ==> ismi name.first “ar” içinde geçen, ilk kayıt hariç, toplam 2 kayıt getirilmiştir.
- “var query = { “name.first”: { $regex: req.params.name } }” : İçinde name parametresi geçen kayıtlar, filitrelenmiştir.
- “var top = parseInt(req.params.top)” : Çekilecek olan kayıt sayısı, top parametresinden sayısal (int) değerine çevrilerek alınmıştır.
- “.sort(‘-name.first’) : İsme göre tersten sıralanmıştır.
- “skip(1)” : ilk kayıt atlanmıştır.
- “limit(top)” : Toplam çekilecek kayıt sayısı belirlenmiştir.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
app.get("/getpeopleContainsOrderTopWith/:name/:top", function (req, res) { //res.send("Günaydın Millet"); var query = { "name.first": { $regex: req.params.name } }; var top = parseInt(req.params.top); Human.find(query) .sort('-name.first') .skip(1) .limit(top) .exec( function (err, doc) { doc.forEach(function (item) { item.fullName = item._doc.name.first + ' ' + item._doc.name.last; }); res.send(doc); } ) }) |
1 |
http://localhost:9480/getpeopleContainsOrderTopWith/ar/2 |
Angular 8 Ara yüz : Sıra geldi tüm bu servislerin kullanılacağı ara yüze. Aşağıdaki komut ile yeni bir Angular 8 projesi (HumanResource) oluşturulur.
1 |
ng new HumanResource |
İlk sayfa, tüm kayıtlı çalışanların listelendiği sayfadır. Bu projede, bootstrap ile çalışılacağı için “<head></head>” tagları arasına aşağıdaki css ve script kodları yazılmıştır.
Index.html:
1 2 |
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous"> <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script> |
Model/(Person): Yeni bir model sınıfı aşağıdaki gib oluşturulup, model klasörü altına konur.
1 2 3 4 5 6 7 8 9 10 11 |
export class Person { gender: String; email: String; username: String; name: { title: String, first: String, last: String }; fullName: String; } |
Service / personService: Aşağıda görüldüğü gibi NodeJs services’den people() servisi buradan çağrılır.
- baseUrl: string = “http://localhost:9480/people” : NodeJs’den ilk çağrılacak, tüm personelin çekildiği servis yoludur.
- headers: new HttpHeaders({ ‘Content-Type’: ‘application/json’ }) : Servise ile Json formatında içerik alınacağı tanımlanır.
- public getPeopleList(): Observable<Person> : NodeJs people() servisinden, Observable şeklinde data çeken methoddur.
- .pipe() : Methodlar birbiri ardına sıraya konur.
- retry(1) : Hata olması durumunda, işleme 1 kere daha tekrar edilirin havalı yolu:)
- errorHandel(): Hatanın, server-side’dan mı yoksa client-side’dan mı olduğu belirlenip, consola yazdırılır.
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 |
import { Injectable } from '@angular/core'; import { HttpClient, HttpHeaders } from '@angular/common/http'; import { Person } from 'src/Model/person'; import { Observable, throwError } from 'rxjs'; import { retry, catchError } from 'rxjs/operators'; @Injectable({ providedIn: 'root' }) export class PersonService { constructor(private httpClient: HttpClient) { } httpOptions = { headers: new HttpHeaders({ 'Content-Type': 'application/json' }) } baseUrl: string = "http://localhost:9480/people"; public getPeopleList(): Observable<Person> { return this.httpClient.get<Person>(this.baseUrl) .pipe( retry(1), catchError(this.errorHandel) ) } errorHandel(error) { let errorMessage = ''; if (error.error instanceof ErrorEvent) { // Get client-side error errorMessage = error.error.message; } else { // Get server-side error errorMessage = `Error Code: ${error.status}\nMessage: ${error.message}`; } console.log(errorMessage); return throwError(errorMessage); } } |
İlgili personService, aşağıda görüldüğü gibi app.module.ts’e eklenir.
app/app.module.ts: [FormsModule, HttpClientModule] imports‘a eklenir. PersonService de providers’a eklenir.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { AppComponent } from './app.component'; import { PersonService } from 'src/Service/personService'; import { HttpClientModule } from '@angular/common/http'; import { FormsModule } from '@angular/forms'; @NgModule({ declarations: [ AppComponent, ], imports: [ BrowserModule, FormsModule, HttpClientModule ], providers: [PersonService], bootstrap: [AppComponent] }) export class AppModule { } |
app.component.ts: Aşağıda görüldüğü gibi [Dependency Injection] ile PersonService constructor’da alınmıştır.
- “this.service.getPeopleList().subscribe((data: any:[]) => {” : İlgili data, servisden çekildiği zaman, subscribe nesnesi şeklinde geri dönülmektedir.
- “this.peopleList = data.slice(0,5);” :Tüm çekilen datanın, [] array peopleList’e, ilk 5 kaydı aktarılır. Yani proje ilk ayağa kalktığı zaman ilk 5 kayıt ekrana basılır.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
import { Component, OnInit } from '@angular/core'; import { PersonService } from 'src/Service/personService'; import { Observable } from 'rxjs'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.scss'] }) export class AppComponent implements OnInit { title = 'HumanResource'; peopleList: any = []; constructor(public service: PersonService) { } ngOnInit(): void { this.getPeople(); } public getPeople() { return this.service.getPeopleList().subscribe((data: any =[]) => { this.peopleList = data.slice(0,5); console.log(this.peopleList); }); } } |
İlk örnek için app.component.html, aşağıdaki gibidir.
app.component.html :
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 |
<!--The content below is only a placeholder and can be replaced.--> <div style="text-align:center"> <h1> Welcome to {{ title }}! </h1> <img width="200" src="assets/img/logo.png"> </div> <div class="table-responsive"> <table class="table table-striped table-dark"> <thead> <tr> <th scope="col">#</th> <th scope="col">Name</th> <th scope="col">Email</th> <th scope="col">Gender</th> </tr> </thead> <tbody> <tr *ngFor="let people of peopleList"> <th scope="row"> <button type="button" class="btn btn-success btn-sm move-right">Edit</button> </th> <td>{{people.name.first}}</td> <td>{{people.email}}</td> <td>{{people.gender}}</td> </tr> </tbody> </table> </div> |
Uygulama çalıştırıldığında ilk servis isteği, NodeJS üzerinde “http://localhost:9480/people” adresine yapılır. Ve “No ‘Access-Control-Allow-Origin‘” no-cors hatası alınır. Bunun nedeni request çekilen url ile nodeJs’in portlardan dolayı aynı domain üzerinde görünmemesidir. Çözüm cors kütüphanesinin aşağıdaki gibi kurulmasıdır.
1 |
npm i cors |
service.js : Cors hatasına düşmemek için, alttaki satırlar eklenir.
1 2 3 4 |
var cors = require("cors"); var app = express(); app.use(cors()); |
Paging : Sıra geldi, sayfa üzerinde paging yapmaya.
personService.ts(2)‘e aşağıdaki paging methodu eklenir. Sayfalama yapılacak NodeJs methodu çağrılır. Her sayfa için static 5 kayıt çekilmektedir.
Not: Bu “5” değerinin, bir config dosyadan okunması çok daha sağlıklı olacaktır.
1 2 3 4 5 6 7 8 |
pagingUrl: string = "http://localhost:9480/getpeoplebyPaging"; public getPeopleListByPaging(pageNo: number): Observable<Person> { return this.httpClient.get<Person>(this.pagingUrl + "/" + pageNo + "/5") .pipe( retry(1), catchError(this.errorHandel) ) } |
app.component: Sayfa üzerine önceki ve sonraki buttonları konacaktır. İlgili buttonlar tıklanınca, alttaki methodlar çağrılacaktır. Sayfa sayısı Next() butonunda 1 artarken, Preview() butonunda 1 azalır, ancak 0’dan düşük olamaz. Ayrıca currentPagecount=1 ise yani ilk sayfada ise, isPreviewActive=false değeri atanır. Böylece “Önceki” buttonu, pasif hale getirilir. Next() methodunda eğer hiçbir kayıt gelmez ise, “currentPagecount” değeri “-1” yapılarak eski haline geri getirilir. Kısaca sayfa sayısı, kayıt yok ise arttırılmaz. Ayrıca “isNextActive” değişkenine false değeri atanarak, “Sonraki” buttonun pasif hale gelmesi sağlanır.
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 |
isNextActive:boolean=true; isPreviewActive: boolean = true; public Next() { this.currentPagecount = this.currentPagecount + 1 >= 0 ? this.currentPagecount + 1 : this.currentPagecount return this.service.getPeopleListByPaging(this.currentPagecount).subscribe((data: any = []) => { if (data.length == 0) { this.currentPagecount = this.currentPagecount - 1; this.isNextActive=false; } else { this.isNextActive = true; this.peopleList = data; this.isPreviewActive = true; } console.log(this.peopleList); }); } public Preview() { this.currentPagecount = this.currentPagecount - 1 > 0 ? this.currentPagecount - 1 : this.currentPagecount return this.service.getPeopleListByPaging(this.currentPagecount).subscribe((data: any = []) => { if (data.length > 0) { this.isNextActive = true; this.isPreviewActive = true; this.peopleList = data; } this.isPreviewActive=this.currentPagecount == 1 ? false : true; console.log(this.peopleList); }); } |
app.component.html: Header kısmı, aşağıdaki gibi değiştirilir.
- [disabled]=”!isPreviewActive” : İlk sayfada, false değeri atanarak geri buttonunun tıklanamaması sağlanır.
- disabled]=”!isNextActive” : Son sayfada ise, false değeri atanarak ileri buttonunun tıklanamaması sağlanır.
1 2 3 4 5 6 7 8 |
<thead> <tr> <th scope="col"><input [disabled]="!isPreviewActive" type="button" value="<== Önceki" (click)="Preview()" class="btn btn-primary"> #</th> <th scope="col">Name</th> <th scope="col">Email</th> <th scope="col">Gender <input [disabled]="!isNextActive" type="button" value="Sonraki==>" class="btn btn-primary" (click)="Next()"></th> </tr> </thead> |
Edit : Sıra geldi seçilen kaydın düzenlenmesine.
- “pipe()”: Methodları bir sıraya konur.
- retry(1)”: Hata olması durumunda, son bir tekrar daha yapılır.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
updateUrl: string = "http://localhost:9480/updatePeople"; httpOptions = { headers: new HttpHeaders({ 'Content-Type': 'application/json' }) } public updatePerson(data: Person): Observable<any> { return this.httpClient.post<Person>(this.updateUrl, JSON.stringify(data), this.httpOptions) .pipe( retry(1), catchError(this.errorHandel) ) } |
app.component.html (Güncelleme):
- “<div *ngIf=”!isEdit”>” : Aşağıda görüldüğü gibi resim ve başlık yeni bir div’in içine alınıp, “isEdit” değişkenine bağlanmıştır. Güncellenme anında, bu div gizlenecektir.
- “<table class=”table table-dark” *ngIf=”isEdit”>” : Yeni bir table eklenerek, güncellenecek personModel için ilgili bazı alanlar bu tablonun içine konmuştur. Sadece çalışanın, unvan’ı, adı, soyadı ve email’inin değiştirilmesi amaçlanmıştır.
- “<button type=”button” class=”btn btn-success btn-sm move-right” (click)=”Edit(people)”>Edit</button>” : Soldaki edit buttonuna basılınca, “Edit(people)” methodu çağrılmaktadır. Edit() methodu makalenin devamında anlatılacaktır.
- “<input type=”text” [(ngModel)]=”personModel.name.title”>” : Tüm input alanlar, makalenin devamında anlatılacak olan personModel‘in propertylerine two way binding “[(ngModel)]” şeklinde bağlanmıştır.
- Bu demek oluyor ki, input alanlarda yapılan bir değişiklik, listelenen grid içerisindeki alanlarda da anlık olarak gözlemlenebilecektir.
- “<input type=”button” value=”Kaydet” (click)=”Save()” class=”btn btn-success”>” : Tıklanma durumunda, Save() methodu çağrılmıştır.
Not: Makalenin çok daha uzamaması için, alanlarla ilgili input validationlar yapılmamıştır.
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 60 61 62 63 64 65 66 67 |
<!--The content below is only a placeholder and can be replaced.--> <div style="text-align:center"> <div *ngIf="!isEdit"> <h1> Welcome to {{ title }}! </h1> <img width="200" src="assets/img/logo.png"> </div> <table class="table table-dark" *ngIf="isEdit"> <tr> <td> <input type="button" value="Kaydet" (click)="Save()" class="btn btn-success"> </td> </tr> <tr> <td> Title : </td> <td> <input type="text" [(ngModel)]="personModel.name.title"> </td> <td> Name : </td> <td> <input type="text" [(ngModel)]="personModel.name.first"> </td> <tr> <td> Surname : </td> <td> <input type="text" [(ngModel)]="personModel.name.last"> </td> <td> Email : </td> <td> <input type="text" [(ngModel)]="personModel.email"> </td> </tr> </table> </div> <div class="table-responsive"> <table class="table table-striped table-dark"> <thead> <tr> <th scope="col"><input [disabled]="!isPreviewActive" type="button" value="<== Önceki" (click)="Preview()" class="btn btn-primary"> #</th> <th scope="col">Name</th> <th scope="col">Email</th> <th scope="col">Gender <input [disabled]="!isNextActive" type="button" value="Sonraki==>" class="btn btn-primary" (click)="Next()"></th> </tr> </thead> <tbody> <tr *ngFor="let people of peopleList"> <th scope="row"> <button type="button" class="btn btn-success btn-sm move-right" (click)="Edit(people)">Edit</button> </th> <td>{{people.name.first}}</td> <td>{{people.email}}</td> <td>{{people.gender}}</td> </tr> </tbody> </table> </div> |
app.component.ts:
- Edit() işlemi olduğu zaman, sayfanın üstünde yazan title ve resmin gizlenmesi için “isEdit=true” değeri atanır.
- Güncellenecek modele, (“personModel=data“) seçilen yani edit buttonuna basılan satırdaki person data atanır.
- Save() işleminde, ilgili servise güncellenecek data(personModel) gönderilir.
- “this.personModel = null” ==> gönderilen model temizlenir. Bu şeklide, düzenleme için kullanılan input alanların içerisi boşaltılır.
- “this.isEdit = false” : Tekrar sayfanın üstündeki başlık ve resmin geri getirilip, güncelleme tablosunun kaldırılması için isEdit ==> false değeri atanır.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
personModel: Person; isEdit: boolean = false; public Edit(data: Person) { this.personModel = data; this.isEdit = true; } public Save() { this.service.updatePerson(this.personModel).subscribe((data: any) => {}); this.personModel = null; this.isEdit = false; } |
Not: service.cs(NodeJs) (Güncelleme): Aşağıda görüldüğü gibi, ilgili servisin dönüşünde bir string açıklama döndürmek “res.send(“succesfully saved”)“, client side tarafta hata olarak algılandı. Bunun önüne geçmek için, dönüş tipi değiştirilerek, “true” değeri dönülmüştür.
1 2 3 4 5 6 7 8 9 10 11 12 |
app.post('/updatePeople', async (req, res) => { console.log("req.username : " + req.body.username); try { var updatePerson = new Human(req.body); const person = await Human.findOne({ username: updatePerson.username }); await person.updateOne(updatePerson); /* return res.send("succesfully saved"); */ return true; } catch (error) { res.status(500).send(error); } }) |
Arama :
Gelin bir örnek de arama için yapalım: Girilen harflerle adı başlayan tüm kayıtları getirelim. Aşağıda görüldüğü gibi NodeJs’deki “getpeopleStartsWith()” çağrılmış ve Observable<Person> şeklinde kayıt dönülmüştür.
personService.ts:
1 2 3 4 5 6 7 8 9 |
searchUrl: string = "http://localhost:9480/getpeopleStartsWith"; public searchPeopleByName(name: string): Observable<Person> { return this.httpClient.get<Person>(this.searchUrl + "/" + name) .pipe( retry(1), catchError(this.errorHandel) ) } |
app.component.html : Aşağıda görüldüğü gibi dataların listelendiği tablonun head’ine, “userSearchInput” adında bir search box konmuştur. İlgili input’a, app.component.ts tarafında erişilecektir.
1 2 3 4 5 6 7 8 9 10 11 |
<table class="table table-striped table-dark"> <thead> <tr> <td> Name: <input type="text" #userSearchInput> </td> </tr> . . . </thead> |
app.component.ts: Öncekine göre bazı değişikliklere gidilmiştir. Tüm kod yazılmamıştır. Sadece değişen ve eklenen yerler yazılmıştır. Yeni eklenen özellik, kişilerin isme göre aranmasıdır.
- Birçok “rxjs” ve “rxjs/operators” kütüphanesi sayfanın başına eklenmiştir.
- “AppComponent implements AfterViewInit” : Artık “OnInit“‘den değil “AfterViewInit“‘den türetilmiştir. Nedeni, sayfanın tamamen yüklenmesinin beklenmesi ve “userSearchInput” input elementine erişilebilmesindendir.
- “@ViewChild(‘userSearchInput’, { static: false }) userSearchInput: ElementRef” : Html sayfada konan “userSearchInput” id’li element’e, app.component.ts tarafından erişilir.
- “isGetPeople:boolean=false” : Aramada input alandan harfler silinirken, 3 karakterden az olması durumunda tüm kayıdın sadece 1 defa çekilmesini sağlayan bir değişkendir.
- “ngAfterViewInit() {” : Tüm sayfanın yüklenmesi bittikten sonra, çağrılan function’dır.
- “this.getPeople()” : En başta, tüm kayıt çekilip ekrana basılır.
- “fromEvent(this.userSearchInput.nativeElement, ‘keyup’).pipe(” : “userSearchInput” html elementinin “keyup” eventi yakalanıp, “pipe()” ile birden fazla method işleme konmuştur.
- “map((event: any) => {” Input alana girilen karakter alınır.
- “event.target.value.length < 3 && !this.isGetPeople ? this.getPeople() : null” : Name alanına girilen karakter sayısı, 3’den az ise tüm kayıdın çekildiği “getPeople()” methodu çağrılır. “isGetPeople” değişkeni, eğer tüm kayıt birkere çağrılmış ise her karakter silme işleminde peş peşe tekrardan çağrılmasının önüne geçilmek amacı ile konmuştur.
- “, filter(res => res.length > 2)” : Girilen karakter sayısı, 2’den fazla ise işleme devam edilir.
- “, debounceTime(250)” : Herbir tuşa basıldığında 250milisaniye beklenmektedir. Amaç hızlıca girilen karakterler için, birkere search işlemi yapmaktır. Aksi taktirde, girilen her bir karakter için arama sorgusu yapılacak, ve bu durumda büyük bir zaman – performans kaybına gidilecektir.
- “, distinctUntilChanged()” : Bu method sonradan kaldırılmıştır. Bir önce aranmış kayıdın aynısı girilir ise, engel olmaktadır.
- “this.service.searchPeopleByName(text).subscribe((res) => {” : Yukarıdaki tüm aşamalar geçilmiş ise, girilen karakter ile ismi başlıyan tüm kayıtlar çekilir.
- “getPeople() {” : Methoduna “this.isGetPeople=true” şeklinde değişkene değer atanarak, aram kutucuğunda karakter silme durumunda tüm kayıt çekilmiş ise tekrardan çekilmesi engellenmiş ve böylece büyük bir performans kaybı gidilmesi önlenmiş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 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
import { Component, ElementRef, ViewChild, AfterViewInit } from '@angular/core'; import { PersonService } from 'src/Service/personService'; import { Observable, fromEvent } from 'rxjs'; import { Person } from 'src/Model/person'; import { map, filter, debounceTime, distinctUntilChanged } from 'rxjs/operators'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.scss'] }) export class AppComponent implements AfterViewInit { @ViewChild('userSearchInput', { static: false }) userSearchInput: ElementRef; isGetPeople:boolean=false; personModel: Person; isEdit: boolean = false; title = 'HumanResource'; peopleList: any = []; currentPagecount: number = 1; isNextActive: boolean = true; isPreviewActive: boolean = false; constructor(public service: PersonService) { } ngAfterViewInit() { this.getPeople(); fromEvent(this.userSearchInput.nativeElement, 'keyup').pipe( // basılan karakter alınır. map((event: any) => { //karakter sayısı 3'den az ise, tüm kayıt içinde ilk 5'i çekilir. event.target.value.length < 3 && !this.isGetPeople ? this.getPeople() : null; return event.target.value; }) //Girilen değer 2'den fazla ise. , filter(res => res.length > 2) // her bir tuşa basıldığında 250 milisaniye beklenir. , debounceTime(250) //gelen değer öncekinden farklı ise //, distinctUntilChanged() //geriye cevap dönülür. ).subscribe((text: string) => { this.service.searchPeopleByName(text).subscribe((res) => { this.isGetPeople = false; console.log('res', res); this.peopleList = res; }, (err) => { console.log('error', err); }); }); } public getPeople() { return this.service.getPeopleList().subscribe((data: any = []) => { this.isGetPeople=true; this.peopleList = data.slice(0, 5); console.log(this.peopleList); }); } . . . } |
Insert : Yeni Kayıt
Öncelikle gelin Angular’da servis tarafını yazalım:
Service / personService.ts: Aşağıda görüldüğü gibi NodeJs tarafında insertPeople() function’ına kaydedilecek Person model, post edilmektedir.
- httpOptions : Header’da post olarak gönderilecek “Person” datasının tipi, json olarak tanımlanmıştır.
- “return this.httpClient.post<Person>(this.inserteUrl, JSON.stringify(data), this.httpOptions)” : Yeni girilen “Person” model, “insertPeople()” NodeJs methoduna json olarak gönderilir.
- pipe() : retry() ve catchError() methodları birleştirilip bir sıraya konur.
- retry(1): Hata çıkması durumunda bir tekrar yapılır.
- getPeopleList() : Yukarıda önceden yazılan bu method, ihtiyaç doğrultusunda değiştirilmiştir. “desc” paramteresinin “true” olması durumunda, NodeJs’in “peopleDesc()” metodu ile, yeni bir kayıt eklendiğinde, kayıtlara mongo’nun her bir document için atadığı “_id” değerine göre, tersten sıralanıp çekilmesi sağlanmıştır. Böylece son kayıt yani yeni girilen kayıt, en başa gelmektedir.
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 |
httpOptions = { headers: new HttpHeaders({ 'Content-Type': 'application/json' }) } inserteUrl: string = "http://localhost:9480/insertPeople"; public insertPeople(data: Person): Observable<any> { return this.httpClient.post<Person>(this.inserteUrl, JSON.stringify(data), this.httpOptions) .pipe( retry(1), catchError(this.errorHandel) ) } baseUrlDesc: string = "http://localhost:9480/peopleDesc"; public getPeopleList(desc: boolean = false): Observable<Person> { let url: string = desc ? this.baseUrlDesc : this.baseUrl; return this.httpClient.get<Person>(url) .pipe( retry(1), catchError(this.errorHandel) ) } |
NodeJs/service.js : Yeni bir kayıt eklendiğinde, çekilen listede en başta gelmesi için NodeJs’e eklenmiş yeni bir methoddur. Normalde Person model’de “_id” alanı yoktur. İlgili alan, mongoDB tarafında atanmaktadır. Burda da tersten sıralama amacı ile “.sort(‘-_id’)” kullanılmıştır.
1 2 3 4 5 6 7 8 9 10 11 |
app.get("/peopleDesc", function (req, res) { Human.find() .sort('-_id') .exec( function (err, doc) { doc.forEach(function (item) { item.fullName = item._doc.name.first + ' ' + item._doc.name.last; }); res.send(doc); }) }) |
Şimdi sıra geldi Html sayfanın doldurulduğu app.component’e. Sadece yeni eklenen ve değiştirilen kısımlar aşağıda yazılmıştır.
app.component.ts :
- “isInsert” : Bu değişken yeni bir kayıt girişi olucağı zaman “true” değerini almakta, böylece edit için açılan alanlar haricinde yeni bir kayıt girmek için ekstra gerekli olan “UserName” ve “Gender” comboları açılmaktadır. Ayrıca kaydet button’u, bu değişkene göre “Insert” ya da “Update” işlemine karar vermektedir.
- “optionSelect [ ]” : “Gender” combosunun dolacağı 2 olasılık, bir dizi olarak kodda tanımlanmıştır.
- “personModel” : Güncellenecek, ya da yeni girilecek modeldir.
- “name” : Her yeni kayıt girişi için personModel’in, boş bir şablonunun çıkarılması gerekmektedir. Sonradan ayrıca tanımlanan Person sınıfının alt sınıfı olan “name” nesnesi boş olarak yaratılıp, person sınıfına atılmaktadır.
- “isEdit” : Güncelleme buttonuna basıldığı zaman, editlenecek alana ait propertylerin sayfada gözükmesini ve Kaydet button’una basıldığı zaman “Güncelleme” işleminin yapılmasını sağlıyan bir değişkendir.
- clickInsert() : New Record buttonuna basılınca, çağrılan methoddur. Yeni boş bir Person() model oluşturulur. Name alanı için “name()” sınıfı boş oluşturulup, “personModel“‘e atanır.
- “this.isInsert = !this.isInsert” Tıklanma durumunda , eğer alanlar gösteriliyorsa gizlenir, gizleniyor ise gösterilir.
- “this.isEdit = false”: Bu bir güncelleme işi değildir. Yani Insert işidir.
- Edit() : Yeni eklenen tek satır budur. “this.isInsert = false” . Yeni kayıt girmek için görünen özel alanları gizler.
- “this.isEdit = (this.personModel == data || this.isInsert==true) ? !this.isEdit : this.isEdit” : Eğer güncelleme buttonuna ardı ardına basılır ise, güncellenme amacı ile açılan alanlar kapanıp açılır. Eğer farklı kayıtlar için Edit tuşuna basılır ise, kapanma işlemi olmadan farklı kayıtların detay bilgisi ekrana basılır. Eğer önce yeni bir kayıt ekranına girilmiş ise, Edit tuşuna basılınca UserName ve Gender alanları gizlenir.
- Save() : “isEdit” true ise “Güncelleme”, değil ise yani “isInsert” true ise “Kaydetme” işlemi yapılır.
- this.service.insertPeople() : Yeni kayıt işleminden sonra ==> “this.getPeople(true)” tekrardan tüm person kaydı “-_id” alanına göre tersden sıralanarak alınmış ve böylece son kayıt üste gelmiştir.
- “this.personModel = null; this.isInsert = false” : Tekrardan personmodel boşaltılmış, böylece FrontEnd tarafında doldurulan html alanların boşaltılması sağlanmıştır. Ayrıca isInsert ‘e false değeri atanarak, üstte açılan tüm alanların kapanması sağlanmıştır.
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 |
. . . isInsert: boolean = false; optionSelect=['male','female'] personModel: Person; name: Name; isEdit: boolean = false; public clickInsert() { this.personModel = new Person(); this.name = new Name(); this.personModel.name = this.name; this.isInsert = !this.isInsert; this.isEdit = false; }; public Edit(data: Person) { this.isEdit = (this.personModel == data || this.isInsert==true) ? !this.isEdit : this.isEdit; this.personModel = data; this.isInsert = false; } public Save() { if (this.isEdit) { this.service.updatePerson(this.personModel).subscribe((data: any) => {}); this.personModel = null; this.isEdit = false; } else if (this.isInsert) { this.service.insertPeople(this.personModel).subscribe((data: any) => { this.getPeople(true); }); this.personModel = null; this.isInsert = false; } } |
Model/Person: Aşağıda görüldüğü gibi, ilgili sınıfı new()’lenerek yaratılabilmesi için constructor() tanımlanmıştır. Not: this.gender = “male” tanımlamasında amaç, Cinsiyet combosunun doldurulması anında, default değer olarak “male” seçeneğinin gelmesi daha model oluşturulurken sağlanmıştır. “Ağaç yaşken eğilir” :)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
export class Person { gender: String; email: String; username: String; name: { title: String, first: String, last: String }; fullName: String; constructor() { this.gender = "male"; } } |
Model / Name : Person modelinin subclass’ı, Person sınıfının boş bir şablon şeklinde new ()’lene bilmesi için ayrıca tanımlanmıştır.
1 2 3 4 5 6 7 |
export class Name { title: String; first: String; last: String; constructor() { } } |
app.component.html: Yeni kayıt girişi yani, insert için gerekli güncellemeler yapılmıştır.
- “<div *ngIf=”!isEdit && !isInsert”>” : Eğer Kaydetme ya da Güncelleme işi yok ise ilgili div gizlenir.
- “<table class=”table table-dark” *ngIf=”isEdit || isInsert”>” : Kaydetme ya da Güncellem işi yok ise ilgili tablo gizlenir.
- “<input type=”button” value=”Kaydet” (click)=”Save()” class=”btn btn-success”>” : Yeni bir kaydın girildiği ya da güncellendiği buttondur. Tıklanma anında “Save()” methodu çağrılmaktadır.
- “<tr *ngIf=”isInsert”>” : “UserName” ve “Gender” alanları sadece Insert, yani yeni bir kayıt girişi için kullanılır.
- “<button type=”button” class=”btn btn-success btn-sm move-right” (click)=”clickInsert()”>New Record</button>” : Yeni Kayıt buttonuna basılınca, eğer yeni personModel’inin dolacağa alanlar gizli ise gösterilir. Gözüküyor ise gizlenir. Tıklanma anında çağrılan method, yukarıda anlatılan “clickInsert()” methodudur.
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 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
<div style="text-align:center"> <div *ngIf="!isEdit && !isInsert"> <h1> Welcome to {{ title }}! </h1> <img width="200" src="assets/img/logo.png"> </div> <table class="table table-dark" *ngIf="isEdit || isInsert"> <tr> <td> <input type="button" value="Kaydet" (click)="Save()" class="btn btn-success"> </td> </tr> <tr> <td> Title : </td> <td> <input type="text" [(ngModel)]="personModel.name.title"> </td> <td> Name : </td> <td> <input type="text" [(ngModel)]="personModel.name.first"> </td> <tr> <td> Surname : </td> <td> <input type="text" [(ngModel)]="personModel.name.last"> </td> <td> Email : </td> <td> <input type="text" [(ngModel)]="personModel.email"> </td> </tr> <tr *ngIf="isInsert"> <td> <div>UserName :</div> </td> <td> <input type="text" [(ngModel)]="personModel.username"> </td> <td> <div>Gender :</div> </td> <td> <select name="gender" id="gender" [(ngModel)]="personModel.gender"> <option *ngFor = "let item of optionSelect" [ngValue]="item">{{item}}</option> </select> </td> </tr> </table> </div> <div class="table-responsive"> <table class="table table-striped table-dark"> <thead> <tr> <td> Name: <input type="text" #userSearchInput> </td> <td> <button type="button" class="btn btn-success btn-sm move-right" (click)="clickInsert()">New Record</button> </td> </tr> . . . </div> |
Delete : Silme
Sıra geldi silme işlemine. Herbir kayıdın soluna “Edit” butonunun yanına, “Delete buttonu yukarıdaki gibi konulur. (click) eventinde “Delete()” function’ı ==> “username” parametresi ile çağrılır. Amaç, seçilen kaydın tekil username’e göre silinmesidir.
app/app.component.ts:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
. . . <tbody> <tr *ngFor="let people of peopleList"> <th scope="row"> <button type="button" class="btn btn-success btn-sm move-right" (click)="Edit(people)">Edit</button> <button style="margin-left:20px" type="button" class="btn btn-danger btn-sm move-right" (click)="Delete(people)">Sil</button> </th> <td>{{people.name.first}}</td> <td>{{people.email}}</td> <td>{{people.gender}}</td> </tr> </tbody> |
Service/personService.ts: Aşağıda görüldüğü gibi silmek amaçlı tıklanan “Person”, NodeJs üzerindeki “deletePeople()” servisine gönderilerek silinir.
- “retry(1)” : Hata olamsı durumunda, bir seferlik tekrarlanır.
1 2 3 4 5 6 7 8 |
deleteUrl: string = "http://localhost:9480/deletePeople"; public deletePeople(data: Person): Observable<any> { return this.httpClient.post<Person>(this.deleteUrl, JSON.stringify(data), this.httpOptions) .pipe( retry(1), catchError(this.errorHandel) ) } |
app/app.component.ts: Seçilen “Person”, silme onayı alındıktan sonra servise gönderilerek silinir. Silinmenin başarılı olması durumunda, “getPeople()” methodu çağrılarak tüm kayıtlar yeniden çekilip sıralanır.
1 2 3 4 5 6 7 8 |
public Delete(data: Person) { if (confirm("Silmek istediğinize emin misiniz ")) { this.service.deletePeople(data).subscribe((data: any) => { console.log("Sil:" + data.username); this.getPeople(); }); } } |
Geldik bir makalenin daha sonuna. Bu makalede NodeJS ve MongoDB’ye bir giriş yaparken, Angular 8 ile de nasıl entegre edebileceğimizi gördük. MongoDB 4, hem kolay kullanım hem de performansı ile kıritik olmayan datalar ile çalışmak için tam bir biçilmiş kaftan. Ayrıca Robo 3T de, mongoDb için gerçekten çok başarılı ücretsiz ve kullanışlı bir ide. NodeJS’i, .Net Core WebApi servisine çok benzeyen bir teknoloji olarak görüyorum. Kapsamlı projelerde kullanımı giderek zorlaşsa da, routing ve express ile kolay entegrasyonu beni bazı şeylerde neden bu kadar uğraşıyormusuz, tek satırla nasıl da işler bitiyormuş gibi düşüncelere sokmadı değil. Makale içinde OsX environment’da NodeJs ve MongoDB ile nasıl çalışılabileceği hakkında, küçük tiyolar vermeye çalıştım. Daha sırada NodeJS Sawagger entegrasyonu, security tokenization, validationlar, microservice ler gibi birçok konu var. Bunları da ya bir ebook ya da makale olarak size aktarmaya çalışacağım.
Yeni bir makalede görüşmek üzere hepinize hoşçakalın.
Source Code : https://github.com/borakasmer/MongoDB-Nodej-Angular8-FullStack-Project
Kaynaklar :
Son Yorumlar