angular $scope digest update from a dom event - angularjs

I have an angular $scope variable that gets instantiated through a window event. The $scope variable is displayed correctly to the user, but updates are not being set through ng-model. What's odd is that ng-change has the correct value that I can use to manually set the scope variable.
I really don't understand why this is happening.
Event Handler:
document.addEventListener('updateSearchBar', handleUpdate);
function handleUpdate(eventData) {
$scope.benefitsFromDate = eventData.detail.fromDate;
$scope.$apply();
}
Front end:
<input
type="date"
name="fromDate"
ng-change="updateBenefitsFromDate(benefitsFromDate)"
ng-model="benefitsFromDate"
/>
<input
type="button"
value="Search"
class="btn btn-default"
ng-click="searchDocuments()"
ng-disabled="isSearchingDocuments"
/>
Angular snippet:
$scope.updateBenefitsFromDate = function(change) {
console.log($scope.benefitsFromDate); //still has old value
console.log(change); //has updated value when param is the $scope variable
//$scope.benefitsFromDate = change; //only way to update
};
$scope.searchDocuments = function() {
//ng-model never updates the variable when the value is changed and uses the instantiated value
console.log($scope.benefitsFromDate);
};
Why doesn't ng-model reflect the change, but passing $scope.benefitsFromDate in the ng-change function have the updated value?

I use the $timeout module when processing these kind of updates and avoid the call to $scope.$apply().
$timeout(function() {
$scope.benefitsFromDate = eventData.detail.fromDate;
});

I learned that angular is finicky about "dot rules".
All I had to do was prefix my data values with something else.
$scope.data = {};
$scope.data.benefitsFromDate = ...;
Or you can declare your controller to use as
ng-controller="appController as vm"

Related

AngularJS - How to pass data from View (HTML) to Controller (JS)

I am really new to AngularJS. I want to pass some object from View (HTML) to my controller (JS).
Actually my Client will send me data in HTML and I have to take that data and process that data in my controller and then display the processed output on screen. He will be using some back-end technology called ServiceNow - https://www.servicenow.com/ .
All the solutions I saw had some event like click event or change event, but in my case this has to be done on page load.
I m using Input type hidden for passing the data to the controller, seems like it's not working.
So is there any other way I can do this ?
Here's the code I am trying to use
<div ng-controller="progressController" >
<input type="hidden" value="ABCD" ng-model="testingmodel.testing">
</div>
app.controller('progressController', function($scope) {
console.log($scope.testingmodel.testing);
});
It says undefined when I console.log my variable in Controller.
You're doing console.log(...) too early. At this time your controller doesn't have any information from the view.
The second problem is that you're binding the view to a variable in controller and not the other way around. Your $scope.testingmodel.testing is undefined and it will obviously the value in the view to undefined.
Solution
Use ng-init to initialize the model and the controller's hook $postLink to get the value after everything has been initialized.
Like this
<div ng-controller="progressController" >
<input type="hidden" ng-model="testingmodel.testing" ng-init="testingmodel.testing = 'ABCD'">
</div>
app.controller('progressController', function($scope) {
var $ctrl = this;
$ctrl.$postLink = function() {
console.log($scope.testingmodel.testing);
};
});
Edit: extra tip
I don't recomment using $scope for storing data since it makes the migration to newer angular more difficult.
Use controller instead.
Something like this:
<div ng-controller="progressController as $ctrl" >
<input type="hidden" ng-model="$ctrl.testingmodel.testing" ng-init="$ctrl.testingmodel.testing = 'ABCD'">
</div>
app.controller('progressController', function() {
var $ctrl = this;
$ctrl.$postLink = function() {
console.log($ctrl.testingmodel.testing);
};
});
You should use the ng-change or $watch
<div ng-controller="progressController" >
<input type="hidden" value="ABCD" ng-model="testingmodel.testing" ng-change="change()">
</div>
app.controller('progressController', function($scope) {
$scope.change = function(){
console.log($scope.testingmodel.testing);
}
});
Or:
app.controller('progressController', function($scope) {
$scope.$watch('testingmodel.testing', function(newValue, olValue){
console.log(newValue);
}
});
If you use ng-change, the function is only called if the user changes the value in UI.
If you use $watch anyway, the function is called.
You can't use value attribute for set or get value of any control, angularJS use ngModel for set or get values.
Here You should try like this way
app.controller('progressController', function($scope) {
//from here you can set value of your input
$scope.setValue = function(){
$scope.testingmodel = {}
$scope.testingmodel.testing = 'ABCD';
}
//From here you can get you value
$scope.getValue = function(){
console.log($scope.testingmodel.testing);
}
});
if you want to bind from html side then you should try like below
<input type="text" ng-model="testingmodel.testing">
<input type="hidden" ng-model="testingmodel.testing">

Directive with ng-model Attribute Not Resolving Using $http

Trying to make a rating directive but I'm stuck at getting rating2 to work. The first rating worked because the rating1 is hardcoded within the controller. But normally I have to get the saved rating from the db, which I'm trying to do with rating2, as u can see the value is fetched but the directive is not appearing.
https://codepen.io/eldyvoon/pen/MbBNLP
<div star-rating ng-model="rating.rating1" max="10" on-rating-select="rating.rateFunction(rating)"></div>
<br>but rating2 is actually there:
{{rating.rating2}}
<star-rating ng-model="rating.rating2" readonly="rating.isReadonly"></star-rating>
Need expert of directive to help.
Initiate rating2 :
function RatingController($http) {
this.rating1 = 5;
this.rating2 = 0; //ADD THIS LINE
var self = this;
it works for me
check here
First of all, I'm not a directive expert but i'm trying to help. I think that when html is first load, the values from db not finish execute and bind into html. The best way is not using directive instead using controller to fetch data from db.
You pass a model without rating2 into your directive and the changes from the parent controller won't affect it, because variable is created afterwards. Adding a watcher in your linker on parent scope will solve the problem;
scope.$parent.$watch('', function(rating){
updateStars();
});
Other solution would be to define a starting value in your controller.
this.rating2 = 1;
Notice that it is bad design to have a scope variable for each rating. It is cleaner to have an array of ratings and you actually do not need the watcher by doing so.
https://codepen.io/hoschnok/pen/LbJPqL
angular controller
function RatingController($http) {
this.ratings = [4];
var self = this;
$http.get('https://api.myjson.com/bins/o0r69').then(function(res){
self.ratings.push(res.data.rating2);
});
}
HTML
<div ng-app="app" ng-controller="RatingController as rating" class="container">
<div ng-repeat="r in rating.ratings">
<div star-rating ng-model="r" max="10" on-rating-select="rating.rateFunction(rating)"></div>
</div>
</div>
The watcher change handler function has parameters reversed:
//INCORRECT parameters
//scope.$watch('ratingValue', function(oldValue, newValue) {
//CORRECT parameters
scope.$watch('ratingValue', function(newValue, oldValue) {
if (newValue) {
updateStars();
}
});
The first argument of the listening function should be newValue.
The DEMO on CodePen
ALSO
The ng- prefix is reserved for core directives. See AngularJS Wiki -- Best Practices
JS
scope: {
//Avoid using ng- prefix
//ratingValue: '=ngModel',
ratingValue: '=myModel',
max: '=?', // optional (default is 5)
onRatingSelect: '&?',
readonly: '=?'
},
HTML
<!-- AVOID using the ng- prefix
<star-rating ng-if='rating' ng-model="rating.rating2"
max="10" on-rating-select="rating.rateFunction(rating)">
</star-rating>
-->
<!-- INSTEAD -->
<star-rating ng-if='rating' my-model="rating.rating2"
max="10" on-rating-select="rating.rateFunction(rating)">
</star-rating>
When a custom directve uses the name ng-model for an attribute, the AngularJS framework instantiates an ngModelController. If the directive doesn't use the services of that controller, it is best not to instantiate it.

Angular data-binding $watch

My web page looks like (abbreviated)
<HTML ng-app="scanning">
...
<BODY ng-controller="scanningController">
<INPUT id=R_name maxLength=50 size=35 ng-bind='BOOKING.R_NAME'>
</BODY>
</HTML>
My code is
function scanningController($scope){
$scope.depot = depot;
$scope.BOOKING = {R_NAME: ''};
/* Does anything get changed. */
$scope.$watch('BOOKING', function() {
...
}, true);
}
var scanningModule = angular.module('scanning', []);
scanningModule.controller('scanningController', scanningController);
The watch event does not get called when the user changes the value of R_NAME via the textbox. It does get called when the page initially loads, just not when values are changed by the user.
I have forced the event to fire by using an interval timer, changing a test property and calling $digest. In that case when I debug I can see that nothing entered through the textbox has been stored on the object. So it seems that I have not setup databinding correctly.
You should use ng-model for two-way binding instead of ng-bind.
<INPUT id="R_name" maxLength="50" size="35" ng-model="BOOKING.R_NAME">
You can also use ng-change instead of adding a $watch in your controller
<INPUT id="R_name" maxLength="50" size="35" ng-model="BOOKING.R_NAME" ng-change="onChange()">
Controller
function scanningController($scope){
$scope.depot = depot;
$scope.BOOKING = {R_NAME: ''};
/* Does anything get changed. */
$scope.onChange = function () {
...
};
}

i need an alternative solution for ng-change

i tried calling the values using ng-change between two different ng-models. it works.
but the data is parsed to the other ng-model only if the data is changed, is there any alternate solution where i can have the data in both ng-models before changing data
I tried something like this
HTML
<input ng-model="customer.name" ng-change='tripsheet.customer_name=customer.name;'>
<input ng-model="tripsheet.customer_name" type="text" class="form-control input-lg" placeholder="Customer Name">
JS
$scope.customer = {
name:$scope.customers[$scope.whichItem].name,
address:$scope.customers[$scope.whichItem].address,
phone:$scope.customers[$scope.whichItem].phone
}
i want the routeParams data in both the above ng-models.
With something like this in your controller?
$scope.customer = {
name: "Tom"
};
$scope.tripsheet = {
customer_name: $scope.customer.name
}
Plunker
#mainguy's answer is a very good approach.
Or alternatively you can go with $watch instead of ng-change.
initialize a watcher for the model object
$scope.$watch('customer.name', customerNameChanged);
and define the function
function customerNameChanged() {
if(!$scope.tripsheet)
$scope.tripsheet = {};
$scope.tripsheet.customer_name = $scope.customer.name
}
ng-change triggers only when there is a user interaction and the data is changed.
$watch is triggered when the model object is changed programmatically and also when they’re being defined the first time

Angular - data not available inside ng-init

Angular partial - HTML.
BaseCtrl
<div ng-controller="SelectTagCtrl">
<input type="text" ng-init="setTags(viewData['users'])" ui-select2="tagAllOptions" ng-model="tagsSelection" name="users" />
{{viewData['users']}} ECHOES CORRECTLY.
But undefined when passed inside ng-init callback.
</div>
<input type="text" class="span12" placeholder="Brief Description" name="description" value="{{viewData['description']}}">
ECHOES CORRECTLY.
Controller.js
function SelectTagCtrl(){
$scope.setTags = function(data){
// data is undfined when viewData['users'] is used. <-- PROBLEM
// correct when I pass some static string.
}
}
//POPULATING viewData to be used inside view partial.
function BaseCtrl(){
$http.get(url).success(function(data){
$scope.viewData = data.data || [];
$scope.view_type = $scope.viewData['view_type'];
$scope.fields = data.data.fields;
console.log($scope);
}).error();
}
Using timeout would be a workaround, instead I would take a $scope variable inside the controller to know if the ajax call has completed.
The problem is ng-init might get called before ajax completion.
I already had ui-if directive configured in my angular project, so I used it with the combination of $scope variable to get the things working.
<div ng-controller="SelectTagCtrl" ui-if="ajax_done">
<input type="text" ng-init="setTags(viewData['users'])" ui-select2="tagAllOptions" ng-model="tagsSelection" name="users" />
</div>
And inside controller,
$http.get(gurl + '.json').success(function(data,status){
// some stuff
$scope.ajax_done = true;
}).error();
Because of angular's magical two-way binding, the element will get updated. Now it sees that ajax request is completed, ui-if will get a truthy value and ng-init directive of the element will get a chance to execute its callback.
EDIT: ui-if was removed from Angular UI in favour of ng-if which is now built in.
Here are two different changes to your fiddle that appear to work.
Fiddle 1 - this version uses $scope.$apply(exp) as described in the documentation here and is useful when you are modifying angular bound data outside of the angular framework. In this example setTimeout is the culprit.
setTimeout(function(){
console.log("updateVal" );
$scope.$apply(function() {
$scope.updateVal2();
});
console.log($scope.tagsSelection);
},5000);
Fiddle 2 - this version uses angular's wrapper for setTimeout called the $timeout service.
$timeout(function(){
console.log("updateVal" );
$scope.updateVal2();
console.log($scope.tagsSelection);
},5000);

Resources