AngularJS scope updated after ng-change - angularjs

I have a directive that centralises my select HTML and functionality but I have an issue where the ng-model is updating after ng-change happens.
Here's a focused jsfiddle example:
http://jsfiddle.net/U3pVM/1568/
(Code because SO complains otherwise)
HTML:
<div ng-app="myApp">
<div ng-controller="MainCtrl">
<p>fooId is currently : {{fooId}}</p>
<app-drop-down model="fooId" options="fooOptions" opt-value="id" opt-label="label" on-change="dropDownChanged"></app-drop-down>
</div>
</div>
JS:
var app = angular.module('myApp', []);
app.controller('MainCtrl', function ($scope, $log) {
$scope.fooId = -1;
$scope.fooOptions = [{
id: 1,
label: "A"
}, {
id: 2,
label: "B"
}];
$scope.dropDownChanged = function (id) {
$log.info('changed : ' + $scope.fooId + ' but really: ' + id);
};
});
app.directive('appDropDown', function () {
return {
restrict: 'E',
replace: true,
scope: {
model: '=',
options: '=',
onChange: '='
},
template:
'<div><select ng-model="model" ng-options="a[optValue] as a[optLabel] for a in options" ng-change="changed()"></select></div>',
link: function (scope, element, attrs) {
scope.optValue = attrs.optValue;
scope.optLabel = attrs.optLabel;
scope.changed = function () {
scope.onChange(scope.model);
};
}
};
});
The console logs:
changed : -1 but really: 1
changed : 1 but really: 2
When you change the select to A, then to B.
It is updating but after the ng-change is triggered.
Obviously, I can work around this by passing the id (like I do) or using $watch in the controller on the value but this isn't ideal for certain more complex scenarios.
Any ideas?

I know this is a bit after the fact, but I had a similar problem and searching around I found this question as well. As there doesn't seem to be a real answer, I thought to post what I ended up doing as it may help someone else in the future. This seems to work for my case (and your fiddle as well) but as I'm only starting to use AngularJS, I might be doing something against the 'rules' so any specialist, feel free to correct me...
Anyway, here is an updated version of your Fiddle with my changes:
http://jsfiddle.net/5vb5oL7e/1/
And here is the actual code of the directive:
app.directive('appDropDown', function () {
return {
restrict: 'E',
replace: true,
scope: {
model: '=',
options: '=',
onChange: '='
},
template:
'<div><select ng-model="model" ng-options="a[optValue] as a[optLabel] for a in options"></select></div>',
link: function (scope, element, attrs) {
scope.optValue = attrs.optValue;
scope.optLabel = attrs.optLabel;
scope.$watch('model', function(newValue, oldValue)
{
// Execute function on change
if (scope.onChange !== undefined &&
newValue !== undefined && oldValue !== undefined)
{
scope.onChange();
}
});
}
};
});
Basically, what I did was to add a watch inside the link function on the model. Inside this watch I fire the onChange function when it's defined. The added checks for undefined on the old and new value were added to prevent the function to change unneeded on page load.
Hope this helps someone...
Kind regards,
Heino

Related

how to make angular directive accept string or model respectively

I am trying to create an angular directive that will be able to get BOTH model object and a string.
if the directive get a string it just output HTML, but if it's a model the the directive will watch the model for changes and will output data respectively.
I had tried to use the next code:
App.directive('iso2symbol', function () {
return {
restrict: 'E',
replace: true,
link: function ($scope, $element, $attrs) {
var curIsoObj = $scope.$eval($attrs.curIso);
//this is object it may change
if (typeof curIsoObj !== 'undefined') {
console.log('not a text');
$scope.$watch('curIso', function (value) {
console.log(value);
});
}
},
template: '<span>{{currencySymbol}}</span>'
}
}]);
This is not working, I had googled it for long time and I don't find the problem....
here is a link to JSfiddle where I had set a DEMO
Becareful with what you're watching.
according to your watch function you're watching $scope.curIso which really isn't a scope object.
you should be watching
$scope.$watch(function(){return $scope.$eval($attrs.curIso);}, function (value) {
$scope.txt = value;
});
Try this:
App.directive('iso2symbol', function () {
return {
restrict: 'E',
replace: true,
require: 'ngModel',
scope: {
curIso: '='
},
link: function ($scope, $element, $attrs) {
$scope.$observe('curIso', function(newValue, oldValue){
var curIsoObj = newValue;
// Do your test now to see if it's undefined,
// a string, or generic object.
// (the first time it will likely be undefined)
}
},
template: '<span>{{currencySymbol}}</span>'
}
}]);

Using angular directive to watch for change values with bootstrap-select

I am new to angular and after having researched for 2 days, I still haven't come up with a solution that will work yet.
I have a select item that will have its options updated and am also using bootstrap-select.js. I can get either or to work on their own (angular items updating dynamically as expected in a standard select list or the bootstrap-select item to work with static options). If someone could provide some guidance as to what I am doing wrong, it would be greatly appreciated! Here is my code:
HTML:
<div ng-app="app">
<div ng-controller="ctrl">
<selectpicker data-array="users" data-selected="info.selected"></selectpicker>
<button ng-click="add()">Add</button>
</div>
</div>
JS:
var app = angular.module('app', []);
app.controller('ctrl', ['$scope', function($scope)
{
$scope.info = {selected: 1};
$scope.users=[];
$scope.users.splice(0);
$scope.users = [{name: "Bob", id: "1"},{name:"Tom", id: "2"}];
$scope.add = function () {
$scope.users.push({name: "John", id: "3"});
};
}]);
app.directive('selectpicker', function($timeout)
{
return {
restrict: 'E',
replace:true,
scope: {
selected: '=',
array: '=',
class: '='
},
template: '<select class="selectpicker" multiple data-selected-text-format="count" ng-model="currentName" ng-options="user.name for user in array">' +
'</select>',
replace:true,
link: function(scope, el, attrs) {
$timeout(function () {
scope.$watch('array', function (newVal) {
console.log(scope.array);
var select = $(el).selectpicker();
select.change(function(evt) {
var val = $(el).selectpicker('val');
$scope.selected = val;
$scope.$apply();
});
}, true);
});
}
};
});
So when I click the Add button, I can see the scope.array value
updates from the console output, but the dropdown itself won't update. I've tried piecing together solutions from similar answers but nothing has yielded results so far.
According to the documentation of Bootstrap-select, you could use refresh() method to update the UI when an underlying select tag has been changed like this:
$(el).selectpicker('refresh');
But lets look further how to improve the directive:
the element variable el is already a jQuery object, no need to wrap it again as $(el).
a $timeout is become unnecessary after the UI refresh is properly handled.
move the change event binding out of $watch, otherwise the handler will be fired multiple times per a change.
$watchCollection is enough if the options will be only add/remove i.e. an individual option will not be changed.
The final result would look like this:
link: function(scope, el, attrs) {
var select = el.selectpicker();
select.change(function (evt) {
scope.selected = el.selectpicker('val');
scope.$apply();
});
scope.$watchCollection('array', function(newVal) {
console.log(scope.array);
el.selectpicker('refresh');
});
}
Example Plunker: http://plnkr.co/edit/Kt0V0UBuHaRYMjKbI5Ov?p=preview

Data binding not working in an event in directive

I cannot get the data binding to work in this directive. The variables are not bound properly when I change them inside the event handler,
What am I doing wrong? > Test Fiddle
var myApp = angular.module('myApp', [])
.directive('inputTest', function () {
return {
restrict: 'E',
template: '<div class="input-group">\
<input type="text" class="form-control" />\
<br>child scope: {{myValue}}\
</div>',
scope: {
myValue: '=',
},
link: function (scope, element, attrs) {
$(element).on('click', function (e) {
alert('c');
scope.myValue = 'clicked';
});
scope.myValue = 'not clicked';
},
};
})
function MyCtrl($scope) {
$scope.myValue = 'parent value';
}
HTML
<div ng-controller="MyCtrl">parent scope: {{myValue}}
<input-test my-value="myValue"></input-test>
</div>
Do not forget to call $scope.$apply() at the end of the event handler.
First level bindings may not work as expected due to how prototypical inheritance works. If you try the first point and still get no results, try putting myValue a level deeper:
$scope.data.myValue = 'parent value';
and:
<input-test my-value="data.myValue"></input-test>

Why does the ngModelCtrl.$valid not update?

I'm trying to create a directive that contains an inputfield with a ng-model and knows if the inputcontrol is valid. (I want to change a class on a label within the directive based on this state.) I want to use the ngModelController.$valid to check this, but it always returns true.
formcontroller.$valid or formcontroller.inputfieldname.$valid do work as exprected, but since im trying to build a reusable component using a formcontroller is not very handy because then i have to determine what field of the form corresponds with the current directive.
I dont understand why one works and one doesnt, because in de angular source it seems to be the same code that should manage these states: The ngModelController.$setValidity function.
I created a test directive that contains a numeric field with required and a min value. As you can see in the fiddle below, the model controller is only triggered during page load and after that never changes.
jsfiddle with example directive
Directive code:
angular.module('ui.directives', []).directive('textboxValid',
function() {
return {
restrict: 'E',
require: ['ngModel', '^form'],
scope: {
ngModel: '='
},
template: '<input type="number" required name="somefield" min="3" ng-model="ngModel" /> '+
'<br>Form controller $valid: {{formfieldvalid}} <br> ' +
'Model controller $valid: {{modelvalid}} <br>'+
'Form controller $valid: {{formvalid}} <br>',
link: function (scope, element, attrs, controllers) {
var ngModelCtrl = controllers[0];
var formCtrl = controllers[1];
function modelvalid(){
return ngModelCtrl.$valid;
}
function formvalid(){
return formCtrl.$valid;
}
scope.$watch(formvalid, function(newVal,oldVal)
{
scope.modelvalid = ngModelCtrl.$valid;
scope.formvalid = formCtrl.$valid;
scope.formfieldvalid = formCtrl.somefield.$valid;
});
scope.$watch(modelvalid, function(newVal,oldVal)
{
scope.modelvalid = ngModelCtrl.$valid;
scope.formvalid = formCtrl.$valid;
scope.formfieldvalid = formCtrl.somefield.$valid;
//This one only gets triggered on pageload
alert('modelvalid ' + newVal );
});
}
};
}
);
Can someone help me understand this behaviour?
I think because you're watching a function and the $watch is only execute when this function is called !!
Watch the model instead like that :
scope.$watch('ngModel', function(newVal,oldVal)
{
scope.modelvalid = ngModelCtrl.$valid;
scope.formvalid = formCtrl.$valid;
scope.formfieldvalid = formCtrl.somefield.$valid;
//This one triggered each time the model changes
alert('modelvalid ' + ngModelCtrl.$valid );
});
I figured it out..
The textboxValid directive has a ng-model directive, and so does the input that gets created by the directive template. However, these are two different directives, both with their own seperate controller.
So, i changed my solution to use an attribute directive like below. This works as expected.
.directive('attributetest',
function() {
return {
restrict: 'A',
require: 'ngModel',
scope: {
ngModel: '='
},
link: function (scope, element, attrs, ngModelCtrl) {
function modelvalid(){
return ngModelCtrl.$valid;
}
scope.$watch(modelvalid, function(newVal,oldVal){
console.log('scope.modelvalid = ' + ngModelCtrl.$valid );
});
}
};
});

AngularJS: Binding inside directives

I'm trying to acheive databinding to a value returned from a service inside a directive.
I have it working, but I'm jumping through hoops, and I suspect there's a better way.
For example:
<img my-avatar>
Which is a directive synonymous to:
<img src="{{user.avatarUrl}}" class="avatar">
Where user is:
$scope.user = CurrentUserService.getCurrentUser();
Here's the directive I'm using to get this to work:
.directive('myAvatar', function(CurrentUser) {
return {
link: function(scope, elm, attrs) {
scope.user = CurrentUser.getCurrentUser();
// Use a function to watch if the username changes,
// since it appears that $scope.$watch('user.username') isn't working
var watchUserName = function(scope) {
return scope.user.username;
};
scope.$watch(watchUserName, function (newUserName,oldUserName, scope) {
elm.attr('src',CurrentUser.getCurrentUser().avatarUrl);
}, true);
elm.attr('class','avatar');
}
};
Is there a more succinct, 'angular' way to achieve the same outcome?
How about this ? plunker
The main idea of your directive is like
.directive('myAvatar', function (CurrentUserService) {
"use strict";
return {
restrict: 'A',
replace: true,
template: '<img class="avatar" ng-src="{{url}}" alt="{{url}}" title="{{url}}"> ',
controller: function ($scope, CurrentUserService) {
$scope.url = CurrentUserService.getCurrentUser().avatarUrl;
}
};
});

Resources