I've got an issue where I populate a page in pieces. There are an arbitrary number of categories with an arbitrary number of items. The code is generally something like the below (warning, transposed).
$scope.getItems = function(key) {
$http.get('get-items?key=' + key)
.then(function(res) {
for (let item of res.data) {
$scope.categories[item.category].items.push(item);
}
});
}
let populateCategories = function() {
for (let key in $scope.categories) {
$scope.getItems(key);
}
}
$scope.getCategories = function(next) {
$http.get('get-categories')
.then(function(res) {
$scope.categories = res.data;
next();
});
$scope.getCategories(populateCategories);
}
The idea is to first get what categories will be on the page, and render them, empty (but w/ a busy icon). After that, hit and endpoint one time per category and populate w/ the results. The busy icon is shown via ng-show & a boolean pointing to the size of the items. 1 or more items = no busy icon, an the items should show.
The loading of the categories more or less works. Populating them though, is not so free flowing. Watching the console output, it takes ages for the browser to render. The busy icon goes away somewhat quickly, but I don't see the items until a bunch of them are ready.
Worth noting, (I think) I saw this problem appear when I moved the html that displays each item from a single file, an template and used ng-include, as I'm using it on two different places. Surely that would not be a cause would it?
EDIT: Adding the html - simplified
item-template.html
<div class="row">
<div class="col-xs-2 col">
<img src="{{item.img}}">
</div>
<div class="col-xs-10 col">
<div>{{item.details}}</div>
</div>
</div>
list.html
<body>
<div class ="container-fluid">
<div class="row">
<div ng-repeat="(key, value) in categories">
<div>{{key}}</div>
<div ng-show="value.busy"">
<img ng-src="{{busy_image}}">
</div>
<div ng-repeat="item in value.items track by $index">
<!-- This in fact seems to be the culprit -->
<div ng-include="item-template.html">
</div>
</div>
</div>
</div>
</body>
So, playing around, if I simply paste the contents of template.html into list.html, the response is much, much better. Looking at this issue, the solution seems to be to use a cache service. I'm happy to use something like that but I'm still curious as to why. The template I'm using isn't small (166 lines) but I can't imagine it being that heavy either on a modern computer.
Several things from the top of head:
amount of items to be shown in the HTML. Large lists with x properties = alot of Angular watchers.
if there are alot of items, maybe check for an alternative to ng-repeat
instead of ng-include item-template.html, create a Component
ng-repeat with track by
use bind once
in this case you can replace ng-show with ng-if
Related
Just starting out learning AngularJS and decided to mock up a basic product gallery using what I've learned so far and I've hit a roadblock. Currently I have a simple product gallery with 3 templates(category listing, products in category listing and product overview). What I would like to do is set up some sort of conditional, where if the products in a selected category have a sub-category, it displays a list of sub-categories using the category-list template. If they don't have a sub-category it just goes straight to the product-list template.
I have created this Plunker showing where I am at so far.
In the above example, if someone clicks on "Cars" I want it to then show a listing of sub-categories using the category-list template. So when you click "Cars" it would take you to a screen with 2 buttons: 4-door and 2-door. Clicking on one of those buttons would then show you the products from those sub-categories using the product-list template. However, if you were to click on "Trucks" from the initial screen, it would just take you directly to the product-list template since the trucks don't have sub-categories.
Here is my category-list template:
<section id="categories" ng-hide="productsVisible">
<div ng-repeat="product in vm.products" class="category">
<div ng-click="vm.selectCategory(product); showProducts();">
<button>{{product.category}}</button>
</div>
</div>
</section>
And here is my product-list template:
<section id="products" ng-show="productsVisible">
<div ng-repeat="product in vm.selectedCategory.items" class="product">
<a href ng-click="vm.selectProduct(product); showResults();">{{product.name}}</a>
</div>
</section>
See my updated plunker
Basically, you need to extend the selectCategory method by grouping the sub-categories and checking whether we're about to enter this sub-category in subsequent click. Like this:
vm.selectCategory = function(category) {
var subCats = [],
map = {};
if (category.items && !category.items[0].subCategory){
vm.selectedCategory = category;
vm.inSubCat = true;
return;
}
vm.inSubCat = !category.items;
if (category.items) category.items.forEach(function(e){
if (!map[e.subCategory]) subCats.push({category: e.subCategory, name: category.category});
map[e.subCategory] = true;
});
vm.products = subCats;
if (vm.inSubCat) vm.selectedCategory = {items: vm.data.filter(function(c){
return c.category == category.name;
})[0].items.filter(function(p){
return p.subCategory == category.category;
}) };
}
I would suggest your data model could use some work, and put all the products in a single array with categories and subcategories as properties. However, you can get what you want with this change to the products-list.html.
<div ng-show="vm.selectedCategory.category=='Cars'">
<input type="radio" ng-model="subcategory" value="2-Door">Coupe
<input type="radio" ng-model="subcategory" value="4-Door">Sedan
</div>
<section id="products" ng-show="productsVisible">
<div ng-repeat="product in vm.selectedCategory.items" class="product">
<a ng-show="product.subCategory===subcategory" href ng-click="vm.selectProduct(product); showResults();">{{product.name}}</a>
</div>
</section>
I advice you to refactor the code in two possible ways:
a) Try to remove lines from controller that control the view (the process of displaying different directives) and use events in directives
b) Control your view by using ng-show and ng-hide directives that will show or hide some part of your code.
Using 1.5.8 other questions haven't provided a solution
I'm displaying images from an array of objects that users can remove by clicking on the image. I want the element in the array to be an empty object after they remove it so there's an empty cell in the view.
<div class="insert-items row m-t-2">
<div class="col-xs text-xs-center slot-item-wrapper" ng-repeat="item in create.availableSlots track by $index">
<div class="slot-item" ng-click="create.removeItemFromSlot(item.id)">
<img ng-src="{{item.img}}" alt="" />
</div>
</div>
</div>
And the JS
function removeItemFromSlot(id){
_.each(create.availableSlots, function(item, i){
if(id === item.id){
create.availableSlots[i] = {};
}
});
}
this does what it intends to... but the item.img is still displaying the old img but there's nothing in the object anymore...
I have a feeling it's because i'm using track by $index and angular can't update the view without it? I have tried using $scope.apply() but it tells me there's already a digest cycle error in the log. Is there a way to update the view using track by here?
I have a problem in which I need to hide results until the user has started typing into the search bar. I am using a DotNetNuke plugin, and therefore did not build this module myself. The scopes I believe I need to use are for '.angrid-search' which has a method that returns the search terms, which it will then use in order to decide if the 'angrid-grid' will be displayed. This is the code I have tried thus far, as well as many different similar variations.
if (angular.element($('.angrid-search')).searchTerms === undefined){
angular.element($('.angrid-grid')).hide();
}
angular.element($('.angrid-search')) comes back with undefined, and returns the search terms once something is typed in. It seems to me that the problem is in the second line, in which I try to hide the element.
I am extremely new to Angular (this is pretty much my first real problem), so explaining in layman's terms would be greatly appreciated, especially since I need to learn just as importantly as I need to solve this problem.
Here is the basic DOM
<div class="angrid">
<div class="angrid-search">
</div>
<div class="angrid-grid-view">
<div class="angrid-grid">
</div>
</div>
</div>
There is a bunch of stuff inbetween, but these are the relavent scopes and I did not want to cpypst the inspector window. My main question is: Is the .hide() method supposed to work in this type of sitation?
You could try something like.
<div class="angrid">
<div class="angrid-search">
</div>
<div class="angrid-grid-view">
<div class="angrid-grid" ng-hide="hidegrid">
</div>
</div>
and in js
if (angular.element($('.angrid-search')).searchTerms === undefined){
angular.element($('.angrid-grid')).hidegrid = true;
}
Yes. It's supposed to work. Here's a demo:
angular.module('demo', [])
.controller('demoCtrl', ['$scope',
function($scope) {
$scope.testFn = function() {
if (angular.element($('.angrid-search')).searchTerms === undefined) {
angular.element($('.angrid-grid')).hide();
}
};
}
]);
<script data-require="jquery#1.11.3" data-semver="1.11.3" src="http://code.jquery.com/jquery-1.11.3.min.js"></script>
<script data-require="angular.js#1.5.5" data-semver="1.5.5" src="https://code.angularjs.org/1.5.5/angular.js"></script>
<div ng-app="demo" ng-controller="demoCtrl">
<div class="angrid">
<div class="angrid-search"></div>
<div class="angrid-grid-view">
<div class="angrid-grid">Grid!</div>
</div>
</div>
<button ng-click="testFn()">the code will work...</button>
</div>
By the way, you mention:
angular.element($('.angrid-search')) comes back with undefined
If it does, then obviously hiding things will not work. You should probably ask another question with enough information to reproduce that problem scenario from scratch, so we can help without resorting to guessing.
I am studying AngularJS by looking at the website http://campus.codeschool.com/courses/shaping-up-with-angular-js/contents and downloaded the videos, then going through the examples on my computer.
Everything went well until the video codeschool_1329.mp4, otherwise called "Shaping_Up_With_Angular_JS_Level_2b". The example works correctly when the logic for selecting the panels is in the HTML code, but when the logic is moved to a controller, it no longer works correctly. Thus I have the relevant HTML code:
<section ng-controller="PanelController as panel">
<ul class="nav nav-pills">
<li ng-class="{active:panel.isSelected(1)}">
<a href ng-click="panel.selectTab(1)">Description</a>
</li>
<!-- Repeated for Specifications and Reviews -->
</ul>
</section>
<div class="panel" ng-show="panel.isSelected(1)">
<h4>Description</h4>
<p>{{product.description}}</p>
</div>
<!-- Repeated for Specifications and Reviews -->
and for the JavaScript code I have:
app.controller('PanelController', function(){
this.tab = 1;
this.selectTab = function(setTab){
this.tab = setTab;
};
this.isSelected = function(checkTab){
return this.tab === checkTab;
};
});
exactly as in the video. The latter is with the Angular module and another Angular controller for the store.
With both Google Chrome and Firefox, when I click on the each of the tabs "Description", "Specifications" and "Reviews", the selected tab is highlighted, as in the video, albeit blue rather than dark purple, but the text that is supposed to be displayed below the selected tab does not show up at all. It looks as if there is some type of a problem with the isSelected function in PanelController with ng-show="panel.isSelected(1)", etc. in the lower part of the HTML code, although it appears to work correctly with ng-class="{active:panel.isSelected(1)}" when the tab is highlighted.
This works correctly when the logic for this is in the HTML code, as I said above, but no matter what I can do, I am unable to get this to work correctly when the logic is in PanelController.
There must be something simple that I am missing, and would be grateful to get this sorted out - many thanks.
<section ng-controller="PanelController as panel">
...
</section>
<div class="panel" ng-show="panel.isSelected(1)">
Only the section element is controlled by the panel controller, but you're trying to use panel.isSelected(1) out of that section. So that can't work.
Put the div inside the section, or wrap everything with another div and move ng-controller="PanelController as panel"to that wrapping div.
I'm using snap.js with AngularJS using the angular-snap.js directive.
https://github.com/jtrussell/angular-snap.js
I'm also using Andy Joslin's angular-mobile-nav.
I'm wondering where I should store the code for the menu:
<snap-drawer>
<p>I'm a drawer! Where do I go in the angular code?</p>
</snap-drawer>
Because this isn't a unique page within the angular-mobile-nav, I'm currently putting the on every page and just using a directive that contains all my menu code/html.
Seems like this could be inefficient as it is loading a new directive on each page, right? Any idea on how to do this better?
Thanks!
So this is what I've done (I also use angular-mobile-nav and angular-snap.js).
This is my HTML Code
<body ng-app="MyApp">
<div snap-drawer>
<ul class="list">
<li ng-repeat="item in sidebar.items" ng-i18next="{{item.name}}" ng-tap="snapper.close();go(item.link)"></li>
</ul>
</div>
<div id="container" snap-content snap-options="snapOpts">
<div mobile-view=""></div>
</div>
</body>
please note that go() is the function to change the page and that I'm using ng-i18next to translate my items. Also ng-tap is a directive which listens for touch events instead of mouse events. With Angular >1.1.5 there's a mobile module, so my ng-tap directive won't be needed anymore.
And by using $rootScope I can put items in the sidebar:
$rootScope.sidebar = {
items: [
{
name: 'item_one',
link: 'page_one'
},
...
]
};
So if you want to change the items in the sidebar, simply override $rootScope.sidebar (not $scope.sidebar) in your controller ;)
If you don't like two animations happen at the same time, you could write a function, which waits for the sidebar to close and then change the page. It could look like this:
$rootScope.waitThenGoTo = function (page, time) {
time = time || 200;
$timeout(function () {
$navigate.go(page);
}, time);
};
If you have still question, please comment. I'll try to update this answer as soon as possible.