Update only selected array item in *ngFor array Angular2 - arrays

I have an *ngFor loop to display libraries. When you click on a library, categories of that library appear beneath in a tree-like structure. The categories are also being displayed with an *ngFor loop. When I have one library expanded and click on another one, the categories in BOTH libraries are updating to the categories in the library that was just clicked. The functionality I am looking for is to only update the selected library categories and leave the others alone. There is a post here that seems to be close to my problem but I couldn't get it to work.
Using *ngFor to loop through an array and *ngIf to select which elements from the array to list
Here is my code:
library.component.html
<div *ngFor="let messageLibrary of onHoldMessageLibraryService.data" class="library mgn-top10 ft-size14">
<a (click)="onHoldMessageLibraryService.getSelectedMessageLibrary(messageLibrary)"><i class="fa fa-folder-o mgn-right10" aria-hidden="true"></i>{{messageLibrary.Name}}</a>
<library-category *ngIf="messageLibrary.treeIsExpanded" (displayMessagesFromSelectedCategory)="getOnHoldMessages()"></library-category>
</div>
library.service
public treeIsExpanded: boolean;
public selectMessages: boolean;
public data: TreeData[];
public selectedName: string;
public selectedValue: number;
public getSelectedMessageLibrary(messageLibrary): void {
this.selectedMessagesLibrary = messageLibrary;
this.selectedMessagesLibrary.treeIsExpanded = !this.selectedMessagesLibrary.treeIsExpanded;
}
public dummyData(): void {
let myTree: TreeData[] = new Array();
myTree.push(
{
Name: 'Banking Library',
Id: 1,
Category: [{
Name: 'Credit Cards',
Id: 11,
Category: null
}]
},
{
Name: 'Automobile Library',
Id: 2,
Category: [{
Name: 'Cars',
Id: 12,
Category: null
}]
},
{
Name: 'Coffee Library',
Id: 3,
Category: [{
Name: 'Americano',
Id: 13,
Category: null
}]
}
)
this.data = myTree;
}
library-category.component.html
<ul *ngFor="let category of onHoldMessageLibraryService.selectedMessagesLibrary.Category">
<li><i class="fa fa-folder-o mgn-right10" aria-hidden="true"></i>{{category.Name}}</li>
</ul>
tree-data.ts
export class TreeData {
Name: string;
Id: number;
Category: TreeData[];
}

public getSelectedMessageLibrary(messageLibrary): void {
this.selectedMessagesLibrary = messageLibrary;
this.selectedMessagesLibrary.treeIsExpanded = !this.selectedMessagesLibrary.treeIsExpanded; //this line is not giving desired result
}
to be replaced with
public getSelectedMessageLibrary(messageLibrary): void {
messageLibrary.treeIsExpanded = !messageLibrary.treeIsExpanded;
}
And it will be good if you use trackBy with NgFor

Related

How to iterate array using ngFor loop in angular

'arr: { a: string[];b: string[];c: {id: number; name: string; }[]; } '
'''arr= {
a: ['rose', 'kelly', '35'],
b: ['marry', 'hadden', '40'],
c:[
{ id: 1, name: "Mark" },
{ id: 2, name: "John" },
{ id: 3, name: "Franc" },
{ id: 4, name: "Andrew " }
]
}
How to iterate above array using *ngFor loop in Angular'''
*ngFor directive expects the Array object to loop over. That's why you're receiving an error.
You can use keyvalue pair pipe to loop over this object, basically that will convert an object to an array, which would have key and value in separate object properties.
Since strictTemplate checking, typescript is throwing an error. To fix/suppress that error you can use define type of arr: {[key: string]: any}.
TS
arr: {[key: string]: any} = {
a: ...,
b: ...,
}
HTML
<div *ngFor="let item of arr | keyvalue">
<b>{{ item.key }}</b>
<div *ngFor="let val of item.value">
{{ val.name ?? val }}
</div>
</div>
Stackblitz

How to store values from a service to an array in Angular?

I have a service with getter and setter methods, they return me id: number and title: String from my dialog component.
I need to store the values from the response to my data array, but I just cant figure out how.
For example:
0: {id: 0, title: "UK ",…}
1: {id: 1, title: "Usd ",…}
2: {id: 2, title: "ff ",…}
3: {id: 3, title: "yy ",…}
4: {id: 4, title: "nn ",…}
5: {id: 5, title: "mh ",…}
6: {id: 6, title: "tr ",…}
7: {id: 7, title: "es ",…}
I would be so grateful if you guys can help me out.
Here is what I got so far:
app.component.ts
export class AppComponent {
clickEventsubscription: Subscription
ngOnInit() {
}
id: number;
title: String;
data: any = [];
constructor(private share: ShareDataService) {
this.clickEventsubscription = this.share.getClickEvent().subscribe(() => {
this.initialize();
})
}
initialize() {
this.id = this.share.getId();
this.title = this.share.getTitle();
console.log(this.id, this.title);
}
}
app.component.html
<app-dialog></app-dialog>
<h2>Add values of my service into array:</h2>
<button (click)="initialize()"></button>
share-data.service.ts
import { Injectable } from '#angular/core';
import { Observable, Subject } from 'rxjs';
#Injectable({
providedIn: 'root'
})
export class ShareDataService {
title: String;
id: number;
getId() {
return this.id
}
getTitle() {
return this.title;
}
private subject = new Subject<any>();
sendClickEvent() {
this.subject.next();
}
getClickEvent(): Observable<any> {
return this.subject.asObservable();
}
}
Many thanks!
As far as I understand your question, there are several solutions to solve this
but the simplest way is to create an new object every time you trigger the initialize method which goes as follows
change this
initialize() {
this.id = this.share.getId();
this.title = this.share.getTitle();
console.log(this.id, this.title);
}
to the following
initialize(): void {
this.id = this.share.getId();
this.title = this.share.getTitle();
const newData = {
id: this.id,
title: this.title
};
this.data.push(newData);
}
There are some syntax errors and ordering issue in your code both the service and app component which should be solved (depends on what version of angular you are using).
Also you have to declare instant fields before instance declaration of instance method, this should come at the beginning of the class/interface
move this to the top of your class in share-data.service.ts
private subject = new Subject<any>();

AngularJS ng-repeat and indexOf() to check objects

I have two json objects (data1 and data2) that have related information. Namely, both objects have properties (arrays) which in turn can have identical data. So, I am trying to figure out how to display those data with highlighting them properly, i.e. identical data with green color and non-identical with red color. Somehow it wrongly highlights all data with red color.
Here is the html:
<ul>
<li ng-repeat="item in vm.data2.features"
ng-class="vm.data1.features.indexOf(item) !== -1 ? 'check' : 'uncheck'">
<span ng-bind="item.id"></span>
</li>
</ul>
and objects:
vm.data1 = {
id: '4569',
name: 'Given data',
features: [
{id: "TEST_TEXT2", desc: 'smth12'},
{id: "TEST_PPP", desc: 'smthsmthsmth'},
{id: "TEST_ECASH", desc: "somelongtexthere"}
]
};
vm.data2 = {
id: '1305',
name: 'Base data',
features: [
{id: "TEST_BP", desc: 'smth'},
{id: "TEST_TEXT2", desc: 'smth12'},
{id: "TEST_PPP", desc: 'smthsmthsmth'},
{id: "TEST_TEXT1", desc: 'blahblah'},
{id: "TEST_ECASH", desc: "somelongtexthere"}
]
};
The full demo is here.
Any help would be appreciated.
Indexof() method will look for similarity in object references not the id itself. findIndex() method can help you here instead.
vm.hasFeature = function(item){
var hasElements= vm.data1.features.findIndex(function(e){
return e.id == item.id;
});
console.log(item, hasElements);
return hasElements;
}
And in html
<li ng-repeat="item in vm.data2.features"
ng-class="vm.hasFeature(item) > -1 ? 'check' : 'uncheck'">
vm.hasFeature = function(item){
var hasElements= vm.data1.features.findIndex(function(e){
return e.id == item.id;
});
console.log(item, hasElements);
return hasElements;
}
CodePen Link: https://codepen.io/anon/pen/ewgLBN?editors=1010
None of the objects will be the same because indexOf(item) will compare object references of item. You'll need to do a deep equals comparison of the items.
i.e.
{id: "TEST_TEXT2", desc: 'smth12'} === {id: "TEST_TEXT2", desc: 'smth12'} // false
vm.data1.features[0] === vm.data1.features[1] // false
Example using lodash would be something like:
_.some(vm.data1.features, otherItem => _.isEqual(item, otherItem))
Because
_.isEqual(vm.data1.features[0], vm.data2.features[1]) // true
Docs for Lodash:
_.some
_.isEqual

AngularJS: GroupBy and show items

I've got an application where I retreive the information of some objects (into an array). That array would have the following structure:
$scope.items = [
{
id: 23289323,
event: {
id: 972823,
name: 'Event A name',
datetime: '2017-02-01 13:45',
},
player: {
id: 58392,
name: 'Player A name'
},
team: {
id: 38839,
name: 'Team A'
},
datetime: '2017-02-03 22:23'
},
{
id: 482273784,
event: {
id: 972823,
name: 'Event A name',
datetime: '2017-02-01 13:45',
},
player: {
id: 2989273,
name: 'Player B name'
},
team: {
id: 2323434,
name: 'Team B'
},
datetime: '2017-02-03 22:23'
},
{
id: 283273939,
event: {
id: 23092803,
name: 'Event B name',
datetime: '2017-02-01 13:45',
},
player: {
id: 58392,
name: 'Player A name'
},
team: {
id: 38839,
name: 'Team A'
},
datetime: '2017-02-03 22:23'
}
...
]
What I'd like
I'd like to be able to have two lists.
On the left, a list of some customizable groupingBy AngularJS filter. So, I can specify "group it by player" and it shows, on this left list, a list of the players with some information (for example, showing the player's name).
On the right, when I select a specific player, show the items that have this player associated.
What I've tried
<li data-ng-repeat="(key, value) in Ctrl.items | groupBy: 'event.id'">
{{key}}<br/>{{value}}
</li>
What I get
23289323
{id: 23289323,event: {id: 972823,name: 'Event name',datetime: '2017-02-01 13:45',}, player: {id: 58392, name: 'Player name'}, team: { id: 38839,name: 'Team A'}, datetime: '2017-02-03 22:23'}
So, I'm getting the whole item object, but I've not found any way of getting the item that I'm groupBying. Because, right now, if there are 3 items with that event.id I get three <li></li> in stead of only one (the one of the event object).
What I ask
Is there any way of using AngularJS groupBy filter and getting in return the (whole) object that is specifying the grouping?
Remember that the groupBy key can be changed by the user.
If you need any further information, please let me know.
Thank you!
I think I've made it through a custom filter. I'm not sure if it's the best way, so if anyone has another solution, please post it!
This is the code of the filter:
(function(){
angular.module("AppModule").filter('groupByGrouped', function() {
return function(list, groupedElement) {
var result = [];
var used_elements = [];
var ref_item;
var ref_check;
// Loop through every list item
angular.forEach(list, function(item){
// We take the object we want to group by inside the item
ref_item = item[groupedElement];
// If it exists
if(ref_item !== undefined){
if(ref_item.id !== undefined){
// If it has an ID, we take the ID to make it faster.
ref_check = ref_item.id;
}else{
// Otherwise, we identify the specific object by JSON string (slower method)
ref_check = JSON.stringify(ref_item);
}
// If it does not exist yet
if(used_elements.indexOf(ref_check) == -1){
// We add it to the results
result.push(ref_item);
// And we add it to the already used elements so we don't add it twice
used_elements.push(ref_check);
}
}else{
// Otherwise we log it into our console
console.warn("The key '"+groupedElement+"' inside the specified item in this list, does not exist.");
}
});
return result;
};
});
})();
This will return the whole object. So our HTML would be something like:
<ul>
<li data-ng-repeat="(key, obj) in Ctrl.items | groupByGrouped: 'event'">
<span class="object_id">{{obj.id}}</span>
</li>
</ul>
Or even with a directive (not tested, but should work aswell):
<ul>
<li data-ng-repeat="(key, obj) in Ctrl.items | groupByGrouped: 'event'">
<obj_directive ourobject="obj"></obj_directive>
</li>
</ul>

Angularjs push list elements to an array

It's a small memory game: the player has to remember a list of words and then he is presented with another list which contains few words from the original list where he has to decide if any of these words were present in the original list. For that he can click on 'Yes' or 'No' radio button.
<div align="center" ng-show="showWords" style="margin-bottom:1cm;">
<div ng-repeat="word in words">
<h4>{{word}}</h4>
</div>
</div>
<div align="center" ng-show="showTest" style="margin-bottom:1cm;">
<div ng-repeat="word in wordsPresent | limitTo:limitTo">
<h4>{{word}}</h4>
<label>
<input type="radio" value="yes">
Yes
</label><br/>
<label>
<input type="radio" ng-value="no">
No
</label><br/>
</div>
</div>
...
<div align="center" ng-if="showOk">
<button class="button button-dark" ng-click="pushToArray()">OK</button>
</div>
In my controller I have:
$scope.words=['Okra', 'Musa', 'Sky', 'India', 'Rose', 'Titanic', 'Onion', 'Germany', 'Beer', 'Run', 'Garden', 'Mountain']
$scope.wordsPresent=['King', 'Garden', 'America', 'Sky', 'Potato', 'Germany', 'Rose', 'Whisky', 'Mumbai', 'Walk']
$scope.playerChoices=[]
The first array is the original array against which I have to check, second array is what I am presenting to the user right now, and third array might be the array where I push his choices.
It looks like following:
How can I implement this?
Update
Please see this updated plunker demo.
To populate the data dynamically onto the DOM, you can use ng-repeat. Please see the code on plunker.
<span ng-repeat="item in someArray">{{item}}</span>
To interpolate the looped item, use {{ }}.
Important : Just please avoid duplicate data on $scope.wordsPresent array coz it will produce error on ng-repeat.
Instead of pushing the "choices" at the end of the quiz/survey(?) why not push it every time the radio changes it's value? Push to array if yes is selected and remove from array if no is selected.
In that case you can use ng-change event to trigger the pushing and removing of data in the array, on the controller. You should take advantage of the 2 way data binding angularjs is offering.
You can then cross check your arrays.
Please see this plunker demo I created and I hope it will help you.
If you can make do with a library, I would suggest lodash.
_.intersection([2, 1], [2, 3]);
// ➜ [2]
Or in the context of your problem:
_.intersection($scope.words, [$scope.wordsPresent]);
// ➜ ['Sky', 'Germany', 'Rose', 'Garden']
https://lodash.com/docs#intersection
It seems to me if you want to check for correct answers, you need to do more than just compare arrays. Suppose they select "No" when something is actually in the list. First, I would put a key field on the items in my array so you are not so limited. Here is a working Plunker
Your data should probably look something like this:
$scope.words = [{
id: 11,
name: 'Okra'
}, {
id: 12,
name: 'Musa'
}, {
id: 4,
name: 'Sky'
}, {
id: 13,
name: 'India'
}, {
id: 14,
name: 'Rose'
}, {
id: 15,
name: 'Titanic'
}, {
id: 16,
name: 'Onion'
}, {
id: 6,
name: 'Germany'
}, {
id: 17,
name: 'Beer'
}, {
id: 18,
name: 'Run'
}, {
id: 2,
name: 'Garden'
}, {
id: 19,
name: 'Mountain'
}]
$scope.wordsPresent = [{
id: 1,
name:'King'
},
{
id: 2,
name: 'Garden'
}, {
id: 3,
name: 'America'
}, {
id: 4,
name: 'Sky'
}, {
id: 5,
name: 'Potato'
}, {
id: 6,
name: 'Germany'
}, {
id: 7,
name: 'Rose'
}, {
id: 8,
name: 'Whisky'
}, {
id: 9,
name: 'Mumbai'
}, {
id: 10,
name: 'Walk'
}]
Then I would add an answer field (you could include it when you set up your data or you can include i after like this:
setAnswers();
function setAnswers() {
angular.forEach($scope.wordsPresent, function (value, key) {
$scope.wordsPresent[key].answer = 'no';
});
angular.forEach($scope.words, function (value, key) {
$scope.words[key].answer = 'yes';
})
}
Your HTML could look like this:
<div ng-app="myModule" ng-controller="HomeCtrl">
<div align="center" style="margin-bottom:1cm;">
<div ng-repeat="word in wordsPresent | limitTo:limitTo">
<h4>{{word.name}}</h4>
<input type="radio" ng-model="word.answer" value="yes">
Yes
<br />
<input type="radio" ng-model="word.answer" value="no">
No
<br />
</div>
</div>
<div ng-show="showAnswers">
<span>Correct Answers: {{correct}}</span> <span>Incorrect Answers: {{wrong}}</span>
You selected:
<div ng-repeat="a in playerChoices">
<span>{{a.name}}</span> <span>{{a.answer}}</span>
</div>
</div>
<div align="center">
<button class="button button-dark" ng-click="pushToArray()">OK</button>
</div>
</div>
And in your controller:
$scope.pushToArray = function () {
$scope.correct = 0;
for (var i = 0; i < $scope.wordsPresent.length; i++) {
$scope.playerChoices.push($scope.wordsPresent[i]);
}
$scope.showAnswers = true;
$scope.correct = correctAnswers($scope.playerChoices, $scope.words);
}
function correctAnswers(checkerArray, targetArray) {
correct = 0;
wrong = 0;
for (var i = 0; i < checkerArray.length; i++) {
if (checkerArray[i].answer == 'yes') {
if (containsObject(checkerArray[i], targetArray) == true) {
correct = correct + 1;
}
}
else if (checkerArray[i].answer = 'no') {
if (containsObject(checkerArray[i], targetArray) == false) {
correct = correct + 1;
}
}
}
return correct;
}
function containsObject(obj, list) {
var i;
for (i = 0; i < list.length; i++) {
if (list[i].id === obj.id) {
return true;
}
}
return false;
}
Happy coding!

Resources