I'm currently playing around with Angular and a Django Rest API I developed previously. The API has an endpoint which returns the number of currently logged-in users.
I'm able to see the JSON response in my browser console, but I'm not sure how I can display the value of it.
This is what I'm doing at my template:
<div *ngIf="authService.isLoggedIn()" >{{ users_online }}</div>
where the variable users_online
only shows [object Promise]
.
This is how I pull the value from the API at my app.service.ts
import {Injectable} from '@angular/core';
import {HttpClient} from "@angular/common/http";
import {environment} from "../environments/environment";
import {firstValueFrom} from "rxjs";
export interface UsersOnlineCounterResponse {
users_online: number;
}
const headers = {
'Authorization': 'Bearer ' localStorage.getItem('access_token'),
}
@Injectable({
providedIn: 'root'
})
export class AppService {
constructor(private http: HttpClient) { }
async UsersOnlineCounter() {
await firstValueFrom(this.http.get<UsersOnlineCounterResponse>(environment.backend '/api/v1/users/online', {headers})).then(data => {
return data.users_online;
}
);
}
}
And finally, this is what I do at my app.component.ts
import { Component } from '@angular/core';
import { Title } from '@angular/platform-browser';
import { AppService } from "./app.service";
import { AuthService } from "./auth/auth.service";
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'example.com'
users_online = this.appService.UsersOnlineCounter();
constructor(private titleService: Title, public authService: AuthService, public appService: AppService) {
}
ngOnInit() {
this.titleService.setTitle(this.title);
}
}
CodePudding user response:
Change your service to make use of Observable:
import { Observable } from 'rxjs';
...
UsersOnlineCounter(): Observable<UsersOnlineCounterResponse> {
return this.http.get<UsersOnlineCounterResponse>(environment.backend '/api/v1/users/online', {headers}));
And then in your component:
ngOnInit(): void {
this.appService.UsersOnlineCounter().subscribe((usersOnline) => {
this.users_online = data.users_online;
}
Angular heavily makes use of the concept of observables. So does RxJs and ngRx
CodePudding user response:
It would be more 'angular' to just use the observables as others answers show, but the problem in your code is that you never return a value:
async UsersOnlineCounter() {
await firstValueFrom(this.http.get<UsersOnlineCounterResponse>(
environment.backend '/api/v1/users/online',
{headers}))
.then(data => {
return data.users_online;
});
You throw away the value from calling firstValueFrom()
without returning it, so your function returns a promise that resolves to undefined
. You can add a return
in front of the await
. Then since your method is async it will return a promise that will resolve to that value. You don't show where you try to use this in your AppComponent
, but users_online
will be a promise.
The 'angular' way would be to let it be an observable, use the $
naming convention, and us an async pipe to display the data. The service would just return the result from your HttpClient call which would be an observable. I use the rxjs of
to emulate that:
import { Injectable } from '@angular/core';
import { of } from 'rxjs';
export interface UsersOnlineCounterResponse {
users_online: number;
}
@Injectable({
providedIn: 'root',
})
export class AppService {
constructor() {}
GetUsersOnline() {
// just like an http call that returns 42 for the users online
return of(<UsersOnlineCounterResponse>{ users_online: 42 });
}
}
Then in your component you have a variable that just contains that response observable:
import { Component, VERSION } from '@angular/core';
import { AppService, UsersOnlineCounterResponse } from './app.service';
import { Observable } from 'rxjs';
@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent {
name = 'Angular ' VERSION.major;
usersOnline$: Observable<UsersOnlineCounterResponse>;
constructor(public appService: AppService) {
this.usersOnline$ = appService.GetUsersOnline();
}
}
And in your template you can use the async pipe and *ngIf
to display one element if the observable has a value and another if it hasn't loaded yet:
<p>
The value returned from GetUsersOnline() is:
<span *ngIf="usersOnline$ | async as usersOnline; else loading">
{{ usersOnline.users_online }}
<span>
<ng-template #loading>
<span>fetching user count ...</span>
</ng-template>
</p>