1

I am loading data from an image from html using * ngFor.

To load the image, another function is performed, which needs the ID that is loaded in * ngFor, to find the image with that ID.

To get the image, I used (click) where I pass the product ID, but the goal is to load the images without using the click, ie automatically.

What I have

HTML

 <div class="row tab-pane Galeria">
        <div *ngFor="let product of products" class="col-xl-2 col-lg-3 col-md-4 col-sm-6">
          <div class="image-item" (click)="fillModal($event, product)">
              <a class="d-block image-block h-100">
              <homeImage> 
                    <img *ngIf [src]="Images" class="Images img-fluid" alt="">                  
              </homeImage> 
              </a>           
            <div class="ImageText" (click)="ImageInfo(product.id)">{{product.name}}</div>
          </div>
        </div>
      </div>

Component.ts

ngOnInit() {
    this.GetProducts();
  }

  GetProducts() {
    let self = this;
    this.Global.refreshToken().subscribe(function (result) {
      self.homeService.getProducts().then(function (resultado) {
        if (resultado) {
         self.products = resultado;
        }
      }).catch();
    }); 
  }


  ImageInfo(id) {
      var self = this;
      self.Global.refreshToken().subscribe(function (result) {
        self.homeService.getImage(id).then(function (resultado) {
          if (resultado) {
            self.Images = resultado;
          }
        }).catch();
      });
    }

Service.ts

getProducts(): Promise<any> {
    let self = this;
    let urlAux = self.url + "/Products/GetProducts";
    return axios.get(urlAux, {
      headers: {
        Authorization: 'Bearer ' + localStorage.getItem("access_token")
      }
    })
      .then(this.extraData)
      .catch(this.handleErroPromisse);
  }

  getImage(id): Promise<any> {
    let self = this;
    let urlAux = self.url + "/Products/GetImage/" + id;

    return axios.get(urlAux, {'responseType': 'arraybuffer'})
      .then((response) => {
        let image = btoa(
          new Uint8Array(response.data)
            .reduce((data, byte) => data + String.fromCharCode(byte), '')
        );
        return `data:${response.headers['content-type'].toLowerCase()};base64,${image}`;
      });
  }

What I tested and didn't work:

Does not work execute the function in a src :(

 <div class="row tab-pane Galeria">
        <div *ngFor="let product of products" class="col-xl-2 col-lg-3 col-md-4 col-sm-6">
          <div class="image-item" (click)="fillModal($event, product)">
              <a class="d-block image-block h-100">
              <homeImage> 
                    <img *ngIf [src]="ImageInfo(product.id)" class="Images img-fluid" alt="">                  
              </homeImage> 
              </a>           
            <div class="ImageText">{{product.name}}</div>
          </div>
        </div>
      </div>

2 Answers 2

2

The action you're performing is asynchronous (it returns a Promise).

That's why you either need to make sure the attribute is flagged to wait for an asynchronous action, or make the action 'less asynchronous' :-).

Do the processing up-front

If you immediately parse the images in your typescript, you can skip the async part of the function and do the same thing in the component.ts file.

Component.ts

ngOnInit() {
  this.GetProducts();
}

GetProducts() {
  let self = this;
  this.Global.refreshToken()
    .subscribe(function (result) {
      self.homeService.getProducts()
        .then(function (resultado) {
          if (resultado) {
            self.products = resultado;
          }
        })
        .then(() => {
          if (self.products) {
            return Promise.all(self.products.map((product) => self.ImageInfo(product.id)));
          }
        })
        .catch((err) => console.error(err));
    });
}


ImageInfo(id) {
  var self = this;
  self.Global.refreshToken().subscribe(function (result) {
    self.homeService.getImage(id).then(function (resultado) {
      if (resultado) {
        // self.Images = resultado;

        self.images[id] = resultado; // <-- changed to id so you can fetch easily
      }
    }).catch();
  });
}

HTML

 <div class="row tab-pane Galeria">
  <div *ngFor="let product of products" class="col-xl-2 col-lg-3 col-md-4 col-sm-6">
    <div class="image-item" (click)="fillModal($event, product)">
        <a class="d-block image-block h-100">
        <homeImage> 
              <img *ngIf="images[product.id]" [src]="images[product.id]" class="Images img-fluid" alt="">                  
        </homeImage> 
        </a>           
      <div class="ImageText">{{product.name}}</div>
    </div>
  </div>
</div>

Now, I'm just showing the image if we already fetched the data when we fetched the products. That way, the async action happens in the component.ts and the HTML is kept synchronous.

The main difference between the two solutions is if you want to use prefetching or not.

Sign up to request clarification or add additional context in comments.

7 Comments

The first, does not work, enters an infinite loop. The second solution is too close to work, I just get the error: Cannot read property '1009' of undefined at line <img * ngIf = "images [product.id]" [src] = "images [product.id]" class = "Images img-fluid" alt = "">
It was the only person who came close to the solution and I really appreciate it, I leave my contribution
@John 1st does not work because your function doesn't return anything. If you use [src]="ImageInfo(product.id) | async" async or not... the function has to return something, a Promise<string> in async case or string in a sync case. In your case it returns undefined.
Sorry but did not realize, what should I change even for this error to disappear? I updated the post with my service.ts
Example 1 does not work, putting the function in src stops working. I posted this but got the same error <img * ngIf = "images [product.id]" [src] = "ImageInfo (product.id) | async" class = "Images img-fluid" alt = "">
|
1

It doesn't work because your ImageInfo(id) doesn't return anything that could be assign to the img src. It loads image info and stores the resultado into this.Images. But in the template you bind the return value of the function to src.

What you probably want to do is to load all products in ngOnInit and then load all ImageInfos for all products (assuming that your API is working like this...)

Something like:

async ngOnInit() {
    this.products = await this.service.loadProducts();
    for (const p of this.product) {
       this.images[p.id] = await this.imageService.getInfo(p.id));
    }
}

and

<div *ngFor="let product of products">
  <img *ngIf="images[product.id]" [src]="images[product.id].src" > 
</div>

2 Comments

I get this error: Cannot read property '1009' of undefined at line <img * ngIf = "images [product.id]" [src] = "images [product.id]" class = "Images img-fluid" alt = "". Thanks to try help me
@John Initialize images in the constructor or add *ngIf="images && images[product.id]

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.