Issues injecting Angular factories and services - angularjs

I don't know what it is about injecting factories, but I am having the most difficult time.
I've simulated what I'm attempting to do via this sample plunk http://plnkr.co/edit/I6MJRx?p=preview, which creates a kendo treelist - it works fine.
I have an onChange event in script.js which just writes to the console. That's also working.
My plunk loads the following:
1) Inits the app module, and creates the main controller myCtrl (script.js)
2) Injects widgetLinkingFactory int myCtrl
3) Injects MyService into widgetLinkingFactory
The order in which I load the files in index.html appears to be VERY important.
Again, the above plunk is NOT the real application. It demonstrates how I'm injecting factories and services.
My actual code is giving me grief. I'm having much trouble inject factories/services into other factories.
For example,
when debugging inside function linking() below, I can see neither 'CalculatorService' nor 'MyService' services. However, I can see the 'reportsContext' service.
(function () {
// ******************************
// Factory: 'widgetLinkingFactory'
// ******************************
'use strict';
app.factory('widgetLinkingFactory', ['reportsContext', 'MyService', linking]);
function linking(reportsContext, MyService) {
var service = {
linkCharts: linkCharts
};
return service;
function linkCharts(parId, widgets, parentWidgetData) {
// *** WHEN DEBUGGING HERE, ***
// I CANNOT SEE 'CalculatorService' AND 'MyService'
// HOWEVER I CAN SEE 'reportsContext'
if (parentWidgetData.parentObj === undefined) {
// user clicked on root node of grid/treelist
}
_.each(widgets, function (wid) {
if (wid.dataModelOptions.linkedParentWidget) {
// REFRESH HERE...
}
});
}
}
})();
A snippet of reportsContext'service :
(function () {
'use strict';
var app = angular.module('rage');
app.service('reportsContext', ['$http', reportsContext]);
function reportsContext($http) {
this.encodeRageURL = function (sourceURL) {
var encodedURL = sourceURL.replace(/ /g, "%20");
encodedURL = encodedURL.replace(/</g, "%3C");
encodedURL = encodedURL.replace(/>/g, "%3E");
return encodedURL;
}
// SAVE CHART DATA TO LOCAL CACHE
this.saveChartCategoryAxisToLocalStorage = function (data) {
window.localStorage.setItem("chartCategoryAxis", JSON.stringify(data));
}
}
})();
One other point is that in my main directive code, I can a $broadcast event which calls the WidgetLinking factory :
Notice how I'm passing in the widgetLinkingFactory in scope.$on. Is this a problem ?
// Called from my DataModel factory :
$rootScope.$broadcast('refreshLinkedWidgets', id, widgetLinkingFactory, dataModelOptions);
// Watcher setup in my directive code :
scope.$on('refreshLinkedWidgets', function (event, parentWidgetId, widgetLinkingFactory, dataModelOptions) {
widgetLinkingFactory.linkCharts(parentWidgetId, scope.widgets, dataModelOptions);
});
I am wasting a lot of time with these injections, and it's driving me crazy.
Thanks ahead of time for your assistance.
regards,
Bob

I think you might want to read up on factories/services, but the following will work:
var app = angular.module('rage')
app.factory('hi', [function(){
var service = {};
service.sayHi = function(){return 'hi'}
return service;
}];
app.factory('bye', [function(){
var service = {};
service.sayBye = function(){return 'bye'}
return service;
}];
app.factory('combine', ['hi', 'bye', function(hi, bye){
var service = {};
service.sayHi = hi.sayHi;
service.sayBye = bye.sayBye;
return service;
}];
And in controller...
app.controller('test', ['combine', function(combine){
console.log(combine.sayHi());
console.log(combine.sayBye());
}];

So it would be most helpful if you created a plunk or something where we could fork your code and test a fix. Looking over your services it doen't seem that they are returning anything. I typically set up all of my services using the "factory" method as shown below
var app = angular.module('Bret.ApiM', ['ngRoute', 'angularFileUpload']);
app.factory('Bret.Api', ['$http', function ($http: ng.IHttpService) {
var adminService = new Bret.Api($http);
return adminService;
}]);
As you can see I give it a name and define what services it needs and then I create an object that is my service and return it to be consumed by something else. The above syntax is TypeScript which plays very nice with Angular as that is what the Angular team uses.

Related

Angularjs cascading dropdowns

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.

AngularJS Amplitude Service Not Acting as Singleton

I have recently posted a similar question, but this is not a duplicate.
Apologies for the code heavy post but I wanted to provide as much context as possible. I am having an issue with defining the analytics tool, 'Amplitude' as a service in my Angular.js application. Services are supposed to act as singletons throughout an application (source), so I am confused to be getting the following behavior.
In my app.js file, I call AmplitudeService.logEvent('EVENT_NAME') in a .run function which successfully logs the event to Amplitude. Note: Console.log(AmplitudeService) returns an object with all the correct functions here.
However, when I call AmplitudeService.logEvent('EVENT_NAME') within any other controller, such as header.js I do not ever see any data in my Amplitude dashboard. Note: Console.log(AmplitudeService) returns an identical object within header.js to the one returned from app.js
Would appreciate any and all insight!
P.S. The official AmplitudeJS SDK is here. I am trying to implement it through this wrapper.
AmplitudeService.js (source)
Note: If you check the author's syntax, he returns an object at the end of his service. In my research, I've read to use the "this" keyword when defining Service functions (source), and that you don't need to return an object as you would with a Factory, so I have updated it accordingly.
angular.module('AmplitudeService', [])
.service('AmplitudeService',
['$amplitude', '$rootScope', 'amplitudeApiKey', '$location',
function ($amplitude, $rootScope, amplitudeApiKey, $location) {
this.init = function() {
$amplitude.init(amplitudeApiKey, null);
}
this.identifyUser = function(userId, userProperties) {
$amplitude.setUserId(userId);
$amplitude.setUserProperties(userProperties);
}
this.logEvent = function(eventName, params) {
$amplitude.logEvent(eventName, params);
}
}]);
angular-amplitude.js (source)
This allows access to "$amplitude" throughout the application
(function(){
var module = angular.module('angular-amplitude', ['ng']);
module.provider('$amplitude', [function $amplitudeProvider() {
this.$get = ['$window', function($window) {
(function(e,t){
var r = e.amplitude || {};
var n = t.createElement("script");
n.type = "text/javascript";
n.async = true;
n.src = "https://d24n15hnbwhuhn.buttfront.net/libs/amplitude-2.2.0-min.gz.js";
var s = t.getElementsByTagName("script")[0];
s.parentNode.insertBefore(n,s);
r._q = [];
function a(e){
r[e] = function(){
r._q.push([e].concat(Array.prototype.slice.call(arguments,0)));
}
}
var i = ["init","logEvent","logRevenue","setUserId","setUserProperties","setOptOut","setVersionName","setDomain","setDeviceId","setGlobalUserProperties"];
for(var o = 0; o < i.length; o++){
a(i[o])
}
e.amplitude = r
}
)(window,document);
return $window.amplitude;
}];
}]);
return module;
}());
App.js
angular.module('app', [
'ngRoute',
'angular-amplitude',
'AmplitudeService',
])
.run(['AmplitudeService', function(AmplitudeService){
console.log(AmplitudeService); // Outputs 'Object {}'
AmplitudeService.init();
*AmplitudeService.logEvent('LAUNCHED_SITE'); // This logs the event*
console.log(AmplitudeService); // Outputs 'Object {}'
}])
Header.js
angular.module('app.common.header', [])
.controller('HeaderCtrl', [ '$rootScope', '$scope', '$location','$route', '$window', 'AmplitudeService', function($rootScope, $scope, $location, $route, $window, AmplitudeService){
$scope.goToSearch = function(term) {
$location.path('/search/' + term);
console.log(AmplitudeService); // Outputs 'Object {}'
*AmplitudeService.logEvent('SEARCHED');* // This does not log the event
};
}]);
Update: I have tried switching the Service to a Factory and that did not generate any new results.
Found the solution and hope this is helpful to anyone that comes across this. My solution was to initialize the SDK by calling AmplitudeService.init() within a .run() function within app.js, which initialized an amplitude object within my window. From there forward, I included $window as a service in each of my controllers and called $window.amplitude.logEvent('event_name_here');
Feel free to contact if you have questions. Thanks.
We had similar issues on a large project and we created a wrapper providing a directive to init Amplitude and a service to provide the logEvent and sendUserId.

function is undefined error in angular controller

I'm running into a problem trying to setup a fairly simple call using a factory and controller in angularjs. I've been attempting to follow the style guides of John Papa and Todd Motto in setting this up.
First I'm using 2 modules
(function(){
'use strict';
angular.module('app',[
'app.core',
'app.property'
]);
})();
In 'app.core' I define the factory
(function(){
'use strict';
angular.module('app.core')
.factory('dataservice',dataservice);
dataservice.$inject = ['$http','$q'];
function dataservice($http,$q) {
var service = {
getListing: getListing
};
return service;
function getListing() {
var def = $q.defer;
$http.get("http://acme.com/property/1?format=json")
.success(function(data){
service.getListing = data;
def.resolve(data);
});
return def.promise;
}
}
})();
and in 'app.property' I defined the controller
(function(){
'use strict';
angular.module('app.property')
.controller('PropertyCtrl',PropertyCtrl);
PropertyCtrl.$inject = ['dataservice'];
function PropertyCtrl(dataservice) {
var vm = this;
vm.listings = [];
activate();
function activate() {
return getListing().then(function(){});
}
function getListing(){
return dataservice.getListing().then(function(data){
vm.listings = data;
console.log("data is");
console.log(data);
return vm.listings;
});
}
}
})();
the error I get in the console output is
Error: dataservice.getListing(...) is undefined except when I inspect dataservice in chrome I can see
Further on I receive
TypeError: Cannot read property 'then' of undefined
TypeError: def.resolve is not a function
Despite these errors the remote call returns json fine.
Hoping someone with angular chops has an idea on where I went wrong.
You're very close. It should be $q.defer() but you've $q.defer
function getListing() {
var def = $q.defer();
$http.get("http://acme.com/property/1?format=json")
.success(function(data){
service.getListing = data;
def.resolve(data);
});
return def.promise;
}
You have to actually create the modules you are building on:
Put this above your app.property module's objects:
angular.module('app.property', []);
Put this above your app.core module's objects:
angular.module('app.core', []);
You are basically attaching a factory and a controller to modules that don't exist. You are trying to inject modules that don't exist into your primary module.
Here is a plunker showing the issues you were having resolved. Your code has some other issues, but at least it's finding the modules now, which was your original problem.
It should also be noted that mohamedrias is also correct - you had an error in syntax by not putting () on your defer call.
I updated my plunker to include his correction as well.

Can not figure out how to store $rootScope in angular.bootstrap

I'm trying to call a web service in AngularJS bootstrap method such that when my controller is finally executed, it has the necessary information to bring up the correct page. The problem with the code below is that of course $rootScope is not defined in my $http.post(..).then(...
My response is coming back with the data I want and the MultiHome Controller would work if $rootScope were set at the point. How can I access $rootScope in my angular document ready method or is there a better way to do this?
angular.module('baseApp')
.controller('MultihomeController', MultihomeController);
function MultihomeController($state, $rootScope) {
if ($rootScope.codeCampType === 'svcc') {
$state.transitionTo('svcc.home');
} else if ($rootScope.codeCampType === 'conf') {
$state.transitionTo('conf.home');
} else if ($rootScope.codeCampType === 'angu') {
$state.transitionTo('angu.home');
}
}
MultihomeController.$inject = ['$state', '$rootScope'];
angular.element(document).ready(function () {
var initInjector = angular.injector(["ng"]);
var $http = initInjector.get("$http");
$http.post('/rpc/Account/IsLoggedIn').then(function (response) {
$rootScope.codeCampType = response.data
angular.bootstrap(document, ['baseApp']);
}, function (errorResponse) {
// Handle error case
});
});
$scope (and $rootScope for that matter) is suppose to act as the glue between your controllers and views. I wouldn't use it to store application type information such as user, identity or security. For that I'd use the constant method or a factory (if you need to encapsulate more logic).
Example using constant:
var app = angular.module('myApp',[]);
app.controller('MainCtrl', ['$scope','user',
function ($scope, user) {
$scope.user = user;
}]);
angular.element(document).ready(function () {
var user = {};
user.codeCampType = "svcc";
app.constant('user', user);
angular.bootstrap(document, ['myApp']);
});
Note Because we're bootstrapping the app, you'll need to get rid of the ng-app directive on your view.
Here's a working fiddle
You could set it in a run() block that will get executed during bootstrapping:
baseApp.run(function($rootScope) {
$rootScope.codeCampType = response.data;
});
angular.bootstrap(document, ['baseApp']);
I don't think you can use the injector because the scope isn't created before bootstrapping. A config() block might work as well that would let you inject the data where you needed it.

Separating scripts in Angular

I've created an angular app that has the following structure.
Application configuration, routes, directives, controllers and filters are all defined in index.js (I know this is not recommended). All of my general functions are in a controller called main.js, this is also the controller I am using in my main view in index.html. From then on the app consists of 10 different views, each has it's own controller.
main.js has become very difficult to maintain, so I would like to separate it into five external "utility" style files that contain the general function the application uses. These functions all use angular's $scope and must be able to be accessed by all the views and controllers that exist in the application.
For the past few days I've tried several different methods, such as defining the functions under angular's factory service, using angular's $provide method, defining a controller without a view and many others. None of them worked for me. What is the simplest way to separate the functions that exist in main.js to external js files without changing any code within the functions themselves. Let's pretend that the function cannot be turned into a directive.
Example -
Function that checks users name for 'guest' string and returns an image
main.js -
$scope.defaultpic = function(username) {
var guest = username;
if (guest.indexOf("guest") != -1){
{return {"background-image": "url('data:image/png;base64,chars"}}
}
}
in the view
<img ng-style="defaultpic(JSON.Value)" class="user_pic" ng-src="getprofilepic/{{JSON.Value}}"/>
Cheers,
Gidon
In order to use the function in markup, you still have to bind it to the scope. But, you can move the body of the function to a service:
angular.module('myapp').factory('picService',[ function () {
return {
defaultpic: function(username) {
var guest = username;
if (guest.indexOf("guest") != -1){
{return {"background-image": "url('data:image/png;base64,chars"}}
}
}
};
}]);
And then bind it up in the controller:
$scope.defaultpic = picService.defaultpic;
Refactor controller functions as services declared in different files
As you correctly stated, a great approach to refactor the functions is to put them into different services.
According to the angular Service docs:
Angular services are singletons objects or functions that carry out specific tasks common to web apps.
Here is an example:
Original code
Here we have a simple Hello World app, with a controller that has two functions: greet() and getName().
app.js
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
$scope.getName = function () {
return 'World';
}
$scope.greet = function (name) {
return 'Hello ' + name;
};
});
index.html
...
<div id="container" ng-controller="MainCtrl">
<h1>{{greet(getName())}}</h1>
</div>
...
We want to test that our scope always has both functions, so we know it is working as intended, so we are going to write two simple jasmine tests:
appSpec.js
describe('Testing a Hello World controller', function() {
var $scope = null;
var ctrl = null;
//you need to indicate your module in a test
beforeEach(module('plunker'));
beforeEach(inject(function($rootScope, $controller) {
$scope = $rootScope.$new();
ctrl = $controller('MainCtrl', {
$scope: $scope
});
}));
it('should say hallo to the World', function() {
expect($scope.getName()).toEqual('World');
});
it('shuld greet the correct person', function () {
expect($scope.greet('Jon Snow')).toEqual('Hello Jon Snow');
})
});
Check it out in plnkr
Step 1: Refactor controller functions into separate functions
In order to start decoupling our controller to our functions we are going to make two individual functions inside app.js.
app.js
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
$scope.getName = getName;
$scope.greet = greet;
});
function getName() {
return 'World';
}
function greet(name) {
return 'Hello ' + name;
}
Now we check our test output and we see that everything is working perfectly.
Check out the plnkr for step 1
Step 2: Move functions to their own services
We will define a NameService and GreetService, put our functions in them and then define the services as dependencies in our controller.
app.js
var app = angular.module('plunker', []);
app.service('NameService', function () {
this.getName = function getName() {
return 'World';
};
});
app.service('GreetService', function() {
this.greet = function greet(name) {
return 'Hello ' + name;
}
});
app.controller('MainCtrl', ['$scope', 'NameService', 'GreetService', function($scope, NameService, GreetService) {
$scope.getName = NameService.getName;
$scope.greet = GreetService.greet;
}]);
We make sure that our tests are still green, so we can move on to the final step.
Have a look at step 2 in plunker
Final Step: Put our services in different files
Finally we will make two files, NameService.js and GreetService.js and put our services in them.
NameService.js
angular.module('plunker').service('NameService', function () {
this.getName = function getName() {
return 'World';
};
});
GreetService.js
angular.module('plunker').service('GreetService', function() {
this.greet = function greet(name) {
return 'Hello ' + name;
}
});
We also need to make sure to add the new scripts to our index.html
index.html
...
<script src="NameService.js"></script>
<script src="GreetService.js"></script>
...
This is how our controller looks like now, neat huh?
app.js
var app = angular.module('plunker', []);
app.controller('MainCtrl', ['$scope', 'NameService', 'GreetService', function($scope, NameService, GreetService) {
$scope.getName = NameService.getName;
$scope.greet = GreetService.greet;
}]);
Plunker for the final step.
And that's it! Our tests still pass, so we know everything works like a charm.

Resources