4
votes

I'm trying to extract a string value from the Observable using the pipe and map, how ever I always got in the end an empty string. I hope that someone can help me to understand the reason behind this issue.

I have an http service that get a file from the back-end:

getFiles(url: string, idProject: number, docProject: string): Observable<any> { 
return this.http.get(`${this.hostUrl}${url}/${idProject}/${docProject}`); 
  }

I call the getFiles(...) like that:

  showFile = false;
  fileUploads: Observable<string[]>;
.
.
.
showFiles(enable: boolean) {
    this.showFile = enable;

    if (enable) {
this.uploadService.getFiles('/projets', this.service.getProjet().idProjet,
  'documentProjets/pv/'//+ td.nomTypeDoc +'/getallfiles')
            .pipe( 
                  map( response => {
                  return response.slice(
                         response.indexOf("files"), 
                         response.lastIndexOf("?"))
                         })).subscribe(
                           (data) => {
                            this.fileUploads=data
                            console.log(data)},
                           (error) => console.log(error));
}
}

The result before the Map is:

http://localhost:8080/api/v1/pv/files/file1.jpg?projetId=563, http://localhost:8080/api/v1/pv/files/file2.jpg?projetId=563 

and after the MAp is: an empty array.

The HTML:

<button class="button btn-info" *ngIf='showFile' (click)='showFiles(false)'>Hide Files</button>

<button class="button btn-info" *ngIf='!showFile' (click)='showFiles(true)'>Show Files</button> 


<div [hidden]="!showFile">
  <div class="panel panel-primary">
    <div class="panel-heading">Liste des fichiers</div>

    <div *ngFor="let file of fileUploads | async">  
      <div class="panel-body">
        <app-details-upload [fileUpload]='file'></app-details-upload>
      </div>
    </div>
  </div>
</div>

I don't know why I get as result an empty value ' ' and this is the error from my consol:

ListUploadComponent.html:3 ERROR Error: InvalidPipeArgument: '' for pipe 'AsyncPipe' at invalidPipeArgumentError (common.js:3981) at AsyncPipe.push../node_modules/@angular/common/fesm5/common.js.AsyncPipe._selectStrategy (common.js:4590) at AsyncPipe.push../node_modules/@angular/common/fesm5/common.js.AsyncPipe._subscribe (common.js:4580) at AsyncPipe.push../node_modules/@angular/common/fesm5/common.js.AsyncPipe.transform (common.js:4562) at Object.eval [as updateDirectives] (ListUploadComponent.html:10) at Object.debugUpdateDirectives [as updateDirectives] (core.js:11054) at checkAndUpdateView (core.js:10451) at callViewAction (core.js:10692) at execComponentViewsAction (core.js:10634) at checkAndUpdateView (core.js:10457)

Thank you in advance.

1
You’re subscribing and using the async pipe. Do one or the other, not both. Also use the $ notation for observable streamsAndrew Allen
Is showFiles called in ngOnInit (or alternatively ngOnChanges / ngDoCheck)? These lifecycle hooks are called before the component renders, hence need to be used to define the fileUploads$ observable before render.Andrew Allen

1 Answers

3
votes

The async pipe takes an Observable and subscribes for you in the template and unsubscribes for you on component destruction. Keep to the convention of using a $ on the end of observable streams variables.

The following

<div *ngFor="let v of values$ | async">
  {{v}}
</div>

is equivalent to

<div *ngFor="let v of values">
  {{v}}
</div>

where in your ts file you do this

this.values$.subscribe(values => this.values = values)


In your example you can either remove the async pipe from the template or remove the subscription and assign the stream.

this.fileUploads$ = this.uploadService.getFiles('/projets', this.service.getProjet().idProjet,
  'documentProjets/pv/'//+ td.nomTypeDoc +'/getallfiles')
            .pipe( map( response => {
                  return response.slice(
                         response.indexOf("files"), 
                         response.lastIndexOf("?"))
                         }))

If you need to console.log for debugging use the tap RxJS pipeable operator.


Update - after debugging steps

The tap operator does not affect the stream (it can be used for side effects like navigation or logging) so it tells you exactly what the next operator in the pipe stream is getting...an array of strings. So type for this as follows...

this.fileUploads$ = this.yourService.pipe(
    // tap(console.log),
    map((responseUrls: string[]) => {
        return responseUrls.map((url: string) => {
           // some function that 
           // uses string methods
           // or regex
           // to return what you
           // want e.g.
           return url.slice(
               url.indexOf(‘files’),
               url.indexOf(‘?’)
           )
        })
    })
    // }),
    // tap(console.log)
)