Angular : How to handle the async set of a variable - javascript

Under my Angular 6 app , i have this service ; where i'm declaring a varibale called permittedPefs , this variable is setted asychronsouly within a httpClient.get call.
#Injectable()
export class myService implements OnInit {
permittedPefs = [];
constructor(){}
ngOnInit() {
// STEP 1
this.loadUserPefsService.getUserRolePefs(roleId).subscribe(
(returnedListPefs) => {
this.permittedPefs = returnedListPefs;
},
error => {
console.log(error);
});
}
// STEP 2
this.myMethod(1);
After that , i ve a call of this method which is using my -supposed setted - var
myMethod(pefId): boolean {
return this.permittedPefs.includes(pefId);
}
the problem is it seems that permittedPefs , haven't got its value yet , and the call of myMethod() point to a wrong value of "permittedPefs"
So what's the simpliest way to make it wait to the just after the http response , without calling if from the http Response callback (as i 'm using it in several places)
Sugesstions??

Asynchronous Hell ! the best choice here is to get an Observable instead of a value
in your service :
getValue (): Observable<any>{
return this.loadUserPefsService.getUserRolePefs(roleId);
}
in your method :
myMethod(pefId): boolean {
this.yourservice.getValue().subscribe(
data => {
if(data){
return data.includes(pefId);
}
});
}

This happens because your method is called when you have not received the result yet. so just move the function call in subscribe function

Call the method this.myMethod(1); from subscription block, so that you wait for asynchronous call to be completed, which will then set the value of permittedPefs.
ngOnInit() {
// STEP 1
this.loadUserPefsService.getUserRolePefs(roleId).subscribe(
(returnedListPefs) => {
this.permittedPefs = returnedListPefs;
// STEP 2
this.myMethod(1); // check this line
},
error => {
console.log(error);
});
}

Related

How to call a http Observable inside a subscription to another Observable?

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);
}
)

typescript/angular pass value out of this?

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.

Data set in observable not updating in template

I'm trying to learn Angular 2 and am rebuilding an Angular 1 app I've made with Angular 2 using the Angular CLI. I've setup a HTTP GET request, which fires successfully, and setup a subscriber to interpret the result, and console logging in the subscriber function shows the data I expect. However, no data is being updated on the template.
I tried setting the data to an initial value, to a value in the ngOnInit, and in the subscriber function, and the initial and ngOnInit update the template accordingly. For the life of me, I can't figure out why the template won't update on the subscribe.
events: any[] = ['asdf'];
constructor(private http: Http) {
}
ngOnInit() {
this.events = ['house'];
this.getEvents().subscribe(this.processEvents);
}
getEvents(): Observable<Event[]> {
let params: URLSearchParams = new URLSearchParams();
params.set('types', this.filters.types.join(','));
params.set('dates', this.filters.dates.join(','));
return this.http
.get('//api.dexcon.local/getEvents.php', { search: params })
.map((response: Response) => {
return response.json().events;
});
}
processEvents(data: Event[]) {
this.events = ['car','bike'];
console.log(this.events);
}
The data is being displayed via an ngFor, but car and bike never show. Where have I gone wrong?
You have gone wrong with not respecting the this context of TypeScript, if you do stuff like this:
.subscribe(this.processEvents);
the context get lost onto the processEvents function.
You have to either bind it:
.subscribe(this.processEvents.bind(this));
Use an anonymous function:
.subscribe((data: Events) => {this.processEvents(data)});
Or set your method to a class property:
processEvents: Function = (data: Event[]) => {
this.events = ['car','bike'];
console.log(this.events);
}
Pick your favourite, but I like the last option, because when you use eventListeners you can easily detach them with this method.
Not really sure with what's going on with that processEvents. If you want to subscribe to your response just do:
this.getEvents()
.subscribe(data => {
this.events = data;
});

Set global variable of class from inside a promise Angular 2

I am facing a weird issue in assigning response to a class's global variable from inside a observable. So my program logic is as follows:
Get latest playlists ID's from elastic search (i use elastic search from a type definition file). This returns me a PromiseLike to which i hook a then operator.
Inside the promise resolution, i make another http get call (i.e an observable)
In Observable subscription, i assign my global array with the response from the server.
Code is working correctly, I am getting responses as they should be but i cant assign the variable to the global one.
Here is my code:
import {Component, OnInit} from '#angular/core';
import {PlaylistService} from '../api/services'
#Component({
selector: 'app-playlists',
templateUrl: './playlists.component.html',
styleUrls: ['./playlists.component.css']
})
export class PlaylistsComponent implements OnInit {
public playlists: any[] = [];
constructor(private playlistService: PlaylistService) {
}
ngOnInit() {
let that = this;
this.playlistService.listIds().then((val) => { // <-- promise resolution
return this.playlistService.getByIds(val).toPromise(); // <-- http get call which i then convert to promise for simplicity
}).then((res) => { // <-- resolution of the http get call
console.log(this.playlists); <-- in this log, i get my desired results
// here is my problem, this assignment doesn't happens
this.playlists = res.data;
});
}
}
The listIds function is as follows:
listIds() {
return this.api.listing('playlist').then((body) => {
let hits = body.hits.hits;
return _.keys(_.groupBy(hits, '_id'));
});
}
and here is my api.listing function (elastic search client)
listing(type: string) {
let es = this.prepareES();
return es.search({
index: 'test',
_source: ["_id"],
type: type
});
}
The return type of es.search is
search(params: SearchParams): PromiseLike>;
Any ideas why i am not being able to assign value to global variable?
It looks like the promise returned by this.playlistservice.listIds() doesn't run inside Angulars zone. This is why Angular2 doesn't run change detection and doesn't recognize the change.
You can invoke change detection explicitly after the change:
constructor(private playlistService: PlaylistService, private cdRef:ChangeDetectorRef) {
...
ngOnInit() {
let that = this;
this.playlistService.listIds().then((val) => { // <-- promise resolution
return this.playlistService.getByIds(val).toPromise(); // <-- http get call which i then convert to promise for simplicity
}).then((res) => { // <-- resolution of the http get call
console.log(this.playlists); <-- in this log, i get my desired results
// here is my problem, this assignment doesn't happens
this.playlists = res.data;
this.cdRef.detectChanges();
});
}
Can you try passing
this.playlistService.listIds()
call inside your
return this.playlistService.getByIds(val)
replace val with first service call and see if your view gets updated. Just for testing purpose like
return this.playlistService.getByIds(this.playlistService.listIds())
.then((results)=>{/*rest of logic here*/});

Chained Promis in angularjs

I am new to angular and I am struggling to see how I should create a particular promise. I am also using typescript for coding.
I need to call a web service to authenticate this has to be done in 2 steps.
Step 1
Request an authentication key
Step 2
Process logon details with authentication key and return to service to retrieve the logged on user or error if incorrect
So I have created an angular service called AuthenticationService
like below (this of course doesn't work)
export class Authentication
{
$inject: string[] = ['$http'];
public Authenticate( userName: string, password: string ): ng.IHttpPromise<ApiModel.ApiResult<ApiModel.AuthenticatedUser>>
{
$http.post( "/Account/AuthenticationKey", null )
.then<ApiModel.ApiResult<string>>( result =>
{
var strKey = userName + password; //TODO: Put correct code here!
var promiseToReturn = $http.post( "/Account/Authenticate", { key: strKey })
.then<ApiModel.ApiResult<ApiModel.AuthenticatedUser>>( result =>
{
return result;
});
});
}
}
How do I go about returning a promise with the correct return type from the authentication method that returns the second result?
I can tell you that in JavaScript as i am not conversant with typescript. The idea is to create your own promise and resolve it whenever you want. Basically
var autenticate=function(user,password) {
var defer=$q.defer();
$http.post( "/Account/AuthenticationKey", null )
.then(function(data) {
//do something on data
$http.post( "/Account/Authenticate", { key: strKey })
.then(function(result) {
defer.resolve(result)
});
})
return defer.promise;
}
A then function should always either return another promise or a return value. The final then function should return a value which will get propagated to the top.
The relevant documentation can be found here :
https://github.com/kriskowal/q
NOTE : Angular's promise implementation is based on kriskowal's q.
This is the relevant section of the documentation :
If promiseMeSomething returns a promise that gets fulfilled later with
a return value, the first function (the fulfillment handler) will be
called with the value. However, if the promiseMeSomething function
gets rejected later by a thrown exception, the second function (the
rejection handler) will be called with the exception.
In your case, you should do something like
export class Authentication
{
$inject: string[] = ['$http'];
public Authenticate( userName: string, password: string ): ng.IHttpPromise<ApiModel.ApiResult<ApiModel.AuthenticatedUser>>
{
return $http.post( "/Account/AuthenticationKey", null )
.then<ApiModel.ApiResult<string>>( result =>
{
var strKey = userName + password; //TODO: Put correct code here!
return $http.post( "/Account/Authenticate", { key: strKey })
.then<ApiModel.ApiResult<ApiModel.AuthenticatedUser>>( result =>
{
return result;
});
});
}
}
Notice the two returns before the $http.posts that you are calling. All $http methods in Angular return a promise, which means you dont need to explicitly create another promise.

Categories