diff --git a/docs/storage/storage.md b/docs/storage/storage.md
index 43b6c5a0b..37eb68a42 100644
--- a/docs/storage/storage.md
+++ b/docs/storage/storage.md
@@ -186,6 +186,16 @@ export class AppComponent {
### Downloading Files
+A convenient pipe exists for simple in page references.
+
+```ts
+@Component({
+ selector: 'app-root',
+ template: `
`
+})
+export class AppComponent {}
+```
+
To download a file you'll need to create a reference and call the `getDownloadURL()` method on an `AngularFireStorageReference`.
```ts
diff --git a/sample/src/app/storage/storage.component.ts b/sample/src/app/storage/storage.component.ts
index b8e3547ee..2dc184ef1 100644
--- a/sample/src/app/storage/storage.component.ts
+++ b/sample/src/app/storage/storage.component.ts
@@ -13,7 +13,8 @@ const TRANSPARENT_PNG
template: `
Storage!
-
+
+
{{ 'google-g.png' | getDownloadURL | json }}
`,
styles: []
diff --git a/src/storage/pipes/storageUrl.pipe.ts b/src/storage/pipes/storageUrl.pipe.ts
new file mode 100644
index 000000000..1aaf2caeb
--- /dev/null
+++ b/src/storage/pipes/storageUrl.pipe.ts
@@ -0,0 +1,39 @@
+import { AsyncPipe } from '@angular/common';
+import { ChangeDetectorRef, NgModule, OnDestroy, Pipe, PipeTransform } from '@angular/core';
+import { Observable } from 'rxjs';
+import { AngularFireStorage } from '../storage';
+
+/** to be used with in combination with | async */
+@Pipe({
+ name: 'getDownloadURL',
+ pure: false,
+})
+export class GetDownloadURLPipe implements PipeTransform, OnDestroy {
+
+ private asyncPipe: AsyncPipe;
+ private path: string;
+ private downloadUrl$: Observable;
+
+ constructor(private storage: AngularFireStorage, cdr: ChangeDetectorRef) {
+ this.asyncPipe = new AsyncPipe(cdr);
+ }
+
+ transform(path: string) {
+ if (path !== this.path) {
+ this.path = path;
+ this.downloadUrl$ = this.storage.ref(path).getDownloadURL();
+ }
+ return this.asyncPipe.transform(this.downloadUrl$);
+ }
+
+ ngOnDestroy() {
+ this.asyncPipe.ngOnDestroy();
+ }
+
+}
+
+@NgModule({
+ declarations: [ GetDownloadURLPipe ],
+ exports: [ GetDownloadURLPipe ],
+})
+export class GetDownloadURLPipeModule {}
diff --git a/src/storage/public_api.ts b/src/storage/public_api.ts
index a2848a409..460348fa1 100644
--- a/src/storage/public_api.ts
+++ b/src/storage/public_api.ts
@@ -3,3 +3,4 @@ export * from './storage';
export * from './task';
export * from './observable/fromTask';
export * from './storage.module';
+export * from './pipes/storageUrl.pipe';
diff --git a/src/storage/storage.module.ts b/src/storage/storage.module.ts
index 4169db29f..c6181645f 100644
--- a/src/storage/storage.module.ts
+++ b/src/storage/storage.module.ts
@@ -1,7 +1,9 @@
import { NgModule } from '@angular/core';
+import { GetDownloadURLPipeModule } from './pipes/storageUrl.pipe';
import { AngularFireStorage } from './storage';
@NgModule({
+ exports: [ GetDownloadURLPipeModule ],
providers: [ AngularFireStorage ]
})
export class AngularFireStorageModule { }
diff --git a/src/storage/storage.spec.ts b/src/storage/storage.spec.ts
index 083d4b645..2f11fde08 100644
--- a/src/storage/storage.spec.ts
+++ b/src/storage/storage.spec.ts
@@ -1,26 +1,45 @@
-import { forkJoin } from 'rxjs';
+import { forkJoin, from } from 'rxjs';
import { mergeMap, tap } from 'rxjs/operators';
import { TestBed } from '@angular/core/testing';
import { AngularFireModule, FIREBASE_APP_NAME, FIREBASE_OPTIONS, FirebaseApp } from '@angular/fire';
import { AngularFireStorage, AngularFireStorageModule, AngularFireUploadTask, BUCKET } from './public_api';
import { COMMON_CONFIG } from '../test-config';
-import 'firebase/storage';
import { rando } from '../firestore/utils.spec';
+import { GetDownloadURLPipe } from './pipes/storageUrl.pipe';
+import { ChangeDetectorRef } from '@angular/core';
+import 'firebase/storage';
+
+if (typeof XMLHttpRequest === 'undefined') {
+ globalThis.XMLHttpRequest = require('xhr2');
+}
+
+const blobOrBuffer = (data: string, options: {}) => {
+ if (typeof Blob === 'undefined') {
+ return Buffer.from(data, 'utf8');
+ } else {
+ return new Blob([JSON.stringify(data)], options);
+ }
+};
describe('AngularFireStorage', () => {
let app: FirebaseApp;
let afStorage: AngularFireStorage;
+ let cdr: ChangeDetectorRef;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
AngularFireModule.initializeApp(COMMON_CONFIG, rando()),
- AngularFireStorageModule
+ AngularFireStorageModule,
+ ],
+ providers: [
+ ChangeDetectorRef
]
});
app = TestBed.inject(FirebaseApp);
afStorage = TestBed.inject(AngularFireStorage);
+ cdr = TestBed.inject(ChangeDetectorRef);
});
afterEach(() => {
@@ -39,101 +58,96 @@ describe('AngularFireStorage', () => {
expect(afStorage.storage.app).toBeDefined();
});
- // TODO tests for node?
- if (typeof Blob !== 'undefined') {
-
- describe('upload task', () => {
-
- it('should upload and delete a file', (done) => {
- const data = { angular: 'fire' };
- const blob = new Blob([JSON.stringify(data)], { type: 'application/json' });
- const ref = afStorage.ref('af.json');
- const task = ref.put(blob);
- task.snapshotChanges()
- .subscribe(
- snap => {
- expect(snap).toBeDefined();
- },
- done.fail,
- () => {
- ref.delete().subscribe(done, done.fail);
- });
- });
-
- it('should upload a file and observe the download url', (done) => {
- const data = { angular: 'fire' };
- const blob = new Blob([JSON.stringify(data)], { type: 'application/json' });
- const ref = afStorage.ref('af.json');
- ref.put(blob).then(() => {
- const url$ = ref.getDownloadURL();
- url$.subscribe(
- url => {
- expect(url).toBeDefined();
- },
- done.fail,
- () => {
- ref.delete().subscribe(done, done.fail);
- }
- );
- });
- });
+ describe('upload task', () => {
+
+ it('should upload and delete a file', (done) => {
+ const data = { angular: 'fire' };
+ const blob = blobOrBuffer(JSON.stringify(data), { type: 'application/json' });
+ const ref = afStorage.ref('af.json');
+ const task = ref.put(blob);
+ task.snapshotChanges()
+ .subscribe(
+ snap => {
+ expect(snap).toBeDefined();
+ },
+ done.fail,
+ () => {
+ ref.delete().subscribe(done, done.fail);
+ });
+ });
- it('should resolve the task as a promise', (done) => {
- const data = { angular: 'promise' };
- const blob = new Blob([JSON.stringify(data)], { type: 'application/json' });
- const ref = afStorage.ref('af.json');
- const task: AngularFireUploadTask = ref.put(blob);
- task.then(snap => {
- expect(snap).toBeDefined();
- done();
- }).catch(done.fail);
+ it('should upload a file and observe the download url', (done) => {
+ const data = { angular: 'fire' };
+ const blob = blobOrBuffer(JSON.stringify(data), { type: 'application/json' });
+ const ref = afStorage.ref('af.json');
+ ref.put(blob).then(() => {
+ const url$ = ref.getDownloadURL();
+ url$.subscribe(
+ url => {
+ expect(url).toBeDefined();
+ },
+ done.fail,
+ () => {
+ ref.delete().subscribe(done, done.fail);
+ }
+ );
});
-
});
- describe('reference', () => {
+ it('should resolve the task as a promise', (done) => {
+ const data = { angular: 'promise' };
+ const blob = blobOrBuffer(JSON.stringify(data), { type: 'application/json' });
+ const ref = afStorage.ref('af.json');
+ const task: AngularFireUploadTask = ref.put(blob);
+ task.then(snap => {
+ expect(snap).toBeDefined();
+ done();
+ }).catch(done.fail);
+ });
- it('it should upload, download, and delete', (done) => {
- const data = { angular: 'fire' };
- const blob = new Blob([JSON.stringify(data)], { type: 'application/json' });
- const ref = afStorage.ref('af.json');
- const task = ref.put(blob);
- // Wait for the upload
- forkJoin([task.snapshotChanges()])
- .pipe(
- // get the url download
- mergeMap(() => ref.getDownloadURL()),
- // assert the URL
- tap(url => expect(url).toBeDefined()),
- // Delete the file
- mergeMap(() => ref.delete())
- )
- // finish the test
- .subscribe(done, done.fail);
- });
+ });
- it('should upload, get metadata, and delete', (done) => {
- const data = { angular: 'fire' };
- const blob = new Blob([JSON.stringify(data)], { type: 'application/json' });
- const ref = afStorage.ref('af.json');
- const task = ref.put(blob, { customMetadata: { blah: 'blah' } });
- // Wait for the upload
- forkJoin([task.snapshotChanges()])
- .pipe(
- // get the metadata download
- mergeMap(() => ref.getMetadata()),
- // assert the URL
- tap(meta => expect(meta.customMetadata).toEqual({ blah: 'blah' })),
- // Delete the file
- mergeMap(() => ref.delete())
- )
- // finish the test
- .subscribe(done, done.fail);
- });
+ describe('reference', () => {
+
+ it('it should upload, download, and delete', (done) => {
+ const data = { angular: 'fire' };
+ const blob = blobOrBuffer(JSON.stringify(data), { type: 'application/json' });
+ const ref = afStorage.ref('af.json');
+ const task = ref.put(blob);
+ // Wait for the upload
+ forkJoin([task.snapshotChanges()])
+ .pipe(
+ // get the url download
+ mergeMap(() => ref.getDownloadURL()),
+ // assert the URL
+ tap(url => expect(url).toBeDefined()),
+ // Delete the file
+ mergeMap(() => ref.delete())
+ )
+ // finish the test
+ .subscribe(done, done.fail);
+ });
+ it('should upload, get metadata, and delete', (done) => {
+ const data = { angular: 'fire' };
+ const blob = blobOrBuffer(JSON.stringify(data), { type: 'application/json' });
+ const ref = afStorage.ref('af.json');
+ const task = ref.put(blob, { customMetadata: { blah: 'blah' } });
+ // Wait for the upload
+ forkJoin([task.snapshotChanges()])
+ .pipe(
+ // get the metadata download
+ mergeMap(() => ref.getMetadata()),
+ // assert the URL
+ tap(meta => expect(meta.customMetadata).toEqual({ blah: 'blah' })),
+ // Delete the file
+ mergeMap(() => ref.delete())
+ )
+ // finish the test
+ .subscribe(done, done.fail);
});
- }
+ });
});
@@ -193,7 +207,7 @@ describe('AngularFireStorage w/options', () => {
it('it should upload, download, and delete', (done) => {
const data = { angular: 'fire' };
- const blob = new Blob([JSON.stringify(data)], { type: 'application/json' });
+ const blob = blobOrBuffer(JSON.stringify(data), { type: 'application/json' });
const ref = afStorage.ref('af.json');
const task = ref.put(blob);
// Wait for the upload