I have an app that deals with a single type of object. I manage the state of the object using a service. In one component I display the object. From here you can click on fields to edit them in another component. You can also click on buttons to add new items to a field and also delete items.
Most of the time this works fine but sometimes it breaks without any error messages. I click on the delete or edit buttons or fields and nothing happens. The click handlers are broken (I have checked this in the console). This usually happens after I have added or deleted from an array.
This is very confusing because when I add an element I use http to make sure it is saved to the database and then only after the observable with the new droplet is returned do I let angular update the display. And the display always updates. However, sometimes the display updates, but Augury does not realise, which is very odd. So I will add a hint, for example, the hint will be visible in the database, be returned and the view will be updated, but Augury will not see it in the component.
It is also inconsistent. Sometimes is works just fine.
Here is some sample code from the view.
<h4>Hints (optional)</h4>
<button class="btn btn-sm" [routerLink]="['/create/create5']">Add New</button>
<div *ngIf="droplet.hints.length < 1">None</div>
<div class="row" *ngFor="let hint of droplet.hints; let i=index">
<div class="hint col-md-10" (click)="selectHint(i)">
<span [innerHTML]="hint.content || empty"></span>
<span (click)="removeElement(i, 'hint')" class="pull-right glyphicon glyphicon-remove" aria-hidden="true"></span>
</div>
</div>
<h4>Tags
<div class="progress-marker" [class.complete]="droplet.tags.length > 0"></div>
<div class="progress-marker" [class.complete]="droplet.tags.length > 1"></div>
<div class="progress-marker" [class.complete]="droplet.tags.length > 2"></div>
</h4>
<button class="btn btn-sm" [routerLink]="['/create/create6']">Add New</button>
<div *ngIf="droplet.tags.length < 1">None</div>
<br>
<button *ngFor="let tag of droplet.tags; let i=index" type="button" class="btn btn-default btn-sm" (click)="removeElement(i, 'tag')">
<span class="glyphicon glyphicon-remove" aria-hidden="true"></span> {{ tag.tag }}
</button>
There are several fields like this but in general the ones with *ngIf are the ones causing the problems. As I said if I add or remove or edit an element in the array it works and the template is updated, but often nothing in the arrays works after this (though the non arrays work fine).
The relevant component code looks like this:
import { Component, OnInit } from '@angular/core';
import { Droplet } from '../droplet';
import { DropletService } from '../droplet.service';
import { Router } from '@angular/router';
import { Subscription } from 'rxjs/Rx';
import { HttpService } from '../http.service';
export class ShowDropletComponent implements OnInit {
droplet: Droplet;
constructor(
private dropletService: DropletService,
private router: Router,
private httpService: HttpService
) { }
ngOnInit() {
this.droplet = this.dropletService.getCurrentDroplet();
this.dropletService.pushedDroplet.subscribe(
droplet => this.droplet = droplet
)
}
//using dummy to ensure element not updated unless returned from server
removeElement(index, element) {
console.log("remove clicked");
let dummy = this.droplet;
if (element === "explanation") {
this.router.navigate(['create/create3']);
dummy.explanations.splice(index, 1);
} else if (element === "question") {
this.router.navigate(['create/create4']);
dummy.questions.splice(index, 1);
} else if (element === "hint") {
this.router.navigate(['create/create5']);
dummy.hints.splice(index, 1);
} else if (element === "tag") {
dummy.tags.splice(index, 1);
}
this.httpService.saveDroplet(dummy)
.subscribe(
(droplet: Droplet) => {
this.dropletService.updateCurrentDroplet(droplet);
}
);
}
editThis(field) {
if (field === "description") {
this.router.navigate(['create/create2']);
} else if (field === "name") {
this.router.navigate(['create/create1']);
}
}
selectExplanation(index) {
console.log("select exp clicked");
this.router.navigate(['create/create3', index]);
}
selectQuestion(index) {
console.log("rselect q clicked");
this.router.navigate(['create/create4', index]);
}
selectHint(index) {
console.log("select hint clicked");
this.router.navigate(['create/create5', index]);
}
}
My guess is that it's something to do with the *ngFor updating the array in the view but either the index is not updating properly or the click handlers are breaking, but it's not just with those in that specific part of the template. I'm at a loss.
When manipulating items in an
*ngFor
directive, it is important to add atrackBy
function that returns a unique index - so the directive can track them property when removing/adding items.Assuming your
tag.tag
is unique you could write your*ngFor
like this instead: