0

I have a Stackblitz.com sample here https://stackblitz.com/edit/angular-xhulyo?file=app%2Fapp.component.ts, and I need to combine totals of users like dbrown or qadmin instead of displaying them on separate lines. It currently displays the results like this:

User    Success Failure
dbrown  16  0
qadmin  4   0
dbrown  4   1
qadmin  21  2
administrator   42  0
cooper  8   0
ad.brown    7   0

and dbrown's totals should be 20 and 1 and qadmin's should be 25 and 2. You can see from the code that it's just iterating through the users variable. Here's the app.component.ts:

import { Component } from '@angular/core';

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.css' ]
})
export class AppComponent  {
  name = 'Angular 5';
  users: any[] = [];
  items: any;

  ngOnInit() {
    this.items = {"Users":{"InteractiveLogon":[{"UserName":"dbrown","Success":"16","Failed":"0"},{"UserName":"qadmin","Success":"4","Failed":"0"}],"NetworkLogon":[{"UserName":"dbrown","Success":"4","Failed":"1"},{"UserName":"qadmin","Success":"21","Failed":"2"},{"UserName":"administrator","Success":"42","Failed":"0"},{"UserName":"cooper","Success":"8","Failed":"0"},{"UserName":"ad.brown","Success":"7","Failed":"0"}]}};

    for (let user of this.items.Users.InteractiveLogon)
      this.users.push(user);

    for (let user of this.items.Users.NetworkLogon)
      this.users.push(user);
  }

}

And here's the app.component.html:

<hello name="{{ name }}"></hello>
<p>
  Start editing to see some magic happen :)
</p>
<hr>
<table *ngIf="users">
  <thead>
    <tr>
      <th align=left>User</th>
      <th>Success</th>
      <th>Failure</th>
    </tr>
  </thead>
    <tr *ngFor="let row of users" >
      <td style="padding-right: 15px">{{row.UserName}}</td>
      <td>{{row.Success}}</td>
      <td>{{row.Failed}}</td>
    </tr>
</table>

I've found issues like Combining JSON Data Sets and Sum of object properties within an array that talk about using properties like hasOwnProperty and the map and reduce functions but I'm not sure how to use them to combine totals of separate value pairs with the same name and how to total just those.

EDIT:

I went with a combination of contributors to make it more clear (for me). Comments are welcome if this is not the best way to do it. I got rid of the "this" operator too. New StackBlitz here. The combineUserTotals function looks like this:

combineUserTotals() {
  let result: any, results: any[] = [];

  this.users.map((user) => {
    result = results.find(u => u.UserName == user.UserName);

    if (!result)
      results.push({ UserName: user.UserName, Success: Number(user.Success), Failed: Number(user.Failed) });
    else {
      result.Success += Number(user.Success);
      result.Failed += Number(user.Failed);
    }
  });

  return results;
}

3 Answers3

1

You can add your users by their UserName to a map:

const map = {};
for (let user of this.items.Users.InteractiveLogon)
  if (!map[user.UserName]) {
     map[user.UserName] = user;
  } else {
     map[user.UserName].Success += user.Success;
     map[user.UserName].Failed += user.Failed;
  }
}
for (let user of this.items.Users.NetworkLogon)
  if (!map[user.UserName]) {
     map[user.UserName] = user;
  } else {
     map[user.UserName].Success += user.Success;
     map[user.UserName].Failed += user.Failed;
  }
}

Then add back your items to the list

for (let key in map) {
  if (map.hasOwnProperty(key)) {
     this.users.push(map[key]);
  }
}
1

You can see if the user is in the users array, if it is not, push it, else add the values to the existing one. As your values are strings, had to convert them with parseIt() so they summ.

for (let user of this.items.Users.InteractiveLogon) {
  let _user = this.users.find( u => u.UserName == user.UserName );
  if ( _user ){
    _user.Success = parseInt(_user.Success) + parseInt(user.Success);
    _user.Failed = parseInt(_user.Failed) + parseInt(user.Failed);
  }
  else {
    this.users.push(user);
  }
}
for (let user of this.items.Users.NetworkLogon) {
  let _user = this.users.find( u => u.UserName == user.UserName );
  if ( _user ){
    _user.Success = parseInt(_user.Success) + parseInt(user.Success);
    _user.Failed = parseInt(_user.Failed) + parseInt(user.Failed);
  }
  else {
    this.users.push(user);
  }
}

RESULT

User    Success Failure
dbrown  20  1
qadmin  25  2
administrator   42  0
cooper  8   0
ad.brown    7   0
wFitz
  • 1,266
  • 8
  • 13
  • lines 2-5 of the for loops don't seem to be getting added to the this.users variable? But this code seems to definitely work! – sfors says reinstate Monica Sep 19 '18 at 16:12
  • 1
    I know it works because I first tried it in your SB and then copied it here. I think it is too much code, there must be a way of doing it simpler, although more difficult to understand. I was looking at parallel fors in c#, but there seems to be no equivalent in typescript. – wFitz Sep 19 '18 at 20:12
  • Thanks, i liked how you resolved it combining answers. – wFitz Sep 20 '18 at 20:14
1

So here how the cool kids in Javascript do things:

So redue is a very useful way to handle array without using forloops. I understand you don't know how reduce works but take a look to this links:

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce https://www.w3schools.com/jsref/jsref_reduce.asp

 this.users= [...this.items.Users.NetworkLogon,...this.items.Users.InteractiveLogon];

https://stackblitz.com/edit/angular-jspxb9

Now, to add the duplicates:

 let result = [];
 this.users.map((a)=> {
    if (!this[a.UserName]) {
        this[a.UserName] = { UserName: a.UserName, Success: 0, Failed: 0 };
        result.push(this[a.UserName]);
    }
    this[a.UserName].Success += Number(a.Success);
    this[a.UserName].Failed += Number(a.Failed);
}, Object.create(null));
this.users = result;

enter image description here

Patricio Vargas
  • 5,236
  • 11
  • 49
  • 100
  • 1
    That's a cool way to total everything, very similar to my second link I attached which used reduce, but it didn't solve my problem. I didn't need to total everything - I needed to *combine* totals of users with the same username.. – sfors says reinstate Monica Sep 19 '18 at 16:33
  • 1
    this.users= [...this.items.Users.NetworkLogon,...this.items.Users.InteractiveLogon]; is confusing. I used this.users = this.items.Users.NetworkLogon.concat(this.items.Users.InteractiveLogon); instead – sfors says reinstate Monica Sep 19 '18 at 19:14
  • 1
    That's fine. It's called the spread operator. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax. it's how the hipster kids do it in ES6+ lol. – Patricio Vargas Sep 19 '18 at 20:07
  • this is probably the best answer based on the use of map and without looping through it and I was able to get it to total the results properly. Also, thanks for the guidance on how to better use JS functions and operators to accomplish this. – sfors says reinstate Monica Sep 19 '18 at 22:18
  • 1
    @sfors no problem. We are here to help. If you get a chance take this udemy course on javascript es6+. https://www.udemy.com/javascript-es6-tutorial/ – Patricio Vargas Sep 20 '18 at 00:13
  • Thanks - very helpful info. Yes, it's clear JS is a weakness for me, that's for sure. – sfors says reinstate Monica Sep 20 '18 at 14:51