Recentelijk moest ik duiken in AngularJS op een Rails-site die we voor een klant aan het bouwen waren. Naast pagina-inhoud heeft de klant bepaalde pagina's die koppen weergeven die verwijzen naar externe bronnen, maar die in onze Rails-stack worden opgehaald via een rake-taak die periodiek wordt uitgevoerd. De site moet zwaar worden gecached via een CDN, zoals CloudFront, maar om de koppen vers te houden, hebben we AngularJS gebruikt (omdat de klant ermee bekend was, dus eenvoudiger onderhoud voor hen) om de rails-stack rechtstreeks te vragen naar de nieuwste koppen.
Dit vereiste dingen op een cross-domain-manier te doen aangezien het domein van de site naar CloudFront zou verwijzen. Ik had nog nooit een Rails-API geschreven die JSONP of een AngularJS-service retourneerde, dus moest ik een beetje verkennen en dit is wat voor mij werkte. (Je kunt ook de gist lezen.)
Opmerking: Als je niet bekend bent met JSONP, maakt dit niet veel zin. Lees dit.
AngularJS Service
AngularJS heeft het concept van services, die gewoon singletons zijn die overal kunnen worden gebruikt waar je ze nodig hebt. In ons geval kun je aan onze koppenservice denken als de koppen-API-client die onze JSONP-aanroepen inbouwt in een eenvoudigere en gecentraliseerde syntax. AngularJS biedt enkele services standaard, maar je kunt je eigen schrijven. In feite, we hadden de ingebouwde $resource
service kunnen gebruiken in plaats van onze eigen te schrijven, maar ik had moeite om JSONP aan de praat te krijgen en ik wilde de lagere AngularJS-componenten beter begrijpen, dus schreef ik mijn eigen:
angular.module('app')
.factory('HeadlineService', ['$http',
function($http) {
'use strict';
var BASE_URL = "<%= [App.settings.api_base_url, '/services/headlines'].join %>",
CALLBACK_STRING = "?callback=JSON_CALLBACK";
return {
getHeadlines: function(){
return $http.jsonp(BASE_URL + CALLBACK_STRING)
},
getHeadlineForId: function(id){
return $http.jsonp(BASE_URL + "/" + id + ".json" + CALLBACK_STRING)
}
}
]);
De bovenstaande service maakt gebruik van de ingebouwde $http
service, die gewoon een AJAX-wrapper biedt, om aanvragen te doen aan onze rails-back-end om koppen te verkrijgen. Een paar dingen om op te merken:
- Het retourneert een object met 2 functies:
getHeadlines
engetHeadlineForId
. We kunnen deze functies vervolgens gebruiken als snelkoppelingen om te praten met de Rails-API in onze AngularJS-controllers. - Het gebruikt JSONP. Let op dat we
$http.jsonp
gebruiken en dat we een querystring?callback=JSON_CALLBACK
doorgeven. AngularJS vervangt de stringJSON_CALLBACK
door de naam van een callback-functie die het voor je maakt. - Het gebruikt een ander domein. Dat is te verwachten aangezien we JSONP doen, maar we realiseren dat door een
.erb
-extensie aan het bestand toe te voegen en te gebruiken wat we instellen voorApp.settings.api_base_url
in de Rails-app als het domein waarmee AngularJS moet praten. Vergeet niet dat dit is om de CDN-cachinglaag te omzeilen zodat onze koppen altijd de meest recente zijn.
AngularJS Controller
Met onze HeadlinesService
gebouwd kunnen we nu de 2 functies die het retourneert, gebruiken in onze AngularJS-app:
angular.module('app')
.controller('HeadlinesCtrl', ['$scope', 'HeadlineService',
function($scope, HeadlineService) {
'use strict';
HeadlineService.getHeadlines().success(function(data, status, headers, config){
$scope.headlines = data;
HeadlineService.getHeadlineForId(data[0].id).success(function(data){
$scope.single_headline = data;
}).error(function(error, status, headers, config){
alert('er is iets misgegaan bij het ophalen van een kop');
});
}).error(function(error, status, headers, config){
alert('er is iets misgegaan bij het ophalen van alle koppen');
});
}
]);
Voor voorbeelddoeleinden haal ik gewoon het volledige kopobject op voor de eerste kop die wordt geretourneerd in de getHeadlines
succes-callback.
Het $scope
-object wordt blootgesteld aan de views in AngularJS, dus we kunnen de koppen op de pagina weergeven door een view te hebben zoals:
<section class="headline_section" ng-controller="HeadlinesCtrl">
<h3>Hier zijn enkele koppen...</h3>
<div class="headlines">
<ul>
<li ng-repeat="headline in headlines">
<a href="{{ headline.url }}" target="_none">{{ headline.title }}</a>
</li>
<ul>
</div>
<div class="headline">
<a href="{{ single_headline.url }}" target="_none">{{ single_headline.title }}</a>
</div>
</section>
Rails Backend
Nu we AngularJS hebben laten bellen naar onze Rails-back-end, moeten we de Rails-back-end schrijven! De volgende controller is vrij basis Ruby on Rails, maar om JSONP te retourneren in plaats van ouderwets JSON, gebruiken we de callback
-optie in de render
-methode. Dat vertelt Rails dat we JSONP gebruiken en dat we de JSON-data die we hebben gegeven, moeten inbouwen in die callback en de Content-Type
-header correct moeten instellen zodat alles voldoet aan de JSONP-standaard en onze AngularJS-client de data kan verwerken.
class Services::HeadlinesController < ApplicationController
def index
page = (params[:page] || 1).to_i
per = (params[:per] || 5).to_i
offset = (page - 1) * per
@headlines = Headline.where(:disabled => false).offset(offset).limit(per).to_a
render :json => @headlines.to_json, :callback => params[:callback]
end
def show
@headline = Headline.find(params[:id])
if @headline.present?
render :json => @headline.to_json, :callback => params[:callback]
else
render :json => {error: 'geen kop gevonden'}.to_json, :callback => params[:callback], :status => 404
end
end
end
En daar hebben we het! Een werkende JSONP-gebaseerde AngularJS-app met een Rails-back-end.
Hier is de volledige gist als je het op die manier wilt lezen. Als dit je geholpen heeft of als je vragen hebt, laat het ons weten op Twitter op @dojo4.