I'm having a problem in AngularJS where my parent controller and child controller are sharing the same model. In this examples, there is recursive comments:
<div ng-controller="ParentController">
<label>Comment</label>
<textarea name="comment_text" ng-model="comment_text"></textarea>
<input type="submit" value="Leave Comment" ng-click="sendComment($event)" />
<div class="replies">
<div class="areply" ng-controller="ChildController">
Someone said: blah blah blah
<label>Reply</label>
<textarea name="comment_text" ng-model="comment_text"></textarea>
<input type="submit" value="Leave Comment" ng-click="sendComment($event)" />
</div>
</div>
</div>
1st question....am I doing it wrong? And my second is there a way to make sure the parent and child scopes with ng-model do not affect each other?
Child controller scopes are prototypical descendants of their parent controllers' scopes. It's an intentional feature of Angular. The child scope has access to the parent's state, but not the other way around.
If you don't want this behaviour there are a couple of options - either don't use nested controllers (you can use custom services to share data where you need to, which is more test friendly and therefore considered best practice), or, more simply, just call the parent and child scope fields different names.
The code that you show is clearly too abstract to give you a more concrete answer, but it's important to notice that a child controller extends the behaviour of its parent (as a child class with its parent in OOP).
Therefore, a same variable should play the same role in a child controller and in its parent. If you want a different behaviour, you should use another name.
Why not just make your child text area use a different model, and have sendComment take an argument for which comment to accept?
<textarea name="comment_text" ng-model="comment_text"></textarea>
<input type="submit" value="Leave Comment" ng-click="sendComment($event, comment_text)" />
...
<textarea name="comment_text" ng-model="child_comment_text"></textarea>
<input type="submit" value="Leave Comment" ng-click="sendComment($event, child_comment_text)" />
Otherwise, you could create a directive that uses an isolate scope. Try a tutorial that goes over isolate scopes and directives, or whatch a screencast, such as those from egghead.io.
I would take joews second answer and not use nested Controllers, you don't need to. It's best practice to keep these separate. Generally each "page" would have it's own controller that dictates that pages functionality. You can use services and other angularjs resources to make sure you aren't rewriting code all the time.
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>
In order give feedback on the validity in my input forms I'm using ng-class. These statements will look something like:
<div ng-class="{ 'has-error': !frmSomeName.vcHeader.$valid && frmSomeName.vcHeader.$dirty }">
<input type="text" name="vcHeader" ng-model="model.someText" ng-minlength="10" required />
</div>
I dislike the lengthiness of the statement, and would like to replace it with something alike:
<div validation-state="frmSomeName.vcHeader">
<input type="text" name="vcHeader" ng-model="model.someText" ng-minlength="10" required />
</div>
In order to avoid having to duplicate ngClass' behavior I'd like the the directive to add the ng-class directive.
This plnkr demonstrates my attempt at adding the attribute, and although it works in the simplest scenario, it is faulty and will not function with transclusion (or other more complex directives).
I know it doesn't work because of the misuse of the compile and link stages, however I'm not sure on how to actually make it work properly. Therefore my question: How do I add different directive-attribute from a directive-attribute?
The elements within the transclude directive aren't receiving the same scope. If you use the angualr $compile method and apply the scope of the transclude directive to the scope of the child directives it should work. The following should be added to your simpleTransclude directive:
link: function(scope, element) {
$compile( element.contents() )( scope )
}
remember to pass $compile into the directive.
I've forked your plnkr and applied the simpleTransclude scope to it's contents.
I'm building a filtering component for a search app and it's my first dip into AngularJS.
Here's a Plunker or what I've got so far, it works how I want it to:
http://plnkr.co/I6ewaU
There are two things which concern me with this implementation:
I have to pass filters as an attribute of the filter directive to get access to filters on the MainController, surely there must be a better way to do this? I want to modify the filters object without having to pass it as an attribute.
Is it correct to define addFilter in the filtergroup directive, or should this be defined on MainController?
I have been searching on Google for a while, but can't seem to find an alternative and would appreciate any help with this.
Cheers.
AngularJS way is to show what it does in html.
Html has to explain the functionality by not hiding it to JS.
For example, how do I know this update filters unless I read your JS? Thus, try not to make any unless it is necessary. If you make any directive, try to expose the functionality to html.
<filtergroup type="filter-name" filter-store="filters">
And there are lots of good directives in Angularjs. I always use them first.
This is how I would do it, http://plnkr.co/edit/okCp5FZJo1ZR9962uaHT?p=preview
<div class="sidebar">
<p>Filters: {{ filters }}</p>
filter1 <input type="checkbox" ng-model="filters[1]"/><br/>
filter2 <input type="checkbox" ng-model="filters[2]"/><br/>
filter3 <input type="checkbox" ng-model="filters[3]"/><br/>
filter4 <input type="checkbox" ng-model="filters[4]"/><br/>
filter5 <input type="checkbox" ng-model="filters[5]"/><br/>
</div>
"less code, less manitenance"
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
I have several regions with repeatable content which is generated on the server-side. I use knockout-js to dynamically hide/show regions within areas. My markup is like the following:
<div>
<input type="checkbox" data-bind="checked: a1" />
<div data-bind="visible: a1">region0</div>
</div>
<div>
<input type="checkbox" data-bind="checked: a2" />
<div data-bind="visible: a2">region1</div>
</div>
<script>
var viewModel = {
a1: ko.observable(false),
a2: ko.observable(false)
};
ko.applyBindings(viewModel);
</script>
Lets say I have 10 such regions. Is there a more convenient/better way to achieve the same?
Lets say, that I explicitly do not want to use foreach binding and generate markup on the client (for site to be accessible with disabled js).
Is there any way to omit viewModel specification (part within script tags), since it feels to me that knockout could detect and auto-create fields for me?
P.S. I'm a JS-novice, so excuse me for simple questions :)
Is there any way to omit viewModel specification (part within script tags), since it feels to me that knockout could detect and auto-create fields for me?
Although I prefer Knockout personally, you might want to take a look at Angular. Angular does automatically create view model properties as this example shows.