Global function with $rootScope (angularJS) [duplicate] - angularjs

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>

Related

Understanding ng-scope inheritance/hierarchy

Just want to understand more about ng-scope inheritance/hierarchy, because i don't exactly understand how it works.
Assume i have a block like this
<div ng-controller="CtrlName">
<div ng-scope-1>
<div ng-scope-2>
<div ng-scope-3>
<div ng-scope-4></div>
</div>
</div>
</div>
Is it correct to assume that from controller's point, to get to
Scope 1, i need to define $scope.scope1 in the controller
Scope 2, i need to define $scope.scope1.scope2 in the controller?
Scope 4, i need to define $scope.scope1.scope2.scope3.scope4 in the controller?
And what if scope 3 block is appended to scope 2 using ngInclude? Does the scope referring changes too?
I asked this because of this one question here - AngularJs: why doesn't ng-switch update when I use ng-click?
And the solution seems to mention about scope inheritance. Not sure if the example i gave above is related to the scope inheritance too.

AngularJS : Correct usage of ng-model

I was reading Angular js docs when I came across the issues mentioned related to using ng-model with directives like ng-include , ng-switch , and ng-view.The reason mentioned was child scope and parent scope but I was not able to understand it completely.
Also it was mentioned that issue with primitives can be easily avoided by following the "best practice" of always have a '.' in your ng-model.
Here's the link
Can anyone please explain it in less-technical language?
ng-include, ng-switch and ng-if creates child scope.
It means that if you create
<div ng-controller="MyCtrl">
<div id="innerDiv" ng-if="!something">
<input ng-model="model">
</div>
</div>`
the model will be created in scope created by the #innerDiv (because of using ng-if). It might be problematic if you want to use model value in the controller, because it won't be possible (Parent scope has no access to properties of child scope!).
The solution is to use $parent.model in <input ng-model="model">. Then the property will be changed in parent's scope and that is what you usually want to achieve.
However using $parent might look not good for someone, so better solution is to create a named model in the controller. The you can use it and follow the rule "always have a '.' in your ng-model." Child scope created by ng-if has access to parrent scope, so he can use already defined $scope.model and change $scope.model.text property.
Controller:
$scope.model = {};
Html:
<div ng-controller="MyCtrl">
<div id="innerDiv" ng-if="!something">
<input ng-model="model.text">
</div>
</div>`
(But remember that if you not define $scope.model in the controller, it would behave like in first example).
If you are not sure that you are in the same scopes, you can check it by displaying scope's $id. Simply add in html {{$id}} above ng-if (or ng-include, ng-switch) and inside.
<div ng-controller="MyCtrl">
scope id: {{$id}}
<div id="innerDiv" ng-if="!something">
child scope id:{{$id}}
</div>
<div>scope id again {{$id}}</div>
</div>`
Some examples:
https://jsfiddle.net/La90btfh/3/

Difference between onLoad and ng-init in angular

I am learning angular. I don't understand what is difference between onLoad and ng-init for initialization of a variable. In which scope it creates this variable.
For example
<ng-include onLoad="selectedReq=reqSelected" src="'partials/abc.html'"></ng-include>
OR
<ng-include ng-init="selectedReq=reqSelected" src="partials/abc.html"></ng-include>
Please also give me some idea about isolated scope.
ng-init is a directive that can be placed inside div's, span's, whatever, whereas onload is an attribute specific to the ng-include directive that functions as an ng-init. To see what I mean try something like:
<span onload="a = 1">{{ a }}</span>
<span ng-init="b = 2">{{ b }}</span>
You'll see that only the second one shows up.
An isolated scope is a scope which does not prototypically inherit from its parent scope. In laymen's terms if you have a widget that doesn't need to read and write to the parent scope arbitrarily then you use an isolate scope on the widget so that the widget and widget container can freely use their scopes without overriding each other's properties.
From angular's documentation,
ng-init SHOULD NOT be used for any initialization. It should be used only for aliasing.
https://docs.angularjs.org/api/ng/directive/ngInit
onload should be used if any expression needs to be evaluated after a partial view is loaded (by ng-include).
https://docs.angularjs.org/api/ng/directive/ngInclude
The major difference between them is when used with ng-include.
<div ng-include="partialViewUrl" onload="myFunction()"></div>
In this case, myFunction is called everytime the partial view is loaded.
<div ng-include="partialViewUrl" ng-init="myFunction()"></div>
Whereas, in this case, myFunction is called only once when the parent view is loaded.
Works for me.
<div ng-show="$scope.showme === true">Hello World</div>
<div ng-repeat="a in $scope.bigdata" ng-init="$scope.showme = true">{{ a.title }}</div>

Cannot get textarea value in angularjs

Here is my plnkr: http://plnkr.co/edit/n8cRXwIpHJw3jUpL8PX5?p=preview You have to click on a li element and the form will appear. Enter a random string and hit 'add notice'. Instead of the textarea text you will get undefined.
Markup:
<ul>
<li ng-repeat="ticket in tickets" ng-click="select(ticket)">
{{ ticket.text }}
</li>
</ul>
<div ui-if="selectedTicket != null">
<form ng-submit="createNotice(selectedTicket)">
<textarea ng-model="noticeText"></textarea>
<button type="submit">add notice</button>
</form>
</div>
JS part:
$scope.createNotice = function(ticket){
alert($scope.noticeText);
}
returns 'undefined'. I noticed that this does not work when using ui-if of angular-ui. Any ideas why this does not work? How to fix it?
Your problem lies in the ui-if part. Angular-ui creates a new scope for anything within that directive so in order to access the parent scope, you must do something like this:
<textarea ng-model="$parent.noticeText"></textarea>
Instead of
<textarea ng-model="noticeText"></textarea>
This issue happened to me while not using the ng-if directive on elements surrounding the textarea element. While the solution of Mathew is correct, the reason seems to be another. Searching for that issue points to this post, so I decided to share this.
If you look at the AngularJS documentation here https://docs.angularjs.org/api/ng/directive/textarea , you can see that Angular adds its own directive called <textarea> that "overrides" the default HTML textarea element. This is the new scope that causes the whole mess.
If you have a variable like
$scope.myText = 'Dummy text';
in your controller and bind that to the textarea element like this
<textarea ng-model="myText"></textarea>
AngularJS will look for that variable in the scope of the directive. It is not there and thus he walks down to $parent. The variable is present there and the text is inserted into the textarea. When changing the text in the textarea, Angular does NOT change the parent's variable. Instead it creates a new variable in the directive's scope and thus the original variable is not updated. If you bind the textarea to the parent's variable, as suggested by Mathew, Angular will always bind to the correct variable and the issue is gone.
<textarea ng-model="$parent.myText"></textarea>
Hope this will clear things up for other people coming to this question and and think "WTF, I am not using ng-if or any other directive in my case!" like I did when I first landed here ;)
Update: Use controller-as syntax
Wanted to add this long before but didn't find time to do it. This is the modern style of building controllers and should be used instead of the $parent stuff above. Read on to find out how and why.
Since AngularJS 1.2 there is the ability to reference the controller object directly instead of using the $scope object. This may be achieved by using this syntax in HTML markup:
<div ng-controller="MyController as myc"> [...] </div>
Popular routing modules (i.e. UI Router) provide similar properties for their states. For UI Router you use the following in your state definition:
[...]
controller: "MyController",
controllerAs: "myc",
[...]
This helps us to circumvent the problem with nested or incorrectly addressed scopes. The above example would be constructed this way. First the JavaScript part. Straight forward, you simple do not use the $scope reference to set your text, just use this to attach the property directly to the controller object.
angular.module('myApp').controller('MyController', function () {
this.myText = 'Dummy text';
});
The markup for the textarea with controller-as syntax would look like this:
<textarea ng-model="myc.myText"></textarea>
This is the most efficient way to do things like this today, because it solves the problem with nested scopes making us count how many layers deep we are at a certain point. Using multiple nested directives inside elements with an ng-controller directive could have lead to something like this when using the old way of referencing scopes. And no one really wants to do that all day!
<textarea ng-model="$parent.$parent.$parent.$parent.myText"></textarea>
Bind the textarea to a scope variable's property rather than directly to a scope variable:
controller:
$scope.notice = {text: ""}
template:
<textarea ng-model="notice.text"></textarea>
It is, indeed, ui-if that creates the problem. Angular if directives destroy and recreate portions of the dom tree based on the expression. This is was creates the new scope and not the textarea directive as marandus suggested.
Here's a post on the differences between ngIf and ngShow that describes this well—what is the difference between ng-if and ng-show/ng-hide.

Shouldn't there be an AngularJS ngWith directive?

Maybe I'm crazy, or too used to KnockoutJS, but I keep looking for an ngWith directive in the docs to define the scope on an element, in a controller, or for an included (ngInclude) partial.
For example:
I'd like to write a controller that augments MyItem like:
MyModule.controller('MyItemCtrl', function($scope) {
$scope.doSomethingToItem = function() {
$scope.name = "bar";
};
});
Or a view/template for MyItem like:
<div ng-controller="MyItemCtrl">
{{name}}
<button ng-click="doSomethingWithItem()">Do Something</button>
</div>
But in both of these cases I'm imagining my $scope to be prototypically inherit from my model, MyItem.
But the scope doesn't inherit from the model!!
Which baffles me.
Instead, my model is a property on the scope.
In the case of a repeater:
<div ng-repeat="item in list">
<div ng-controller="MyItemCtrl">
{{item.name}}
<button ng-click="doSomethingWithItem()">Do Something</button>
</div>
</div>
which means everywhere I have to use item.this or item.that instead of just this and that. I have to remember which functions are native to the model, and which were applied directly to the scope by a controller.
If I want to have a partial to display names (stupid example, I know, but you get the idea):
<h3>{{name}}</h3>
I have to write it
<h3>{{item.name}}</h3>
and then ensure the model is always item. Usually by wrapping it in a directive simply to defines a scope with an item property.
What I often feel like I want to do is simply:
<div ng-include="'my/partial.html'" ng-with="item"></div>
or
<div ng-repeat="list" ng-controller="MyItemCtrl">
{{name}}
<button ng-click="doSomethingWithItem()">Do Something</button>
</div>
Is there some magical directive out there that I haven't found? Or am I completely wrong and just looking for trouble?
Thanks.
EDIT:
Many thanks to Brandon Tilley for explaining the dangers of using scopes as models. But I still often find the need for some quick declarative scope manipulation and wish for an ng-with directive.
Take, for example, you have a list of items which, when clicked, shows an expanded view of the selected item. You might write it something like:
<ul>
<li ng-repeat="item in items" ng-click="selection = item">{{item.minView}}</li>
</ul>
<div ng-controller="ItemController">
{{selection.maxView}}
</div>
now you have to get properties of the selected item using selection.property rather than what I'd want: item.property. I'd also have to use selection in ItemController! Making it wholly coupled with this view.
I know, in this simple example I could have a wrapping controller to make it work, but it illustrates the point.
I've written a very basic with directive:
myApp.directive('with', ['$parse', '$log', function(parse, log) {
return {
scope: true,
link: function(scope, el, attr) {
var expression = attr.with;
var parts = expression.split(' as ');
if(parts.length != 2) {
log.error("`with` directive expects expression in the form `String as String`");
return;
}
scope.$watch(parts[0], function(value) {
scope[parts[1]] = value;
}, true);
}
}
}]);
that simply creates a new scope parsing one expression onto another value, allowing:
<ul>
<li ng-repeat="item in items" ng-click="selection = item">{{item.minView}}</li>
</ul>
<div with="selection as item" ng-controller="ItemController">
{{item.maxView}}
</div>
This seems infinitely useful to me.
Am I missing something here? Just making trouble for myself down the line somehow?
I've found I can just put an array around the source of ng-repeat and it becomes functionally an ng-with. :)
<div ng-repeat="name in [vm.dto.Person.Name]" >
<input type="text" ng-model="name.First" />
<input type="text" ng-model="name.Last" />
</div>
Seems like some purests may not like this... Also doesn't seem like there is a good alternative.
This is a great question. I can see how this may be confusing coming from another front-end framework, but in Angular, the scope has a reference to the model, and the syntax you're describing is normal. I personally like to describe the scope as more like a view model.
Miško Hevery, the author of AngularJS, does a good job of explaining this concept in this video, starting at about the 30 minute mark and lasting about 3 minutes:
People oftentimes think that the scope is the model, and that's not the case. Scope has references to the model. [...] So in the view, you say model dot whatever property you want to access.
While it may be possible to write an ngWith directive that does kind-of what you're looking for, since Angular uses prototypal inheritance for scopes, you will likely run into the same issues that Miško describes in the aforementioned video at 31:10 (where you think you're updating a value on the parent scope but you're actually not). For more details on prototypal inheritance in AngularJS, check out the excellent article The Nuances of Scope Prototypal Inheritance on the AngularJS wiki.
Another approach would be to set a new variable via ng-init:
<div ng-init="name = vm.dto.Person.Name" >
<input type="text" ng-model="name.First" />
<input type="text" ng-model="name.Last" />
</div>
I think one side effect of this when coming from knockout is that is encourages you to create more components with less responsibility, and of course within a component the 'this' is the viewmodel for that component. If you get to a point where it is really annoying you then it might be a sign you need a new component.
And it sure is nice being able to just refer to something in the component view model without thinking about the scope you're in. And you can't tell me you miss $parents[2] and $root ;-)
PS. Yes I know you can use let in knockout, which means fewer $ stuff.
I definitely miss knockout when doing Angular stuff (I still use both) but it sure is nice to do certain things things like title="Customer #{{ row.customerId }} - {{ row.fullName }}" and not have everything under data-bind.

Resources