-2

I have 2 buttons for votting, on click I call a methed "onWhich" that compares the number of up and down clicks and depending on the result the background-color changes

but the editor is showing Cannot find name 'srcElement'

maybe my method is incomplete....

yes= 0;
no = 0;

  constructor() { }

  ngOnInit() {
  }


onYes(index: number){
this.yes ++;
this.onWhich();
}

onNo(index: number){
this.no ++;
this.onWhich();
}

onWhich(){
  if(this.yes > this.no){
    srcElement.parentElement.style.backgroundColor = 'blue';
  }else if(this.yes < this.no){
    srcElement.parentElement.style.backgroundColor = 'red';
  }else{
    srcElement.parentElement.style.backgroundColor = 'white';
  }

}

here is the template

<ul class="list-group">
    
   <li class="list-group-item" *ngFor="let post of posts; let i = index"> 


      <p style="font-size:35px;"> {{  post.title }} 
      
                   
   <button class="btn btn-success"  (click)="onYes(i)" >Love it!</button> &nbsp;
   <button class="btn btn-danger" (click) ="onNo(i)">Don't love it!</button> <br>


    </li>    
        
 </ul> 
wentjun
  • 40,384
  • 10
  • 95
  • 107
alex
  • 23
  • 1
  • 4

2 Answers2

0

You can make use of template reference variables when it comes to referencing to the DOM element on your template/component.html.

First, we use the # character to declare the template reference variables for both buttons.

<ul class="list-group">    
  <li class="list-group-item" *ngFor="let post of posts; let i = index"> 
    <p style="font-size:35px;"> {{  post.title }} 
    <button class="btn btn-success"  (click)="onYes(i)" #buttonYes>Love it!</button> &nbsp;
    <button class="btn btn-danger" (click) ="onNo(i)" #buttonNo>Don't love it!</button> <br>
  </li>    
</ul> 

And on you component.ts, we use @ViewChildren to access the list of reference variables within the ngFor. We will also pass the index of the clicked button to the methods binded to the click events. This will allow the component logic to access the index which is clicked, thus using the index to provide the references for the index of the element within the QueryList.

Do note that we have to use the .ToArray() to return a copy of the result from QueryList as an array, so that we can access the each element within the QueryList via its index. For more details regarding the usage of QueryList, feel free to refer to the documentation.

import { Component, ViewChildren, ElementRef, QueryList } from '@angular/core';
.
.
posts = [{
 title: 'AAA',
 yes: 0,
 no: 0 
},{
 title: 'BBB',
 yes: 0,
 no: 0 
}, {
 title: 'CCC',
 yes: 0,
 no: 0  
}];

onYes(index: number){
  this.posts[index].yes++;
  this.onWhich(index);
}

onNo(index: number){
  this.posts[index].no++;
  this.onWhich(index);
}

onWhich(index: number){
  const parentNode = this.buttonYes.toArray()[index].nativeElement.parentNode;
  if (this.posts[index].yes > this.posts[index].no) {
    parentNode.style.backgroundColor = 'blue';
  } else if (this.posts[index].no > this.posts[index].yes) {
    parentNode.style.backgroundColor = 'red';
  } else{
    parentNode.style.backgroundColor = 'white';
  }    
}

I have created a demo.


Alternatively, the better solution is to use ngClass, whereby the classes .list-group-item-success and .list-group-item-danger are conditionally applied based on the number of yes and no votes for each post.

<ul class="list-group">    
  <li class="list-group-item" *ngFor="let post of posts; let i = index"> 
    <p [ngClass]="{
      'list-group-item-success': post.yes > post.no,
      'list-group-item-danger': post.yes < post.no}" style="font-size:35px;"> {{  post.title }} 
      <button class="btn btn-success"  (click)="onYes(i)" #buttonYes>Love it!</button> &nbsp;
      <button class="btn btn-danger" (click) ="onNo(i)" #buttonNo>Don't love it!</button> 
    </p>
  </li>    
</ul> 

And on the component.css, we define the classes required.

.list-group-item-success {
  background-color: blue;
}

.list-group-item-danger {
  background-color: red;
}

DEMO.

wentjun
  • 40,384
  • 10
  • 95
  • 107
  • it works, thanks! but it does not work perfectly.. well for example if I have 2 posts and I want to vote to the second one the vote is only for the first means if I up vote to the second or down vote for it all it goes to the first one and the changes is only for it – alex Jul 24 '19 at 16:11
  • @alex yeah I know. It is simplified code. The main point of my answer is to show that you can use template reference variables to mutate the element's CSS properties :) – wentjun Jul 24 '19 at 16:15
  • alright but could u guide me how to fix this problem in order to separate them (I don(t master this language too much) – alex Jul 24 '19 at 16:23
  • @alex understood! I have updated my demo to reflect my new logic. If you click on the demo, you will see the updated one. I will try to explain it on my post :) – wentjun Jul 24 '19 at 16:58
  • there is no other way ? I guess it is not practise to initialise yes and no at every post.. in this exemple is easy but should think when u have more data :) – alex Jul 24 '19 at 17:09
  • the editor shows **Property 'toArray' does not exist on type 'ElementRef'.** . look at [demo](https://stackblitz.com/edit/angular-hjckyy?embed=1&file=src/app/app.component.ts).. and I have to admit that I didn't understand exactly what this line do `const parentNode = this.buttonYes.toArray()[index].nativeElement.parentNode;` – alex Jul 24 '19 at 23:51
  • @alex Hmm.. Can you click on the demo(https://stackblitz.com/edit/angular-hxqhbw?file=src/app/app.component.ts) I have posted above again? I have previously made some changes to line 9 and 10 to use QueryList instead. Also, the `parentNode` variable is a HTMLElement reference to the parent node of the button – wentjun Jul 25 '19 at 00:52
  • yeah I know that parentNode refers to the parent element of the current node, I just didn't understand the conversion `toArray()` – alex Jul 25 '19 at 18:12
  • I see! Ok, that is because, I am trying to return it as a copy of the List as an an array, so that I can access the child/elements of the List via the index! – wentjun Jul 25 '19 at 18:37
  • what if I wanted to use `list-group-item-success` instead of `style.backgroundColor = 'blue';` – alex Jul 26 '19 at 20:34
  • @alex hello, sorry for the late response. I was on vacation. Do you still need help? – wentjun Aug 03 '19 at 15:07
  • hi wentjun ! hope u had fun :D yeah I still need help – alex Aug 04 '19 at 21:09
  • Oh, what is `list-group-item-success` supposed to be? – wentjun Aug 06 '19 at 15:27
  • it would be something like this (but it is not the correct form) `if (this.posts[index].yes > this.posts[index].no{ parentNode.style.backgroundColor = 'list-group-item-success'; } else if (this.posts[index].no > this.posts[index].yes) { parentNode.style.backgroundColor = 'list-group-item-danger'; }` u got what I mean ? – alex Aug 06 '19 at 15:34
  • @alex Hmmm... Sorry, I mean, what does `list-group-item-success` actually represent? You have to supply a valid color string (such as black, white, red, etc), or some hex code values! – wentjun Aug 07 '19 at 14:47
  • oh yes, since `list-group-item-success` and `list-group-item-danger` form specific classes in **bootstrap**, we must use `[ngClass]` – alex Aug 07 '19 at 19:14
  • it seems like u changed ur mind about helping me ! :o – alex Aug 09 '19 at 23:39
  • @alex ohh I thought you have figured it out, because you mentioned you are going to use `ngClass`!! Sorry about that. Alternatively, you can use `. className` do apply the class dynamically. Let me try it in a while – wentjun Aug 10 '19 at 03:18
  • no problem hahà, I prefer to use `ngClass` (I'm already asked to employ it) but if u can do the 2 demo (`className` & `ngClass`) I won't say no. So I will learn how it works both ;) – alex Aug 10 '19 at 10:10
  • @alex sorry for the late response!! I have just done it with `ngClass`, as shown in this demo! https://stackblitz.com/edit/angular-njvhuf?file=src/app/app.component.html . I added the conditions on the `ngClass`, such that the different classes are used based on the number of yes/no votes. – wentjun Aug 11 '19 at 11:07
  • sorry u might didn't figure out what I meant exactly.. instead of writting `background-color: red ` we have to use `list-group-item-danger` which will color it by light red, whereas this code does not use bootstrap classes. here is just used as selector – alex Aug 11 '19 at 14:34
  • 1
    Hi @wentjun I just wanted to tell u that I managed to reach the goal.. and also to thank u for your patience hahà :D – alex Aug 13 '19 at 11:03
  • 1
    this was the expected [result](https://stackblitz.com/edit/angular-1smibu) – alex Aug 13 '19 at 11:04
0

I would go the same route as wentjun with adding properties yes and no, but I wouldn't manipulate the DOM from the TS file, instead we can use ngClass and apply style classes. Let's say classes like:

.blue-bg {
  background-color: blue;
}

.red-bg {
  background-color: red;
}

.white-bg {
  background-color: white;
}

If you have an existing array of posts, you can add the new properties with:

this.posts = this.posts.map(post => { return {...post, yes: 0, no: 0} });

Then add the click handlers for the button like wentjun and pass the index:

<button (click)="onYes(i)">Love it!</button>
<button (click) ="onNo(i)">Don't love it!</button>

And the corresponding functions:

onYes(i: number){
  this.posts[i].yes++;
}

onNo(i: number){
  this.posts[i].no++;
}

Then mark in the template with conditions in ngClass:

[ngClass]="[post.yes > post.no ? 'blue-bg' : '',  
            post.yes < post.no ? 'red-bg' : '', 
            post.yes === post.no ? 'white-bg' : '']"

DEMO: Stackblitz (which I kindly forked from wentjuns stackblitz)

AT82
  • 71,416
  • 24
  • 140
  • 167
  • thank u for providing the effort to answer.. ur code is fine too, but u know I can not admit more than one answer, and since wentjun preceded u I had to admit his answer :) manipulating DOM from ts file is a best way ? – alex Jul 25 '19 at 18:23
  • My personal opinion is that do NOT manipulate the DOM in any way if only possible. Angular have other tools to do what you need. Also for example angular team warns about manipulating the DOM: https://angular.io/api/core/ElementRef#description I just have taken it as a rule of thumb to not manipulate the dom from the TS if only possible, thus I prefer my solution (guess I'm a bit biased tho, hahah) But yes, accept the answer that you feel is more suitable for you. If you think Wentjun's answer is more suitable for you, I'm ok with thast ;) – AT82 Jul 26 '19 at 07:58