Starting a new project with a angularjs client app and a flask app providing the api. I'm using mongodb as the database. I had to immediately rule out jsonp since I would need the ability to POST across different ports. So we have localhost:9000 for the angular app and localhost:9001 for the flask app.
I went through and made the changed needed for CORS in my API as well as my angular files. See source below. First issue I ran in to was that there is a bug that CORS allow header does not recognize localhost in Chrome. I updated my hosts file so I could use moneybooks.dev and this worked for my GET requests without using JSONP.
Now, to the issues I'm facing. When submitting a POST request, its stating Origin http://moneybooks.dev:9000 is not allowed by Access-Control-Allow-Origin What? GET can go through but POST is declined. I see the request come through to flask but it returns HTTP 400. I need help making POST requests work.
Another issue, which may be related, is that on my GET requests, sometimes the GET request doesn't fire at all. Like in BudgetCtrl the loadBudget function. On #/budgets/budgetID the name of the budget will sometimes not load at all. I check the flask log and don't see a request coming through. Then I click refresh, I see the request, the budget name appears on the page however in the flask log I see an error. [Errno 10053] An established connection was aborted by the software in your host machine. Its a connection error that only appears in the flask log when the GET request succeeds.
Are these issues related? Can anyone see what I'm doing wrong?
app.js
'use strict';
angular.module('MoneybooksApp', ['ui.bootstrap', 'ngResource'])
.config(['$routeProvider', '$httpProvider', function ($routeProvider, $httpProvider) {
$httpProvider.defaults.useXDomain = true;
delete $httpProvider.defaults.headers.common['X-Requested-With'];
$routeProvider
.when('/', {
templateUrl: 'views/main.html',
controller: 'MainCtrl'
})
.otherwise({
redirectTo: '/'
});
}]);
budgets.js
'use strict';
angular.module('MoneybooksApp')
.config(['$routeProvider', function ($routeProvider) {
$routeProvider
.when('/budgets', {
templateUrl: 'views/budgets-list.html',
controller: 'BudgetListCtrl'
})
.when('/budgets/:budgetID', {
templateUrl: 'views/budget.html',
controller: 'BudgetCtrl'
});
}])
.controller('BudgetListCtrl', function ($scope, $http, $resource) {
$scope.budgets = [];
var init = function () {
$scope.loadBudgets();
}
$scope.loadBudgets = function() {
$http.get('http://moneybooks.dev:9001/api/budgets')
.success(function (data) {
$scope.budgets = data;
})
.error(function (data) {
console.error(data);
});
};
init();
})
.controller('BudgetCtrl', function ($scope, $http, $routeParams, $resource) {
$scope.budget = {};
var init = function () {
$scope.loadBudget();
};
$scope.loadBudget = function() {
$http.get('http://moneybooks.dev:9001/api/budgets/'+$routeParams['budgetID'])
.success(function (data) {
$scope.budget = data;
})
.error(function (data) {
console.error(data);
});
};
init();
})
.controller('TransactionCtrl', function ($scope, $http, $routeParams, $resource) {
$scope.transactions = [];
$scope.editing = false;
$scope.editingID;
var init = function () {};
$scope.syncUp = function () {
$http.post('http://moneybooks.dev:9001/api/budgets/'+$routeParams['budgetID']+'/transactions', {transactions: $scope.transactions});
};
$scope.syncDown = function () {
$http.get('http://moneybooks.dev:9001/api/budgets/'+$$routeParams['budgetID']+'/transactions')
.success(function (transactions) {
$scope.transactions = transactions;
});
};
$scope.add = function() {
$scope.transactions.push({
amount: $scope.amount,
description: $scope.description,
datetime: $scope.datetime
});
reset();
$scope.defaultSort();
};
$scope.edit = function(index) {
var transaction = $scope.transactions[index];
$scope.amount = transaction.amount;
$scope.description = transaction.description;
$scope.datetime = transaction.datetime;
$scope.inserting = false;
$scope.editing = true;
$scope.editingID = index;
};
$scope.save = function() {
$scope.transactions[$scope.editingID].amount = $scope.amount;
$scope.transactions[$scope.editingID].description = $scope.description;
$scope.transactions[$scope.editingID].datetime = $scope.datetime;
reset();
$scope.defaultSort();
};
var reset = function() {
$scope.editing = false;
$scope.editingID = undefined;
$scope.amount = '';
$scope.description = '';
$scope.datetime = '';
};
$scope.cancel = function() {
reset();
};
$scope.remove = function(index) {
$scope.transactions.splice(index, 1);
if ($scope.editing) {
reset();
}
};
$scope.defaultSort = function() {
var sortFunction = function(a, b) {
var a_date = new Date(a['datetime']);
var b_date = new Date(b['datetime']);
if (a['datetime'] === b['datetime']) {
var x = a['amount'], y = b['amount'];
return x > y ? -1 : x < y ? 1 : 0;
} else {
return a_date - b_date
}
};
$scope.transactions.sort(sortFunction);
};
$scope.descriptionSuggestions = function() {
var suggestions = [];
return $.map($scope.transactions, function(transaction) {
if ($.inArray(transaction.description, suggestions) === -1){
suggestions.push(transaction.description);
return transaction.description;
}
});
};
$scope.dateSuggestions = function () {
var suggestions = [];
return $.map($scope.transactions, function(transaction) {
if ($.inArray(transaction.datetime, suggestions) === -1){
suggestions.push(transaction.datetime);
return transaction.datetime;
}
});
}
$scope.getRunningTotal = function(index) {
var runningTotal = 0;
var selectedTransactions = $scope.transactions.slice(0, index+1);
angular.forEach(selectedTransactions, function(transaction, index){
runningTotal += transaction.amount;
});
return runningTotal;
};
init();
$(function(){
(function($){
var header = $('#budget-header');
var budget = $('#budget');
var pos = header.offset();
$(window).scroll(function(){
if ($(this).scrollTop() > pos.top && header.css('position') == 'static') {
header.css({
position: 'fixed',
width: header.width(),
top: 0
}).addClass('pinned');
budget.css({
'margin-top': '+='+header.height()
});
} else if ($(this).scrollTop() < pos.top && header.css('position') == 'fixed') {
header.css({
position: 'static'
}).removeClass('pinned');
budget.css({
'margin-top': '-='+header.height()
});
}
});
})(jQuery);
});
});
API.py
from flask import Flask, Response, Blueprint, request
from pymongo import MongoClient
from bson.json_util import dumps
from decorators import crossdomain
from bson.objectid import ObjectId
try:
import json
except ImportError:
import simplejson as json
class APIEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, objectid.ObjectID):
return str(obj)
app = Flask(__name__)
client = MongoClient()
db = client['moneybooks']
api = Blueprint('api', __name__, url_prefix="/api")
#api.route('/budgets', methods=['GET', 'POST', 'OPTIONS'])
#crossdomain(origin='*', methods=['GET', 'POST', 'OPTIONS'], headers=['X-Requested-With', 'Content-Type', 'Origin'])
def budgets():
if request.method == "POST":
budget_id = db.budgets.insert({
'name': request.form['name']
})
budget_json = dumps(db.budgets.find_one({'_id': budget_id}), cls=APIEncoder)
if request.method == "GET":
budget_json = dumps(db.budgets.find(), cls=APIEncoder)
return Response(budget_json, mimetype='application/json')
#api.route('/budgets/<budget_id>', methods=['GET', 'OPTIONS'])
#crossdomain(origin='*', methods=['GET', 'OPTIONS'], headers=['X-Requested-With', 'Content-Type', 'Origin'])
def budget(budget_id):
budget_json = dumps(db.budgets.find_one({'_id': ObjectId(budget_id)}), cls=APIEncoder)
return Response(budget_json, mimetype='application/json')
#api.route('/budgets/<budget_id>/transactions', methods=['GET', 'POST', 'OPTIONS'])
#crossdomain(origin='*', methods=['GET', 'POST', 'OPTIONS'], headers=['X-Requested-With', 'Content-Type', 'Origin'])
def transactions(budget_id):
if request.method == "POST":
db.budgets.update({
'_id': ObjectId(budget_id)
}, {
'$set': {
'transactions': request.form['transactions']
}
});
budget_json = dumps(db.budgets.find_one({'_id': ObjectId(budget_id)}), cls=APIEncoder)
if request.method == "GET":
budget_json = dumps(db.budgets.find_one({'_id': ObjectId(budget_id)}).transactions, cls=APIEncoder)
return Response(budget_json, mimetype='application/json')
app.register_blueprint(api)
if __name__ == '__main__':
app.config['debug'] = True
app.config['PROPAGATE_EXCEPTIONS'] = True
app.run()
decorators.py
from datetime import timedelta
from flask import make_response, request, current_app
from functools import update_wrapper
def crossdomain(origin=None, methods=None, headers=None, max_age=21600, attach_to_all=True, automatic_options=True):
if methods is not None:
methods = ', '.join(sorted(x.upper() for x in methods))
if headers is not None and not isinstance(headers, basestring):
headers = ', '.join(x.upper() for x in headers)
if isinstance(max_age, timedelta):
max_age = max_age.total_seconds()
def get_methods():
if methods is not None:
return methods
options_resp = current_app.make_default_options_response()
return options_resp.headers['allow']
def decorator(f):
def wrapped_function(*args, **kwargs):
if automatic_options and request.method == 'OPTIONS':
resp = current_app.make_default_options_response()
else:
resp = make_response(f(*args, **kwargs))
if not attach_to_all and request.method != 'OPTIONS':
return resp
h = resp.headers
h['Access-Control-Allow-Origin'] = origin
h['Access-Control-Allow-Methods'] = get_methods()
h['Access-Control-Max-Age'] = str(max_age)
if headers is not None:
h['Access-Control-Allow-Headers'] = headers
return resp
f.provide_automatic_options = False
f.required_methods = ['OPTIONS']
return update_wrapper(wrapped_function, f)
return decorator
Edit
Output from chrome dev console.
Console:
XMLHttpRequest cannot load http://moneybooks.dev:9001/api/budgets/5223e780f58e4d20509b4b8b/transactions. Origin http://moneybooks.dev:9000 is not allowed by Access-Control-Allow-Origin.
Network
Name: transactions /api/budgets/5223e780f58e4d20509b4b8b
Method: POST
Status: (canceled)
Type: Pending
Initiator: angular.js:9499
Size: 13 B / 0 B
Latency: 21 ms
As #TheSharpieOne pointed out, the CORS error is likely a red herring caused by a Chrome Dev Tools bug. If it was an actual CORS issue, the pre-flight OPTIONS call should have returned the same error.
I believe your 400 error may be coming from request.form['transactions'] in the handler for the POST request. request.form is a MultiDict datastructure and according to the documentation at http://werkzeug.pocoo.org/docs/datastructures/#werkzeug.datastructures.MultiDict:
From Werkzeug 0.3 onwards, the KeyError raised by this class is also a subclass of the BadRequest HTTP exception and will render a page for a 400 BAD REQUEST if caught in a catch-all for HTTP exceptions.
I believe that if you check for the 'transactions' key in request.forms.keys(), you'll find that it does not exist. Note that the content type for the POST is application/json not x-www-form-urlencoded. According to the documentation at http://flask.pocoo.org/docs/api/#flask.Request.get_json, you'll want to get the request data using the request.get_json() function when the request mimetype is application/json.
Is the POST sending content ? I had a similar issuee when the body was null. If it is, adding either an empty body ("") when the object is falsy, or adding the ContentLength header as 0 both seemed to work.
$scope.syncUp = function () {
var objToSend = $scope.transactions ? { transactions: $scope.transactions } : "";
$http.post('http://moneybooks.dev:9001/api/budgets/'+$routeParams['budgetID']+'/transactions', objToSend);
};
Ensure app.js is included before budget.js in your HTML page
Related
I'm trying to display the results from a submitted form, AngularJS > PHP > Back but I'm getting nothing. I've tried a lot of different ways and according to all of google I'm doing it right but the console log just says that it's undefined.
Here is the submit function:
$scope.testProcessForm = function() {
$http({
method : 'POST',
url : 'test.php',
data : $scope.formData,
headers : {'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8'}
})
.success(function(data) {
if (data.errors) {
// Showing errors.
$scope.errorselectedServices = data.errors.selectedservices;
$scope.errorincEmail = data.errors.incemail;
} else {
$scope.submissionMessage = data.messageSuccess;
$scope.test= data.test;
The PHP:
$data['test'] = $test;
echo json_encode($data);
HTML:
<div ng-show="test">{{test}}</div>
Why am I getting "test is undefined" and no div? If I put an echo into PHP I get the proper reply back. It doesn't appear to hang anywhere in the code after some debugging. What am I doing wrong?
// app.js
// create our angular app and inject ngAnimate and ui-router
// =============================================================================
angular.module('formApp', ['ngAnimate', 'ngMessages', 'ui.router'])
// configuring our routes
// =============================================================================
.config(function ($stateProvider, $urlRouterProvider) {
$stateProvider
// route to show our basic form (/form)
.state('form', {
url: '/form',
templateUrl: 'form.html',
controller: 'formController'
})
// nested states
// each of these sections will have their own view
// url will be nested (/form/profile)
.state('form.tjanst', {
url: '/tjanst',
templateUrl: 'form-tjanster.html'
})
// url will be /form/interests
.state('form.epost', {
url: '/epost',
templateUrl: 'form-epost.html'
})
// url will be /form/payment
.state('form.fax', {
url: '/fax',
templateUrl: 'form-fax.html'
})
// url will be /form/payment
.state('form.sms', {
url: '/sms',
templateUrl: 'form-sms.html'
})
// url will be /form/payment
.state('form.mcl', {
url: '/mcl',
templateUrl: 'form-mcl.html'
})
// url will be /form/payment
.state('form.review', {
url: '/review',
templateUrl: 'form-review.html'
});
// catch all route
// send users to the form page
$urlRouterProvider.otherwise('/form/tjanst');
})
.value('formSteps', [
{uiSref: 'form.tjanst', valid: false},
{uiSref: 'form.epost', valid: false},
{uiSref: 'form.fax', valid: false},
{uiSref: 'form.sms', valid: false},
{uiSref: 'form.mcl', valid: false},
{uiSref: 'form.review', valid: false}
])
.run([
'$rootScope',
'$state',
'formSteps',
function($rootScope, $state, formSteps) {
// Register listener to watch route changes
$rootScope.$on('$stateChangeStart', function(event, toState, toParams, fromState, fromParams) {
var canGoToStep = false;
// only go to next if previous is valid
var toStateIndex = _.findIndex(formSteps, function(formStep) {
return formStep.uiSref === toState.name;
});
console.log('toStateIndex',toStateIndex)
if(toStateIndex === 0) {
canGoToStep = true;
} else {
canGoToStep = formSteps[toStateIndex - 1].valid;
}
console.log('canGoToStep', toState.name, canGoToStep);
// Stop state changing if the previous state is invalid
if(!canGoToStep) {
// Abort going to step
event.preventDefault();
}
});
}
])
// our controller for the form
// =============================================================================
.controller('formController', function($scope, $state, $http, formSteps) {
// we will store all of our form data in this object
$scope.formData = {};
$scope.submission = false;
$scope.formStepSubmitted=false;
$scope.formData.selectedServices = {};
$scope.messitServices = [{'name':'Fax', 'id':1}, {'name':'SMS', 'id':2}, {'name':'Minicall', 'id':3}];
$scope.someSelected = function (object) {
return Object.keys(object).some(function (key) {
return object[key];
});
};
var nextState=function(currentState) {
switch (currentState) {
case 'form.tjanst':
return 'form.epost'
break;
case 'form.epost':
return 'form.fax'
break;
case 'form.fax':
return 'form.sms'
break;
case 'form.sms':
return 'form.mcl'
break;
case 'form.mcl':
return 'form.review'
break;
default:
alert('Did not match any switch');
}
};
var updateValidityOfCurrentStep=function(updatedValidity) {
var currentStateIndex = _.findIndex(formSteps, function(formStep) {
return formStep.uiSref === $state.current.name;
});
formSteps[currentStateIndex].valid = updatedValidity;
};
$scope.goToNextSection=function(isFormValid) {
console.log('isFormValid ', isFormValid);
// set to true to show all error messages (if there are any)
$scope.formStepSubmitted = true;
if(isFormValid) {
// reset this for next form
$scope.formStepSubmitted = false;
// mark the step as valid so we can navigate to it via the links
updateValidityOfCurrentStep(true /*valid */);
$state.go(nextState($state.current.name));
} else {
// mark the step as valid so we can navigate to it via the links
updateValidityOfCurrentStep(false /*not valid */);
}
};
$scope.testProcessForm = function() {
$http({
method : 'POST',
url : 'kundreg.php',
data : $scope.formData,
headers : {'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8'}
})
.success(function(data) {
if (data.errors) {
// Showing errors.
$scope.errorselectedServices = data.errors.selectedservices;
$scope.errorincEmail = data.errors.incemail;
} else {
$scope.submissionMessage = data.messageSuccess;
$scope.faxSenderPhoneNo = data.faxSenderPhoneNo;
$scope.faxSender = data.messit.faxSender;
console.log(faxSender);
// $scope.formData = {};
}
});
};
});
<!DOCTYPE html>
<h3 class="text-center">Granskning</h3>
<h4 class="text-center">Vänligen kontrollera:</h4><br>
<div class="form-group row"></div>
<!-- <span ng-show="errorselectedServices">{{errorselectedServices}}</span>
<span ng-show="errorincEmail">{{errorincEmail}}</span>></div> -->
<div ng-show="faxSender">{{ faxSender }} ng show faxsenderphoneno</div>
<br>
<div class="form-group row">
<div class="col-xs-6 col-xs-pull">
<a ui-sref="form.fax" class="btn btn-block btn-info">
Föregående <span class="glyphicon glyphicon-circle-arrow-left"></span></a>
</div>
<div class="col-xs-6 col-xs-push">
<a ng-click="testProcessForm()">
Skapa <span class="glyphicon glyphicon-circle-arrow-right"></span>
</a>
</div>
</div>
<?php
$errors = array();
$data = array();
$selectedServices = array();
// Getting posted data and decodeing json
$_POST = json_decode(file_get_contents('php://input'), true);
// checking for blank values.
if (empty($_POST['selectedServices']))
$errors['selectedServices'] = 'Minst en tjänst måste väljas.';
if (empty($_POST['incEmail']))
$errors['incEmail'] = 'Epost som tillåts använda tjänsterna saknas';
$selectedServices = $_POST['selectedServices'];
if (!empty($errors)) {
$data['errors'] = $errors;
} else {
if (!empty($_POST["faxSenderPhoneNo"])) {
// ta bort allt som inte är siffror
$faxSender = preg_replace('/[^0-9\/+]/', '', $_POST["faxSenderPhoneNo"]);
// finns ingen nolla så lägger vi till den så vi kan matcha den i regexen
//regex med internationellt format så databasen blir glad
if (preg_match('/^0/', $faxSender) === 0) {
$faxSender = "0{$faxSender}";
}
$faxSenderPhoneNo = preg_replace("/(^0|^46)/", "+46", $faxSender);
$messit['faxSender'] = $faxSenderPhoneNo;
}
else {
$faxSenderPhoneNo = 'NULL';
}
if (!empty($_POST["deliveryReportFax"])) {
$deliveryReportFax = $_POST["deliveryReportFax"];
}
else {
$deliveryReportFax = '3';
}
}
}
if (!$error) {
// sql
echo json_encode($data);
?>
I found the error. Apparently you have to quote the variable into the array;
$data['faxSender'] = "$faxSenderPhoneNo";
Now works as intended.
EDIT:
Well it worked to a point. My divs still weren't displaying. After logging with console.log(data) I could see that I had a lot of undefined indexes but my data array was there so I didn't understand why I couldn't access it.
I fixed the undefined stuff and then suddenly every div was displayed. Not a clue why PHP decides to dump all that info into my $data array.
2nd edit: Apparently .success is deprecated. Using .then instead with error_reporting(1); seems to always give me an array with data that angular then can use.
Since you are JSON encoding data in php file, file returning a String. so, you will need decode JSON to Java script object first. Also, you $http returns angular promise($q service). I am not sure about using
.success
method. Instead use
.then
.then(function successCallback(response) {
// this callback will be called asynchronously
// when the response is available
// decode JSON firs since you are sending JSON from PHP
var data = JSON.parse(response);
$scope.test = data.test;
}, function errorCallback(response) {
// called asynchronously if an error occurs
// or server returns response with an error status.
// Handle error here
});
What am I missing ? I am new to Angularjs. Trying angularjs with asp.net mvc. I am unable to access an asp.net mvc controller to return me a JsonResult using $resource of angular.
However, I get success otherwise using $.getJson of javascript but not using angularjs. What am I missing ? please guide. Thank you for replying any.
Following is my Service
EbsMvcApp.factory('classListService', function ($resource, $q)
{
var resource = $resource
(
'/Home/ClassList'
, {}
//{ method: 'Get', q: '*' }, // Query parameters
, { 'query': { method: 'GET' , isArray:false } }
);
function get($q)
{
console.log('Service: classListServic > Started');
var Defered = $q.defer();
resource.get
(
function (dataCb)
{
console.log('success in http service call');
Defered.resolve(dataCb);
}
, function (dataCb)
{
console.log('error in http service')
Defered.reject(dataCb);
}
);
return Defered.promise; // if missed, would throw an error on func: then.
};
return { get: get };
});
angular Controller:
var EbsMvcApp = angular.module('myApp', ['ngResource']);
//'classListService',
EbsMvcApp.controller
(
'myAppController',
['$scope','classListService','$q' , function ($scope, classListService, $q)
{
console.log('controller myAppController started');
var classList = classListService.get($q);
classList = classList.then(
function ()
{
(
function (response)
{
console.log('class list function response requested');
return response.data;
}
);
}
);
console.log(classList.ClassName);
console.log(classList);
console.log('end part of ctrl');
$scope.classList = classList;
$scope.SelectedClassID = 0;
$scope.message = ' message from Controller ';
}
]
);
Asp.net MVC Controller
namespace EBS_MVC.Controllers
{
public class HomeController : BaseController
{
ApplicationDbContext db = new ApplicationDbContext();
public JsonResult ClassList()
{
var List = new SelectList(db.tblClass, "ID", "ClassName");
return Json(List, JsonRequestBehavior.AllowGet);
}
}
}
Brower's response (F12):
ControllerTry1.js:11 controller myAppController started
serviceGetClassList.js:16 Service: classListServic > Started
ControllerTry1.js:28 undefined
ControllerTry1.js:29 c
ControllerTry1.js:31 end part of ctrl
angular.js:12520 Error: [$resource:badcfg]
[Browers response: screen shot][1]
Oky, finally, I got a solution using the $http service. from here
http://www.infragistics.com/community/blogs/dhananjay_kumar/archive/2015/05/13/how-to-use-angularjs-in-asp-net-mvc-and-entity-framework-4.aspx
in csHtml file, a reference to the service.js and Controler.js is required.
I am not sure if I have added it earlier or later now. but its required.
ng-Controller:
EbsMvcApp.controller('ClassListController', function ($scope, ClassListService2) {
console.log('ClassListController Started');
GetClassList();
function GetClassList()
{
ClassListService2.GetJson()
.success(function (dataCallBack) {
$scope.classList = dataCallBack;
console.log($scope.classList);
})
.error(function (error) {
$scope.status = 'Unable to load data: ' + error.message;
console.log($scope.status);
});
}
});
ng-Service:
EbsMvcApp.factory('ClassListService2', ['$http', function ($http) {
console.log('ClassListService2 Started');
var list = {};
list.GetJson = function () {
return $http.get('/Home/ClassList');
};
return list;
}]);
csHtml View:
<div class="text-info" ng-controller="ClassListController">
<h3> Text from Controller: </h3>
#*table*#
<table class="table table-striped table-bordered">
<thead>
<tr><th>DisplayName</th><th>Value</th>
</thead>
<tbody>
<tr ng-hide="classList.length">
<td colspan="3" class="text-center">No Data</td>
</tr>
<tr ng-repeat="item in classList">
<td>{{item.Text}}</td>
<td>{{item.Value}}</td>
</tr>
</tbody>
</table>
Sorry for the delay, I just wrote up some code to quickly test the ngResource module as I haven't used it yet.
I've got the code working to do what you want using the ngResource module. I think part of the problem was that you was configuring the query method but calling the get method so your configurations was not applied.
Here is the service class that I wrote to test against a controller the same as yours.
(function () {
'use strict';
angular
.module('myApp')
.service('classService', ClassService);
ClassService.$inject = ['$resource', '$q'];
function ClassService($resource, $q) {
var resource = $resource
(
'/Home/ClassList',
{},
{
'get': { method: 'GET', isArray: true },
'query': { method: 'GET', isArray: true }
}
);
var service = {
get: get
};
return service;
////////////
function get() {
var Defered = $q.defer();
resource.get(function (dataCb) {
console.log('success in http service call');
Defered.resolve(dataCb);
}, function (dataCb) {
console.log('error in http service')
Defered.reject(dataCb);
});
return Defered.promise;
};
};
})();
The controller looks like this
(function () {
'use strict';
angular
.module('myApp')
.controller('classController', ClassController);
ClassController.$inject = ['$scope', 'classService'];
function ClassController($scope, classService) {
var vm = this;
vm.data = null;
activate();
/////////////
function activate() {
var classList = classService.get().then(function (response) {
console.log('class list function response requested');
vm.data = response;
console.log(vm.data);
});
console.log('end part of ctrl');
$scope.SelectedClassID = 0;
$scope.message = ' message from Controller ';
};
};
})();
I've included some of your original code just so you can see how it would fit in.
Glad to see you have got it working though!
I have a custom directive for soundcloud that requires the soundcloud url. The soundcloud url is fetched from the database through the $http service, however, the div for the soundcloud custom directive is loaded and requires the value of the soundcloud url before it is even defined.
The Plangular Directive Code I got is here:
https://github.com/jxnblk/plangular/blob/master/src/plangular.js *I did not develop this
This is my HTML code:
<div plangular="{{soundcloud}}">
<button ng-click="playPause()">Play/Pause</button>
<progress ng-value="currentTime / duration || 0">
{{ currentTime / duration || 0 }}
</progress>
</div>
And this is the Angular Code:
displaySong.controller('song', ['$scope', '$http', 'fetchSong', function($scope, $http, fetchSong) {
$scope.songID
$scope.songName;
//Controller properties
$scope.songPromise; //The song promise for fetching
$scope.init = function(songID, userID) {
$scope.songID = songID;
$scope.userID = userID;
$scope.songPromise = $http({
method: "post",
url: fetchSong,
data: {
song_id: $scope.songID
},
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
}).then(function(successResponse) {
console.log('Successfully fetched song');
console.log(successResponse);
var song = successResponse.data;
$scope.songID = song.song_id;
$scope.songName = song.song_name;
$scope.songType = song.song_type;
$scope.songEmbed = song.song_embed;
$scope.soundcloud = song.song_embed;
}, function(errorResponse) {
console.log('Error fetching');
$scope.songID = null;
});
};
}]);
I know it's a problem with the asynchronous nature because when I add this line in the beginning of my song controller:
$scope.soundcloud = "https://soundcloud.com/jshigley/shine";
It works perfectly fine. I've also noticed that when I spam the play/pause button that DOES come up from the directive, I get multiple console errors of "HTTP 404 Not Found", which leads me to believe it's trying to find a track of undefined url
Since it's a div directive and not a function call I can't use promises such as chaining a then to my $scope.songPromise. I've thought of putting it into a controller and having the controller do something like $timeout for 5 seconds, but I don't think this delays the execution of the DOM.
The soundcloud URL DOES end up getting loaded, but it remains undefined in the eyes of the plangular directive (I've actually encountered lots of these problems with bad timing of loading scope and directives). Any Angular Wizards willing to teach me how to tame the asynchronous nature of AngularJS?
You can use $watch in the custom directive to watch when url attributes is changed.
In
link: function(scope, el, attr) {
change from
if (src) {
resolve({ url: src, client_id: client_id }, function(err, res) {
if (err) { console.error(err); }
scope.$apply(function() {
scope.track = createSrc(res);
if (Array.isArray(res)) {
scope.tracks = res.map(function(track) {
return createSrc(track);
});
} else if (res.tracks) {
scope.playlist = res;
scope.tracks = res.tracks.map(function(track) {
return createSrc(track);
});
}
});
});
}
to
scope.$watch('attr.plangular', function(newVal) {
resolve({ url: attr.plangular, client_id: client_id }, function(err, res) {
if (err) { console.error(err); }
scope.$apply(function() {
scope.track = createSrc(res);
if (Array.isArray(res)) {
scope.tracks = res.map(function(track) {
return createSrc(track);
});
} else if (res.tracks) {
scope.playlist = res;
scope.tracks = res.tracks.map(function(track) {
return createSrc(track);
});
}
});
});
}, true);
If you dont want to change the directive then you might want to use ng-if to load that plangular div only when you get the url.
<div plangular="{{soundcloud}}" ng-if="haveurl">
and in the angular code :
}).then(function(successResponse) {
console.log('Successfully fetched song');
console.log(successResponse);
$scope.haveurl = true;
Try using ng-show like this to only show the div once your $http request has been completed.
<div ng-show="httpRequestComplete" plangular="{{soundcloud}}">
<button ng-click="playPause()">Play/Pause</button>
<progress ng-value="currentTime / duration || 0">
{{ currentTime / duration || 0 }}
</progress>
</div>
displaySong.controller('song', ['$scope', '$q', '$http', 'fetchSong', function($scope, $http, fetchSong) {
/* add $q promise library */
$scope.songID
$scope.songName;
var httpRequest = function() {
var deferred = $q.defer();
$http({
method: "post",
url: fetchSong,
data: {
song_id: $scope.songID
},
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
}).success(function(successResponse) {
deferred.resolve({response: successResponse});
console.log('Successfully fetched song', successResponse);
var song = successResponse.data;
$scope.songID = song.song_id;
$scope.songName = song.song_name;
$scope.songType = song.song_type;
$scope.songEmbed = song.song_embed;
$scope.soundcloud = song.song_embed;
}).error(function(error) {
console.log(error);
});
return deferred.promise;
};
httpRequest().then(function(response) {
$scope.httpRequestComplete = true;
console.log('div will show');
};
}]);
I would do something like this that delays the showing of the div until httpRequestComplete = true, or until your promise ($q) is fulfilled. This will make sure that your div isn't loaded until you have the information available.
I have such json representation of a post by its id:
http://127.0.0.1:8000/update/1?format=json
{"title": "about me", "content": "I like program", "created": "2014-11-29T18:07:18.173Z", "rating": 1, "id": 1}
I try to update rating by button click:
<button ng-click="click(post.id)">Click me</button>
I have such javascript code:
<script>
var demoApp = angular.module('demoApp',['ngResource']);
demoApp.controller( 'AllPosts', function ($scope, $http)
{
$http.get('/blogpost/?format=json').success(function(data,status,headers,config)
{
$scope.posts = data.results;
$scope.predicate = '-title';
$scope.click = function(post_id, $resource){
var Post = $resource('/update/:PostId ',{PostId:post_id,format:'json'} );
post = Post.get({PostId:post_id}, function() {
post.rating = post.rating+ 1 ;
post.$save();
});
};
}).error(function(data,status,headers,config)
{} )
;})
</script>
Peharps i have mistake because in json i have a single object. But i dont really know
Besides i have such view to have a json by certain post by its id:
class UpdateModel(generics.RetrieveUpdateDestroyAPIView):
lookup_field = 'id'
queryset = BlogPost.objects.all()
serializer_class = BlogPostSerializer
permission_classes = (AllowAny,)
A quick tidy up of your script tag shows that you are only defining the function click if the http call was successful.
I would suggest moving the definition of the click method outside of the success callback.
You may also be running into a race condition that the click function has not been defined before clicking the actual button. Regardless you will want to move that function definition to where the controller is actually created.
Suggested edits (moved click definition outside of http call response):
var demoApp = angular.module('demoApp', ['ngResource']);
demoApp.controller('AllPosts', function($scope, $http, $resource) {
$scope.click = function(post_id) {
var Post = $resource('/update/:PostId ', {
PostId: post_id,
salutation: 'json'
});
post = Post.get({
PostId: post_id
}, function() {
post.rating = post.rating + 1;
post.$save();
});
};
$http.get('/blogpost/?format=json').success(function(data, status, headers, config) {
$scope.posts = data.results;
$scope.predicate = '-title';
}).error(function(data, status, headers, config) {});
})
Updates:
Injected $resource into the controller
Removed $resource from click function params
Below I've got an angular app and controller where the controller have data access inside of it (bad idea, I know)
var app = angular.module('app',[]);
app.controller('HomeController',function($scope,$http){
$scope.people = null;
$scope.get = function() {
$http({
url: 'largeTestData.json',
method: 'GET'
}).then(function(data){
console.log('request successful, here is your data: ');
console.log(data['data']);
$scope.people = data['data'];
},function(reason){
console.log('this failed, this is the reason: ');
console.log(reason);
})
}
});
app.controller('ControllerWithService',function($scope, MyService){
$scope.get = MyService.get;
$scope.get(function(data){
console.log('you succeeded');
},function(reason){
console.log('you failed');
console.log(reason);
})
})
This will work in retrieving data and putting it onto the page. Knowing that having data Access in the controller is no bueno I tried to abstract that out into a service:
app.service('MyService',function($http,$q){
var get = function(){
var deferred = $q.defer();
var url = 'test.json';
$http.get(url).success(deferred.resolve).error(deferred.reject);
}
return {
get: get
}
})
Here my 'data layer' is a service that only has one method: get from the above listed URL.
app.service('MyService',function($http,$q){
var get = function(){
var deferred = $q.defer();
var url = 'test.json';
$http.get(url).success(deferred.resolve).error(deferred.reject);
}
return {
get: get
}
})
and my HTML
<body>
<script src="libs/angular-1.2.15.js"></script>
<script src="app/app.js"></script>
<script src="app/DocumentService.js"></script>
<script src="libs/jQuery-2.1.1.js"></script>
<div ng-controller="HomeController">
<button ng-click="get()" href="#">Get data</button>
<div>{{message}}</div>
<!--<div ng-repeat="p in people" >-->
<!--<b>Business Doc ID: </b><h1>{{p['busDocId']}}</h1>-->
<!--<b>DOC ID: </b>{{p['docId']}}-->
<!--<b>FILE NAME: </b><div style="color: green">{{p['fileName']}}</div>-->
<!--</div>-->
</div>
<div ng-controller="ControllerWithService">
{{message}}
<button ng-click="get()">get data</button>
<div>{{data}}</div>
</div>
</body>
I'm not getting any error messages, and the commented out out stuff in my HomeController works as expected. What am I doing wrong in trying to make my AJAX calls a service?
working solution changes:
app.service('MyService',function($http,$q){
this.get = function(){
return $http.get('test.json')
}
})
app.controller('ControllerWithService',function($scope, MyService){
$scope.data = null;
$scope.get = function() {
MyService.get().then(function (data) {
console.log('this is the success data: ');
console.log(data)
$scope.data = data;
}, function (reason) {
console.log('this is the fail reason');
console.log(reason);
$scope.data = reason;
})
}
})
It looks like it could be a couple different things. I'll post an example I have working in one of my projects right now. It should be extremely similar and simple with what you're goal is.
Service:
'use strict';
angular.module('srcApp')
.service('Getlanguage', function Getlanguage($location, $http, $log, $state, $rootScope) {
this.getContent = function() {
var language = $location.path().split('/'),
languageCulture = language[1];
if (!languageCulture) {
languageCulture = 'en';
}
$rootScope.cultureCode = languageCulture;
return $http({method: 'GET', url: '/languages/' + languageCulture + '.json'})
.error(function() {
// If service cannot find language json file, redirect to index
$state.go('lang', {lang: 'en'});
});
};
});
Controller Call to service:
After passing in the service as a dependency into the controller.
Getlanguage.getContent().then(function(res) {
$scope.content = res.data;
});
Hope this helps.