Skip to content
This repository was archived by the owner on Mar 17, 2025. It is now read-only.

Commit 9881546

Browse files
authored
Merge pull request #865 from firebase/storage
feat(storage): Firebase Storage for AngularFire
2 parents c22ef72 + ce4f392 commit 9881546

File tree

11 files changed

+679
-10
lines changed

11 files changed

+679
-10
lines changed

src/module.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,17 @@
55
angular.module("firebase.config", []);
66
angular.module("firebase.auth", ["firebase.utils"]);
77
angular.module("firebase.database", ["firebase.utils"]);
8+
angular.module("firebase.storage", ["firebase.utils"]);
89

910
// Define the `firebase` module under which all AngularFire
1011
// services will live.
11-
angular.module("firebase", ["firebase.utils", "firebase.config", "firebase.auth", "firebase.database"])
12+
angular.module("firebase", [
13+
"firebase.utils",
14+
"firebase.config",
15+
"firebase.auth",
16+
"firebase.database",
17+
"firebase.storage"
18+
])
1219
//TODO: use $window
1320
.value("Firebase", exports.firebase)
1421
.value("firebase", exports.firebase);

src/storage/FirebaseStorage.js

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
(function() {
2+
"use strict";
3+
4+
/**
5+
* Take an UploadTask and create an interface for the user to monitor the
6+
* file's upload. The $progress, $error, and $complete methods are provided
7+
* to work with the $digest cycle.
8+
*
9+
* @param task
10+
* @param $firebaseUtils
11+
* @returns A converted task, which contains methods for monitoring the
12+
* upload progress.
13+
*/
14+
function _convertTask(task, $firebaseUtils) {
15+
return {
16+
$progress: function $progress(callback) {
17+
task.on('state_changed', function () {
18+
$firebaseUtils.compile(function () {
19+
callback(_unwrapStorageSnapshot(task.snapshot));
20+
});
21+
});
22+
},
23+
$error: function $error(callback) {
24+
task.on('state_changed', null, function (err) {
25+
$firebaseUtils.compile(function () {
26+
callback(err);
27+
});
28+
});
29+
},
30+
$complete: function $complete(callback) {
31+
task.on('state_changed', null, null, function () {
32+
$firebaseUtils.compile(function () {
33+
callback(_unwrapStorageSnapshot(task.snapshot));
34+
});
35+
});
36+
},
37+
$cancel: task.cancel,
38+
$resume: task.resume,
39+
$pause: task.pause,
40+
then: task.then,
41+
catch: task.catch,
42+
$snapshot: task.snapshot
43+
};
44+
}
45+
46+
/**
47+
* Take an Firebase Storage snapshot and unwrap only the needed properties.
48+
*
49+
* @param snapshot
50+
* @returns An object containing the unwrapped values.
51+
*/
52+
function _unwrapStorageSnapshot(storageSnapshot) {
53+
return {
54+
bytesTransferred: storageSnapshot.bytesTransferred,
55+
downloadURL: storageSnapshot.downloadURL,
56+
metadata: storageSnapshot.metadata,
57+
ref: storageSnapshot.ref,
58+
state: storageSnapshot.state,
59+
task: storageSnapshot.task,
60+
totalBytes: storageSnapshot.totalBytes
61+
};
62+
}
63+
64+
/**
65+
* Determines if the value passed in is a Firebase Storage Reference. The
66+
* put method is used for the check.
67+
*
68+
* @param value
69+
* @returns A boolean that indicates if the value is a Firebase Storage
70+
* Reference.
71+
*/
72+
function _isStorageRef(value) {
73+
value = value || {};
74+
return typeof value.put === 'function';
75+
}
76+
77+
/**
78+
* Checks if the parameter is a Firebase Storage Reference, and throws an
79+
* error if it is not.
80+
*
81+
* @param storageRef
82+
*/
83+
function _assertStorageRef(storageRef) {
84+
if (!_isStorageRef(storageRef)) {
85+
throw new Error('$firebaseStorage expects a Storage reference');
86+
}
87+
}
88+
89+
/**
90+
* This constructor should probably never be called manually. It is setup
91+
* for dependecy injection of the $firebaseUtils and $q service.
92+
*
93+
* @param {Object} $firebaseUtils
94+
* @param {Object} $q
95+
* @returns {Object}
96+
* @constructor
97+
*/
98+
function FirebaseStorage($firebaseUtils, $q) {
99+
100+
/**
101+
* This inner constructor `Storage` allows for exporting of private methods
102+
* like _assertStorageRef, _isStorageRef, _convertTask, and _unwrapStorageSnapshot.
103+
*/
104+
var Storage = function Storage(storageRef) {
105+
_assertStorageRef(storageRef);
106+
return {
107+
$put: function $put(file, metadata) {
108+
var task = storageRef.put(file, metadata);
109+
return _convertTask(task, $firebaseUtils);
110+
},
111+
$putString: function $putString(data, format, metadata) {
112+
var task = storageRef.putString(data, format, metadata);
113+
return _convertTask(task, $firebaseUtils);
114+
},
115+
$getDownloadURL: function $getDownloadURL() {
116+
return $q.when(storageRef.getDownloadURL());
117+
},
118+
$delete: function $delete() {
119+
return $q.when(storageRef.delete());
120+
},
121+
$getMetadata: function $getMetadata() {
122+
return $q.when(storageRef.getMetadata());
123+
},
124+
$updateMetadata: function $updateMetadata(object) {
125+
return $q.when(storageRef.updateMetadata(object));
126+
},
127+
$toString: function $toString() {
128+
return storageRef.toString();
129+
}
130+
};
131+
};
132+
133+
Storage.utils = {
134+
_unwrapStorageSnapshot: _unwrapStorageSnapshot,
135+
_isStorageRef: _isStorageRef,
136+
_assertStorageRef: _assertStorageRef
137+
};
138+
139+
return Storage;
140+
}
141+
142+
/**
143+
* Creates a wrapper for the firebase.storage() object. This factory allows
144+
* you to upload files and monitor their progress and the callbacks are
145+
* wrapped in the $digest cycle.
146+
*/
147+
angular.module('firebase.storage')
148+
.factory('$firebaseStorage', ["$firebaseUtils", "$q", FirebaseStorage]);
149+
150+
})();
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/* istanbul ignore next */
2+
(function () {
3+
"use strict";
4+
5+
function FirebaseStorageDirective($firebaseStorage, firebase) {
6+
return {
7+
restrict: 'A',
8+
priority: 99, // run after the attributes are interpolated
9+
scope: {},
10+
link: function (scope, element, attrs) {
11+
// $observe is like $watch but it waits for interpolation
12+
// any value passed as an attribute is converted to a string
13+
// if null or undefined is passed, it is converted to an empty string
14+
// Ex: <img firebase-src="{{ myUrl }}"/>
15+
attrs.$observe('firebaseSrc', function (newFirebaseSrcVal) {
16+
if (newFirebaseSrcVal !== '') {
17+
var storageRef = firebase.storage().ref(newFirebaseSrcVal);
18+
var storage = $firebaseStorage(storageRef);
19+
storage.$getDownloadURL().then(function getDownloadURL(url) {
20+
element[0].src = url;
21+
});
22+
}
23+
});
24+
}
25+
};
26+
}
27+
FirebaseStorageDirective.$inject = ['$firebaseStorage', 'firebase'];
28+
29+
angular.module('firebase.storage')
30+
.directive('firebaseSrc', FirebaseStorageDirective);
31+
})();

tests/automatic_karma.conf.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ module.exports = function(config) {
1010
singleRun: true,
1111

1212
preprocessors: {
13-
"../src/*.js": "coverage",
13+
"../src/!(lib)/**/*.js": "coverage",
1414
"./fixtures/**/*.json": "html2js"
1515
},
1616

tests/initialize.js

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,15 @@ if (window.jamsine) {
22
jasmine.DEFAULT_TIMEOUT_INTERVAL = 5000;
33
}
44

5-
var config = {
6-
apiKey: 'AIzaSyC3eBV8N95k_K67GTfPqf67Mk1P-IKcYng',
7-
authDomain: 'oss-test.firebaseapp.com',
8-
databaseURL: 'https://oss-test.firebaseio.com',
9-
storageBucket: 'oss-test.appspot.com'
10-
};
11-
firebase.initializeApp(config);
5+
try {
6+
// TODO: stop hard-coding this
7+
var config = {
8+
apiKey: "AIzaSyCcB9Ozrh1M-WzrwrSMB6t5y1flL8yXYmY",
9+
authDomain: "oss-test.firebaseapp.com",
10+
databaseURL: "https://oss-test.firebaseio.com",
11+
storageBucket: "oss-test.appspot.com"
12+
};
13+
firebase.initializeApp(config);
14+
} catch (err) {
15+
console.log('Failed to initialize the Firebase SDK [web]:', err);
16+
}

tests/protractor/upload/logo.png

49.7 KB
Loading
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<!DOCTYPE html>
2+
<html ng-app="upload">
3+
<head>
4+
<title>AngularFire Upload e2e Test</title>
5+
6+
<!-- Angular -->
7+
<script src="../../../node_modules/angular/angular.min.js"></script>
8+
9+
<!-- Firebase -->
10+
<script src="../../../node_modules/firebase/firebase.js"></script>
11+
12+
<!-- AngularFire -->
13+
<script src="../../../dist/angularfire.js"></script>
14+
15+
<!-- Initialize the Firebase SDK -->
16+
<script src="../../initialize.js"></script>
17+
18+
</head>
19+
20+
<body ng-controller="UploadCtrl">
21+
<div ng-show="!isUploading">
22+
<input type="file" onchange="angular.element(this).scope().select(this)">
23+
<button id="submit" ng-click="upload()">Submit</button>
24+
</div>
25+
26+
<div ng-show="isUploading" id="uploading">
27+
{{(metadata.bytesTransferred / metadata.totalBytes)*100}}%<br />
28+
</div>
29+
30+
<br />
31+
<div ng-show="metadata.downloadURL" id="url">{{metadata.downloadURL}}</div>
32+
33+
<div ng-show="error">
34+
{{ error | json }}
35+
</div>
36+
37+
<!-- Custom JS -->
38+
<script src="upload.js" defer></script>
39+
</body>
40+
</html>

tests/protractor/upload/upload.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
var app = angular.module('upload', ['firebase.storage']);
2+
3+
app.controller('UploadCtrl', function Upload($scope, $firebaseStorage, $timeout) {
4+
// Create a reference
5+
const storageRef = firebase.storage().ref('user/1.png');
6+
// Create the storage binding
7+
const storageFire = $firebaseStorage(storageRef);
8+
9+
var file;
10+
11+
$scope.select = function (event) {
12+
file = event.files[0];
13+
}
14+
15+
$scope.upload = function() {
16+
$scope.isUploading = true;
17+
$scope.metadata = {bytesTransferred: 0, totalBytes: 1};
18+
$scope.error = null;
19+
20+
// upload the file
21+
const task = storageFire.$put(file);
22+
23+
// monitor progress state
24+
task.$progress(metadata => {
25+
$scope.metadata = metadata;
26+
});
27+
// log a possible error
28+
task.$error(error => {
29+
$scope.error = error;
30+
});
31+
// log when the upload completes
32+
task.$complete(metadata => {
33+
$scope.isUploading = false;
34+
$scope.metadata = metadata;
35+
});
36+
37+
}
38+
39+
});
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
var protractor = require('protractor');
2+
var firebase = require('firebase');
3+
var path = require('path');
4+
require('../../initialize-node.js');
5+
6+
describe('Upload App', function () {
7+
// Reference to the Firebase which stores the data for this demo
8+
var firebaseRef;
9+
10+
// Boolean used to load the page on the first test only
11+
var isPageLoaded = false;
12+
13+
var flow = protractor.promise.controlFlow();
14+
15+
function waitOne() {
16+
return protractor.promise.delayed(500);
17+
}
18+
19+
function sleep() {
20+
flow.execute(waitOne);
21+
}
22+
23+
function clearFirebaseRef() {
24+
var deferred = protractor.promise.defer();
25+
26+
firebaseRef.remove(function (err) {
27+
if (err) {
28+
deferred.reject(err);
29+
} else {
30+
deferred.fulfill();
31+
}
32+
});
33+
34+
return deferred.promise;
35+
}
36+
37+
beforeEach(function (done) {
38+
if (!isPageLoaded) {
39+
isPageLoaded = true;
40+
41+
browser.get('upload/upload.html').then(function () {
42+
return browser.waitForAngular()
43+
}).then(done)
44+
} else {
45+
done()
46+
}
47+
});
48+
49+
it('loads', function () {
50+
expect(browser.getTitle()).toEqual('AngularFire Upload e2e Test');
51+
});
52+
53+
it('uploads a file', function (done) {
54+
var fileToUpload = './upload/logo.png';
55+
var absolutePath = path.resolve(__dirname, fileToUpload);
56+
57+
$('input[type="file"]').sendKeys(absolutePath);
58+
$('#submit').click();
59+
60+
var el = element(by.id('url'));
61+
browser.driver.wait(protractor.until.elementIsVisible(el))
62+
.then(function () {
63+
return el.getText();
64+
}).then(function (text) {
65+
var result = "https://firebasestorage.googleapis.com/v0/b/oss-test.appspot.com/o/user%2F1.png";
66+
expect(text.slice(0, result.length)).toEqual(result);
67+
done();
68+
});
69+
});
70+
});

0 commit comments

Comments
 (0)