In an attempt to clean up my partials I recently moved some of my global menus into seperate templates which I now include in the views which need them. As the menu (including a search bar) is global I have created a service which keeps track of the state of the menu and so on.
Problem is something funny happened after I started including.
HTML (Jade) of the View
div(ng-controller='HeroUnitCtrl', ng-include src='heroTemplate')
div(ng-controller='MainSearchBarCtrl', ng-include src='searchBarTemplate')
div.row-fluid
div.span12
table.table.table-striped.table-bordered
tr
th
a(ng-click='setOrder("id")') ID#
th
a(ng-click='setOrder("client.name")') Kunde
th
a(ng-click='setOrder("technician.name")') Tekniker
th
a(ng-click='setOrder("createdAt")') Opprettet
th
a(ng-click='setOrder("statusString")') Status
tr(ng-repeat='record in records | orderBy:orderProp | filter:searchBar')
td
a(ng-href='#/records/show/{{record.id}}') {{record.id}}
td {{record.client.name}}
td {{record.technician.name}}
td {{record.createdAt}}
td {{record.statusString}}
HTML (Jade) searchBarTemplate
input#searchField.input-xxlarge(type='text', placeholder='placeholder', ng-change='searchBarUpdated()', ng-model='searchBar')
Now to the bit I really don't understand,
MainSearchBarCtrl
function MainSearchBarCtrl(MainSearchBarService, $scope, $location) {
$scope.searchBarTemplate = 'partials/main-searchbar';
$scope.searchBar = 'Hello World!';
$scope.searchBarUpdated = function() {
console.log('Search bar update: ' + $scope.searchBar);
MainSearchBarService.searchBarUpdated($scope.searchBar);
}
}
Initially the value of searchBar is as expected "Hello World". However, if I append any text it still only prints "Hello World". Or, if I replace the text it prints undefined. So it seems the binding is broken, but I don't really see why this is happening. Worth mentioning is that this wasn't case before I moved my search bar into a separate template.
Any help is greatly appreciated.
As discussed in the comments above, ng-include creates a new child scope. So in your searchBarTemplate, using ng-model="searchBar" results in a new searchBar property being created on the child scope, which hides/shadows the parent searchBar property of the same name.
In the controller, define an object:
$scope.obj = {searchBar: 'Hello World!};
And then use
ng-model="obj.searchBar"
in your template. When objects are used (instead of primitives), the child scope does not create a new property. Rather, due to the way JavaScript prototypal inheritance works, the child scope will find the property on the parent scope.
See also https://stackoverflow.com/a/14146317/215945 which has a picture showing the child and parent scopes and how the child property hides/shadows the parent property if a primitive is used.
Note that using $parent is another option, but it is not "best practice".
Try using $parent instead of $scope in your included template
Related
I am trying to understand a working code. It can build a very simple json data by adding name:value pairs one by one with GUI; by a custom directive and its link function, it builds a html template as the right hand of the image below:
What puzzles me is ng-model="$parent.keyName" in the highlighted part, as well as $parent.valueType, ng-model="$parent.valueName" in other part.
1) What does the $parent refer to (in the code or in the example of the above image)?
2) Is there a way to show the value of $parent or $parent.keyName in the console or by adding something (e.g., alert) in the program?
1) $parent refers to the parent scope (of any given scope). All scopes have a parent apart from $rootScope which is the top level scope. They can be created by any angular ng-* directive, including but not limited to ng-controller.
Generally speaking, it's considered bad practice to use $parent because it refers to the immediate parent scope which is subject to change. If the scope hierarchy does change (which it could do by adding a ng-* directive to a html element held in a custom directive template for example) then the hierarchy can be broken and $parent won't refer to the same scope that it once did.
2) Yes you can. In Chrome developer tools, if you Right Click > Inspect Element (as you have in the screen shot), you select the element. Then if you leave that element highlighted and go to the console tab and type:
angular.element($0).scope()
That returns the scope that the selected element resides in. Then if you type:
angular.element($0).scope().$parent()
That returns the parent scope of the scope that the selected element resides in. And for what it's worth you can also do:
angular.element($0).scope().$parent().$parent()
and keep going up the scope hierarchy.
Also check out the AngularJS Batarang chrome extension which allows you to look through the scopes in the Chrome Developer Tools.
What does this $parent refer to?
Parent refers to the scope of your parent controller. The image in your question doesn't give a clear picture so I will add an example for it.
<div ng-controller="EditorController">
<div ng-controller="ChildEditorController">
....
</div>
</div>
Assume you have a property called editorsList (array) in your EditorController (which is the parent controller here), you can do something like
<div ng-controller="EditorController">
<div ng-controller="ChildEditorController">
Editors Count: {{$parent.editorsList.length}}
</div>
</div>
So you can access the scope of your parent with the $parent.
Let's say my grandparent component/directive's scope variable is declared like this: $scope.g = { // methods here };
In each of my nested component's/directive's controllers, I'm referring to that variable like $scope.$parent.$parent.g and then calling some function off of g such as $scope.fromNgClick = function() { $scope.$parent.$parent.g.run(); };. That works great (though I would like a better way of referring to ancestors such as an alias name? instead of a $parent chain).
When I natively drag a component from it's grandparent component into another grandparent sibling component (got that?), the same $scope.fromNgClick = function() { $scope.$parent.$parent.g.run(); }; still points to the original scope, not the new one like I need it to. So clicking the same ng-clickable element still triggers the run() method on the previous grandparent's scope.
That all makes sense; but, is there a way to get the scope to point to the new dropped locations grandparent scope instead?
Thanks!
EDIT
The markup would be something like the following where <g-directive> is treated as a grandparent because it uses transclude on its template, ultimately wrapping the child directives:
<g-directive>
<child-directive></child-directive>
<another-child-directive></another-child-directive>
<yet-another-child-directive></yet-another-child-directive>
</g-directive>
<g-directive>
<c-child-directive></c-child-directive>
<c-another-child-directive></c-another-child-directive>
<c-yet-another-child-directive></c-yet-another-child-directive>
</g-directive>
That's the reason for the $scope.$parent.$parent.g on the child directives/components.
A child component can be dragged and then dropped into another <g-directive> but it's controller still points to its original grandparent (original <g-directive>'s controller scope variable). I want it to point to the new grandparent is was dropped into, essentially resetting it's scope to the newly placed scope.
<g-directive some-attr=1>
<child-directive></child-directive some-attr=1>
<another-child-directive></another-child-directive some-attr=1>
<yet-another-child-directive></yet-another-child-directive some-attr=1>
</g-directive>
<g-directive some-attr=2>
<child-directive></child-directive some-attr=2>
<another-child-directive></another-child-directive some-attr=2>
<yet-another-child-directive></yet-another-child-directive some-attr=2>
</g-directive>
Each can have different listener for broadcast and emit.
if the directives are repeated through ng-repeat then the $index can be that attribute.
Hope this helps.
I remember seeing this famous quote from a video on AngularJS saying that should be always using a . (dot) in your models.
Well I am trying to follow this say I have
var item = {}
item.title = "Easy Access to support";
item.available = true;
item.price = 31.67;
So this works great in my view i do
{{ item.title }}
{{ item.available }}
I am using a dot so I think this is good.
But I have some properties that I don't consider part of the model but maybe I am wrong. For example I have a property I use to enable or disable a button using the ng-disable, I have entered this using dot format. Its basically entered like so
$scope.disableButton = true;
and I use it like
ng-disable="disableButton"......
Should I make this part of the model "item" ? or create another js object just so i can hold this property using a dot ?
Anybody know if this acceptable or should I be doing everything (even these simple properties) with a .dot ??
Thanks
The "there should always be a dot in your model" refers to ngModel. This directive does two-way binding. If you two-way bind to a primitive (such as a Boolean in your case), the setter will set it on the current scope rather than the scope on which it is defined, which can cause a headache when you have a large user-interface with a lot of child scopes. It does not refer to other directives such as ngDisable. See this explanation for more details on this specific issue.
Sample scenario: a parent scope with $scope.foo = "bar", and a child scope with a <input type="text" data-ng-model="foo">. It will display bar initially, but once the user changes the value, a foo will be created on the child scope and the binding will read and write that value. The parent's foo will remain bar. Hope that summarises it well.
So for ngModel purposes, you might have to create an object to work around such binding issues, but for any other directive you should have the regular, logical grouping.
Here's a situation where a dot is needed.
When you have a $scope value that you want to use as a ngModel value (let's call it selectedItem), you might be tempted to just create $scope.selectedItem and pass that to the template. However, this is dangerous if the template creates child scopes.
There are certain AngularJS directives that create child scopes:
ngRepeat
ngIf
ngController
...and others (their doc page will say "This directive creates new scope" under the Usage heading).
Child scopes are dangerous because of how scope inheritance works. The relationship is this:
-- $parent scope
├ selectedItem
└─ $child scope
As a child scope, the $child object prototypically inherits from $parent. That's a javascript term that basically means you can get any $parent property by getting $child.<property>. But you cannot set values, which is the problem. This is just how javascript works.
So a template can access $parent.selectedItem by reading $child.selectedItem. But if the template sets $child.selectedItem, it sets it on $child not $parent, so now you have two versions of selectedItem:
-- $parent scope
├ selectedItem
└─ $child scope
└ selectedItem
And ngModel directives both get and set the scope value. The getting works, but the setting breaks things (ss others have explained).
Why using a dot solves the problem
When you store the selectedItem value with a dot on the parent scope (e.g. $scope.vm.selectedItem, then the child scope template only ever gets the vm object.
Using vm, the relationship looks like this:
-- $parent scope
├ selectedItem
│ └─ vm
│ └─ selectedItem
└─ $child scope
The $child scope only ever reads the vm object; it never writes a new vm object (in JS, objects are references not values). And prototypical inheritance is only involved in accessing vm. After a scope gets the vm object, it can directly use its values without any prototypical inheritance.
Think of it as passing around an object between scopes.
I think I'm missing something simple (and important) here. I'm using an included template that contains an input that's mapped to some value:
<div ng-controller="Ctrl">
<div ng-include src="'template.html'"></div>
</div>
<!-- template -->
<script type="text/ng-template" id="template.html">
<input ng-model="testvalue" />
</script>
Controller:
function Ctrl($scope) {
$scope.testvalue= "initial value";
}
Alerting the value of $scope.testvalue always shows the initial value, not the updated value (when you type in the input). Help me Obi-Wan. You're our only hope.
Fiddle: http://jsfiddle.net/h5aac/
This is the all too common of binding to a primitive instead of an object. The value of the string gets passed around and not a reference to an object. If you use an object instead of a primitive, it works fine. Something like this in your scope.
$scope.foo = {testvalue: "initial value"};
See http://jsfiddle.net/h5aac/2/
Also:
Using `ng-model` within a transcluded directive in AngularJS
binding issue when a directive in a ngRepeat
AngularJS - updating scope value with asynchronous response
I'm sure there are more...
An alternative to referencing an object property in the parent scope is to use $parent to access the primitive in the parent scope:
<input ng-model="$parent.testvalue" />
ng-include creates a child scope. That scope prototypically inherits from Ctrl's parent scope. Here's how the 3 variations work:
$parent.testvalue ties the model to the property in the parent scope
testvalue by itself ties the model to a new property that will be created on the child scope. This property "shadows/hides" the parent scope property by the same name.
foo.testvalue (e.g., see #dnc253's answer) also ties the model to a parent property. It works like this: Javascript doesn't see/find 'foo' in the child scope, so it looks for it in the parent scope (due to prototypical inheritance) and finds it there.
To see what the child scope looks like, use your original fiddle, and add this code to your template somewhere:
<a ng-click="showScope($event)">show scope</a>
And add this code to your Ctrl:
$scope.showScope = function(e) {
console.log(angular.element(e.srcElement).scope());
}
Before you type into the textbox, click the "show scope" link. In the console (I'm using Chrome), you can expand the "Child" scope and see it does not contain a testvalue property yet (which I find surprising, because I don't know how it is displaying the "initial value" in the textbox). You can expand the $parent and you'll see the testvalue property there -- a property with this name appears to only be in the parent scope at this point.
Now, clear the console, type into the textbox, and click the "show scope" link again. You'll see that the "Child" scope now has a new testvalue property. It shadows/hides the parent property. So, things in the child scope see the child scope testvalue property, and things in the parent scope see the parent scope testvalue property.
Update: FYI, I recently wrote an extensive answer/article about scope prototypical inheritance that explains the above concepts in much more detail, with lots of pictures: What are the nuances of scope prototypal / prototypical inheritance in AngularJS?
I apologize in advance if i'm not wording this properly. I have a textbox with ng-model inside an ng-repeat and when I try to get the textbox value it's always undefined. I just want it to display whatever I type in the corresponding textbox.
It seems to be an issue with the $scope, so how would I make the $scope.postText global or at the controller root level so it can be accessible?
Here's the JSFiddle to help clear things up: http://jsfiddle.net/stevenng/9mx9B/14/
As #Gloopy already stated, ng-repeat creates a new child scope for each item in your posts array. Since each item of the posts array is a primitive (a string), ng-repeat also creates a post property on each child scope, and assigns each of them the appropriate value from the array. Inside the ng-repeat block is ng-model="postText". This creates a postText property on each of the child scopes. Here is what that all looks like (for 2 of the 4 child scopes):
When a user types some text into one of the input textboxes, the appropriate gray box will store the text. (E.g., the 2nd (from the top) gray box will store text a user types into the "tech" textbox.) The parent scope cannot see the postText properties in the child scope -- this is the problem you had. There are three common solutions:
#Gloopy's answer: define a function on the parent scope (which the child scopes can access, because ng-repeat child scopes prototypically inherit from the parent scope) and pass the child scope property value (i.e., postText's value) up to the parent.
Use objects instead of primitives in your posts array. E.g.,
$scope.posts = [ {type: 'tech'}, {type: 'news'}, ...];Then in your ng-repeat loop, use
<input type="text" ng-model="post.postText">
Because each array item is an object (and not a primitive), each child scope gets a reference to the appropriate object in array posts, rather than a copy (of a value). Therefore, post.postText gets created on parent $scope property posts, and hence it is visible to the parent scope. (So, in this case the child scopes would simply call savePost() -- there would be no need to pass any values up to the parent scope.)
In other words, if a user typed "this is tech related" into the first text box, the posts array in the parent would be automatically updated as follows:
$scope.posts = [ {type: 'tech', postText: 'this is tech related'}, {type: 'news'}, ...];One last note: the postText property is not added to the posts object until the user types something.
Use ng-model="$parent.someProperty" to bind the form element to a property on the parent scope, rather than on the child scope. This solution would be difficult to implement for your scenario, and it is a rather fragile solution, as it depends on the HTML structure for scope inheritance... but I mention it for completeness.
(A fourth solution was presented by #Renan in comments on #Gloopy's answer. This is a like solution 1., but with a variation: this is used instead of passing a value up to the parent. I'm not a fan of this approach as it makes it difficult to determine which $scope is being accessed or modified. I think it is better that functions defined on $scope only access and modify their own $scope.)
For more information (and lots more pictures) about how prototypal scope inheritance works in Angular, see What are the nuances of scope prototypal / prototypical inheritance in AngularJS?
In your click expression you can reference the postText and access it in your savePost function. If this wasn't in an ng-repeat you could access the single $scope.postText successfully but ng-repeat creates a new scope for each item.
Here is an updated fiddle.
<div ng-repeat="post in posts">
<strong>{{post}}</strong>
<input type="text" ng-model="postText">
save post
</div>
$scope.savePost = function(post){
alert('post stuff in textbox: ' + post);
}
This may be a late answer. Please refer this fiddle. http://jsfiddle.net/X5gd2/
Please refer to the firebug's console, when u click on the links after typing some texts in the text box. The idea is to have a itemcontroller for each of the view that is repeated inside the ng-repeat.
The item controller:
function postItemController($scope){
$scope.savePost = function(){
console.log($scope.postText + " which belongs to " + $scope.post +" will be saved")
}
}
Split the model up into heading and value
angular.module('MyApp',[]);
function PostCtrl($scope) {
$scope.posts = [{heading:'tech',value:''}, {heading:'news',value:''}, {heading:'sports',value:''},{heading:'health',value:''}];
$scope.savePost = function(post){
alert( 'post stuff in textbox: ' + post);
}
}
HTML below..
<div ng-app="MyApp">
<div ng-controller="PostCtrl">
<div ng-repeat="post in posts">
<strong>{{post.heading}}</strong>
<input type="text" ng-model="post.value">
save post
</div>
</div>
</div>
Check out this Fiddle..