I'm new to AngularJS and have problem with removing certain element in a nested custom directive with ng-repeat. Here is my plunker
I have such structure of nested directives:
<button ng-click="mc.addCat()">Add Category</button>
<category ng-repeat="cat in mc.main.categories">
<ul class="first-level">
{{ $index +1 }}. Category
<br>
<input type="text" ng-model="mc.main.categories[$index].name">
<button ng-click="cc.removeCat($index)">Del Category</button>
<button ng-click="cc.addItem()">Add Item</button>
<item ng-repeat="item in cc.cat.items track by item.id">
<li class="second-level">
{{ $parent.$index +1 }}.{{ $index +1 }}
<input type="text" ng-model="mc.main.categories[$parent.$index].items[item.id].name">
<button ng-click="ic.removeItem(item)">del</button>
</li>
</item>
</ul>
</category>
Adding and removing categories works as I need and the parent model updates.
But when I enter something in the item field, and then remove it, this item remains in the parent model.
I think my issue in nested scopes, could you tell me what am I doing wrong?
Thanks!
the delete function in directive should call a method to update the main model....i have edited the plunker and forked the edit....
`
$scope.$on('updateItems',function(event,index,pindex){
console.log(index);
console.log(pindex);
var sti = ""+index+"";
delete vm.main.categories[pindex].items[sti];
});
`
here is the plunker link plunker
From what i understand from your code is, your new item is being added and you are entering some information which is updating your parent model....
<input type="text" ng-model="mc.main.categories[$parent.$index].items[item.id].name">
While deleting the items, you are deleting the copy and the original object.
$scope.$on('delItem', function(event, data){
var items = vm.cat.items;
items.splice(items.indexOf(data), 1);
});
All the below function in category seems to be not working..
vm.cat = {
items: [{
id: 0,
name: ''
}]
};
vm.itemId = 0;
vm.addItem = function() {
vm.itemId = vm.itemId + 1;
var item = {
id: vm.itemId,
name: ''
};
vm.cat.items.push(item);
};
$scope.$on('delItem', function(event, data) {
var items = vm.cat.items;
items.splice(items.indexOf(data), 1);
});
Related
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
User sees a list of options (eg: 1.Apple ; 2.Banana ; 3.Mango). There is a textbox where user types in the desired option and clicks send to proceed further.
Existing Setup:
HTML:
<p ng-repeat="opt in objHistory.OptionItems track by $index">
{{ opt.SortOrder }}. {{ opt.OptionName }}
</p>
<textarea ng-model="userInput"></textarea>
Send
JS:
$scope.GetNextItem = function () {
alert($scope.userInput);
//some code
}
The above code is working good. But now I have changed the options to anchor tags, so user can click on them, instead of typing and the same flow follows.
New HTML:
<p ng-repeat="opt in objHistory.OptionItems track by $index">
<a href="javascript:;"
ng-model = "userInput"
ng-init="userInput=opt.SortOrder"
ng-click="GetNextItem()">{{ opt.SortOrder }}. {{ opt.OptionName }}
</a>
</p>
I get undefined in the alert now. Where am I going wrong? Can the same ng-model variable name be used multiple times (I'm using it for the textbox and also the anchor tags)?
ng-model cannot be used on anchor tags unless you have an input field or a custom directive. According to docs:
The ngModel directive binds an input,select, textarea (or custom form control) to a property on the scope using NgModelController, which is created and exposed by this directive.
Why are you using ngModel on anchor tags?
Sorry not enough rep to comment
You are trying to read the set value before Angular is done assigning.
HTML:
<p ng-repeat="opt in objHistory.OptionItems track by $index">
<a href="javascript:;"
ng-model = "userInput"
ng-init="init(opt)"
ng-click="GetNextItem()">{{ opt.SortOrder }}. {{ opt.OptionName }}
</a>
</p>
<textarea ng-model="userInput"></textarea>
Controller:
angular.module("app",[]).controller("MainController", function($scope) {
$scope.GetNextItem = function () {
alert($scope.userInput);
//some code
};
$scope.init = function(opt) {
$scope.userInput = opt.SortOrder; // set it in the init method
};
$scope.objHistory = {
OptionItems: [{
SortOrder : "1",
OptionName: "Hi"
}, {
SortOrder : "2",
OptionName: "Hi"
}]
}
});
Working DEMO
Update based on comment:
$scope.GetNextItem = function (opt) {
$scope.userInput = opt.SortOrder;
//some code
};
Now in your HTML:
<p ng-repeat="opt in objHistory.OptionItems track by $index">
<a href="javascript:;"
ng-click="GetNextItem(opt)">{{ opt.SortOrder }}. {{ opt.OptionName }}
</a>
</p>
Move ng-model to the parent <p>
I stumpled upon this post about how to reverse the display of an array, which is working fine when I try to do it. angular ng-repeat in reverse
However...then itmes I'm listing are editable and when I'm reversing the array the $index is not being reversed so when I edit the 1 item listed the edit happens in another item that holds the correct index.
So how do I make sure that my $index is reversed as well?
So the code looks like this
<form action="" method="post" autocomplete="off" ng-controller="itemCtrl">
<input type="text" name="createitem" ng-model="item" />
<button ng-click="addItem(item)">Add item</button>
<ul>
<li ng-repeat="item in items | reverse">
<h2>{{item.title}}</h2>
</li>
</ul>
</form>
var itemApp = angular.module('itemApp', []);
itemApp.filter('reverse', function() {
return function(items) {
return items.slice().reverse();
};
});
itemApp.controller('itemCtrl', function ($scope) {
$scope.items = [];
$scope.addItem = function(item){
$scope.items.push({
title: item
});
};
Here's what I'm using.
You just need to subtract the array length by the current $index:
{{myArray.length - $index}}
I'm pretty new in angularjs and I have no idea how do this.
<div class="userData" ng-repeat="user in users">
<div class="float"
<img ng-click="deleteUser($event)" src="myurl">
</div>
<div class="userDiv"
<input type="checkbox" ng-model="user.active" ng-click="handleUserActive()">
<input type="text" ng-model="user.text">
</div>
</div>
I need remove a item when the user click the img.
My model is:
$scope.users = [
{active: true, text: text}
]
Just do:
<img ng-click="deleteUser($index)" src="myurl">
And the method:
$scope.deleteUser = function(index) {
$scope.users.splice(index, 1)
}
What tymeJV said, however, you want to pass the object as the parameter rather than the index like this:
<img ng-click="deleteUser(user)" src="myurl">
$scope.deleteUser = function(user) {
var index = $scope.users.indexOf(user);
$scope.users.splice(index, 1)
}
The reason why is the index can change if you do an orderBy on the ng-repeat.
See an example here: http://jsfiddle.net/6a5zT/
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)">