Checkbox list breaks, why? And optimal Angularjs way? - angularjs

I don't understand this, but I suspect I'm doing something wrong, or a non-angularjs way.
I have a checkbox list inside ng-repeat. It controller loads the list from a JSON. Pretty straightforward really. I'm then using a directive (car-select) on each of the resulting checkboxes. This directive calls a function inside the main $scope (selectBrand()). This cycles through the selected checkboxes, and if checked==true, add to $scope.brand. I've added a textbox so that $scope.brand fills it, and i've set it to required so that it fires the built in validation e.g:
HTML:
<div ng-repeat="v in viewModel">
<label class="checkbox">
<input type="checkbox" ng-model="v.c" ng-checked="v.c" />{{v.n}}
</label>
</div>
<input type="text" name="brands" ng-model="brands" car-select required/> <br>
JS:
$scope.selectBrand = function() {
var selectedBrands = [];
angular.forEach($scope.viewModel, function(v){
if (v.c)
selectedBrands.push(v.v);
})
if (selectedBrands.length > 0)
$scope.brands = selectedBrands;
else
$scope.brands = null;
}
DIRECTIVE:
app.directive('carSelect', function() {
return function(scope, element) {
element.bind('change', function() {
scope.selectBrand();
})
}
});
Here's the weird part which I don't understand. It took a while to figure out that this particular line was making this whole thing work. If I add the following in the page, everything works great. But if i remove it, the whole thing breaks. WHY?!
<div>{{selectBrand()}}</div>
It's like the whole thing doesn't bind unless the above is called in the HTML. It's called in the directive, and I've tried putting that call inside the clickButton() function, but eventually it breaks. Either way, the live update of the textbox seems to fail if the above is removed. I'd love to get a good explanation of how I'm doing something wrong and how I could fix it :)
PLUNKER:
http://plnkr.co/edit/4QISKcq7YYH678YLsTTF?p=preview

Ok, i create fork ;-)
update variable with only data checked
your model :
{"cars":
[
{"v":"m","n":"Mini","c":false},
{"v":"c","n":"Corvette","c":true},
{"v":"b","n":"BMW","c":true},
{"v":"l","n":"Lamborghini","c":true},
{"v":"f","n":"Ferrari","c":false}
]
}
you want only checked :
$scope.brands = $filter('filter')($scope.viewModel, {c: true});
when model change you want to update your variable so use watch in controller
$scope.$watch('viewModel', function(newval, oldval){
if (oldval != newval)
{
$scope.brands = $filter('filter')($scope.viewModel, {c: true});
}
},true
);
});
see http://plnkr.co/edit/PnABre?p=preview

Related

Angular way of binding elements of the same name together

In angular, I have a list of checkboxes that are all binded to a value of boolean value that I get from a json:
<div ng-repeat="err in rec.errorList"><input type="checkbox" ng-model="err.ignore" name="{{err.errorCode}}" ng-value="err.errorCode" check-all="{{err.errorCode}}" /></div>
But mean while, I am trying to check all those checkboxes with the same name, when checking one of the checkboxes!
what is the best way of doing that in angular way? I mean is there a way of binding all these checkboxes with the same Name attribute for example, together?
I tried to write a directive, something like this but don't know how should I continue on that:
.directive("checkAll", function(){
return {
link: function(scope, element, attr){
element.bind('change', function(){
var errorCode = attr["checkAll"];
var elms = scope.errorCode;
})
}
}
})
Here is a plunker of what I actually want to do
http://plnkr.co/edit/sLXGlXRh9vu7FETDmJd1?p=preview
I can have many lists, and what I want to is whenever I click on one of these checkboxes, update all the checkboxes with the same errorCode maybe without looping on all those errorLists again.
You could do this simply using the same ng-model for each name.
This will look like this :
Controller
$scope.errorList = [{errorCode:1},{errorCode:2},
{errorCode:1},{errorCode:3},{errorCode:1},{errorCode:1},
{errorCode:2},{errorCode:1},{errorCode:3},{errorCode:3}];
$scope.checkboxByName = {};
View
<div ng-repeat="err in errorList">
<input type="checkbox" ng-model="checkboxByName[err.errorCode]">
</div>
If you really need the error.ignore var on each error, you could add this function :
$scope.updateIgnore = function(){
angular.forEach($scope.errorList, function(error){
error.ignore = $scope.checkboxByName[error.errorCode];
})
}
And a ng-change on all your inputs :
ng-change="updateIgnore()"
Here is a plunker showing the full implementation
Hope it helped.

pre-populated form data is undefined in service

jQuery:
$("#inputParentName").val(response.name);
HTML/Angular Form:
<form role="form" ng-submit="addParentService.addParent()">
<div class="form-group">
<label for="inputParentName">Name</label><input class="form-control" id="inputParentName" value="" type="text" ng-model="addParentService.inputParentName" />
</div>
...
<button class="btn btn-default" type="submit">Submit</button>
</form>
The following code when run diplays my name correctly in the input box.
However in my service when I try to see what the value is for inputParentName I get an undefined error. But, when I type something in to the textbox for inputParentName the typed in value displays.
Controller Code:
myapp.controller('AddParentController', function ($scope, addParentService) {
$scope.addParentService = addParentService;
});
Service Code:
myapp.service('addParentService', function () {
var vm = this;
vm.parent = [];
vm.addParent = function () {
alert(vm.inputParentName);
};
});
What can I do differently so I can get the pre-loaded data to register so that my service recognizes the data?
This is just basic code that I'm trying to get working. I realize it isn't pure AngularJS. I am just trying to see how I can get this to work. I will refactor with directives after everything works as I think it should.
If you want the initial value to be "something" when the view displays, you can (technically) use ng-init, though the docs tell us expressly NOT to do this.
The only appropriate use of ngInit is for aliasing special properties
of ngRepeat, as seen in the demo below. Besides this case, you should
use controllers rather than ngInit to initialize values on a scope.
But if you're just trying to test something, ng-init would look like:
<input ng-model="test.val" ng-init="test.val='something'" />
The preferred way though would be to add the value to the controller $scope.
<input ng-model="test.val" />
Then in your controller:
myapp.controller('MyCtrl', function ($scope) {
$scope.test = {
val: 'something'
}
});
#jme11 and This Answer gave me the insight to the following way I figured out how to get this to work:
jQuery code for Facebook logic:
$("#inputParentName").val(response.name);
$("#inputParentEmail").val(response.email);
$("#inputParentBirthday").val(response.birthday);
angular.element(document.getElementById('inputParentName')).scope().$apply();
angular.element($('#inputParentName')).scope().setName(response.name);
angular.element($('#inputParentEmail')).scope().setEmail(response.email);
angular.element($('#inputParentBirthday')).scope().setBirthday(response.birthday);
My Controller code:
$scope.setName = function (val) {
addParentService.inputParentName = val;
}
$scope.setEmail = function (val) {
addParentService.inputParentEmail = val;
}
$scope.setBirthday = function (val) {
addParentService.inputParentBirthday = val;
}
Service Code:
vm.addParent = function () {
alert(vm.inputParentName);
alert(vm.inputParentBirthday);
alert(vm.inputParentEmail);
alert(vm.inputParentCellPhone);
alert(vm.inputParentCarrier);
};
Now when I'm adding my Parent the values pre-populated from Facebook are usable in my service code.
Again - Thanks to jme11 for helping me solve this.

Angular strap tooltip visible by default

I using http://mgcrea.github.io/angular-strap/#/tooltips#tooltips
and i can't use Scope methods ($show(), $hide()). Help me please. How i can use this methods?
I have input in ng-repeat
<div ng-repeat="item in data.queue" >
<input type="text" maxlength="40" bs-tooltip data-animation="am-flip-x" data-title="{{item.file.tooltip_title}}">
<div>
And i need to set visible tooltips if item.file.flag=== true, and then hide tooltip of beyond the 5 second.
To get show() and hide() methods you gotta do everything on javascript side. Something like this:
markup
<div id="div1">some</div>
directive
app.directive('someThing', ['$tooltip', '$timeout', function($tooltip, $timeout){
return {
link: function($scope){
$scope.someFunction = function (item){
$timeout(function(){
var target = angular.element(document.getElementById('div1'));
var myTooltip = $tooltip(target, { title:'tip!!', trigger:'manual', placement:'top'});
myTooltip.$promise.then(function() { myTooltip.show(); });
$timeout(function(){
myTooltip.$promise.then(function() { myTooltip.hide(); });
}, 4000);
}, 1500);
};
}
};
}]);
The Angular way of solving your problem is using, for instance, data-bs-show="item.file.flag".
It will show your tooltip while item.file.flag == true.
The bsShow attribute expects a boolean value, so if you need to hide after 5 seconds, you might have another flag and set it to false after this time using $timeout.
It is possible to use $show()/$hide() but it's tricky and ugly so I'd avoid that if possible.

AngularJS directive remove class in parent element

I am using the following code to add / remove class "checked" to the radio input parent. It works perfectly when I use JQuery selector inside the directive but fails when I try to use the directive element, can someone please check my code and tell me why it is not working with element and how I can possibly add/ remove class checked to the radio input parent while using element instead of the jquery selectors? Thanks
.directive('disInpDir', function() {
return {
restrict: 'A',
scope: {
inpflag: '='
},
link: function(scope, element, attrs) {
element.bind('click', function(){
//This code will not work
if(element.parent().hasClass("checked")){
scope.$apply(function(){
element.parent().removeClass("checked");
element.parent().addClass("checked");
});
}else{
scope.$apply(function(){
element.parent().addClass("checked");
});
}
//This code works perfectly
$('input:not(:checked)').parent().removeClass("checked");
$('input:checked').parent().addClass("checked");
});
}
};
});
HTML:
<div class="inpwrap" for="image1">
<input type="radio" id="image1" name="radio1" value="" inpflag="imageLoaded" dis-inp-dir/>
</div>
<div class="inpwrap" for="image2">
<input type="radio" id="image2" name="radio1" value="" inpflag="imageLoaded" dis-inp-dir/>
</div>
Your code actually works for me in Plnkr (more or less):
http://plnkr.co/edit/vJJRYQQxH7u2bKSc27UA?p=preview
When you run this, the 'checked' class gets correctly added to the parent DIVs using only the first code you included. (I commented out the jQuery mechanism - I didn't add jQuery to this page, as a test.)
However, I think what you're trying to accomplish isn't working out because you're only capturing click events. The radio button that loses its checked attribute doesn't get a click event, only the next one does. In jQuery your selector is really broad - you're hitting every radio button, so it does what you want. But since you only trap click on the radio button that receives the click, it doesn't do what you want using the other pattern. checked gets added, but never removed.
A more Angular-ish pattern would be something like this:
http://plnkr.co/edit/HN7tLxkRA0jUL5GPjk5V?p=preview
link: function($scope) {
$scope.checked = false;
$scope.$watch('currentValue', function() {
$scope.checked = ($scope.currentValue === $scope.imgNumber);
});
$scope.setValue = function() {
$scope.currentValue = $scope.imgNumber;
};
}
What you see here lets Angular do all the dirty work, which is kind of the point. You can actually go a lot further than this - you could probably cut half the code out and do it all with expressions. The point is that in Angular, you really want to focus on the DATA (the model). You wire all of your behaviors and events up (controller) to things that manipulate that data, and then wire up all your DOM styles, classes, templates (view), etc. up to conditionals against that same data. And that is the point of MVC!

AngularJS setting model value from directive and calling a parent scope function holds on to the previous value inside that function

js fiddle http://jsfiddle.net/suras/JzaV9/4/
This is my directive
'use strict';
barterApp.directive('autosuggest', function($timeout, $http) {
return {
restrict: "E",
scope: {
modelupdate:"=",
suggestions:"=",
urlsend:"#"
},
template: '<ul><li ng-repeat="suggest in suggestions" ng-click="updateModel(suggest)">{{suggest}}</li></ul>',
link: function (scope, element) {
scope.$watch('modelupdate', function() {
$timeout(function(){
$http.post(scope.urlsend, {q:scope.modelupdate}).then(function(data){
scope.suggestions = data.data;
console.log(data.data);
});
}, 3000);
});
scope.updateModel = function(value){
scope.modelupdate = value;
scope.$parent.getBookInfo();
}
}
};
});
controller is
barterApp.controller('openLibraryCtrl', ['$scope','$http',function ($scope,$http) {
$scope.title = "";
$scope.getBookInfo = function(value){
if($scope.title == "" || $scope.title == " ") //here title is 'r'(previous value)
{
return;
}
$http.get('/book_info.json?q='+$scope.title).then(function(res){
if(Object.keys(res).length !== 0)
{
data = res.data
console.log(data);
}
});
}
//here title is 'rails' (updated value from directive).
//( used a watch function here on model update
// and checked it but inside getBookInfo it is still 'r' )
}]);
in the update model function i set the model value and call the getBookInfo function on parent scope. but the thing here is when (this is a autocomplete) i enter the value in a input field that contains ng-model say for example 'r' then triggers the watch and i get suggestions from a post url (lets say "rails", "rock") and show it through the template as in the directive. when i click one of the suggestions (say 'rails') it triggers the updatemodel function in directive and sets the model value. its fine upto this but when i call the getBookInfo function in parent scope then $scope.title is 'r' inside the function (i checked with console log outside the function the model value was updated correctly as 'rails' ). again when i click 'rock' the model value inside getBookInfo is 'rails'.
i have no clue whats going on. (i also tested with watch function in controller the model gets updated correctly but the function call to getBookInfo holds back to the previous value)
view
<form ng-controller="openLibraryController">
<input type="text" ng-model="title" id="title" name="book[title]" />
<autosuggest modelupdate = "title" suggestions = "book_suggestions" urlsend="/book_suggestions.json"> </autosuggest>
</form>
I didn't look deep into it, but I suspect (with a high degree of confidence) that the parent scope has not been updated at the time of calling getBookInfo() (since we are still in the middle of a $digest cycle).
Not-so-good Solution 1:
You could immediately update the parent scope as well (e.g. scope.$parent.title = ...), but this is clearly a bad idea (for the same reasons as nr 2, but even more so).
Not-so-good Solution 2:
You could pass the new title as a parameter to getBookInfo().
Both solutions result in mixing controller code with directive code and creating a tight coupling between your components, which as a result become less reusable and less testable.
Not-so-bad Solution:
You could watch over the title and call getBookInfo() whenever it changes:
$scope.$watch('title', function (newValue, oldValue) {
getBookInfo();
});
This would be fine, except for the fact that it is totally unnecessary.
Better Solution:
Angular is supposed to take care of all that keep-in-sync stuff for us and it actually does. You don't have given much context on what is the purpose of calling getBookInfo(), but I am guessing you intend to update the view with some info on the selected book.
In that case you could just bind it to an element (using ng-bind) and Angular will make sure it is executed properly and timely.
E.g.:
<div>Book info: <span ng-bind="getBookInfo()"></span></div>
Further more, the autosuggest directive doesn't have to know anything about it. It should only care about displaying suggestions, manipulating the DOM (if necessary) and updating the specified model property (e.g. title) whenever a suggestion is clicked. What you do with the updated value should be none of its business.
(BTW, ideally the suggestions should be provided by a service.)
Below is a modified example (based on your code) that solves the problem. As stated above there are several methods of solving the problem, I just feel this one tobe cleaner and more aligned to the "Angular way":
Book title: <input type="text" ng-model="book.title" />
<autosuggest modelupdate="book.title"
suggestions="book.suggest()"></autosuggest>
Book info: <span ng-bind="book.getInfo()"></span>
Just by looking at the HTML (without knowing what is in JS), one can easily tell what is going on:
There is a text-field bound to book.title.
There is a custom autosuggest thingy that offers suggestions provided by book.suggest() and updates book.title.
There is a span that displays info about the book.
The corresponding directive looks like this:
app.directive('autosuggest', function() {
return {
restrict: 'E',
scope: {
modelupdate: '=',
suggestions: '&'
},
template:
'<ul><li ng-repeat="suggest in suggestions()" ' +
'ng-click="modelupdated = suggest">' +
'{{suggest}}</li></ul>'
};
});
As you can see, all the directive knows about is how to retrieve suggestions and what to update.
Note that the same directive can be used with any type of "suggestables" (even ones that don't have getBookInfo()); just pass in the right attributes (modelupdated, suggestions).
Note also, that we could remove the autosuggest element and the app would continue to work as expected (no suggestions of cource) without any further modification in HTML or JS (while in your version the book info would have stopped updating).
You can find the full version of this short demo here.

Resources