I'm new in Firebase. I'm using Firestore database and Ionic, I have this problem with an asynchronous call and I can't solve it. Basically in the item variable goes the data that I have saved in the firestore database. But when I want to show them, through a button, in a new html page a strange thing happens, in the url the passed parameter appears and disappears immediately and nothing works anymore. I had a similar problem in the past that I solved using the angular pipe "async" , but in this case it doesn't even work.
In detail, I have a list of items in a component:
ngOnInit() {
this.itemService.getItemsList().subscribe((res)=>{
this.Tasks = res.map((t) => {
return {
id: t.payload.doc.id,
...t.payload.doc.data() as TODO
};
})
});
}
and in item.service.ts I have defined the function:
constructor(
private db: AngularFireDatabase,
private ngFirestore: AngularFirestore,
private router: Router
) { }
getItemsList() {
return this.ngFirestore.collection('items').snapshotChanges();
}
getItem(id: string) {
return this.ngFirestore.collection('items').doc(id).valueChanges();
}
For each item I have a button to show the detail:
<ion-card *ngFor="let item of Tasks" lines="full">
....
<ion-button routerLinkActive="tab-selected" [routerLink]="['/tabs/item/',item.id]" fill="outline" slot="end">View</ion-button>
In component itemsDescription.ts I have:
ngOnInit() {
this.route.params.subscribe(params => {
this.id = params['id'];
});
this.itemService.getItem(this.id).subscribe((data)=>{
this.item=data;
});
}
Finally in html page:
<ion-card-header>
<ion-card-title>{{item.id}}</ion-card-title>
</ion-card-header>
<ion-icon name="pin" slot="start"></ion-icon>
<ion-label>{{item.Scadenza.toDate() | date:'dd/MM/yy'}}</ion-label>
<ion-card-content>{{item.Descrizione}}</ion-card-content>
The Scadenza and Descrizione information are shown, instead id is not. Also the url should be tabs/items/:id but when I click on the button to show the item information, the passed parameter immediately disappears and only tabs/items is displayed. If I remove the data into {{}}, the parameter from the url doesn't disappear
SOLVED
I followed this guide https://forum.ionicframework.com/t/async-data-to-child-page-with-ionic5/184197. So putting ? , for example {{item?.id}} now everything works correctly
Your nested code order not right. You will get the value of id after subscription.
Check this Code:
ngOnInit() {
this.route.params.subscribe(params => {
this.id = params['id'];
this.profileService.getItem(this.id).subscribe((data)=>{
this.item=data;
});
});
}
Related
I have an app that saves items in an sqlite database and shows them in a list view.
It loads the listview with the correct data at the beginning of the app, but it doesn't refreshes the listview when I add a new item to the database.
This is the component.ts where I load the items to the observable
export class HomeComponent implements OnInit {
items: ObservableArray<IDataItem>;
constructor(public _itemService: DataService) {
}
ngOnInit(): void {
this.items = this._itemService.selectItems();
}
}
This is the DataService:
export class DataService {
private items = new ObservableArray<IDataItem>();
private database = new DatabaseService();
private db: any;
selectItems(): ObservableArray<IDataItem> {
this.database.getdbConnection()
.then(db => {
db.all("SELECT * FROM items").then(rows => {
for (let row in rows) {
this.items.push({ id: rows[row][0], sitioWeb: rows[row][1], usuario: rows[row][2], password: rows[row][3] });
}
this.db = db;
}, error => {
console.log("SELECT ERROR", error);
});
});
for(let i = 0; i < this.items.length; i++){
Toast.makeText(""+this.items[i].id+" "+this.items[i].sitioWeb+" "+this.items[i].usuario+" "+this.items[i].password,"10")
}
return this.items;
}
getItems(): ObservableArray<IDataItem> {
return this.items;
}
getItem(id: number): IDataItem {
return this.items.filter((item) => item.id === id)[0];
}
}
This is the view where the listview is located at:
<ActionBar class="action-bar">
<Label class="action-bar-title" text="Home"></Label>
</ActionBar>
<GridLayout class="page page-content" >
<ListView [items]="items | async" class="list-group" >
<ng-template let-item="item">
<Label [nsRouterLink]="['../item', item.id]" [text]="item.sitioWeb" class="list-group-item"></Label>
</ng-template>
</ListView>
</GridLayout>
It only loads items to the list view on the init of the app, but I want to refresh it when an item is added.
A simple solution would be to either fire a new request to get the list after you add a new item, or just add it in memory on the front end if you get a success response from the save request. However, while this would work, it's not a great solution. It wouldn't update the list when another user added an item.
I think you should look into using websockets for this. You can have a socket open listening for messages on the front end. The back end would emit a message every time something was added, even if it was added by another user. The front end listener would add that item to the list.
Here is a good tutorial using Sock.js and STOMP to implement websockets in angular.
https://g00glen00b.be/websockets-angular/
You can create sharer service with list of your item like this:
import { Injectable } from '#angular/core';
import { BehaviorSubject } from 'rxjs';
#Injectable()
export class DataService {
private messageSource = new BehaviorSubject('default message');
currentMessage = this.messageSource.asObservable();
constructor() { }
changeMessage(message: string) {
this.messageSource.next(message)
}
}
Where currentMessage is your list of items. Bind that list to you html and in callback function on create new item call method changeMessage witch will be add new created item in your array.
See this example
And not forget subscribe on changes in your list component.
Once you add a new item to the database you can call selectItems() service which will update the item list whenever new item is added or you can call selectItems() in ngAfterViewChecked() hook as it is executed every time the view of the given component has been checked by the change detection algorithm of Angular. This method executes after every subsequent execution of the ngAfterContentChecked()
I'm trying to override the document title on a route.
This is the route with a default title.
{
path: 'artikel/:id/:slug',
component: ArticleComponent,
data: {title: 'Article', routeType: RouteType.ARTICLE,
description: metaDescription},
resolve: {error: ErrorResolverService, article: ArticleResolveService},
}
I am using an ArticleResolverService to get the article from the ID and then apply a new Title.
resolve(route: ActivatedRouteSnapshot): Observable<Article> {
let id = route.paramMap.get('id');
return this.as.getArticle(id).take(1).map(article => {
if (article) {
//this seems to set the title temporarely (flickering visible)
this.ts.setTitle(article.title);
return article;
}
return null;
});
}
Since this approach doesn't quite work I tried setting the title in the ArticleComponent, which is the target of the route. (This happens in ngOnInit)
this.route.data.subscribe((data:{article: Article}) => {
this.article = data.article;
//this seems to set the title temporarely (flickering visible)
this.ts.setTitle(this.article.title);
//using this in browser console works permanently
window['setTitle'] = (t) => this.ts.setTitle(t);
});
Whatever I am doing, everytime I load the page i see the flickering of the wanted title but then it gets instantly reset to the default title (if I don't use a default title it will just show the page URL in the title bar, also just after flickering of the wanted title).
How do i effectively set a permanent title for this page?
The last step from your question is not required (window['setTitle']). Works just fine with the approach below. The default title will show until the setTitle function is called, then it will stay at the new title
Import Title service
import { Title } from '#angular/platform-browser';
Inject it
constructor(
...
private titleService: Title,
) {...}
Use it in the ngOnInit
this.titleService.setTitle("Some custom title" + this.newInfoFromAPI);
I'm using Angular5.
I have Header component.
Goal: I need to implement breadcrumbs in header.
My view:
Routing already implemented, I just need to:
parse ID from URL
get Item from BackEnd
get Name
set Name as Breadcrumb to Header component
My current progress:
I can parse ID from URL using following code:
this.activatedRouter.params.subscribe(params => {
this.id = params['id'];
});
Now, in template I have:
<ul class="breadcrumb">
<li><a routerLink="/items" routerLinkActive="active">Items</a></li>
<li>{{id}}</li>
</ul>
Question: How to update ID value in Component each time after new route executed?
For example:
I'm on Items Page => My URL localhost:4002/items
I clicked on Item, and redirected to Items page => My URL localhost:4002/items/34 => My Breadcrumbs = Items > 34
Thanks in advance
you can use :
constructor( private route: ActivatedRoute) {}
this.route.routerState.params.subscribe(params => {
this.id = params['id'];
});
your component will be updated with the new state
I found solution
constructor(private router: Router) {
this.router.events.subscribe((val: NavigationEnd) => {
...action with val.url variable
}
}
Each time, after redirect you will get data from your URL.
I have Ionic 2 app with one view for 3 different data sets. Data are loaded in constructor and based on variable in page params, it's decided which data set to show.
At every successful data call by observable, event handler logs success when data are loaded. But this only works when I click/load view for a first time. If I click for 2nd or any other time, data are not re-loaded (no log). Also, when I just console log anything, it won't show at 2nd+ click.
So I wonder what should I change to load data everytime and how constructor works in this manner.
This is how my code looks like. Jsons are called from namesListProvider.
#Component({
templateUrl: '...',
})
export class ListOfNames {
...
private dataListAll: Array<any> = [];
private dataListFavourites: Array<any> = [];
private dataListDisliked: Array<any> = [];
constructor(private nav: NavController, ...) {
...
this.loadJsons();
console.log('whatever');
}
loadJsons(){
this.namesListProvider.getJsons()
.subscribe(
(data:any) => {
this.dataListFavourites = data[0],
this.dataListDisliked = data[1],
this.dataListAll = data[2]
if (this.actualList === 'mainList') {
this.listOfNames = this.dataListAll;
this.swipeLeftList = this.dataListDisliked;
this.swipeRightList = this.dataListFavourites;
}
else if (...) {
...
}
this.listSearchResults = this.listOfNames;
}, err => console.log('hey, error when loading names list - ' + err),
() => console.info('loading Jsons complete')
)
}
What you're looking for are the Lifecycle events from Ionic2 pages. So instead of using ngOnInit you can use some of the events that Ionic2 exposes:
Page Event Description
---------- -----------
ionViewLoaded Runs when the page has loaded. This event only happens once per page being created and added to the DOM. If a page leaves but is cached, then this event will not fire again on a subsequent viewing. The ionViewLoaded event is good place to put your setup code for the page.
ionViewWillEnter Runs when the page is about to enter and become the active page.
ionViewDidEnter Runs when the page has fully entered and is now the active page. This event will fire, whether it was the first load or a cached page.
ionViewWillLeave Runs when the page is about to leave and no longer be the active page.
ionViewDidLeave Runs when the page has finished leaving and is no longer the active page.
ionViewWillUnload Runs when the page is about to be destroyed and have its elements removed.
ionViewDidUnload Runs after the page has been destroyed and its elements have been removed.
In your case, you can use the ionViewWillEnter page event like this:
ionViewWillEnter {
// This will be executed every time the page is shown ...
this.loadJsons();
// ...
}
EDIT
If you're going to obtain the data to show in that page asynchronously, since you don't know how long would it take until the data is ready, I'd recommend you to use a loading popup so the user can we aware of something happening in the background (instead of showing a blank page for a few seconds until the data is loaded). You can easily add that behaviour to your code like this:
// Import the LoadingController
import { LoadingController, ...} from 'ionic/angular';
#Component({
templateUrl: '...',
})
export class ListOfNames {
...
private dataListAll: Array<any> = [];
private dataListFavourites: Array<any> = [];
private dataListDisliked: Array<any> = [];
// Create a property to be able to create it and dismiss it from different methods of the class
private loading: any;
constructor(private loadingCtrl: LoadingController, private nav: NavController, ...) {
...
this.loadJsons();
console.log('whatever');
}
ionViewWillEnter {
// This will be executed every time the page is shown ...
// Create the loading popup
this.loading = this.loadingCtrl.create({
content: 'Loading...'
});
// Show the popup
this.loading.present();
// Get the data
this.loadJsons();
// ...
}
loadJsons(){
this.namesListProvider.getJsons()
.subscribe(
(data:any) => {
this.dataListFavourites = data[0],
this.dataListDisliked = data[1],
this.dataListAll = data[2]
if (this.actualList === 'mainList') {
this.listOfNames = this.dataListAll;
this.swipeLeftList = this.dataListDisliked;
this.swipeRightList = this.dataListFavourites;
}
else if (...) {
...
}
this.listSearchResults = this.listOfNames;
}, err => console.log('hey, error when loading names list - ' + err),
() => {
// Dismiss the popup because data is ready
this.loading.dismiss();
console.info('loading Jsons complete')}
)
}
The solution is don't do this in the constructor, use ngOnInit() instead. Components are created only once, therefore the constructor will only be called when first created.
Your component class must implement the OnInit interface:
import { Component, OnInit } from '#angular/core';
#Component({
templateUrl: '...',
})
export class ListOfNames implements OnInit {
constructor(...)
ngOnInit() {
this.loadJsons();
}
private loadJsons() {
...
}
}
i'm coming from Angular 2 world, not ionic, but angular 2 has the option to register callbacks on init/destory (ngInit/ngDestory).
try to move initialization to ngInit, save subscription handler, and don't forget to unsubscribe it on destory.
i think your issue related to that you are not unsubscribing.. :\
So i have a Modal Component with a form in it, this component is used for both creating an entry to the DB and editing an existing one.
It has a subscription option to the onSubmit event, which is being executed on a successful submit.
What happens for some reason is that some of this component's element subscription executes and some won't, and it looks like those on the "create-mode" will and those on the "edit-mode" wont.
Code Section
CreateOrUpdateTransactionComponent:
#Component({
selector: 'create-update-transaction',
templateUrl: './CreateOrUpdateTransaction.html',
providers: [AccountTransactionsService]
})
export class CreateOrUpdateTransactionComponent {
closeResult: string;
modalRef: NgbModalRef;
#Input() transaction: Transaction = new Transaction();
#Input() isCreate: boolean;
#Output() onSubmit: EventEmitter<void> = new EventEmitter<void>();
constructor(private modalService: NgbModal,
private transactionsService: AccountTransactionsService) {}
sendTransaction(): void{
let localModalRef = this.modalRef;
this.transactionsService.createOrUpdateTransaction(this.transaction, (isSuccessful)=>{
if (isSuccessful) {
this.onSubmit.emit(); //<--- The problem is here
localModalRef.close();
}
});
}
}
The HTML:
<table>
<caption>Account Transactions</caption>
<thead>
// Omitted thead
</thead>
<template let-transaction ngFor [ngForOf]="accountTransactions" let-i="index">
<tr data-toggle="collapse" [attr.data-target]="'#'+i">
// Omitted <td>s
<td> //<----These updateTransactions() are not being executed
<create-update-transaction [isCreate]="false" [transaction]="transaction" (onSubmit)="updateTransactions()"></create-update-transaction>
</td>
</tr>
<div class="container collapse" [attr.id]="i">
// some content
</div>
</template>
</table>
<create-update-transaction [isCreate]="true" (onSubmit)="updateTransactions()"></create-update-transaction>
//<---- This updateTransactions() successfully executes
Notice
If I only display one row in the table not using ngFor (keeping the call to the back-end to update the DB), it works perfectly fine.
Any idea why would this happen?
Thanks in advance!
Update1
Debugging i could notice that when on the create-mode the this.onSubmit.observers is an array with one observer and on the edit-mode its an array with 0 observers, so thats the problem. any idea why?
Update2
Debugging again and found that the this in this.transactionsService.createOrUpdateTransaction... is fine and its' onSubmit.observers contains 1 observer, before reaching the callback's code, in which the this.onSubmit.observers is an array of 0 observers
AccountTransactionsService:
#Injectable()
export class AccountTransactionsService{
private loggedBankAccount: number;
private queryObservable: ObservableQuery;
constructor(private userManagingService: UserManagingService) {
this.loggedBankAccount = userManagingService.getLoggedBankAccountNumber();
this.queryAccountTransactions();
}
queryAccountTransactions(): void{
this.queryObservable = // querying the back-end
}
createOrUpdateTransaction(transaction: Transaction, callback: (isSuccessfull: boolean) => void): void{
let isSuccessful: boolean = false;
client.mutate(/*inserting the backend*/).then((graphQLResult) => {
const { errors, data } = graphQLResult;
if (data) {
console.log('got data', data);
isSuccessful = true;
}
if (errors) {
console.log('got some GraphQL execution errors', errors);
}
callback(isSuccessful);
}).catch((error) => {
console.log('there was an error sending the query', error);
callback(isSuccessful);
});
}
getAccountTransactions(): ObservableQuery{
return this.queryObservable;
}
}
Notice
If i just execute the callback give to the AccountTransactionService.createOrUpdateTransaction (removing the call to the back-end to actually update the DB) it works perfectly fine and all the subscribers to this onSubmit event are being called.
this Console image
Set the null as a parameter :
this.onSubmit.emit(null);
So I found out the case was that the data the ngFor is bound to was being replaced by a new instance as I updated the backend, hence, it rerendered it's child Components causing reinitializing (destory + init) of them, which made the instance of the Component to be overwritten.
In order to solve this issue i have changed the querying of the accountTransaction to be only one time querying, on initializing of the parent component, and never refetching again (which triggers the rerendering).
Im displaying to the client the changes only if they succeeded on the server side, and if they failed i use a backup of the data, so the client is kept update of the real state of the server WITHOUT refetching
For the future lookers to come:
The key to the problem was that the Parent Component's *ngFor depended on data that was changing in the Child Components, causing reinitializing of the *ngFor (the Child Components) BEFORE finishing executions of the Child Components methods.
Hope it'll be helpful to somebody :)