AngularJS ve Play Framework ile Modern Web Uygulamaları Oluşturma

Yayınlanan: 2022-03-11

Doğru amaç için doğru aracı seçmek, özellikle modern web uygulamaları oluşturmak söz konusu olduğunda, uzun bir yol kat eder. Birçoğumuz AngularJS'ye aşinayız ve sağlam web uygulaması ön uçları geliştirmeyi ne kadar kolaylaştırıyor. Birçoğu bu popüler web çerçevesinin kullanımına karşı çıkacak olsa da, kesinlikle sunacağı çok şey vardır ve çok çeşitli ihtiyaçlar için uygun bir seçim olabilir. Öte yandan, arka uçta kullandığınız bileşenler, genel kullanıcı deneyimi üzerinde etkileri olduğu için web uygulamasının performansı hakkında çok şey belirleyecektir. Play, Java ve Scala için yüksek hızlı bir web çerçevesidir. Hafif, durum bilgisi olmayan, web dostu bir mimariye dayanır ve Rails ve Django'ya benzer MVC kalıplarını ve ilkelerini takip eder.

angularjs ve oyun çerçevesi ile web uygulamaları

Bu yazıda, temel bir kimlik doğrulama mekanizmasına ve gönderi ve yorum yapma yeteneğine sahip basit bir blog uygulaması oluşturmak için AngularJS ve Play'i nasıl kullanabileceğimize bir göz atacağız. Bazı Twitter Bootstrap özellikleriyle AngularJS geliştirmesi, Play tabanlı bir REST API arka ucunun üzerinde tek sayfalık bir uygulama deneyimine güç vermemizi sağlayacak.

Web Uygulamaları - Başlarken

AngularJS Uygulama İskeleti

AngularJS ve Play uygulamaları buna göre istemci ve sunucu dizinlerinde bulunur. Şimdilik “client” dizini oluşturacağız.

 mkdir -p blogapp/client

Bir AngularJS uygulama iskeleti oluşturmak için harika bir iskele aracı olan Yeoman'ı kullanacağız. Yeoman'ı kurmak kolaydır. Basit bir iskelet AngularJS uygulamasını iskele için kullanmak muhtemelen daha da kolaydır:

 cd blogapp/client yo angular

İkinci komutu çalıştırmak, seçmeniz gereken birkaç seçenek izleyecektir. Bu proje için “Sass (Pusula ile)” ihtiyacımız yok. Aşağıdaki AngularJS eklentileriyle birlikte Boostrap'a ihtiyacımız olacak:

  • açısal-animate.js
  • açısal-cookies.js
  • açısal-resource.js
  • açısal-route.js
  • açısal-sanitize.js
  • açısal-touch.js

Bu noktada seçimlerinizi tamamladıktan sonra terminalinizde NPM ve Bower çıktılarını görmeye başlayacaksınız. İndirmeler tamamlandığında ve paketler kurulduğunda, kullanıma hazır bir AngularJS uygulama iskeletine sahip olacaksınız.

Çerçeve Uygulama İskeleti Oyna

Yeni bir Play uygulaması oluşturmanın resmi yolu, Typesafe Activator aracının kullanılmasını içerir. Kullanmadan önce, bilgisayarınıza indirip yüklemeniz gerekir. Mac OS kullanıyorsanız ve Homebrew kullanıyorsanız, bu aracı tek bir komut satırıyla yükleyebilirsiniz:

 brew install typesafe-activator

Komut satırından bir Play uygulaması oluşturmak çok kolaydır:

 cd blogapp/ activator new server play-java cd server/

Bir IDE'ye içe aktarma

Uygulamayı Eclipse veya IntelliJ gibi bir IDE'ye aktarmak için, uygulamanızı "gizlemeniz" veya "idealleştirmeniz" gerekir. Bunu yapmak için aşağıdaki komutu çalıştırın:

 activator

Yeni bir istem gördüğünüzde, "eclipse" veya "idea" yazın ve sırasıyla Eclipse veya IntelliJ için uygulama kodunu hazırlamak için enter tuşuna basın.

Kısaca, bu yazıda sadece projenin IntelliJ'e aktarılması sürecini ele alacağız. Eclipse'e aktarma işlemi de aynı derecede basit olmalıdır. Projeyi IntelliJ'e aktarmak için, “Dosya -> Yeni” altında bulunan “Mevcut kaynaklardan proje…” seçeneğini etkinleştirerek başlayın. Ardından build.sbt dosyanızı seçin ve “Tamam”a tıklayın. Bir sonraki iletişim kutusunda tekrar "Tamam"ı tıkladığınızda, IntelliJ Play uygulamanızı bir SBT projesi olarak içe aktarmaya başlamalıdır.

Typesafe Activator ayrıca bu iskelet uygulama kodunu oluşturmak için kullanabileceğiniz bir grafik kullanıcı arayüzü ile birlikte gelir.

Play uygulamamızı IntelliJ'e aktardığımıza göre artık çalışma alanına AngularJS uygulamamızı da aktarmalıyız. İster ayrı bir proje olarak isterseniz de Play uygulamasının bulunduğu mevcut projeye modül olarak import edebiliriz.

Burada Angular uygulamasını modül olarak import edeceğiz. “Dosya” menüsü altında “Yeni -> Mevcut Kaynaklardan Modül…” seçeneğini seçeceğiz. İletişim kutusundan “istemci” dizinini seçeceğiz ve “Tamam” ı tıklayacağız. Sonraki iki ekranda sırasıyla “Next” ve “Finish”e tıklayın.

Yerel Sunucuları Oluşturma

Bu noktada, AngularJS uygulamasını IDE'den Grunt görevi olarak başlatmak mümkün olmalıdır. İstemci klasörünüzü genişletin ve Gruntfile.js'ye sağ tıklayın. Açılır menüde "Grunt Görevlerini Göster"i seçin. "Grunt" etiketli bir panel, bir görev listesiyle görünecektir:

Yerel Sunucuları Oluşturma

Uygulamayı sunmaya başlamak için “servis” üzerine çift tıklayın. Bu, varsayılan web tarayıcınızı hemen açmalı ve bir yerel ana bilgisayar adresine yönlendirmelidir. Yeoman'ın logosunun bulunduğu bir saplama AngularJS sayfası görmelisiniz.

Ardından, arka uç uygulama sunucumuzu başlatmamız gerekiyor. Devam etmeden önce, birkaç sorunu ele almalıyız:

  1. Varsayılan olarak, hem AngularJS uygulaması (Yeoman tarafından önyükleme yapılır) hem de Play uygulaması 9000 numaralı bağlantı noktasında çalışmaya çalışır.
  2. Üretimde, her iki uygulama da muhtemelen tek bir etki alanı altında çalıştırılacak ve istekleri buna göre yönlendirmek için muhtemelen Nginx kullanacağız. Ancak geliştirme modunda, bu uygulamalardan birinin bağlantı noktası numarasını değiştirdiğimizde, web tarayıcıları onlara farklı etki alanlarında çalışıyormuş gibi davranacaktır.

Bu iki sorunu çözmek için tek yapmamız gereken, Play uygulamasına yapılan tüm AJAX isteklerinin proxy'ye aktarılması için Grunt proxy kullanmaktır. Bununla, özünde bu uygulama sunucularının her ikisi de aynı görünen bağlantı noktası numarasında mevcut olacaktır.

Öncelikle Play uygulama sunucusunun port numarasını 9090 olarak değiştirelim. Bunun için “Run/Debug Configurations” penceresini “Run -> Edit Configurations” diyerek açınız. Ardından, “Açılacak URL” alanındaki bağlantı noktası numarasını değiştirin. Bu değişikliği onaylamak ve pencereyi kapatmak için “Tamam”a tıklayın. “Çalıştır” düğmesine tıklamak bağımlılık çözüm sürecini başlatmalıdır - bu sürecin günlükleri görünmeye başlayacaktır.

Yerel Sunucuları Oluşturma

Tamamlandığında, web tarayıcınızda http://localhost:9090'a gidebilir ve birkaç saniye içinde Play uygulamanızı görebilmelisiniz. Grunt proxy'sini yapılandırmak için önce NPM kullanarak küçük bir Node.js paketi kurmamız gerekiyor:

 cd blogapp/client npm install grunt-connect-proxy --save-dev

Daha sonra Gruntfile.js dosyamızda ince ayar yapmamız gerekiyor. Bu dosyada, "bağlan" görevini bulun ve "vekil sunucular" anahtarını/değerini ondan sonra ekleyin:

 proxies: [ { context: '/app', // the context of the data service host: 'localhost', // wherever the data service is running port: 9090, // the port that the data service is running on changeOrigin: true } ],

Grunt artık tüm "/app/*" isteklerini arka uç Play uygulamasına proxy yapacak. Bu, bizi arka uca yapılan her aramayı beyaz listeye almaktan kurtaracak. Ayrıca, canlı yükleme davranışımızı da değiştirmemiz gerekiyor:

 livereload: { options: { open: true, middleware: function (connect) { var middlewares = []; // Setup the proxy middlewares.push(require('grunt-connect-proxy/lib/utils').proxyRequest); // Serve static files middlewares.push(connect.static('.tmp')); middlewares.push(connect().use( '/bower_components', connect.static('./bower_components') )); middlewares.push(connect().use( '/app/styles', connect.static('./app/styles') )); middlewares.push(connect.static(appConfig.app)); return middlewares; } } },

Son olarak, “serve” görevine yeni bir “'configureProxies:server” bağımlılığı eklememiz gerekiyor:

 grunt.registerTask('serve', 'Compile then start a connect web server', function (target) { if (target === 'dist') { return grunt.task.run(['build', 'connect:dist:keepalive']); } grunt.task.run([ 'clean:server', 'wiredep', 'concurrent:server', 'autoprefixer:server', 'configureProxies:server', 'connect:livereload', 'watch' ]); });

Grunt'u yeniden başlattıktan sonra, günlüklerinizde proxy'nin çalıştığını gösteren aşağıdaki satırları fark etmelisiniz:

 Running "autoprefixer:server" (autoprefixer) task File .tmp/styles/main.css created. Running "configureProxies:server" (configureProxies) task Running "connect:livereload" (connect) task Started connect web server on http://localhost:9000

Kayıt Formu Oluşturma

Blog uygulamamız için bir kayıt formu oluşturarak başlayacağız. Bu aynı zamanda her şeyin olması gerektiği gibi çalıştığını doğrulamamızı da sağlayacaktır. Bir Kaydolma denetleyicisi oluşturmak ve AngularJS'de görüntülemek için Yeoman'ı kullanabiliriz:

 yo angular:controller signup yo angular:view signup

Ardından, bu yeni oluşturulan görünüme başvurmak için uygulamamızın yönlendirmesini güncellemeli ve otomatik olarak oluşturulan fazlalık "hakkında" denetleyiciyi ve görünümü kaldırmalıyız. "app/scripts/app.js" dosyasının içinden, "app/scripts/controllers/about.js" ve "app/views/about.html" referanslarını kaldırın ve şunu bırakın:

 .config(function ($routeProvider) { $routeProvider .when('/', { templateUrl: 'views/main.html', controller: 'MainCtrl' }) .when('/signup', { templateUrl: 'views/signup.html', controller: 'SignupCtrl' }) .otherwise({ redirectTo: '/' });

Benzer şekilde, gereksiz bağlantıları kaldırmak için “app/index.html” dosyasını güncelleyin ve kayıt sayfasına bir bağlantı ekleyin:

 <div class="collapse navbar-collapse"> <ul class="nav navbar-nav"> <li class="active"><a href="#/">Home</a></li> <li><a ng-href="#/signup">Signup</a></li> </ul> </div> </div>

Ayrıca, "about.js" için komut dosyası etiketini kaldırın:

 <!-- build:js({.tmp,app}) scripts/scripts.js --> <script src="scripts/app.js"></script> <script src="scripts/controllers/main.js"></script> <script src="scripts/controllers/signup.js"></script> <!-- endbuild --> </body> </html>

Ardından, “signup.html” dosyamıza bir form ekleyin:

 <form name="signupForm" ng-submit="signup()" novalidate> <div> <label for="email">Email</label> <input name="email" class="form-control" type="email" placeholder="Email" ng-model="email"> </div> <div> <label for="password">Password</label> <input name="password" class="form-control" type="password" placeholder="Password" ng-model="password"> </div> <button type="submit" class="btn btn-primary">Sign up!</button> </form>

Formu Angular controller tarafından işlememiz gerekiyor. "app.js" içindeki yönlendirme mantığımız, görünümümüz yüklenmeden önce otomatik olarak bir denetleyici çalıştırdığından, görünümlerimize özellikle "ng-controller" özniteliğini eklememize gerek olmadığını belirtmekte fayda var. Bu formu bağlamak için tek yapmamız gereken $scope içinde tanımlanmış uygun bir "kaydolma" işlevine sahip olmak. Bu, "signup.js" dosyasında yapılmalıdır:

 angular.module('clientApp') .controller('SignupCtrl', function ($scope, $http, $log) { $scope.signup = function() { var payload = { email : $scope.email, password : $scope.password }; $http.post('app/signup', payload) .success(function(data) { $log.debug(data); }); }; });

Şimdi Chrome geliştirici konsolunu açalım, “Ağ” sekmesine geçelim ve kayıt formunu göndermeyi deneyelim.

Angular Play kayıt formu örneği

Oynatma arka ucunun doğal olarak bir "İşlem bulunamadı" hata sayfasıyla yanıt verdiğini göreceğiz. Henüz uygulanmadığı için bu bekleniyor. Ancak bunun anlamı, Grunt proxy kurulumumuzun doğru şekilde çalıştığıdır!

Ardından, Play uygulama denetleyicisinde esasen bir yöntem olan bir "Eylem" ekleyeceğiz. “app/controllers” paketindeki “Application” sınıfına yeni bir “signup” yöntemi ekleyin:

 public static Result signup() { return ok("Success!"); }

Şimdi “conf/routes” dosyasını açın ve aşağıdaki satırı ekleyin:

 POST /app/signup controllers.Application.signup

Son olarak, web tarayıcımız olan http://localhost:9000/#/signup'a dönüyoruz. Bu sefer “Gönder” düğmesine tıklamak farklı bir sonuç verecektir:

Tarayıcıda Angular Play kayıt formu örneği

Kaydolma yönteminde yazdığımız sabit kodlanmış değeri görüyor olmalısınız. Bu durumda, geliştirme ortamımız hazır olduğundan ve hem Angular hem de Play uygulamaları için çalıştığından devam etmeye hazırız.

Play'de Ebean Modellerini Tanımlama

Modelleri tanımlamadan önce bir datastore seçelim. Bu yazımızda H2 in-memory veritabanını kullanacağız. Bunu etkinleştirmek için, “application.conf” dosyasında aşağıdaki satırları bulun ve yorumunu kaldırın:

 db.default.driver=org.h2.Driver db.default.url="jdbc:h2:mem:play" db.default.user=sa db.default.password="" ... ebean.default="models.*"

Ve aşağıdaki satırı ekleyin:

 applyEvolutions.default=true

Blog alan modelimiz oldukça basittir. Her şeyden önce, gönderi oluşturabilecek kullanıcılarımız var ve ardından her gönderi, oturum açmış herhangi bir kullanıcı tarafından yorumlanabilir. Ebean Modellerimizi oluşturalım.

kullanıcı

 // User.java @Entity public class User extends Model { @Id public Long id; @Column(length = 255, unique = true, nullable = false) @Constraints.MaxLength(255) @Constraints.Required @Constraints.Email public String email; @Column(length = 64, nullable = false) private byte[] shaPassword; @OneToMany(cascade = CascadeType.ALL) @JsonIgnore public List<BlogPost> posts; public void setPassword(String password) { this.shaPassword = getSha512(password); } public void setEmail(String email) { this.email = email.toLowerCase(); } public static final Finder<Long, User> find = new Finder<Long, User>( Long.class, User.class); public static User findByEmailAndPassword(String email, String password) { return find .where() .eq("email", email.toLowerCase()) .eq("shaPassword", getSha512(password)) .findUnique(); } public static User findByEmail(String email) { return find .where() .eq("email", email.toLowerCase()) .findUnique(); } public static byte[] getSha512(String value) { try { return MessageDigest.getInstance("SHA-512").digest(value.getBytes("UTF-8")); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } } }

Blog yazısı

 // BlogPost.java @Entity public class BlogPost extends Model { @Id public Long id; @Column(length = 255, nullable = false) @Constraints.MaxLength(255) @Constraints.Required public String subject; @Column(columnDefinition = "TEXT") @Constraints.Required public String content; @ManyToOne public User user; public Long commentCount; @OneToMany(cascade = CascadeType.ALL) public List<PostComment> comments; public static final Finder<Long, BlogPost> find = new Finder<Long, BlogPost>( Long.class, BlogPost.class); public static List<BlogPost> findBlogPostsByUser(final User user) { return find .where() .eq("user", user) .findList(); } public static BlogPost findBlogPostById(final Long id) { return find .where() .eq("id", id) .findUnique(); } }

Yorum Gönder

 // PostComment.java @Entity public class PostComment extends Model { @Id public Long id; @ManyToOne @JsonIgnore public BlogPost blogPost; @ManyToOne public User user; @Column(columnDefinition = "TEXT") public String content; public static final Finder<Long, PostComment> find = new Finder<Long, PostComment>( Long.class, PostComment.class); public static List<PostComment> findAllCommentsByPost(final BlogPost blogPost) { return find .where() .eq("post", blogPost) .findList(); } public static List<PostComment> findAllCommentsByUser(final User user) { return find .where() .eq("user", user) .findList(); } }

Gerçek Kayıt İşlemi

Şimdi, kullanıcıların kaydolmasına izin vererek ilk gerçek eylemimizi oluşturalım:

 // Application.java public static Result signup() { Form<SignUp> signUpForm = Form.form(SignUp.class).bindFromRequest(); if ( signUpForm.hasErrors()) { return badRequest(signUpForm.errorsAsJson()); } SignUp newUser = signUpForm.get(); User existingUser = User.findByEmail(newUser.email); if(existingUser != null) { return badRequest(buildJsonResponse("error", "User exists")); } else { User user = new User(); user.setEmail(newUser.email); user.setPassword(newUser.password); user.save(); session().clear(); session("username", newUser.email); return ok(buildJsonResponse("success", "User created successfully")); } } public static class UserForm { @Constraints.Required @Constraints.Email public String email; } public static class SignUp extends UserForm { @Constraints.Required @Constraints.MinLength(6) public String password; } private static ObjectNode buildJsonResponse(String type, String message) { ObjectNode wrapper = Json.newObject(); ObjectNode msg = Json.newObject(); msg.put("message", message); wrapper.put(type, msg); return wrapper; }

Bu uygulamada kullanılan kimlik doğrulamanın çok basit olduğunu ve üretim amaçlı kullanım için önerilmediğini unutmayın.

İşin ilginç yanı, kayıt formlarını işlemek için Play formlarını kullanmamızdır. SignUp form sınıfımıza birkaç kısıtlama koyduk. Doğrulama, açık doğrulama mantığına ihtiyaç duymadan bizim için otomatik olarak yapılacaktır.

Web tarayıcısında AngularJS uygulamamıza geri dönersek ve tekrar “Gönder” e tıklarsak, sunucunun şimdi uygun bir hata ile yanıt verdiğini - bu alanların gerekli olduğunu göreceğiz.

oyun formu gönder

AngularJS'de Sunucu Hatalarını İşleme

Yani sunucudan bir hata alıyoruz, ancak uygulama kullanıcısının neler olduğu hakkında hiçbir fikri yok. Yapabileceğimiz en az şey, hatayı kullanıcımıza göstermektir. İdeal olarak, ne tür bir hata aldığımızı anlamamız ve kullanıcı dostu bir mesaj göstermemiz gerekir. Hatayı göstermemize yardımcı olacak basit bir uyarı hizmeti oluşturalım.

İlk olarak, Yeoman ile bir hizmet şablonu oluşturmamız gerekiyor:

 yo angular:service alerts

Ardından, bu kodu "alerts.js" dosyasına ekleyin:

 angular.module('clientApp') .factory('alertService', function($timeout) { var ALERT_TIMEOUT = 5000; function add(type, msg, timeout) { if (timeout) { $timeout(function(){ closeAlert(this); }, timeout); } else { $timeout(function(){ closeAlert(this); }, ALERT_TIMEOUT); } return alerts.push({ type: type, msg: msg, close: function() { return closeAlert(this); } }); } function closeAlert(alert) { return closeAlertIdx(alerts.indexOf(alert)); } function closeAlertIdx(index) { return alerts.splice(index, 1); } function clear(){ alerts = []; } function get() { return alerts; } var service = { add: add, closeAlert: closeAlert, closeAlertIdx: closeAlertIdx, clear: clear, get: get }, alerts = []; return service; } );

Şimdi uyarılardan sorumlu ayrı bir controller oluşturalım:

 yo angular:controller alerts
 angular.module('clientApp') .controller('AlertsCtrl', function ($scope, alertService) { $scope.alerts = alertService.get(); });

Şimdi aslında güzel Bootstrap hata mesajlarını göstermemiz gerekiyor. En kolay yol, Angular UI kullanmaktır. Yüklemek için Bower'ı kullanabiliriz:

 bower install angular-bootstrap --save

"app.js" dosyanıza Angular UI modülünü ekleyin:

 angular .module('clientApp', [ 'ngAnimate', 'ngCookies', 'ngResource', 'ngRoute', 'ngSanitize', 'ngTouch', 'ui.bootstrap' ])

“index.html” dosyamıza uyarı yönergesini ekleyelim:

 <div class="container"> <div ng-controller="AlertsCtrl"> <alert ng-repeat="alert in alerts" type="{{alert.type}}" close="alert.close()">{{ alert.msg }}</alert> </div> <div ng-view=""></div> </div>

Son olarak, SignUp denetleyicisini güncellememiz gerekiyor:

 angular.module('clientApp') .controller('SignupCtrl', function ($scope, $http, $log, alertService, $location, userService) { $scope.signup = function() { var payload = { email : $scope.email, password : $scope.password }; $http.post('app/signup', payload) .error(function(data, status) { if(status === 400) { angular.forEach(data, function(value, key) { if(key === 'email' || key === 'password') { alertService.add('danger', key + ' : ' + value); } else { alertService.add('danger', value.message); } }); } if(status === 500) { alertService.add('danger', 'Internal server error!'); } }) }; });

Şimdi boş formu tekrar gönderirsek, formun üzerinde görüntülenen hataları göreceğiz:

Angular Play hataları

Artık hatalar işlendiğine göre, kullanıcı kaydı başarılı olduğunda bir şeyler yapmamız gerekiyor. Kullanıcıyı, gönderi ekleyebileceği bir gösterge tablosu sayfasına yönlendirebiliriz. Ama önce, onu oluşturmalıyız:

 yo angular:view dashboard yo angular:controller dashboard

"signup.js" denetleyici kayıt yöntemini, başarılı olduğunda kullanıcıyı yeniden yönlendirecek şekilde değiştirin:

 angular.module('clientApp') .controller('SignupCtrl', function ($scope, $http, $log, alertService, $location) { // .. .success(function(data) { if(data.hasOwnProperty('success')) { $location.path('/dashboard'); } });

“apps.js” içine yeni bir rota ekleyin:

 .when('/dashboard', { templateUrl: 'views/dashboard.html', controller: 'DashboardCtrl' })

Ayrıca kullanıcının login olup olmadığını da takip etmemiz gerekiyor. Bunun için ayrı bir servis oluşturalım:

 yo angular:service user
 // user.js angular.module('clientApp') .factory('userService', function() { var username = ''; return { username : username }; });

Ayrıca, kullanıcıyı yeni kaydolmuş birine ayarlamak için kayıt denetleyicisini değiştirin:

 .success(function(data) { if(data.hasOwnProperty('success')) { userService.username = $scope.email; $location.path('/dashboard');; } });

Gönderi eklemenin ana işlevini eklemeden önce, oturum açma ve oturumu kapatma, kullanıcı bilgilerini gösterge panosunda görüntüleme ve ayrıca arka uçta kimlik doğrulama desteği ekleme gibi diğer bazı önemli özelliklerle ilgilenelim.

Temel Kimlik Doğrulama

Play uygulamamıza geçelim ve giriş ve çıkış işlemlerini uygulayalım. “Application.java” dosyasına tez satırları ekleyin:

 public static Result login() { Form<Login> loginForm = Form.form(Login.class).bindFromRequest(); if (loginForm.hasErrors()) { return badRequest(loginForm.errorsAsJson()); } Login loggingInUser = loginForm.get(); User user = User.findByEmailAndPassword(loggingInUser.email, loggingInUser.password); if(user == null) { return badRequest(buildJsonResponse("error", "Incorrect email or password")); } else { session().clear(); session("username", loggingInUser.email); ObjectNode wrapper = Json.newObject(); ObjectNode msg = Json.newObject(); msg.put("message", "Logged in successfully"); msg.put("user", loggingInUser.email); wrapper.put("success", msg); return ok(wrapper); } } public static Result logout() { session().clear(); return ok(buildJsonResponse("success", "Logged out successfully")); } public static Result isAuthenticated() { if(session().get("username") == null) { return unauthorized(); } else { ObjectNode wrapper = Json.newObject(); ObjectNode msg = Json.newObject(); msg.put("message", "User is logged in already"); msg.put("user", session().get("username")); wrapper.put("success", msg); return ok(wrapper); } } public static class Login extends UserForm { @Constraints.Required public String password; }

Ardından, belirli arka uç çağrılarına yalnızca kimliği doğrulanmış kullanıcılara izin verme özelliğini ekleyelim. Aşağıdaki kodla “Secured.java” oluşturun:

 public class Secured extends Security.Authenticator { @Override public String getUsername(Context ctx) { return ctx.session().get("username"); } @Override public Result onUnauthorized(Context ctx) { return unauthorized(); } }

Bu sınıfı daha sonra yeni eylemleri korumak için kullanacağız. Ardından, AngularJS uygulamamızın ana menüsünü, kullanıcı adı ve çıkış bağlantılarını gösterecek şekilde değiştirmeliyiz. Bunun için controller oluşturmamız gerekiyor:

 yo angular:controller menu
 // menu.js angular.module('clientApp') .controller('MenuCtrl', function ($scope, $http, userService, $location) { $scope.user = userService; $scope.logout = function() { $http.get('/app/logout') .success(function(data) { if(data.hasOwnProperty('success')) { userService.username = ''; $location.path('/login'); } }); }; $scope.$watch('user.username', function (newVal) { if(newVal === '') { $scope.isLoggedIn = false; } else { $scope.username = newVal; $scope.isLoggedIn = true; } }); });

Ayrıca giriş sayfası için bir görünüme ve bir denetleyiciye ihtiyacımız var:

 yo angular:controller login yo angular:view login
 <!-- login.html --> <form name="loginForm" ng-submit="login()" novalidate> <div> <label for="email">Email</label> <input name="email" class="form-control" type="email" placeholder="Email" ng-model="email"> </div> <div> <label for="password">Password</label> <input name="password" class="form-control" type="password" placeholder="Password" ng-model="password"> </div> <button type="submit" class="btn btn-primary">Log in</button> </form>
 // login.js angular.module('clientApp') .controller('LoginCtrl', function ($scope, userService, $location, $log, $http, alertService) { $scope.isAuthenticated = function() { if(userService.username) { $log.debug(userService.username); $location.path('/dashboard'); } else { $http.get('/app/isauthenticated') .error(function() { $location.path('/login'); }) .success(function(data) { if(data.hasOwnProperty('success')) { userService.username = data.success.user; $location.path('/dashboard'); } }); } }; $scope.isAuthenticated(); $scope.login = function() { var payload = { email : this.email, password : this.password }; $http.post('/app/login', payload) .error(function(data, status){ if(status === 400) { angular.forEach(data, function(value, key) { if(key === 'email' || key === 'password') { alertService.add('danger', key + ' : ' + value); } else { alertService.add('danger', value.message); } }); } else if(status === 401) { alertService.add('danger', 'Invalid login or password!'); } else if(status === 500) { alertService.add('danger', 'Internal server error!'); } else { alertService.add('danger', data); } }) .success(function(data){ $log.debug(data); if(data.hasOwnProperty('success')) { userService.username = data.success.user; $location.path('/dashboard'); } }); }; });

Ardından, kullanıcı verilerini gösterebilmesi için menüyü ince ayarlıyoruz:

 <!-- index.html --> <div class="collapse navbar-collapse" ng-controller="MenuCtrl"> <ul class="nav navbar-nav pull-right" ng-hide="isLoggedIn"> <li><a ng-href="/#/signup">Sign up!</a></li> <li><a ng-href="/#/login">Login</a></li> </ul> <div class="btn-group pull-right acc-button" ng-show="isLoggedIn"> <button type="button" class="btn btn-default">{{ username }}</button> <button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-expanded="false"> <span class="caret"></span> <span class="sr-only">Toggle Dropdown</span> </button> <ul class="dropdown-menu" role="menu"> <li><a ng-href="/#/dashboard">Dashboard</a></li> <li class="divider"></li> <li><a href="#" ng-click="logout()">Logout</a></li> </ul> </div> </div>

Şimdi uygulamaya giriş yaparsanız, aşağıdaki ekranı görebilmeniz gerekir:

Ekran görüntüsünü oynat

Gönderi Ekleme

Artık temel kayıt ve kimlik doğrulama mekanizmalarına sahip olduğumuza göre, gönderme işlevini uygulamaya başlayabiliriz. Gönderi eklemek için yeni bir görünüm ve denetleyici ekleyelim.

 yo angular:view addpost
 <!-- addpost.html --> <form name="postForm" ng-submit="post()" novalidate> <div> <label for="subject">Subject</label> <input name="subject" class="form-control" type="subject" placeholder="Subject" ng-model="subject"> </div> <div> <label for="content">Post</label> <textarea name="content" class="form-control" placeholder="Content" ng-model="content"></textarea> </div> <button type="submit" class="btn btn-primary">Submit post</button> </form>
 yo angular:controller addpost
 // addpost.js angular.module('clientApp') .controller('AddpostCtrl', function ($scope, $http, alertService, $location) { $scope.post = function() { var payload = { subject : $scope.subject, content: $scope.content }; $http.post('/app/post', payload) .error(function(data, status) { if(status === 400) { angular.forEach(data, function(value, key) { if(key === 'subject' || key === 'content') { alertService.add('danger', key + ' : ' + value); } else { alertService.add('danger', value.message); } }); } else if(status === 401) { $location.path('/login'); } else if(status === 500) { alertService.add('danger', 'Internal server error!'); } else { alertService.add('danger', data); } }) .success(function(data) { $scope.subject = ''; $scope.content = ''; alertService.add('success', data.success.message); }); }; });

Ardından “app.js”yi aşağıdakileri içerecek şekilde güncelleriz:

 .when('/addpost', { templateUrl: 'views/addpost.html', controller: 'AddpostCtrl' })

Ardından, gösterge tablosu menüsündeki "addpost" görünümümüz için bir bağlantı eklemek üzere "index.html" dosyasını değiştiririz:

 <ul class="dropdown-menu" role="menu"> <li><a ng-href="/#/dashboard">Dashboard</a></li> <li><a ng-href="/#/addpost">Add post</a></li> <li class="divider"></li> <li><a href="#" ng-click="logout()">Logout</a></li> </ul>

Şimdi Play uygulaması tarafında, addPost yöntemiyle yeni bir controller Post oluşturalım:

 // Post.java public class Post extends Controller { public static Result addPost() { Form<PostForm> postForm = Form.form(PostForm.class).bindFromRequest(); if (postForm.hasErrors()) { return badRequest(postForm.errorsAsJson()); } else { BlogPost newBlogPost = new BlogPost(); newBlogPost.commentCount = 0L; newBlogPost.subject = postForm.get().subject; newBlogPost.content = postForm.get().content; newBlogPost.user = getUser(); newBlogPost.save(); } return ok(Application.buildJsonResponse("success", "Post added successfully")); } private static User getUser() { return User.findByEmail(session().get("username")); } public static class PostForm { @Constraints.Required @Constraints.MaxLength(255) public String subject; @Constraints.Required public String content; } }

Yönlendirmede yeni eklenen yöntemleri işleyebilmek için rota dosyasına yeni bir giriş ekleyin:

 POST /app/post controllers.Post.addPost

Bu noktada, yeni gönderiler ekleyebilmelisiniz.

Play'de yeni gönderiler ekleme

Gönderileri Görüntüleme

Gönderileri görüntüleyemezsek, eklemenin değeri azdır. Yapmak istediğimiz tüm gönderileri ana sayfada listelemek. Uygulama denetleyicimize yeni bir yöntem ekleyerek başlıyoruz:

 // Application.java public static Result getPosts() { return ok(Json.toJson(BlogPost.find.findList())); }

Ve onu rota dosyamıza kaydettirmek:

 GET /app/posts controllers.Application.getPosts

Ardından, AngularJS uygulamamızda ana denetleyicimizi değiştiriyoruz:

 // main.js angular.module('clientApp') .controller('MainCtrl', function ($scope, $http) { $scope.getPosts = function() { $http.get('app/posts') .success(function(data) { $scope.posts = data; }); }; $scope.getPosts(); });

Son olarak, “main.html”deki her şeyi kaldırın ve şunu ekleyin:

 <div class="panel panel-default" ng-repeat="post in posts"> <div class="panel-body"> <h4>{{ post.subject }}</h4> <p> {{ post.content }} </p> </div> <div class="panel-footer">Post by: {{ post.user.email }} | <a ng-href="/#/viewpost/{{ post.id }}">Comments <span class="badge">{{ post.commentCount }}</span></a></div> </div>

Şimdi, uygulama ana sayfanızı yüklerseniz, buna benzer bir şey görüyor olmalısınız:

Angularjs Play örneği yüklendi

Ayrıca muhtemelen bireysel gönderiler için ayrı bir görüşümüz olmalıdır.

 yo angular:controller viewpost yo angular:view viewpost
 // viewpost.js angular.module('clientApp') .controller('ViewpostCtrl', function ($scope, $http, alertService, userService, $location) { $scope.user = userService; $scope.params = $routeParams; $scope.postId = $scope.params.postId; $scope.viewPost = function() { $http.get('/app/post/' + $scope.postId) .error(function(data) { alertService.add('danger', data.error.message); }) .success(function(data) { $scope.post = data; }); }; $scope.viewPost(); });
 <!-- viewpost.html --> <div class="panel panel-default" ng-show="post"> <div class="panel-body"> <h4>{{ post.subject }}</h4> <p> {{ post.content }} </p> </div> <div class="panel-footer">Post by: {{ post.user.email }} | Comments <span class="badge">{{ post.commentCount }}</span></a></div> </div>

Ve AngularJS rotası:

 app.js: .when('/viewpost/:postId', { templateUrl: 'views/viewpost.html', controller: 'ViewpostCtrl' })

Daha önce olduğu gibi, uygulama denetleyicimize yeni bir yöntem ekliyoruz:

 // Application.java public static Result getPost(Long id) { BlogPost blogPost = BlogPost.findBlogPostById(id); if(blogPost == null) { return notFound(buildJsonResponse("error", "Post not found")); } return ok(Json.toJson(blogPost)); }

… Ve yeni bir rota:

 GET /app/post/:id controllers.Application.getPost(id: Long)

Artık http://localhost:9000/#/viewpost/1 adresine giderseniz, belirli bir gönderi için bir görünüm yükleyebilirsiniz. Ardından, kontrol panelinde kullanıcının gönderilerini görme özelliğini ekleyelim:

 // dashboard.js angular.module('clientApp') .controller('DashboardCtrl', function ($scope, $log, $http, alertService, $location) { $scope.loadPosts = function() { $http.get('/app/userposts') .error(function(data, status) { if(status === 401) { $location.path('/login'); } else { alertService.add('danger', data.error.message); } }) .success(function(data) { $scope.posts = data; }); }; $scope.loadPosts(); });
 <!-- dashboard.html --> <h4>My Posts</h4> <div ng-hide="posts.length">No posts yet. <a ng-href="/#/addpost">Add a post</a></div> <div class="panel panel-default" ng-repeat="post in posts"> <div class="panel-body"> <a ng-href="/#/viewpost/{{ post.id }}">{{ post.subject }}</a> | Comments <span class="badge">{{ post.commentCount }}</span> </div> </div>

Ayrıca Posta denetleyicisine yeni bir yöntem ve ardından bu yönteme karşılık gelen bir yol ekleyin:

 // Post.java public static Result getUserPosts() { User user = getUser(); if(user == null) { return badRequest(Application.buildJsonResponse("error", "No such user")); } return ok(Json.toJson(BlogPost.findBlogPostsByUser(user))); }
 GET /app/userposts controllers.Post.getUserPosts

Artık gönderi oluşturduğunuzda, bunlar gösterge tablosunda listelenecektir:

Kontrol panelinde listelenen yeni gönderiler

İşlevselliği Yorumlama

Yorum yapma işlevini uygulamak için, Post controller'a yeni bir yöntem ekleyerek başlayacağız:

 // Post.java public static Result addComment() { Form<CommentForm> commentForm = Form.form(CommentForm.class).bindFromRequest(); if (commentForm.hasErrors()) { return badRequest(commentForm.errorsAsJson()); } else { PostComment newComment = new PostComment(); BlogPost blogPost = BlogPost.findBlogPostById(commentForm.get().postId); blogPost.commentCount++; blogPost.save(); newComment.blogPost = blogPost; newComment.user = getUser(); newComment.content = commentForm.get().comment; newComment.save(); return ok(Application.buildJsonResponse("success", "Comment added successfully")); } } public static class CommentForm { @Constraints.Required public Long postId; @Constraints.Required public String comment; }

And as always, we need to register a new route for this method:

 POST /app/comment controllers.Post.addComment

In our AngularJS application, we add the following to “viewpost.js”:

 $scope.addComment = function() { var payload = { postId: $scope.postId, comment: $scope.comment }; $http.post('/app/comment', payload) .error(function(data, status) { if(status === 400) { angular.forEach(data, function(value, key) { if(key === 'comment') { alertService.add('danger', key + ' : ' + value); } else { alertService.add('danger', value.message); } }); } else if(status === 401) { $location.path('/login'); } else if(status === 500) { alertService.add('danger', 'Internal server error!'); } else { alertService.add('danger', data); } }) .success(function(data) { alertService.add('success', data.success.message); $scope.comment = ''; $scope.viewPost(); }); };

And finally add the following lines to “viewpost.html”:

 <div class="well" ng-repeat="comment in post.comments"> <span class="label label-default">By: {{ comment.user.email }}</span> <br/> {{ comment.content }} </div> <div ng-hide="user.username || !post"><h4><a ng-href="/#/login">Login</a> to comment</h4></div> <form name="addCommentForm" ng-submit="addComment()" novalidate ng-show="user.username"> <div><h4>Add comment</h4></div> <div> <label for="comment">Comment</label> <textarea name="comment" class="form-control" placeholder="Comment" ng-model="comment"></textarea> </div> <button type="submit" class="btn btn-primary">Add comment</button> </form>

Now if you open any post, you will be able to add and view comments.

View comments screenshot

What's Next?

In this tutorial, we have built an AngularJS blog with a Play application serving as a REST API back-end. Although the application lacks robust data validation (especially on the client side) and security, these topics were out of the scope of this tutorial. It was aiming to demonstrate one of the many possible ways of building an application of this kind. For convenience, the source code of this application has been uploaded to a GitHub repository.

If you find this combination of AngularJS and Play in web application development interesting, I highly recommend you review the following topics further:

  • AngularJS documentation
  • Play documentation
  • Recommended security approach in one page JS app with Play as back-end (contains example)
  • Secure REST API without OAuth
  • Ready Play authentication plug-in (might be not fully usable for single-page JavaScript applications, but can be used as a good example)