0

I have a service, that returns Observables<SubscriberModel[]>.

export interface SubscriberModel {
    email: string;
}

in my component I get subscribers$ in constructor:

public subscribers$: Observable<SubscriberModel[]>;
this.subscribers$ = this.subscribersService.getAll();

and show their in template:

<p *ngFor="let subscriber of subscribers$ | async">{{subscriber.email}}</p>
<button (click)="onDownloadClick()">
    <a [href]="fileUrl" download="file.txt">DownloadFile</a>
</button>

I have to download my subscribers into file:

public data: any;

public onDownloadClick(): void {
    this.subscribers$.pipe(
        map(res => res.map(i => i.email)),
    )
        .subscribe(res => {
            console.log(res) //array of string
            this.data = res;
        });

    console.log(this.data); //undefined
    const blob: Blob =
        new Blob([this.data], {type: 'application/octet-stream'});

    this.fileUrl =
        this.sanitizer.bypassSecurityTrustResourceUrl(window.URL.createObjectURL(blob));
}

Why in console.log this.data is undefined?

2
  • Observables work asynchronously. Therefore the code following your subscription runs before the subscription finishes. Commented May 27, 2020 at 19:09
  • Tell me please, how can I fix that? Commented May 27, 2020 at 19:15

2 Answers 2

4

put your process related service's subscribe's inside. Async functions doesn't stop another process. So code goes on with next line while it is still executed. so outside of async function may not read data due to response of server.

public onDownloadClick(): void {
   this.subscribers$.pipe( map(res => res.map(i => i.email)),)
   .subscribe(res => {
       console.log(res) //array of string
       this.data = res;
       console.log(this.data); //undefined
       const blob: Blob = new Blob([res], {type: 'application/octet-stream'});       
       this.fileUrl = this.sanitizer.bypassSecurityTrustResourceUrl(window.URL.createObjectURL(blob));
    });

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

12 Comments

In this case file.txt have an html, not subscribers email value
Observables work asynchronously so it doesn't matter where data comes. You need to take inside subscribe
It works correct only if I pass res const blob: Blob = new Blob([res]], {type: 'application/octet-stream'}); inside subscribe
if u directly assign res to blob then you don't need to assignt to this.data
oh, sorry, with res I have the same problems. The value of file is correct only after the second button click(
|
2

this.data is assigned data asynchronously. As such, all the dependent statements must be inside the subscription. So the console.log should be inside the subscription

public data: any;

public onDownloadClick(): void {
  this.subscribers$.pipe(
    map(res => res.map(i => i.email)),
  )
  .subscribe(res => {
    console.log(res) //array of string
    this.data = res;
    console.log(this.data);
    const blob: Blob = new Blob([this.data], {type: 'application/octet-stream'});
    this.fileUrl = this.sanitizer.bypassSecurityTrustResourceUrl(window.URL.createObjectURL(blob));
  });
}

More info on accessing asynchronous data: https://stackoverflow.com/a/14220323/6513921

2 Comments

But in this case value in file.txt is undefined and only if I click the second time to download button, the file includes correct value
The other statements were also dependent on the async data. They should be moved inside the subscription as well. I've updated the answer.

Your Answer

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.