AngularFire / FireStore - getDownloadURL - firebase-storage

What I'm using
AngularFire
FireStore
New AngularFire syntax for Firestore
What I'm trying to do
get the download URL for each returned image
Issue
I'm getting the download URL for the first image and applying it to a global variable.
If four images are in storage, four are returned, but will all be the same image, opposed to four unique ones
Question
I'm using the new AngularFire Firestore syntax to fetch the data
How would I loop through each of the returned images and bind them to my HTML?
Component TS
getIssueImages() {
this.albumsCollection = this.afs.collection<any>(/albums/${albumId}/images`);
this.albums = this.albumsCollection.snapshotChanges().map(actions => {
return actions.map(a => {
const data = a.payload.doc.data();
const id = a.payload.doc.id;
console.log(data);
// Firebase Reference
var storage = firebase.storage();
// Get the image storage reference
var image = data.image_thumbnail;
//Create an image reference to the storage location
var imagePathReference = storage.ref().child(image);
// Get the download URL and set the local variable to the result (url)
imagePathReference.getDownloadURL().then((url) => {
this.image_thumbnail = url;
});
return { id, ...data };
});
});
}
Component.HTML
As expected, this correctly returns the unique storage references of each image, but not the unique download URL references.
<ul>
<li *ngFor="let image of images | async">
{{ image.image_thumbnail }}
</li>
</ul>
**Component.HTML - Incorrect **
So this one is trying to reference the firebase storage downloadURL that i'm trying to fetch from my function in my component.ts. As expected, I'm not looping through each one, an instead probably getting just the first item. As a result, all four images returned are the same.
<ul>
<li *ngFor="let image of images | async">
{{ image_thumbnail }}
</li>
</ul>
UPDATE
Image update based on #James Daniels response.

The main problem that you are facing is the async nature of getting the download URL. You can solve that with an Observable.fromPromise.
getIssueImages() {
this.albumsCollection = this.afs.collection<any>(/albums/${albumId}/images`);
this.albums = this.albumsCollection.snapshotChanges().map(actions => {
return actions.map(a => {
const data = a.payload.doc.data();
const id = a.payload.doc.id;
// Firebase Reference
var storage = firebase.storage();
// Get the image storage reference
var image = data.image_thumbnail;
//Create an image reference to the storage location
var imagePathReference = storage.ref().child(image);
// Get the download URL and set the local variable to the result (url)
var image_thumbnail = Observable.fromPromise(imagePathReference.getDownloadURL());
return { id, image_thumbnail, ...data };
});
});
}
Now image_thumbnail is async.
<ul>
<li *ngFor="let image of images | async">
{{ image.image_thumbnail | async }}
</li>
</ul>

Related

How to Implement a <ion-searchbar>in Ionic app to filter Firebase data?

How do I Implement a Search Bar in the Ionic app to filter Firebase data? I want to implement a to filter data as user types in. I am not able to get it done. I have seen several tutorials but still unable to achieve them. Any help will be appreciated.
this is my list.page.html
<ion-searchbar (ionChange)="search($event.target.value)"></ion-searchbar>
<ion-list class="bg-transparent " lines="none" *ngFor="let user of usersArrayFiltered" color="none" >
<ion-item color="none"> <ion-card>
<ion-card-content>
<h2>{{user.name}}</h2>
<p>{{user.email}}</p>
<p>₹ {{user.mobile}}</p>
</ion-card-content>
</ion-card>
</ion-item>
</ion-list>
</div>
</ion-content>
this is my list.page.ts
export class HomePage implements OnInit {
UsersArray = [];
usersArrayFiltered =[];
constructor(
private apiService: UserService
) { }
ngOnInit() {
this.fetchBookings();
let bookingRes = this.apiService.getUserList();
bookingRes.snapshotChanges().subscribe(res => {
this.UsersArray = [];
res.forEach(item => {
let a = item.payload.toJSON();
a['$key'] = item.key;
this.UsersArray.push(a as User);
this.usersArrayFiltered = [...this.UsersArray];
})
})
}
search(query) {
if (!query) { // revert back to the original array if no query
this.usersArrayFiltered = [...this.UsersArray];
} else { // filter array by query
this.usersArrayFiltered = this.UsersArray.filter((user) => {
return (user.name.includes(query) || user.email.includes(query) || user.phone.includes(query));
})
}
}
fetchBookings() {
this.apiService.getUserList().valueChanges().subscribe(res => {
console.log('Fetched users list!')
})
}
}
Here's a basic example. I would have two arrays – the original array usersArray which gets left unfiltered, and a modifiable array usersArrayFiltered which gets filters applied to it. You will bind to the filtered array in your HTML.
Right after you populate your usersArray with data in your ngOnOnit hook, assign usersArrayFiltered as a copy of the original array.
// just populated usersArray
this.usersArrayFiltered = [...this.usersArray];
Now create a search method that takes in the search query as a parameter. If the query is an empty string, then re-assign usersArrayFiltered as a copy of the original usersArray. If it's not an empty string, then turn usersArrayFiltered into a filtered array of usersArray by only including objects that have values which contain the query string.
search(query) {
if (!query) { // revert back to the original array if no query
this.usersArrayFiltered = [...this.usersArray];
} else { // filter array by query
this.usersArrayFiltered = this.usersArray.filter((user) => {
return (user.name.includes(query) || user.email.includes(query) || user.phone.includes(query));
})
}
}
Listen to value changes on your <ion-searchbar> and pass the value of it to your search function.
<ion-searchbar (ionChange)="search($event.target.value)"></ion-searchbar>
Bind your *ngFor loop to the filtered array.
<ion-list *ngFor="let user of usersArrayFiltered">
...
</ion-list>

Ionic Application not able to update dynamically with database

I'm connecting ionic application with MySql database using PHP, all functionalities are working fine but when i upload data in database it is taking atleast hour of time for data updation in ionic application, Please find sample code for the same:
I havent used any sessions and when loading component every time will fire request to fetch data from dabase using PHP, tried placing ngZone but still issue remains same.
this.zone.run(() => {
this.http
.get('http://localhost:8100/dbcon/retreive-monthcircular.php')
.subscribe((monthdata : any) =>
{
console.dir(monthdata);
this.loadData = false;
this.circularmonthdata = monthdata;
if (this.circularmonthdata == null) {
this.displayCircular = false;
} else {
this.displayCircular = true;
}
},
(error : any) =>
{
console.dir(error);
});
});
Ideally Application should dynamically update
Look at this example
HTML :
<ion-item>
<ion-label>Date</ion-label>
<ion-datetime displayFormat="DD/MM/YYYY" pickerFormat="DD/MM/YYYY" [(ngModel)]="myDate"></ion-datetime>
</ion-item>
<button ion-button block (click)="getData(myDate)">Get Data</button>
Bellow is your current data from array we created.
<ion-item *ngFor="let data of fetchedData">
Date: {{data.date}} - Description: {{data.description}}
</ion-item>
your TS:
fetchedData = [ // suppose your data looks like this
{
date: '20-02-1990',
description: 'this is First date'
},
{
date: '21-03-1991',
description: 'this is Second date'
}
]
getData(myDate){
this.headers = {'Content-Type': 'application/json'};
this.http.get('http://localhost:8100/dbcon/retreive-monthcircular.php', {headers: this.headers})
.map(res => res.json())
.subscribe(data => {
console.log(data);
this.fetchedData = data.data; // update your variable it will update data on your view.
});
}
by clicking on this function will update your data at DOM. Or you should post your HTML and fetched Data from server.

Select multiple images without Image-Picker plugin ionic

I am searching for an alternative to Ionic's image-picker plugin or some guidance on using it to attach a file to a Form for upload. The api that I am using requires that file be uploaded via form. Thoughts or suggestions are much appreciated. The feature of multiple file selection is the most important part of this.
You can use standard web api (file input) to achieve this and use "multiple" as attribute.
Your template:
<button ion-button>
<ion-icon name="image"></ion-icon>
<input multiple type="file” (change)="loadImageFromDevice($event)" accept="image/png, image/jpeg">
</button>
Your ts:
myImages: Array<string>;
...
loadImageFromDevice(event) {
const files = event.target.files;
const blobReader = new FileReader();
files.forEach(file => {
blobReader.readAsArrayBuffer(file);
blobReader.onload = () => {
let blob: Blob = new Blob([new Uint8Array((blobReader.result as ArrayBuffer))]);
let blobURL: string = URL.createObjectURL(blob);
this.myImages.push(blobURL);
};
blobReader.onerror = (error) => {
console.log(error);
};
})
};
I am facing same issue
use this code to choose file in Ionic HTML
<ion-input type="file" (change)="changeListener($event)"></ion-input>
Ts file
changeListener(event) {
var reader = new FileReader();
reader.readAsDataURL(event.target.files[0]);
reader.onload = (_event) => {
this.imgURL = reader.result;
//base64 image
console.log("Image File =>", this.imgURL);
}
}
Then make file controller array and make one button to increase it then you can make multiple file controls to handle and upload to server.
check this for reference
http://masteringionic.com/blog/2018-02-06-dynamically-add-and-remove-form-input-fields-with-ionic/

Couldn't manipulate Images.find() in CollectionFS for MeteorJS app

My app is sort of like TelescopeJS, but a lot simpler. I'm trying to echo the particular image that has been added in the post-adding form which takes an input of the name of the post, picture, categories and description. It has 2 collections, one for Articles and the other for Images (NOT a mongo collection, it's an FS collection.) The articles collection stores the name,description and category name and the other one stores image. **My Problem is: ** in the FS collection doc, the loop
{{#each images}}
<img src="{{this.url}}" alt="" class="thumbnail" />
{{/each}}
Where images: returns Images.find({}) and my articles code is :
{{#each articles}}
<li style="margin-right: 1%;">{{>article}}</li>
{{/each}}
Where articles: returns Articles.find({})
MY articles template HAS the images loop and this causes ALL THE IMAGES in the collection to be shown in one post. I just want specific images to be shown for the specific post.
These are the events:
'change .img': function(event, template) {
FS.Utility.eachFile(event, function(file) {
Images.insert(file, function (err, fileObj) {
//Inserted new doc with ID fileObj._id, and kicked off the data upload using HTTP
});
});
},
'click .save':function(evt,tmpl){
var description = tmpl.find('.description').value;
var name = tmpl.find('.name').value;
var date=new Date();
var cat = tmpl.find('.selectCat').value;
Articles.insert({
description:description,
name:name,
time:date.toLocaleDateString()+' at '+date.toLocaleTimeString(),
author:Meteor.userId(),
userEmail:Meteor.user().username,
category:cat,
});
}
<template name="article">
{{#each images}}
<img src="{{this.url}}" alt="" class="thumbnail" />
{{/each}}
Here goes the {{name_of_post}}
Here {{the_category}}
Here {{the_description}}
</template>
So what happens is, all the images that I've uploaded so far shows in one post and all the posts' picture looks the same. Help please!
You should know that fsFile support Metadata so maybe you don't need the Articles Collection
So we can make a new eventHandler.
'click .save':function(evt,tmpl){
var description = tmpl.find('.description').value,
file = $('#uploadImagePost').get(0).files[0], //here we store the current file on the <input type="file">
name = tmpl.find('.name').value,
date=new Date(),
cat = tmpl.find('.selectCat').value,
fsFile = new FS.File(file); // we create an FS.File instance based on our file
fsFile.metadata = { //this is how we add Metadata aka Text to our files
description:description,
name:name,
time:date.toLocaleDateString()+' at '+date.toLocaleTimeString(),
author:Meteor.userId(),
userEmail:Meteor.user().username,
category:cat,
}
Images.insert(fsFile,function(err,result){
if(!err){
console.log(result) // here you should see the new fsFile instance
}
});
}
This is how our new event will look, now our .save button insert everything on the same collection.
This is how we can access to the FS.File instances fields using the keyword 'metadata.fieldName'.
For example.
Teamplate.name.helpers({
showCategory:function(){
// var category = Session.get('currentCategory') you can pass whatever data
// you want here from a select on the html or whatever.
//lets say our var its equal to 'Music'
return Images.find({'metadata.category':category});
}
})
Now we use that helper on the html like any normal collection
<template name="example">
{{#each showCategory}}
Hi my category is {{metadata.category}} <!-- we access the metadata fields like any normal field on other collection just remember to use the 'metadata'keyword -->
This is my image <img src="{{this.url}}" >
{{/each}}
</template>

Confused as to how the template render action is called

I am trying to make the songs in a playlist appear on screen each time a user enters a song of choice. I have the following action to insert the song that they chose into the database:
Template.search_bar.events({
'keypress #query' : function (evt,template) {
// template data, if any, is available in 'this'
if (evt.which === 13){
var url = template.find('#query').value;
$("#query").val('');
$('#playlist_container').animate({scrollTop: $('#playlist_container')[0].scrollHeight});
Template.list.search_get(url,0); //insert records into the database
}
}
});
Template.list.search_get inserts the record into the database:
Meteor.call('update_record',Template.list.my_playlist_id, song, function(err,message){});
on the server side, I am pushing records into my database with the following format:
update_record: function(sessID, songObj){
Links.update({sess: sessID}, {$push: {songs: {song_title: songObj["title"], videoId: songObj["video_id"], thumbnail: songObj["thumbnail"], index: songObj["index"]}}});
},
basically all my records have the format of:
{_id:,
sess:,
songs: [{song_title:,
videoId:,
thumbnail:,
index:},
{song_title:,
videoId:,
thumbnail:,
index:},...]
}
An array of song objects inside the songs field of the record. What I am trying to do is each time a user hits the search button, make that new song appear in the list. I am not sure how many times the render function gets called or how template renders a database object in hmtl. Currently i have the following html template for my list:
<template name="list">
<div id="playlist_container">
<ul id="playlist">
{{#each my_playlist.songs}}
{{> track}}
{{/each}}
</ul>
</div>
I believe my_playlist should call the following action on the client:
Template.list.my_playlist = function(){
console.log("myplaylist is called");
return Links.findOne({sess: Template.list.my_playlist_id});
}
It should return an object which contains an array of song object, for which i iterate through in #each my_playlist.songs, which should render each of the following track template:
<template name="track">
<li id="{{index}}" class="list_element">
<div class="destroy"> </div>
<div class="element_style">{{song_title}}</div>
</li>
</template>
However, upon successful insertion of record, i am not seeing the new song title appear. Any suggestions on how I might approach this?
This code is the problem.
Template.list.my_playlist = function(){
return Links.findOne({sess: Template.list.my_playlist_id});
}
Template.list.my_playlist_id never updates, and thus, the new template never renders.
Try this approach.
if (Meteor.isServer) {
Meteor.methods({
update_record: function(sessID, songObj){
// update Links
return Links.findOne({sess: sessID});
}
});
} else {
Meteor.call('update_record',Template.list.my_playlist_id, song, function(err, song){
Session.set('playlist', song);
});
Template.list.my_playlist = function(){
return Session.get('playlist');
}
}