Angular5: Need to prevent ngbTooltip from rendering before data is returned from observable

Viewed 2431

I'm new to angular 2+, and inherited an incomplete project. My goal is to create a tooltip that checks for audit details on a particular field, and displays them. An example might be:

"Updated by Seth, on 8/18/18."

To do this I've created a componentin angular that passes all relevant information back via a service. I can see that the service call is working as expecte by looking in the web debugger. Unfortunately, what is rendering is a tooltip with the following incomplete text:

 "updated by , " 

Here is my component template.

<ng-template #tipContent>
<span *ngIf="loadCompleted">Updated By {{ auditDetail.updatedBy }}, {{ auditDetail.updatedAt | amTimeAgo }}</span>
</ng-template>
<i [ngbTooltip]="tipContent" (mouseenter)="refreshAuditDetail()" class="fas fa-info-circle fa-sm"></i>

Here is the component typescript.

@Component({
 selector: 'audit-tooltip',
 templateUrl: './audit-tooltip.component.html',
 styleUrls: ['./audit-tooltip.component.css']
})
export class AuditTooltipComponent {
 @Input() plan: Plan;
 @Input() fieldName: string;
 auditDetail: AuditDetail;
 loadCompleted: boolean = false;

constructor(private planService: PlanService) { }

refreshAuditDetail() {
 var planId = this.plan.id;
 var fieldName = this.fieldName;
 var fieldValue = this.plan[this.fieldName];
 this.loadCompleted = false;


this.planService.getAuditDetails(planId, fieldName, fieldValue)
  .subscribe((auditDetail: AuditDetail) => {
    console.log(auditDetail);
    this.auditDetail = auditDetail;
  }, () => {}, () => this.loadCompleted = true);
}
}

And, if necessary, here is where I'm using it.

  <h5 class="card-title">{{fieldLabel}}<audit-tooltip [fieldName]="fieldName" [plan]="plan"></audit-tooltip> </h5>

What am I missing?

3 Answers

Look into using OnInit lifecycle hook which is processed first when a component is loaded.

Documentation For ngOnInit

Thats simple enough, then move that call to the service as seen below into the life cycle hook.

Give the class a public variable ie

public toolTipData: string;

(moved into OnInit)

this.planService.getAuditDetails(planId, fieldName, fieldValue)
   .subscribe((auditDetail: AuditDetail) => {
    this.tooTipData = auditDetail.toolTipInfo // (guessing at name)
    this.auditDetail = auditDetail;

    this.loadCompleted = true);
  },
  (errors) => 
  { 
    console.log(errors) // api error status code.
  }
});

In the HTML set the tool tip value to

[ngbTooltip]="toolTipData"

This should help populate the tool tip appropriately.

The asynchronous changes aren't showing up because NgbTooltip window uses ChangeDetectionStrategy.OnPush and the only way to trigger change detection is calling open() on a closed tooltip.

In order to access the NgbTooltip API:

Add a template reference to NgbTooltip

    <i #tooltip [ngbTooltip]="tipContent" (mouseenter)="refreshAuditDetail()" class="fas fa-info-circle fa-sm"></i>

Bring the reference into the component

    @ViewChild('tooltip') tooltip: NgbTooltip;

Hacky Solution

Close and re-open tooltip once the data is loaded. This could cause issues if the user already moved off of the tooltip by the time the data is loaded.

     refreshAuditDetail() {
      ...
      this.planService.getAuditDetails(planId, fieldName, fieldValue)
        .subscribe((auditDetail: AuditDetail) => {
          this.auditDetail = auditDetail;
          this.tooltip.close();
          this.tooltip.open();
        }, () => {}, () => this.loadCompleted = true);
      }
    }

Better Solution

Set triggers="manual". Call this.tooltip.open() in refreshAuditDetails .Add a (mouseleave) event which cancels the subscription from refreshAuditDetails and calls this.tooltip.close().

Unfortunately - the answer wasn't anything to do with timing. I overlooked a casing issue 50x before I posted. While trying to implement the suggestions I received, I finally noticed the problem.

The HttpResponse was returning

{{UpdatedBy: XX, UpdatedAt: YY}) whereas AuditDetail had updatedBy and updatedAt as properties. it was failing to map, which made it look like what i thought was a timing issue.

Related