I want to share a specific property across different components' (different controllers in them).
Trying to inject a service that I created in the app module.
The service has get/set functions.
I created the service called SharedProperties. It's not accessible from any component in my app, says "Uknown". Why?
This is how my app is defined, and here's the very simple service.
mapotApp = angular
.module('mapotApp', [])
.service('sharedProperties', function () {
var property = 'test';
return {
getProperty: function () {
return property;
},
setProperty: function(value) {
property = value;
}
};
});
and then in my component:
angular.module('aboutPage').component('aboutPage', {
templateUrl: 'app/about-page/about-page.html',
controller: ['sharedProperties', function AboutPageController($http, $scope, sharedProperties) {
var self= this;
//inherting global variable
self.prop = sharedProperties.getProperty(); //UNKOWN PROVIDER ERROR HERE
}]
});
This returns:
Error: [$injector:unpr] Unknown provider: sharedPropertiesProvider <- sharedProperties
What can I do?
I tried injecting it in tons of places and still doesn't work.
thanks so much.
Here is the working DEMO with your code.
Your shareable service is correct, but make sure to properly initialize your controller, as well as correctly inject $http and $scope in it (see how $scope and $timeout services are injected in my demo).
Related
I have the below code in one controller.
$scope.DataModel= [];
$scope.DataModelTexts= { buttonDefaultText: '' };
I will be using the same code in one more controller. Now instead of writing the same code in 2nd controller too, i want to know if there is a way to put this in a common code in some factory or service and use that in both the controllers. I have tried to read about factory and services and i am getting a bit confused of how to use any one of these in my scenario.
Any information would help me gain more knowledge about factory and services in angularjs. Thanks.
You're on the right track, use can use a factory or a service to share code between controllers. Note that in angular services(and factories) are singletons; they are instantiated once when the app starts and then anytime you inject it into a controller, you are referencing the same instance. Consider the following code:
var myApp = angular.module('myApp',[]);
myApp.service('MyService', function() {
let _someValue = 'Initial Value';
this.setValue = function(value){
_someValue = value;
}
this.getValue = function(){
return _someValue;
}
});
//First Controller Run
myApp.controller('ControllerA', function($scope, MyService) {
MyService.getValue(); //Initial Value
MyService.setValue("BRAND NEW VALUE!!!");
});
//Run after ControllerA
myApp.controller('ControllerB', function($scope, MyService) {
MyService.getValue(); //BRAND NEW VALUE!!!
});
Her you'll see that MyService holds the state of someValue. ControllerA get MyService injected to it and can use the methods of that service to set a new value. Now for any subsequent call for that same state, like for instance by ControllerB, the updated value will be returned.
You can use the .config() or a run() blocks (good SO on these here: AngularJS app.run() documentation?) to bind these reused variables to $rootScope, then call them from $rootScope.DataModel and $rootScope.DataModelTexts from within your controllers or services (as long as you inject $rootScope into these controllers and services).
I have been looking around online, but seems, no one really injecting the actual dependencies into a unit test for Angularjs using Jasmine and Karma.
I think there is definitely a separation of concern for the testing process, but I also would like to know how it integrated well with current dependencies in use... so just in case a dependencies is not working well with my component, I will be aware of it!
So, I wonder how can I inject the actual dependencies? So far I found online articles are all about mocking it with a fake one... But I want to use the actual one. Right now, when I enter karma start I am getting a error of Error: [$injector:unpr] Unknown provider: _Provider <- _ <-MyService
I inject services in forEach block like this
beforeEach(angular.mock.inject(function(_MyService_) {
I wonder if its because I am not using the fake service?
describe('MyCtrl', function() {
//Data Exposure Prep
var $controller;
var $rootScope;
var $scope;
var controller;
var MyService;
dd1 = {
itinerary: globalMockData.d1,//I stored globalMockData somewhere else
};
beforeEach(angular.mock.module('myapp'));
beforeEach(angular.mock.inject(function(_$rootScope_, _$controller_, _$httpBackend_) {
$rootScope = _$rootScope_;
$controller = _$controller_;
$httpBackend = _$httpBackend_;
$scope = $rootScope.$new();
controller = $controller('MyCtrl', { $scope: $scope }, dd1);
}));
//BASIC INFO
describe('should receive sth', function() {
it('finds sth', function() {
expect(controller.answer).toBeDefined();
});
});
});
It really depend on your Controlller implementation.
If you're using scope, you'll probably want to test things on controller's scope, not on the controller itself.
Testing controller with $scope
function MyController($scope, MyService) {
$scope.greetUpperCase = function(name) {
return MyService.greet(name).toUpperCase();
}
}
Basically, when you’re working with $scope, you don’t really care about the controller itself, since it’s doing everything on it’s scope.
So, we will need to:
inject $rootScope
create a new scope
inject it to a new controller
test the $scope
it('test greetUpperCase()', function() {
var myScope = $rootScope.$new();
$controller('MyController', {
$scope: myScope
});
expect(myScope .greetUpperCase('bob')).toBe('HELLO BOB');
});
https://jsfiddle.net/ronapelbaum/pkhaxmdg/
I have this module as my main app which uses api:
var app = angular.module('app.main', ['api']);
app.controller('Home', function($scope, api, search){
$scope.search = function(){
api.search.lookup($scope.domain);
search.lookup($scope.domain);
};
});
I then have api which uses a few other modules like this:
var app = angular.module('api', [
'api.search'
// other modules here
]);
app.service('api', function($cookies, $http, $rootScope, search){
// Some more code
});
The search search module looks like this:
var app = angular.module('api.search', []);
app.service('search', function($scope, $http){
this.lookup = function(domain){
// query
};
});
When I run my controller and inject api, I can not access search because I get TypeError: Cannot read property 'lookup' of undefined, and if I inject search instead, I get this error:
Error: [$injector:unpr] http://errors.angularjs.org/1.4.3/$injector/unpr?p0=<div ng-view="" class="ng-scope" data-ng-animate="1">copeProvider%20%3C-%20%24scope%20%3C-%search
So, how can I access search from my controller?
The problem, as it seems, is that you are trying to inject $scope into a service generating function.
$scope is a local injectable available only for controllers, which makes sense, since scope is contextual to where the controller is defined and it has no meaning for singleton services.
Instead, you could inject $rootScope into a service, if needed.
app.service('search', function($rootScope, $http){
// ...
});
I have the following new service:
var SignatureService = function ($scope) {
this.announce = function () {
alert($scope.specificName);
}
};
SignatureService.$inject = ['$scope'];
This is declared for the app as follows:
MyAngularApp.service('SignatureService', SignatureService);
Several other services are added to the app in exactly the same way, and they all seem to work OK. I then have a controller declared and defined as follows:
MyAngularApp.controller('MyController', MyController);
...
var MyController = function ($scope, Service1, Service2, $location, $modal, SignatureService) {
...
}
MyController.$inject = ['$scope', 'Service1', 'Service2', '$location', '$modal', 'SignatureService'];
I am simply using the slightly unconvcentionaly manner of defining the servivce and injecting it that is standard in the app I am working on, as this works for all existing services, and I would prefer to simply slot mine in as per standard.
When the controller loads, I get an [$injector:unpr] in the browser console, with the error info:
$injector/unpr?p0=$scopeProvider <- $scope <- SignatureService
You can't inject $scope into your custom service. It just doesn't make sense since SignatureService can be injected anywhere including other services and other controlles. What $scope is supposed to be if you say inject it into two nested controllers, which one should be injected?
Scope object ($scope) is always associated with some DOM node, it is attached to it. That's why you see $scope in controllers and directives. And this is the reason why you can't have it in service: services are not related to specific DOM elements. Of course you can inject $rootScope but this is unlikely what you need in your question.
Summary: $scope is created from the $rootScope and injected in necessary controllers, but you can't injected it into custom service.
UPD. Based on comments you want to use service to define reusable controller methods. In this case I would go with what I call mixin approach. Define methods in the service and mix them in the necessary controllers.
app.service('controllerMixin', function() {
this.getName = function() {
alert(this.name);
};
});
and then extend controller scope with angular.extend:
app.controller('OneController', function($scope, controllerMixin) {
angular.extend($scope, controllerMixin);
// define controller specific methods
});
app.controller('TwoController', function($scope, controllerMixin) {
angular.extend($scope, controllerMixin);
// define controller specific methods
});
This is pretty effective, because controllerMixin doesn't have any notion of $scope whatsoever, when mixed into controller you refer to the scope with this. Also service doesn't change if you prefer to use controllerAs syntax, you would just extend this:
angular.extend(this, controllerMixin);
Demo: http://plnkr.co/edit/ePhSF8UttR4IgeUjLRSt?p=info
I ran into issues writing jasmine tests for an AngularJS application using angular ui-router. My services and app get initialized properly in the test, but the controllers do not start up properly. I've taken the application in question out of the equation and reduced the problem to a simple one controller example that exhibits the same behavior. Here's the actual test code:
describe('Test', function() {
var async = new AsyncSpec(this);
var scope = {};
beforeEach(angular.mock.module('TestApp'));
beforeEach(angular.mock.inject(function($rootScope, $state, $templateCache) {
scope.$rootScope = $rootScope;
scope.$state = $state;
$templateCache.put('start.html', '<div class="start"></div>');
}));
async.it('Test that TestCtrl is initialized', function(done) {
scope.$rootScope.status = { done: false };
scope.$rootScope.$on('$stateChangeSuccess', function(event, state, params) {
expect(scope.$rootScope.status.done).toBe(true);
done();
});
scope.$state.transitionTo('start', {}, { notify: true });
scope.$rootScope.$apply();
});
});
Here's the complete runnable test
The application gets initialized correctly, the ui router is able to transition the application to the correct state, but the controller does not get initialized. I need the router to initialize the controllers as the router passes critical configuration to them. I want to avoid duplicating that configuration in the tests.
I must be missing something, but what? I appreciate any and all input, thanks!
You need to use the $controller service to instantiate your controller in your tests and pass it your scope. For example...
ctrl = $controller('TestCtrl', {$scope: scope});
Notice that I also moved the declaration of $rootScope.done to the TestCtrl to prevent an error about $rootScope.done being undefined. Here's the fiddle...
http://jsfiddle.net/C8QtB/3/