Change HTML in Angular 7 by reference - javascript

I'm passing UL by reference in an Angular function and I want to append some 'li' in that UL.
<ng-container *ngIf="FileUploaded">
<tr class="" *ngFor="let row of this.fileJson">
<td class="text-left">{{row.Comment}}</td>
<td class="text-center">
<div class="tags">
<ul class="tags tags--ds ui-sortable" #forAddingTag> // This is the reference which is passed in below button's function named "AddTag()"
<ng-container *ngFor="let key of this.Object.keys(row)">
<li #ToRemoveTag class="tags__item" *ngIf="key.substring(0,5)=='Topic'">
<p>{{row[key]}}</p> <span class="close__item"><i class="fal fa-times"
(click)="removeTag(ToRemoveTag)"></i></span>
</li>
</ng-container>
</ul>
</div>
</td>
<td class="text-center">
<a class="btn" (mousedown)="AddTag(forAddingTag)">Update</a> // Here the reference is being passed
</td>
</tr>
</ng-container>
Here is the typescript code
AddTag(evalue){
debugger
var text = "";
if (window.getSelection) {
text = window.getSelection().toString();
}
var value = text;
}
I just want to add some more "li" at the end of this UL. I can do it with the help of JQuery but it shouldn't be used in Angular.

I'd recommend not to run any function in the *ngFor tag. It'll be called too many times than you think it does. Better way would be to create another object in the controller (something like filteredRows) and loop over it in the template with the keyvalue pipe.
Template
<ng-container *ngIf="FileUploaded">
<tr class="" *ngFor="let row of filteredRows; let rowIndex=index">
<td class="text-left">{{ row.comment }}</td>
<td class="text-center">
<div class="tags">
<ul class="tags tags--ds ui-sortable" #forAddingTag>
<ng-container *ngFor="let topic of filteredRows.topics | keyvalue; let topicIndex=index">
<li #ToRemoveTag class="tags__item">
<p>{{ topic.value }}</p> <span class="close__item"><i class="fal fa-times"(click)="removeTag(rowIndex, topicIndex)"></i></span>
</li>
</ng-container>
</ul>
</div>
</td>
<td class="text-center">
<a class="btn" (mousedown)="addTag(rowIndex)">Update</a>
</td>
</tr>
</ng-container>
Then you could modify the filteredRows variable (add/remove properties) to reflect the changes in the template.
Controller
filteredRows: {comment: any, topics: any[]}[];
ngOnInit() {
fileJson.forEach(row => {
const comment = row['Comment'];
const topics = JSON.parse(JSON.stringify(row)); // <-- create a deep copy of `row` object
for (const key in topics) {
if (topics.hasOwnProperty(key)) {
if (key.substring(0, 5) !== 'Topic') {
delete topics[key];
}
}
}
this.filteredRows.push({comment: comment, topics: topics});
});
}
addTag(index) {
this.filteredRows[index].topics.push(
{
// object content
}
);
}
removeTag(rowIndex, topicIndex) {
delete this.filteredRows[rowIndex].topics[topicIndex];
}
Function name AddTag() goes against the usual convention of camel case naming for functions.

Related

Add a class on item selection and remove it to add on another element selection

I have a table that shows me a list of item.
I want to be able to add a class on the selected item and remove it when I select another one.
Here's my template:
<div class="row">
<div class="col-10 pl-3">
<h1>My Shopping List</h1>
<h3 class="mb-0">Add all the ingredients you need</h3>
<app-shopping-edit></app-shopping-edit>
<table>
<tr>
<th>Ingredient</th>
<th>Amount</th>
</tr>
<tr
*ngFor="let ingredient of ingredients; let i = index"
(click)="onEditItem(i)"
>
<td>{{ ingredient.name }}</td>
<td>{{ ingredient.amount }}</td>
</tr>
</table>
</div>
</div>
Here's my ts code:
onEditItem(index: number) {
this.shoppingListService.startedEditing.next(index);
this.addStyleOfSelection(event);
}
addStyleOfSelection(event: any) {
let selection = event.target;
let trSelected = selection.parentNode;
trSelected.classList.toggle('selected');
}
Also "event" is deprecated, but I can't figure out what to use instead.
you can do something like that
<div *ngFor="let item of items" [class.is-selected]="item === selectedItem" (click)="selectedItem = item">{{ item.name }}</div>
you can try another way also, *.html
<div
*ngFor="let item of items; let idx = index"
[ngClass]="{ selected: activeIdx == idx }"
(click)="handleActiveRow(idx)"
>
{{ item.name }}
</div>
and also *.ts file looks
public activeIdx: number | null = null;
public items: Array<any> = [{ name: 'hitee' }, { name: 'krish' }];
public handleActiveRow(event: any) {
this.activeIdx = event;
}
and also *.css looks like,
.selected {
background-color: blueviolet;
}

Hiding elements using ngFor loop

I have a list of items i am getting from backend.
I do not want to show two items in UI.
I do not want to display Conference and reception in UI.
HTML Code:
<ng-container *ngFor="let space of space_name">
<div *ngIf="space">
<a class="dropdown-item text-light" [routerLink]="['/spaces']"
routerLinkActive="current"
[queryParams]="{space_name:space}" data-toggle="collapse"
data-target=".navbar-collapse.show"
style="background-color: #8d0528;">{{ space | uppercase }}</a>
</div>
</ng-container>
TS Code:
this.sharedService.getDropdownspace().subscribe(data => {
this.spaceDropdown = data;
this.api_data = Object.values(this.spaceDropdown);
this.space_name = this.api_data[0];
})
Please suggest me a approach to do this.
You can use the array filter method to do the same. Create a function in your component and use it on the *ngFor directive like below.
In your component create a function to filter the result.
filter(itemList: space_name[]): space_name[] {
let result: space_name[] = [];
result = itemList.filter(item => {
return (item != "Conference" && item != "Reception")
});
return result;
}
In your template file
<ng-container *ngFor="let space of filter(space_name)">
<div *ngIf="space">
<a class="dropdown-item text-light" [routerLink]="['/spaces']" routerLinkActive="current" [queryParams]="{space_name:space}" data-toggle="collapse" data-target=".navbar-collapse.show" style="background-color: #8d0528;">{{ space | uppercase }}</a>
</div>
</ng-container>
Code and Demo: https://jsfiddle.net/ybdmrL2z/

Display categories that only contain images

I developed a gallery of images separated by several categories.
I just want to display categories that contain images.
I have three categories: "new", "old", "try".
Of these three categories, only new and old have images. My problem is that all categories are appearing, even those that have no image (as is the case with try).
Is there a way to present only the categories that contain images?
How can I do this?
DEMO
code
<div *ngFor="let cat of Cats">
<div class="row ">
<span class="">{{cat}}</span>
</div>
<ul class="mdc-image-list my-image-list" style="padding-left: 10px;padding-right: 10px;">
<ng-container *ngFor="let product of data; let j = index;">
<li class="mdc-image-list__item" *ngIf="product.Cat == cat">
<div class="mdc-image-list__image-aspect-container">
<ng-container *ngIf="product.image == null; else productImage">
<img src="./assets/image-not-found.svg" class="mdc-image-list__image imagenotfound">
</ng-container>
<ng-template #productImage>
<img [src]="product.image" class="mdc-image-list__image">
</ng-template>
</div>
</li>
</ng-container>
</ul>
</div>
The try should not be presented :(
You can wrap your category div in an <ng-container>, and then use *ngIf to check if your data array contains a product in a given category. The best way to achieve that is to have an array with product counts per given category.
Add this to your AppComponent class:
get counts() {
return this.data.reduce((obj, value) => {
if (value.Cat in obj) {
obj[value.Cat]++;
} else {
obj[value.Cat] = 1;
}
return obj;
}, {});
}
And then use this as your template:
<ng-container *ngFor="let cat of Cats">
<div *ngIf="counts[cat]">
<div class="row ">
<span class="">{{cat}}</span>
</div>
<ul class="mdc-image-list my-image-list" style="padding-left: 10px;padding-right: 10px;">
<ng-container *ngFor="let product of data; let j = index;">
<li class="mdc-image-list__item" *ngIf="product.Cat == cat">
<div class="mdc-image-list__image-aspect-container">
<ng-container *ngIf="product.image == null; else productImage">
<img src="./assets/image-not-found.svg" class="mdc-image-list__image imagenotfound">
</ng-container>
<ng-template #productImage>
<img [src]="product.image" class="mdc-image-list__image">
</ng-template>
</div>
</li>
</ng-container>
</ul>
</div>
</ng-container>
You can write format function, write data as an object where the key is category, value is array of images and use keyvalue pipe.
For example:
component property: formattedData: {[key: string]: string[]} = {}
format function:
formatData(data: {image: string; cat: string}[]): void {
data.forEach((item: {image: string; cat: string}) => {
if (this.formattedData[item.cat]) {
this.formattedData[item.cat].push(item.image)
} else {
this.formattedData[item.cat] = [item.image]
}
});
}
call the format function in constructor:
constructor() {
this.formatData(this.data);
}
template:
<div *ngFor="let item of formattedData | keyvalue">
<div class="row">
<span class="">{{item.key}}</span>
</div>
<ul class="mdc-image-list my-image-list" style="padding-left: 10px;padding-right: 10px;">
<ng-container *ngFor="let image of item.value;">
<li class="mdc-image-list__item">
<div class="mdc-image-list__image-aspect-container">
<img [src]="image ? image : './assets/image-not-found.svg'" class="mdc-image-list__image" [ngClass]="{'imagenotfound': !image}">
</div>
</li>
</ng-container>
</ul>
</div>
I think it looks better because we have a little less logic in the template. You have only listed categories that have images.

AngularJS - Target specific element inside ng-repeat

I´m using ng-repeat to iterate an array and create a table. In that table, I have a button to make a download. When I click the download button I want the link to disappear and make a loading spin appear. The problem is, the spin is showing up in all the rows and not in just the one i click.
Html -
<tbody md-body>
<tr md-row ng-repeat="">
<td md-cell>
<div layout="row" layout-align="center center">
<md-progress-circular ng-if="isSubmit"></md-progress-circular>
<a ng-if="!isSubmit" ng-click="download($index)">Download</a>
</div>
</div>
</td>
</tr>
</tbody>
JS -
$scope.download = function(index) {
angular.forEach($scope.downloads, function (download) {
// I can console log the index i click
console.log(index)
});
}
You should use the $index on the ng-repeat and instead a boolean isSubmit, use the index to compare to array index.
HTML
<tr ng-repeat="item in items">
<td>
<md-progress-circular ng-if="isLoadingIndex == $index"></md-progress-circular>
<a ng-if="isLoadingIndex != $index"
ng-click="download($index)">Download</a>
</td>
</tr>
CTRL
$scope.isLoadingIndex = null;
$scope.donwload = function($index) {
$scope.isLoadingIndex = $index;
//Rest of your code...
}

Angular show/hide with nested repeats

I have a double repeat going on here, an initial repeat list, and then a second one (for expanding details). What is the best way to show/hide the second repeat ONLY at that index in which clicked at. I have them separated by class name however Angular's Jquery Lite doesn't support the "nextUntil" feature. I'm thinking a ng-class conditional but I don't want based on the Scope (needs to be temp for each expand).
<tr ng-repeat-start="x in calls track by $index" class="{{x.status}}" class="{{x.status}}" ng-click="getCallDetails(x,$index)" my-draggable >
<td><small><i class="fa fa-plus"></i></small></td>
<td style="text-align:center;"><span class="label label-default stat-context {{x.status}}">{{x.statusdesc}}</span></td>
<td>{{x.cust_no}}</td>
<td>{{x.company}}</td>
<td>{{x.name}}</td>
<td>{{x.emp_id}}</td>
<td>{{x.enterdate}}</td>
</tr>
<tr class="callDetails" ng-class="callDetails" ng-repeat-end ng-repeat="y in x.callDetails" >
<td></td>
<td colspan="2">{{y.emp_name}}</td>
<td>{{y.attempt_date}}</td>
<td colspan="2">{{y.note}}</td>
<td class="allAtt-stat">{{y.callstatus}}</td>
</tr>
</tbody>
my simple angular functions
$scope.getCalls = function() {
$http.get("callView.php")
.success(function(response) {
$scope.calls = response;
});
};
$scope.getCallDetails = function(attempt,ind) {
$http.get("callDetails.php?a=" + attempt.action_id)
.success(function (response) {
attempt.callDetails = response;
});
};
With nested ng-repeats, $index would refer to the innermost scope.
If you want to differentiate between parentindex and childindex, child elements can be accessed using $index while parent elements can be accessed either using $parent.$index or using ng-init, parentindex can be initialised to some value.
First approach:
<div ng-repeat="item in items">
<div>{{item.key}}</div>
<ul ng-repeat="val in item.value track by $index">
<li >child index {{$index}} -- parentIndex {{$parent.$index}}</li>
</ul>
</div>
Second approach:
<div ng-repeat="item in items" ng-init="parentIndex=$index">
<div>{{item.key}}</div>
<ul ng-repeat="val in item.value track by $index">
<li >child index {{$index}} -- parentIndex {{parentIndex}}</li>
</ul>
</div>
see the demo for reference

Categories

Resources