Angular method returns undefined - javascript

As a beginner, I facing a problem with Angular and Observables. I have API for getting information about one specific restaurant in the database, but I have to get it with a POST request. I successfully get restaurantID from auth.service and another API when the restaurant is logged in, But when I tried to log restaurant in console, I get undefined. Uniformly I don't have permission to show API here. The code:
restaurant.service.ts
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { Restaurant } from '../models/Restaurant';
import { LoggedRestaurant } from '../models/LoggedRestaurant';
#Injectable({
providedIn: 'root'
})
export class RestaurantService {
private restaurantUrl = 'xxxxxxxxxxxx';
public restaurant: Restaurant;
public loggedRestaurant: LoggedRestaurant
public restaurantID;
constructor(private http: HttpClient) { }
public getRestaurant(): Observable<LoggedRestaurant> {
return this.http.post<LoggedRestaurant>(this.restaurantUrl, this.restaurantID);
}
}
informacije.component.ts
import { Component, OnInit } from '#angular/core';
import { AuthService } from '../services/auth.service';
import { RestaurantService } from '../services/restaurant.service';
import { Restaurant } from '../models/Restaurant';
import { LoggedRestaurant } from '../models/LoggedRestaurant';
import { Observable } from 'rxjs';
#Component({
selector: 'app-informacije',
templateUrl: './informacije.component.html',
styleUrls: ['./informacije.component.scss']
})
export class InformacijeComponent implements OnInit {
restaurant: Restaurant;
loggedRestaurant: LoggedRestaurant;
restaurantID;
constructor(private restaurantService: RestaurantService, private authService: AuthService ) { }
getRestaurant() {
return this.restaurantService.getRestaurant()
}
ngOnInit() {
this.restaurant = this.authService.currRestaurant[0];
console.log(this.restaurant)
console.log(this.loggedRestaurant)
this.restaurantID = this.restaurant.id;
console.log(this.restaurantID)
this.restaurantService.restaurantID =this.restaurantID;
}
}

httpClient.post() returns an observable (RXJS). So you need to subscribe to that. Otherwise, you may use the async pipe.
in your html, you can try this,
<span>{{getRestaurant() | aync}}</span>
OR,
you can declare a variable in your ts like data, and,
this.restaurantService.getRestaurant().subscribe(payload => {
this.data = payload;
})
and in your html, you can add,
<span *ngIf="data">{{data}}</span>

You need to subscribe to your API call.
In informacije.component.ts
getRestaurant() {
return this.restaurantService.getRestaurant()
.subscribe(data => this.restaurant = data);
}
This will asign the value returned by your service to your restaurant field in an asynchronous fashion.

In ngOnInit() call getRestaurant as follows
async ngOnInit() {
let restaurant = await this.getRestaurant().toPromise();
...
}

Related

how do i assign data from the subscribe call to a local variable in Angular

I have tried to declare the variable inside the ngOnInit() but it goes out of scope. I would like to use the variable to iterate over the data and populate my html component. I have followed the answered questions on the same issue but most of them suggest calling a function inside the subscribe function, unfortunately in my case i just need to view the returned data.
import { Component, OnInit } from "#angular/core";
import { Playlist } from "../playlist";
import { PlaylistService } from "../playlist.service";
#Component({
selector: "app-market-place",
templateUrl: "./market-place.component.html",
styleUrls: ["./market-place.component.css"],
})
export class MarketPlaceComponent implements OnInit {
_playList: Playlist[] = []; //never gets assigned here
errorMessage;
constructor(private playListService: PlaylistService) {}
ngOnInit() {
this.playListService.getPlaylist().subscribe({
next: (playList) => {
this._playList = playList;
console.log(this._playList); // am able to log the data but its never assigned to my _playList variable
},
error: (err) => (this.errorMessage = err),
});
}
}
here is the service class
import { Injectable } from "#angular/core";
import {HttpClient, HttpErrorResponse} from '#angular/common/http'
import { Observable, throwError } from "rxjs";
import {catchError, map, tap} from 'rxjs/operators'
import { Playlist } from "./playlist";
#Injectable({
providedIn: "root",
})
export class PlaylistService {
private playListUrl = "https://reqres.in/api/users";
constructor(private http: HttpClient) {}
getPlaylist():Observable<Playlist[]> {
return this.http.get<Playlist[]>(this.playListUrl).pipe(
map((response) => <Playlist[]>response),
catchError(this.handleError),
);
}
private handleError(err:HttpErrorResponse){
let errorMessage = '';
if (err.error instanceof ErrorEvent){
errorMessage = `An error occurred: ${err.error.message}`
}else {
errorMessage = `Server returned code: ${err.status}, error message is: ${err.message}`;
}
console.error(errorMessage);
return throwError(errorMessage)
}
}
I do not know, what your Playlist object looks like, but maybe I have a simple idea, that could solve your issue. I suppose that console.log can not show the array of your objects. Did you try the following?
console.log(this._playList[0]);

In Angular 9, how do I update a component's data field to show in the DOM without re-instantiating it?

I'm fairly new to Angular 9. I have a program where a user enters in a name - which, upon submitting - a POST HTTP request is sent and the name is stored. I then have an unrelated component for a sub-header that lists the names that have been stored using a GET HTTP request using ngOnInit(). However, I need the sub-header to update that list of names dynamically each time a new list is entered rather than just whenever the component instantiates.
I'm unsure how to proceed. I'm sure I could simply add a button that fetches and updates said list, but trying for something more dynamic. Thanks in advance!
//SERVICE.TS...
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { NewList } from './new-list.model';
import { map } from 'rxjs/operators';
#Injectable({
providedIn: 'root'
})
export class ListService {
createdLists: NewList[] = [];
constructor(private http: HttpClient) { }
createList(postData) {
return this.http
.post(
'API_KEY',
postData
);
}
getLists() {
return this.http
.get<NewList>(
'API_KEY'
).pipe(map(responseData => {
const responseArray: NewList[] = [];
for (const key in responseData) {
responseArray.push(responseData[key])
}
return responseArray;
})
);
}
}
// NEW-LIST-MENU.TS (USER ENTERS A NAME)...
import { Component, OnInit } from '#angular/core';
import { NgForm } from '#angular/forms';
import { Router } from '#angular/router';
import { ListService } from 'src/app/shared/list.service';
import { NewList } from 'src/app/shared/new-list.model';
import { UIService } from 'src/app/shared/ui.service';
#Component({
selector: 'app-new-list-menu',
templateUrl: './new-list-menu.component.html',
styleUrls: ['./new-list-menu.component.css']
})
export class NewListMenuComponent implements OnInit {
constructor(private listService: ListService,
private uiService: UIService,
private router: Router) { }
ngOnInit(): void {
}
onSubmit(form: NgForm) {
const listName = form.value.listname;
const newListObj = new NewList(listName, []);
this.listService.createList(newListObj)
.subscribe(() => {
this.router.navigate(['']);
});
const lists = this.listService.updateLists(newListObj);
form.reset();
}
onCancel() {
this.router.navigate(['']);
}
}
// SUB-HEADER.TS...
import { Component, OnInit, Output } from '#angular/core';
import { Router } from '#angular/router';
import { ListService } from 'src/app/shared/list.service';
import { NewList } from 'src/app/shared/new-list.model';
import { faWindowClose } from '#fortawesome/free-solid-svg-icons';
import { faPlusCircle } from '#fortawesome/free-solid-svg-icons';
import { faList } from '#fortawesome/free-solid-svg-icons';
import { faSignOutAlt } from '#fortawesome/free-solid-svg-icons';
import { Subject } from 'rxjs';
#Component({
selector: 'app-sub-header',
templateUrl: './sub-header.component.html',
styleUrls: ['./sub-header.component.css']
})
export class SubHeaderComponent implements OnInit {
createdLists: NewList[];
faWindowClose = faWindowClose;
faPlusCircle = faPlusCircle;
faList = faList;
faSignOutAlt = faSignOutAlt;
#Output() closeSub = new Subject();
constructor(private listService: ListService,
private router: Router) { }
ngOnInit(): void {
this.listService.getLists().subscribe((responseData) => {
this.createdLists = responseData;
});
}
onCloseSelect() {
this.closeSub.next();
}
onNewListSelect() {
this.onCloseSelect();
this.router.navigate(['new-list-menu']);
}
onLogOutSelect() {
}
}```
You can accomplish this in many ways, as these components are not related to each other, you can introduce a state service and use observables. see below possible solution
Create a new state service ListStateService
export class ListStateService {
private listData = new BehaviorSubject<NewList >({} as NewList);
listData$ = this.listData .asObservable();
}
Inject ListStateService into NewListMenuComponent
In the onSubmit, after you update,
const lists = this.listService.updateLists(newListObj);
this.listData .next(lists );
Inject ListStateService into SubHeaderComponent
In the ngOnInit(), subscribe to the ListStateService.listData$ and here you will get the value on changes
In your service, use an event emitter (very useful):
import { EventEmitter } from "#angular/core";
#Output() myEvent: EventEmitter<any> = new EventEmitter();
then emit new data to your sub header component through your service like so:
emitEvent (newData: Array<string>) {
this.myEvent.emit({
data: newData,
});
}
Subscribe to new data in your sub header component ngOnInit and use it:
this.myService.myEvent.subscribe((newData: Array<string>) => {
console.log(JSON.stringify(newData.data));
});
Note: Subscriptions will cause memory leaks if constantly re-subscribed in the component, so you can save the subscription and call unsubscribe() on it in the ngOnDestroy callback.
It's a little unclear what you are trying to do, but if you are trying to pass data from a parent component to a child component, you can do this either with Input fields or a ViewChild
to use Input fields your parent might looks like this:
<app-sub-header [names]="names"></app-sub-header>
then use an "Input" field in the child. Updating names in the parent should update the same named variable in the child in real time.

AngularFire2 return 'undefined' [object Object] with Angular 6

I try to get details from the firebase database but keep getting undefined
here is my code for getting the Object from the data base:
import { AppUser } from './models/app-user';
import { Injectable } from '#angular/core';
import { AngularFireDatabase, AngularFireObject } from 'angularfire2/database';
import * as firebase from 'firebase';
#Injectable({
providedIn: 'root'
})
export class UserService {
constructor(private db: AngularFireDatabase ) { }
get(uid: string): AngularFireObject<AppUser> {
console.log(this.db.object('/users/' + uid));
return this.db.object('/users/' + uid);
}
}
the console log from this get method is: [object Object]
I can't find how to get the username or other information of this user.
Here is my AppUser:
export interface AppUser {
email: string;
isAdmin: boolean;
name: string;
}
I found some answers, but it is related to older version of Angular, and is didn't help my issue.
I also saw some answer related to async pipe, but this is in the HTML, and I need the data to be available in a service.ts file.
I need to get the result in my component (not in the html).
I tried to extract the data of the user like that:
get appUser$(): Observable<AppUser> {
return this.user$
.pipe<AppUser>(map(user => {
if ( user ) {
this.userService.get(user.uid);
}
}));
}
but again the log say I got [object Object]...
In my final method that need to use this information:
canActivate() {
return this.auth.appUser$
.pipe(map(user => {
console.log(user);
return user.isAdmin;
}));
}
The console log give undefined
Use JSON.stringify
Console.log(JSON.stringify(this.db.object('/users/' + uid)));
You are returning a AngularFireDatabase.
You want to subscribe to AngularFireObject. so, you have to call :
get(uid: string): AngularFireObject<any> { // <----
console.log(this.db.object('/users/' + uid));
return this.db.object('/users/' + uid).valueChanges(); // <---
}
and than, you can subscribe and reveal the object in your component.
--Updated--
The current object type is AngularFireObject for single object and AngularFireList for a list of objects.
You still have to call .valueChanges() to get the observable.
I've managed to solve it with help from you, and other blogs. Thanks a lot!
Here is the full solution:
The user service.ts file:
import { AppUser } from './models/app-user';
import { Injectable } from '#angular/core';
import { AngularFireDatabase, AngularFireObject } from 'angularfire2/database';
import * as firebase from 'firebase';
#Injectable({
providedIn: 'root'
})
export class UserService {
constructor(private db: AngularFireDatabase ) { }
get(uid: string): AngularFireObject<AppUser> {
return this.db.object('/users/' + uid);
}
}
Here is the Authentication service.ts file:
import { AppUser } from './models/app-user';
import { pipe } from 'rxjs';
import { CanActivate } from '#angular/router';
import { Injectable } from '#angular/core';
import { UserService } from './user.service';
import { map } from 'rxjs/operators';
import * as firebase from 'firebase';
#Injectable({
providedIn: 'root'
})
export class AdminAuthGuard implements CanActivate {
fireBuser: firebase.User;
constructor(private userService: UserService) { }
canActivate() {
this.fireBuser = firebase.auth().currentUser;
return this.userService.get(this.fireBuser.uid)
.valueChanges()
.pipe(map((appUser: AppUser) => {
console.log(appUser.isAdmin);
return appUser.isAdmin;
}));
}
}
console.log(appUser.isAdmin) - give the correct property saved in the database.

Cannot access variable outside the subscription

I have an component in angular. I get data from the API and I instantiate a new object to create a map. But I can't access a variable outside the subscribe function. I also can't access my method.
maps.service.ts
This part, get data form api
import { Injectable } from '#angular/core';
import {HttpClient} from '#angular/common/http';
#Injectable()
export class MapsService {
constructor(private http: HttpClient) { }
getMap(params) {
console.log('Service', params);
return this.http.get('/api/posts/' + params.id);
}
}
map.component.ts
Here, where I build the map with google in the future
import { Component, OnInit } from '#angular/core';
import {MapsService} from '../maps.service';
import {ActivatedRoute} from '#angular/router';
import {MapPath} from '../map-path';
#Component({
selector: 'app-maps',
templateUrl: './maps.component.html',
styleUrls: ['./maps.component.css']
})
export class MapsComponent implements OnInit {
results: any;
params: {};
test: any;
constructor(
private mapsService: MapsService,
private route: ActivatedRoute,
) { }
ngOnInit() {
this.route.params.subscribe( params => {
this.params = params;
});
this.mapsService.getMap(this.params).subscribe(
data => {
this.results = data;
this.test = new MapPath(data, 'test');
},
err => {
console.log('Error occured', err);
});
console.log('this.test', this.test.getX());
console.log('this.results', this.results);
}
}
map-path.ts
Here get the different properties from geoJSON
export class MapPath {
test: string;
constructor(path: any, test: string) {
this.test = test;
console.log('Path', path);
console.log('test', test);
}
getX() {
return this.test;
}
}
Thanks.
Your problem is that Observables are async functions, meaning your subscription callback will happen later than the console.log calls following your this.mapsService.getMap call.
This is guaranteed by async nature of Observables.
You can either move your console logs inside of the subscribe function or create another subscription.
Hope this helps.

Angular 2 Interface throwing error of non existing property

I have an Angular 2 interface books.ts
export interface Books {
artists: Object;
tracks: Object;
}
This is the my service file where I am using it with http request searchService.ts
import { Injectable } from '#angular/core';
import { Http, Response, Headers, RequestOptions } from '#angular/http';
import { Observable } from 'rxjs/Observable';
import { Books } from 'app/pages/search-results/books';
import 'rxjs/add/operator/map'
#Injectable()
export class SearchService {
constructor(private _http:Http) { }
getBook(keyword): Observable<Books[]>{
return this._http.get('https://api.spotify.com/v1/search?q=' + keyword + '&type=track,artist')
.map((response: Response) => <Books[]> response.json());
}
}
And this is my component where I am using interface searchResults.ts
import { Component, OnInit } from '#angular/core';
import { ActivatedRoute, Router } from '#angular/router';
import { SearchService } from 'app/shared/search/search.service';
import { Books } from 'app/pages/search-results/books';
#Component({
selector: 'app-search-results',
templateUrl: './search-results.component.html',
styleUrls: ['./search-results.component.css'],
providers: [SearchService]
})
export class SearchResultsComponent implements OnInit {
keyword: any;
sub: any;
books: Books[];
errMessage: string;
arists: Object;
constructor(private _route: ActivatedRoute, private _router: Router, private _search: SearchService) { }
ngOnInit() {
this.sub = this._route
.queryParams
.subscribe(params => {
// Defaults to 0 if no query param provided.
this.keyword = params['keyword'] || 0;
this.getBooks(this.keyword);
});
//
}
getBooks(value) {
this._search.getBook(value)
.subscribe(
res => {
this.books = res;
console.log(res.artists);
},
error => { this.errMessage = <any>error }
);
}
}
The error comes when I try to console the res.artists. The error says Property 'artists' does not exist on type 'Books[]'. I am new to Angular 2 and doesn't know how to fix that.
The response is looks like
{artists:{limit: 20, item:[]}, tracks:{limit: 20, item:[]}}
I'm not sure but I think you try to get res.artist from collection of books. You can check it by for or e.g res[0].artist to get concrete artist.
getBook function in class SearchService return an array of Books object (Books[])
so, the res in getBooks function in SearchResultsComponent will be an Array of Books.
You can console.log(res) to see detail, if you want access to artists please try with res[0].artists if the res is not an empty array
The problem is that I am getting Object in response and I am assigning it to an Array which is causing the error. I have simply changes the both types to object and it solved my problem.
From this
books: Books[];
To this
books: Books;

Categories

Resources