ngShow expression is evaluated too early

54 Views Asked by At

I have a angular component and controller that look like this:

export class MyController{

    static $inject = [MyService.serviceId];

    public elements: Array<string>;
    public errorReceived : boolean;

    private elementsService: MyService;

    constructor(private $elementsService: MyService) {
        this.errorReceived = false;
        this.elementsService= $elementsService;
    }

    public $onInit = () => {
        this.elements =  this.getElements();
        console.log("tiles: " + this.elements);
    }


    private getElements(): Array<string> {
        let result: Array<string> = [];
        this.elementsService.getElements().then((response) => {
            result = response.data;
            console.log(result);
        }).catch(() => {
            this.errorReceived = true;
        });
        console.log(result);
        return result;
    }
}

export class MyComponent implements ng.IComponentOptions {
    static  componentId = 'myId';

    controller = MyController;
    controllerAs = 'vm';

    templateUrl = $partial => $partial.getPath('site.html');
}

MyService implementation looks like this:

export class MyService {

    static serviceId = 'myService';

    private http: ng.IHttpService;

    constructor(private $http: ng.IHttpService) {
        this.http = $http;
    }

    public getElements(): ng.IPromise<{}> {
        return this.http.get('./rest/elements');
    }

}

The problem that I face is that the array elements contains an empty array after the call of onInit(). However, later, I see that data was received since the success function in getELements() is called and the elements are written to the console.

elements I used in my template to decide whether a specific element should be shown:

<div>
    <elements ng-show="vm.elements.indexOf('A') != -1"></elements>
</div>

The problem now is that vm.elements first contains an empty array, and only later, the array is filled with the actual value. But then this expression in the template has already been evaluated. How can I change that?

1

There are 1 best solutions below

0
On

Your current implementation doesn't make sense. You need to understand how promises and asynchronous constructs work in this language in order to achieve your goal. Fortunately this isn't too hard.

The problem with your current implementation is that your init method immediately returns an empty array. It doesn't return the result of the service call so the property in your controller is simply bound again to an empty array which is not what you want.

Consider the following instead:

export class MyController {
   elements: string[] = [];

   $onInit = () => {
     this.getElements()
       .then(elements => {
         this.elements = elements;
       });
  };

  getElements() {
    return this.elementsService
      .getElements()
      .then(response => response.data)
      .catch(() => {
        this.errorReceived = true;
      });
   } 
}

You can make this more readable by leveraging async/await

export class MyController {
   elements: string[] = [];

   $onInit = async () => {
     this.elements = await this.getElements();
   };

   async getElements() {
     try {
       const {data} = await this.elementsService.getElements();
       return data;
     } 
     catch {
       this.errorReceived = true;
     }
   } 
}

Notice how the above enables the use of standard try/catch syntax. This is one of the many advantages of async/await.

One more thing worth noting is that your data services should unwrap the response, the data property, and return that so that your controller is not concerned with the semantics of the HTTP service.