[LRUG] [JOBS] Help with AngularJS + Rails

Jasim A Basheer jasim.ab at gmail.com
Sat Jan 18 09:37:32 PST 2014


Thanks Mark, I'm very glad to hear that.


On Fri, Jan 17, 2014 at 4:09 PM, Mark Weston <mark at markweston.me.uk> wrote:

> As a spectator to this conversation, I just want to say thank you.  That
> was an awesome post.
>
>
> On 16 January 2014 18:41, Jasim A Basheer <jasim.ab at gmail.com> wrote:
>
>> Hi Diego,
>>
>> We're working on an Angular-Rails project and have made some mistakes and
>> learned a few things along the way. I cannot offer to do a paid and/or
>> onsite gig, but I'd be happy to help with any specific questions you might
>> have (thru LRUG, if it isn't terribly off-topic, or through email).
>>
>> To start with, here are a bunch of pointers in no particular order.
>> Apologies if you're already familiar with them. I'm also curious to hear
>> everybody's thoughts on this and hopefully learn something in the process.
>> General
>>
>>    -
>>
>>    Use Coffeescript.
>>    -
>>
>>    Start with Egghead.io. Read AngularJS by Brad Green, Shyam Seshadri (
>>    http://shop.oreilly.com/product/0636920028055.do). Spend a few hours
>>    and skim it cover-to-cover at least once. Angular documentation on the web
>>    is quite fragmented; a book is the best way to begin learning Angular.
>>    -
>>
>>    If you aren't familiar with Underscore.js already, go through it. Add
>>    it to the project as soon as you begin. You'll need it.
>>    -
>>
>>    Drop gem 'ng-rails-csrf' into your Gemfile to make Angular AJAX
>>    requests play well with Rails CSRF protection.
>>
>> Directives
>>
>>    -
>>
>>    Some material on the web might lead you to think that a deep
>>    understanding of Directives is crucial to ‘get’ Angular. But for a
>>    beginner, the important thing is to just have fun doing things the Angular
>>    way (it is loads of fun, let me assure you). The basic directives Angular
>>    provides (eg: ng-repeat and ng-show) can go a surprisingly long way. Worry
>>    about transclusion, $compile, $digest and other arcane incantations only
>>    after you get comfortable with the basics.
>>    -
>>
>>    One reason for you to write a directive is when you need to use
>>    existing widgets. A lot of popular widgets already have Angular wrappers.
>>    For example, we use the select2 wrapper
>>    https://github.com/angular-ui/ui-select2 for multi-select.
>>
>>    There is also AngularStrap (http://mgcrea.github.io/angular-strap/)
>>    with a bunch of nice components that wraps over Twitter Bootstrap. The v1 (
>>    http://mgcrea.github.io/angular-strap/1.0/) version has Datepicker
>>    and Timepicker which isn't available in the new version yet.
>>    -
>>
>>    You would hit a point when off-the-shelf directives won’t be enough
>>    and you'd want to do something that requires DOM manipulation. That would
>>    be a good time to start digging into Directives. You can also start
>>    exploring Directives when you start seeing patterns in your controllers
>>    that can be extracted out into reusable components.
>>
>> Serialization
>>
>>    -
>>
>>    We use ActiveModel::Serializer. It is a safe choice.
>>    -
>>
>>    Pay close attention to what you include in each serializer. Here is
>>    the obvious thing: you can put a bunch of has_many in your User
>>    model, and if you keep doing that for other objects, arender json:
>>    User.all will end up sending your entire db graph to the client.
>>
>> We implement a base serializer for most entities with some basic fields
>> and no associations. When we need more data/associations, we sub-class them
>> for that specific use case. Discourse follows a similar style -
>> https://github.com/discourse/discourse/tree/master/app/serializers
>>
>>    - Try not to do any decoration on the server side; it is tempting
>>    with all the view helper goodness Rails provides, but then you are
>>    splitting logic between client and server and it causes all sorts of
>>    headaches. You can use Angular filters as client-side helpers on some
>>    occasions. eg: {{ start_time | date }} prints out nicely formatted dates.
>>
>> Forms, validations and POJO form objects
>>
>>    -
>>
>>    Angular gives you validation attributes like ng-required,
>>    ng-minlength, ng-maxlength etc. on all form INPUTs. For simple use cases,
>>    this is the best way to go. But for a complex form, we ended up building an
>>    ActiveModel-like POJO Form Object to hold validation information, and for
>>    doing some slight transformations on the data before submission. We bound
>>    this object to the scope and removed a lot of clutter from our controllers
>>    that had begun to get bloated. We have to see how this pans out in the long
>>    run; I can give code snippets if you wish.
>>    -
>>
>>    When I introduced Angular to our Rails project that was already under
>>    development, I tried to minimize the amount of change required by trying to
>>    keep form POSTs as they were.
>>
>>    <input type='text' ng-model='post.title' name='post[title]'>
>>    <input type='submit' />
>>
>>
>> It worked for the most part, but we ran into some edge cases that wasted
>> a lot of time. That was when I learned to let go and fully embrace the
>> Angular way. We switched over to something like this:
>>
>> <input type='text' ng-model='post.title' name='post[title]'>
>> <button ng-click='submitForm()'>
>>
>> And the controller:
>>
>> angular.module('myApp').controller 'postCtrl', ['$log', '$scope', '$http', '$state', '$stateParams', '$window', 'flash', ($log, $scope, $http, $state, $stateParams, $window, flash) ->
>>   $scope.submitForm = ->
>>     $http.post(
>>       Routes.posts_path,
>>       $scope.post           # this is the ng-model hash we bound the input boxes to.
>>     ).success((data, status) ->
>>       $state.go("posts.show", {post_id: data.post.id})
>>     ).error((data, status) ->
>>       if data['message']
>>         flash.error = data['message']
>>       else
>>         flash.error = "Could not create Post. #{data}"
>>     )
>>
>>
>> # `flash`  is from https://github.com/wmluke/angular-flash.
>>
>> # $state.go is ui-router. https://github.com/angular-ui/ui-router. More on that later.
>>
>> # we use JSRoutes to access Rails routes from JS. Quite useful. https://github.com/railsware/js-routes/
>>
>> The above code isn't ideal. You will be better off putting the $http.post inside
>> an Angular service. This looks like a good post describing that:
>> http://sravi-kiran.blogspot.in/2013/03/MovingAjaxCallsToACustomServiceInAngularJS.html
>> .
>> Rails+Angular
>>
>>    -
>>
>>    Go all the way in. Don’t try to keep a mix of Angular and non-Angular
>>    pages. Except for Devise, all our pages are rendered client-side. We don't
>>    have to worry about SEO since this is an internal app.
>>    -
>>
>>    Use ui-router https://github.com/angular-ui/ui-router, not the one
>>    that comes with Angular. The ui-router README could be confusing, but like
>>    most of Angular documentaiton, it becomes obvious in hindsight if you
>>    persevere.
>>    -
>>
>>    We keep everything together in the same Rails project. Here is a
>>    rough structure:
>>
>>    routes.rb:
>>
>>    root ‘base#angular’
>>    resources :posts, only: [:index]
>>
>>    app/views/base/angular.html.erb:
>>
>>    <div ng-app='myApp'>
>>      <div ui-view></div>
>>    </div>
>>
>>    posts_controller.rb:
>>
>>      class PostsController < ApplicationController
>>        respond_to :json
>>
>>        def index
>>          render json: Post.all, root: false
>>        end
>>      end
>>
>>    app/assets/javascripts/routes.coffee.erb:
>>
>>    angular.module('myApp').run ($rootScope, $state, $stateParams) ->
>>      $rootScope.$state = $state
>>      $rootScope.$stateParams = $stateParams
>>
>>    angular.module('myApp').config ($stateProvider, $urlRouterProvider) ->
>>
>>      # The default route
>>      $urlRouterProvider.when("", "/posts/list")
>>
>>      $stateProvider
>>        .state("posts",
>>          url: "/posts"
>>          controller: "postsRootCtrl"
>>          template: '<div ui-view></div>'
>>          abstract: true
>>        ).state("posts.index",
>>          url: "/list"
>>          controller: "postsIndexCtrl"
>>          templateUrl: "<%= asset_path('posts/index.html.slim') %>"
>>          resolve:
>>            postsPromise: ($http) ->
>>              $http.get(Routes.posts_path())
>>        )
>>
>>    app/assets/javascripts/posts.js.coffee:
>>
>>    angular.module('myApp').controller 'postsCtrl', ['postsPromise', '$log', '$scope', '$http', '$state', '$stateParams', '$window', (postsPromise, $log, $scope, $http, $state, $stateParams, $window) ->
>>      $scope.posts = postsPromise.data
>>
>>    app/assets/templates/index.html.slim
>>
>>    div ng-repeat='post in posts'
>>      h1
>>        | {{post.title}}
>>      p
>>        | {{post.body}}
>>
>>
>> Quirks and bugs
>>
>>    -
>>
>>    It is worth keeping in mind that in JS {} == {} is false, since
>>    comparisons are by object identity, not by object value. You can do explict
>>    comparison using Angular.equals when you need it. But in some cases
>>    Angular does an implicit comparison, for which it doesn't use
>>    Angular.equals. For example, if you try loading a page with a bunch of
>>    pre-selected radio-buttons, if their values are JS Objects (Hash), Angular
>>    won't pre-select them since the comparison will fail. This is not an issue
>>    if your values are strings/numbers.
>>    -
>>
>>    JS treats the keys of all objects as strings.a={1: "Hello"};
>>    _.each(a, (value, key) -> console.log(typeof key)). If your server
>>    sends Hashes whose keys are numeric ids, they'll end up in JS with string
>>    ids.
>>    -
>>
>>    A quick and dirty way to inspect an Angular model is to drop this in
>>    your Angular view template:
>>
>>    pre
>>      | {{ post | json }}
>>
>>    -
>>
>>    There is also Batarang, a Chrome plugin which lets you inspect your
>>    scope interactively. But I've found it to causes my pages to misbehave in
>>    certain cases. I these days use angular-chrome-debug, (
>>    https://gist.github.com/mzgol/7893061) which is quite light-weight. I
>>    load this script in my app in development env so that I don't have to go
>>    thru Chrome Snippets everytime.
>>    -
>>
>>    Angular has Angular.copy for deep copy when you need it -
>>    https://github.com/angular/angular.js/blob/master/src/Angular.js#L725.
>>
>> Well, that's mostly it. Please keep in mind that the structure I
>> described is working well for our app, which is not meant for public
>> consumption. You might want to look at keeping your Angular and Rails
>> project separately and utilize JS build tools like Bower, Yeoman and Grunt
>> if your app needs that.
>> I'm generally liking building rich UIs with Angular and wouldn't go back
>> to building for the web without using data-binding. Awesome sauce.
>>
>>  --
>> Jasim A Basheer — Nilenso Software — http://nilenso.com
>> http://twitter.com/jasim_ab
>>
>>
>>
>>
>> On 16-Jan-2014, at 6:20 am, Matthew Rudy Jacobs <
>> matthewrudyjacobs at gmail.com> wrote:
>>
>> On 15 January 2014 14:38, Matt Spendlove <matt at cenatus.org> wrote:
>>
>>>
>>> I’ve never used the service but these guys seem to be trying to solve
>>> your kind of request:
>>>
>>> https://www.airpair.com
>>>
>>
>> Also check out CodeMentor, who do something similar.
>> https://www.codementor.io/directory/angularjs
>>   _______________________________________________
>> Chat mailing list
>> Chat at lists.lrug.org
>> http://lists.lrug.org/listinfo.cgi/chat-lrug.org
>>
>>
>>
>> _______________________________________________
>> Chat mailing list
>> Chat at lists.lrug.org
>> http://lists.lrug.org/listinfo.cgi/chat-lrug.org
>>
>>
>
> _______________________________________________
> Chat mailing list
> Chat at lists.lrug.org
> http://lists.lrug.org/listinfo.cgi/chat-lrug.org
>
>


-- 
Jasim A Basheer -- http://twitter.com/jasim_ab
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.lrug.org/pipermail/chat-lrug.org/attachments/20140118/3ee9ffba/attachment.html>


More information about the Chat mailing list