Setting dynamic click and model expressions with AngularJS - javascript

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>

Related

Whats the approach to make use of AngularJS watchers in jQuery datatables

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];
}
});

Angular, nested directive repeats, pass if parent is $last

I am trying to trigger an event once all the repeats in this nested repeat are done. So I know you can do a $last, but I have no way of telling inside the inner repeat that it is on the $last in the parent repeat. So I have this
<div>
<div ng-repeat="filter in filters.filters" repeat-directive-one filter="filter" update-filter="updateFilter">
</div>
</div>
Then inside repeat-directive-one looks like
<div ng-repeat="item in filter.values | limitTo: filter.showMore ? filter.values.length : '5' track by $index" repeat-directive-two value="::item" update-filter="updateFilter" ng-if="!$last"></div>
<div ng-repeat="item in filter.values | limitTo: filter.showMore ? filter.values.length : '5' track by $index" repeat-directive-two value="::item" update-filter="updateFilter" ng-if="$last"checkbox-fix></div>
So what that does is if it is on the last item in that repeat it adds on the checkbox-fix directive to fix an issue. But if the upper repeat has 4 items, this fires 4 times so I would like something to the degree of
ng-if="$last && parent.$last"
but that logic doesn't seem to be working as intended. I even tried to pass the parent last (boolean) as an isolate scope attr like (in repeat directive one)
<div ng-repeat="filter in filters.filters" repeat-directive-one filter="filter" update-filter="updateFilter" last-property="$last">
but logging $scope.lastProperty returns nothing. So I would like a way to pass down some sort of flag that the parent repeat is $last. Is there any way of doing this? Thanks!
You can try something like this to broadcast when the outerloop is complete and then the final loop of the inner should start watching the $last
<div ng-repeat="stuff in stuffs" outer-loop>
<div ng-repeat="item in items" inner-loop></div>
</div>
angular.module('test').directive('outerLoop', function($rootScope){
return function(scope, element, attrs) {
if (scope.$last){
$rootScope.$broadcast('outerLoopComplete');
}
};
});
angular.module('test').directive('innerLoop', function(){
return function(scope, element, attrs) {
var outerLoopComplete = false;
scope.$on('outerLoopComplete', function(){
outerLoopComplete;
});
if(outerLoopComplete && $last){
//inner loop complete
}
};
});
I ended up solving it based on #trevor 's advice like so :
<div ng-repeat="item in filter.values | limitTo: filter.showMore ? filter.values.length : '5' track by $index" repeat-directive-two value="::item" update-filter="updateFilter" checkbox-fix></div>
Stuck the checkbox-fix on the repeat (no need for 2 toggled via ng-if), and then inside the checkbox-fix directive I did like so :
return {
restrict: 'A',
link: function(elem, attr, tr) {
if(elem.$parent.$parent.$last && elem.$last){
//fixed!
}
}
};
So elem.$last worked great, I had to check the data/structure a bit to realize I had to do elem.$parent.$parent.$last instead of just 1 level of parent.
https://docs.angularjs.org/api/ng/directive/ngInit
You can use ngInit to set the value of $last to a scope variable, you should be able to access this variable from the child scope directly and indirectly in the case of isolate scope.

ng-click with ng-repeat not working with direct expressions (not functions)

I am having an ng-repeat block and with it i am writing an ng-click with a direct expression of setting a $scope variable to true.. but it doesn't work.. can someone plz help.. here is the plnkr
HTML:
selected: {{selected}}
<ul>
<li ng-repeat="t in t.header" ng-click="selected = true;">{{t.a1}}</li>
</ul>
JS:
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
$scope.selected = false;
$scope.t = {
header: [
{
a1:'a1'
},
{
a1:'a1'
}
]
}
});
for now i am having a workaround to have a function call on its click and set the variable that is required, but curious to know whats wrong with the other approach?
Use $parent because ng-repeat creates it's own scope
<li ng-repeat="t in t.header" ng-click="$parent.selected = true;">{{t.a1}}</li>
Plunker
It's working, but because of the ngRepeat, selected is binded to the new scope. You can instead put selected in an object, so it won't create a new selected property on the child scope:
<li ng-repeat="t in t.header" ng-click="selectedObj.selected = true;">{{t.a1}}
$scope.selectedObj = { selected: false };
Check this plunker.
Try this:
//$scope.selected = false;
$scope.model = {};
$scope.model.selected = false;
plnkr
What happens is that the child scope gets its own property that hides/shadows the parent property of the same name. This is not something AngularJS is doing – this is how JavaScript prototypal inheritance works.
For better understanding:
https://github.com/angular/angular.js/wiki/Understanding-Scopes
Just use $parent scope, like this:
<li ng-repeat="t in t.header" ng-click="$parent.selected = true;">
Every item in ng-repat has his own scope.

angular ng-switch not switching between options but appending them

I have the following in my controller which is the variable that the ng-switch depends on:
$scope.currentClock = {state: true};
This is what I use to change the value of currentClock.state:
<timepanel>
<div id="hour" ng-click="currentClock.state = true">12</div>
<div id="minute" ng-click="currentClock.state = false">00</div>
</timepanel>
I have also tried using a function in the ng-click instead, with the same results, here is the function in (from my controller):
$scope.changeClock = function(elm){
$scope.currentClock.state = elm;
}
And finally here is my ng-switch:
<clockcontainer>
<div ng-switch on="currentClock.state">
<minuteclock ng-switch-when=false></minuteclock>
<hourclock ng-switch-when=true></hourclock>
</div>
<clockcontainer>
The problem is: The hourclock directive appears as default, as it should since currentClock.state is true, but when I click on the minute div, setting currentClock.state to false, instead of hourclock going away and minuteclock taking its place, it gets appended with the minuteclock directive, and when I click again on the hour div, now there is two hourclock directives

ng-repeat finish event

I want to call some jQuery function targeting div with table. That table is populated with ng-repeat.
When I call it on
$(document).ready()
I have no result.
Also
$scope.$on('$viewContentLoaded', myFunc);
doesn't help.
Is there any way to execute function right after ng-repeat population completes? I've read an advice about using custom directive, but I have no clue how to use it with ng-repeat and my div...
Indeed, you should use directives, and there is no event tied to the end of a ng-Repeat loop (as each element is constructed individually, and has it's own event). But a) using directives might be all you need and b) there are a few ng-Repeat specific properties you can use to make your "on ngRepeat finished" event.
Specifically, if all you want is to style/add events to the whole of the table, you can do so using in a directive that encompasses all the ngRepeat elements. On the other hand, if you want to address each element specifically, you can use a directive within the ngRepeat, and it will act on each element, after it is created.
Then, there are the $index, $first, $middle and $last properties you can use to trigger events. So for this HTML:
<div ng-controller="Ctrl" my-main-directive>
<div ng-repeat="thing in things" my-repeat-directive>
thing {{thing}}
</div>
</div>
You can use directives like so:
angular.module('myApp', [])
.directive('myRepeatDirective', function() {
return function(scope, element, attrs) {
angular.element(element).css('color','blue');
if (scope.$last){
window.alert("im the last!");
}
};
})
.directive('myMainDirective', function() {
return function(scope, element, attrs) {
angular.element(element).css('border','5px solid red');
};
});
See it in action in this Plunker.
If you simply want to execute some code at the end of the loop, here's a slightly simpler variation that doesn't require extra event handling:
<div ng-controller="Ctrl">
<div class="thing" ng-repeat="thing in things" my-post-repeat-directive>
thing {{thing}}
</div>
</div>
function Ctrl($scope) {
$scope.things = [
'A', 'B', 'C'
];
}
angular.module('myApp', [])
.directive('myPostRepeatDirective', function() {
return function(scope, element, attrs) {
if (scope.$last){
// iteration is complete, do whatever post-processing
// is necessary
element.parent().css('border', '1px solid black');
}
};
});
See a live demo.
There is no need of creating a directive especially just to have a ng-repeat complete event.
ng-init does the magic for you.
<div ng-repeat="thing in things" ng-init="$last && finished()">
the $last makes sure, that finished only gets fired, when the last element has been rendered to the DOM.
Do not forget to create $scope.finished event.
Happy Coding!!
EDIT: 23 Oct 2016
In case you also want to call the finished function when there is no item in the array then you may use the following workaround
<div style="display:none" ng-init="things.length < 1 && finished()"></div>
//or
<div ng-if="things.length > 0" ng-init="finished()"></div>
Just add the above line on the top of the ng-repeat element. It will check if the array is not having any value and call the function accordingly.
E.g.
<div ng-if="things.length > 0" ng-init="finished()"></div>
<div ng-repeat="thing in things" ng-init="$last && finished()">
Here is a repeat-done directive that calls a specified function when true. I have found that the called function must use $timeout with interval=0 before doing DOM manipulation, such as initializing tooltips on the rendered elements. jsFiddle: http://jsfiddle.net/tQw6w/
In $scope.layoutDone, try commenting out the $timeout line and uncommenting the "NOT CORRECT!" line to see the difference in the tooltips.
<ul>
<li ng-repeat="feed in feedList" repeat-done="layoutDone()" ng-cloak>
{{feed | strip_http}}
</li>
</ul>
JS:
angular.module('Repeat_Demo', [])
.directive('repeatDone', function() {
return function(scope, element, attrs) {
if (scope.$last) { // all are rendered
scope.$eval(attrs.repeatDone);
}
}
})
.filter('strip_http', function() {
return function(str) {
var http = "http://";
return (str.indexOf(http) == 0) ? str.substr(http.length) : str;
}
})
.filter('hostName', function() {
return function(str) {
var urlParser = document.createElement('a');
urlParser.href = str;
return urlParser.hostname;
}
})
.controller('AppCtrl', function($scope, $timeout) {
$scope.feedList = [
'http://feeds.feedburner.com/TEDTalks_video',
'http://feeds.nationalgeographic.com/ng/photography/photo-of-the-day/',
'http://sfbay.craigslist.org/eng/index.rss',
'http://www.slate.com/blogs/trending.fulltext.all.10.rss',
'http://feeds.current.com/homepage/en_US.rss',
'http://feeds.current.com/items/popular.rss',
'http://www.nytimes.com/services/xml/rss/nyt/HomePage.xml'
];
$scope.layoutDone = function() {
//$('a[data-toggle="tooltip"]').tooltip(); // NOT CORRECT!
$timeout(function() { $('a[data-toggle="tooltip"]').tooltip(); }, 0); // wait...
}
})
Here's a simple approach using ng-init that doesn't even require a custom directive. It's worked well for me in certain scenarios e.g. needing to auto-scroll a div of ng-repeated items to a particular item on page load, so the scrolling function needs to wait until the ng-repeat has finished rendering to the DOM before it can fire.
<div ng-controller="MyCtrl">
<div ng-repeat="thing in things">
thing: {{ thing }}
</div>
<div ng-init="fireEvent()"></div>
</div>
myModule.controller('MyCtrl', function($scope, $timeout){
$scope.things = ['A', 'B', 'C'];
$scope.fireEvent = function(){
// This will only run after the ng-repeat has rendered its things to the DOM
$timeout(function(){
$scope.$broadcast('thingsRendered');
}, 0);
};
});
Note that this is only useful for functions you need to call one time after the ng-repeat renders initially. If you need to call a function whenever the ng-repeat contents are updated then you'll have to use one of the other answers on this thread with a custom directive.
Complementing Pavel's answer, something more readable and easily understandable would be:
<ul>
<li ng-repeat="item in items"
ng-init="$last ? doSomething() : angular.noop()">{{item}}</li>
</ul>
Why else do you think angular.noop is there in the first place...?
Advantages:
You don't have to write a directive for this...
Maybe a bit simpler approach with ngInit and Lodash's debounce method without the need of custom directive:
Controller:
$scope.items = [1, 2, 3, 4];
$scope.refresh = _.debounce(function() {
// Debounce has timeout and prevents multiple calls, so this will be called
// once the iteration finishes
console.log('we are done');
}, 0);
Template:
<ul>
<li ng-repeat="item in items" ng-init="refresh()">{{item}}</li>
</ul>
Update
There is even simpler pure AngularJS solution using ternary operator:
Template:
<ul>
<li ng-repeat="item in items" ng-init="$last ? doSomething() : null">{{item}}</li>
</ul>
Be aware that ngInit uses pre-link compilation phase - i.e. the expression is invoked before child directives are processed. This means that still an asynchronous processing might be required.
It may also be necessary when you check the scope.$last variable to wrap your trigger with a setTimeout(someFn, 0). A setTimeout 0 is an accepted technique in javascript and it was imperative for my directive to run correctly.
I did it this way.
Create the directive
function finRepeat() {
return function(scope, element, attrs) {
if (scope.$last){
// Here is where already executes the jquery
$(document).ready(function(){
$('.materialboxed').materialbox();
$('.tooltipped').tooltip({delay: 50});
});
}
}
}
angular
.module("app")
.directive("finRepeat", finRepeat);
After you add it on the label where this ng-repeat
<ul>
<li ng-repeat="(key, value) in data" fin-repeat> {{ value }} </li>
</ul>
And ready with that will be run at the end of the ng-repeat.
<div ng-repeat="i in items">
<label>{{i.Name}}</label>
<div ng-if="$last" ng-init="ngRepeatFinished()"></div>
</div>
My solution was to add a div to call a function if the item was the last in a repeat.
This is an improvement of the ideas expressed in other answers in order to show how to gain access to the ngRepeat properties ($index, $first, $middle, $last, $even, $odd) when using declarative syntax and isolate scope (Google recommended best practice) with an element-directive. Note the primary difference: scope.$parent.$last.
angular.module('myApp', [])
.directive('myRepeatDirective', function() {
return {
restrict: 'E',
scope: {
someAttr: '='
},
link: function(scope, element, attrs) {
angular.element(element).css('color','blue');
if (scope.$parent.$last){
window.alert("im the last!");
}
}
};
});
i would like to add another answer, since the preceding answers takes it that the code needed to run after the ngRepeat is done is an angular code, which in that case all answers above give a great and simple solution, some more generic than others, and in case its important the digest life cycle stage you can take a look at Ben Nadel's blog about it, with the exception of using $parse instead of $eval.
but in my experience, as the OP states, its usually running some JQuery plugins or methods on the finnaly compiled DOM, which in that case i found that the most simple solution is to create a directive with a setTimeout, since the setTimeout function gets pushed to the end of the queue of the browser, its always right after everything is done in angular, usually ngReapet which continues after its parents postLinking function
angular.module('myApp', [])
.directive('pluginNameOrWhatever', function() {
return function(scope, element, attrs) {
setTimeout(function doWork(){
//jquery code and plugins
}, 0);
};
});
for whoever wondering that in that case why not to use $timeout, its that it causes another digest cycle that is completely unnecessary
I had to render formulas using MathJax after ng-repeat ends, none of the above answers solved my problem, so I made like below. It's not a nice solution, but worked for me...
<div ng-repeat="formula in controller.formulas">
<div>{{formula.string}}</div>
{{$last ? controller.render_formulas() : ""}}
</div>
I found an answer here well practiced, but it was still necessary to add a delay
Create the following directive:
angular.module('MyApp').directive('emitLastRepeaterElement', function() {
return function(scope) {
if (scope.$last){
scope.$emit('LastRepeaterElement');
}
}; });
Add it to your repeater as an attribute, like this:
<div ng-repeat="item in items" emit-last-repeater-element></div>
According to Radu,:
$scope.eventoSelecionado.internamento_evolucoes.forEach(ie => {mycode});
For me it works, but I still need to add a setTimeout
$scope.eventoSelecionado.internamento_evolucoes.forEach(ie => {
setTimeout(function() {
mycode
}, 100); });
If you simply wants to change the class name so it will rendered differently, below code would do the trick.
<div>
<div ng-show="loginsuccess" ng-repeat="i in itemList">
<div id="{{i.status}}" class="{{i.status}}">
<div class="listitems">{{i.item}}</div>
<div class="listitems">{{i.qty}}</div>
<div class="listitems">{{i.date}}</div>
<div class="listbutton">
<button ng-click="UpdateStatus(i.$id)" class="btn"><span>Done</span></button>
<button ng-click="changeClass()" class="btn"><span>Remove</span></button>
</div>
<hr>
</div>
This code worked for me when I had a similar requirement to render the shopped item in my shopping list in Strick trough font.

Categories