AngularJS. Prevent updating ALL data in $scope - angularjs

I am novice in Angular and I have a question.
I noticed that angular updates all scope data on view (am I right?), even if it has been changed only one variable (that renders on view). Is it normal ? What if I have large data on view and I want to update it only when this is data being changed.
Code for example (every time when scope.word is being modified function func is executing):
<div ng-app="myApp" ng-controller="myCtrl">
Word: <input ng-model="word">
{{func()}}
</div>
<script>
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope) {
$scope.word = "John Doe";
$scope.func = function(){
alert("Who dared to disturb me !? >(");
};
});
</script>

Is it normal? - You bet it's normal, this is the whole idea.
What you're doing is not a good practice at all. However, because when you bind a function as an expression in the view, Angular doesn't "know" when it should update the expression in the view, so it updates it on every digest cycle that happens a lot! Almost every time the user interacts with the view (Click, scroll) or if anything is changed on the controller side, so you might find yourself ending up with this error.
You should bind properties to the view, not functions. Example:
angular.module('app',[]).controller('ctrl', function($scope) {
$scope.welcomeMessage = "Hi, welcome to AngularJS!";
$scope.updateMessage = function(message) {
$scope.welcomeMessage = message;
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app" ng-controller="ctrl">
<strong>{{ welcomeMessage }}</strong>
<hr>
<input type="text" ng-model="msg">
<button ng-click="updateMessage(msg)">Update Message</button>
</div>
Note that if you know that you need to bind a property in the view only once, then you can use one time binding:
<strong>{{ ::welcomeMessage }}</strong>
Or
<strong ng-bind="::welcomeMessage"></strong>
By adding :: to the expression you prevent angular from tracking this expression after it is bound to the view the first time, and will not update it again, even if it was changed on the controller. Which is good for the performances of your app and can dramatically improve them.
Here is a working example of one-time binding: https://jsfiddle.net/hu9zcbwh/2/ (I couldn't create stack-snippet because it doesn't have angular 1.3 where this feature was first introduced)
I'm editing this with #MaximShoustin comment, that should help make this more clear and summarizes better the differences between the normal binding and one time binding:
ng-bind or {{}} generates one watcher and it will be fired after each digest cycle. On the other hand, :: expression creates watcher and cancels it once the value is not undefined
Sorry, not a native English speaker :(

Related

Is a variable enclosed in {{ }} checked for changes on my page?

I thought there was a way that I can just display something on the page and not have AngularJS check it for changes.
Can someone tell me how to do this? Is it just if I have a label like this:
{{ abc }}
You may use binding like this {{::abc}} so you app will not watch for changes after first render of the data. See one-time-binding doc
It is a scope variable. Means your controller has an scope object as $scope if you define any variable like $scope.abc = "string". then a new property called abc will be created in your controller scope.
In AngularJS scope object was always watched and once any change in that object made it will reflect in the view.
Thankfully, Angular 1.3 and up versions has put a lot of effort into performance with the feature One-time binding using {{ ::abc }} works:
angular
.module('MyApp', [])
.controller('MyController', function($scope, $timeout) {
$scope.abc = 'Some text'
$timeout(function() {
$scope.abc = 'new value';
console.log('Changed but not reflected in the view:', $scope.abc);
}, 1000);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.0/angular.js"></script>
<div ng-app="MyApp" ng-controller="MyController">
<p>{{ ::abc }}</p>
</div>
As there is a two-way data binding, angular will watch for any changes on the variable $scope.abc and update the DOM with the changes. However if you want to make sure it does not watch for any changes you can go for the one-way binding, where any change made to the variable will not be watched upon by angular. You can do this using by {{::abc}} or ng-bind="::abc".
For example refer - https://jsfiddle.net/m8L2pogg/

How does AngularJS respond to a form value being updated in Javascript?

I don't use Angular regularly, but I understand that one of the key features is that when data is updated on a form element, it is automatically updated in the model.
If you are instead using a library like jQuery, you must manually attach an event to the form input that updates the model when it is changed, as in $('#myInput').on('change', updateModel);
Although the above handler will be fired when myInput is changed by the user, it will not be fired if myInput is changed by Javascript code such as $('#myInput').val('hello world');
My question is, how does Angular know when a form input is changed in Javascript code?
Angular applies a scope digest every time it's needed (by an Angular function) during which it checks the states of all the scope variables, including the models used, of course.
If you modify some of those variables manually, using JavaScript, jQuery, etc... Angular will not know that the changes have occured and you need to tell it so either by doing $scope.$apply() or by wrapping the code block in a $timeout callback (these are the most commonly used methods).
If you don't do it manually, you'd have to wait for some (if any) other Angular event to trigger the digest cycle, which is never good.
See this example, note how nothing happens when you just update the value, but you need to do it manually (ng-click does it) in order for DOM to update:
angular.module('app', [])
.controller('Ctrl', function($scope){
$scope.ourValue = 'Initial Value';
window.exposedFunc = function(v, digest) {
$scope.ourValue = v;
if (digest) {
$scope.$apply();
}
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app" ng-controller="Ctrl">
<button onclick="exposedFunc('First Button Value')">Update Value - No Digest</button>
<button onclick="exposedFunc('Second Button Value', true)">Update Value - Force Digest</button>
<button ng-click="">Force Digest only</button>
<p>{{ourValue}}</p>
</div>
Here's a super simple example of binding using keyup event. It should be enough to get you started on your projects:
var res = document.getElementById('r');
function handleChange(v) {
res.textContent = v;
}
<input onkeyup="handleChange(this.value)" type="text" value="Initial value" />
<p id="r">No binding yet</p>

multiple inputs based on array

My angular experience is basically about 3 days part time, so there's probably something simple I'm missing here.
I'm trying to create a dynamic list of multiple inputs based on an array, which I then want to reference from elsewhere in the app. What I've tried is loading a template from a custom directive, then $compile-ing it.
<input data-ng-repeat="term in query" data-ng-model="term">
My controller contains $scope.query = [""] which successfully creates the first empty input box. But the input box doesn't seem to update $scope.query[0] when I modify it. This means that when I try to create another empty input box with $scope.query.push(""); (from a keypress listener looking for the "/" key) I get a "duplicates not allowed" error.
I've tried manually listening to the inputs and updating scope.$query based on their value, but that doesn't feel very "angular", and results in weird behaviour.
What do I need to do to link these values. Am I along the right lines or way off?
I made a simple jsfiddle showing how to use an angular model (service) to store the data. Modifying the text inputs will also modify the model. In order to reference them somewhere else in your app, you can include TestModel in your other controllers.
http://jsfiddle.net/o63ubdnL/
html:
<body ng-app="TestApp">
<div ng-controller="TestController">
<div ng-repeat="item in queries track by $index">
<input type="text" ng-model="queries[$index]" />
</div>
<br/><br/>
<button ng-click="getVal()">Get Values</button>
</div>
</body>
javascript:
var app = angular.module('TestApp',[]);
app.controller('TestController', function($scope, TestModel)
{
$scope.queries = TestModel.get();
$scope.getVal = function()
{
console.log(TestModel.get());
alert(TestModel.get());
}
});
app.service('TestModel', function()
{
var queries = ['box1','box2','box3'];
return {
get: function()
{
return queries;
}
}
});

AngularJS: Create new controller and scope for dynamic content

Have a template that I'd like to load using ng-include and assign a controller instance to. This new template/scope/controller needs to be loaded in response to a user interaction (hover or click).
The content of the template has to be set using element.innerHTML because the content is set by a 3rd party.
The user can then click out of the new div and I would like to destroy the controller/scope that was created.
Pseudocode for what I want to achieve:
popup.setContent("<div ng-controller='PopupController'><div ng-include=\"views/LayerPopup.html\"></div></div>");
How do I tell angular to process the ng-include and ng-controller just as though the page was being loaded for the first time?
Thanks!
Edit:
Add plunker to illustrate question
http://plnkr.co/edit/DPuURCoq2hJ0LCLIN2dc?p=preview
http://jsfiddle.net/ADukg/5420/
Not using ngInclude, but it does fill these criteria:
You pass in a templateURL.
Pass in the name of the controller you would like to use.
Pass in the third party content (which in turn gets set with $element.innerHTML).
Setup a click listener someplace outside the $scope of the popup, which triggers a kill command on the popup.
This is how I imagine you would instantiate it:
<directive tpl="tpl.html"
ctrl="DirectiveController"
third-party-content="{{thirdPartyContent}}">
</directive>
Not sure this will suit you, but I had a fun time putting it together and maybe it'll prove useful to someone else.
In any case, I have to agree with the comments you've recieved so far. It's a bit cryptic as to what you have to work with right now and what possible options are available to you.
Here is a plunker of what you are trying to do. If you click on a button, a popup will show a template, and you can click on the template and it will stay up, but if you click out of it, it will get removed.
HTML
<body ng-controller="MainCtrl" ng-click="closePopup()">
<button ng-click="openPopup($event)" id="clicktarget">Click</button>
<p>Hello {{name}}!</p>
<div ng-include="getPopup()" ng-click="$event.stopPropagation()">
</div>
<script type="text/ng-template" id="theTemplate.html">
<div ng-controller="PopupController">
<div ng-include="'LayerPopup.html'"></div>
</div>
</script>
</body>
JS
angular.module('plunker', [])
.controller('MainCtrl', function($scope, $templateCache) {
$scope.name = 'World';
$scope.popupTmpl = null;
$scope.openPopup = function($event){
$scope.popupTmpl = 'theTemplate.html';
$event.stopPropagation();
};
$scope.getPopup = function(){
return $scope.popupTmpl;
};
$scope.closePopup = function(){
$scope.popupTmpl = null;
};
})
.controller('PopupController', ['$scope', function($scope) {
$scope.aVariableMaybe = 'lulz something';
}]);
On a side note, try to get rid of that JQuery stuff when you are using Angular. Angular can do everything on its own

How to avoid "sausage" type binding when having nested model

I have nested model and I am trying to avoid vm.someObject.someChild.ChildOfChild.name type of situations. Is there a way to set the model for outer <div> so that I can instead do ChildOfChild.name or even name. In Silverlight this was called DataContext. I put "vm" on the $scope, but in html I would like to avoid having to type the full path to attribute.
For example:
<div>
{{someObject.Id}}
<div>
{{someObject.name.first}}
{{someObject.name.last}}
</div>
<div>
{{someObject.someChild.name.first}}
</div>
</div>
I would like to do something like this
<div datacontext = someObject>
{{Id}}
<div datacontext = name>
{{first}}
{{last}}
</div>
<div datacontext = someChild.name>
{{first}}
</div>
</div>
You can do this with a custom directive.
HTML:
<div ng-app="myApp" ng-controller="myCtrl as ctrl">
<div>
Access from deepObj: {{ctrl.deepObj.one.two.three.four}}
</div>
<div scope-context="ctrl.deepObj.one.two.three">
Access from third level: {{four}}
</div>
</div>
JS:
var myApp = angular.module('myApp', []);
var myCtrl = function() {
this.deepObj = {one: {two: {three: {four: "value"}}}};
};
myApp.directive('scopeContext', function () {
return {
restrict:'A',
scope: true,
link:function (scope, element, attrs) {
var scopeContext = scope.$eval(attrs.scopeContext);
angular.extend(scope, scopeContext);
}
};
});
See the documentation on $compile for information on what scope: true does.
Make sure you don't call the directive something like data-context as an attribute starting with data- has a special meaning in HTML5.
Here is the plunker: http://plnkr.co/edit/rMUQlaNsH8RTWiRrmohx?p=preview
Note that this can break two-way bindings for primitive values on the scope context. See this plunker for an example: http://plnkr.co/edit/lCuNMxVaLY4l4k5tzHAn?p=preview
You could try/abuse ng-init
Try ng-init, you'll have one more ., but it's better than the other answer I've seen proposed:
<div ng-init="x = foo.bar.baz">
{{x.id}}
{{x.name}}
</div>
BUT Be warned, doing this actually creates a value on your scope, so doing this with something like ng-model if you're reusing the same name, or in a repeater, will produce unexpected results.
Why a custom directive for this probably isn't a good idea
What #rob suggests above is clever, and I've seen it suggested before. But there are issues, which he touches on, in part at least:
Scope complexity: Adding n-scopes that need to be created (with prototypical inheritence) whenever views are compiled.
View processing complexity: Adding an additional directive (again for no real functional benefit) that needs to be checked on each node when the view is compiled.*
Readability? The next Angular developer will likely less readable because it's different.
Forms Validation: If you're doing anything with forms in Angular, this might break things like validation.
ng-model woahs: Setting things with ng-model this way will not be at all intuitive. You'll have to use $parent.whatever or $parent.$parent.whatever depending on how may contexts deep you are.
* For reference, views are $compiled more than you think: For every item in a repeater, whenever it's changed for example.
A common idea that just doesn't jive with Angular
I feel like this question comes up frequently in StackOverflow, but I'm unable to find other similar questions ATM. ... regardless, if you look at the approaches above, and the warnings given about what the side effects will be, you should be able to discern it's probably not a good idea to do what you're trying to do just for the sake of readability.

Resources