1

So I noticed a pretty weird effect on my bootstrap modals in combination with Angular 4, where the previously opened image pops in first before loading the newly selected image from my gallery. The reason is the value for my details variable is still populated with the old value from the previous (click) event. I can verify this in my console logs. My question is how do I reset this variable in my getArtworkDetail() method before giving it a new value?

I tried setting it like

this.details = undefined;

or

this.details = null;

but it does't seem to register the value at all and instead prevents the modal from loading. My colleague said this is due to the single-threaded nature of TypeScript, but my research on the topic came up short, so I was hoping for some help here. Much obliged!

index.component.html

<!--SEARCH-->

<div class="input-group">
  <input #myInput
         [(ngModel)]="searchTerm"
         type="text"
         class="form-control"
         placeholder="Search artist or artwork..."
         (keyup.enter)="search()"/>
  <span class="input-group-btn">
    <button #myBtn
            (click)="search()"
            class="btn btn-default"
            type="button">Go!
    </button>
  </span>
</div>

<!--INDEX-->

<div class="row"
     *ngIf="hasArtPieces">
  <div *ngFor="let artwork of artworks"
       class="col-md-3 col-xs-12 centered">
    <div class="card">
      <a (click)="getArtworkDetail(artwork.id)"
         data-toggle="modal"
         data-target="#myModal">
        <img class="img-fluid thumb-img"
             src="{{artwork.url}}"
             alt="Thumbnail">
      </a>
      <div class="card-block">
        <p style="text-align: center"
           *ngIf="artwork.title.length < 20; then show else hide">
        </p>
        <ng-template #show>{{artwork.title }}</ng-template>
        <ng-template #hide>{{artwork.title.substring(0,20)}}...</ng-template>
      </div>
    </div>
    <br>
  </div>
</div>

<!--DETAIL-->

<div *ngIf="details"
     class="modal fade"
     id="myModal" tabindex="-1"
     role="dialog"
     aria-labelledby="myModalLabel">
  <div class="modal-dialog"
       role="document">
    <div class="modal-content">
      <div class="modal-header">
        <button type="button"
                class="close"
                data-dismiss="modal"
                aria-label="Close">
          <span aria-hidden="true">&times;</span>
        </button>
      </div>
      <div class="modal-body">
        <h1>{{details.title}}</h1>
        <h3>{{details.artist}}</h3>
        <img class="img-fluid" src="{{details.url}}"
             alt=""><br><br>
        <p>{{details.description}}</p>
      </div>
      <div class="modal-footer">
        <button type="button"
                class="btn btn-default"
                data-dismiss="modal">Close
        </button>
      </div>
    </div>
  </div>
</div>

index.component.ts

// imports emitted    

@Component({
  selector: 'artwork-index',
  templateUrl: './index.component.html',
  styleUrls: ['./index.component.css']
})


export class IndexComponent implements OnInit {
  artworks: ArtworkModel[];
  searchTerm: string;
  details: ArtworkDetailModel;
  hasArtPieces = false;

  constructor(private detailService: ArtworkDetailsService, private artworksService: ArtworksService) {
  }

  search() {
    if (this.searchTerm) {
      this.searchArtworks(this.searchTerm);
    }
  }

  searchArtworks(keyword) {
    this.artworksService.searchArtworks(keyword)
      .subscribe((artworkData: ArtworkModel[]) => {
          this.artworks = artworkData;
          this.hasArtPieces = true;
        }, err => console.log(err),
        () => console.log('Getting artworks complete...'));
  }

  getArtworkDetail(id) {
    this.detailService.getArtworkDetail(id)
      .subscribe((detailData: ArtworkDetailModel) => {
          this.details = detailData;
        }, err => console.log(err),
        () => console.log('Getting details complete...'));
  }

  ngOnInit() {
    this.searchArtworks('Rembrandt');
  }
}
Florestan Korp
  • 662
  • 2
  • 12
  • 24

2 Answers2

2

What appears to be happening for you is that the data-toggle="modal" anchor element (with (click)="getArtworkDetail(artwork.id)") triggers the modal before the request for the artwork detail has completed.

Try these two things:

  1. Remove the *ngIf="details" directive from the modal div. Instead, put *ngIf="details" on an inner element -- perhaps the .modal-body element or a container div inside of that element.
    • This will allow the thumbnail button to trigger the modal without having to wait for the artwork detail to load
  2. Add this.details = null; back to getArtworkDetail as the first line of the function. This will clear out all of the old detail information as the new detail information is loading.

You will likely want to display a spinner until that request has completed at some point. In that case, you can use your *ngIf="details" directive to provide a spinner while the details value is null: *ngIf="details; else spinner".


The single-threaded nature that your colleague referred to is actually with regards to JavaScript, rather than TypeScript, since TypeScript gets compiled to JavaScript before being executed. JavaScript effectively has runs as a single-threaded event-loop (although this is not guaranteed), but certain tasks triggered from these events may occur asynchronously. One such asynchronous task is an XMLHttpRequest. Since this occurs asynchronously, the script does not have to wait for it to complete before attempting to display the modal, which is where your problem seems to lie.

Mike Hill
  • 3,622
  • 23
  • 27
1

As Mike Hill pointed out the solution was to add the *ngIf to an inner element and set my this.details = null which didn't work, but this.details = undefined worked so it ended up looking like this with the *ngif on the modal-body:

index.component.html

...

<!--DETAIL-->

<div
  class="modal fade"
  id="myModal" tabindex="-1"
  role="dialog"
  aria-labelledby="myModalLabel">
  <div class="modal-dialog"
       role="document">
    <div class="modal-content">
      <div class="modal-header">
        <button type="button"
                class="close"
                data-dismiss="modal"
                aria-label="Close">
          <span aria-hidden="true">&times;</span>
        </button>
      </div>
      <div class="modal-body"
           *ngIf="details">
        <h1>{{details.title}}</h1>
        <h3>{{details.artist}}</h3>
        <img class="img-fluid" src="{{details.url}}"
             alt=""><br><br>
        <p>{{details.description}}</p>
      </div>
      <div class="modal-footer">
        <button type="button"
                class="btn btn-default"
                data-dismiss="modal">Close
        </button>
      </div>
    </div>
  </div>
</div>

index.component.ts

...

  getArtworkDetail(id) {
    this.details = undefined;
    this.detailService.getArtworkDetail(id)
      .subscribe((detailData: ArtworkDetailModel) => {
          this.details = detailData;
        }, err => console.log(err),
        () => console.log('Getting details complete...'));
  }
Florestan Korp
  • 662
  • 2
  • 12
  • 24