Edit Mode inside Angular ng-repeat - angularjs

I have a requirement to implement an editable list for a project I am working on. When you click on an item it changes to an edit state that has a bunch of options related to the item you clicked on. Our UX wants the items to be edited in-line but I am not sure of the best way to do this in angular and would like to know which way is better.
Example 1 Template
<div class="person" ng-repeat="person in people" ng-click="editing=!editing">
<div class="details-view" ng-hide="editing" ng-bind="person.name"></div>
<div class="edit-view" ng-show="editing">
<input type="text" /> <button type="button">Save</button> <a>Cancel</a>
</div>
</div>
Example 1 Controller
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
$scope.people = [
{name: 'Joe', age:23},
{name: 'Jim', age:32},
{name: 'Jill', age:13}
]
});
The first way (example here) is to have an ng-repeat and inside of each ng-repeat item create an edit mode that is specific to the ng-repeat item. This works great but I don't want to leave edit mode until I have a successful response from the server and I don't understand how to handle that using this method.
Example 2 Template
<div class="person" ng-repeat="person in people" ng-click="toggleEditing(person)">
<div class="details-view" ng-hide="person.editing" ng-bind="person.name"></div>
<div class="edit-view" ng-show="person.editing">
<input type="text" /> <button type="button">Save</button> <a>Cancel</a>
</div>
</div>
Example 2 Controller
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
$scope.people = [
{name: 'Joe', age:23},
{name: 'Jim', age:32},
{name: 'Jill', age:13}
];
$scope.toggleEditing = function(person) {
person.editing = !person.editing;
};
});
The second way (example here) I thought of is to duck punch the view state onto the object. I don't like this way because I don't want to modify the object handed to me by the ng-repeat.This method does allow me to handle the successful save that the first way above doesn't.
Are there any better options?

If you don't want to clutter the object with the view state, you can save the view state in an different object.
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
$scope.editedItems = {};
$scope.people = [
{name: 'Joe', age:23},
{name: 'Jim', age:32},
{name: 'Jill', age:13}
];
$scope.toggleEditing = function(person) {
$scope.editedItems[person.name] =
!$scope.editedItems[person.name] || true;
};
});
HTML
<div class="person" ng-repeat="person in people" ng-click="toggleEditing(person)">
<div class="details-view" ng-hide="editedItems[person.name]" ng-bind="person.name"></div>
<div class="edit-view" ng-show="editedItems[person.name]">
<input type="text" /> <button type="button">Save</button> <a>Cancel</a>
</div>
</div>

Have you tried ng-grid instead of ng-repeat? They have a good edit in-line model and it seems to have better UX than the ng-hide/ng-show options.

Related

ng-repeat with chips in angular material

I'm trying to use md-chips to collect input from user with auto-complete. For ex: price,available,Y,N. where each component will be rendered in chips.There will be multiple inputs from user per line. When i submit the form i need all the chips per line entered by user. This is where i'm facing the problem.
<div ng-repeat ="rule in rules">
<md-chips ng-model="selectedHeaders">
<md-chip-template>
{{$chip}}
</md-chip-template>
</md-chips>
</div>
the above code works as my model is just selectheader and in js it's
$scope.selectedHeaders = [];
how should i use it to for rule.selectheader??. If i change my model to rule.selectheader , it throws this below error
Cannot set property 'selectedHeaders' of undefined.
Any pointers to solve this issue will be much apppreciated. If issue is not clear, please ask
Have you defined rules values?
In js, at least the value of rules must be something like this
var rules = [{
selectedHeaders: ['Apple', 'Orange']
}, {
selectedHeaders: ['Banana']
}];
you can do like below
Js code
var app = angular.module('myApp', []);
app.controller('ctrl1', function($scope) {
$scope.rules = [{
name: 'rule1',
id: 1
}, {
name: 'rule2',
id: 2
}];
$scope.data = {};
});
HTML
<div ng-app='myApp'>
<div ng-controller='ctrl1'>
<div ng-repeat="rule in rules">
<md-chips ng-model="data.selectedHeaders">
<md-chip-template>
{{$chip}}
<!-- for testing-->{{rule}}
</md-chip-template>
</md-chips>
</div>
</div>
</div>
Here is the link Jsfiddle demo

How can I do an edit box appear in the position of an item clicked with angular

If we have a list like:
<li id="element1">one</li>
<li id="element2">two</li>
<li id="element3">three</li>
and an edit box:
<div id="editbox">
<input id="newName" type="text" value="">
<input type="button" value="apply" ng-click="changeName()">
</div>
I would like to do that when you press in one element from the list, for example, if I press in the element1, how can I do that the input(box) appear just on the rigth of the element element1 using angular?
The list is dynamic and I'm not sure if I can use like a target where I can associate the id of the element where I have pressed.
I was trying but I cannot get it.
Thank you for the help.
You can do it with ng-show (this one toggles):
<div ng-app="myApp" ng-controller="dummy">
<ul>
<li ng-repeat="thing in stuff">
<span id="{{thing.id}}" ng-click="showThing = !showThing">{{thing.name}}</span>
<input type="text" ng-model="thing.name" ng-show="showThing">
</li>
</ul>
</div>
angular.module('myApp', [])
.controller('dummy', ['$scope', function ($scope) {
$scope.stuff = [
{id: 1, name: 'node1'},
{id: 2, name: 'node2'},
{id: 3, name: 'node3'},
{id: 4, name: 'node4'},
{id: 5, name: 'node6'},
{id: 6, name: 'node6'},
{id: 7, name: 'node7'},
{id: 8, name: 'node8'},
{id: 9, name: 'node9'},
{id: 10, name: 'node10'},
];
$scope.selected = false;
}]);
Some different options / examples:
Fiddle
Fiddle
Fiddle
I was bored so I made you another:
FIDDLE
To get the objthis thing working you need to use controllerAs syntax, take a look at the docs here for more information.
For your fiddle, you can look at the changes I made to get it working:
Updated Fiddle
You needed to first make a copy of the object you are editing, so you are not immediately changing the actual object. That way when you cancel, it will go back to where it was before.
$scope.openEditBox = function(nodeSelected) {
objthis.currentNodeSelected = nodeSelected;
objthis.currentEdit = angular.copy(objthis.currentNodeSelected);
$scope.showThing = true;
};
the next thing was to add the objthis.currentEdit as the ng-model in the edit box:
<input type="text" ng-model="d.currentEdit.name" ng-show="showThing">
Then, when you save it you get:
$scope.changeName = function() {
objthis.currentNodeSelected.name = objthis.currentEdit.name;
console.log($scope.name);
console.log(objthis.currentNodeSelected);
};
You can use ng-show:
JSFiddle
HTML:
<li id="element1">one <input id="box" type="text" value="" ng-show="selected === 1"></li>
JS:
angular.module('myApp', [])
.controller('dummy', ['$scope', function ($scope) {
$scope.selected = false;
}]);

One time binding: update model and re-render view

I was wondering if possible, using angular one time binding, to completely re-render the view/template after a model update, also by recompiling the template.
For instance, on a button press, maybe in the way react works: I update the model and explicitly force to update the view.
Basically here is what I am trying to achieve:
// controller
angular.module('app', []).controller('AppCtrl', function($scope) {
$scope.items = [
{id: 1},
{id: 2},
{id: 3}
];
$scope.addAndRefresh = function() {
$scope.items.push({id: 4});
// manually call render logic here???
};
});
<!-- HTML template -->
<div ng-repeat="item in ::items">
{{item.id}}
</div>
<button ng-click="addAndRefresh()">Add</button>
By clicking on the "Add" button I would like to refresh the view to see the newly added item.
I was trying to figure out some way to do this elegantly as well. I wish there was something built into the framework to refresh one-time bindings. All I came up with is using ngIf to remove the element I wanted to refresh and the add it back.
Here's a demo. Click the Add Item button, you'll see that the list does not refresh due to the one-time binding on the repeat. Check the refresh values and click again, and the items will be updated:
var app = angular.module('demo', []);
app.controller('RefreshCtrl', function($scope, $timeout) {
var counter = 4;
$scope.visible = true;
$scope.items = ['Item1', 'Item2', 'Item3'];
$scope.addItem = function() {
if ($scope.refresh) {
$scope.visible = false;
}
$scope.items.push('Item' + counter);
counter++;
$timeout(function() {
$scope.visible = true;
});
};
});
<script src="https://code.angularjs.org/1.3.17/angular.js"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
<div ng-app="demo" ng-controller="RefreshCtrl" class="container">
<button class="btn btn-default" ng-click="addItem()">Add Item</button>
<input type="checkbox" ng-model="refresh" />Refresh Values
<div ng-if="visible">
<h3 ng-repeat="item in ::items">{{item}}</h3>
</div>
<p>Items Array: {{items}}</p>
</div>
Depending on what you are after, I would recommend one of two solutions:
Get angular-bind-notifier.
Does not recompile your template, only refreshes the bound values.
Get kcd-recompile.
Recompiles the template along with the bound values.
I'm the author of the former, and the big difference between it and other solutions is the choice of hooking into the $parse service.
As such, you can use the introduced {{:refreshkey:expression}}/:refreshkey:expression syntax in most (if not all) areas of Angular where an expression is accepted.
In your case, the implementation could look something like this:
js
angular.module('app', []).controller('AppCtrl', function($scope) {
$scope.items = [
{id: 1},
{id: 2},
{id: 3}
];
$scope.addAndRefresh = function() {
$scope.items.push({id: 4});
/**
* '$$rebind' is the internal namespace used by angular-bind-notifier.
* 'refresh' is the refresh key used in your view.
*/
$scope.$broadcast('$$rebind:refresh');
};
});
markup
<!-- HTML template -->
<div ng-repeat="item in :refresh:items">
{{::item.id}}
</div>
<button ng-click="addAndRefresh()">Add</button>
Or, if you wanted something semi-dynamic
js
angular.module('app', []).controller('AppCtrl', function($scope) {
$scope.items = [
{id: 1},
{id: 2},
{id: 3}
];
$scope.add = function() {
$scope.items.push({id: 4});
};
});
markup
<!-- HTML template -->
<div bind-notifier="{ refresh: items.length }">
<div ng-repeat="item in :refresh:items">
{{::item.id}}
</div>
</div>
<button ng-click="add()">Add</button>
Check out the README and this jsBin for some usage examples.

Display list of items and edit them via AngularJS

I try to solve a classic problem using AngularJS: I need to display list of some entities and provide ability to add, edit and view details of this entities.
I implement two controllers: ListController to iterate list of entities and ItemController to display and save entity details. This is html code:
<div ng-app="myApp">
<a class="btn" data-toggle="modal" data-target="#modal">Add new item</a>
<div ng-controller="ListController">
<h4>List</h4>
<ul>
<li ng-repeat="item in list">
{{item.name}}
<a class="btn" data-toggle="modal" data-target="#modal" ng-click="editItem(item)">Edit item</a>
</li>
</ul>
</div>
<div id="modal" role="dialog" class="modal hide fade">
<div ng-controller="ItemController">
<div class="modal-header">
Item Dialog
</div>
<div class="modal-body">
<label for="txtName" />
<input type="text" id="txtName" ng-model="item.name" />
</div>
<div class="modal-footer">
<button class="btn btn-primary" ng-click="saveItem()" data-dismiss="modal">OK</button>
</div>
</div>
</div>
and controllers code:
var db_list = [{ name: "Test1" }, { name: "Test2" }];
var app = angular.module('myApp', []).
controller('ListController', function($scope, $rootScope) {
$scope.list = db_list;
$scope.editItem = function(item) {
$rootScope.item = item;
}
}).
controller('ItemController', function($scope, $rootScope) {
$scope.saveItem = function() {
db_list.push($rootScope.item);
$rootScope.item = null;
}
});
Also you can find the working ptototype at http://jsfiddle.net/yoyoseek/9Qntw/16/.
The general problem in this code that I store entity to display its description using scope of the ListController (via editItem()), but I need this stored entity details in the ItemController. I use $rootScope for sharing entity to edit and it looks like hack for me. Is it a normal practice?
This code has one more drawback: $rootScope.item have to been cleared on modal dialog hide.
It looks like the main problem here is that events triggered by data-toggle happen outside of your control and it's not part of the AngularJS bindings (I am new to it so I may be wrong).
Anyway, it seems like there is no way to cross-reference controllers in Angular, and the only way to get hold of them is via inspecting the DOM. But, once you get into that, you may as well initialize the scope directly (http://jsfiddle.net/B4kAW/4/):
var db_list = [{ name: "Test1" }, { name: "Test2" }];
var app = angular.module('myApp', []);
app.controller('ListController', function($scope) {
$scope.list = db_list;
$scope.editItem = function(item) {
angular.element(document.getElementById("modal")).scope().item = item;
};
});
app.controller('ItemController', function($scope) {
$scope.saveItem = function(item) {
//db_list.push(item);
//$rootScope.item = null;
};
});
Note:
The modal dialog here has no way of knowing whether it's opened for editing, or adding a new item (I commented out push).
Since the dialog is linked with "main" item in the list, it updates it instantly (can be seen while the dialog is open, on the background). You may need to copy it instead of using a reference.
Inspired by this answer. It looks like "the Angular way" around dialogs is to convert them into services.

Multiple select boxes with different selected values in angularJS

I have multiple select boxes and I'm using angular JS. Each select box needs to have a different selected value. So far everything I've seen has elements that share the same selected value. In order to achieve this a scope is used. Since I can potentially have hundreds of drop downs... actually thousands... what is the correct approach here? Create a scope for each one? Try to have one scope that mutates with each select box?Here is an example with what I have jsfiddle.Any help is much appreciated.
Thanks
function MyCntrl($scope) {
$scope.colors = [
{name:'black'},
{name:'red'},
{yellow:'yellow'}
]
$scope.isSel = $scope.colors[1];
}
You need to bind each select box to its own scope. You can do it manually, binding each one to a new object instead of the same isSel, or you can use a ng-repeat like so:
http://jsfiddle.net/zmU8R/9/
html:
<div ng-app="">
<div ng-controller="MyCntrl">
<div ng-repeat="control in controls">
<select ng-model="control.isSel" ng-options="c.name for c in colors"></select><br />
</div>
<hr />
<div ng-repeat="control in controls">
{{control.id}}: {{control.isSel}}
</div>
</div>
</div>
script:
function MyCntrl($scope) {
$scope.controls = [
{id: 1, isSel:null},
{id: 2, isSel:null},
{id: 3, isSel:null}
];
$scope.colors = [
{name:'black'},
{name:'red'},
{name:'yellow'}
];
}
Not sure I've figured out what you exactly want. As far as I understand, you need each selectbox to have different value. So, you need to bind each selectbox to a different variable.
<div ng-app="myApp">
<div ng-controller="myCtrl">
<hr/>
<div ng-repeat="n in [] | range: selectionsCount">
<select ng-model="selectedValues[$index]" ng-options="c.name for c in colors"></select>
</div>
{{ selectedValues }}
</div>
</div>
For a much clearer example, I made selectboxes count variable here.
angular.module('myApp', [])
.controller('myCtrl', function ($scope) {
$scope.colors = [
{name: 'black'},
{name: 'red'},
{name: 'yellow'}
];
$scope.selectedValues = [];
$scope.selectionsCount = 5;
})
.filter('range', function () {
return function(input, total) {
total = parseInt(total);
for (var i=0; i<total; i++)
input.push(i);
return input;
};
});
You can test it here: http://jsfiddle.net/zmU8R/7/
If I misunderstood your question, feel free to correct me.

Resources