Our requirement is to display 1000+ rows in a single page but then we should also show/hide button in one of the columns. This button would be toggled by NG watcher on some action.
We didn't have issue while displaying these many records but the performance degraded when watchers were used - for the obvious reason that watchers are directly proportional to the number of rows
We don't want to paginate
We would like to leverage upon AngularJS watchers and ng-models
Plz can someone suggest if there is an alternative to jQuery datatables or any hack to use watchers without compromising on the performance.
Without seeing the code it sounds like you are creating a $scope.$watch for each row in the table. it's not surprising you are seeing performance issues. Instead I would do something like this which responds to a ng-click to change row state Plunker Here:
View.html
<div ng-repeat="item in items">
{{item.name}}
<div ng-show="showHide[$index]===false">
Showing Me for index {{$index}}
</div>
<button ng-click="toggle($index)">
<span ng-show="showHide[$index]===true || showHide[$index]===undefined">Show</span>
<span ng-show="showHide[$index]===false">Hide</span>
</button>
</div>
Controller.js
var app = angular.module('app', []);
app.controller('demoController', function($scope) {
$scope.input = [];
$scope.editing = {};
$scope.items = [{id: 1, name: 'One'}, {id: 2, name: 'Two'}, {id: 3, name: 'Three'}]
$scope.showHide = {};
$scope.toggle = function(index) {
if ($scope.showHide[index] === undefined) {
$scope.showHide[index] = true; // assume show is default
}
$scope.showHide[index] = !$scope.showHide[index];
}
});
Related
So, basically what I'm doing is loading the data in the view using ng-repeat. When a drag and drop occurs, I'm moving the item from one group to another using a directive. The directive calls the move function in the service and updates the list variable.
My problem is that when the item is moved, the ng-repeat is not updated. I tried using $scope.$watch, but with no luck. The service list variable is being updated, but the watch in not being triggered for some reason. I tried using broadcast, which worked, but I've read that it's a bad practice to use broadcast in the controller as it creates bugs.
What is the best way to update the ng-repeat? Not sure why it isn't working. If you require more details, please let me know.
This is my service
angular.module('Data', [])
.factory('DataService', DataService);
function DataService() {
var list = [];
list.push({
id: 6,
title: "First Group",
items: [
{
id: 1,
title: "This is an item"
}
]
});
list.push({
id: 7,
title: "Testng",
items: [
]
});
return {
'get': get,
'move': move,
};
function get() {
return list;
}
function move(index, fromItemIndex, toItemIndex) {
list[fromItemIndex].items.push({
id: 5,
title: "This is an item"
});
}
}
This is my controller
function MyController($scope, DataService) {
var vm = this;
vm.list = DataService.get();
$scope.$watch(function() {
return DataService.get();
}, function(value) {
console.log('Wtatching');
console.log(value);
}, true);
}
My View
<div ng-repeat="item in vm.list track by $index">
<div class="group" droppable group="<% $index %>">
<div class="group-title">
<% group.title %>
</div>
<div class="group-content">
<div ng-repeat="item in item.items track by $index">
<div class="group-item">
<div class="group-item-title"
draggable
group="<% $parent.$index %>"
item="<% $index %>">
<% item.title %>
</div>
</div>
</div>
</div>
</div>
</div>
EDIT
Here is a plnker version: https://plnkr.co/edit/bQuotNU7oOm92PCgmhcj
When you drag item 1 and drop it onto itself, you will see that the service list is update, but the watch is not triggered.
I think the problem is with your ng-repeat expression. Try removing the track by $index.
If you are using jQuery (or other non-angular callback) for moving items (drag-and-drop), then you need to wrap your move code like this:
function move(index, fromItemIndex, toItemIndex) {
$timeout(function() {
list[fromItemIndex].items.push({
id: 5,
title: "This is an item"
});
});
}
Make sure you inject $timeout in your factory. There is a concept of digest cycle in Angular where Angular regularly update the data within the Angular context.
From the docs:
In the $digest phase the scope examines all of the $watch expressions
and compares them with the previous value. This dirty checking is done
asynchronously.
If some Angular data modified in a non-angular context like the jQuery drag & drop, the Angular will be unaware of that change until you tell it. So using $scope.$apply() is used to tell Angular that something has changed. But $apply() sometimes fails if digest cycle is already in progress so using some wrapped service of Angular like $timeout is recommended.
The gist:
The $timeout is used to implicitly trigger a digest cycle
More detail here: https://docs.angularjs.org/error/$rootScope/inprog#triggering-events-programmatically
(Consider using UI.Sortable maintained by Angular UI team if you are using jQuery based library.)
i'm trying to set a random background color from an array in each element in a ng-repeat. This is a simple fiddle i made:
http://jsfiddle.net/akbb6car/2/
As you can see, my problem is i can't set the color in each "row" but it's set in all rows the same. I can't understand how fix that. I also tried using an id="$index" but not works. Here's some code:
<div ng-app="myApp" ng-controller="dummy">
<div ng-repeat="customer in customerData" class="col-sm-3">
<div ng-init="getRandomColor()" class="contact-box" ng-style="{background: bgColor}"> {{customer.name}} got: {{customer.color}}</div>
</div>
</div>
angular.module('myApp', ['ngSanitize'])
.controller('dummy', ['$scope', function ($scope) {
$scope.doc_classes_colors = [
"#339E42",
"#039BE5",
"#EF6C00",
"#A1887F",
"#607D8B",
"#039BE5",
"#009688",
"#536DFE",
"#AB47BC",
"#E53935",
"#3F51B5"
];
$scope.bgColor = "#339E42";
$scope.getRandomColor = function () {
$scope.bgColor = $scope.doc_classes_colors[Math.floor(Math.random() * $scope.doc_classes_colors.length)];
};
$scope.customerData = [{
name: "Mike"
}, {
name: "Tom"
}, {
name: "ASD"
}, {
name: "Lol"
}];
}]);
Your color list is already in scope, why not just select a new color from it based on the $index?
<div class="contact-box" ng-style="{background: doc_classes_colors[$index % doc_classes_colors.length]}">
http://jsfiddle.net/akbb6car/6/
I used $index % doc_classes_colors.length instead of just $index so that the colors repeat if you have more rows than colors.
If you're willing to bring in a dependency on lodash, there's a handy _.shuffle method to randomize the order of the list, which would make the order of the colors random on page load. The colors would repeat in the same order, but each page load would order the color list differently.
http://jsfiddle.net/akbb6car/8/
If you don't want to bring in lodash, I'd probably just look at their source to see how they do it, I'm not sure offhand.
In my angular application I have several panels which have a shared structure, identical html.
Inside the panel the content and behavior changes, basically each one is a separate directive, let's call them panel-content.
This is the close to the solution I think is optimal but I have some architectural doubts.
Since I have directive (which has transclude true set):
<panel></panel>
It's template looks like this:
<div>
Content
<ng-transclude></ng-transclude>
</div>
I have to repeat the panels
<ul>
<li ng-repeat="panel in panels">
<panel>
<panel-content></panel-content>
</panel>
</li>
</ul
This is all fine, but what would be a reasonably good way of "choosing" on each iteration which <panel-content> should I show?
Lets say I have a panel.id I can use.
I notice I can achieve it in several ways, I could do an ng-switch inside the <panel-content> view using the panel id, I could set up that the templateUrl of has a dynamic part and links to different URLs depending on the panel.id.
But for some reason, I am convinced I am missing something nicer more straightforward?
Please not that this architecture is not set in stone, if there is another structure that would better fit my needs please let me know.
So, the question again is, how do I choose? Or rather, who is responsible for choosing which <panel-content> should be displayed.
If I understand you right, I would use ng-include in directive that changes each time the template by id:
Something like:
<ul>
<li ng-repeat="panel in panels">
<panel type-id="panel.id">
</panel>
</li>
</ul>
and Directive:
app.directive('panel',
function() {
return {
restrict: 'E',
replace: true,
template: '<ng-include src="getTemplateUrl()"/>',
link: function(scope,elem,attrs) {
scope.getTemplateUrl = function() {
var url;
if(attrs.typeId !== undefined){
var url = $rootScope.list_templates[attrs.typeId].value; // TODO: add validation
}
return url;
};
}
});
app
$rootScope.list_templates = [
{ id: 0, value: 'partials/upcoming_0.html'},
{ id: 1, value: 'partials/upcoming_1.html'},
{ id: 2, value: 'partials/upcoming_2.html'}
];
I have this inside a ng-repeat block:
{{item.type}}
<div ng-show="collapsed{{$index}}">
{{item.type}}
</div>
I need to have each iteration keep track of its own collapsed state. I get all sorts of errors trying to do the above. The ng-model doesn't like {{ }}'s, the ng-click doesn't seem to either. I've also tried [$index] without much luck.
Any ideas on the proper way to do this?
In your controller, you can try to introduce an array of booleans, where it keeps the collapse state of each item in your list.
// if every one of them starts off in a collapsed state, all booleans are true
$scope.collapses = [true, true, true, ...];
Then it just becomes
<a ng-click="toggle($index)">{{item.type}}</a>
<div ng-hide="collapses[$index]">{{item.type}}</div>
As for the toggle() function:
$scope.toggle = function(index) { collapses[index] = !collapses[index]; };
I think it would be a lot cleaner to keep track of the collapsed state if you put this into a controller:
Javascript:
app.controller('collapseCtrl', ['$scope', function($scope){
$scope.collapsed = true;
$scope.toggle = function(){
$scope.collapsed = !$scope.collapsed;
};
}]);
HTML:
<div ng-controller="collapseCtrl">
{{item.type}}
<div ng-show="collapsed">
{{item.type}}
</div>
</div>
I have very little javascript experience. I need to add a menu on click of an item. We have been asked to build it from scratch without using any library like bootstrap compoments or JQuery.
We are using Angularjs. In angular I want to know the correct method to create new elements. Something like what we did not document.createElement.
I am adding some of the code for you guys to have a better idea what I want to do.
Menu Directive
.directive('menu', ["$location","menuData", function factory(location, menuData) {
return {
templateUrl: "partials/menu.html",
controller: function ($scope, $location, $document) {
$scope.init = function (menu) {
console.log("init() called");
console.log("$document: " + $document);
if (menu.selected) {
$scope.tabSelected(menu);
}
}
$scope.creteMenu = function(menuContent){
//This is to be called when the action is an array.
}
$scope.tabSelected = function(menu){
$location.url(menu.action);
$scope.selected = menu;
}
$scope.click = function (menu) {
if (typeof (menu.action) == 'string') {
$scope.tabSelected(menu);
}
}
},
link: function (scope, element, attrs) {
scope.menuData = menuData;
}
};
}])
Menu data in service.
.value('menuData', [{ label: 'Process-IDC', action: [] }, { label: 'Dash Board', action: '/dashboard', selected: true }, { label: 'All Jobs', action: '/alljobs', selected: false }, { label: 'My Jobs', action: '/myjobs', selected: false }, { label: 'Admin', action: '/admin', selected: false }, { label: 'Reports', action: '/reports', selected: false }]);
If you notice the action of Process-IDC menu is an array it will contain more menu with actions in it and it should be opened in a sub menu.
Menu.html (partial)
<ul class="menu">
<li ng-class="{activeMenu: menu==selected}" ng-init="init(menu)" data-ng-click="click(menu)" data-ng-repeat="menu in menuData">{{menu.label}}</li>
</ul>
A few things come to mind. First of all, are you sure you need to actually create the element on click? If you are doing to to show a fixed element on click then the better approach would be to generate the element as normal, but not show it until you click. Something like:
<div ng-click="show_it=true">Show item</div>
<div ng-show="show_it">Hidden until the click. Can contain {{dynamic}} content as normal.</div>
If you need it to be dynamic because you might add several elements, and you don't know how many, you should look at using a repeat and pushing elements into a list. Something like this:
<div ng-click="array_of_items.push({'country': 'Sparta'})">Add item</div>
<div ng-repeat="item in array_of_items"> This is {{item.country}}</div>
Each click of the "Add item" text here will create another div with the text "This is Sparta". You can push as complex an item as you want, and you could push an item directly from the scope so you don't have to define it in the template.
<div ng-click="functionInControllerThatPushesToArray()">Add item</div>
<div ng-repeat="item in array_of_items"> This is {{item.country}}</div>
If neither of those options would work because it is a truly dynamic object, then I would start looking at using a directive for it like others have suggested (also look at $compile). But from what you said in the question I think a directive would be to complicate things needlessly.
I recommend you read the ngDirective and the angular.element docs.
Hint: angular.element has an append() method.
This is both really simple, but some what complex if you don't know where to start - I really recommend looking at the Tutorial, and following it end to end: http://docs.angularjs.org/tutorial/ - As that will introduce you to all the concepts around Angular which will help you understand the technical terms used to describe the solution.
If you're creating whole new menu items, if in your controller your menu is something like:
// An Array of Menu Items
$scope.menuItems = [{name: 'Item One',link: '/one'},{name: 'Item Two',link:'/two'}];
// Add a new link to the Array
$scope.addMenuItem = function(theName,theLink){
$scope.menuItems.push({name: theName,link:theLink});
}
And in the template, use the array inside ng-repeat to create the menu:
<ul>
<li ng-repeat="menuItem in menuItems">{{menuItem.name}}</li>
</ul>
If you just want to toggle the display of an item that might be hidden, you can use ng-if or ng-show
Assuming that you are doing it in a directive and you have angular dom element, you can do
element.append("<div>Your child element html </div>");
We can use $scope in App Controller to create Div Elements and then we can append other Div elements into it similarly.
Here's an Example:
$scope.div = document.createElement("div");
$scope.div.id = "book1";
$scope.div.class = "book_product";
//<div id="book1_name" class="name"> </div>
$scope.name = document.createElement("div");
$scope.name.id = "book1_name";
$scope.name.class= "name";
// $scope.name.data="twilight";
$scope.name.data = $scope.book.name;
$scope.div.append($scope.name);
console.log($scope.name);
//<div id="book1_category" class="name"> </div>
$scope.category = document.createElement("div");
$scope.category.id = "book1_category";
$scope.category.class= "category";
// $scope.category.data="Movies";
$scope.category.data=$scope.book.category;
$scope.div.append($scope.category);
console.log("book1 category = " + $scope.category.data);
//<div id="book1_price" class="price"> </div>
$scope.price = document.createElement("div");
$scope.price.id = "book1_price";
$scope.price.class= "price";
// $scope.price.data=38;
$scope.price.data=$scope.book.price;
$scope.div.append($scope.price);
console.log("book1 price = " + $scope.price.data);
//<div id="book1_author" class="author"> </div>
$scope.author = document.createElement("div");
$scope.author.id = "book1_author";
$scope.author.class= "author";
// $scope.author.data="mr.book1 author";
$scope.author.data=$scope.book.author;
$scope.div.append($scope.author);
console.log("book1 author = " + $scope.author.data);
//adding the most outer Div to document body.
angular.element(document.getElementsByTagName('body')).append($scope.div);
For more illustration, Here each book has some attributes (name, category, price and author) and book1 is the most outer Div Element and has it's attributes as inner Div elements.
Created HTML element will be something like that