Can't access options for ng-options inside an ng-repeat - angularjs

I would like to be able to access a set of select options within an ng-options inside an ng-repeat, but the ng-repeat creates a new scope where I can no longer access this. Is there a way around this without adding it to the repeating data?
<body ng-controller="mainCtrl" class="container">
<test repeater="repeater"></test>
</body>
Template:
<div ng-repeat="repeat in repeater">
<span>{{repeat.type}}</span>
<select ng-options="value for value in options">
<option value="">Choose one:</option>
</select>
</div>
JS:
angular.module('app').controller('mainCtrl', function($scope) {
$scope.repeater = [
{
type: 'best'
},
{
type: 'worst'
},
{
type: 'ok'
}
];
});
angular.module('app').directive('test', function() {
return {
scope: {
repeater: '='
},
templateUrl: 'test.html',
controller: function($scope) {
$scope.options = ['Nope', 'Yup', 'Sure'];
}
};
});
Plunker

You're close, but missing 1 thing. ng-model
<select ng-model="selectedOption" ng-options="value for value in options">
<option value="">Choose one:</option>
</select>

Related

AngularJS parent ng-model is not binding with custom directive when using ng-if in the template [duplicate]

This question already has answers here:
What are the nuances of scope prototypal / prototypical inheritance in AngularJS?
(3 answers)
Closed 4 years ago.
I am trying to create a custom directive to render dropdown(select).
app.directive("uiDropdown", function () {
return {
restrict: "E",
replace: true,
scope: {
'model': '=ngModel',
'readOnly':'=?'
},
templateUrl : 'template/dropdownTemplate.html',
link: function (scope, elem, attrs) {
}
};
});
the template is
<span ng-if="!readOnly">
<select ng-model="model" >
<option value="1">One</option>
<option value="2">Two</option>
<option value="3">Three</option>
</select>
Html code to use the directive is
<ui-dropdown ng-model="region" read-only='readOnly'>
The plunker code is plunker
If I remove the code 'ng-if="!readOnly"' from the template file, it is working as expected. If I change from "ng-if" to "ng-show" it is working as well.
Am I missing something here? Actually, the directive is supposed to do much more functionality than the one shown in this example. I perfer to use ng-if instead of ng-show. Please help in resolving this issue.
It has to do with the fact that ng-if creates its own child scope and then you're using a primitive directly. ng-if will actually create a local model boolean that has no relation to the parent. This would be an issue with ng-if even if it weren't being used via a directive, too.
You can work around this by passing an object and reading/setting a value on that object. Here's a simple example showing that your ng-if issue is not related to the directive and then how you can fix this using an object:
angular.module('app', [])
.controller('ctrl', function($scope) {
$scope.readOnly = false;
$scope.primitive = "1";
$scope.object = {
selectedValue: "1"
};
})
.directive('uiDropdown', function() {
return {
restrict: 'E',
templateUrl: 'dropdownTemplate.html',
scope: {
model: '=ngModel',
fieldName: '#',
readOnly: '=?'
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.7.5/angular.min.js"></script>
<div ng-app="app" ng-controller="ctrl">
<div>
<label>Readonly: <input type="checkbox" ng-model="readOnly" /></label>
</div>
<div>
<h1>ng-if with primitive - no directive</h1>
<h2>This will not work</h2>
<div>
Value: {{ primitive }}
</div>
<div ng-if="!readOnly">
<select ng-model="primitive">
<option value="1">One</option>
<option value="2">Two</option>
<option value="3">Three</option>
</select>
</div>
</div>
<div>
<h1>ng-if with object - directive</h1>
<h2>This will work</h2>
<div>
Value: {{ object.selectedValue }}
</div>
<ui-dropdown ng-model="object" read-only="readOnly" field-name="selectedValue"></ui-dropdown>
</div>
<script type="text/ng-template" id="dropdownTemplate.html">
<div ng-if="!readOnly">
<select ng-model="model[fieldName]">
<option value="1">One</option>
<option value="2">Two</option>
<option value="3">Three</option>
</select>
</div>
</script>
</div>
ng-if contains a statement that's either true or false
Try
<ui-dropdown ng-model="region" read-only='true'>

How to pass ng-options comprehension_expression to AngularJS component?

I am trying to add a new binding to existing AngularJS component that should take in value of type comprehension_expression as explained in the ng-options Directive API Reference.
Please check the code at the bottom to understand the situation. Note that the top <select> control comes through component named selectField. It does not show any select-options. The bottom control is added directly to index.html and works properly.
I would appreciate if someone can tell me if there is a bug in my script, any alternate approaches to pass value to ng-options attribute to the template, or let me know that there is no way for a component or directive to have such bindings.
angular.module('myApp', [])
.controller('MainController', function MainController() {
this.colors = ['red', 'blue', 'green'];
this.myColor = this.colors[1]; // blue
}).component('selectField', {
template: `
<select ng-model="$ctrl.inputModel"
ng-options="{{::$ctrl.inputOptionsExpression}}">
</select>
Selected: {{$ctrl.inputModel}}</span>
`,
bindings: {
inputModel: '=',
inputOptionsExpression: '#'
}
});
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular.min.js"></script>
</head>
<body ng-app="myApp">
<div ng-controller="MainController as vm">
<div>
<select-field input-model="vm.myColor"
input-options-expression="color for color in vm.colors">
</select-field>
</div>
<div>
<select ng-model="vm.myColor"
ng-options="color for color in vm.colors">
</select>
Selected: {{vm.myColor}}
</div>
</div>
</body>
</html>
See Why mixing interpolation and expressions is bad practice.
In this case the ng-options directive will parse the comprehension expression before the interpolation directive renders the desired expression.
Re-write the component to input the choices:
app.component('selectField', {
require: {ngModelCtrl: 'ngModel'},
bindings: {
ngModel: '<',
choices: '<'
},
template: `
<select ng-model="$ctrl.ngModel"
ng-change="$ctrl.render($ctrl.ngModel)"
̶n̶g̶-̶o̶p̶t̶i̶o̶n̶s̶=̶"̶{̶{̶:̶:̶$̶c̶t̶r̶l̶.̶i̶n̶p̶u̶t̶O̶p̶t̶i̶o̶n̶s̶E̶x̶p̶r̶e̶s̶s̶i̶o̶n̶}̶}̶"̶ ̶
ng-options="c for c in choices">
</select>
Selected: {{$ctrl.ngModel}}</span>
`,
controller: function() {
this.render = (value) => {
this.ngModelCtrl.$setViewValue(value);
};
}
})
Usage:
<select-field ng-model="vm.myColor" choices="vm.colors">
</select-field>
The DEMO
angular.module('myApp', [])
.controller('MainController', function MainController() {
this.colors = ['red', 'blue', 'green'];
this.myColor = this.colors[1]; // blue
})
.component('selectField', {
require: {ngModelCtrl: 'ngModel'},
bindings: {
ngModel: '<',
choices: '<'
},
template: `
<fieldset>Select field
<select ng-model="$ctrl.ngModel"
ng-change="$ctrl.render($ctrl.ngModel)"
ng-options="c for c in $ctrl.choices">
</select>
Selected: {{$ctrl.ngModel}}
</fieldset>
`,
controller: function() {
this.render = (value) => {
this.ngModelCtrl.$setViewValue(value);
};
}
})
<script src="//unpkg.com/angular/angular.js"></script>
<body ng-app="myApp" ng-controller="MainController as vm">
<div>
<select-field ng-model="vm.myColor"
choices="vm.colors">
</select-field>
</div>
<div>
<select ng-model="vm.myColor"
ng-options="color for color in vm.colors">
</select>
Selected: {{vm.myColor}}
</div>
</body>

$watch not displaying change with two ui-views

I have $watch function where it watches whenever a country is selected, but it's not saving the changed data to the scope. I had one ui-view, which worked fine, but then I changed the structure and now have two ui-views under same page, both uses the same controller. Reason for the change was for was to have a clean html page, I could always go back to one ui-view, but it's messy. This is where it started having problems.
here is app.js
$stateProvider
.state('homepage',{
templateUrl: '/static/partial/home/homeLayout.html',
controller: 'UploadFileCtrl',
})
.state('homepage.show',{
url: '/',
views: {
querySetup: {
templateUrl: '/static/partial/home/querySetup.html',
},
queryResults: {
templateUrl: '/static/partial/home/queryResults.html',
},
}
})
layout.html - ui-views:
<div class="col-sm-12 panel-container">
<div class="col-sm-6" style="height: 100%;">
<div ui-view="querySetup" ></div>
</div>
<div class="col-sm-6" style="height: 100%;">
<div ui-view="queryResults"></div>
</div>
</div>
controller watch:
$scope.currentCountries = [
{
name: 'United States',
code: 'US'
},
{
name: 'United Kingdom',
code: 'GB'
},
{
name: 'Germany',
code: 'DE'
},
{
name: 'France',
code: 'FR'
},
{
name: 'China',
code: 'CN'
},
{
name: 'Japan',
code: 'JP'
}
];
$scope.$watch('pickedCountry', function(){
if($scope.pickedCountry != null){
$scope.countryCode = $scope.pickedCountry.code;
}
});
here is querySetup view:
<select name="brand" ng-model = "pickedCountry"
data-ng-options="country as country.name for country in currentCountries">
<option value="">Select Brand</option>
</select>
I moved the controller out of the state homepage, and set for each view under the state homepage.show. This worked fine, but an extra operation is not working. When selecting a brand, it's supposed to run a calculation and set it in the queryResults view. The results is set to a scope which have to be displayed to queryResults.
here is queryResults:
<div class="panel panel-info">
<div class="panel-heading">
<h3 class="panel-title">OSM RESULTS</h3>
</div>
<div class="panel-body">
<textarea rows="50" cols="100" class="xmlOutput">
{{ queryResults }}
</textarea>
</div>
</div>
Please help! Not familiar with Angular $watch, and I think the problem comes when there is two ui-views. Thanks in advance for your help.
UPDATE
Found the solution to my problem, so I need to add $parent to my model and pass the changed data as a param to my $watch function:
html update:
<select name="brand" ng-model = "$parent.pickedCountry"
data-ng-options="country as country.name for country in currentCountries">
<option value="">Select Brand</option>
</select>
controller update
$scope.$watch('pickedCountry', function(data){
if(data != null){
$scope.countryCode = data.code;
}
});
This solved the issue, but still don't quite understand why I needed to add the parent. Can someone explain why?
You have your controller set up to live in homepage, but your templates live in homepage.show which is a child state of homepage. In order to access the variables from that controller you can use $parent (as you've discovered) to access the parent state's scope.
I would recommend restructuring your state definition though so you don't have to deal with that.
.state( 'homepage', {
url: '/homepage',
views: {
'':{
templateUrl:'/static/partial/home/homeLayout.html',
controller: 'UploadFileCtrl',
},
'querySetup#homepage': {
templateUrl:'/static/partial/home/querySetup.html'
},
'queryResults#homepage':{
templateUrl:'/static/partial/home/queryResults.html'
}
}
})
I believe it is because child states operate the same as prototypical inheritance (scope: true) on directives.
You might consider defining in your controller:
$scope.input = {};
$scope.$watch('input.pickedCountry', function(data){
if(data != null){
$scope.countryCode = data.code;
}
});
and in the querySetup template:
<select name="brand" ng-model = "input.pickedCountry"
data-ng-options="country as country.name for country in currentCountries">
<option value="">Select Brand</option>
</select>

get selected tags text with select2 AngularJS

How do I get user selected multiple tags with angularjs, taking in consideration the following snippet
Markup
<input type="hidden" ui-select2="select2Options" ng-model="list_of_string" style="width:100%" />
<small>hint: start to type with a</small>
Angular scope
$scope.list_of_string = [];
$scope.select2Options = {
data: function() {
api.categories().then(function(response) {
$scope.data = response;
});
return {'results': $scope.data};
},
'multiple': true,
formatResult: function(data) {
return data.text;
},
formatSelection: function(data) {
return data.text;
}
}
Try changing your <input .../> to <select> </select>
like
<select multiple ui-select2 ng-model="list_of_string" style="width: 100%">
<option ng-repeat="option in data">{{option.text}}</option>
</select>

ng-select gives only string, but I want an integer

This is my angular html file code. In my mongo database frequencyType added as frequencyType = "1", but I want frequencyType = NumberInt(1);
<div class="span4">
<select class="span12 combobox" ng-model="frequencyType" required>
<option value="1">Daily</option>
<option value="2">Weekly</option>
<option value="3">Monthly</option>
<option value="4">Yearly</option>
</select>
</div>
I get in my database frequencyType = "1" but I want frequencyType = NumberInt(1).
There is an easier way:
Just use value*1 as your id, that will convert the string to int without changing the value... assuming the id is an integer.
so the result is>
<div ng-app ng-controller="MyCtrl">
<select ng-model="selectedItem" ng-options="selectedItem*1 as selectedItem for selectedItem in values"></select>
selectedItem: {{selectedItem}}
</div>
This will work in more cases, since indexOf is not available in objects, only in arrays. This solution works for objects with integers as keys (for example if some keys are missing, or if you use db ids)
Most solutions discussed here require using the ngOptions directive instead of using static <option> elements in the HTML code, as was the case in the OP's code.
Angular 1.6.x
Edit If you use Angular 1.6.x, just use the ng-value directive and it will work as expected.
angular.module('nonStringSelect', [])
.run(function($rootScope) {
$rootScope.model = { id: 2 };
})
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.0/angular.min.js"></script>
<div ng-app="nonStringSelect">
<select ng-model="model.id">
<option ng-value="0">Zero</option>
<option ng-value="1">One</option>
<option ng-value="2">Two</option>
</select>
{{ model }}
</div>
Older versions
There is a way to make it work while still using static elements. It's shown in the AngularJS select directive documentation.
The idea is to use a directive to register a parser and a formatter on the model. The parser will do the string-to-int conversion when the item is selected, while the formatter will do the int-to-string conversion when the model changes.
Code below (taken directly from the AngularJS documentation page, credit not mine).
https://code.angularjs.org/1.4.6/docs/api/ng/directive/select#binding-select-to-a-non-string-value-via-ngmodel-parsing-formatting
angular.module('nonStringSelect', [])
.run(function($rootScope) {
$rootScope.model = { id: 2 };
})
.directive('convertToNumber', function() {
return {
require: 'ngModel',
link: function(scope, element, attrs, ngModel) {
ngModel.$parsers.push(function(val) {
return parseInt(val, 10);
});
ngModel.$formatters.push(function(val) {
return '' + val;
});
}
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<div ng-app="nonStringSelect">
<select ng-model="model.id" convert-to-number>
<option value="0">Zero</option>
<option value="1">One</option>
<option value="2">Two</option>
</select>
{{ model }}
</div>
I've just had the same problem and figured out the right way to do this - no 'magic' required. :)
function MyCtrl($scope) {
$scope.values = [
{name : "Daily", id : 1},
{name : "Weekly", id : 2},
{name : "Monthly", id : 3},
{name : "Yearly", id : 4}];
$scope.selectedItem = $scope.values[0].id;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.2/angular.min.js"></script>
<div ng-app ng-controller="MyCtrl">
<select ng-model="selectedItem" ng-options="selectedItem.id as selectedItem.name for selectedItem in values"></select>
selectedItem: {{selectedItem}}
</div>
I think the angular way to do this is to use the directive "convert-to-number" in the select tag.
<select class="span12 combobox" ng-model="frequencyType" required convert-to-number>
<option value="1">Daily</option>
<option value="2">Weekly</option>
<option value="3">Monthly</option>
<option value="4">Yearly</option>
</select>
You can see the documentation and a fiddle here at the end of the page : https://docs.angularjs.org/api/ng/directive/select
I suggest you to try to use indexOf.
JS
function MyCtrl($scope) {
$scope.values = ["Daily","Weekly","Monthly","Yearly"];
$scope.selectedItem = 0;
}
HTML
<div ng-app ng-controller="MyCtrl">
<select ng-model="selectedItem" ng-options="values.indexOf(selectedItem) as selectedItem for selectedItem in values"></select>
selectedItem: {{selectedItem}}
</div>
Demo Fiddle
So send to mongoDB $scope.selectedItem value
This is the easiest way I have found so far:
function Controller($scope) {
$scope.options = {
"First option" : 1,
"Second option" : 2,
"Last option" : 10
}
$scope.myoption = 2;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app ng-controller="Controller">
<select ng-model="myoption" ng-options="label for (label, value) in options"></select>
Option Selected: {{myoption}}
</div>
Or, if you want to use numeric indexes:
function Controller($scope) {
$scope.options = {
1 : "First option",
2 : "Second option",
10 : "Last option"
}
$scope.myoption = 2;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app ng-controller="Controller">
<select ng-model="myoption" ng-options="value*1 as label for (value, label) in options"></select>
Option Selected: {{myoption}}
</div>
For Angular 2, follow the rabit...

Resources