Новички в Ангуляре часто путаются из-за того, что быстрые функции $http-сервиса (напр.,
То можно обнаружить, что следующий точно такой же не работает из коробки в Ангуляре:
Проблема, с которой можно столкнуться, в том, что сервер не может получить параметры
Различие в том, как Джиквери и Ангуляр сериализуют и передают данные. В основном, проблема заключается в языке, на котором написана серверная часть и который просто не понимает передачу Ангуляра с настройками по-умолчанию — чертовски обидно, потому что Ангуляр, конечно, не делает ничего плохого. По умолчанию, Джиквери передает данные с использованием
К счастью, разработчики Ангуляра позаботились о поддержке такого метода $http-сервисом, чтобы установить
Примечание: Большой фрагмент выше необходимо всегда использовать с указанием функции
Просто используйте приведенный выше фрагмент и всё будет в порядке!
Теперь можно двигаться дальше, используя
HTML-шаблон
Клиентский код (AngularJS)
Серверный код (PHP)
Другие примечания
Напрашивается вопрос, можно ли всё-таки в PHP прочитать JSON-запрос Ангуляра? Ну, конечно, если читать входной поток PHP и декодировать JSON:
Очевидный недостаток в том, что код немного менее интуитивен (мы привыкли к $_POST, в конце концов), и если серверные обработчики уже написаны с использованием $_POST, то придется переписывать серверный код. Если используется хороший фреймворк, вероятно, можно осуществить глобальные изменения, так, чтобы обработчик входа прозрачно определял JSON запросы, но я отвлекся.
Примечание переводчика: Если есть возможность, лучше настроить сервер так, чтобы он принимал
Добавка. Дополнение от nervgh
$http.post()
) не взаимозаменяемы с эквивалентными функциями Джиквери (напр., jQuery.post()
), не смотря на то, что соответствующие руководства описывают их использование схожим образом. То есть, если код в Джиквери до этого имел вид:(function($)
{
jQuery.post('/endpoint', { foo: 'bar' }).success(function(response)
{
// ...
});
})(jQuery);
То можно обнаружить, что следующий точно такой же не работает из коробки в Ангуляре:
var MainCtrl = function($scope, $http)
{
$http.post('/endpoint', { foo: 'bar' }).success(function(response)
{
// ...
});
};
Проблема, с которой можно столкнуться, в том, что сервер не может получить параметры
{ foo: 'bar' }
из запроса Ангуляра.Различие в том, как Джиквери и Ангуляр сериализуют и передают данные. В основном, проблема заключается в языке, на котором написана серверная часть и который просто не понимает передачу Ангуляра с настройками по-умолчанию — чертовски обидно, потому что Ангуляр, конечно, не делает ничего плохого. По умолчанию, Джиквери передает данные с использованием
Content-Type: x-www-form-urlencoded
и привычной строки foo=bar&baz=moe
. Ангуляр, однако, передает данные с использованием Content-Type: application/json
и { "foo": "bar", "baz": "moe" }
строки JSON, которую, к сожалению, некоторые серверные языки — особенно РНР — изначально не преобразуют в объект.К счастью, разработчики Ангуляра позаботились о поддержке такого метода $http-сервисом, чтобы установить
x-www-form-urlencoded
для всех наших передач. Существует множество решений, предлагаемых на форумах и StackOverflow, но они не идеальны, поскольку требуют либо изменить код сервера, либо схему использования $http. Поэтому представляю вам наилучшее возможное решение, которое не требует изменений ни серверного, ни клиентского кода, а предлагает внести некоторые незначительные изменения в работу $http в настройках модуля приложения:// Корневой модуль приложения ...
angular.module('MyModule', [], function($httpProvider)
{
// Используем x-www-form-urlencoded Content-Type
$httpProvider.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=utf-8';
// Переопределяем дефолтный transformRequest в $http-сервисе
$httpProvider.defaults.transformRequest = [function(data)
{
/**
* рабочая лошадка; преобразует объект в x-www-form-urlencoded строку.
* @param {Object} obj
* @return {String}
*/
var param = function(obj)
{
var query = '';
var name, value, fullSubName, subValue, innerObj, i;
for(name in obj)
{
value = obj[name];
if(value instanceof Array)
{
for(i=0; i<value.length; ++i)
{
subValue = value[i];
fullSubName = name + '[' + i + ']';
innerObj = {};
innerObj[fullSubName] = subValue;
query += param(innerObj) + '&';
}
}
else if(value instanceof Object)
{
for(subName in value)
{
subValue = value[subName];
fullSubName = name + '[' + subName + ']';
innerObj = {};
innerObj[fullSubName] = subValue;
query += param(innerObj) + '&';
}
}
else if(value !== undefined && value !== null)
{
query += encodeURIComponent(name) + '=' + encodeURIComponent(value) + '&';
}
}
return query.length ? query.substr(0, query.length - 1) : query;
};
return angular.isObject(data) && String(data) !== '[object File]' ? param(data) : data;
}];
});
Примечание: Большой фрагмент выше необходимо всегда использовать с указанием функции
param()
. НЕ используйте вместо этого jQuery.param()
; что вызовет хаос, когда попытаетесь воспользоваться $resource Ангуляра, поскольку jQuery.param()
будет отклонять каждый, переданный ему, метод класса $resource! (Это особенность JQuery из-за которой свойства объекта для параметризации, содержащие функции, вызываются и возвращаемое ими значение используется в качестве параметризованного, но в случае с Ангуляром это может быть губительно, так как мы обычно передаем «реальные» экземпляры объектов с методами и т.д.)Просто используйте приведенный выше фрагмент и всё будет в порядке!
Теперь можно двигаться дальше, используя
$http.post()
и другие подобные методы, предполагая, что существующий серверный код ожидает x-www-form-urlencoded
данные. Вот несколько примеров конечного результата для обыденного, законченного кода (пример того, на что вы надеялись и мечтали):HTML-шаблон
<div ng-app="MyModule" ng-controller="MainCtrl">
<p ng-show="loading">Loading...</p>
<p ng-hide="loading">Response: {{response}}</p>
</div>
Клиентский код (AngularJS)
var MainCtrl = function($scope, $http)
{
$scope.loading = true;
$http.post('/endpoint', { foo: 'bar' }).success(function(response)
{
$scope.response = response;
$scope.loading = false;
});
};
Серверный код (PHP)
< ?
header('Content-Type: application/json');
// Та-дам! $_POST теперь нормальный; PHP без проблем
// преобразует запросы Ангуляра в объекты
echo json_encode($_POST);
?>
Другие примечания
Напрашивается вопрос, можно ли всё-таки в PHP прочитать JSON-запрос Ангуляра? Ну, конечно, если читать входной поток PHP и декодировать JSON:
< ?
$params = json_decode(file_get_contents('php://input'));
// или так, если нужно преобразовать в PHP массив (прим. переводчика)
$params = json_decode(trim(file_get_contents('php://input')), true);
?>
Очевидный недостаток в том, что код немного менее интуитивен (мы привыкли к $_POST, в конце концов), и если серверные обработчики уже написаны с использованием $_POST, то придется переписывать серверный код. Если используется хороший фреймворк, вероятно, можно осуществить глобальные изменения, так, чтобы обработчик входа прозрачно определял JSON запросы, но я отвлекся.
Примечание переводчика: Если есть возможность, лучше настроить сервер так, чтобы он принимал
Content-Type: application/json
, что более логично. В статье описывается подход для крайних случаев, когда на сервере ничего нельзя поделать.Добавка. Дополнение от nervgh
'use strict';
(function() {
angular.extend( angular, {
toParam: toParam
});
/**
* Преобразует объект, массив или массив объектов в строку,
* которая соответствует формату передачи данных через url
* Почти эквивалент [url]http://api.jquery.com/jQuery.param/[/url]
* Источник [url]http://stackoverflow.com/questions/1714786/querystring-encoding-of-a-javascript-object/1714899#1714899[/url]
*
* @param object
* @param [prefix]
* @returns {string}
*/
function toParam( object, prefix ) {
var stack = [];
var value;
var key;
for( key in object ) {
value = object[ key ];
key = prefix ? prefix + '[' + key + ']' : key;
if ( value === null ) {
value = encodeURIComponent( key ) + '=';
} else if ( typeof( value ) !== 'object' ) {
value = encodeURIComponent( key ) + '=' + encodeURIComponent( value );
} else {
value = toParam( value, key );
}
stack.push( value );
}
return stack.join( '&' );
}
}());
'use strict';
// change default settings
var app = angular.module( 'app', [ ... ]);
app
.config( function( $httpProvider ) { // [url]http://habrahabr.ru/post/181009/[/url]
$httpProvider.defaults.headers.post[ 'Content-Type' ] = 'application/x-www-form-urlencoded;charset=utf-8';
$httpProvider.defaults.transformRequest = function( data ) {
return angular.isObject( data ) && String( data ) !== '[object File]' ? angular.toParam( data ) : data;
};
});