Нещодавно мені довелося пірнути в AngularJS на сайті Rails, який ми будували для клієнта. Крім вмісту сторінок, клієнт має певні сторінки, які відображають заголовки, що посилаються на зовнішні джерела, але завантажуються у наш стек Rails за допомогою періодичної задачі rake. Сайт повинен бути сильно кешований через використання CDN, наприклад, CloudFront, але щоб заголовки залишалися свіжими, ми використовували AngularJS (тому що клієнт був знайомий з ним, тому простіше в обслуговуванні для них), щоб робити запити до стеку rails безпосередньо для останніх заголовків.
Це вимагало робити речі в міждоменному режимі, оскільки домен сайту мав би вказувати на CloudFront. Я ще не писав API Rails, який повертав JSONP або сервіс AngularJS, тому мені довелося дослідити це, і це працювало для мене. (Ви також можете прочитати гіст.)
Примітка: Якщо ви не знайомі з JSONP, це не матиме багато сенсу. Прочитайте це.
Сервіс AngularJS
У AngularJS є концепція сервісів, які є просто синглтонами, які можна використовувати де завгодно. У нашому випадку ви можете уявити наш сервіс заголовків як API-клієнт заголовків, який обгортає наші виклики JSONP у простіший і централізований синтаксис. AngularJS надає деякі сервіси за замовчуванням, але ви можете писати власні. Насправді, ми могли б використовувати вбудований $resource
сервіс замість написання власного, однак у мене були деякі проблеми з роботою JSONP, і я хотів краще зрозуміти компоненти нижчого рівня AngularJS, тому я написав свій:
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)
}
}
}
]);
Сервіс вище використовує вбудований $http
сервіс, який просто надає обгортку AJAX, щоб робити запити до нашого бекенду rails для отримання заголовків. Декілька речей, на які варто звернути увагу:
- Він повертає об’єкт з 2 функціями:
getHeadlines
таgetHeadlineForId
. Ми можемо використовувати ці функції як скорочення для зв’язку з API Rails у наших контролерах AngularJS. - Він використовує JSONP. Поміть, що ми використовуємо
$http.jsonp
і передаємо рядок запиту?callback=JSON_CALLBACK
. AngularJS замінює рядокJSON_CALLBACK
назву функції зворотного виклику, яку він створює для вас. - Він використовує інший домен. Ну, це очікувано, оскільки ми робимо JSONP, але ми досягаємо цього, додавши розширення
.erb
до файлу та використовуючи те, що ми встановили дляApp.settings.api_base_url
у додатку Rails як домен, з яким повинен спілкуватися AngularJS. Пам'ятайте, що це для обходу кешувального шару CDN, щоб наші заголовки завжди були найсвіжішими.
Контролер AngularJS
З побудованим HeadlinesService
ми тепер можемо використовувати 2 функції, які він повертає, у нашому додатку AngularJS:
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('щось пішло не так при отриманні заголовку');
});
}).error(function(error, status, headers, config){
alert('щось пішло не так при отриманні всіх заголовків');
});
}
]);
Для прикладу я просто отримую повний об’єкт заголовків для першого заголовку, повернутого в успішному колбеку getHeadlines
.
Об’єкт $scope
доступний для переглядів у AngularJS, тому ми можемо відобразити заголовки на сторінці, маючи перегляд, наприклад:
<section class="headline_section" ng-controller="HeadlinesCtrl">
<h3>Ось деякі заголовки...</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
Тепер, коли ми заставили AngularJS звертатися до нашого бекенду Rails, нам потрібно написати бекенд Rails! Наступний контролер є досить простим рубі на рейлзах, однак для повернення JSONP замість старого доброго JSON ми використовуємо опцію callback
у методі render
. Це говорить Rails, що ми використовуємо JSONP, і щоб обгорнути дані JSON, які ми йому надали, в цей колбек і правильно встановити заголовок Content-Type
, щоб все відповідало стандарту JSONP, і наш клієнт AngularJS міг обробляти дані.
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]