I have an issue with a partial that is rendered almost instantly for the first time navigating to that page, however if navigate away from that page and sometime later i come back it is rendered noticeably slow. It contains an ng-repeat on a list of about 140 items.
e.g: home (initialize) --> spells (instant) --> home --> spells (slow)
GitHub Repository
Why does this happen and how do i fix it?
Steps i've already taken to try and improve:
Add bind-once in the repeater
Use etags in my API so i don't get back a full list of the items i already have each time i load a partial.
Added track by to ng-repeat.
Code
<div ng-repeat="color in ::colors">
<div>
<span data-target="#{{$index + 'col'}}">
<header>
<label>{{color.name}}</label>
</header>
</span>
</div>
<ul id="{{$index + 'col'}}">
<li ng-repeat="spell in ::spells track by spell.name"
ng-if="spell.color.name === color.name"
ng-dblclick="addToCharacter(spell, 'spell')"
on-long-press="addToCharacter(spell, 'spell')">
<div>
<label ng-hide="isSpell(spell)">Ability</label>
<label ng-show="isSpell(spell)">{{spell.cost}} mana</label>
</div>
<div data-target="#{{$index + 'sp'}}">
<a>{{spell.name}}</a>
</div>
<div id="{{$index + 'sp'}}">
{{spell.description}}
</div>
</li>
</ul>
</div>
IsSpell()
$rootScope.isSpell = function (spell) {
return spell.type != null && spell.cost != null;
};
I have implement a little mechanism to only load the items when they are needed, so the page loads instantaneous and when the user clicks on a category (which is animated) it loads the items it needs. This all works very smoothly and i am quite happy with it.
See the first div (colors) and the ng-if on the li
<div ng-repeat="color in ::colors" ng-init="loadItems = false" ng-click="loadItems = true">
<div>
<span data-target="#{{$index + 'col'}}">
<header>
<label>{{color.name}}</label>
</header>
</span>
</div>
<ul id="{{$index + 'col'}}">
<li ng-repeat="spell in ::spells track by spell.name"
ng-if="(spell.color.name === color.name) && (loadItems = true)"
ng-dblclick="addToCharacter(spell, 'spell')"
on-long-press="addToCharacter(spell, 'spell')">
<div>
<label ng-hide="isSpell(spell)">Ability</label>
<label ng-show="isSpell(spell)">{{spell.cost}} mana</label>
</div>
<div data-target="#{{$index + 'sp'}}">
<a>{{spell.name}}</a>
</div>
<div id="{{$index + 'sp'}}">
{{spell.description}}
</div>
</li>
</ul>
</div>
Related
I am trying to create portlets on my website which are generated when a user inputs a number and clicks a button.
I have the HTML in a script tag (that way it's invisible). I am able to clone the HTML contents of the script tag and append it to the necessary element without issue. My problem is, I cannot seem to modify the text inside the template before appending it.
This is a super simplified version of what I'd like to do. I'm just trying to get parts of it working properly before building it up more.
Here is the script tag with the template:
var p = $("#tpl_dashboard_portlet").html();
var h = document.createElement('div');
$(h).html(p);
$(h).find('div.m-portlet').data('s', s);
$(h).find('[data-key="number"]').val(s);
$(h).find('[data-key="name"]').val("TEST");
console.log(h);
console.log($(h).html());
console.log(s);
$("div.m-content").append($(h).html());
<script id="tpl_dashboard_portlet" type="text/html">
<!--begin::Portlet-->
<div class="m-portlet">
<div class="m-portlet__head">
<div class="m-portlet__head-caption">
<div class="m-portlet__head-title">
<h3 class="m-portlet__head-text">
<span data-key="number"></span> [<span data-key="name"></span>]
</h3>
</div>
</div>
<div class="m-portlet__head-tools">
<ul class="m-portlet_nav">
<li class="m-portlet__nav-item">
<i class="la la-close"></i>
</li>
</ul>
</div>
</div>
<!--begin::Form-->
<div class="m-portlet__body">
Found! <span data-key="number"></span> [<span data-key="name"></span>]
</div>
</div>
<!--end::Portlet-->
</script>
I'm not sure what I'm doing wrong here. I've tried using .each as well with no luck. Both leave the value of the span tags empty.
(I've removed some of the script, but the variable s does have a value on it)
You have two issues here. Firstly, every time you call $(h) you're creating a new jQuery object from the original template HTML. As such any and all previous changes you made are lost. You need to create the jQuery object from the template HTML once, then make all changes to that object.
Secondly, the span elements you select by data-key attribute do not have value properties to change, you instead need to set their text(). Try this:
var s = 'foo';
var p = $("#tpl_dashboard_portlet").html();
var $h = $('<div />');
$h.html(p);
$h.find('div.m-portlet').data('s', s);
$h.find('[data-key="number"]').text(s);
$h.find('[data-key="name"]').text("TEST");
$("div.m-content").append($h.html());
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script id="tpl_dashboard_portlet" type="text/html">
<div class="m-portlet">
<div class="m-portlet__head">
<div class="m-portlet__head-caption">
<div class="m-portlet__head-title">
<h3 class="m-portlet__head-text">
<span data-key="number"></span> [<span data-key="name"></span>]
</h3>
</div>
</div>
<div class="m-portlet__head-tools">
<ul class="m-portlet_nav">
<li class="m-portlet__nav-item">
<i class="la la-close"></i>
</li>
</ul>
</div>
</div>
<div class="m-portlet__body">
Found! <span data-key="number"></span> [<span data-key="name"></span>]
</div>
</div>
</script>
<div class="m-content"></div>
In my case only this is working:
var template = $('template').clone(true, true); // Copies all data and events
var $h = $('<div />');
$h.html(template);
$h.find('.input-name').attr('value', "your value here"); // Note: .val("your value here") is not working
$('.list').prepend($h.html());
I have the above image which has Add Person button, on click of Add person, Person 1 row gets created and so on. On the right end of each row, I have a share icon, on click of an icon, I wanted to open a ul element. The problem is the number of popups that gets displayed depends on the number of rows. If 5 rows are added, then 5 popups are displayed. Ideally, I need only one popup to be displayed, for Person 4 row it should be the popup with 33( basically the popup the is present for that particular row). I tried to add *ngIf = i> 1 condition, but the popup with 00 is only displayed every time which is not correct because the popup position will always be in parallel to Person 1 position.
<div>
<div *ngFor="let person of persons; let i = index">
<div>
<div class="userIcon">
<div>
<img class="person-img" src="assets/images/person.png" alt="My Profile">
</div>
<div>
<input id="form3" class="form-control" type="text">
<label for="form3" class="">{{person.name}}</label>
</div>
</div>
</div>
<div>
<div>
<input id="form5" class="form-control" #enteramount type="text">
<a class='dropdown-trigger sharebtn' href='#' data-target='dropdown{{i}}' (click)="shareIconClicked($event, i, enteramount)"></a>
{{i}}
<ul id='dropdown{{i}}' [ngClass]="{'popupShare': showPopup == true}" class='dropdown-content sharebtn-content'>
<li> {{i}}
Copy Message
</li>
<li>Whatsapp</li>
<li>Email</li>
</ul>
</div>
</div>
</div>
</div>
Below image represents the single popup after adding ngIf = 'isFirst'. I have clicked the Person 4 share icon. If I click the Person 3 share or Person 5 share icon, the popup is always positioned on the first row.
Just add check for first using *ngFor like this -
<div *ngFor="let person of persons; first as isFirst">
....
<ul id='dropdown{{i}}' *ngIf='first' [ngClass]="{'popupShare': showPopup == true}" class='dropdown-content sharebtn-content'>
...</ul>
</div>
For more in details refer official docs -
https://angular.io/api/common/NgForOf
You should try angular Mat-menu feature like this.
<div *ngFor="let person of persons; first as isFirst">
.... code
<button mat-button [matMenuTriggerFor]="menu">Share</button>
<mat-menu #menu="matMenu">
<button mat-menu-item (click)="sharteWithFacebook(person)">Facebook</button>
<button mat-menu-item (click)="shareWithWhatapp(person)">Whatsapp</button>
</mat-menu>
</div>
I am stuck in this problem where I have a div tag which updates and shows a list of images. I want to add animation when the value in the div tag is updated in the transition from one set of images to another.
Here as you can see in the bottom there are a set of images for girl's hair. And when the user goes to other tab, a different set of images comes. I want animation in that transition.
The AngularJS part for the transition is as follows :
<div ng-swipe-left="avDesignController.onSwipeLeftAction()" ng-swipe-right="avDesignController.onSwipeRightAction()">
<!-- CUSTOMIZABLE TAB BAR -->
<div class="tab-bar-container" >
<div class="scrollmenutab">
<!-- CUSTOMIZABLE MENU -->
<div ng-repeat="customizable in customizables"
ng-click="avDesignController.onCustomizableClicked($event)"
style="display: inline;">
<a ng-if="customizable.allowed == 1">
<div ng-class="{selected: $index==currentIndex}">
<div ng-if="customizable.name == 'Hair'">
<img class="scrollmenutab-icon"
id="{{customizable.name}}-{{$index}}"
src="/app/app_resources/icons/{{genderImage}}Hair.png">
</div>
<div ng-if="customizable.name != 'Hair'">
<img class="scrollmenutab-icon"
id="{{customizable.name}}-{{$index}}"
src="/app/app_resources/icons/{{customizable.name}}.png">
</div>
</div>
</a>
</div> <!-- MENU : END -->
</div>
</div>
<!-- CUSTOMIZABLES -->
<div class="avdesign-item-container" id="avdesign-item-container">
<div id="four-columns" class="grid-container" >
<!-- LOAD CUSTOMIZABLES IF NOT LAST ITEM IN TAB -->
<ul ng-if="currentIndex < (customizables.length - 1)"
class="rig columns-4">
<li ng-repeat="customizableItem in currentCustomizable.customizable_item">
<img class="tab-icon"
src="/app/app_resources/resources/av/{{avatarInfo.name}}/as/{{customizableItem.as}}"
id="customizable-{{$index}}"
ng-click="avDesignController.onCustomizedItemClicked($event)"
ng-class="{highlight: customizableItem.id==currentID}">
</li>
</ul>
<!-- LOAD OUTFITS (FROM avatarOutfit) IF LAST ITEM IN TAB -->
<ul ng-if="currentIndex == (customizables.length - 1)"
class="rig columns-outfit">
<div ng-repeat="brand in outfitBrand" ng-style="{'margin-bottom':'1vh'}">
<div class="brand-icon" >
<img src="/app/app_resources/icons/{{brand.bg_image}}">
</div>
<li ng-repeat="outfit in brand.outfitList">
<img class="outfit-icon"
src="/app/app_resources/resources/av/{{avatarInfo.name}}/as/{{outfit.as}}"
id="outfit-{{$index}}"
ng-click="avDesignController.onOutfitItemClicked($event,$parent.$index)"
ng-class="{highlightOutfit: $index==avatar.outfit_index && $parent.$index==indexParent}">
</li>
</div>
</ul>
</div>
</div>
</div>
Where the functions being called in the JS part is updating accordingly the images.
So Question being how to add transition animation for the same element when it is updated because we are never leaving or entering that element tag
Your question is answered here
Html
<div ng-controller="c">
<div id={{my_id}} width={{widthOfOutsideWrapper}} height={{heightOfOutsideWrapper}}>
<img ng-src="{{src}}" imageonload />
</div>
</div>
and in controller
var app = angular.module('app', []);
app.directive('imageonload', function() {
return {
restrict: 'A',
link: function(scope, element, attrs) {
element.bind('load', function() {
alert('image is loaded');
scope.widthOfOutsideWrapper= this.width;
scope.heightOfOutsideWrapper= this.height;
scope.my_id = "testing";
});
}
};
});
app.controller('c', function($scope) {
$scope.src ="https://www.google.com.ua/images/srpr/logo4w.png";
});
and http://jsfiddle.net/2CsfZ/855/ working example is available here
The above answer is very helpful and might be of a great help for many.
But the answer to my question is you simply can't. ngAnimate's ngEnter and ngLeave will not able to recognize a change unless the element is changed and not what is inside the element.
So either some hack needs to be applied like using ngRepeat with different id's for different updation that happens to the element or something else needs to be done.
I am working on an angularJS application which has a page where I display around 30 items using ng-repeat. In front of each item, there is a toggle button (enabled/disabled). With the current code that I have, I can toggle these items. But the problem is if I scroll down and toggle lets say item 25, then automatically it scrolls to the top of the page. If I now scroll down, I can see that the toggle actually took place.
So the requirement now is to make sure that the scroll position is retained after the toggle button is clicked.
Please see below the code that I have.
HTML
<div id="eventTypes" class="panel-body">
<div ng-if="!spinner" ng-repeat="item in items" class="panel-body">
<div class="row">
<div class="col-md-9">{{item.itemName)}}</div>
<div class="col-md-3">
<input id="toggleEnabled"
type="button"
ng-class="{'btn-primary': item.enabled}"
value="{{item.enabled ? 'enabled' : 'disabled'}}"
ng-click="toggleEnabled(item)">
</div>
</div>
</div>
<div ng-if="spinner" class="spinner">
<div class="spinner-container container1">
<div class="circle1"></div>
<div class="circle2"></div>
<div class="circle3"></div>
<div class="circle4"></div>
</div>
<div class="spinner-container container2">
<div class="circle1"></div>
<div class="circle2"></div>
<div class="circle3"></div>
<div class="circle4"></div>
</div>
<div class="spinner-container container3">
<div class="circle1"></div>
<div class="circle2"></div>
<div class="circle3"></div>
<div class="circle4"></div>
</div>
</div>
</div>
JS
(function () {
'use strict';
angular.module('myApp').controller('itemsController', function ($scope, itemsService) {
var serviceError = function (errorMsg) {
console.log(errorMsg);
$scope.turnOffSpinner();
};
$scope.items = [];
$scope.item = {};
$scope.spinner = true;
$scope.toggleEnabled = function (item) {
$scope.turnOnSpinner();
itemsService.toggleEnabled(item)
.then(function () {
$scope.loaditems();
});
};
$scope.loaditems = function () {
itemsService.getitems().then(function (response) {
$scope.items = response.data;
}, serviceError);
$scope.turnOffSpinner();
};
$scope.turnOnSpinner = function () {
$scope.spinner = true;
};
$scope.turnOffSpinner = function () {
$scope.spinner = false;
};
$scope.loaditems();
});
}());
How this works right now is, once I click the toggle button, a spinner is enabled. Meanwhile the controller will call the itemService.toggleEnabled() method which does an ajax call to the server to just change the status of the item(enabled to disabled or vice-versa) in the backend. On successful change of the status and when the ajax call returns, the $scope.loadItems() method is called in the controller. This method will then do another ajax call to fetch the items (now with the updated status of the item that was toggled). The spinner is disabled and the data is then displayed on the UI.
When all of this is done, the page is scrolled to the top. This is annoying when I want to toggle an item which is way down in the list.
I want the page to be present at the same position when I clicked the toggle button of the corresponding item and not scrolling up to the top.
I am new to AngularJS and any help in this regard would be really helpful.
It looks like your spinner scheme is what's causing you problems:
...
<div ng-if="!spinner" ng-repeat="item in items" class="panel-body">
...
<div ng-if="spinner" class="spinner">
...
Whenever you click your button, you are removing every single element in your ng-repeat from the DOM when you $scope.turnOnSpinner(). That's why it appears to jump to the top. It's not really jumping, there just aren't enough DOM elements to fill up the page, making the page so short that the scrollbar disappears (even if it's only for a second). Then when the spinner is done, your ng-repeat fills up the page with DOM elements again, resulting in your scroll position being lost.
So basically what you are trying to fix is a symptom of a less than ideal loading spinner implementation.
ng-if is a "brutal" way of hiding things in Angular. It's mostly meant to hide things for a longer period of time than "softer" directives like ng-show/ng-hide. One solution to your problem is to use ng-disabled on each one of your buttons to prevent the user from interacting with it while the spinner is active, rather than doing a hard removal of each element:
Before:
<div ng-if="!spinner" ng-repeat="item in items" class="panel-body">
<div class="row">
<div class="col-md-9">{{item.itemName)}}</div>
<div class="col-md-3">
<input id="toggleEnabled"
type="button"
ng-class="{'btn-primary': item.enabled}"
value="{{item.enabled ? 'enabled' : 'disabled'}}"
ng-click="toggleEnabled(item)">
</div>
</div>
</div>
After:
<div ng-repeat="item in items" class="panel-body">
<div class="row">
<div class="col-md-9">{{item.itemName)}}</div>
<div class="col-md-3">
<input id="toggleEnabled"
ng-disabled="spinner"
type="button"
ng-class="{'btn-primary': item.enabled}"
value="{{item.enabled ? 'enabled' : 'disabled'}}"
ng-click="toggleEnabled(item)">
</div>
</div>
</div>
Another solution, which I really like and use myself is this Angular module: https://github.com/darthwade/angular-loading
You can attach it to any element in the page and it will put a loading spinner over it and prevent you from interacting with it until your ajax or whatever is done.
If you don't like either of those, try putting your ng-repeat into a container that you can use to prevent interaction with your elements when the spinner is up:
<div class="container" ng-class="{'you-cant-touch-this': spinner}">
<div ng-repeat="item in items" class="panel-body">
<div class="row">
<div class="col-md-9">{{item.itemName)}}</div>
<div class="col-md-3">
<input id="toggleEnabled"
type="button"
ng-class="{'btn-primary': item.enabled}"
value="{{item.enabled ? 'enabled' : 'disabled'}}"
ng-click="toggleEnabled(item)">
</div>
</div>
</div>
</div>
Now you can style it in some way to prevent interaction without having to remove all those items from the DOM:
.you-cant-touch-this {
pointer-events: none;
}
I wanted to view different HTML pages based on what a user clicks. For example, I have three tabs set up as so:
<div class="span7" >
<ul class="nav nav-tabs" style="margin-bottom: 5px;">
<li class="active">First</li>
<li>Second</li>
<li>Third</li>
</ul>
<div class="span12" style="margin-left:0;" ng-grid="gridOptions"></div>
</div>
And I merely want to view a different page based off of what the individual clicks. For example, if he clicks First, he will see First's html page where source.sourceObject in the code below denotes which html page to view. It is written like so:
<div class="span5">
<div class="edus-activity-container">
<div ng-show="sourceViewState.selected" class="edus-admin-activities-grid" />
</div>
<div ng-include="'/partials/' + source.sourceObject + '.html'"/>
</div>
where in my javascript file, source.sourceObject is defined based off of if I click the First, Second or Third tab. However, my implementation is not working. I made sure I had no typos in the spelling of my files in source.sourceObject. Any ideas on how to do so?
In your controller:
var pages = { 'one': 'partials/one.html', 'two':'partials/two.html' }
$scope.currentPage = pages['one'] ; //This is required if you want a default page
$scope.first = function(){ $scope.currentPage = pages['one']; }
$scope.two = function(){ $scope.currentPage = pages['two']; }
In your template/HTML
<div ng-include="currentPage"/>