Angular scope variable is null when using ng-include (added a plunkr) - angularjs

The model in the $scope isn't there when I try to access it in the save function.
As per the code below: desc has the value from the text input, however desc2 does not.
Is there anything wrong with the code, or am I missing something when it comes to the use of the $scope?
Html:
<input type="text" ng-model="myModel.description">
JavaScript
MyApp.controller('MyCtrl', ['$scope', function($scope) {
$scope.save = function() {
var desc = this.myModel.description; // This has a value
var desc2 = $scope.myModel.description; // This does NOT have a value
};
}]);
EDIT:
Added plunker: http://plnkr.co/edit/3l9ZE7O2wpbfnH7XPA9X?p=preview
This problem seem to be related to using ng-include.
If I don't use ng-include then it seem to work fine.

ng-include element introduces a child scope. Now your myModel is attached to a child, but your controller's $scope is the parent. Parent scopes can't see their children's variables, but children can see their parents'.
You can add myModel = {}; to your controller and it should work fine. That way the child scope references his parents' myModel and doesn't create his own.

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.

What sense does have a new child scope inaccessible from parent controller? (created by ng- directives)

In angular.js Some directives create child scopes. (ng-include, ng-if, etc)
I know there are ways to solve it, for example by declaring the variable in the scope of the controller. Just uncomment //$scope.inner = '1234' and removeng-init="inner='1234'and will work.
Another solution would be to use a object in the parent scope containing the variable.
Still does not make sense to me.
What sense does have a scope without a controller?
What practical use have these new child scope?
This is my example.
var app = angular.module('app', []);
app.controller('ctrl', ['$scope', function($scope) {
$scope.result = "Result";
$scope.outer = "outer";
//$scope.inner = "1234";
$scope.test1 = function() {
if ($scope.inner) {
$scope.result = $scope.inner;
} else {
alert("inner is not accesible");
}
}
$scope.test2 = function() {
if ($scope.outer) {
$scope.result = $scope.outer;
} else {
alert("inner2 is not accesible");
}
}
}]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app" ng-controller="ctrl" >
<script type="text/ng-template" id="/tpl.html">
<input type="text" ng-init="inner='Inner'" ng-model="inner"></input>
<button ng-click="test1()">TEST1</button>
</script>
<div>
<ng-include src="'/tpl.html'"></ng-include>
<br/>
<input type="text" ng-model="outer"></input>
<button ng-click="test2()">TEST2</button>
<p>{{result}}</p>
</div>
</div>
First you need to understand that scopes and controllers are two separate concepts.
The scope is an object that refers to your application model while a controller is a constructor function that you use to manipulate the scope.
So, "from an Angular's point of view", it's perfectly acceptable to have a scope that is not augmented by a controller.
The idea for creating new child scopes is to have a logical way to separate the application's model. Could you imagine having only one scope for your entire application? You would have to be very careful not to override functions or properties while manipulating the scope in your controllers. Since child scopes prototypically inherit from their parent scope you don't have to worry about that.
One practical example of the usability of these child scopes is, for example, when you have two ng-repeat directives side-by-side, "under" the same scope. If they didn't create their own child scopes, how would you have access to the $index, $first, $last, etc... properties from each of the ng-repeat directives? Without child scopes both would be polluting the "parent" scope with the same properties, overriding each other.
You can read more information on scopes here and on controllers here.
Specifically for ngInclude this is by design: In many cases you want the included content to be isolated.
A scope really does make little sense if there is no js code that works with it, but that code may be in a controller or link function or (as in the case with ngInclude) a postLink function.
Also see How to include one partials into other without creating a new scope? which is almost a duplicate and has a workaround.

AngularJS referencing parent controller

I am trying to implement the following behavior. I am including a template using ng-inculde where I am doing something like this: {{something}}
I want that 'something' to have double binding with another variable in the parent controller's scope and to have the ability to set the name of property of the parent controller's scope. So on one include, something will reference an apple and on other include, something will reference an orange.
What I do is, that I wrote a custom controller which has a lookAt(v) method and i am calling this method in ng-init of the div where i use ng-include and ng-controller. In this method, I am trying to set the binding but it does not work. I assume, that parent scope already has that variable defined.
Here is my code:
mod.controller('FooController', ['$scope', function($scope) {
$scope.lookAt = function (variable) {
$scope.something=$scope[variable];
}
}]);
Thanks for any suggestion on how to solve this.
In your template that is pulled in with ng-include, instead of {{something}} use something(), where the something method is defined on the parent controller.
You can still init your variable name if you want:
mod.controller('FooController', ['$scope', function($scope) {
$scope.initMethod = function(variable) {
$scope.variable = variable;
}
$scope.something = function () {
return $scope[$scope.variable];
}
}]);
When using Controller As Syntax you can't just do $scope.$parent in the consumer (constructor for child) you need to address the parent's data-object like so: $scope.$parent.vmParent.

Modify $rootscope property from different controllers

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.

AngularJS - Access to child scope

If I have the following controllers:
function parent($scope, service) {
$scope.a = 'foo';
$scope.save = function() {
service.save({
a: $scope.a,
b: $scope.b
});
}
}
function child($scope) {
$scope.b = 'bar';
}
What's the proper way to let parent read b out of child? If it's necessary to define b in parent, wouldn't that make it semantically incorrect assuming that b is a property that describes something related to child and not parent?
Update: Thinking further about it, if more than one child had b it would create a conflict for parent on which b to retrieve. My question remains, what's the proper way to access b from parent?
Scopes in AngularJS use prototypal inheritance, when looking up a property in a child scope the interpreter will look up the prototype chain starting from the child and continue to the parents until it finds the property, not the other way around.
Check Vojta's comments on the issue https://groups.google.com/d/msg/angular/LDNz_TQQiNE/ygYrSvdI0A0J
In a nutshell: You cannot access child scopes from a parent scope.
Your solutions:
Define properties in parents and access them from children (read the link above)
Use a service to share state
Pass data through events. $emit sends events upwards to parents until the root scope and $broadcast dispatches events downwards. This might help you to keep things semantically correct.
While jm-'s answer is the best way to handle this case, for future reference it is possible to access child scopes using a scope's $$childHead, $$childTail, $$nextSibling and $$prevSibling members. These aren't documented so they might change without notice, but they're there if you really need to traverse scopes.
// get $$childHead first and then iterate that scope's $$nextSiblings
for(var cs = scope.$$childHead; cs; cs = cs.$$nextSibling) {
// cs is child scope
}
Fiddle
You can try this:
$scope.child = {} //declare it in parent controller (scope)
then in child controller (scope) add:
var parentScope = $scope.$parent;
parentScope.child = $scope;
Now the parent has access to the child's scope.
One possible workaround is inject the child controller in the parent controller using a init function.
Possible implementation:
<div ng-controller="ParentController as parentCtrl">
...
<div ng-controller="ChildController as childCtrl"
ng-init="ChildCtrl.init()">
...
</div>
</div>
Where in ChildController you have :
app.controller('ChildController',
['$scope', '$rootScope', function ($scope, $rootScope) {
this.init = function() {
$scope.parentCtrl.childCtrl = $scope.childCtrl;
$scope.childCtrl.test = 'aaaa';
};
}])
So now in the ParentController you can use :
app.controller('ParentController',
['$scope', '$rootScope', 'service', function ($scope, $rootScope, service) {
this.save = function() {
service.save({
a: $scope.parentCtrl.ChildCtrl.test
});
};
}])
Important:
To work properly you have to use the directive ng-controller and rename each controller using as like i did in the html eg.
Tips:
Use the chrome plugin ng-inspector during the process. It's going to help you to understand the tree.
Using $emit and $broadcast, (as mentioned by walv in the comments above)
To fire an event upwards (from child to parent)
$scope.$emit('myTestEvent', 'Data to send');
To fire an event downwards (from parent to child)
$scope.$broadcast('myTestEvent', {
someProp: 'Sending you some data'
});
and finally to listen
$scope.$on('myTestEvent', function (event, data) {
console.log(data);
});
For more details :- https://toddmotto.com/all-about-angulars-emit-broadcast-on-publish-subscribing/
Enjoy :)
Yes, we can assign variables from child controller to the variables in parent controller. This is one possible way:
Overview: The main aim of the code, below, is to assign child controller's $scope.variable to parent controller's $scope.assign
Explanation: There are two controllers. In the html, notice that the parent controller encloses the child controller. That means the parent controller will be executed before child controller. So, first setValue() will be defined and then the control will go to the child controller. $scope.variable will be assigned as "child". Then this child scope will be passed as an argument to the function of parent controller, where $scope.assign will get the value as "child"
<!DOCTYPE html>
<html>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular.min.js"></script>
<script type="text/javascript">
var app = angular.module('myApp',[]);
app.controller('child',function($scope){
$scope.variable = "child";
$scope.$parent.setValue($scope);
});
app.controller('parent',function($scope){
$scope.setValue = function(childscope) {
$scope.assign = childscope.variable;
}
});
</script>
<body ng-app="myApp">
<div ng-controller="parent">
<p>this is parent: {{assign}}</p>
<div ng-controller="child">
<p>this is {{variable}}</p>
</div>
</div>
</body>
</html>

Resources