So, I can change a model value from a child controller, but when the child controller is in ng-switch then it doesn't work, why? I created an example to demonstrate it.
One way to avoid this is to use the . in the model name, like bunnies.kills. Is this a bug or this is a feature ?
Using Angular 1.0.6
Using your code structure, in your child controllers you would need to change:
$scope.$parent.kills++;
to
$scope.$parent.$parent.kills++;
Explanation: MainCtrl's scope is the parent scope of SimpleParentCtrl, but the grandparent of Step1Ctrl and Step2Ctrl. As some others pointed out, ng-switch creates its own scope, and then your Step1Ctrl and Step2Ctrl each created a child scope of the ng-switch.
Note: Each time the 1 or 2 button is clicked, both the ng-switch and it's currently matched child controller get a new scope.
Also: In case you happen to be looking in the Angular source and wondering how the ng-switch directive creates its own scope without a scope property, the answer is that it does so manually in its link method via scope.$new(). The directives ng-include, ng-switch, ng-repeat, and ng-view all create new scope this way, either in the link method or the compile method's returned link function.
Resources:
https://github.com/angular/angular.js/wiki/The-Nuances-of-Scope-Prototypal-Inheritance
http://www.youtube.com/watch?v=ZhfUv0spHCY&feature=youtu.be&t=30m
ng-switch creates its own child scope, which is why #sh0ber's answer is one way to get it to work. In general, models should be referenced in controller scopes (hence reference objects), and not be not primitives. So using a . is a "best practice".
This is not a bug, but it is not a feature either. This is the way JavaScript prototypal inheritance works with primitives.
I would take a slightly different approach to this problem.
Rather than use $scope.$parent, I would recommend you move all of your bunny killing logic into a shared service/model.
Also, I would try to avoid referencing parent views/controllers. Referencing the parent can make it difficult to reuse your code and can be painful to debug as the project grows. It is okay for a parent to know about it's children but a child should know little to nothing about it's parent.
Here is an updated Plunk: http://plnkr.co/edit/PLDbfU8Fu7m59A42qdR6?p=preview
HTML
<body ng-controller="MainCtrl">
<p>
Dead bunnies: <strong>{{Elmer.deadWabbits}}</strong>
</p>
<div ng-controller="SimpleParentCtrl">
<button ng-click="Elmer.killTheWabbit()">Kill from simple parent gun</button>
</div>
<hr>
<div ng-switch="" on="step">
<div ng-switch-when="first" ng-controller="Step1Ctrl">
<button ng-click="Elmer.killTheWabbit()">Kill from 1 tab gun</button>
</div>
<div ng-switch-when="second">
<div ng-controller="Step2Ctrl">
<button ng-click="Elmer.killTheWabbit()">Kill from 2 tab gun</button>
</div>
</div>
</div>
<hr>
<p>
<button ng-click="changeStep('first')">1</button> <button ng-click="changeStep('second')">2</button>
</p>
</body>
JS
angular.module('plunker', []).
service("Elmer", [function() {
this.deadWabbits = 0;
this.killTheWabbit = function() {
this.deadWabbits++;
};
}]).
controller('MainCtrl', function($scope, Elmer) {
$scope.Elmer = Elmer;
$scope.step = 'first';
$scope.changeStep = function(name){
$scope.step = name;
};
}).
controller('SimpleParentCtrl', function() {}).
controller('Step1Ctrl', function() {}).
controller('Step2Ctrl', function() {});
One way to avoid this is to use the . in model name, like bunnies.kills. Is this a bug or this is a feature ?
This has been explained numberous times : https://github.com/angular/angular.js/wiki/The-Nuances-of-Scope-Prototypal-Inheritance
and in mhevery's video
Related
This question already has answers here:
What are the nuances of scope prototypal / prototypical inheritance in AngularJS?
(3 answers)
Closed 5 years ago.
I have a general template for all pages which contains a menu bar and it is outside the ng-view.From one of my page which is inside the ng-view i want to bind input data to this template area(this area is under a different controller than the input page).I mean when i will enter name,the name will appear to the template area.Is it possible ?
Here is the plunker
<body ng-app="sampleApp">
<div class="container">
<div class="row">
name is :{{name}}<br/>
username is :{{uname}}
<div class="col-md-3">
<ul class="nav">
<li> Add name </li>
<li> add username </li>
</ul>
</div>
<div class="col-md-9">
<div ng-view></div>
</div>
</div>
</div>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js"></script>
<script src="app.js"></script>
</body>
This issue with primitives can be easily avoided by following the "best practice" of always have a '.' in your ng-models – watch 3 minutes worth. Misko demonstrates the primitive binding issue with ng-switch.
Having a '.' in your models will ensure that prototypal inheritance is in play. So, use
<input type="text" ng-model="someObj.prop1"> rather than
<input type="text" ng-model="prop1">.
— AngularJS Wiki - What are the nuances of scope prototypal / prototypical inheritance?
The DEMO on PLNKR
$scope.obj is working like a $rootScope variable. Is it for prototypal inheritance?
Scopes are arranged in hierarchical structure which mimic the DOM structure of the application. Each AngularJS application has exactly one root scope, but may have any number of child scopes.
ng-app --> $rootScope
|- ng-controller --> $scope (container)
|- ng-view --> $scope (view)
By using: <input ng-model="obj.name" /> the ng-model directive in the view controller uses prototypical inheritance to find $scope.obj from outside the view. It can then get and set the name property of that object.
For more information, see AngularJS Developer Guide - Scope Hierarchies
$rootScope exists, but it can be used for evil
Scopes in AngularJS form a hierarchy, prototypically inheriting from a root scope at the top of the tree. Usually this can be ignored, since most views have a controller, and therefore a scope, of their own.
Occasionally there are pieces of data that you want to make global to the whole app. For these, you can inject $rootScope and set values on it like any other scope. Since the scopes inherit from the root scope, these values will be available to the expressions attached to directives like ng-show just like values on your local $scope.
Of course, global state sucks and you should use $rootScope sparingly, like you would (hopefully) use with global variables in any language. In particular, don't use it for code, only data. If you're tempted to put a function on $rootScope, it's almost always better to put it in a service that can be injected where it's needed, and more easily tested.
Conversely, don't create a service whose only purpose in life is to store and return bits of data.
— AngularJS FAQ - $rootScope exists, but it can be used for evil
Angular's $rootScope can be used to share information between the app's components (besides other uses). It is discouraged to rely upon it too much because it could become polluted or difficult to trace up and down the app's entire scope 'stack'. But if you really need or want to set 'global' data, it works:
In your new plunkr, you are using both ng-model and ng-value for the text input. Remove the ng-value altogether. (It is used typically for elements that have 'value' properties, like radio buttons and checkboxes, where the 'value' is 'checked' or 'selected', etc.) ng-model is what you want.
http://plnkr.co/edit/DnzOdRicXLHtg4DqoVdJ?p=preview
name is :{{$root.name}}
username is :{{$root.uname}}
and
Name: <input ng-model="$root.name">
<h1>You entered: {{$root.name}}</h1>
I am using ng-template in angularjs
<script type="text/ng-template" id="ng-wig/views/ng-wig.html">
<div class="ng-wig">
........
</div>
<script>
and i have a textarea using this ng-template.
<textarea ng-wig="content" ng-model="contents"></textarea>
but this ng-model cannot be accessed inside the controller.
please help.
As you are loading your textarea template inside ng-include, if you look at ng-include directive you will see that it does create a new scope which is prototypically inherited from the parent scope.
If you use any scope variable which are declared in controller won't be accessible inside the include div. In order to get the controller variable accessible inside ng-include the you must declare it as object like $scope.model= {} then you should declare the properties in it. like ng-mode="model.contents"
Markup
<textarea ng-wig="model.content" ng-model="model.contents"></textarea>
Controller
$scope.model = {};
Here you can find similar answer
There are several approaches apart from above by which you could solve this issue.
The other way around would be, you could use controller as approach in that way you can avoid. In that you need to use this inside a controller & use controller alias while showing variable on html like vm.contents here
Makrup
<div ng-controller="myCtrl as vm">
<div ng-include="'ng-wig/views/ng-wig.html'"></div>
</div>
Textarea
<textarea ng-wig="content" ng-model="vm.contents"></textarea>
Also you could point to the parent scope of ng-include which is nothing but controller scope just by doing $parent in your ng-model, this case your ng-model would be $parent.contents
Textarea
<textarea ng-wig="$parent.content" ng-model="$parent.contents"></textarea>
Note
Don't use this approach, use the 1st one which is more preferable.
I try to follow an example from http://blog.novanet.no/creating-multilingual-support-using-angularjs/
to make multilingual support using AngularJS. This example works well.
I try another way to implement that. I use ng-include to manage module separation of the header, content and footer by using the AngularJS ng-include.
My HTML structure is like this:
<div ng-include = "'header.html'"> </ div>
<div ng-include = "'content.php'"> </ div>
<div ng-include = "'footer.html'"> </ div>
I save my work on Plunker
My question is, when I try to implement multi-language, and put the selector of language in header.html and the result on content.php it is not working.
When your templates create models, they do so on the current scope, which isn't shared with your controller scope (as mentioned in a previous answer, ng-include creates a new child scope). Because of the way inheritance works, items added to this child scope are not visible to the parent (or siblings).
However, If you add items to an item already on the parent scope (google "dot notation angular"), those additions will be visible to the parent and siblings.
A good way to do this is use the controller-as syntax. I've updated your Plunk
<body ng-app="myApp" ng-controller="myController as vm">
app.controller('myController',['$scope', 'translationService',
function ($scope, translationService){
//Run translation if selected language changes
$scope.translate = function(){
translationService.getTranslation($scope, $scope.vm.selectedLanguage);
};
//Init
$scope.vm.selectedLanguage = 'en';
$scope.translate();
}]);
I have problem with scopes of controllers. I'm using controller as directive and I have code similar to this example:
<div ng-controller="ItemsController as itemCtrl">
<table> .. some data ... </table>
<a ng-click="itemCtrl.createItem()">Create new item</a>
</div>
<div id="create-form" ng-controller="ItemFormController as itemFormCtrl">
<form ng-submit="itemFornCtrl.saveItem()">... form inputs ...</form>
</div>
<div id="edit-items" ng-controller="MultipleItemsEdit as multiEditCtrl">
... table with some data ....
<!-- I need this -->
<a ng-click="itemCtrl.createItem()">Create new item</a>
<!-- -->
</div>
Basically there are 3 isolated scopes. But I need to break this isolation and call methods from one scope on another.
I'm currently using ugly "delegate" kind of hack.
Controllers and their methods are not so interesting, only interesting methods are ItemsController.createItem():
this.createItem = function(dataCollection) {
angular.element( $("#create-form) ).controller().createNewItem(dataCollection);
}
and ItemFormController.createNewItem(dataCollection):
this.createNewItem = function(dataCollection) {
... some initialization ....
$("#add-item").dialog( "open" );
}
I need to call createNewItem method on ItemFormController to show modal box. But I cannot do it directly, so I'm using method createItem which gets the create-form element and its controller and calls createNewItem method on it. It is kind of a delegate. But I don't like it, because I need to call createNewItem from many places of my code and I don't want to populate all my controllers with this kind of delegate methods.
Maybe I could make these delegates on some kind of root controller, but isn't there any better solution?
You can nest the edit controller scope in the list controller scope by simply nesting the divs (move the div with ng-controller="MultipleItemsEdit as multiEditCtrl" into the div with ng-controller="ItemsController as itemCtrl"). That way the you can call the method directly.
I have an html file with ng-includes inside
<div ng-controller="MapMenuCtrl">
<div class="mapMenu row">
<ng-include src="'partials/mapMenu/filterDropdown.html'"></ng-include>
<ng-include src="'partials/mapMenu/alertDropdown.html'"></ng-include>
<ng-include src="'partials/mapMenu/investigationDropdown.html'"></ng-include>
</div>
</div>
The problem is that I need MapMenuCtrl for each of ng-include. But when it is set as in example, it works, but only a half. For example in one of this files I use ng-model for one of $scope variables of MapMenuCtrl and it doesn't bind.
I was trying to set controller for each of ng-include, but it loads 3 times, though I need only 1.
I hope you understood me. I know, that my english is quite bad
ng-include creates a new scope. Put an object on the controller scope and put all bindable stuf inside.
You can call it model for example. Then the bindings should work as expected.
Here is a link for a details of the problem you face i believe:
Does my ng-model really need to have a dot to avoid child $scope problems?