14

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.

Damian Nadales
  • 4,907
  • 1
  • 21
  • 34
  • Did you see Michael Snoyman's repository: https://github.com/snoyberg/yesod-js ? – Sibi Nov 17 '14 at 11:27
  • Yes. That's one of the "inaccessible to me" resources I referred to. I need something in a tutorial format given my limited understanding of Yesod. I wouldn't want to end up with a solution I don't understand. That's why I though about taking the "shortcut" I described. – Damian Nadales Nov 17 '14 at 12:05
  • 1
    See this: http://blog.wuzzeb.org/posts/2015-02-04-yesod-angular-bower.html – Fábio Roberto Teodoro Jun 27 '17 at 14:58

1 Answers1

11

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 <atefth@gmail.com>"
  ],
  "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!

atefth
  • 1,624
  • 13
  • 26
  • Also, if you want angular routing to function properly you will need to make a catch all route in `config/routes` that serves this `default-layout-wrapper.hamlet`. Like the answer to this question: https://stackoverflow.com/q/31011940/7941330 – Fábio Roberto Teodoro Jun 27 '17 at 01:51
  • 1
    there's this post too: http://blog.wuzzeb.org/posts/2015-02-04-yesod-angular-bower.html – Fábio Roberto Teodoro Jun 27 '17 at 15:00
  • Now that I read the post, I think I certainly understand a bit more than when I answered the question! – atefth Sep 12 '17 at 07:37