8

What I'm trying to accomplish here is a simple idea.

  1. Upload file to Firebase Storage

  2. Grab the link to the file and insert it in the form.

Problem is, I can't get the download URL.

When I upload something, it does upload, but I get this error message:

Object { code_: "storage/object-not-found", message_: "Firebase Storage: Object 'rnmgm3vvpz' does not exist.", serverResponse_: "{\n  \"error\": {\n    \"code\": 404,\n    \"message\": \"Not Found.  Could not get object\"\n  }\n}", name_: "FirebaseError" }

And this is the code to upload on component.ts:

upload(event) {
  const id = Math.random().toString(36).substring(2);
  this.ref = this.afStorage.ref(id);
  this.task = this.ref.put(event.target.files[0]);
  this.uploadState = this.task.snapshotChanges().pipe(map(s => s.state));
  this.uploadProgress = this.task.percentageChanges();
  this.downloadURL = this.ref.getDownloadURL();
}

And on component.html:

<input type="file" (change)="upload($event)" accept=".png,.jpg" />

How can I grab the downloadURL after the file is uploaded?

Rosenberg
  • 2,424
  • 5
  • 33
  • 56
  • Try this answer how it works at this point of time [https://stackoverflow.com/a/57267424/11127383](https://stackoverflow.com/a/57267424/11127383) – Daniel Danielecki Jul 30 '19 at 13:48
  • The object is not found because the downloadURL call should be made after the file has finished loading. Remove it from the uploadEvent function and add it in the finalize function as mentioned by @dAxx_ – PatrickS Sep 16 '21 at 13:43

6 Answers6

7

You should add a finalize() to the pipe, something like:

this.task.snapshotChanges().pipe(
  finalize(() => {
    this.downloadURL = this.ref.getDownloadURL(); // <-- Here the downloadURL is available.
  })
).subscribe();

In the finalize() step, the downloadURL is available, so u can grab him from the ref asynchronously. --UPDATE
You said you are using Angular 6, so I assume you are using the last version of firebase.
They change getDownloadURL() to Observable from Task, So to get the actual URL you just have to subscribe.

this.task.snapshotChanges().pipe(
  finalize(() => {
    this.ref.getDownloadURL().subscribe(url => {
      console.log(url); // <-- do what ever you want with the url..
    });
  })
).subscribe();
dAxx_
  • 2,210
  • 1
  • 18
  • 23
  • 1
    `StorageReference.getDownloadUrl()` return a `Task` and not the actual URL. You might want to update your answer to show how to add a completion listener to the task and get the actual URL from it. – Frank van Puffelen Jul 19 '18 at 19:54
  • @FrankvanPuffelen Precisely what I'm getting. It's a good answer but it doesn't get me the downloadURL. – Rosenberg Jul 19 '18 at 20:02
  • @Rosenberg, If you are using the latest firebase, getDownloadURL() is not a task but an Observable. I edit my post for the answer. good luck.. – dAxx_ Jul 19 '18 at 20:20
  • Ah... you're using AngularFire. It'd indeed be an observable there, which also neatly gets rid of/encapsulates the async behavior. – Frank van Puffelen Jul 19 '18 at 23:35
1
this.angularFireStorage.upload("path_name", file).then(rst => {
        rst.ref.getDownloadURL().then(url => {
          console.log(url);
        })
      })

this is the answer. don't want to call twice. my package versions are

"@angular/fire": "^5.1.2", "firebase": "^5.9.1"

Udeesha Induwara
  • 605
  • 1
  • 10
  • 20
1

ref.getDownloadURL() has to be called after task.snapshotChanges() completes.

Option 1: You can use concat and defer the execution of ref.getDownloadURL()

concat(
  this.task.snapshotChanges().pipe(ignoreElements()) // ignore snapshot changes
  defer(() => this.ref.getDownloadURL()) // execute getDownloadURL when snapshot changes completed
).subscribe(url => console.log(url));

Option 2: switchMap to ref.getDownloadURL() after task.snapshotChanges() completes.

this.task.snapshotChanges().pipe(
  last(),  // emit the last element after task.snapshotChanges() completed
  switchMap(() => this.ref.getDownloadURL())
).subscribe(url => console.log(url))
frido
  • 13,065
  • 5
  • 42
  • 56
0

The beginning of @dAxx_'s answer is correct, but then, nesting subscriptions is pretty much bad practice.

See this StackOverflow answer Is it good way to call subscribe inside subscribe?

Now, the AngularFire docs are fairly clear, except maybe for the use of the getDownloadURL pipe in the following example

@Component({
 selector: 'app-root',
 template: `<img [src]="'users/davideast.jpg' | getDownloadURL" />`
 })
 export class AppComponent {}

They could have mentioned that you need the following import in the relevant module

import { GetDownloadURLPipeModule } from '@angular/fire/compat/storage';

...

imports: [
  GetDownloadURLPipeModule
 ],...

but, the simple answer if that you probably don't need the download URL.

I'm not sure what the reasoning behind it is, but the Firebase team has provided us with enough ways to display the uploaded image, without having to care about the download URL string.

To start with, getDownloadURL is an Observable so you can display your image like so

...
finalize(() => {
      this.downloadURL = this.ref.getDownloadURL()
...


<img *ngIf="downloadURL | async as imgUrl"  [src]="imgUrl" alt="">

alternatively, you can use the first solution I mentioned above, using the file path & the getDownloadURL pipe

Finally, you can use the following example as seen in the Angular Fire docs

   @Component({
   selector: 'app-root',
    template: `<img [src]="profileUrl | async" />`
    })
   export class AppComponent {
     
     profileUrl: Observable<string | null>;
     constructor(private storage: AngularFireStorage) {
       const ref = this.storage.ref('users/davideast.jpg');
       this.profileUrl = ref.getDownloadURL();
    }
   }

in which case you would only need to care about the file path, if, for instance, you needed to save a reference to the uploaded image in a database.

PatrickS
  • 9,539
  • 2
  • 27
  • 31
-1

Try this, works for me

task.snapshotChanges()
    .pipe(
          finalize(() => {
            this.downloadURL = fileRef.getDownloadURL();
            this.downloadURL.subscribe(downloadURLResponse => {
               console.log('downloadURL', downloadURLResponse);
            });
          }),
     )
    .subscribe();
Naing Lin Aung
  • 3,373
  • 4
  • 31
  • 48
Andy Torres
  • 189
  • 1
  • 11
-1

Here is a full approach with all the imports.

import { AngularFireStorage, AngularFireStorageReference, AngularFireUploadTask } from '@angular/fire/storage';
import {finalize} from 'rxjs/operators';

constructor(private afStorage:AngularFireStorage) { }

yourfile:File;

onsubmit(){
const id = Math.random().toString(36).substring(2);
const fileRef:AngularFireStorageReference=this.afStorage.ref("YourDir").child(id);
const task: AngularFireUploadTask =fileRef.put(this.yourfile);
task.snapshotChanges().pipe(
    finalize(() => {
        fileRef.getDownloadURL().subscribe(downloadURL => {
          this.profileurl=downloadURL;
            console.log(downloadURL);
        });
  })
).subscribe();
}