The change of data in controller doesn't update automatically

I pick an image from album and assign it to a variable in class. But it won’t be updated in my page until I scroll my screen up/down a bit.

For example, my html:

<img [src]='thumbnail' alt="">

My class:

export class NewPostPage {
  constructor(app: IonicApp, nav: NavController, params: NavParams) {
    this.nav = nav;
    this.thumbnail = 'img/placeholder.png';
  }

  imagePick() {
    var self = this;
    return new Promise(function(resolve,reject) {
      window.imagePicker.getPictures(function(results) {
        for (var i = 0; i < results.length; i++ ) {            
          self.thumbnail = results[i];
          return resolve(result[i]);
        }
      }, function(error) {
        return reject(error);
      });
    }
  }

In ionic 1, we probably can call $scopy.$apply. What should I do in ionic 2? Any idea? Thanks.

I would try the async pipe. I’ve not had any experience with it, but I believe this is the correct use case

it would be something along the lines of:
<img [src]='thumbnail | async' alt="">

@Component({
  selector: 'pipes',
  changeDetection: 'ON_PUSH'
})
export class NewPostPage {
  constructor(app: IonicApp, nav: NavController, params: NavParams) {
  this.nav = nav;
  this.thumbnail = 'img/placeholder.png';
 }

See this for more info: https://auth0.com/blog/2015/09/03/angular2-series-working-with-pipes/

Also try not using self as reference to this, i had an incopatibility issue before where self already was a property in that context, so it could collide and generate unexpected behavior.

Also with the ES6 new ‘fat arrow functions’ you don’t need that kind of things any more, you can now just keep using this.

I’m not very familiar with ES6’s fat arrow function. Does that mean I can write something like this?

return new Promise( (resolve,reject) => {

I thought it only looks different.

Not only that, it means this, bye bye self variable :smile: :

// var self = this; No longer needed with fat arrow functions
return new Promise((resolve, reject) => {
  window.imagePicker.getPictures(function(results) {
    for (var i = 0; i < results.length; i++ ) {            
      // self.thumbnail = results[i];  Not any more
      this.thumbnail = results[i]
      return resolve(result[i]);
    }
  }, function(error) {
    return reject(error);
  });
}

Thanks for your reminding. Then can do it even better with:

return new Promise((resolve, reject) => {
  window.imagePicker.getPictures((results) => {
    for (var i = 0; i < results.length; i++ ) {            
      this.thumbnail = results[i]
      return resolve(result[i]);
    }
  }, (error) => {
    return reject(error);
  });
}

Am I right?

Then I got some error messages:

ORIGINAL EXCEPTION: Component ‘NewPostPage’ must have either ‘template’, ‘templateUrl’, or ‘@View’ set.
ORIGINAL STACKTRACE:
Error: Component ‘NewPostPage’ must have either ‘template’, ‘templateUrl’, or ‘@View’ set.

In ionic 2, I only have @Page, any idea? My code:

import {Component} from 'angular2/core';

@Component({
  selector: 'pipes',
  changeDetection: 'ON_PUSH'
})

@Page({
  templateUrl: 'build/pages/post/new-post.html'
})

If I tried this:

@Page({
  selector: 'pipes',
  changeDetection: 'ON_PUSH', 
  templateUrl: 'build/pages/post/new-post.html'
})

then I got another error:

EXCEPTION: Invalid argument ‘img/placeholder.png’ for pipe ‘AsyncPipe’ in [thumbnail | async in NewPostPage@25:13]

The annotations have to be on top of a class, you can’t annotate another annotation:

@Component({...})
class CustomComponent{...}
@Page({...})
class CustomPage{...}

What were you trying to achieve with those 2 annotations without a class?

For this error post also the 5 lines of HTML above and below of the async pipe you’re trying to use.

Sorry, the code is not complete. Here is my code with the class definition.

My class:

import {IonicApp, Page,NavController, NavParams} from 'ionic/ionic';

@Page({
  selector: 'pipes',
  changeDetection: 'ON_PUSH', 
  templateUrl: 'build/pages/post/new-post.html'
})

export class NewPostPage {
  constructor(app: IonicApp, nav: NavController, params: NavParams) {
    this.nav = nav;
    this.thumbnail = 'img/placeholder.png';
  }

  imagePick() {
    return new Promise( (resolve,reject) => {
      window.imagePicker.getPictures((results) => {
        for (var i = 0; i < results.length; i++ ) {
          this.thumbnail = results[i];
          return resolve(result[i]);
        }
      }, (error) => {
        return reject(error);
      });
    }
  }

My html:

<img [src]='thumbnail | async' alt="">

My issue is the thumbnail I got from imagePick function is not updated in my page. (It will only be shown after I scroll my page).

This code after I add ‘async’ does not work because :

EXCEPTION: Invalid argument ‘img/placeholder.png’ for pipe ‘AsyncPipe’ in [thumbnail | async in NewPostPage@25:13]

I have no idea how to attach my thumbnail to async pipe.

Any idea? Thanks

Async accepts either a Promise or an Observable, so passing it a string won’t work, you want a placeholder while the async stuff is done right?

In that case try making an observable which the first next is the placeholder and the next one is the returned by imagePick, i think you could just make the array with the just operator for the placeholder and use from with the promise, then use flatMap operator to join them in one Observable or something like that,

Maybe can also use the Subject class instead, with the first call to next in the constructor and the next just above of the resolve of the promise.

I’m not familiar with Promise, Observable in ionic(angular) 2. Have to study first. Thanks.

The Promise is the one of ES6, the basic one of the new spec, the Observable from Rxjs could be more challenging to understand, basically you need to put a Promise in thumbnail that resolves later to the image source you want.

Basically you could call instead of thumbnail the function imagePick() since it returns a promise and it resolves to the image source.

yes. That is what’s commonly called as lambda.

() => { }

is the same as:

function () { }

so in your case function (resolve, reject) { } is the same as what you wrote.

I went through the Promise and Observable document. But sill have no clue how to solve my issue. I can simplify my imagePick function and even run inside zone.run(), but my problem is still there. I tried a few methods and even change my cordova image pick plugin. Here is one of my method by using image base64 data instead of url:

var options = {
  quality : 75,
  destinationType : Camera.DestinationType.DATA_URL,
  sourceType : sourceType,
  allowEdit : true,
  encodingType: Camera.EncodingType.JPEG,
  popoverOptions: CameraPopoverOptions,
  saveToPhotoAlbum: false
};

navigator.camera.getPicture((imageData)=>{
  zone.run(()=>{
    this.thumbnail = 'data:image/jpeg;base64,' + imageData;     
  });      
}, (error)=>{
  alert(error);    
}, options);

Unfortunately this still doesn’t work although the same method is working fine in my ionic 1 project.

It could be an issue with ionic2 or the version you have. It is still in beta. Or maybe try using src="{{ something here.}}". I wouldn’t use the [src] because that is view to the code. {{ }} is more of a code to the view. Maybe that is you problem???

PS: That use of lambda DOES make a huge difference on readability.

{{ }}: interpolation: code => view
[ ]: property binding: view => code
[(ngModel)]: pretty much goes both way. Dont know how Angular2 calls it: code <==> view

I tried:

<img src='{{thumbnail}}' alt="">

Same result. But it will work if I update thumbnail outside the camera callback function. So the issue is definitely related to the getPicture callback.

Really? it should be easy as PI, you already had a function that returned a promise, you just needed to call imagePick() in your html like this:

<img [src]='imagePick() | async' alt="">

Or just set thumbnail to the Promise:

imagePick() {
  this.thumbnail =  new Promise( (resolve,reject) => {
    window.imagePicker.getPictures((results) => {
      for (var i = 0; i < results.length; i++ ) {
        return resolve(result[i]);
      }
    }, (error) => {
      return reject(error);
    });
  }
}

Doesn’t work. How can you assign a promise to a src of image? I tested your code.

Method 1:

The window.imagePicker.getPictures was repeatedly called.

Method 2:

image

Apparently the src of the image is wrong.