Modify $rootscope property from different controllers - angularjs

In my rootscope I have a visible property which controls the visibility of a div
app.run(function ($rootScope) {
$rootScope.visible = false;
});
Example HTML:
<section ng-controller='oneCtrl'>
<button ng-click='toggle()'>toggle</button>
<div ng-show='visible'>
<button ng-click='toggle()'>×</button>
</div>
</section>
Controller:
var oneCtrl = function($scope){
$scope.toggle = function () {
$scope.visible = !$scope.visible;
};
}
The above section works fine, the element is shown or hide without problems. Now in the same page in a different section I try to change the visible variable to show the div but it doesn't work.
<section ng-controller='otherCtrl'>
<button ng-click='showDiv()'>show</button>
</section>
Controller:
var otherCtrl = function($scope){
$scope.showDiv = function () {
$scope.visible = true;
};
}

In AngularJS, $scopes prototypically inherit from their parent scope, all the way up to $rootScope. In JavaScript, primitive types are overwritten when a child changes them. So when you set $scope.visible in one of your controllers, the property on $rootScope was never touched, but rather a new visible property was added to the current scope.
In AngularJS, model values on the scope should always "have a dot", meaning be objects instead of primitives.
However, you can also solve your case by injecting $rootScope:
var otherCtrl = function($scope, $rootScope){
$scope.showDiv = function () {
$rootScope.visible = true;
};
}

How familiar are you with the concept of $scope? It looks to me based on your code that you're maintaining two separate $scope variables called "visible" in two different scopes. Do each of your controllers have their own scopes? This is often the case, in which case you're actually editing different variables both named "visible" when you do a $scope.visible = true in different controllers.
If the visible is truly in the rootscope you can do $rootScope.visible instead of $scope.visible, but this is kind of messy.
One option is to have that "otherCtrl" code section in a directive (you should probably be doing this anyway), and then two-way-bind the directive scope to the parent scope, which you can read up on here. That way both the directive and the page controller are using the same scope object.
In order to better debug your $scope, try the Chrome plugin for Angular, called Batarang. This let's you actually traverse ALL of your scopes and see the Model laid out for you, rather than just hoping you're looking in the right place.

Related

Programmatically creating new instances of a controller

So here is my problem, I have some functions/variables in a parent controller
function parentController($scope) {
$scope.numberOfChildren = $scope.numberOfChildren + 1 || 1;
console.log($scope.numberOfChildren);
$scope.someFunction = function(argument) {
// do stuff
$scope.someVariable = result of the function
}
}
I am calling this controller in two other controllers that are directives controllers and are called in the same view
function firstChildController ($scope, $controller) {
var aVariable = 1;
$scope.otherVariable = 10;
$controller('parentController', {$scope: $scope});
$scope.someFunction(aVariable);
}
function secondChildController ($scope, $controller) {
var aVariable = 6;
$scope.otherVariable = 11;
$controller('parentController', {$scope: $scope});
$scope.someFunction(aVariable);
}
What I want, is not to share the parent scope for the two children.
Right now, there is only one instance of the parent controller and so when I call two directives depending on it on the same view, I get $scope.numberOfChildren === 2.
What I want is this parent controller to be loaded twice but have separated scopes ($scope.numberOfChildren === 1 in each child controller)
I managed to do this using ng-controller in the view template and deleting the $controller calls but I want to do it programmatically. (I don't want to have to write the same ng-controller code each time I am calling the directive).
<div ng-controller="parentController">
<first-directive></first-directive>
</div>
<div ng-controller="parentController">
<second-directive></second-directive>
</div>
Finally, to keep homogeneity in the code of the project, I'd rather not use the this and vm stuff to do the job if it possible.
parentController does NOT have its own scope, it operates on the $scope you're passing to it when you instantiate it this way $controller('parentController', {$scope: $scope}).
Checkout this simple demo fiddle.
The problem in your case might be caused by directives sharing the same scope and, thus, passing the same scope to the parent controller.
What you expect is exactly same with the way system run: scope is not sharing between two controller.
When you use a ng-controller in html, a new scope (controller instance) will be created. From your code above, two controller instance will be created. You can see it by adding {{$id}} to html and see id of scope instance.
<div ng-controller="parentController">
{{$id}}
<first-directive></first-directive>
</div>
<div ng-controller="parentController">
{{$id}}
<second-directive></second-directive>
</div>
If you see {{numberOfChildren == 2}} mean that your code is wrong in somewhere, not by sharing scope issue.

Tabs directive not exposing an api in my scope

So I'm trying the Tabs directive and having some problems.
the structure is something like:
//routes
$routeProvider..when('/course/:id', {
controller: 'CourseCtrl',
templateUrl: '/app/views/course.html'
});
//course.html
<div ng-controller="CourseTabsCtrl">
<tabset>
<tab>
<tab-heading>Title</tab-heading>
<div ng-include="'/view.html'"></div>
</tab>
....
</tabset>
</div>
Problem is i can't access the api to enable or disable tabs, select a tab, in none of the controllers CourseTabsCtrl or CourseCtrl.
Is this because the directive is working on an isolated scope? and if so, is there a way to get around that? How can i fix it?
Thanks
Looking at the source and the documentation, you should be able to pass in an expression to the <tab> directive, that dictates whether it is enabled or disabled.
app.controller('CourseTabsCtrl', function ($scope) {
$scope.expr = true;
});
<div ng-controller="CourseTabsCtrl">
<tabset>
<tab disabled="expr">
<tab-heading>Title</tab-heading>
<div ng-include="'/view.html'"></div>
</tab>
....
</tabset>
</div>
If however, this is not enough for you. You could 'hack' the tabset directive and use another controller than the one currently specified. Now, you would have to replicate the old behaviour of the default TabSetController, but you could add functionality on top of it to cater to your needs.
The best way (I've found) to do this is to decorate the directive itself.
Like so:
app.config(function ($provide) {
$provide.decorator('tabSetDirective', function ($delegate) {
// $delegate in a directive decorator returns an array. The first index is the directive itself.
var dir = $delegate[0];
dir.controller = 'CourseTabsController';
return $delegate;
});
});
You could build further on this and pass in the controller to the directive itself.
<tabset ctrl="someCustomCtrl"></tabset>
The config block would then look like this:
app.config(function ($provide) {
$provide.decorator('tabSetDirective', function ($delegate) {
// $delegate in a directive decorator returns an array. The first index is the directive itself.
var dir = $delegate[0];
dir.controller = function ($scope, $element, $attrs, $controller) {
return $controller($attrs.ctrl, {
$scope: $scope
});
};
return $delegate;
});
});
Note: If you go with the decorator way, you may have to do it in the config block for the angular-ui module. In that case, you will probably want to have a look here, to be able to configure third party modules without touching their core code.
A second gotcha to the passed-in controller way, is that you need to make use of $injector in order to get dependencies (apart from $scope, $element and $attrs) into the controller. This can either be done in the config block, or in the controller itself by adding $injector as a dependency, like so:
app.controller('CourseTabsController', function ($scope, $injector) {
var $timeout = $injector.get('$timeout');
// etc...
});
What the f* am I on about?
Given that I don't personally work with the AngularUI Bootstrap components, and the fact that there is no plunker/jsBin available I'm throwing out some tips n tricks on how to add custom behaviour to third party components, without polluting their core code.
To address the questions at the end of your post:
Is this because the directive is working on an isolated scope?
It very well might be, the idea of isolated scopes is to not pollute the outside world with their inner properties. As such, it's highly likely that the only live 'endpoint' connected to the <tab> directive API is the default AngularUI TabSetController.
... and if so, is there a way to get around that? How can i fix it?
You can either do what I've suggested and roll your own controller (bare in mind that you should duplicate the code from the TabSetController first), that way you should have full access to the endpoint of the <tab> directive. Or, work with the options that are available to the directive as of this writing and wait for some more functionality to be introduced.
I'll try to fire up a jsBin soon enough to further illustrate what I mean.
Edit: We can do the whole decorator dance and duplication of the old controller behaviour, without the need to pass in a new controller. This is how we would achieve that:
app.config(function ($provide) {
$provide.decorator('tabSetDirective', function ($delegate, $controller) {
// $delegate in a directive decorator returns an array. The first index is the directive itself.
var dir = $delegate[0];
var origController = dir.controller;
dir.controller = function ($scope) {
var ctrl = $controller(origController, { $scope: $scope });
$scope.someNewCustomFunction = function () {
console.log('I\'m a new function on the ' + origController);
};
$scope.someNewCustomFunction();
return ctrl;
};
return $delegate;
});
});
Here's a jsBin illustrating the last example: http://jsbin.com/hayulore/1/edit

ng-model not updating the value to object using templates

I am working on an MVC application using Angular. I need to open various bootstrap modal in one of the application pages. for that i just wrote a simple angular service to get the template for modal from a folder called templates and load at Run-time. Everything works fine except one thing. ng-model is not working for check box controls and DropDownList(select) items.
service to load template:
var defaultPath = "/app/services/dialog/templates/";
function _loadModalTemplate(templateName) {
var defer = $q.defer();
if (angular.isUndefined($templateCache.get(templateName))) {
return $http.get(defaultPath + templateName).then(function (data) {
$templateCache.put(templateName, data.data);
return defer.resolve();
});
} else {
return $.when($templateCache.get(templateName));
}
return defer.promise;
}
Controller
notebook.controller('createworkitemcontroller', ['$scope', '$modalInstance', 'workitemDataContext', 'common', 'options',
function ($scope, $modalInstance, workitemDataContext, common, options) {
$scope.activities = options.activities || [];
$scope.activity = $scope.activities[0];
}]);
Template HTML
<div class="col-lg-6">
<label class="text-xs">Activity</label>
<select class="form-control input-sm" data-ng-options="a.Name for a in activities" data-ng-model="activity"></select>
</div>
Data Binding is working fine but its not updating the property $scope.activity when any change is made. Same case with checkboxes as well but working with TextBox
I'm wondering if the assignment is breaking the chain of scope. Try adding your variables inside a dedicated object like $scope.model = {activity: ...} and see if it works. I ran into this when I started using multiple modals and controllers. Here's a fiddle I made a while back demonstrating the concept:
http://jsfiddle.net/6XDtN/
http://jsfiddle.net/6XDtN/1/
In the first one, the parent is oblivious because the child re-defined the variable, breaking the chain of scope. It isn't obvious it re-defined the variable, but there's no way to really re-assign a value without doing so.
In the second one, the complex type (i.e. an object), is not redefined, just a property is updated. Thus, the chain of scope is strong in this one.
Hope this helps!

How to interact with isolate scope variable within a directive controller?

I have directive myDirective, that has an two-way binding isolate scope. When the user clicks a button, I want to change the isolate scope to be a value. I thought isolate scopes were bound to the $scope, but I am wrong. How do I 'grab' and interact with that isolate scope? Are they not attached to the directive controller's scope?
angular.module("app", [])
.controller("myCtrl", function($scope){
$scope.ctrlTwoway = "Eggs";
})
.directive("myDirective", function(){
return {
scope: {
twoway: =
},
template: "<button ng-click="changeTwoway()">Change two way isolate scope</button>",
controller: function($scope, $element, $attrs){
$scope.changeTwoway = function(){
// get twoway from isolate scope, and update the value with "bacon"
// $scope.twoway = "bacon" doesn't work
// nor does $attrs.twoway = "bacon" work, either :(
};
}
}
});
And the HTML
...
<div my-directive twoway="{{ctrlTwoway}}"></div>
Current value: {{ctrlTwoway}}
I created a plunker with working version.
You don't need to put {{variable}} on on the twoway="". Just change to twoway="ctrlTwoway" to work.
Another thing is that the way that you declare the binding. You are using = instead of '='.
Another thing is: try to use the link function instead of controller function in directives. It's a good practice and the right place if you want to manipulate DOM elements.
Source
I hope it helps.

Difference between adding functions to $scope and this in directive controller?

When going through egghead video, the one about Directive to directive communication suggests we use controller to add functions to 'this' object and access it from other directives.
The full code used in the video: Adding functions to this object
The relevant controller code is as follows:
controller: function($scope){
$scope.abilities = [];
this.addStrength = function(){
$scope.abilities.push("Strength");
}
this.addSpeed = function(){
$scope.abilities.push("Speed");
}
this.addFlight = function(){
$scope.abilities.push("Flight");
}
},
I was wondering instead of adding functions to 'this' why not add it to the $scope itself especially when we are using isolated scope?
Code adding functions to $scope: Adding functions to $scope
The relevant controller code is as follows:
controller: function($scope){
$scope.abilities = [];
$scope.addStrength = function(){
$scope.abilities.push("Strength");
};
$scope.addSpeed = function(){
$scope.abilities.push("Speed");
};
$scope.addFlight = function(){
$scope.abilities.push("Flight");
};
},
Or why have the controller function at all. Why can not we use the link function to achieve the same result?
Adding functions to $scope in the link function: Using link funtciont instead of controller
The relevant controller and link function is as follows:
controller: function($scope){
$scope.abilities = [];
$scope.addStrength = function(){
$scope.abilities.push("Strength");
};
$scope.addSpeed = function(){
$scope.abilities.push("Speed");
};
$scope.addFlight = function(){
$scope.abilities.push("Flight");
};
},
I am pretty sure there is valid reason to use controller and this object. I am not able to understand why.
You are correct that you can expose functions in the link function and get the same results. Directive controllers are a bit of an odd bird, but as I've written more complex directives, I've settled on pushing as much behavior into controllers and leaving DOM-related stuff in the link function. The reason why is:
I can pass in a controller name instead of having the function inside my directive; things get cleaner IMO
The controllers can expose public APIs used inside of sibling or related directives so you can have some interop and encourage SoC.
You can isolate the controller testing apart from the directive compilation if you like.
I typically only introduce controllers when there are perhaps complex state transitions, external resources being handled (ie $http), or if reuse is a concern.
You should note that Angular 1.2 exposes 'controllerAs' on directives which allows you to directly consume the controller in directive templates and reduce a bit of the ceremony the $scope composition introduces.

Resources