'i want to assign the content of a variable i filled with an array of an firebase subscription
i want to assign the content of a variable i filled with an array of an firebase subscription, my problem is i cant assign the value which i created inside the subscription. it feels like i cant use the created value outsite of the subcription(from angularfirestore). i know how to use the created variable inside the.html file, but i need it inside the componentclass. PS:these files were created with the angular schemantics "ng generate @angular/material:table "
//table-datasource.ts
import { DataSource } from '@angular/cdk/collections';
import { MatPaginator } from '@angular/material/paginator';
import { MatSort } from '@angular/material/sort';
import { map } from 'rxjs/operators';
import { Observable, of as observableOf, merge } from 'rxjs';
import { AuthService } from "../../services/auth.service";
// TODO: Replace this with your own data model type
export interface TableItem {
email: string;
displayName: string;
photoURL: string;
emailVerified: boolean;
}
const EXAMPLE_DATA: TableItem[] = [//this hardcoded data shows but i obviously dont want it hardcoded
{
"emailVerified": false,
"displayName": "string",
"email": "[email protected]",
"photoURL": "string"
},
{
"email": "[email protected]",
"photoURL": "string",
"emailVerified": false,
"displayName": "string",
},
{
"emailVerified": false,
"email": "[email protected]",
"displayName": "string",
"photoURL": "string",
},
{
"displayName": "string",
"photoURL": "string",
"emailVerified": false,
"email": "[email protected]",
},
{
"photoURL": "string",
"emailVerified": false,
"displayName": "string",
"email": "[email protected]",
},
{
"email": "[email protected]",
"photoURL": "string",
"displayName": "string",
"emailVerified": true,
},
{
"photoURL": "string",
"emailVerified": false,
"displayName": "string",
"email": "[email protected]",
},
];
/**
* Data source for the Table view. This class should
* encapsulate all logic for fetching and manipulating the displayed data
* (including sorting, pagination, and filtering).
*/
export class TableDataSource extends DataSource<TableItem> {
//data: TableItem[] = EXAMPLE_DATA; //works with hardcoded data
data: TableItem[] = []; //i basicly need data to be the res of the subscription(see below)
paginator: MatPaginator | undefined;
sort: MatSort | undefined;
userData: any;
constructor(public authService: AuthService) {
super();
this.getUsers2();
this.printUser();//works only with hardcoded EXAMPLE_DATA, else shows empty Array
}
printUser() {
console.log("this.data", this.data);
}
getUsers2() {
this.authService
.getCollection2("users")
.subscribe((res: any[]) => {
this.data = res //here this.data gets the values from res
console.log("table-datasource", this.data)//works here only
})
}
...
}
//table.component.ts
import { AfterViewInit, Component, ViewChild } from '@angular/core';
import { MatPaginator } from '@angular/material/paginator';
import { MatSort } from '@angular/material/sort';
import { MatTable } from '@angular/material/table';
import { AuthService } from "../../services/auth.service";
import { TableDataSource, TableItem } from './table-datasource';
@Component({
selector: 'app-table',
templateUrl: './table.component.html',
styleUrls: ['./table.component.scss']
})
export class TableComponent implements AfterViewInit {
@ViewChild(MatPaginator) paginator!: MatPaginator;
@ViewChild(MatSort) sort!: MatSort;
@ViewChild(MatTable) table!: MatTable<TableItem>;
dataSource: TableDataSource;
/** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */
displayedColumns = ['id', 'name'];
constructor(public authService: AuthService) {
this.dataSource = new TableDataSource(authService);
}
ngAfterViewInit(): void {
this.dataSource.sort = this.sort;
this.dataSource.paginator = this.paginator;
this.table.dataSource = this.dataSource;
}
}
//table.component.html
<div class="mat-elevation-z8">
<table mat-table class="full-width-table" matSort aria-label="Elements">
<!-- Id Column -->
<ng-container matColumnDef="id">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Id</th>
<td mat-cell *matCellDef="let row ">{{row.uid}}</td>
</ng-container>
<!-- Name Column -->
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Name</th>
<td mat-cell *matCellDef="let row">{{row.email}}</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
<mat-paginator #paginator
[length]="dataSource?.data?.length"
[pageIndex]="0"
[pageSize]="10"
[pageSizeOptions]="[5, 10, 20]"
aria-label="Select page">
</mat-paginator>
</div>
Solution 1:[1]
You've forgotten that api calls take time to complete. You can't print the user if you haven't got your data back yet!
For example this will work:
getUsers2() {
this.authService
.getCollection2("users")
.subscribe((res: any[]) => {
this.data = res //here this.data gets the values from res
this.printUser();
})
}
Javascript does not wait for subscriptions to complete, it executes other code in the meantime. It is single threaded, but it pushes an action to the back of a queue that says: check if we have received a response yet - if not continue with other code execution. That's how it makes asynchronous calls non-blocking.
So you will subscribe, execute all the rest of the code you have pushed onto the stack (ie. your constructor), then it will start checking the queue for operations, which includes checking to see if the api has responded. Only if the api has responded will the callback function from subscribe execute. Otherwise it will push this check to the back of the queue again.
To read more about how Javascript asynchronous code works:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop
The best way to do something after a subscription callback is to simply put whatever you want to do into a function, and call it at the end of your subscription.
getUsers2() {
this.authService
.getCollection2("users")
.subscribe((res: any[]) => {
this.data = res //here this.data gets the values from res
this.doWhateverWithMyData();
})
}
However, if you insist on blocking your code execution to wait for a response, you can use async / await. RXJS has firstValueFrom() to receive a Promise that resolves after receiving one response.
constructor(public authService: AuthService) {
super();
}
async ngOnInit() {
const res: any[] = await firstValueFrom(this.authService.getCollection2('users'))
console.log(res);
}
But heed the warning from firstValueFrom() - this is an example of why it is better to use observables over promises.
If the observable stream completes before any values were emitted, the returned promise will reject with EmptyError or will resolve with the default value if a default was specified.
If the observable stream emits an error, the returned promise will reject with that error.
WARNING: Only use this with observables you know will emit at least one value, OR complete. If the source observable does not emit one value or complete, you will end up with a promise that is hung up, and potentially all of the state of an async function hanging out in memory. To avoid this situation, look into adding something like timeout, take, takeWhile, or takeUntil amongst others.
source: https://rxjs.dev/api/index/function/firstValueFrom
If you wanted to add a five second timeout and some error handling it would look like this:
async ngOnInit() {
const res = await firstValueFrom(
this.authService.getCollection2('users').pipe(timeout({ each: 5000 }))
).catch((err) => console.error(err));
console.log(res);
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 |
