I am getting errors in custome service if I use $scope, But it works fine with $rootScope. Can't we use $scope in custome service
app.service("myService", function ($rootScope) {
console.log($rootScope);
this.root = $rootScope;
});
app.run(function (myService, $rootScope) {
console.log($rootScope);
console.log(myService.root);
});
You can only get $rootScope injected to services and run function, because each child scope is inherited from its parent scope and the top level scope is rootScope. Since it would be ambigous to inject any scope. Only root scope is provided.
Services are created before controllers, so there is no child scope available to inject.
Related
I have developed an web application using Angular JS. I am getting few additional CR which needs to implemented in using TTD approach. We have return unit test cases using Jasmine and Karma. The challenge currently we face is when we try to write unit test case for multiple controllers. I have a main page return on Home Controller & its has an broadcast event in another controller. When i write a unit test case Object for the controller which has this broadcast event is not initialized.
Is there any way to inject the second controller as a dependent object. Answers with Reference Sample link or demo code is much appreciated.
You state you are using Jasmine and Karma so I assume you are unit testing. If you are "unit" testing you should test each controller individually while mocking, spy, all injected services.
beforeEach(inject(function ($rootScope, $controller) {
rootScope = $rootScope;
scope = $rootScope.$new();
controller = $controller('MyCtrl as ctrl', {
'$scope': scope
});
}));
it('', function(){
//Arrange
controller.counter = 0; // Your controller is listening on scope.$on to update this counter.
//Act
rootScope.$broadcast('xyz', {});
//Assert
expect(controller.counter == 1).toBe(true);
rootScope.$broadcast('xyz', {});
expect(controller.counter == 2).toBe(true);
rootScope.$broadcast('xyz', {});
expect(controller.counter == 3).toBe(true);
});
Just be careful with broadcast. Only a domain events (model updated/deleted/created), or something global (signin,signout) should travel over $broadcast. Otherwise, it should be replaced with a service + directive. An example is angular material https://material.angularjs.org/latest/api/service/$mdDialog that is 1 directive with a backing service that can be open/closed from anywhere.
You can inject any controller with the $controller service, e.g.
beforeEach(inject(function ($rootScope, $controller) {
scope = $rootScope.$new();
controller = $controller('MyCtrl', {
'$scope': scope
});
}));
see docs here:
https://docs.angularjs.org/api/ngMock/service/$controller
so what you do is inject that controller first, then your other controller. then the first controller will have been instantiated at the time the second gets instantiated.
I am new to angular, it seems to be possible to inject multiple controllers at once. However best practice is to generate a mock controller that behaves as you expect the second controller to behave. This reduces the number of things you are testing at once.
The following link may be helpful for creating a mock controller, http://www.powdertothepeople.tv/2014/08/28/Mocking-Controller-Instantiation-In-AngularJS-Unit-Test/ .
I got problem, I'm trying to get controller instance in my service:
myAppModule.service('modalService',
function ($modal, $controller) {
var controller = $controller('ExperienceDetailsModalCtrl');
});
but I've got error:
TypeError: Cannot read property '$scope' of undefined
Is it possible to access controller (defined in another file) and pass it to modal?
My controller:
myAppIndexModule
.controller('ExperienceDetailsModalCtrl', function ($scope) {
});
You can't access controller scope in service, factory or provider. The data which you wanted to share should be placed inside a service. & make it available to other controller.
I think you do want to pass controller scope to $modal then you can achieve this by doing from controller itself.
$modal.open({$scope: $controller('ExperienceDetailsModalCtrl', {$scope: $scope}), templateUrl: 'abc.html'})
Update
You could do it like below
myAppModule.service('modalService',
function ($modal, $controller, $rootScope) {
var scope = $rootScope.$new(true);
$controller('ExperienceDetailsModalCtrl',{scope: $scope });
//in scope now you will have ExperienceDetailsModalCtrl scope
});
In Angular JS controllers, why do I have to inject both the scope and root scope, can't a controller have its scope as a child of the rootscope, and be injected by default, as in the view, I can always reference the attributes of both the scope and root scope as naked variables.......Why is this not for the controller too and even applied to the services as well?
Yes your thoughts are right about $rootScope. As per Angular Js documentation
Every application has a single root scope. All other scopes are descendant scopes of the root scope. Scopes provide separation between the model and the view, via a mechanism for watching the model for changes. They also provide an event emission/broadcast and subscription facility.
But reason of injecting is , thats how only Angular Works , injector functionality of Angular look for all injected dependency and create a reference of object. As per documentation also :
A root scope can be retrieved using the $rootScope key from the $injector.
Actually , More users confused why both are used in controller
$scope is used for communicate between controller and view. $scope binds a view (DOM element) to the viewmodel
But rootscope, There is only one rootscope in the app and it is shared among all the components of an app. $rootscope a global variable. all others $scopes are children of that $rootScope.
For Example
there are two controllers both have scope
var app = angular.module('myApp', []);
app.controller('Ctrl1', function ($scope, $rootScope) {
$scope.msg = 'World';
$rootScope.name = 'AngularJS';
});
app.controller('Ctrl2', function ($scope, $rootScope) {
$scope.msg = 'Dot Net Tricks';
$scope.myName = $rootScope.name;
});
rootscope only availble for all controllers but scope didn't get from another controller
Note
When you use ng-model with $rootScope objects then AngularJS updates
those objects under a specific $scope of a controller but not at
global level $rootScope. Create a private $scope for each controller
to bind it to the view.
I have a controller "MyController" and a directive "MyDirective" in separate modules "app" and "directiveModule" respectively. DirectiveModule has been injected into the angular.module of "app".
The issue I am having is as part of "app", I have a controller that emits an event "TEST" that the controller for the directive does not pick up. How can I successfully get the directive of its own module to catch the emit? Is this possible? (Note: I tried $scope originally, then used $rootScope, but both do not make a difference.)
My controller:
app.controller('MyController', function($scope, $rootScope) {
$rootScope.$emit('TEST');
});
My directive:
directiveModule.directive('MyDirective', ['$rootScope', 'MyService',
function($rootScope, MyService) {
return {
restrict: 'E',
controller: ['$scope', '$rootScope', function($scope, $rootScope) {
$rootScope.$on('TEST', function() {alert("Event Caught")})
}]};
}];
UPDATE: It looks like my directive has not initiated by the time the event is broadcast. Is there a way to make it such that I could "wait for directive to instantiate" as opposed to waiting an arbitary "1000" ms or another alternative?
I think the only case your directive needs to catch that event is to get some initial data depending on the MyController because if your directive is independent of the MyController, you don't need to catch that event, just initiate the directive independently.
My solution is using $watch to get notified when the initial data from the MyController is ready.
app.controller('MyController', function($scope) {
$scope.data = {}; //initialize the data, this data could be fetched from an ajax
});
directiveModule.directive('MyDirective', ['MyService',
function(MyService) {
return {
restrict: 'E',
controller: ['$scope', function($scope) {
$scope.$watch("data",function(newValue,OldValue,scope){
//do your work here when data is changed.
});
}]};
}];
In case you use isolate scope in your directive:
directiveModule.directive('MyDirective', ['MyService',
function(MyService) {
return {
restrict: 'E',
scope : {
directiveData:"=",
},
controller: ['$scope', function($scope) {
$scope.$watch("directiveData",function(newValue,OldValue,scope){//watch on the directive scope property
//do your work here when data is changed.
});
}]};
}];
Your html may look like this:
<my-directive directive-data="data" /> //bind isolate scope directive property with controller's scope property.
I think below questions are some similar cases like this:
AngularJS : Directive not able to access isolate scope objects
AngularJS : directives and scope
all modules injected into main module share same rootScope. $emit however travels up the scope tree, you need $broadcast.
If you $broadcast from $rootScope it will be received by all active scopes so in directive could actually use $scope.$on and wouldn't need to inject $rootScope just for the purpose of using $on
Similarly if directive is descendant of the controller, could also broadcast from controller scope
You can. Demo: http://plnkr.co/edit/vrZgnu?p=preview
A side note: if you try to communicate to children element that is lower in DOM tree, you should use $broadcast. $emit is used to communicate with parents elements ("up" direction).
But your true problem might be that when you tried to emit the event, the directive might not be initiated yet. I used $timeout service to wait 1000ms to broadcast the event.
Update
If you don't want to wait a arbitrary 1000ms, you can let your directive $emit "ready" event to inform controller that it's ready. Then broadcast from the controller.
Also removed the 1000ms in this demo http://plnkr.co/edit/GwrdIw?p=preview, everything still works. But I don't think this is a reliable solution.
I need current path from url in template (content of $location.path). But not via controller, because I have a lot of controllers (and I do not want to duplicate declaration of $scope.currentUrl = $location.path;). Thanks for the advice.
AngularJS template can only see what is available in a scope so you will need somehow to put $location service in a scope. There is one scope that is always available in AngularJS application called $rootScope so it could be use for your use-case.
What you could do is to use run() method of a module to expose $location in the $rootScope:
var myApp = angular.module('myApp', []).run(function($rootScope, $location) {
$rootScope.location = $location;
});
this would make 'location' available in all templates so later on you could do in your template:
Current path: {{location.path()}}
An alternative is to use the more semantic and versatile ui-router, then in the controller, retrieve the current state, and store it on the $scope:
app.controller('MyCtrl', ['$scope', '$state', function MyCtrl($scope, $state) {
$scope.state = $state.current.name;
...
}