Av: Kim Lodfeldt

2013-04-03

Angular vs. Backbone vs. Ember

Som frontend-utvecklare hör man ofta talas om olika JavaScript-baserade ramverk vars syfte är att förbättra användarupplevelsen genom att möjliggöra snabba och dynamiska gränssnitt. Dessa ramverk har som sagt liknande syfte och använder i regel någon sorts MVC-tänk för att strukturera koden. Exempel på sådana ramverk är Angular.js, Backbone.js, Ember.js, Knockout.js och Meteor.js.

Det blir ju mer eller mindre omöjligt att hänga med och “lära sig” ALLA nya JS-ramverk men jag ville testa några av de mer populära ramverken och försöka bilda mig en uppfattning om dess styrkor och svagheter samt vad de passar till. Mitt tillvägagångssätt har varit att beta av ett ramverk i taget och börja med tutorials/screencasts/läsa dokumentation för att sedan skriva en “Todo”-applikation med liknande funktionalitet med hjälp av respektive ramverk. De tekniker jag valt att fokusera på är Angular, Backbone samt Ember. Alla dessa test-applikationer har jag embeddat i en Rails-baserad backend som tillhandahållit en databasmodell och en controller med ett simpelt JSON API enligt följande:

“`
class TodoItemsController < ApplicationController
respond_to :json

def index
respond_with TodoItem.all
end
def show
respond_with TodoItem.find(params[:id])
end

def create
respond_with TodoItem.create(params[:todo_item])
end

def update
respond_with TodoItem.update(params[:id], params[:todo_item])
end

def destroy
respond_with TodoItem.destroy(params[:id])
end
end
“`

Denna artikel är helt enkelt en sammanfattning av mina experiment med de olika ramverken samt hur jag upplevt att arbeta med dem och lite reflektion kring slutresultatet. De user stories jag arbetat mot att uppfylla med applikationerna var:

– Som användare kan jag lägga till en ny “Todo” i listan
– Som användare kan jag markera en “Todo” som klar
– Som användare kan jag ta bort klarmarkerade “Todos” ur listan

Grundtanken var även att använda fördelarna med ramverken så att alla ovanstående actions skall kunna genomföras snabbt, utan att sidan laddas om och att delar av sidan uppdateras dynamiskt efter ändringar. Ett ytterligare krav på appen är att all data skall sparas ned i databasen i bakgrunden.

## Angular.js
Det första jag slås av när jag sätter tänderna i Angular är hur otroligt lätt det är att komma igång. Då min applikation är en så kallad “single page application” har jag definierat upp en rot-URL för applikationen som pekar på en landningssida. Min landningssida är hittils endast en H1-tagg som säger “Todo list” och i detta skede tänker jag mig att det kommer bli betydligt fler rader kod och säkert massa nya filer att skapa innan jag har all funktionalitet på plats. Till skillnad från en del andra ramverk jag tittat på kände jag aldrig något behov att skapa tusentals filer utan eftersom att mkt av Angular-syntaxen hamnar i den vanliga HTML-templaten lade jag enbart till en JavaScript-fil förutom den landningssida jag redan hade. Det slutade med att hela implementationen blev ENDAST 40 rader kod, vilket enligt min mening är ofattbart bra.

Besök gärna projektet på Github om ni är nyfikna på koden! Appen är även uppe och snurrar på Heroku. Här följer ett kodexempel, vilket råkar vara ALL JavaScript som krävdes för att få klart hela appen:

“`
app = angular.module(“TodoList”, [“ngResource”])

function TodoCtrl($scope, $resource) {
TodoItemService = $resource(“/todo_items/:id”, {id: “@id”})
$scope.todos = TodoItemService.query()
$scope.addTodo = function () {
item = TodoItemService.save({title:$scope.formTodoText, done: false})
$scope.todos.push(item);
$scope.formTodoText = “”;
return false;
}

$scope.clearCompleted = function () {
var templist = [];
angular.forEach($scope.todos, function(item) {
if(!item.done)
templist.push(item);
else
TodoItemService.delete(item)
});

$scope.todos = templist;
}
}
“`

Högst upp definiterar jag en resurs som jag kan använda likt en service för att spara och uppdatera todo-items och få det att persistera till databasen via mitt JSON-API i Rails-appen. Sen definierar jag en Angular-controller som dels tar in ett scope som motsvarar dess omfattning i vyn där den används och slutligen även resursen. Sen har jag helt enkelt två självförklarande funktioner som använder sig av scopet och resursen för att lägga till och ta bort todos med hjälp utav funktionalitet som Angular tillhandahåller.

Fördelar som jag ser med Angular är att ramverket i sig är väldigt litet och smidigt. Jag har dessutom kunnat skriva hela applikationen helt utan jQuery, vilket kan vara skönt då man t ex slipper selektorer som krockar, risken med att någon tar bort en klass eller ett id som din jQuery-kod var beroende av samt att du slipper ladda in några fler JS-bibliotek utöver Angular.

Några “Gotchas” var dock att Angular inte lirar helt med CoffeeScript out of the box. Det kan dessutom bli problem i produktion om man exempelvis använder Rails och release-processen involverar minifiering av JavaScript-koden. En typisk minifiering ändrar nämligen variabelnamn och eftersom att Angular “injectar” serivices och liknande i funktioner kommer den inte längre känna till dessa services i den minifierade produktions-koden. Detta går att komma runt på olika sätt och jag valde att ändra minifieringsprocessen till att inte sätta om just variabelnamnen.

## Backbone.js
Även backbone upplevde jag likt Angular som ett relativt litet och smidigt ramverk även om själva strukturen skiljer sig en del. I Backbone har man en mappstruktur som påminner mycket om Rails och säkerligen andra MVC-baserade ramverk. Man har en mapp för modeller, en för routers, en för vad som i Backbone kallas views men som egentligen är controllers, en separat mapp för collections samt en folder för templates. Så redan när det var dags att definiera ett “todo”-item skapade jag enligt Backbones konvention en modell, en collection, en router, en vy-folder samt en template. Det blir med andra ord till skillnad från i Angular snabbt betydligt fler filer men samtidigt också en ganska tydlig uppdelning och en naturlig strukur i projektet.

Koden till mitt Backbone-exempel finns även den uppe på Github. Den Backbone-relaterade koden finns då under “app/assets/javascripts”. Jag har även i detta fall pushat upp applikationen till Heroku. Här följer som exempel en Backbone-view som hanterar själva landningssidan i min applikation:

“`
class TodoListWithBackbone.Views.TodoItemsIndex extends Backbone.View

template: JST[‘todo_items/index’]

events:
‘submit #new_todo_item’: ‘createTodoItem’,
‘click #clear-todo-items’: ‘clearTodoItems’

initialize: ->
@collection.on(‘reset’, this.render, this)
@collection.on(‘add’, @appendTodoItem, this)

render: ->
$(@el).html(@template())
@collection.each(@appendTodoItem)
this

appendTodoItem: (todo_item) ->
view = new TodoListWithBackbone.Views.TodoItem(model: todo_item)
$(‘#todo-items’).append(view.render().el)

clearTodoItems: (event) ->
doneItems = @collection.where({done: true})
@collection.remove(doneItems)
$(“#todo-items > li.done”).each (index, element) ->
$(element).slideUp()
false

createTodoItem: (event) ->
attributes = name: $(‘#new_todo_item_name’).val()
@collection.create attributes,
wait: true
success: ->
$(‘#new_todo_item’)[0].reset()
false
“`

Först definiterar man vilken template som skall renderas av vyn, därefter binder jag upp några events som vyn ska lyssna på vilket i detta fall dels är ett submit-event från ett skapa-formulär samt ett click-event på en knapp som ska rensa alla färdiga todos. I initialize-blocket binder jag upp ett par lyssnare på kollektionen som lyssnar på *reset” och “add” i detta fall och kör en viss funktion om eventen triggas. Jag definierar även vad som ska hända när vyn kör sin render-funktion samt slutligen några funktioner som vyn använder sig av.

Till skillnad från i Angular-exemplet känns Backbone mer integrerat med jQuery och jag tycker att det känns naturligare att blanda Backbone-relaterad kod med exempelvis jQuery-selectors. Sen påminner själva API:t i Backbone om just jQuery med exempelvis .on(), off(), once() för att hantera events på ett objekt.

För att hantera collections använder Backbone förutom sitt egna API även Underscore. Det är ett JavaScript-bibliotek som tillhandahåller massa praktiska metoder som påminner om de man använder i exempelvis Ruby. Jag är själv ett fan av metoder som each, map, pluck etc. inom Ruby-världen så det känns smidigt att jag kan använda samma tänk även i Backbone.

Slutligen för att jämföra lite med Angular.js tycker jag att Backbone tog lite längre tid att komma igång med och det blev helt klart fler filer, fler rader kod och lite rörigare att hålla koll på var man stoppat vilken kod. När själva tänket började sätta sig tycker jag ändå att det finns ett tydligt tänk och jag tror att backbone med sin mappstruktur och sina filtyper lämpar sig bättre för mer omfattande appar än vad Angular gör. Anledningen till att jag får den känslan är för att Backbone implicit inbjuder till en viss strukturering av koden och föreslår att viss typ av kod hör hemma i en viss typ av fil. När jag arbetade med Angular kände jag att det bara var att “tuta och köra” i de få filer jag hade och att en eventuell strukturering av en större applikation mer ligger i utvecklarens händer.

## Ember.js
Ember är ett relativt nytt ramverk som släppte sin 1.0-version för ett par månader sedan. Då personer bakom Rails är med och utvecklar Ember är det något jag väntat på och velat testa och därför tog jag med Ember i mitt lilla Todo-experiment. Jag började med att kika på en 2-delad screencast och fick upp en hyfsad grundstomme för att kicka igång min Todo-app. En känsla jag fick direkt var att Ember var betydligt mer “heavy” än de ramverk jag nämnt ovan. Det kändes även tidigt som att inlärningskurvan var högre samt att hela strukturen kändes mer “enterprise” och därmed mer svårövergriplig.

Om den grundläggande mapp- och filstrukturen kändes liten och behändig i Angular, aningen mer utspridd i Backbone så blir Ember ytterligare ett snäpp i antal filer och mappar. Konventionen är mer eller mindre att varje action har en egen vy, en egen controller, en egen template osv osv, likt Backbones struktur men på en mer detaljerad nivå. Som exempel kan det bli en vy, en controller och en template för att skapa ett nytt item, sen blir det en ny vy, en ny template och en ny controller för att ta bort ett item osv. Sen vet jag inte om den approachen är “best practice” eller “the way to do it” men det var så det presenterades i screencasten jag följde inledningsvis.

Likt Angular arbetar man annars med 2-way bindings vilket ger kraftfullt dynamiska vyer som är beredda på att snabbt uppdatera sig om data ändras. Jag har däremot inte lagt upp någon kod på Github eller skickat upp någon app på Heroku då jag endast lyckades implementera “lägg till en ny todo”. Jag satt och laborerade lite med övriga features men tyckte helt enkelt att det tog för lång tid för att jag skulle hinna med allt inom motsvarande tid som jag hade gjort det med Angular och Backbone.

## Slutsats
Sammanfattningsvis kring alla tre ramverken är mitt bestående intryck att Ember har en högre inlärningskurva än de övriga. Detta beror främst på jag upplever att det är sämre dokumentation, tar längre tid att greppa strukturen/konventionen och att applikationskoden är mer svåröverskådligt till en början. Om mitt intryck var att Angular passade bra i mindre appar/projekt och att Ember passar bra i stora mer omfattande projekt så hamnar Backbone som någon sorts hybrid däremellan. Egentligen blir det någon sorts tripp, trapp, trull-gradering för dessa 3 ramverk både sett till inlärningskurva, storleken på appar de lämpar sig till och hur bra det gick för mig att implementera min Todo-applikation både sett till tid och total kodmängd.

Slutligen känner jag efter mitt experiment att jag absolut kan tänka mig att använda både Angular.js och Backbone.js igen. Vilket jag skulle välja att arbeta med kommer jag nog basera mycket på projektets omfattning och strutur. Ember.js känns det däremot som att jag kommer att dra mig för att använda igen om det inte skulle visa sig vara väldigt passande för ett specifikt projekt. Om man bortser från vilket av alla dessa liknande ramverk man väljer tycker jag helt klart att det finns fördelar med många av dem rent gränssnittsmässigt. Att få till ett snabbare gränssnitt som inte behöver ladda om hela sidan eller vänta på långsamma svar från servern bidrar helt klart till en bättre användarupplevelse. Att dessutom få en mer strukturerad JavaScript-kod “på köpet” ser jag som en trevlig bonus!

//Kim Lodfeldt

#AngularJs #Backbone.js #Ember.js

Så här håller du dig uppdaterad inom webbutveckling

JavaScript-tips