0
votes

I'm relatively new to Angular so please forgive my may be trivial question.

I've also googled a lot, found a lot of similar questions, but none really addressing my problem.

Situation

Let's assume the following component's template:

<div class="wrapper">
    <!-- Stuff -->
    .
    .
    .
    <div *ngFor="let group of groups">
        <button #btn (click)="onClick(btn, group)">Change group title</button>
        <div class="group-title">
            {{group.title | translate}}
        </div>
        .    
        .    
        .    
        <div *ngFor="let item of group.subGroup">
            {{item.title}}
            .    
            .    
            .    
        </div>
    </div>
<div/>

Now I want the user to be able to trigger a series of actions/events that eventually change the title of a single Group (single item in ngFor).

I.e.

onClick(btnRef, group) {
    .    
    .    
    .
    anAsyncronousEvent.subscribe(
        success => group.title = success.BrandNewTitle;
    )    
}

Desired outcome

The DOM should display the new title of the affected group, but it doesn't. I've also tried without the 'translate' pipe, but in vain.

By googling around, I seem to understand that a change in DOM is detected by Angular only by looking at object reference used to generate the ngFor items, so that if I change only some internal property of the Object/Item, a ChangeEvent is not triggered.

Possibile solutions

One idea could be to make a deep copy of the Group Object and replace the corresponding item in the array behind the ngFor, but that would cripple the whole mess of making Angular refresh DOM only for what is really changed. Another idea could be to use markForCheck() but here I have two choices:

1) Use markForCheck() in the clickHandler but, again, this would trigger re-rendering for all groups in the component.

2) I could somehow create a subcomponent only for the group title but it seems to me kind of an overkill, and I'm also not sure it would work.

So, how do I force Angular to refresh ONLY the group title?

Thank you all in advance

EDIT

Ok. Probably the code posted above is a bit oversimplified.

This one here is closer to my real world app and is running on stackblitz here, where it reproduces the problem I'm trying to solve.

// Template
<div class="wrapper">
    <div *ngFor="let group of groups">
        <button #btn (click)="onClick(btn, group)">Pop away item</button>
        <div class="group-title">
            Group info: count = {{group.info | format}}
        </div>
        <div *ngFor="let item of group.items">
            &nbsp;&nbsp;&nbsp;&nbsp;Item title: "{{item.title}}"
        </div>
    </div>
</div>

//Component
onClick(btn, group: any) {
  setTimeout(() => { // In the real App this is an Observable
    let i = this.groups.indexOf(group);
    group.items.pop();
    group.info.count--;
  }, 1000);
}

I'm expecting the items count to be show decreased after button click, but it is not.

1
Can you provide a minimal repro of this on stackblitz?yurzui
@yurzui I'm working at it (stackblitz). As soon as it's ready I'll post the linkuser4096537
but that would cripple the whole mess of making Angular refresh DOM only for what is really changed .. this would not be the case if you use trackByVikas
@Vikas Well, not exactly because trackBy is meant for make angular track changes not by reference to the object but by other means. In my case, however, I do want Angular track the reference. Suppose I generate a new object with the same title (which in my case is allowed). What should do Angular if I tell it to track by the title? It wouldn't refresh, right?user4096537
ngFor by default tracks list items using object identity if you generate new object with same title angular will pick up the changes and it would re-create DOM but with trackBy it would just update the DOM rather than destroying and re-creating itVikas

1 Answers

0
votes

As per your requirement if you don't want to update the entire groups[] but only want to change a particular group title in the DOM, one thing what you can do is pass the index of the group to the click event and use that to generate the ids dynamically so that u can access that id and change the HTML value when the event is triggered. Something like below

<div *ngFor="let group of groups; let i = index">
    <button #btn (click)="onClick(btn, group, i)">Change group title</button>
    <div class="group-title" id="group{{i}}">
        {{group.title | translate}}
    </div>
    .    
    .    
    .    
    <div *ngFor="let item of group.subGroup">
        {{item.title}}
        .    
        .    
        .    
    </div>
</div>

Here you can make use of that index"i" to update your groups[] and then since you have access to with unique id you can change the value in that .

onClick(btnRef, group, index) {
.    
.    
.
anAsyncronousEvent.subscribe(
    success => {
         document.getElementById(`group${index}`)
             .text(success.BrandNewTitle);
      }
    )    
  }

Alternatively you can use a component where you can pass each group data, in that case you can explicitly reload only that group-component