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.
Related
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.
I am defining a service in AngularJS 1.2.x like below where I inject the DreamFactory API for use in this service. Now, when I do this, I get the error message like
TypeError: Cannot read property 'getRecords' of undefined
I interprete that the injection has not completely terminated. So I went ahead and delayed the call to the API 1000ms which solves the problem. But of course, this delay is not an acceptable solution.
Somehow I need an event for injection completion upon which I can then launch the DreamFactory.api.db.getRecord() method.
Can anyone help?
(function () {
'use strict';
angular
.module('app')
.service('VisitService', VisitService);
VisitService.$inject = ['$cookieStore', '$rootScope', '$timeout', 'DreamFactory'];
function VisitService($cookieStore, $rootScope, $timeout, DreamFactory) {
var service = {};
service.getVisits = getVisits;
function getVisits(callback) {
//setTimeout(function(){
DreamFactory.api.db.getRecords({table_name: 'visitors'})
.then(function(result) {
console.log(result.data.record);
callback(result.data.record)
},
function(reject) {
});
//}, 1000);
}
return service;
}
})();
in the controller, I call the service like so
VisitService.getVisits(function(result){
alert("this callback was triggered")
console.log(result)
});
DreamFactory uses, besides being a promise based API, another condition that one has to look for.
I do this with :
$scope.$on('api:ready', function (e) { }
This watcher being present solves my problem.
Thanks everybody who tried to help
I wonder if I can call controller method from service.
I know that Service is singleton and I can't inject $scope to the service.
In my case I manage Google Maps in service and want to open modal Dialog when user right clicks on Polygon.
As I know, to open/create new instance of dialog, somehow Service must notify controller to do that.
This is a template with controller + method and service: Template
var myApp = angular.module('myApp', []);
function MyCtrl($scope, gridService, $timeout) {
// how to call "foo" method from service?
$scope.foo = function(){
alert('called from service');
};
}
myApp.service('gridService', ['$timeout', function ( $timeout) {
var grid = {
fetching: false,
pristine: true,
pageType: 'Edit'
}
return {
gridSetup: function () {
return grid;
},
setGridSetup: function (newGrid) {
}
}
}]);
Thanks,
The answer is simple: you don't.
The service exists to manipulate data, nothing else. And it really shouldn't care "why" it's doing what it's doing. You ask the service to do something and wait for a response.
Personally I prefer using promises to resolve async operations (i.e. to notify the controller about a state change) since it's heavily supported by many angular services like $http.
But feel free to use callbacks of you wish.
Usually you do not need to call controller from the service - in general the single service could be used by any controller so service shall know nothing about them. In most cases controller calls to the service in react to some user's action and then you need to update view from controller somehow when service did its work (get response from server, etc.). I see following general ways how to do it.
1. Use callbacks.
//controller
$scope.onButtonClick = function() {
$scope.label = "wait, loading...";
function onSuccess(result) {
$scope.label = "done! result is " + result;
}
myService.doSomeWork(param1, param2, onSuccess);
}
//service
doSomeWork: function(param1, param2, onSuccess) {
$.get({...}, onSuccess);
}
So you provide a callback for each action.
2. Subscribe on events
You may use jQuery for events subscribing/triggering
//controller
$(myService).on('update', function() {
$scope.lastUpdateTime = new Date();
});
$scope.onButtonClick = function() {
myService.doUpdate();
}
//service
doUpdate: function() {
$.get({...}, function onOk() {
$(this).trigger('update');
});
}
3. Use promises
A lot of built-in angular services return promise objects, you may use them too:
//controller
myService.doSomething(param1).then(function(result) {
$scope.result = result;
});
//service
doSomething: function(param1) {
return $http.get({...});
}
4. Share some data
An example is $resource service - for example when you call query method it returns empty array-like object that could be safely put to scope and then fills it with values when http request is done.
//controller
$scope.options = myService.options;
$scope.onClick = function() { myService.update() }
//service
options: [],
update: function() {
var self = this;
$http.get({...}).success(function(res) {
self.options.splice(0, self.options.length); //to keep same array
self.options.push.apply(self.options, res.data.options);
});
}
In all these cases services and controllers are separated, services could be used with any controller and you may easily write unit-tests on services that will not break if you change your controller/view part somehow.
A possible solution would be to have a dialog service which you can inject into the grid service. So when the user right clicks on the polygon the handler would call open on the dialog service.
Take a look at the modal service on angular ui as an example.
All I need to do is to download a json file and assign it to OCategories in PCategory provider after I set the path. However I get an error that $http doesnt exist. How can I inject it into my provider and download inside of the setPath function?
var app = angular.module('NSApp',
[
'ui.bootstrap',
'MDItem',
'MDUser',
'MDNotification',
'MDUpload'
]
);
app.config(function(PCategoriesProvider)
{
PCategoriesProvider.setPath('data/csv/categories.json');
});
MDItem/provider/category.js
angular.module('MDItem').provider('PCategories',function(){
var OCategories;
var OPath;
return{
setPath: function(d){
OPath = d;
console.log('Path is set. Trying to download categories.');
OCategories = $http.get(oPath);
},
$get : function() {
return {
categories : OCategories
}
}
}
});
You can never inject service instances into config functions or providers, since they aren't configured yet. Providers exist to configure specific services before they get injected. Which means, there's always a corresponding provider to a certain service. Just to clarify, here's a little example configuring $location service using $locationProvider:
angular.module('myModule').config(function ($locationProvider) {
$locationProvider.html5Mode(true);
});
So what happens here, is that we configure $location service to use its html5mode. We do that by using the interfaces provided by $locationProvider. At the time when config() is executed, there isn't any service instance available yet, but you have a chance to configure any service before they get instantiated.
Later at runtime (the earliest moment ist the run() function) you can inject a service. What you get when injecting a service is what its providers $get() method returns. Which also means, each provider has to have a $get() function otherwise $injector would throw an error.
But what happens, when creating custom services without building a provider? So something like:
angular.module('myModule').factory('myService', function () {
...
});
You just don't have to care about, because angular does it for you. Everytime you register any kind of service (unless it is not a provider), angular will set up a provider with a $get() method for you, so $injector is able to instantiate later.
So how to solve your problem. How to make asynchronous calls using $http service when actually being in configuration phrase? The answer: you can't.
What you can do, is run the $http call as soon as your service gets instantiated. Because at the time when your service get instantiated, you're able to inject other services (like you always do). So you actually would do something like this:
angular.module('myModule').provider('custom', function (otherProvider, otherProvider2) {
// some configuration stuff and interfaces for the outside world
return {
$get: function ($http, injectable2, injectable3) {
$http.get(/*...*/);
}
};
});
Now your custom provider returns a service instance that has $http as dependency. Once your service gets injected, all its dependencies get injected too, which means within $get you have access to $http service. Next you just make the call you need.
To make your this call is getting invoked as soon as possible, you have to inject your custom service at run() phrase, which looks like this:
angular.module('myModule').run(function (custom, injectable2) {
/* custom gets instantiated, so its $http call gets invoked */
});
Hope this makes things clear.
Since all services are singletons in angular you could simply store a variable in a factory with the $http promise. And then when the factory is called at startup it will download the json.
You can then also expose a method on the factory that refreshes the data.
I know this is not the exact answer to your question, but I thought I'd share how I would do it.
angular.module('MDItem').factory('PCategories', function ($http, PCategoriesPath) {
var service = {
categories: [],
get: function () {
if (angular.isUndefined(PCategoriesPath)) {
throw new Error('PCategoriesPath must be set to get items');
}
$http.get(PCategoriesPath).then(function (response) {
service.categories = response.data;
});
}
};
// Get the categories at startup / or if you like do it later.
service.get();
return service;
});
// Then make sure that PCategoriesPath is created at startup by using const
angular.module('MDItem').const('PCategoriesPath', 'data/csv/categories.json');
angular.module('app').controller('myCtrl', function ($scope, PCategories) {
$scope.categories = PCategories.categories;
// And optionally, use a watch if you would like to do something if the categories are updated via PCategories.get()
$scope.$watch('categories', function (newCategories) {
console.log('Look maa, new categories');
}, true); // Notice the true, which makes angular work when watching an array
})
You have to inject $http in the function $get, because that's the function called by the injector.
However, to download the categories you would be better off using promises:
angular.module('MDItem').provider('PCategories',function(){
var OCategories;
var OPath;
return{
setPath: function(d){
OPath = d;
console.log('Path is set');
},
$get : function($http) {
return {
fetch: function () {
var deferred = $q.defer();
$http.get(oPath).then(function (value) {
deferred.resolve(value);
}
return deferred.promise;
}
}
}
}
});
I implemented what I wanted with a diffrent approach which is quite simple and effective. Just add a dummy controller in the main index.html(NOT PARTIAL). Data is now shared between all my modules and controllers and everything is downloaded once. :) Oh I love AJ.
...
<div ng-controller="initController" hidden></div>
...
initController:
angular.module('NSApp',[]).controller("initController",function($scope, $http, FCategory, FLocation){
$http.get('data/json/categories.json').then(function (response) {
FCategory.categories = response.data;
});
$http.get('data/json/cities.json').then(function (response) {
FLocation.cities = response.data;
});
$http.get('data/json/regions.json').then(function (response) {
FLocation.regions = response.data;
});
});
And now you can access it:
angular.module('MDTest', []).controller("test",function($scope, FCategory, FLocation){
$scope.categories = FCategory.categories;
FCategory factory
angular.module('MDItem').factory('FCategory', function ($http) {
var service = {
categories: [],
....
};
return service;
});
PRELIMINARIES
I am developing a web app using angularjs. At some point, my main controller connects to a web service which sends data continuously. To capture and process the stream I am using (http://ajaxpatterns.org/HTTP_Streaming). Everything works like a charm. I would like to share these streaming data with another controller that will process and display them via a jquery chart library (not yet decided which one I gonna use but it is out of the scope of this question). To share these data I have followed this jsfiddle (http://jsfiddle.net/eshepelyuk/vhKfq/).
Please find below some relevant parts of my code.
Module, routes and service definitions:
var platform = angular.module('platform', ['ui']);
platform.config(['$routeProvider',function($routeProvider){
$routeProvider.
when('/home',{templateUrl:'partials/home.html',controller:PlatformCtrl}).
when('/visu/:idVisu', {templateUrl: 'partials/visuTimeSeries.html',controller:VisuCtrl}).
otherwise({redirectTo:'/home',templateUrl:'partials/home.html'})
}]);
platform.factory('mySharedService', function($rootScope) {
return {
broadcast: function(msg) {
$rootScope.$broadcast('handleBroadcast', msg);
}
};
});
PlatformCtrl definition:
function PlatformCtrl($scope,$http,$q,$routeParams, sharedService) {
...
$scope.listDataVisu ={};
...
$scope.listXhrReq[idVisu] = createXMLHttpRequest();
$scope.listXhrReq[idVisu].open("get", urlConnect, true);
$scope.listXhrReq[idVisu].onreadystatechange = function() {
$scope.$apply(function () {
var serverResponse = $scope.listXhrReq[idVisu].responseText;
$scope.listDataVisu[idVisu] = serverResponse.split("\n");
sharedService.broadcast($scope.listDataVisu);
});
};
$scope.listXhrReq[idVisu].send(null);
var w = window.open("#/visu/"+idVisu);
$scope.$on('handleBroadcast', function(){
console.log("handleBroadcast (platform)");
});
}
VisuCtrl definition:
function VisuCtrl($scope,$routeParams,sharedService) {
$scope.idVisu = $routeParams.idVisu;
$scope.data = [];
/* ***************************************
* LISTENER FOR THE HANDLEBROADCAST EVENT
*****************************************/
$scope.$on('handleBroadcast', function(event,data){
console.log("handleBroadcast (visu)");
$scope.data = data[$scope.idVisu];
});
}
Injection:
PlatformCtrl.$inject = ['$scope','$http','$q','$routeParams','mySharedService'];
VisuCtrl.$inject = ['$scope','$routeParams','mySharedService'];
PROBLEM DEFINITION
When running this code, it looks like only the PlatformCtrl controller listens for the handleBroadcast event. Indeed, having a look to the console all what is displayed is only handleBroadcast (platform) every time new data arrive. I am very surprised because I have read in the official documentation that the $broadcast function
dispatches an event name downwards to all child scopes (and their
children) notifying the registered ng.$rootScope.Scope#$on listeners.
Since all the scopes in a given app inherits from $rootScope, I do not get why the $on function in VisuCtrl is not launched every time new data are broadcasted.
What I think is that when you open a new browser window you are launching a new AngularJS instance. This way it's not possible that the two controllers are able to communicate via a service.
If you have problems with scopes communicating, you can inject the $rootScope and see whether all the scopes that should communicate are actually instanciated.
function VisuCtrl($scope, $routeParams, sharedService, $rootscope) {
console.log($rootScope);
}
Your request flow comes out of the angular, therefore it would not be recognized until the next $digest phase (see how angular handles two-way binding via dirty matching). To get in to the angular world you need to use $apply:
$scope.listXhrReq[idVisu].onreadystatechange = function() {
$scope.$apply(function () {
var serverResponse = $scope.listXhrReq[idVisu].responseText;
$scope.listDataVisu[idVisu] = serverResponse.split("\n");
sharedService.broadcast($scope.listDataVisu);
});
};
Could it be that your VisuCtrl hasn't been initialized yet, since you are using custom routing?
Is it still the same, when you navigate to /visu/:idVisu?