I am new to Angular and struck with subscribe method. I need to trigger subscribe first in which initialization of property is done. Please help me with this.
Here is my code..
json.service.ts
#Injectable({providedIn: 'root'})
export class JsonService {
constructor(private http: HttpClient) {}
//This fetch the json present in assets folder
url: string = `${window.location.origin}/assets/urls-list.json`;
getRestJson() {
this.http.get(url);
}
url.setting.ts
#Injectable({providedIn: 'root'})
export class UrlService {
fetchedJson: Array<any>;
constructor(private service: JsonService) {
this.fetchingJsonFromService();
}
fetchingJsonFromService() {
this.service.getRestJson().subscribe(
response => {
this.fetchedJson = response;
});
}
private static getValue(key: string) {
// use the key do some stuff with "this.fetchedJson"
and return only that value back to getServiceUrl()
return something;
}
public static getServiceUrl(key:string) {
const result = UrlService.getValue(key);
return result;
}
}
From debugging this code realized that "getValue()" is called first and the "this.fetchedJson" is undefined. Then the call goes to "fetchingJsonFromService()" where fetchedJson gets initialized which is too late. Kindly help me to solve this issue. It would be of great help.
"getServiceUrl(key)" - is called by multiple services throughout the application
Thanks a lot!! 😊
As getRestJson() is async you'll need to execute getValue() when it's response has arrived, which You can simply do by calling getValue() inside your fethcingJsomFromService()
fetchingJsonFromService() {
this.service.getRestJson().subscribe(
response => {
this.fetchedJson = response;
this.getValue();
});
}
private getValue() {
// do some stuff with "this.fetchedJson" and return only that value to other method
}
Related
I am trying to use a DataService property myData that is waiting for callback. But it is undefined when I call in DataComponent. How can I access and use it there?
export class DataService {
public myData;
constructor(private http: HttpClient) {
this.load().then((data) => {
this.myData = data
})
}
load() {
return new Promise((resolve) => {
this.http.get('https://reqres.in/api/users').subscribe(
(res: any) => {
console.log(res.data)
resolve(res.data)
},
(error) => {
console.log(error);
}
)
})
}
}
export class DataComponent implements OnInit {
constructor(private dataService: DataService) {
this.prepareData();
}
prepareData() {
console.log(this.dataService.myData)
}
ngOnInit(): void {
}
}
Here is the source code: https://stackblitz.com/edit/angular-ivy-kbpdpo
You are running into a race condition since this is an asynchronous function.
This change works: https://stackblitz.com/edit/angular-ivy-vf3llg
Consider reading up on https://angular.io/guide/http
Personally, I just have services return raw data and manipulate it elsewhere, but if needed you can tap into the response as I have shown i the updated example.
This question and answer are probably really a duplicate of this question...
What are pipe and tap methods in Angular tutorial?
your load() method is asynchronous, that means that it can return the response after 2 hours, so it will execute your callback after 2 hours, and you are asking myData synchronously which means that you are asking it right now, so it won't work.
you have to wait until the answer is returned, in your code there is no chance to accomplish this, so either remove yourData field and just subscribe it into the component, or create BehaviorSubject and emit value to the component
I follow this guide, and i try to do something similar at Unrelated Components: Sharing Data with a Service paragraph
Data Service:
#Injectable()
export class MyDataService{
private messageSource = new BehaviorSubject(null);
currentMessage = this.messageSource.asObservable();
constructor(private http: HttpClient) {
setInterval(() => { this.changeMessage(this.resultFromRestCall()); }, 10 * 1000);
}
changeMessage(message: object) {
this.messageSource.next(message);
}
resultFromRestCall(){
const json;
this.http.get<object>(myApiUrl).subscribe(res =>
json['data'] = res['data'] //this is an example
);
return json;
}
Component:
export class MyComponent implements OnInit {
constructor(private dataservice: MyDataService) {}
ngOnInit() {
this.dataservice.currentMessage.subscribe(
message => {this.handleVarChange(message); }
);
}
handleVarChange(message) {
console.log(message.data);
}
With this code i got "undefined" in handleVarChange log
Instead of calling this.handleVarChange(message); in subscribe I write console.log(message) i got my result correctly.
So, my question is if it's possible use the value coming from data service in some function of my component.
Thanks in advance
With:
resultFromRestCall(){
const json;
this.http.get<object>(myApiUrl).subscribe(res =>
// takes x amount of time to populate json
json['data'] = res['data'] //this is an example
);
// executed instantly after above request has been called
return json;
}
You are returning json before it has been populated, since the request is asynchronous.
Instead you can flip it around a bit, and call resultFromRestCall() first, and when you get the response, then call changeMessage():
setInterval(() => {
this.resultFromRestCall().subscribe((data) => {
this.changeMessage(data);
});
}, 10 * 1000);
where resultFromRestCall simply returns an observable:
resultFromRestCall(){
return this.http.get<object>(myApiUrl);
}
Also remember to clearInterval in OnDestroy!
DEMO
Omit the .data in handleVarChange:
Instead of
handleVarChange(message) {
console.log(message.data);
}
write
handleVarChange(message) {
console.log(message);
}
Imagine we have the following factory:
#Injectable()
export class DataClassFactory {
constructor(
private dataService: DataService,
) { }
public createThing(initialData?: InitialData): AsyncSubject<DataClass> {
let dataClass: AsyncSubject<DataClass> = new AsyncSubject<DataClass>();
if (!!initialData) {
dataClass.next(new DataClass(initialData));
dataClass.complete();
} else {
this.dataService.getData().subscribe((dataResponse) => {
dataClass.next(new ReportRequest(dataResponse));
dataClass.complete();
});
}
}
return dataClass;
}
}
We inject this factory, invoke the createThing method, and subscribe to the response in some component. I originally tried to use a plain Subject, but then I realized that in the case where we already have initial data, next() is called before the response is returned, so the subscriber in the component never gets that value.
My question is: is this correct situation in which to use an AsyncSubject, or is there a different/better way to handle this sort of method that has potential synchronous and asynchronous timelines?
I would do something along these lines
public createThing(initialData?: InitialData): Observable<DataClass | ReportRequest> {
if (!!initialData) {
const data = new DataClass(initialData);
return of(data);
} else {
return this.dataService.getData()
.pipe(map(dataResponse => new ReportRequest(dataResponse));
}
}
Whoever calls createThing would get an Observable to which it would have to subscribe.
This Observable would emit an instance of DataClass if initialData is not null, otherwise it would return and instance of ReportRequest as soon as dataService responds.
I am having an issue with my variables being undefined. I am certain this is because the observable hasn't finished. Here is the part of my code in my .ts file that is causing the issue. (I'm placing the minimum code required to understand the issue. Also myFunction gets called from a click event in the HTML).
export class myClass {
myVariable: any;
myFunction() {
this.myService.getApi().subscribe(data => {
this.myVariable = data;
});
console.log(myVariable) --> undefined
}
}
So this piece of code calls a function in my service that returns some data from an API. The issue is that when I try to access the variable myVariable right outside of the subscribe function it returns undefined. I'm sure this is because the subscribe hasn't finished before I try to access myVariable
Is there a way to wait for the subscribe to finish before I try to access myVariable?
why not create a separate function and call it inside the subscription.
export class myClass {
myVariable: any;
myFunction() {
this.myService.getApi().subscribe(data => {
this.myVariable = data;
this.update()
});
this.update()
}
update(){
console.log(this.myVariable);
}
}
As you know subscriptions are executed when server return data but the out side of subscription code executed synchronously. That is why console.log outside of it executed. The above answer can do your job but you can also use .map and return observable as shown below.
let say you are calling it from s service
export class myClass {
myVariable: any;
// calling and subscribing the method.
callingFunction() {
// the console log will be executed when there are data back from server
this.myClass.MyFunction().subscribe(data => {
console.log(data);
});
}
}
export class myClass {
myVariable: any;
// this will return an observable as we did not subscribe rather used .map method
myFunction() {
// use .pipe in case of rxjs 6
return this.myService.getApi().map(data => {
this.myVariable = data;
this.update()
});
}
}
I am working on an Angular4 app.
Here is a service I am using to get the data-
export class BookingService {
constructor(private http: HttpClient) {}
getMemberBookings(memberID: number): Observable<any> {
return this.http.get('http://myapi.com/bookings/member/'+memberID).map(response => response['bookings']);
}
}
And then in my component-
export class PrintButtonComponent implements OnInit {
bookings: any;
constructor(private service: BookingService) {}
ngOnInit() {}
downloadPDF() {
this.getBookings(memberID);
//pdf creation logic
}
getBookings(memberID: number) {
this.service.getMemberBookings(memberID).subscribe(data => this.bookings = data);
}
}
The problem is I want to use the data from the service in the downloadPDF method as there is other data in it that will be needed to create the PDF.
But when I return the data from the subscribe or set it to a property, it is giving undefined. I understand that this is due to asynchronous nature, but I dont want to put my pdf creation logic inside the subscribe method.
So how do I solve this problem? I am quite new to Angular and observables.
Thank you.
Since the code above doesn't involve multiple values per observable and doesn't require to stick to them, this can be done with async..await with no extra nesting:
async downloadPDF() {
await this.getBookings(memberID);
//pdf creation logic
}
async getBookings(memberID: number) {
this.bookings = await this.service.getMemberBookings(memberID).toPromise();
}
As any other RxJS operator, toPromise has to be imported.