Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to build an AngularJS app with Yesod

I managed to successfully write a small app using Yesod. Now I am in the phase in which I want to add better interaction to it, and I would like to do this using AngularJS.

As far as I can see, the support for AngularJS in Yesod is still experimental. Moreover, the documentation I found so far is quite unaccessible for me. I don't master all of the Yesod concepts.

So I was wondering, what are possible ways to integrate AngularJS and the Yesod framework. What I'm thinking about doing is:

  1. Writing the front-end in AngularJS.
  2. Develop the web-service using Yesod.
  3. Connect the front-end and the web-service by means of GET and POST http requests. Information can be sent to the server by means of input forms (leveraging some of Yesod capabilities in this way), and information can be sent to the front-end by means of JSON objects.

Ideally I'd like to write everything in Haskell, but in the current state of affairs that may not be a possibility. Thus I wanted to ask if the alternative I have in mind is a good one, and whether there are ways to improving it.

Thank you.

like image 551
Damian Nadales Avatar asked Nov 17 '14 10:11

Damian Nadales


1 Answers

So I don't know a thing about haskell or yesod but it wasn't too difficult to integrate angular with Yesod. Please do follow the steps in order to end up with a working app!

Here are the steps I followed to set up

  • Followed the Yesod quickstart to set up a Yesod app

    brew install haskell-stack

    stack new my-project yesod-sqlite && cd my-project

    stack install yesod-bin cabal-install --install-ghc

    stack build

    stack exec -- yesod devel

Now you can access a simple OTB web app here. The generated app has the following structure:

enter image description here

  • I used bower to set pull in angular, jQuery and bootstrap
  • I used a custom .bowerrc file to pull the packages inside the static folder

.bowerrc

{
    "directory": "static/bower_modules"
}

bower.json

{
  "name": "my-project",
  "version": "0.0.0",
  "authors": [
      "Atef Haque <[email protected]>"
  ],
  "description": "Haskell with Angular",
  "keywords": [
      "haskell",
      "angular"
  ],
  "license": "MIT",
  "ignore": [
      "**/.*",
      "node_modules",
      "bower_components",
      "static/bower_modules",
      "test",
      "tests"
  ],
  "dependencies": {
      "angular": "~1.5.3",
      "angular-route": "~1.5.3",
      "bootstrap": "~3.3.6"
  }
}

Running bower install installs the packages inside the static/bower_packages directory

enter image description here

  • Now I added my scripts and templates inside static/scripts and static/templates directories respectively

enter image description here

app.js

var app = angular.module('app', ['ngRoute']);

app.run(['$rootScope', function ($rootScope) {
    $rootScope.title = 'home';
}]);

app.config(['$routeProvider', function ($routeProvider) {
    $routeProvider
    .when('/', {
        templateUrl : 'static/templates/layout.html',
        controller  : 'HomeCtrl'
    });
}])

app.controller('HomeCtrl', ['$scope', 'Comment', function ($scope, Comment) {
    $scope.comments = [];
    $scope.post = function () {
        Comment
        .post($scope.message)
        .success(function (data) {
            $scope.comments.push(data);
        })
        .error(function (error) {
            console.log(error);
        });
    };
}])

app.service('Comment', ['$http', function ($http) {
    this.post = function (comment) {
        return $http
        .post('http://localhost:3000/comments', {message: comment})
    }
}])

layout.html

<div class="jumbotron">
    <div class="container">
        <div class="page-header" align="center">
          <h1>Haskell <small>+</small> Angular</h1>
        </div>
        <div class="well well-lg">
            <div class="row">
                <div class="col-lg-12">
                <form role="form" ng-submit="post()">
                        <legend>Post a comment</legend>
                        <div class="form-group">
                            <label for="">Message</label>
                            <input type="text" class="form-control" placeholder="Message" ng-model="message">
                        </div>
                        <button type="submit" class="btn btn-primary">Comment</button>
                    </form>
                </div>
            </div>
            <hr style="border: 2px solid steelblue">
            <div class="row">
                <div class="col-lg-6" ng-repeat="comment in comments">
                    <div class="panel panel-info">
                        <div class="panel-heading">
                            <h3 class="panel-title">Comment #{{ comment.id }}</h3>
                        </div>
                        <div class="panel-body">
                            {{ comment.message }}
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>

At this point we're all set up on the front-end. Now I had to configure the backend to serve just the one index.html from where angular can take over!

  • I edited the templates/default-layout-wrapper.hamlet and got rid of most of the default stuff

default-layout-wrapper.hamlet head

<!doctype html>
<html lang="en" ng-app="app">
  <head>
    <meta charset="UTF-8">

    <title>{{ title }}
    <meta name="description" content="">
    <meta name="author" content="">

    <meta name="viewport" content="width=device-width,initial-scale=1">

    <style link="rel" src="static/bower_modules/bootstrap/dist/css/bootstrap.min.css">

    ^{pageHead pc}

default-layout-wrapper.hamlet body

<body>
    <div class="container" ng-controller="HomeCtrl">
      <div ng-view>
    <script type="text/javascript" src="static/bower_modules/jquery/dist/jquery.min.js">
    <script type="text/javascript" src="static/bower_modules/bootstrap/dist/js/bootstrap.min.js">
    <script type="text/javascript" src="static/bower_modules/angular/angular.js">
    <script type="text/javascript" src="static/bower_modules/angular-route/angular-route.min.js">
    <script type="text/javascript" src="static/scripts/app.js">

Unfortunately Stackoverflow probably doesn't allow hamlet code snippets so I hade to separate it

Now when you go here you'll have a web app with an angular front-end powered by a yesod backend.


Things that might seem like magic

  1. Posting comments works without any backend code? Nope, it comes OTB :)

Hope I could make thinks clearer than they were!

like image 98
atefth Avatar answered Oct 17 '22 16:10

atefth