AngularJS ng-options inside custom directive which is inside ng-repeat - angularjs

Could you help me to find a way to set and get select element values which are placed inside my custom directive.
This is what I have:
<body ng-app="myApp">
<div ng-controller="MyCtrl">
<div ng-repeat="category in categories">
{{category.Name}}:
<status-selector items="statuses" ng-model="selectedStates[status.Id]"></status-selector>
</div>
</div>
</body>
I have two arrays: categories and statuses. Each category can have its own status. When a status for a category is chosen, it should be saved to selectedStatus to finally have something like [{CategoryId:1,StatusId:2},{CategoryId:2,StatusId:3}]. In case if selectedStatus was already initialized, I would like to see chosen statuses for corresponding categories, means that I also need to put values, not just read them.
myApp
.controller("MyCtrl", function($scope) {
$scope.categories = [{Id:1, Name: "Category1"}, {Id: 2, Name: "Category2"}];
$scope.statuses = [{Id: 1, Name: "Low"}, {Id: 2, Name: "Normal"}, {Id: 3, Name: "High"}]
$scope.selectedStates = {};
})
.directive('statusSelector', function() {
return {
restrict: 'E',
replace: true,
scope: { items: '=', ngModel: '='},
template: '<span><select class="select" ng-options="obj.Id as obj.Name for obj in items" ng-model="ngModel"></select></span>',
link: function(scope, element, attrs) {
}
}
});
Thank you.
Demo: Fiddle

You should have your own category model as ng-model. It probably make more sense.
<status-selector items="statuses" ng-model="category.Status"></status-selector>
To set the status, just bring the Status filled in the JSON.
// ...
.controller("MyCtrl", function($scope) {
$scope.categories = [{Id:1, Name: "Category1", Status: 1}, {Id: 2, Name: "Category2"}];
// ...
Obviouslly, to get user's selection, just grab the category.Status property. Here's the updated fiddle.
Also, just a side tip. You seem to come from a .Net background, where you're used to Pascal case. In javascript, it's common sense to use camel case, instead, so you'd have {id: ..., status: ...} instead of {Id: ..., Status: ...}.

Related

AngularJS two-way data binding not working properly in directive

i am trying to implement radio-button list using ng-repeat.
typeList.html
<div ng-repeat="type in types" >
<input type="radio" id={{type.id}} name="{{type.name}}" ng-model="result" ng-value="type.id" >
{{type.name}}
<div> Result {{result}} </div> //result is changing only in the row of clicked radio-button. It should change in every row.(two way data-binding).
</div>
Directive:
angular.module('app').directive('myList',function(){
return{
restrict: 'A',
scope: {
types: '=', //here list is passed to be printed with ng-repeat
result: '=' //here I want to store which radio-button was selected last time by id
},
templateUrl: 'html/typeList.html'
};
});
Directive has isolated scope. I am passing two parameters. List to be printed with radio buttons and result object which stores answer(id-what radio button was clicked last time) in parent scope. Unfortunately whenever i click on radio-buttons my result is changing only locally.
Passing parameters to my directive.
<div my-list types="list" result="selected"></div>
Passed list and result paramater from controller to myList directive.
$scope.list = [
{ id: 1, name:'Name 1' },
{ id: 2, name:'Name 2' },
{ id: 3, name:'Name 3' }
];
$scope.selected = -1;
I would be grateful for any help.
You have to pass a non-primitive object to the model to get its reference for two-war binding. Just wrap selected into an object for its reference.
In your controller use.
$scope.list = [{
id: 1,
name: 'Name 1'
}, {
id: 2,
name: 'Name 2'
}, {
id: 3,
name: 'Name 3'
}];
$scope.ctrlModel = {
selected: -1
}
And in the Markup that is 'html/typeList.html'
<div ng-repeat="type in types" >
<input type="radio" id={{type.id}} ng-model="result.selected" ng-value="type.id" >
{{type.name}}
</div>
Result {{result.selected}}
Working Fiddle Demo
Hope it helps.
try to have scope variables as object like
$scope.types = {
list: {},
selected: 'radioValueThatNeedsToBeSelected'
}

Matching two models up with directive

I'm attempting to solve an issue with the design of my models, view, and directive in AngularJS. Essentially I have a list of products being populated on the page by category with quantity fields being added for each one. I need these quantity fields to have bidirectional binding with an array of orderItem that I return from and post to the server.
Currently I can get the oc.order.orderItems array to update with new orderItem when I change the quantity field of a product. But when I try to populate a previous order, I am having trouble updating the model and view. Majority of my issue stems from how I should handle the ngModel attribute of the quantity field tied with my directive.
Here are some of my code samples (slightly stripped down). If anything needs clarification to help with the assist, because I know this sounds confusing, please let me know.
Product Model:
var product = { id: 1, category_id: 1, name: 'Apple' };
OrderItem Model:
var orderItem = { id: 1, order_id: 1, product_id: 1, quantity: 2 };
View:
<div ng-repeat="category in oc.categories">
<h2>{{ category.name }}<h2>
<div ng-repeat="product in category.products">
<h3>{{ product.name }}</h3>
<input type="number" placeholder="Quantity"
order-item="product" ng-model="oc.order.orderItems[$index]" />
</div>
</div>
Directive:
angular
.module('OrderApp')
.directive('orderItem', function ($window) {
return {
restrict: 'A',
scope : {
product: '=orderItem'
},
require: 'ngModel',
link: function (scope, element, attrs, ngModelCtrl) {
ngModelCtrl.$parsers.push(function (viewValue) {
if (!viewValue > 0) return;
if (!scope.id) scope.id = 0;
if (!scope.order_id) scope.order_id = 0;
return {
id: scope.id,
order_id: scope.order_id,
quantity: viewValue,
product_id: scope.product.id
};
});
ngModelCtrl.$formatters.push(function (modelValue) {
if (!modelValue) return;
if (!modelValue.id) scope.id = modelValue.id;
if (!modelValue.order_id) scope.order_id = modelValue.order_id;
return modelValue.quantity;
});
}
};
});
(Directive doesn't seem right at all and don't know where to put a view $render and scope $watch)
After looking at the problem differently, I found that my solution should not have been a directive but actually a service to share the data for each model between controllers.
Solution: Gist

Watching for change in select options

I want to disable an input based on number of elements in it (eg. disable in case of only 1 element). Right now I have my eyes on a directive. The reason I went with a directive is that the ng-options often get quite complicated so it would be annoying to copy/paste them with a comparison into an ng-disabled.
My problem is that I cannot listen to the number of options in the select (or at least I couldn't find a way yet). I.e. my idea would have been to do something like so in the link-function. Basic select (auto-disable being my directive):
<select name="field1" ng-model="data.field1" ng-options="i.value as i.text for i in values" auto-disable >
<option disabled value="" style="display: none;">placeholder</option>
</select>
and the link method being defined as such:
link: function (scope, elem, attrs) {
scope.$watch(elem[0].length, function() {
elem.prop('disabled', elem[0].length <= 1);
});
}
So: Is there some way to watch the elements in a select, or can I somehow access the select's option count easily inside the ng-disabled?
PS: I already tried listening to "data.field1" but that does not work in all cases (eg. if just other values change but the selected value does not).
If I understand what you need here is the JSFiddle to disable the select when there are less than 2 elements, you don't need a directive just look at the array length:
HTML:
<div ng-app ng-controller="MyCtrl">
<select name="field1" ng-model="data.field1" ng-options="i.value as i.text for i in values | filter: { valid: true }" ng-disabled="(values |filter: { valid: true }).length < 2">
</div>
JS:
function MyCtrl($scope) {
$scope.values =
[
{value: 'hello', text: 'world', valid: true},
{value: 'hello2', text: 'world', valid: false},
{value: 'hello3', text: 'world', valid: false},
{value: 'hello4', text: 'world', valid: false},
];
}
EDIT USING DIRECTIVE:
HTML:
<div ng-controller="MyCtrl">
<select id="field1" ng-model="data.field1" ng-options="i.value as i.text for i in values | filter: { valid: true }" auto-disable></select>
</div>
JS DIRECTIVE:
app.directive('autoDisable', function() {
return {
restrict: 'A',
replace: true,
link: function(scope, elm, attrs) {
angular.element(document).ready(function () {
if ((elm[0].options.length - 1) < 2) {
elm.attr("disabled", "disabled");
}
});
}
};
});
Why do you need to subtract 1 from options.length? Because ng-options automatically adds 1 empty option you have to remove from the count.
Hope it helps
Here is the JSFiddle with the directive:

Saving a checkbox list in Angularjs with node

I want to save and load a checkbox list using binding in angularjs with a node backend. This SO question (How do I bind to list of checkbox values with AngularJS?) answers how I can load the check box from a static javascript object but how can I save the checkbox values after the user selects them?
I want to save the checkbox values in a single field but how can I tell angular to bind the checkbox values into a single field defined in a model to my mongodb? I cant just use ng-model as there are multiple checkboxes.
Needless to say that I am new to angular so any help here is greatly appreciated ...
Thanks for any help you can provide.
kseudo
Just add ng-model to your checkbox. By this way you can update model in controller on any checkbox state change.
Here is example:
HTML
<div ng-controller="Ctrl">
<label ng-repeat="fruit in fruits">
<input
type="checkbox"
name="fruit.name"
ng-model="fruit.value"
> {{fruit.name}}
</label>
<pre>{{fruits| json}}</pre>
</div>
JS
var app = angular.module('app', []);
function Ctrl($scope) {
$scope.fruits = [{
name: 'apple',
value: false
}, {
name: 'orange',
value: false
}, {
name: 'pear',
value: false
}, {
name: 'naartjie',
value: false
}];
}
Demo Fiddle
[EDIT]
BTW we can make the copy by using angular.copy() method. When we press button, the new copy of fruits model will be created (and you should send it to server as json). Old model fruits will stay the same:
$scope.fruitsCopy = [];
$scope.init = function(){
$scope.fruitsCopy = angular.copy($scope.fruits );
}
To convert data to JSon I would use:
var jsonData = JSON.stringify($scope.fruitsCopy);
Demo2 Fiddle
Let's say you defined your model as such:
function Ctrl($scope) {
$scope.items = [{
name: 'A',
checked: false
}, {
name: 'B',
checked: false
}, {
name: 'C',
checked: false
}, {
name: 'D',
checked: false
}];
}
And created a view for the model:
<ul>
<li ng-repeat="item in items">
<label>
<input type="checkbox" ng-model="item.checked">
{{item.name}}
</label>
</li>
</ul>
<button ng-click="save()">save</button>
Next you have to define save function:
$scope.save = function() {
//angular.toJson converts array to string, something like
// '[{"name":"A","checked":false},{"name":"B","checked":true},...]'
var json = angular.toJson($scope.items);
//Service is angular service for your model that makes http requests to your backend
Service.save(json).then(function(){
//here you can notify user that data is persisted
}, function() {
//here you can notify user that there was a problem with request
});
}
And a simple model service:
.service('Service', function($http) {
return new function() {
this.save = function(data) {
return $http.post('url/to/backend', data);
}
}
});

How to create object-driven tabs in AngularJS using a single directive and a custom label field for each tab?

The example on AngularJS homepage creates tabs by using two directives: one for the tabs and one for the panes. Is it possible to create a single template and a single directive like so:
HTML:
<div ng-app="components">
<tabs objects="someObjects" labelprop="name" shouldbe="the value of object.name"></tabs>
<tabs objects="someObjects" labelprop="id" shouldbe="the value of object.id"></tabs>
</div>
JS:
angular.module('components', []).
directive('tabs', function() {
return {
restrict: 'E',
replace: true,
scope: {objects: '=', labelprop: '#', shouldbe: '#'},
controller: function($scope, $element) {
$scope.someObjects = [
{name: "One", id: 1, data: 'foo'},
{name: "Two", id: 2, data: 'bar'},
{name: "There", id: 3, data: 'foobar'}
]
},
template:
'<div class="tabbable">' +
'<ul class="nav nav-tabs">' +
'<li ng-repeat="object in someObjects">' +
'<p>How do I use "labelprop" to get {{shouldbe}}</p>' +
'</li>' +
'</ul>' +
'</div>'
}
})
(See this fiddle illustrating my question: http://jsfiddle.net/2GXs5/3/ )
From what I understand so far, because ng-repeat is its own directive, it goes off and does its own thing and I cannot access the object property in the scope, say if I wanted to do this in the directive's link function: scope.label = scope.object['scope.labelprop']
Also, I am still trying to wrap my head around interpolation, transclusion, etc. so the solution might involve that somehow.
To solve issue in demo you have access to both the repeating item and the directive scope within the template
<p>{{labelprop}} is {{object[labelprop]}}</p>
Anything not filter related within the expression that isn't quoted will be treated as a variable so long as that vriable is within current scope
demo: http://jsfiddle.net/2GXs5/4/

Resources