I have made an API to display the followers and following users of an user. Everything is displayed properly on the screen. But the problem is if I try to console.log() the array it is stored in after calling the method it says that it is an empty array. I'm really stuck and don't know what to do further. Here are my examples could someone help out or is experiencing the same problem please let me know.
ngOnInit:
ngOnInit(): void {
localStorage.setItem("gd", "78F88FC0-7A58-49CD-881E-4B36C5C29B71");
this.getUsers();
this.getFollowing();
this.getFollowers();
console.log("Followers: ", this.followers)
console.log("Following: ", this.following)
}
Methods:
getUsers() {
this._userService.getUsers().subscribe(res => {
this.users = res;
})
}
getFollowing() {
this._userService.getFollowing().subscribe(res => {
this.following = res;
})
}
getFollowers() {
this._userService.getFollowers().subscribe(res => {
this.followers = res;
})
}
Console:
HTML output:
I want to make an answer less generally that the link exposed
We can use forkJoin to make all the calls and get the response in an unique subscribe
ngOnInit()
{
forkJoin(this._userService.getUsers(),
this._userService.getFollowing(),
this._userService.getFollowers())
.subscribe(([users,following,followers])=>{
this.users=users
this.following=following
this.followers=followers
console.log(this.users) //<---give value
})
console.log(this.users) //<--has no value outside subscribe function
}
And yes, only has sense into subscribe function make a console.log(this.users)
Remember: the services return observables, we subscribe in components
Related
I am trying to apply the Page Object Model (POM) to some tests using Cypress.
Unfortunaly I can't read some webelement value and return it as a method or function value. I needed to make something like these to work:
var1 = cy.get("input#inpUserName").then(($var1) => {
cy.log($var1.val());
})
cy.log(var1)
But all I could do was:
cy.get("input#inpUserName").then(($var1) => {
cy.log($var1.val());
})
For what I researched I think there is no solution for that with Cypress. But I'd like to see if anyone has any suggestion about it.
You can return a value from a POM method, but it is Promise-like so you use .then() to access the value.
class Login {
...
username() {
return cy.get("input#inpUserName").then(($var1) => {
return $var1.val()
})
}
}
...
const login = new Login()
login.username().then(username => {
expect(username).to.eq('Joe')
})
New to Angular/Apollo/TS and this is driving me nuts, so any help is appreciated.
I am working on setting up a small app with Angular 10, Apollo, and a GraphQL API. I recently built the same thing in Vue and thought recreating the project would be a good way to pick up some Angular.
My connection to the API is working, as is my query, but I can't figure out how to map the results to an array so I can access them in my component. Using console.log inside the subscription shows the correct data is returned. console.log outside of the query on 'this' shows the query results, however they are never saved/mapped to the variable they should be set to.
Here's the code for my service:
import { Injectable } from '#angular/core';
import { Apollo } from 'apollo-angular';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import gql from 'graphql-tag';
const USER_SEARCH = gql`
query getUsers {
search(query: "moose", type: USER, first: 10) {
nodes {
... on User {
login
email
location
name
}
}
pageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
userCount
}
}`;
export class UserService {
loading: boolean = true;
users: [];
constructor(private apollo: Apollo) { }
getUsers(): any {
this.apollo.watchQuery<any>({
query: USER_SEARCH
})
.valueChanges
.subscribe(({ data, loading }) => {
this.loading = loading;
this.users = data.search;
});
console.log(this);
return this.users;
}
}
I can call the getUsers() function from my component, and 'this' has the service listed, and inside of it 'users' has my query results listed in it. However, console.log for this.users in the service or the component returns undefined.
I've tried about every type of example I could find, including the query examples from the apollo docs, and the example of using apollo with angular from hasura.io. Tried using a pipe and map, pluck, just valueChanges, a few different subscribes, setting a variable inside the function to assign the data value to, setting the query to variable, setting the query in ngOnInit in the component, and a few other things I'm sure I'm forgetting. Nothing seems to work. I looked into using a callback to wait for the query to return before setting the value, but my understanding is that I shouldn't have to do anything like that. I'm sure it's something dumb I'm missing or don't know about with Apollo or Angular, but I'm just not positive what it is I'm missing.
Any ideas?
this.getUsers = this.getUsers.bind(this);
within a constructor?
using setTimeout is not an ideal solution, you can directly update your component variable in subscribe callback function and do whatever you want to do with it in your template. Look at my example
getItems() {
this.apollo
.watchQuery({
query: this.getItemsQuery,
})
.valueChanges.subscribe((result: any) => {
this.items = result?.data?.items;
});
}
and in template
<mat-option *ngFor="let item of items" [value]="item.price">
{{ item.name }}
</mat-option>
Maybe not the ideal solution, so I'm still open to trying other things, but I was able to get the value set in my component by using a promise with a timer in the service, then an async await in the component.
Service
getUsers(): any {
return new Promise((resolve, reject) => {
let me = this;
this.apollo.watchQuery<any>({
query: USER_SEARCH
})
.valueChanges
.subscribe(({ data, loading }) => {
this.loading = loading;
this.users = data.search;
});
setTimeout( function() {
if(me.users !== 'undefined'){
resolve(me.users)
}
}, 1000)
})
}
Component
async getUsers(): Promise<any> {
this.users = await this.userService.getUsers();
console.log(this.users);
}
This allows this.users to be set from the service. As far as I can tell, Apollo is still running the query when Angular starts setting values, resulting in the value originally being shown as undefined, but my service having values from the query in the console. Not sure if there's a better way with Apollo or Angular to resolve this issue, but if so I'd love to hear about it.
Thanks!
I have a subject that is subscribed to and fires when a user searches.
let searchView;
this.searchSubject
.switchMap((view: any) => {
searchView = view;
this.http.post(this.url, view);
})
.subscribe(page => {
this.searchHistoryService.addRecentSearch(searchView).subscribe();
})
searchHistoryService.addRecentSearch records this search so the user can see their recent searches.
I don't think this is good practice as the observable is subscribed to everytime, I would rather use a subject which I'm calling .next() on, or combine the history call with the search call itself.
If searchHistoryService.addRecentSearch returns a Subject I can call .next() but where would I subscribe to it?
I tried adding this in the searchHistoryService's constructor
this.searchHistorySubject.do(observableIWantToCall()).subscribe()
and then replacing the subscription to 'addRecentSearch' with this:
this.searchHistoryService.searchHistorySubject.next(searchView)
But it doesnt work.
The inner observable, observableIWantToCall() gets called but the observable returned isnt subscribed to.
What's wrong with this and what is best practice for subscribing to an observable when another is finished emitting?
I think you can do something like this:
let searchView;
private searchHistorySubject$: Subject<any> = new Subject<any>();
constructor(){
this.searchHistoryService.addRecentSearch(searchView).first().subscribe(
response => {
//It will entry when you send data through next
},
error => {
console.log(error);
}
);
}
...
addRecentSearch(searchView) {
...
return this._searchHistorySubject$.asObservable();
}
setSearchHistoryEvent(value: any) {
this._searchHistorySubject$.next(value);
}
this.searchSubject
.switchMap((view: any) => {
searchView = view;
this.http.post(this.url, view);
})
.subscribe(page => {
this.searchHistoryService.setSearchHistoryEvent(searchView);
}
)
Before Angular 5, we imported and used Http from #angular/http. Now we use HttpClient from #angular/common/http. There's some level of convenience added, because no we don't need the unnecessary step of turning all of our response data to JSON format every time we make a call using .map(res => res.json()). However, now we aren't suppose to use .map. We are suppose to go straight to .subscribe(). Again, this is suppose to be convenient. However, I used to do some logic in my services, where my API calls live, before returning the data to the subscribing functions in other components. For example,
The component:
getData(id) {
this.service.getDataFromApi(id).subscribe(res => this.doSomething(res));
}
The service:
public getDataFromApi(id) {
if (this.data.length > 0) {
return Observable.of(this.data);
} else {
return this.http.get(this.apiPath + id)
.map((response: any) => {
this.data = response.json();
return this.data;
});
}
}
Now, they suggest we shorten the service call to something like the following:
public getDataFromApi(id) {
if (this.data.length > 0) {
return Observable.of(this.data);
} else {
return this.http.get(this.apiPath + id);
}
}
I am not suppose to use .map because it will no longer be supported in the future. How can I do some logic before returning the data? I don't want to make a call for some data that already exists and doesn't change. Multiple components are using this service. Am I suppose to turn every single service call into a raw promise and run some logic before resolving? If I were to just call subscribe right on the component's function, I wouldn't even need to have the service. Am I missing something here?
You can use map. The new HttpClient has the added convenience of defaulting the response type to JSON, but it still returns an Observable -- and there are no plans to deprecate map from the Observable API.
So, your code only needs slight modification (take out the .json()):
public getDataFromApi(id) {
if (this.data.length > 0) {
return Observable.of(this.data);
} else {
return this.http.get(this.apiPath + id)
.map((response: any) => {
this.data = response;
return this.data;
});
}
}
See the Observable API docs here.
If you use the new HttpClient, there is no res.json method. It will work automatically, just pass the response type like this:
return this.http.get(this.apiPath + id)
.toPromise()
.then(data => {
console.log(data)
return data
});
So in normal javascript if I wanted to assign a value to a variable and then use that value outside of a function it would be done by declaring the variable first and then define it's value in the function. I'm brand new to typescript and angular so I am missing how to do this.
In the code below I am trying to get the value from a method in a service and then pass that value into my return. (I hope that makes sense). However I keep getting undefined on console.log(url) with no other errors.
emailsAPI() {
let url: any
this.apiUrlsService.urlsAPI().subscribe(
data => {
this.results = data
url = this.results.emails
}
);
console.log(url)
return this.http.get('assets/api/email_list.json')
}
api-urls service:
import { Injectable } from '#angular/core';
import { HttpClient, HttpErrorResponse } from '#angular/common/http';
#Injectable()
export class ApiUrlsService {
constructor(
private http: HttpClient
) { }
urlsAPI () {
return this.http.get('assets/api/api_urls.json')
}
}
That's because you're calling async method subscribe and then trying to log the coming value before subscription is resolved. Put last two statements (console.log and return) inside the curly braces just after assigning this.results.emails to the url variable
emailsAPI(): Observable<any> {
let url: any
return this.apiUrlsService.urlsAPI()
.flatMap(data => {
this.results = data
url = this.results.emails
// you can now access url variable
return this.http.get('assets/api/email_list.json')
});
}
As per reactive programming, this is the expected behaviour you are getting. As subscribe method is async due to which you are getting result later on when data is received. But your console log is called in sync thread so it called as soon as you are defining subscribe method. If you want the console to get printed. Put it inside your subscribe data block.
UPDATE:
As per your requirement, you should return Subject instead of Observable as Subject being data consumer as well as data producer. So it will consume data from httpget request from email and act as a producer in the method from where you called emailsAPI method.
emailsAPI(): Subject<any> {
let emailSubject:Subject = new Subject();
this.apiUrlsService.urlsAPI()
.flatMap(data => {
this.results = data
return this.results.emails;
}).
subscribe(url=> {
this.http.get(your_email_url_from_url_received).subscribe(emailSubject);
});
return emailSubject;
}
The subject can be subscribed same as you will be doing with Observable in your calee method.