I have one error in $http.json line.
Here is my code:
var myApp = angular.module('myApp', ["ionic"]);
myApp.service("Pressed", ["$http", "$log", Pressed]);
myApp.controller("AppCtrl", ["$scope", "Pressed", "$log", AppCtrl]);
function AppCtrl($scope, Pressed, $log){
$scope.refresh = function(){
Pressed.getBlogs();
}
}
function Pressed($http, $log) {
this.getBlogs = function() {
$http.jsonp('http://oasisgroups.com/oApp/product.php?callback=JSON_CALLBACK()')
.success(function(result){
$log.info(JSON.stringify(result.product));
});
};
}
When I click on refresh button, an error is displayed in the console:
You can also here find the respective service.
I believe the main issue is that the service doesn't seem to support a jsonp call. No matter how I call the service you provided it only responds with standard JSON results and not with the JSON wrapped in the callback function. Your screen shot of Chrome even shows the raw JSON, not JSONP response from the service. If a service doesn't support JSONP you can't force it to, that is something each service does on a case by case basis depending on how it is written. So the root cause of your error is that AngularJS is expecting the callback function to be part of the response, it cannot find it, and you get the error you are seeing.
I have constructed a jasmine test for your code and it passes. That is the best I can do to confirm that your code is correct and the issue is outside of your Angular code.
Unless the web service actually responds with the expected callback function wrapping the JSON, you need to switch to a standard $http.get() and deal with any potential XSS issues that you might encounter in a different way.
You can see a working JSONP example with this url. You will note that it starts with "getdata" and then wraps the JSON content inside that function's (). Your service is not doing that with the callback query string attribute.
var myApp = angular.module('myApp', []);
myApp.controller("AppCtrl", ["$scope", "Pressed", "$log", function ($scope, Pressed, $log) {
$scope.refresh = function () {
Pressed.getBlogs($scope);
}
}]);
myApp.service('Pressed', ['$http', '$log', function ($http, $log) {
var pressed = {};
pressed.getBlogs = function ($scope) {
$http.jsonp('http://oasisgroups.com/oApp/product.php?callback=JSON_CALLBACK')
.success(function (data,status,headers,config) {
$log.info(JSON.stringify(data));
$scope.products = data.product;
console.log('Found ' + data.product.length + ' products');
})
.error(function () {
console.log("Error during http get request.");
});
};
return pressed;
}]);
Then the test would look something like this:
describe('bad_jsonp', function () {
var service, scope;
beforeEach(module('myApp'));
beforeEach(angular.mock.inject(function ($rootScope) {
scope = $rootScope.$new();
}));
beforeEach(inject(function($httpBackend, _Pressed_) {
backend = $httpBackend;
service = _Pressed_;
}));
it('test that service response contains the attribute product', function () {
backend.expect("JSONP","http://oasisgroups.com/oApp/product.php?callback=JSON_CALLBACK").
respond(200,
{"success":1,"msg":"success","product":[{"image":"http:\/\/oasisgroups.com\/images\/oacgallery\/160112WP_20160112_17_57_49_Pro__1452604019_113.193.193.146.jpg","title":"Shreenath Ji"},{"image":"http:\/\/oasisgroups.com\/images\/oacgallery\/1601124e199090-c030-4f01-be11-c5140cf20273__1452603831_113.193.193.146.jpg","title":"Acrylic Jali"},{"image":"http:\/\/oasisgroups.com\/images\/oacgallery\/1601128a718e95-7df0-4189-876e-204b715cf90d__1452603868_113.193.193.146.jpg","title":"Acrylic Jali"},{"image":"http:\/\/oasisgroups.com\/images\/oacgallery\/16011229b095c9-b897-4942-831f-92073f527374__1452603895_113.193.193.146.jpg","title":"Wooden Decorative"},{"image":"http:\/\/oasisgroups.com\/images\/oacgallery\/16011255ce3155-3956-4cfb-8dd5-39021713d350__1452603914_113.193.193.146.jpg","title":"Acrylic Jali Oranage"},{"image":"http:\/\/oasisgroups.com\/images\/oacgallery\/160112WP_20160112_17_33_11_Pro__1452603994_113.193.193.146.jpg","title":"Acrylic Jali Green"},{"image":"http:\/\/oasisgroups.com\/images\/oacgallery\/160112607c733c-8dd5-442c-a584-6179339abb0e__1452603974_113.193.193.146.jpg","title":"Acrylic Jali White"},{"image":"http:\/\/oasisgroups.com\/images\/oacgallery\/160112300cca44-e783-48f7-b035-59ef0529ad53__1452603956_113.193.193.146.jpg","title":"Wooden Decorative"},{"image":"http:\/\/oasisgroups.com\/images\/oacgallery\/16011279e7c001-6663-4dfe-91ce-70cc87e6ca2d__1452603940_113.193.193.146.jpg","title":"Wooden Decorative"},{"image":"http:\/\/oasisgroups.com\/images\/oacgallery\/160112WP_20160112_17_58_35_Pro__1452604069_113.193.193.146.jpg","title":"Corian Design "},{"image":"http:\/\/oasisgroups.com\/images\/oacgallery\/160112WP_20160112_17_59_14_Pro__1452604098_113.193.193.146.jpg","title":"Corian Design "},{"image":"http:\/\/oasisgroups.com\/images\/oacgallery\/160112WP_20160112_18_00_34_Pro__1452604138_113.193.193.146.jpg","title":"AalaBuster"},{"image":"http:\/\/oasisgroups.com\/images\/oacgallery\/160112WP_20160112_18_01_20_Pro__1452604320_113.193.193.146.jpg","title":"AalaBuster"},{"image":"http:\/\/oasisgroups.com\/images\/oacgallery\/160112WP_20160112_18_02_08_Pro__1452604343_113.193.193.146.jpg","title":"Corian wash basin"},{"image":"http:\/\/oasisgroups.com\/images\/oacgallery\/160112WP_20160112_18_02_25_Pro__1452604370_113.193.193.146.jpg","title":"3d Corian Design"},{"image":"http:\/\/oasisgroups.com\/images\/oacgallery\/160112WP_20160112_18_02_43_Pro__1452604393_113.193.193.146.jpg","title":"3d Corian Design"},{"image":"http:\/\/oasisgroups.com\/images\/oacgallery\/160112WP_20160112_18_03_13_Pro__1452604424_113.193.193.146.jpg","title":"3d Wooden Decorative"}]}
);
expect(service).toBeDefined();
service.getBlogs(scope);
backend.flush();
console.log(scope.products);
var products = scope.products;
expect(products.length).toBe(17);
expect(products[0].title).toBe("Shreenath Ji");
});
});
The test doesn't include the actual callback in the response content because the mocking framework handles that wrapping and unwrapping for you just like AngularJS does in the first place, so it isn't an exact test but it is as close as I can get with what we have.
Related
formApp.controller('load', function ($scope, ApiCall, $window, $http) {
$window.onload = function () {
alert("the page loaded and will now call the function");
ApiCall.GetApiCall("signOn", "GetSingleSignOn").success(function (data) {
alert("successful call to singleSignOn, GetSingleSignOn");
var data = $.parseJSON(JSON.parse(data));
$scope.apiGetInfo = data;
alert("successful call to singleSignOn, GetSingleSignOn");
alert(data);
});
};
This code works fine up to the var data- $.parseJson(JSON.parse(data));
I looked at some examples of how to do this in the Controller online and they all looked this way with $.parseJSON(JSON.parse(data)).
It gives me: ReferenceError: $ is not defined
Not sure why as every example I looked at to call an API Controller in Angular showed this way.
You don't need the $.parseJSON. remove it and leave the JSON.parse intact:
var data = JSON.parse(data);
If you want to use JQuery ($) you have to import the script.
UPDATE:
if you want to redirect to an URL you can use $window:
$window.location.href = 'http://www.google.com';
I need to use angular service to create cascading drop-downs.The commented code I created for testing purpose and it is working fine. I need to create two services to call two methods from the MVC controller : GetCompanies() and GetDocTypes()
My questions are: Is my first service correct and how can I call the services from the controller?
Thank you.
/// <reference path="angular.js" />
//var myApp = angular
// .module("myApp", [])
// .controller("companyController", function ($scope, $http) {
// $http.post('CurrentSettings/GetCompanies')
// .then(function (response) {
// var response = $.parseJSON(response.data);
// $scope.currentSettings = response;
// });
// });
var myApp = angular.module("myApp", []);
myApp.service('getCompanies', function () {
$http.post('CurrentSettings/GetCompanies')
.then(function (response) {
var response = $.parseJSON(response.data);
$scope.currentSettings = response;
});
});
myApp.controller("companyController", function ($scope, getCompanies, $http) {
});
The problem with your service is two-fold:
Firstly, there is no way to call the service. You injected it fine, but now what? Think of your service as an API; it's no good just having a reference to it somewhere, you need to be able to use it. I would change to the following:
var myApp = angular.module("myApp", []);
myApp.service('getCompanies', ["$http", function($http) {
this.currentSettings = "Hello";
$http.post('CurrentSettings/GetCompanies')
.then(function(response) {
var response = $.parseJSON(response.data);
this.currentSettings = response;
});
}]);
myApp.controller("companyController", ["$scope", "getCompanies",
function($scope, getCompanies) {
$scope.currentSettings = getCompanies.currentSettings;
}]);
Note a few things:
You need to explicitly inject $http into your service.
I specify the names of the services that I'm injecting as part of an array that includes the function. This actually allows you to name the parameters anything you want, and is considered a best practice.
The service doesn't use $scope directly. Instead, it makes a field available to clients of the service. That client (the controller in this case) can then do with the value whatever it wants, including assigning it to a $scope field.
The controller reads this field from the service. It could also call any functions you specified - making the service an API, as I mentioned before.
The second problem is one of timing. Notice that I used the super-original value of "Hello" to initialize the service field.
The value you receive from the service will depend on whether or not the controller reads the value after your call to the MVC controller returns.
To fix this, the service could expose a second field to indicate that the company list is fully loaded, but that really shifts the problem around instead of fixing it.
What you need is a function that returns a promise. If the value has already been loaded, the promise resolves immediately. If not, it returns a promise that will return once the $http call is done.
Here is the modified code:
var myApp = angular.module("myApp", []);
myApp.service('companiesService', ['$http', '$q', function($http, $q) {
var currentSettings = null;
this.getList = function() {
var def = $q.defer()
if (currentSettings) {
def.resolve(currentSettings);
} else {
$http.get('CurrentSettings/GetCompanies')
.then(function(response) {
var response = response.data;
currentSettings = response;
def.resolve(currentSettings);
});
}
return def.promise;
}
}]);
myApp.controller('companyController', ['$scope', 'companiesService',
function($scope, companiesService) {
$scope.currentSettings = '';
companiesService.getList().then(function(value) {
$scope.currentSettings = value;
});
}
]);
It becomes a bit more complicated because you have to use promises, but these are the things to note:
I changed the name of the service to make it more generic. It can now offer a number of company-related features.
currentSettings is no longer added to this on the service, but instead becomes a normal (private) variable. The calling code can only read it by calling the getList function.
getList returns a promise. The promise is resolved immediately if currentSettings has been assigned. If not, it only resolves once the value is received from the web service.
The controller calls getList and assigns the value to the $scope field in the then function.
I have this piece of code:
.service('webSocket', function ($rootScope, socketFactory, CONFIG, $timeout) {
angular.element(document).ready(function () {
$rootScope.log('Waiting for connection...',CONSOLE_INFO);
});
And I am getting this error:
TypeError: $rootScope.log is not a function
This service is injected into this controller:
.controller('mainCtrl', function mainCtrl($scope, $rootScope, webSocket, myConsole ...
In which I have:
$rootScope.log = function (msg, type) { myConsole.log(msg,type); ... };
Can you tell me where is the problem? Or at least point me in the right direction? The reason I am using document ready function is because apart from logging messages to browser console (console.log) I use notifications for user (pNotify library) which needs to be called after DOM is loaded.
Sharing something between services using $rootScope should be considered generally as anti-pattern. If you don't have some different implementation of console for different controllers, you can do it Angular-way and perform all configurations in config block. Subscribing to document ready event in the service is also not a good idea (I would prefer to do it in run block), since in angular service is instantiated once it is first time required by any other service or controller or whatever. In order to have configurable service that may have different console implementation I would implement it using provider as follows:
angular.module('app',[]).
constant('console', console).
constant('PNotify', PNotify).
provider('myConsole', function() {
var log = angular.noop;
function MyConsoleFactory() {
return {
log: log,
debug: log
}
}
this.setLog = function(logImplementation) {
log = logImplementation
}
this.$get = [MyConsoleFactory];
}).
config(['myConsoleProvider', 'console', 'PNotify', function(myConsoleProvider, console, PNotify) {
myConsoleProvider.setLog(function(msg) {
console.log('[LOG] '+ Date.now() + ':\t' + msg);
new PNotify({
title: 'Title',
text: msg
});
});
}]).
run(['myConsole', '$document', function(myConsole, $document) {
$document.ready(function () {
myConsole.log('Waiting for connection...');
});
}]);
In this case you don't need any controller at all.
Plunker: http://plnkr.co/edit/aV9TIO07pnDs26xDBPtf?p=preview
That happens because service code runs before service was added to controller(where $rootScope.log method is defined). You can move $rootScope.log = function (msg, type) { myConsole.log(msg,type); ... }; into app.run(...) method and it will work.
I have a AngularJS app with a service that load a HTTP request, that works all fine, but...
When my app is offline at the beginning, i show a "offline" page with a retry button, when i click the button and the APP is online I need the service to load the data.
The problem is that nothing happen when I click the button, the online check works fine but the web service is never called :(
my service:
.service('webService', function ($http, $q){
var defferer = $q.defer()
var webservice_url = "http://mywebservice_url/";
$http.get(webservice_url+"?action=get_settings").success(function(data) {
defferer.resolve(data);
});
return defferer.promise;
})
my controller:
.controller('NetworkCtrl', function($scope, $location, $timeout, webService) {
$("#tryagain-button").click(function(e) {
e.preventDefault();
if(checkifonline()) {
webService.then(function(data) {
$scope.data = data;
});
}
});
})
First of all, using jQuery within an controller is absolutely not how angularjs works. Use the ng-click directive. Even if your service would be implemented correctly, it wouldn't work since AngularJS will not get notified about model changes in the scope (you would need to use $apply, but just stick to the ng-click directive)
Second, the service function takes a constructor function. Within this constructor you're making an http request (while you're offline) and the promise is resolved. You will never make a new request, since the service always retuns the same promise. Your service should look like:
.service('webService', function ($http){
var webservice_url = "http://mywebservice_url/";
this.getSettings = function(){
return $http.get(webservice_url+"?action=get_settings");
};
})
and your controller:
.controller('NetworkCtrl', function($scope, $location, $timeout, webService) {
$scope.onClick = function(){
webService.getSettings().success(function(data){
$scope.data = data;
}
}
})
and your HTML button:
<button ng-click="onClick()">reload</button>
I have a very strange problem with an angular app we are building. Whenever i load some data from a resource defined like below (simplest example I could build) I get some data back wich I can use for databinding (eg. ng-repeat='message in messages' or as {{message.id}}) however I can never read it from javascript by accessing it as an array or object (depending on wether i used get({id:myId}) or query()).
Iterating over it only gives me keys like $get, $query, $save, etc... but no actual data.
app.factory('Message', ['$resource', function($resource) {
return $resource('MY_URL/messages/:id', {id: '#id'});
}]);
app.service('messageService', ['$rootScope', 'Message', function($rootScope, Message) {
var messages = Message.query();
var selectedMessage = null;
var service = {};
service.get = function(id) {
// Problem A (see below for details)
if (arguments.length === 1) return Message.get({id: id});
else return messages;
};
var MenuCtrl = function($scope, Project, messageService, otherService) {
$scope.projects = Project.query();
$scope.messages = messageService.get();
// Problem B (details below)
};
At Problem A i want to be able to return a single element form a collection that has already been fetched, however i need some way to handle calls that happen before the data is ready.
At problem B i would like to process some of the fetched data and pass the result to "otherService" however i need a way to delay this until the data is ready.
I have only seen this issue come up in unit testing, and the way to get around it is by "flushing" the mock $httpBackend.
According to the API docs:
The $httpBackend used in production, always responds to requests with responses asynchronously. If we preserved this behavior in unit testing, we'd have to create async unit tests, which are hard to write, follow and maintain. At the same time the testing mock, can't respond synchronously because that would change the execution of the code under test. For this reason the mock $httpBackend has a flush() method, which allows the test to explicitly flush pending requests and thus preserving the async api of the backend, while allowing the test to execute synchronously.
Here's an example with some context:
// define a resource within a service
angular.module('app.services', ['ngResource'])
.factory('Message', function ($resource) {
var url = ...
, params = ...
, actions = ...;
return $resource(url, params, actions);
}
// Query the resource in a controller
function MessagesCtrl ($scope, $routeParams, Message) {
$scope.messages = Message.query();
}
// unit test with a mocked backend
describe('MessagesCtrl', function() {
var scope, ctrl, $httpBackend, messages;
beforeEach(inject(function (_$httpBackend_, $rootScope, $controller) {
$httpBackend = _$httpBackend_;
scope = $rootScope.$new();
messages = [
{
id: '1',
text: 'foo',
}, {
id: '2',
text: 'foo',
}
]
$httpBackend.expectGET('/api/messages').respond(messages);
ctrl = $controller('MessagesCtrl', {$scope: scope});
}));
it('should get a list of messages', function () {
// THE NEXT LINE WILL SOLVE THE PROBLEM
$httpBackend.flush();
expect(scope.message).toEqualData(message);
});
});