I have noticed that in one of my controllers I am getting $rootScope and $scope injected, and they both point to the same object.
Furthermore, in all my other controllers, the $scope object is shared. So whenever I inject scope, it contains the properties/methods assigned to it in all the other controllers that have so far been instantiated.
This isn't an app that I worked on from the beginning and it's pretty massive. I haven't seen this behavior before and I don't know where to begin diagnosing it. Any ideas what is causing this?
The way that we are setting up our controllers/directives is pretty standard and it looks like this:
angular.module('myApp')
.directive('mainNav', function() {
return {
restrict: 'A',
templateUrl: 'scripts/directives/mainNav/mainNav.html',
controller: 'mainNavCtrl',
replace: true,
link: function(scope, element) {
//Do DOM-related stuff
});
}
};
})
.controller('mainNavCtrl', function($rootScope, $scope, $state) {
//Do controller stuff
});
We do also configure our app as follows:
angular.module('myApp', ['ui.router', 'kendo.directives'])
.config(function ($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise('/');
$stateProvider
.state('app', {
url: '/',
templateUrl: 'views/app.html',
resolve: {
//Fetch stuff
}
})
; });
In response to Kursad Gulseven's comment, this is what I'm seeing in Batarang:
The scope with ID 002 gets passed in as $scope and $rootScope to the first controller. When properties are added to $scope, they show up on $rootScope. Then all the other controllers are receiving the scope with ID 00A. So properties added to $scope in those controllers are visible to all other controllers getting $scope injected.
$rootScope and $scope are not the same object.
If you have an object named anObject in root scope and it has an attribute named anAttribute; then you can access that attribute using $scope like this:
$scope.anObject.anAttribute
That's because Angular looks up parent scopes if it cannot find an object in $scope.
UPDATE:
Your mainNav directive should have an inherited child scope by adding scope: true.
When "scope" is false, which is the default setting for directives, the parent controller and directive's controller share the same scope object. Setting scope true will create a new child scope for the directive.
Related
I got a directive which has a model passed by an attribute:
use strict;
angular.module('ebs-front')
.directive('ebsIa', function() {
return{
restrict: 'A'.
scope: {
opened: '=ebsIaOpened',
model: '=ebsIaModel',
cb: '&ebsIaCb'
},
controller: function($scope, $uibModal){
console.log('check');
$scope.text = { text: 'test'};
$scope.$watch('opened', function(newValue) {
if(newValue === true){
var modalInstance = $uibModal.open({
controller: 'ImpactAnalyseController',
templateUrl: 'common/directive/ebs-ia-template.html'
});
}
});
}
}
});
In this directive, I need to do some operations and then open a modal window. So for so good, but the thing is, I want the $scope.model to be accessible in ImpactAnalysisController as well.
My assumption was that $scope.test and $scope.model will be available in ImpactAnalysisController automatically, but apparently a isolated scope is created which is only valid for the controller: function part.
What would be a good way to pass the model variable of the scope to the ImpactAnalysisController?! And why isn't it default behaviour in angular?
If I define my directive like below, then the removeFromFilters (in this case) IS available in the directive, so I'm kinda puzzled. Any help would be appreciated...
use strict;
angular.module('ebs-front')
.directive('ebsIa', function() {
return{
restrict: 'A'.
scope: {
opened: '=ebsIaOpened',
model: '=ebsIaModel',
cb: '&ebsIaCb'
},
controller: 'ImpactAnalysisController'
};
)};
There are several ways to share data between controllers in Angular. A couple that come to mind:
1- Use a $rootScope.broadcast('keyName', value) and listen for the value with $scope.on('keyName', function(){...} Use with care, not the best approach most of the time
2- Keep the data not in the controller but in a Service or Factory, and inject that into your controllers (preferable)
What would be a good way to pass the model variable of the scope to the ImpactAnalysisController?!
Depends on what the controller has access to and intends to do with it.
And why isn't it default behaviour in angular?
You're asking the wrong question. You chose an Isolate Scope. Why did you choose an Isolate Scope, if you wanted to inherit properties from its parent?
What may solve your problem:
If you're passing a pure model and expect to have some IO where the user is potentially altering the model I recommend reading and implementing: NgModelController
It will make the model and mechanisms to interact with it available to your directive(s) via an injectable Controller, independent of the type of scope you choose. All you have to do is require 'ngModel' according to $compile documentation.
Fixed with uibmodal's resolve functionality:
var modalInstance = $uibModal.open({
animation: $scope.animationsEnabled,
templateUrl: 'myModalContent.html',
controller: 'ModalInstanceCtrl',
size: size,
resolve: {
items: function () {
return $scope.items;
}
}
})
Item is passed from the parent scope to the ModalInstanceCtrl and becomes available in the controller as variable. Exactly was I was looking for!
I have an Angular app using ui-router with the following states defined in my app.
$stateProvider
.state('classes', {
url: '/classes/:type',
templateUrl: 'classes.html',
controller: 'ClassesCtrl',
})
.state('classes.detail', {
url: '/:id',
views: {
"#": {
templateUrl: 'class.html',
controller: 'ClassCtrl'
}
}
});
and my controllers look like:
app.controller('ClassesCtrl', function($scope, $stateParams){
$scope.foo = "bar";
});
app.controller('ClassCtrl', function($scope, $stateParams){
console.log($scope.foo);
});
As you can see, class.detail hides the parent view by targeting the root level unnamed ui-view. The issue I'm having is that the child is not inheriting the parent's scope (I can't access $scope.foo in class.detail). It seems like the parent state gets destroyed when I go to the child state because if I click back, it has to reload all the data.
How do I effectively hide the parent view but still access the parent data?
From the ui-router documentation:
Scope Inheritance by View Hierarchy Only
Keep in mind that scope properties only inherit down the state chain if the views of your states are nested. Inheritance of scope properties has nothing to do with the nesting of your states and everything to do with the nesting of your views (templates).
Well, you're using # to define your view but your parent state is named, so you should name it #classes
Edit: I made this fiddle to explain about names.
In this ng-book JSBin, does $scope.$watch() resolves to $rootScope.$watch() due to prototypal inheritance.
Can we inject $rootScope explicitly inside the controller so that $scope would be same as $rootScope inside the controller, without going through prototypal inheritance?
Replicating code here for reference:
// open this example and type person.name into the test field
angular.module('myApp', [])
.controller('MyController',
['$scope', '$parse', function($scope, $parse) {
$scope.person = {
name: "Ari Lerner"
};
$scope.$watch('expr', function(newVal, oldVal, scope) {
if (newVal !== oldVal) {
// Let's set up our parseFun with the expression
var parseFun = $parse(newVal);
// Get the value of the parsed expression, set it on the scope for output
scope.parsedExpr = parseFun(scope);
}
});
}]);
Just inject it the same way as $scope or $parse, anything defined on $rootScope will be then accessible inside your controller.
app.controller('MyController', ['$scope', '$parse', '$rootScope',
function($scope, $parse, $rootScope) {
$rootScope.foo();
console.log($rootScope.bar);
}
]);
etc.
If you intend to use the rootScope so badly, it has a provider just as scope does. Including '$rootScope' into your controller as you do with the '$scope' does the trick.
There's also the $parent attribute of the $scope which could come in handy, but IMO it tends to make code less maintainable if abused. In particular when multiple scopes are nested, as you need to traverse the whole hierarchy.
I want t expose images storage URL to views so I can use it like this in all my views:
<img ng-src="{{storageUrl}}/logo.png"
Right now I have config service which I inject in my every controller, and then expose storageUrl variable to view via $scope.
angular.module('myApp').controller('MyCtrl', [
'$scope',
'AppConfigService',
function ($scope, AppConfigService) {
$scope.storageUrl = AppConfigService.storageUrl;
}
But the problem is that almost in every controller I need to inject this service and expose this varialbe to the view. I don't want to duplicate code so much. So i'm intersting in other ways to globally expose some config variable to the ALL views. What you can suggest?
Thanks
The "global" scope way
Set it on $rootScope. Although global scope is ill-advised.
Also, if you must use global scope ($rootScope) to track this, you can set it in a run() block, and it will be set as soon as the application is ready:
angular.module('myApp').run([
'$rootScope', 'AppConfigService',
function($rootScope, AppConfigService) {
$rootScope.storageUrl = AppConfigService.storageUrl
}
]);
The problem with global scope is that any other modules you load into your app could easily clobber your variable on $rootScope and it will be very hard to debug.
Better way: Use the service directly in an "outer controller":
app.controller('OuterCtrl', [
'$scope', 'AppConfigService',
function($scope, AppConfigService) {
$scope.config = AppConfigService;
}
]);
Then wrap your whole app in that controller:
<body ng-controller="OuterCtrl">
<div ng-controller="MyCtrl"> other stuff here </div>
</body>
Why does this work? Because all controllers and directives under this controller prototypically inherit their scope from this controller's scope.
One option is to create a directive for this, and use that directive everywhere instead of ng-src. Something like this:
myModule.directive('mySrc', ['AppConfigService', function(AppConfigService) {
return {
restrict: 'A',
compile: function(element, attrs) {
element.attr('src', AppConfigService.storageUrl + attrs.mySrc);
}
};
}]);
Then you can just use relative paths for your images everywhere
<img my-src="/logo.png" />
I just created a new directive, however the console is giving me an error. I believe there is a problem with dependencies as the directive is unable to see a method within the controller.
How can i fix this?
Error message:
Error: $scope.resetMessages is not a function
Controller:
angular.module('modulename').controller('controllerName', ['$scope', '$location', 'Global', 'Company', function ($scope, $location, Global, Company) {
/* A bunch of declarations and methods... */
$scope.resetMessages = function() {
$scope.errorMessage = null;
$scope.successMessage = null;
};
}]);
Directive:
angular.module('modulename').directive('directiveName', function() {
return {
restrict: 'E',
templateUrl: 'path/to/template.html',
scope: {
'Company': '&',
'Global': '&'
},
controller: function($scope) {
/*
A bunch of methods...
*/
}
};
});
As Anthony Chu alluded to, this section creates an isolated scope:
scope: {
'Company': '&',
'Global': '&'
},
That means it does NOT prototypically inherit from the parent scope, although the parent scope is available via $parent although, as Anthony mentions, this isn't a great idea because it creates coupling between the two, which you are presumably trying to sever with an isolated scope in the first place.
You shouldn't need an isolated scope at all in this case. To get access to Company and Global (both available in the Controller via dependency injection) you can just inject them into your directive too:
angular.module('modulename').directive('directiveName',
['Global', 'Company', function (Global, Company) {
// Return directive config here
}]);
Then you can omit scope: (anything) in the directive completely, and it will be the SAME scope as the controller, or if necessary, you can do scope: true to get a new scope that DOES prototypically inherit from the parent. In either case, $scope.resetMessages() would be available.
Here is a handy cheat sheet to the different directive scope options.