Angularjs Controller destructor - angularjs

I have an AngularJs app. I use Controllers for some child scopes. In every Controller I can set a number of variables that belong to the corresponding Child Scope. When AngularJs instantiate a controller, there is a constructor where I can set a default value to my child-scope variables.
Do I have a controller "destructor"? How do I know when a controller is closing and the scope is being cleaned (destroyed by the $destroy function)?
Thanks!

You have to listen to the $destroy event, e.g.:
function MyController($scope, ...) {
...
$scope.$on("$destroy", function handler() {
// destruction code here
});
}
Relevant docs: https://docs.angularjs.org/api/ng/type/$rootScope.Scope

Related

Access view controller data from app controller in Angular

I use ng-view to display a view inside my main web page. This view has it's own controller and in there I populate ui-grid data. In my outer page I have import/export buttons which seem like they use the app's controller. So how can I access the views data (it's grid object) inside it's controller from the app's controller?
I'm using Angular 1.
<body ng-app="myApp" ng-controller="myController" style="background-color: yellow;">
<!-- the header that the entire app will use no matter what view is displayed -->
<div ng-include="'Views/header.htm'"></div>
<div style="height: 100%;" ng-view></div>
</body>
I have a button inside header.htm. When it's pressed it seems to be at the myApp scope as that's the function that gets raised. Inside that I need to tell whatever view controller is loaded to do something.
You can use $broadcast service to let your view controller know about click event in app controller.
angular.module('app').controller('AppController', ['$scope', '$rootScope', function($scope, $rootScope){
$scope.exportList = function() {
$rootScope.$broadcast('exportData', { object: test}); // If data needs to be passed you can pass it in event
};
}])
angular.module('app').controller('ViewController', ['$rootScope', function($rootScope){
$rootScope.$on('exportData', function(event, data){
if(data.object) {
// Export logic here
}
});
}])
Call exportLink function on ng-click in your view.
You have mentioned in question that your data is populating in ui-grid from ViewController, so you don't need parameters to be passed from view as you already have it in your $scope variable. If needed, use event argument as shown.
You can use broadcast to send event to the child scope and listen back using on. There's another one , $emit. Since you want to pass the event/messages to the child scope/downward you must use broadcast, $emit is the reverse.
Keep in mind : $emit dispatches an event upwards through the scope hierarchy, while $broadcast dispatches an event downwards to all child scopes
myController.js
$scope.buttonClick = function() {
$rootScope.$broadcast('pass-event');
}
Received event
ViewController.js (eg)
$rootScope.$on('pass-event', function (event, args) {
// do what you want to do
});

Is it possible to get access to a controller without declaring it?

I have a bit of a weird situation which requires me to pass my controllers to my directives via a directive scope variable. This works great as long as there are only one controller in use per route, which is declared in my $routeProvider.
But now I have to have 2 controllers in use in the same template, which causes problems because I can't declare my controllers using ng-controller because that will throw a routeProvider error since I'm trying to access data from my route resolve. (You can only access route resolve data if you declare the controller in the same route as the resolve, which then makes using ng-controller in the template and controller in the directive useless to me).
So this is what I want to do:
// Declare one controller in the routing
.when('/someroute', {
controller: 'MyCtrl'
}
// But pass a different controller to my directive that hasn't been declared
// in either the route, template or directive
<my-directive ctrl="MyOtherCtrl"></my-directive>
But my question is, is it possible to access a controller and its functions without declaring the controller as ng-controller, controller in directive or in route? My far-fetched idea is that there's a service or something that you can inject which holds all of the controllers, but so far I've come up with none.
You could inject th controller by name into your directives controller using $controller.
var someOtherController = $controller('SomeOtherController ',{$scope: $scope});
It's methods would now be available on $scope. Be careful with this though, things can get hairy quickly.
There is a service to get an instance of any controller, use the $controller
in your directive, inject the $controller service then use it in your link function:
myApp.directive("myDirective", function($controller){
return {
scope: {
ctrl: "#"
},
link: function(scope, element, attrs){
var myNeededCtrl = $controller(scope.ctrl, {$scope: scope, otherDepenciesThatTheControllerNeed: ...});
myNeededCtrl.doSomething();
}
};
});

Why Injecting Scope and RootScope to Controllers in angularjs

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.

How can I trigger `$on` events in AngularJS Karma unit tests?

I am attempting to trigger the $scope.$on() method in a controller that is fired when I $rootScope.broadcast() an event. I found this question useful: How can I test events in angular?, but I'm still having trouble detecting an event being broadcast from the $rootScope up through a controller's $scope.
So far I've managed to test that the $broadcast method is called on a corresponding $rootScope, but not that the $on method was called on a corresponding $scope when $broadcast is called on the $rootScope.
I attempted to $rootScope.$broadcast directly in my test, but my spy is not picking up on the event.
This is my controller:
angular.module('app.admin.controllers.notes', [])
.controller('NotesCtrl', function($scope) {
$scope.$on('resource-loaded', function(event, resource) { // I want to test this
$scope.parentType = resource.type;
$scope.parentId = resource.id;
});
});
This is my test:
describe('The notes controller', function() {
beforeEach(module('app.admin.controllers.notes'));
var scope, rootScope, NotesCtrl;
beforeEach(inject(function($controller, $injector, $rootScope) {
rootScope = $rootScope;
scope = $rootScope.$new(); // I've tried this with and without $new()
NotesCtrl = $controller('NotesCtrl', {$scope: scope}); // I've tried explicitly defining $rootScope here
}));
it('should respond to the `resource-loaded` event', function() {
spyOn(scope, '$on');
rootScope.$broadcast('resource-loaded'); // This is what I expect to trigger the `$on` method
expect(scope.$on).toHaveBeenCalled();
});
});
And here's the plunkr. I've included a passing test of the $broadcast method for reference, mainly because I setup the tests in the same manner.
I've read quite a few questions relating to testing events in AngularJS, and it always seems to be a scoping issue. I've heard that in Karma unit testing, $rootScope and $scope are the same thing, but I'm not really sure what the implication is. I've tried defining the $rootScope and the $scope as the same object, as well as explicitly injecting the $rootScope into the NotesCtrl during testing, but nothing makes my test go green.
How can I get the $on method in my NotesCtrl to fire for this test?
What makes it not working is the fact that you're spying the $on function. It works fine when not sying it: http://plnkr.co/edit/hNEj7MmDDKJcJ7b298OB?p=info. And the reason is actually simple. When an event is brodcasted, what is called is not the $on() function. What is called is the callback function passed as argument to $on() previously: the listener.
Note that, by spying the $on function, you're not testing your code here. All you're trying to test is that when broadcasting en event, child scopes receive it. So you're testing AngularJS itself.
Try to use
$rootScope.$emit('resource-loaded');
Works fine in my tests.
#kirill.buga
Using $broadcast is right, not $emit because :
https://docs.angularjs.org/api/ng/type/$rootScope.Scope
Dispatches an event name upwards through the scope hierarchy notifying the registered $rootScope.Scope listeners.
Problem of #ben-harold is trying to spy $on instead of the result of code in the $on.

AngularJS: when creating a new directive, why there is a controller?

To my understanding, controller is responsible for preparing the model, and pass the model to the directive which is responsible for updating DOM.
So when creating a new directive, why there is a controller inside?
Does this mean that I can do something like connecting server inside a directive?
app.directive('hover', function () {
return {
restrict: 'E',
controller: function ($scope) {
// what is the controller for?
...
}
}
}
});
From the docs on $compile service:
controller
[…] The controller is instantiated before
the pre-linking phase and it is shared with other directives (see
require attribute). This allows the directives to communicate with
each other and augment each other's behavior. The controller is
injectable (and supports bracket notation) with the following locals:
$scope - Current scope associated with the element
$element - Current element
$attrs - Current attributes object for the element
$transclude - A transclude linking function pre-bound to the correct transclusion scope. The scope can be overridden by an optional first argument.
function([scope], cloneLinkingFn).

Resources