[LRUG] [JOBS] Help with AngularJS + Rails
David Burrows
david at designsuperbuild.com
Mon Jan 20 01:55:56 PST 2014
Jasim, just catching up on this thread, thanks for the extensive notes,
massively useful - you should put this up somewhere (blog, company site,
whatever) people ask me about Rails/Angular all the time and would love to
be able to point them at this.
Thanks,
David
--
David Burrows
079 1234 2125
@dburrows
http://www.designsuperbuild.com/ | @dsgnsprbld
On 18 January 2014 13:51, Iain Smith <iain at mountain23.com> wrote:
> Wow, great post. Thanks for sharing.
>
> Iain
>
> On 17 Jan 2014, at 10:39, 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
>
>
> _______________________________________________
> Chat mailing list
> Chat at lists.lrug.org
> http://lists.lrug.org/listinfo.cgi/chat-lrug.org
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.lrug.org/pipermail/chat-lrug.org/attachments/20140120/3aa95d4a/attachment-0003.html>
More information about the Chat
mailing list