If all array items are hidden show another div in AngularJS - javascript

I'm using ng-repeat on my page. ng-class working very well.
<div class="card news" ng-repeat="item in news track by $index" id="{{news.nid}}" ng-init="parentIndex = $index" ng-class="{hidden: '{{getCheck($index)}}' == 'true'}">
...
</div>
Now I need, if all items are hidden, show this div:
<h3 class="news-empty">No news</h3>
Whats the rules? How can I do it? Thanks.

You need another method that checks if all elements are hidden:
$scope.everythingIsHidden = function() {
return $scope.news.every((new, index) => $scope.getCheck(index));
}
$scope.getCheck = function(index) { // Your getChek function that I suppose it checks if an element is hidden based on index
//...
}
<h3 class="news-empty" ng-if="everythingIsHidden()">No news</h3>

TheCog's answer will work. If you want to do this in a more 'Angular' way you're going to need to refactor what you have.
You shouldn't be trying to hide them with a CSS class. ngRepeats have a built in filter syntax. So, you should be filtering them out.
<div class="card news"
ng-repeat="item in news | filterMethod as results track by $index"
id="{{news.nid}}"
ng-init="parentIndex = $index"
>
<h3 class="news-empty" ng-if="results.length === 0" >No news</h3>
The as results statement in the repeat will store the filtered array in results. filterMethodneeds to be an angular filter and it will probably work similarly to your getCheck($index) method.

You want to add an ngShow to the h3 tag, and aim it at a function you write in your controller that checks if the array is empty, probably by iterating over the same array that's hidden and running getCheck($index) on each.

Related

Angular show hide with combine condition

I have an array of object need to show hide based on filter like below:
HTML CODE:
Filter:
<div (click)="filter(1)"> F1 </div>
<div (click)="filter(2)"> F2 </div>
<div (click)="filter(3)"> F3 </div>
<div (click)="filter(4)"> F4 </div>
<div *ngFor="let data of datas">
<span *ngIf="data.show">
{{data.name}}
</span>
</div>
ts Code:
this.datas = `[{'name':'product one','filter':'1'},{'name':'product two','filter':'2'},{'name':'product three','filter':'3'},{'name':'product three','filter':'3'},{'name':'product','filter':''},{'name':'product','filter':''},{'name':'product one','filter':'1'},{'name':'product'}]`
filter(query){
this.datas.forEach(function (element, index) {
if (element.filter == query ) {
element.show = true;
} else {
element.show = false;
}
I have tried the above approach it's not working .
Expected like:
By default display all product.
Filter is toggle(on/off)
Need to filter like (F1 & F2 & F3) at the same time like combination
The array objects do not have the show property that you test in the *ngIf directive
Rather simple, really.
I made you a small stackblitz app. But you also really, really need to work on your code. Even as a description for your help, this is just pretty messy.
I didn't code your "Multiple Selections" for you.
https://stackblitz.com/edit/angular-ivy-fbpgdv?file=src/app/app.component.ts
Observation/Suggestions :
Instead of adding a new property show. You can easily achieve that by filtering out the datas array based on the value passed in the filter() method.
Instead of modifying the original array, you can make a deep copy and do the operation on click on filter().
Working Demo : https://jsfiddle.net/srvpw2bo/

How to name dynamically a reference in a div on Angular template inside a loop

I'm developing a project using Angular 8.3.
I have a loop (using *ngFor) and I need that every div that I add has an unique reference name created dinamically).
This is my example code:
<div *ngFor="let item of list; let i = index;">
<div
cdkDropList
#done0="cdkDropList"></div>
</div>
Below I have another drag and drop zone that must receive these elements:
<div
cdkDropList
#donesList="cdkDropList"
[cdkDropListData]="DonesList"
[cdkDropListConnectedTo]="[done0, done1]"
class="movie-list"
(cdkDropListDropped)="onDrop($event)">
<div *ngFor="let itemList of DonesList" cdkDrag>{{itemList}}</div>
</div>
I need to change dynamically #done0, e.g. (#done0, #done1, #done2, #done3, etc).
How can I do it? I have tryed using #done{{i}}} but it doesn't work.
I don't know if I can use trackBy in this case and how to apply it.
Many thanks,
Reference a container:
<div *ngFor="let item of list; let i = index;" #doneContainer>
<div cdkDropList></div>
</div>
and in .ts you have different references in a NodeList:
const list = this.doneContainer.childNodes
or in a more Angular way, which is better, as it will update when necessary:
#ViewChildren('doneContainer') list: QueryList<any>;

How can I show comments of clicked feed only

I'm stuck in a situation, in a nested loop. The top loop iterating over the Feeds array and every feed in Feeds have comments array. This comments is used for inner loop.
My problem is, I have Comment button and on click of that button, I want to show the list of comments only for that feed post. But instead of showing comments on current feed post, it is showing comments for all other feeds also, that's because of use of only one Boolean variable showComments created inside .ts file.
So how can make that work for only the given context Feed post?
HTML
<ng-container *ngFor="let feed of feeds">
<div class="ed-card ed-card-feed">
<div class="ed-card-feed--footer">
<div class="card-feed-values">
<a class="mr-4"><span>{{feed.likes_count}}</span> Likes</a>
<a><span>{{feed.comments_count}}</span> Comments</a>
</div>
<div class="card-feed-actions">
<a class="mr-5" (click)="likePost(feed.id)"><span></span> Like</a>
<span></span> Comment button
</div>
</div>
// Below div needs to be open for that particular feed only
<div *ngIf="showComments" class="ed-card-feed--comments">
<div class="feed-comments-list">
<ng-container *ngFor="let comment of feed.comments?.edges">
<div class="comment-list-item">
<p class="comment-box mt-1">
{{comment.node?.body}}
</p>
</div>
</ng-container>
</div>
</div>
</div>
</ng-container>
P.S. - Please don't confuse with feed.comments?.edges that's just because of GraphQL. It is just an array.
Thanx
You can maintain an array of showComments[] equal to size of feeds with initially set to false. showComments[false,false...]
<ng-container *ngFor="let feed of feeds; let myIndex = index"">
//code here
<span></span> Comment button
//check if true
<div *ngIf="showComments[myindex] === true" class="ed-card-feed--comments">
</ng-container>
Instead of a boolean, you can define a selectedFeeds array:
selectedFeeds = new Array<Feed>();
In the template, you add the feed that has been clicked to selectedFeeds:
<a href="javascript:void(0)" class="mr-5" (click)="selectedFeeds.push(feed)>Comment button</a>
and you filter the comments according to the selected feeds:
<div *ngIf="selectedFeeds.indexOf(feed) >= 0" class="ed-card-feed--comments">
The best way to achieve this is to separate the code to different components. And since each of the component will have it's own instance of comments - you won't have a problem creating a local variable and showing/displaying the comments in there.
I've created an example of how it can be implemented: stackblitz
Another approach
<ng-container *ngFor="let feed of feeds">
Comment button
<div *ngIf="feed.showComments" class="ed-card-feed--comments">
</ng-container>
Js file
toggleComments(feed) {
feed.showComments = true // (feed.showComments = !feed.showComments => to show or hide (toggle))
}

track by $index isn't solving dupes error

I am trying to iterate through an array of objects and display them in my HTML using Angular. I have done this before without any problems by using "track by $index". But the standard error is still raised:
Error:
[ngRepeat:dupes] http://errors.angularjs.org/1.3.15/ngRepeat/dupes?p0=(key%2C%20value)%20in%20n&p1=string%3Ab&p2=b
But I have already added track by $index. I am confident there isn't any issue requesting the data as I have been able to display it correctly.
<div ng-repeat="n in post.userData.bookmarks track by $index" >
<div ng-repeat="(key, value) in n">
<div>{{ key }}</div>
</div>
</div>
Suggestions?
Dataset:
bookmarks: [{"1234": "Title1"}, {"5678": "Title2"}]
What happens is that a user bookmarks a post that they want to read later. I save the post id and the title of the post to an object and store this in an array. (I actually just wanted to store it to an object like so: {"1234": "Title1", "5678": "Title2"}, but couldn't figure it out with mongodb, since it doesn't seem to let you save as object data type.)
Then I want to ng-repeat the user's bookmarks in their profile. The only way I know how to do this is to iterate through the array and then iterate through each object. Of course, there is only one item in each object. But this is why I have the second ng-repeat. But when I do track by $index for both, as one of the answers lists below, it still doesn't give the desired behavior.
Something that could be causing a problem is that I also have a sort of dashboard that can be viewed on the side while viewing any given post. And on the dashboard they should be able to see their bookmarks. Also if they are on a post that they have bookmarked, if they click a (un)bookmark button on the post, it should automatically remove that bookmark on their bookmark dashboard.
But none of the suggestions have worked.
You forgot another ng-repeat: <div ng-repeat="(key, value) in n track by $index">
I don't know Why u write two times repeat
<div ng-repeat="n in post.userData.bookmarks track by $index" >
<a ng-href="/tuts/{{n.key}}">{{ n.key}}</a>
</div>
OR
<div ng-repeat="(key,value) in post.userData.bookmarks track by $index" >
<a ng-href="/tuts/{{value}}">{{ value}}</a>
</div>
key is anything which you get from object.

How to get consolidated results from ng-repeat?

See this plunker.
<div ng-repeat="subCategory in subCategorys | filter:{tags:tag}:true | orderBy:'id'">
{{subCategory.id}} {{subCategory.name}} {{subCategory.tags}}
<br/><br/>
You are now seeing details of <span ng-init="subCats = subCats + ' ' + subCategory.name">{{subCats}}</span>
</div>
This HTML page shows a filtered result from an object. However, I want to display a consolidated result of the names after "You are now seeing details of" like for example, "You are now seeing details of jim tom". This consolidated list should appear after the element which has ng-repeat directive.
How can this be done?
Thanks
I made an updated plunker for you.
Please try to make your example plunker way more reduced to the specific problem in the future as this helps us to help you.
First I added the search binding as filter to the ng-repeat to make the filter workable:
<div ng-repeat="subCategory in subCategorys | filter:{tags:tag}:true | filter:{id:search} | orderBy:'id'">
To avoid executing the filter twice you can save the filter result directly into a scope variable by simply assinging it (in my example to subCategorysFilter):
<div ng-repeat="subCategory in subCategorysFilter = (subCategorys | filter:{tags:tag}:true | filter:{id:search} | orderBy:'id')">
I further changed your getAllFilteredNames() method to take a filter object as argument and made it loop through the results, build an array of the names and join them with a , as separation:
$scope.getAllFilteredNames = function(filter){
var names = [];
angular.forEach(filter, function(element){
names.push(element.name);
});
return names.join(", ");
};
This is now called outside the ng-repeat directive:
You are now seeing details of {{getAllFilteredNames(subCategorysFilter)}}
Have fun!
Update
Two possible solutions for getting a multilined output:
1 - You might change the line
<div>You are now seeing details of {{getAllFilteredNames(subCategorysFilter)}}</div>
to
<div>You are now seeing details of <span ng-bind-html="getAllFilteredNames(subCategorysFilter)"></span></div>
Then any html tags within the expression are compiled as html code. But there are meaningful reasons for angular disabling this feature by default. If your objects are editable by users you need to prevent them from breaking your design by escaping all html tags...
2 - But if you do not need to display the cosolidated information within a single string, you might simply use another ng-repeat combined with an <ul> like this:
<div>You are now seeing details of <br/>
<ul>
<li ng-repeat="subCategory in subCategorysFilter">{{subCategoryName}}</li>
</ul>
</div>
Just style your li accordingly to be displayed underneath each other and you're ready to go.
You can do this in your HTML by moving your consolidated list outside of the ngRepeat and calling the filter again:
<div ng-repeat="subCategory in subCategorys | filter:{tags:tag}:true | orderBy:'id'">
{{subCategory.id}} {{subCategory.name}} {{subCategory.tags}}
<br/><br/>
</div>
<div>
You are now seeing details of
<span ng-repeat="subCategory in subCategorys | filter:{tags:tag}:true | orderBy:'id'">
{{subCategory.name}}
</span>
</div>
The drawback to this approach is that you are calling the filter twice. A better alternative would be to set up a $watch in your parent controller and invoke the $filter manually. I.e. Save the filtered results in a scope variable. The benefit is that the filter is called half as many times and the scope variables you set up are visible to the original list and the consolidated list.
app.controller('ParentController', function($scope, $filter) {
$scope.subCategorys = [{...}];
$scope.tag = {...};
$scope.$watchCollection('subCategorys', function(newList){
//if the collection changes, create a new tag
//reference that is a copy of the old one to trigger
//the tag watch listener
if (newList)
$scope.tag = angular.copy($scope.tag);
});
$scope.$watch('tag', function(newTag){
// if tag changes, apply the filter,
// and save the result to a scope variable
if(newTag)
$scope.filteredList = $filter('filter')
($scope.subCategories, { tags: newTag}, true);
});
});
HTML
<div ng-controller="ParentController">
<div ng-repeat="subCategory in filteredList | orderBy:'id'">
{{subCategory.id}} {{subCategory.name}} {{subCategory.tags}}
<br/><br/>
</div>
<div>
You are now seeing details of
<span ng-repeat="subCategory in filteredList | orderBy:'id'">
{{subCategory.name}}
</span>
</div>
</div>
I'm afraid there is no way of doing that except for selecting the subCategory back. Fortunately, there is a pretty elegant 'angular' way of doing that. Add this to your controller:
$scope.getSubCatById = function(someId) {
return $filter('filter')($scope.subCategorys, {id:someId})[0];
}
And then your html:
<div ng-repeat="subCategory in subCategorys | filter:{tags:tag}:true | orderBy:'id'">
{{subCategory.id}} {{subCategory.name}} {{subCategory.tags}}
<br/><br/>
You are now seeing details of {{ getSubCatById(2).name }}
</div>
I hope I interpreted your question correctly.

Categories

Resources