Trying to understand Angular context & Watchers - javascript

I read like AngularJS has a context where it has deployed watchers and digestive loop. So if any variable is updated from either front-end or backend-end, these above-mentioned components will help to make sure all the respective fields where this variable is involved will be updated.
So I thought of testing this, so created a sample multiple by 2 like below
<!DOCTYPE html>
<html ng-app="myapp">
<head>
<title>Parcel Sandbox</title>
<meta charset="UTF-8" />
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.8.2/angular.js"></script>
</head>
<body>
<div id="app"></div>
<script>
</script>
<div class="container" ng-controller="maincontroller">
<label id="input_value">Please enter number </label>
<input type="text" id="input_value" ng-model="number" />
<p>You have entered : {{ number }}</p>
<p>
Multiplied by 2 : {{ multipleBy2 }}
</p>
</div>
<script src="src/index.js"></script>
</body>
</html>
and my index.js code is as below
var myapp = angular.module("myapp", []);
var maincontroller = function ($scope) {
$scope.number = "";
$scope.multipleBy2 = $scope.number * 2; // this is returning zero at the beginning of the page.
};
myapp.controller("maincontroller", maincontroller);
is it wrong if I expect that, the moment number input field received 2, I should see Multiplied by 2 : as 4 ?
Am I missing anything here?

In order for this to work as you're expecting out of the box, AngularJS would need to do one of two things:
Reprocess everything on every change, which would be a performance nightmare, or
Be smart enough to know when certain scope properties are dependent on the value of other scope properties and only reprocess those relevant properties, which would be massively complex.
The easy way to render the result of something that always needs to run fresh is to make it the result of a function instead of a property.
$scope.multipleBy2 = () => $scope.number * 2;
While AngularJS expressions bound to function results will always re-run on a digest loop, it won't actually re-render the DOM of the corresponding element unless the value changes in between digests, so thankfully there is some optimization that takes place.
As a best practice, these functions should should not mutate the state of the $scope when run, otherwise you could get into infinite digest loop issues.
$scope.multipleBy2 = () => $scope.number++ * 2;
// infinite $digest loop error

Related

ng-controller variable Vs ng-init vairable

For below code, using angularJS,
<script type="text/javascript">
angular.module('app').controller('add', ['$scope',function($scope) {
$scope.name = "Bonita Ln";
}]);
</script>
corresponding Javascript code to access $scope variable member name is,
<div ng-app="app" ng-controller="add">
<script type="text/javascript">
var dom_el = document.querySelector('[ng-controller="add"]');
var ng_el = angular.element(dom_el);
var ng_el_scope = ng_el.scope();
var name = ng_el_scope.name;
</script>
</div>
Below is the angular code, accessing ng-init variable name using angular expression,
<body ng-app="">
<div ng-init="name='Bonita Ln'">
{{name}}
</div>
</body>
How do I access the ng-init variable name using JavaScript?
you can do this by accessing variable $scope.name in your controller, but it has to be define inside your scope.
<div ng-init="name='Bonita Ln'">
{{name}}
<div ng-controller="MyCtrl">
Hello, {{name2}}!
</div>
</div>
angular.module('myApp',[]).controller('MyCtrl',function($scope) {
$scope.name2 = $scope.name;
})
this works, as you have define name in parent scope to controller, and is being inherited
but if for same controller html template will look like that:
<div ng-controller="MyCtrl">
<div ng-init="name='Bonita Ln'">
{{name}}
Hello, {{name2}}!
</div>
</div>
then it won't work, as variable was undefined when controller function was invoked
You could use it in exactly the same way, but you shouldn't.
I've downvoted your question because it is really really really bad practice, and I mean like every line of code you provided is bad practice.
I'm struggling trying to find out what you'd like to do, so I can't really provide you with better code to do so, but I can provide you with some links you must check out before continuing with whatever you're coding now.
Shaping up with Angular is a free codeschool course provided by the angular team, it is a really good course that will give you more insight on how to use angular than the phone-tutorial on the angular website.
Papa Johns Angular styleguide a great guide on how to write maintainable code.
thinkster.io a step by step guide to learn to master Angular
egghead.io a collection of good video tutorials
I know that my comment sounds quite harsh, but future you will thank you if you write more standardized Angular, and keep to a defined style-guide.
Also remember, in angular, your code should not know about the DOM, you don't need to specify eventListeners to DOM-elements, just use the appropriate directives like ng-click.
(yes I am aware that there can be exceptions to the rule)

AngularJS ng-model does not work with ng-include

I have the following code (see below) in whichI use ng-include. The ng-model="numLines" creates a binding of the value in the box and the function changeNumLines() specified in ng-change is called every time the value inside the input box changes. The value of the $scope.numLines inside the changeNumLines() is supposed to be the new changed value. However, it is not. The value that I output to the console is always "1" no matter what input is typed in the box.
However, if I do not use ng-include and just copy paste the partial html file: structCustomRows.htm into index.html instead of ng-include line everything works fine.
So, why is this happening with ng-include and how can I get around it?
Thanks.
index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.3.14/angular.min.js"></script>
<base href="/">
</head>
<script>
var app = angular.module('parserUI', []);
app.controller('CustomRowsCtrl', ['$scope', function($scope) {
$scope.numLines = 1;
$scope.changeNumLines = function() {
console.log("DEBUG: changeNumLines()");
console.log(String($scope.numLines));
}
}]);
</script>
<body ng-app="parserUI">
<h2>Header</h2>
<div ng-controller="CustomRowsCtrl">
<div ng-include src="'app/partials/structCustomRows.htm'"></div>
</div> <!-- ng-controller="CustomRowsCtrl" -->
</body>
</html>
structCustomRows.htm
<input type="text" ng-model="numLines" ng-change="changeNumLines()">
Looking at the docs, it says that "This directive creates new scope". Meaning the $scope.numLines in your controller is actually in the parent scope of your ngInclude directive.
The simplest way to fix this is to put the controller definition in the ngInclude to make it all the same scope. Otherwise, you will have to access the parent scope using something like $scope.$parent.numLines.
When you use ng-include, Angular creates a new scope, causing your variable to be overwritten to the new scope, instead of the one you wanted. As the new scope is an instance of the original scope (the one you want), you can create an object on CustomRowsCtrl; the objects are copied to the new scope as a reference, enabling you to share the same object in between child and parent scope.
Obs.: You can also use $parent, as suggested on the link below, but using pointers is a lot cleaner and if you use multiple nesting, you won't need to use $parent.$parent and so on.
Example:
On the controller:
$scope.selection = {}
On the HTML:
ng-model="selection.numLines"
More about ng-include scope:
AngularJS - losing scope when using ng-include
It's all about scope creation in Angular.
The directive ng-include create a new scope and you've defined the numLines in the parent scope, i.e the scope of the controller.
If you want to see the changes in your parent scope, you'll have either to write ng-model="$parent.numLines" in the included Html template or simply use the onload attribute of the ng-include directive : onload="numLines = numLines"
ng-include directive creates its new scope, There is so many work round for tackle this problem.
Create your own directive of static include which does not creates its own scope. for example.
app.directive('staticInclude', ['$http', '$templateCache', '$compile', function ($http, $templateCache, $compile) {
return function (scope, element, attrs) {
var templatePath = attrs.staticInclude;
$http.get(templatePath, { cache: $templateCache }).success(function (response) {
var contents = $('<div/>').html(response).contents();
element.html(contents);
$compile(contents)(scope);
});
};
}]);
so you can use this directive, its not create own scope.
Your html looks like
<body ng-app="parserUI">
<h2>Header</h2>
<div ng-controller="CustomRowsCtrl">
<div static-include=="app/partials/structCustomRows.htm"></div>
</div> <!-- ng-controller="CustomRowsCtrl" -->
</body>
You include controller in template means your file 'app/partials/structCustomRows.htm' has main div which initialize its relevant controller.

how to process html using angular and bindings (?)

here is my testing page :
<div ng-controller="test">
<input ng-keyup="asIwrite($event)" />
<p id="entityContent"></p>
</div>
and my controller :
EntitiesApp.controller('test', ['$scope', function ($scope) {
$scope.asIwrite = function ($event) {
$('#entityContent').html($event.srcElement.value);
}
}]);
this is actually working, if i write click in the input, the paragraph will hold the clickable url (processed html).
I am using jQuery to update the content of the paragraph as to show html element as they render in the page..
But using jQuery is a work-around in my opinion.
I want to achieve that using just Angular. How can I do ?
note : sure I can sweep jQuery off my project and use innerHTML of the
element but I want to know the way of doing that in Angular with
bindings. I used ng-bind on a paragraph element but that just
render the value of the textarea as is, no html process is performed.
See working example below.
You are right by doubting using jQuery is the right thing to do, and as you would expect it is not. The angular way to do that is register your input into the scope using ng-model, and the way to display it is using the ng-bind-html directive. (or simply ng-bind if it was simple text with no HTML)
However, Angular will not allow HTML binding by default as it could be a security issue. If you are sure about what you write, you can use $scope.trustAsHtml as showed in my example.
angular.module('test', [])
.controller('test', ['$scope', '$sce', function ($scope, $sce) {
$scope.trust = function(content) {
return $sce.trustAsHtml(content);
}
}]);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="test" ng-controller="test">
<input ng-model="content"/>
<p ng-bind-html="trust(content)"></p>
</div>
You probably have tried to do something like this but the content doesn't come out as html
<input ng-model="input" />
<p id="entityContent" ng-bind-html="input"></p>
You need to ensure strict context escaping as described here:
https://docs.angularjs.org/api/ng/service/$sce
Please keep in mind what you're saying you want to do is explicitly prevented by angular developers as a way to mitigate XSS attacks
You don't need to do that, angular is simplier than that.
<div ng-controller="test">
<input ng-model="variable1" />
<p id="entityContent">{{variable1}}</p>
</div>
Angular binds variables automatically. When the input's value change, the new value will be printed.

View is not updating in AngularJS

In my app, i am calculating total bill and displaying on my view. First time its work fine. But when I increment the $scope.bill_total it is not updating the view. But in console it is changing. I have tried $scope.$apply(). But it is throwing error.what are the reasons of view not get updating in general case Can anyone explain it?
HTML
<div id="total" class="col col-30 cart-item text-right text-black">
Bill Total<br/> ${{ bill_total }}
</div>
<button class="button button-small button-light" ng-click="increment()"> +</button>
JavaScript:
$scope.calculate = function () {
$scope.bill_total = parseFloat($scope.gross_bill) + parseFloat($scope.taxes) + parseFloat($scope.tips);
}
$scope.calculate();
$scope.increment = function () {
$scope.gross_bill++;
$scope.calculate();
console.log($scope.bill_total )
}
Need to see more of your code, but why are you updating gross_bill when you are expecting bill_total to change?
If you aren't using gross_bill in your template, it won't be watched and hence changing it's value won't redraw the view.
So, modify things that are bound in your template. If there's some reason I'm wrong and you need to do scope.apply, and maybe that's the case, try wrapping your code in a $timeout which will trigger a digest, is the 'recommended' solution preferred to calling apply directly.
notes on apply vs timeout
as we can only see part of your source code, it looks all good.
to test if everyting is in the same digest scope, you can manually do an async apply:
$scope.increment = function () {
setTimeout(function () {
$scope.gross_bill++;
$scope.calculate();
$scope.$apply();
console.log($scope.bill_total );
});
}
and pls also double check below points:
if bill_total is one-time binding {{ ::bill_total }}
if the directive scope is isolated with one-way binding bill_total
The problem is probably that $scope.bill_total is not an object and therefore cannot be watched. You need to change it to something like $scope.bill_total = {value: 3}. Then when you update the value it should be updated correctly in your view. There should be no reason to call $scope.apply since you are within a digest cycle already.
Make sure to change your html to :
<div id="total" class="col col-30 cart-item text-right text-black">
Bill Total<br/> ${{ bill_total.value }}
</div>

Angular NgModelController behaviour inside ng-repeat

Once I realized how ng-model directive works and was absolutely confident about it's behaviour this example just blowed my mind
<html ng-app>
<head>
<link href="//netdna.bootstrapcdn.com/twitter-bootstrap/2.2.1/css/bootstrap-combined.min.css" rel="stylesheet">
</head>
<body ng-init="names = ['Sam', 'Harry', 'Sally']">
<h1>Fun with Fields and ngModel</h1>
<p>names: {{names}}</p>
<h3>Binding to each element directly:</h3>
<div ng-repeat="name in names">
Value: {{name}}
<input ng-model="name">
</div>
<p class="muted">The binding does not appear to be working: the value in the model is not changed.</p>
<h3>Indexing into the array:</h3>
<div ng-repeat="name in names">
Value: {{names[$index]}}
<input ng-model="names[$index]">
</div>
<p class="muted">Type one character, and the input field loses focus. However, the binding appears to be working correctly.</p>
</body>
ng-repeat fiddle. Main problem is angular's behaviour for three different versions. I understand that ng-repeat creates a new scope for each array item, I suppose (not sure) it tracks only array reference and it's size (so, array items shouldn't cause $digest loop on change), but, what we've got here (watch just first example without $index using): for version 1.0.3 < we have expected behaviour: changing 'name' property with input will only change ngModelController's value (scope inheritance) and this is fair, 1.1.1 - well, what's going on there: we don't even get new values inside of input and! we don't rerender our items cause we don't lose the focus (and i really don't understand why $digest loop fires value replacement for this input as Artem has said), third version - 1.2.1: changing input values also changes 'name' value in the outer scope (as I understand ngModelController should inherit a scope created by ng-repeat directive). So, what really happens (and why) in all three examples?
Using Angular latest version (1.2.1) and track by $index. This issue is fixed
http://jsfiddle.net/rnw3u/55/
<div ng-repeat="name in names track by $index">
Value: {{names[$index]}}
<input ng-model="names[$index]">
</div>

Categories