Scope issue with ui-bootstrap tabs - angularjs

I'm having a problem understanding scope. ui-bootstrap creates a new scope when you use tabset, I get that. I thought parent methods were available to child scopes? Here is a jsbin showing my problem.
JSBin
I'm sure there is something simple I'm missing, but I can't see it.
Code inline in case JSBin acts up:
angular.module('app', ['ui.bootstrap'])
.controller('MainController', function($scope) {
$scope.filters = [];
$scope.search = '';
$scope.providerVersions = [
{ name:'SomeOS', type:'pv' },
{ name:'OtherOS', type:'pv' }
];
$scope.scripts = [
{ name:'Something', pv:'SomeOS', type:'script' },
{ name:'Somebody', pv:'SomeOS', type:'script' },
{ name:'Other thing', pv:'OtherOS', type:'script' },
{ name:'Other body', pv:'OtherOS', type:'script' }
];
$scope.addFilter = function(f) {
$scope.filters.push({ name:f.name, type:f.type });
};
$scope.remFilter = function(i) {
$scope.filters.splice(i,1);
};
$scope.filterByName = function(n) {
var name = n.name.toLowerCase();
var search = $scope.search.toLowerCase();
return name.indexOf(search) > -1;
};
$scope.filterByFilters = function(f) {
if ($scope.filters.length===0) { return true; }
var byName = _.where($scope.filters, { type:'script' });
if (byName.length > 0) {
return _.contains(_.pluck(byName, 'name'), f.name);
}
return _.contains(_.pluck($scope.filters, 'name'), f.pv);
};
});
HTML
<body ng-app="app">
<div ng-controller="MainController">
<h3>This works</h3>
<p>Filters on both name and role as expected.</p>
<div ng-repeat="s in scripts|filter:filterByFilters">
{{ s.name }}
</div>
<form name="searchForm">
<input type="text" class="form-control" placeholder="Filter by name or role" ng-model="search">
</form>
<span ng-repeat="f in filters"><a ng-click="remFilter($index)">{{ f.name }}</a><span ng-if="!$last">, </span></span>
<ul ng-show="search.length>0" class="dropdown-menu" style="display:block; position:static;">
<li class="dropdown-header">Name</li>
<li ng-repeat="s in scripts|filter:filterByName"><a ng-click="addFilter(s)">{{ s.name }}</a></li>
<li class="divider"></li>
<li class="dropdown-header">Role</li>
<li ng-repeat="p in providerVersions|filter:search"><a ng-click="addFilter(p)">{{ p.name }}</a></li>
</ul>
<h3>This does not work</h3>
<p>Only filters on role. It does not call $scope.filterByName.</p>
<tabset>
<tab heading="Scripts">
<div ng-repeat="s in scripts|filter:filterByFilters">
{{ s.name }}
</div>
<form name="searchForm">
<input type="text" class="form-control" placeholder="Filter by name or role" ng-model="search">
</form>
<span ng-repeat="f in filters"><a ng-click="remFilter($index)">{{ f.name }}</a><span ng-if="!$last">, </span></span>
<ul ng-show="search.length>0" class="dropdown-menu" style="display:block; position:static;">
<li class="dropdown-header">Name</li>
<li ng-repeat="s in scripts|filter:filterByName"><a ng-click="addFilter(s)">{{ s.name }}</a></li>
<li class="divider"></li>
<li class="dropdown-header">Role</li>
<li ng-repeat="p in providerVersions|filter:search"><a ng-click="addFilter(p)">{{ p.name }}</a></li>
</ul>
</tab>
</tabset>
</div>
</body>

Scopes are interesting and confusing. All scopes, except $rootScape, have ancestors. I don't know for certain but I assume that the ui-bootstrap tabset creates its own isolate scope. That isolate scope does have a parent, but you've "isolated" it so it can't see any attributes of an ancestor scopes unless you've specifically included them within the directive. Should an isolate scope have a child scope that is not an isolate scope, it can see and manipulate its parents attributes but nothing farther back the ancestor chain. Events can pass freely up and down the chain and you should be very careful using them since, when dealing with isolate scopes, may cause side effects you aren't expecting.
If the above was just a bunch of blather, go to https://docs.angularjs.org/guide/directive and reread the info there - maybe that will make it more clear.
Here's probably one of the best dissertation on scope you'll fnd in a quick, concise and clear explanation - What are the nuances of scope prototypal / prototypical inheritance in AngularJS?

I was able to work around this. Honestly I don't know why this works, but it does. Maybe somebody else can fill in the gaps for me. What I did was instead of trying to filter my list, I changed the filter function to return an array, and I use that result for ng-repeat to iterate.
Old filter function
$scope.filterByName = function(n) {
var name = n.name.toLowerCase();
var search = $scope.search.toLowerCase();
return name.indexOf(search) > -1;
};
New filter function
$scope.filterByName = function(list, srch) {
var ret = [];
_.each(list, function(l) {
if (l.name.toLowerCase().indexOf(srch.toLowerCase()) > -1) {
ret.push(l);
}
});
return ret;
};
Old ng-repeat
<li ng-repeat="s in scripts|filter:filterByName">
New ng-repeat
<li ng-repeat="s in filterByName(scripts, search)">

Related

how to bind $index from ng-repeat to controller

In Angular I wanted to bind $index to controller. How can I send $index value in to my controller. Here is my html code.
<body>
<div class="container">
<div class="row row-content" ng-controller="demoController as demoCtrl">
<ul>
<li ng-repeat="val in demoCtrl.list" >Hello {{$index}}</li>
</ul>
</div>
</div>
Here is my controller code
var app = angular.module('confusionApp',[])
app.controller('demoController', function(){
var list = [1,2,3,4,5];
this.list = list;
var array = ['abc','def','ghi','jkl','mno']
this.array = array
console.log(this.array[index]);
});
I need to use ng-modal in HTML and bind that value to some variable in my controller.
Based on the selection of index, it should check in array and respective array should have to print.
Can any of you please help
To get your current iteration position in your controller you have define a function.
So your html code like this.
<body>
<div class="container">
<div class="row row-content" ng-controller="demoController as demoCtrl">
<ul>
<li ng-repeat="val in demoCtrl.list" ng-click="dispArray($index)">Hello {{$index}}</li>
</ul>
</div>
</div>
And your controller code
var app = angular.module('confusionApp',[])
app.controller('demoController', function($scope){
$scope.dispArray = function(index){
// console.log(index);
// your code
}
});
Depending on what you're trying to accomplish you might be better served creating a custom iterator.
function makeIterator(array){
var nextIndex = 0;
return {
next: function(){
return nextIndex < array.length ?
{value: array[nextIndex++], done: false} :
{done: true};
}
}
}
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Iterators_and_Generators
Angular is going to iterate over everything in the list when you use ngRepeat. If you're trying to track which list item a user clicks then you'll just add that in there using $index.
<li ng-repeat="val in demoCtrl.list" >
<span ng-click="demoCtrl.userClicked($index)"> Hello {{$index}}</span>
</li>
If you're just trying to print the data in each item, ng-repeat is already iterating everything for you.
<li ng-repeat="val in demoCtrl.list" >
<span ng-click="demoCtrl.userClicked($index)"> Hello {{val}}</span>
</li>
It all depends on what you're trying to do.
i dont konw what u want to do. if u want to use event. you can pass $index to your controller function like:
<li ng-repeat="val in demoCtrl.list" ng-click="getIndex($index)">Hello {{$index}}
$scope.getIndex = function($index) {
console.log($index)
}
hope to help u.

Watch attribute-values of multiple elements without directive?

Given the following:
<div ng-app="interactive">
<main ng-controller="RecipesController">
<ul>
<li ng-repeat="option in options">
<ul decisionIndex="{{ value }}">
<li>Edit {{ option.name }}</li>
<li>Choose {{ option.name }}</li>
<li>Delete {{ option.name }}</li>
</ul>
</li>
</ul>
</main>
</div>
How can I use $watch to detect whether "value" (is either 0, 1 or 2) changes?
Do I need to use $watchGroup ? As far as I know that only works if you have multiple attributes which are not the same.
I found this working with directives and isolated scopes, but it does not quite match my case and I would prefer not using directives. Is it possible using isolated scopes without directives? Or how can I watch for changes of an attribute-value occuring multiple times?
EDIT:
This is how I tried using $watch
app.controller('RecipesController', ['$scope', function ($scope) {
$scope.$watch('value', function () {
console.log('value: ' + $scope.value);
});
}]);
<div ng-app="myApp" ng-controller="myCtrl">
<input type="text" ng-model="value">
<p>Current value is: {{value}}</p>
<ul>
<li ng-repeat="option in options">
<ul decisionIndex="{{value}}">
<li ng-click="setValue(0)">Edit {{option.name}}</li>
<li ng-click="setValue(1)">Choose {{option.name}}</li>
<li ng-click="setValue(2)">Delete {{option.name}}</li>
</ul>
</li>
</ul>
</div>
Controller:
angular.module('myApp', [])
.controller('myCtrl', ['$scope', function($scope) {
$scope.value = 0; //Set initial value, or just declare it;
$scope.options = [
{name: "Bob"},
];
$scope.setValue = function(a){
$scope.value = a;
}
$scope.$watch('value', function(newVal, oldVal){
console.log(newVal);
})
}]);

Angular-Js unable to push data into array of controller

HTML:-
<div ng-controller="countryController">
{{name}}
<ul>
<li ng-repeat="country in countries">
{{ country.name }}
<ul ng-show="country.states.length > 0">
<li ng-repeat="state in country.states">
{{ state.name }}
</li>
</ul>
<input type="text" ng-model="newState">
<a href ng-click="addStateState(country)">Add </a>
{{newState}}
</li>
</ul>
</div>
When I key in newState model it appears in client side.
Now i try to get that value into controller and try to push in array of states, its unable to add into states array.
JS Controller:-
myApp.factory('countryService', function($http){
var baseUrl = 'services/'
return {
getCountries : function(){
return $http.get(baseUrl + 'getcountries.php');
}
};
});
myApp.controller('countryController', function($scope, countryService){
countryService.getCountries().success(function(data){
$scope.countries = data;
});
$scope.addStateState = function(country){
country.states.push({'name' : $scope.newState});
$scope.newState = "";
};
});
Your main issue is that your $scope.newState is not available inside $scope.newStateState function. It is bad practice to manipulate an object like $scope within a $scope function. In fact, you are creating multiple multiple objects that are not the same as the $scope that you inject; otherwise, the input boxes for two different countries should match.
Working Plunkr below.
http://plnkr.co/edit/HG3zWG?p=preview
JS:
angular.module('myApp',[])
.controller('countryController', function($scope){
$scope.countries = [
{ name: 'USA', states:[
{name: 'Virginia'},
{name: 'Utah'}
]
},
{ name: 'Brazil', states:[
{name: 'Pernabuco'}
]
}
];
$scope.addStateState = function(country, data){
country.states.push({'name' : data.newState});
data.newState = "";
};
});
HTML:
<div ng-controller="countryController">
{{name}}
<ul>
<li ng-repeat="country in countries">
{{ country.name }}
<ul ng-show="country.states.length > 0">
<li ng-repeat="state in country.states">
{{ state.name }}
</li>
</ul>
<input type="text" ng-model="data.newState" />
Add
{{data.newState}}
</li>
</ul>
</div>
When you are dealing with things like ng-repeat, ng-if, or ng-include, they each create a new scope. This means that when you try to bind to a primitive on the root level of that scope, it will get dereferences when you update the value. So, you need to put your primitives inside of an object. John Papa's style guide recommends making a "vm" property on your scope. vm stands for view model.
Here is a jsfiddle of how to use it.
http://jsfiddle.net/fbLycnpg/
vm.newState

Filter data on click function in AngularJS

I have data on some bikes in my HTML page. I have to filter that data via an on click function. I have used a filter in the text box area, but I want the same functionality via an on click function.
So how can I bind the filter with the click function?
http://jsfiddle.net/3G7Kd/114/
<div ng-app='app' class="filters_ct">
<ul class="nav" ng-controller="selectFilter">
<li ng-repeat="filter in filters" ng-click="select($index)" ng-class="{sel: $index == selected}">
<span class="filters_ct_status"></span>
{{filter.name}}
<ul class="subul" ng-if=filter.lists.length>
<li ng-repeat="list in filter.lists" ng-click=" $event.stopPropagation()">
<input type="checkbox"> {{list}}
</li>
</ul>
</li>
</ul>
<input type="text" ng-model="search">
<div ng-controller="listctrl">
<div class="list" ng-repeat="list in lists | filter:{brand:search}">
{{list.brand}}
{{list.year}}
</div>
</div>
</div>
Angular
var app = angular.module('app', []);
app.controller('selectFilter', function($scope) {
$scope.filters = [
{
"name": "brand",
'lists': ['yamaha','ducati','KTM','honda']
},
{
'name': "year",
'lists': [2012,2014,2015]
}
];
$scope.selected = 0;
$scope.select= function(index) {
if ($scope.selected === index)
$scope.selected = null
else
$scope.selected = index;
};
});
app.controller('listctrl', function($scope) {
$scope.lists = [
{
"brand": "ducati",
'year': 2012
},
{
'brand': "honda",
'year': 2014
},
{
'brand': "yamaha",
'year': 2015
},
{
'brand': "KTM",
'year': 2012
}
];
});
You already knew how to use the filter when given an object within the partial. I moved one of your controllers so that you have an outer and an inner controller.
<div ng-app='app'ng-controller="MainCtrl as mainCtrl">
<div ng-controller="listCtrl">
<!-- your filter object is now accessible here -->
</div>
</div>
I added a scope variable to the outer controller $scope.activeFilters (filling this you should be able to do on your own, see the plunker for one possible solution.
This object is now changed when clicking on the checkboxes. As $scope.activeFilters is now accessible from the inner controller we can pass it to the filter as before:
<div class="list" ng-repeat="list in lists | filter:activeFilters">
{{list.brand}}
{{list.year}}
</div>
Note that there are probably nicer solutions (using the checkbox with a model among other things).
Working plunker:
http://jsfiddle.net/ywfrbgoq/

Detect user selection for li

I have a list of items ("locals" array) which I show in a list
<ul class="list-group">
<li ng-repeat="loc in locals" class="list-group-item"><a href="" data-id={{loc.ID}}>{{loc.location}}</a>
</li>
</ul>
I want the user to be able to select an item, and then to use this item in code.
What is the preferred way to do it.
Also I am creating the application for mobile, so I should be able to know that the user chose this item in mobile( and not just use mouseclick for example).
You can make use of angular js ng-click event (on the li item where ng-repeat is and do something like this: fiddle
code snippet of controller:
function MyCtrl($scope) {
$scope.templateList = [{id:1, name: 'Template1'}, {id:2, name: 'Another Template'}]
$scope.template = {};
$scope.setValue = function(list) {
$scope.template.template_id = list.id;
$scope.template.template_name = list.name;
}
}
Of HTML:
<div ng-app>
<form ng-controller="MyCtrl">
<input type="hidden" name="template_id" ng-model="template.template_id" />
<input type="text" name="template_name" ng-model="template.template_name" />
<ul>
<li ng-repeat="list in templateList" ng-click="setValue(list)">{{list.name}}</li>
</ul>
</form>
</div>
Try this:
In html,
<ul class="list-group">
<li ng-repeat="loc in locals" class="list-group-item">
<a href="" data-id={{loc.ID}} ng-click="selectLoc(loc.location)">
{{loc.location}}
</a>
</li>
</ul>
In JS,
$scope.selectLoc = function(location){
console.log(location);
//Here you will get the selected location.
var SomeVar = location;
}
Hope this helps....

Resources