show the page after getting two different service calls - javascript

i want to show the page after getting the results of two
different service calls.service1 and service2 are two different
services
don't want to use second service call inside of first service
subscribe.service1 and service2 are two different services.
this.service1.getProfile1(id).subscribe((data1) => {
console.log(data1);
});
this.service2.getProfile2(id).subscribe((data2) => {
console.log(data2);
});
how to i found i got both service calls ?

You can use forkJoin from rxjs https://www.learnrxjs.io/operators/combination/forkjoin.html
import { forkJoin } from 'rxjs';
forkJoin(
this.service1.getProfile1(id),
this.service2.getProfile2(id)
).subscribe(([profile1, profile2]) => {
console.log(profile1, profile2);
});

You can merge the two observables with a fork join in this way:
import { forkJoin } from 'rxjs';
forkJoin([
this.service1.getProfile1(id),
this.service2.getProfile2(id),
]).subscribe(r => {
const data1 = r[0];
const data2 = r[1];
console.log(data1);
console.log(data2);
});
The calls are serialized, and then observable returns with an array of results. The position of the items reflects the order you created the forkJoin.

Actually in this particular use case you are navigating the user and trying to make a service call with two endpoints. Once you receive a response from those end point you are trying to merge it with fork join and send it to component as a observable
But in your question you are looking for a way to make the http calls even before redirecting to the page. There is a good approach for this use case in angular router.
You can specify what is the http calls which you want to perform even before user taken to a page.
Implement resolve in your service level and define that service under route resolve.
Example :
import { Injectable } from '#angular/core';
import { APIService } from './api.service';
import { Resolve } from '#angular/router';
import { ActivatedRouteSnapshot } from '#angular/router';
#Injectable()
export class APIResolver implements Resolve<any> {
constructor(private apiService: APIService) {}
resolve(route: ActivatedRouteSnapshot) {
return this.apiService.getItems(route.params.date);
}
}
Routes :
{
path: 'items/:date',
component: ItemsComponent,
resolve: { items: APIResolver }
}

Related

How can I mock nested function with Jasmine& Angular

constructor(private searchService: SearchService, private storageService: StorageService) {
this.searchService.pagesOutput.subscribe(images => {
this.images = images;
this.pages = this.searchService.numberOfPages;
})
}
I have a service as a dependency injection. I would like to mock it for test.
I know how to mock service and some methods
searchServiceMock = jasmine.createSpyObj('SearchService', ['pagesOutput']);
searchServiceMock.pagesOutput.and.returnValue(someValue)
But how can I reach nested functions?(searchServiceMock.pagesOutput.subscribe?)
Since you're going to be subscribing to the value, if you return an observable, the subscribe will work. To mock an observable, I use of.
import { of } from 'rxjs';
...
searchServiceMock = jasmine.createSpyObj('SearchService', ['pagesOutput']);
searchServiceMock.pagesOutput.and.returnValue(of(/* whatever value you want to be for images in subscribe callback */))

Setting up ngrx/data for integration testing

I am trying to set up an integration test which will grab some data from a backend API service using ngrx/data entities.
I have this StackBlitz set up: https://stackblitz.com/edit/ngrxdata-testing-not-working-pxfmkb?file=src/main.ts
It should run tests on startup - there are no expectations in my test cases, however I am looking in the console logs and expecting it to show the log in ClientDataService (/src/app/data/client/client-data.service.ts), that is:
console.log('never goes here :(');
In the integration test (data.integration.spec.ts) I am configuring the module, defining the Client entity type and including the AppDataServiceModule which in turn does this:
import { NgModule } from '#angular/core';
import { ClientDataService } from './client/client-data.service';
import { EntityDataService, EntityDefinitionService } from '#ngrx/data';
#NgModule({
providers: [
ClientDataService,
],
})
export class AppDataServiceModule {
constructor(
entityDataService: EntityDataService,
clientDataService: ClientDataService
) {
entityDataService.registerService('Client', clientDataService);
}
}
As you can see I am registering the data service as suggested by the ngrx docs, here
I feel like I am pretty close but just need a nudge in the right direction to get it working!
A custom DataService has to extend the DefaultDataService. Should look something like this:
export class ClientDataService extends DefaultDataService<Client> {
constructor(
http: HttpClient, httpUrlGenerator: HttpUrlGenerator
) {
super('Client', http, httpUrlGenerator);
}
public getAll(): Observable<any> {
// get Data here
}
}
The BackendService has to return the Observable:
public getClients(): Observable<Array<Client>> {
// will be mocked
return of([
{
id: '1: Will not return as it will be mocked'
},
{
id: '2: Will not return as it will be mocked'
}
])
}
There are two more things which look suspicious to me:
There is no subscription in the code, so I assume your Observable is cold.
The clientResolve.resolve({}, {}) call expects an ActivatedRouteSnapshot as first parameter. I'm not so familiar with the Resolve interface but maybe thats an issue too.

NestJs - Unable to get user context in RolesGuard

I'm using NestJS as the framework for a client API. Within the framework we are using a pretty standard Passport/JWT auth infrastructure that is working fine. Our AuthGuard is firing when the bearer token is found and, in secure API endpoints, I can inject the HTTP context via '#Res() request' and get access to the 'request.user' property which contains the payload of my Jwt token.
On top of this we are attempting to implement a 'RolesGuard' in a very similar fashion to the sample code provided in the documentation and some of the sample projects on GitHub (none of which actually use this guard but they include it as a sample guard).
Our issue is that our AuthGuard fires and validates the Jwt token and THEN our RolesGuard fires but the request object it is passed does not have the user meta-data attached to the request.
The key code in our RolesGuard is:
const request = context.switchToHttp().getRequest();
const user = request.user;
if (!user) {
return false;
}
In the above snipped the user is always false. Has anyone written a role/permission based guard in Nest that successfully gets access to the scope of the current user? All the code is firing and everything appears registered correctly.
-Kevin
Ultimately this appears to be an ordering issue with the guards and it doesn't look like it can be easily resolved (without the framework allowing some control over the ordering).
My hope was to register the RolesGuard globally but that causes it to be registered first and fire first.
#UseGuards(AuthGuard('jwt'), RolesGuard)
#Roles('admin')
If I register it at the endpoint level and put it after the AuthGuard then it fires second and I get the user context I am expecting within the guard itself. It isn't perfect but it works.
-Kevin
register RoleGuard at the endpoint level and put it after the AuthGuard then it fires second and I get the user context I am expecting within the guard itself.
don't register RoleGuard at module causes it'll be registered first and fire first.
*.module.ts
imports: [],
providers: [{provide: APP_GUARD, useClass: RolesGuard} ,], // remove guard
controllers: [],
exports: [],
Make your RolesGuard extend AuthGuard('StrategyName') and then call super.canActivate for example:
#Injectable()
export class RolesGuard extends AuthGuard('jwt') {
async canActivate(context: ExecutionContext): Promise<boolean> {
// call AuthGuard in order to ensure user is injected in request
const baseGuardResult = await super.canActivate(context);
if(!baseGuardResult){
// unsuccessful authentication return false
return false;
}
// successfull authentication, user is injected
const {user} = context.switchToHttp().getRequest();
}
}
In other words you have to Authenticate first then Authorize
If anyone else stumbles across this question: putting multiple guards into one #UseGuards decorator works, but if you want to keep them separated (say, if you use a custom decorator), you can give the 2nd guard access to req.user by placing it before the #UseGuards call that puts the user on the request object, as in this example:
#RestrictTo(UserAuthorities.admin)
#UseGuards(JwtAuthGuard)
#Get("/your-route")
It seems that this is a consequence of how decorators work in TypeScript.
You can also use multiple roles for role-based Authentication.
In UserResolver
import { Args, Mutation, Query, Resolver } from '#nestjs/graphql';
import { UseGuards } from '#nestjs/common';
import { RolesGuard } from 'src/guards/auth.guard';
#UseGuards(new RolesGuard(['admin']))
#Resolver()
export class UserResolver { ... }
In RolesGuard
import { ExecutionContext, Injectable, UnauthorizedException } from '#nestjs/common';
import { ExecutionContextHost } from '#nestjs/core/helpers/execution-context-host';
import { GqlExecutionContext } from '#nestjs/graphql';
import { AuthGuard } from '#nestjs/passport';
#Injectable()
export class RolesGuard extends AuthGuard('jwt') {
constructor(private roles: string[] | null) {
super();
}
canActivate(context: ExecutionContext) {
const ctx = GqlExecutionContext.create(context);
const { req } = ctx.getContext();
return super.canActivate(new ExecutionContextHost([req]));
}
handleRequest(err: any, user: any, info: string) {
if (!this.roles) {
return user || null;
}
if (!user) {
throw new UnauthorizedException('Not Valid User.');
}
const role = user.role;
const doesRoleMatch = this.roles.some(r => r === role);
if (!doesRoleMatch) {
throw new UnauthorizedException('Not Valid User.');
}
return user;
}
}

add data to the end of a behavior object array Angular 5

I have some data that I want to be shared with my entire app so I have created a service like so..
user.service
userDataSource = BehaviorSubject<Array<any>>([]);
userData = this.userDataSource.asObservable();
updateUserData(data) {
this.userDataSource.next(data);
}
then in my component Im getting some data from an api and then sending that data to userDataSource like so..
constructor(
private: userService: UserService,
private: api: Api
){
}
ngOnInit() {
this.api.getData()
.subscribe((data) => {
this.userService.updateUserData(data);
})
}
now that all works but.. I want to be able to add data to the end of the array inside the userDataSource so basically the equivalent of a .push am I able to just call the updateUserData() function and add more data or will doing that overwrite what is currently in there?
Any help would be appreciated
You can add a new method to your service like addData in which you can combine your previous data with new data like.
import {Injectable} from '#angular/core';
import {BehaviorSubject} from 'rxjs/BehaviorSubject';
#Injectable()
export class UserService {
userDataSource: BehaviorSubject<Array<any>> = new BehaviorSubject([]);
userData = this.userDataSource.asObservable();
updateUserData(data) {
this.userDataSource.next(data);
}
addData(dataObj) {
const currentValue = this.userDataSource.value;
const updatedValue = [...currentValue, dataObj];
this.userDataSource.next(updatedValue);
}
}
For someone that may come accross this issue with a BehaviorSubject<YourObject[]>.
I found in this article a way to properly add the new array of YourObject
import { Observable, BehaviorSubject } from 'rxjs';
import { YourObject} from './location';
import { Injectable } from '#angular/core';
#Injectable({
providedIn: 'root'
})
export class ObjService {
private theObjData: BehaviorSubject<YourObject[]> = new BehaviorSubject<YourObject[]>(null);
constructor() {
}
public SetObjData(newValue: YourObject[]): void {
this.theObjData.next(Object.assign([], newValue));
}
}
How to update data:
// inside some component
this.api.userData().subscribe((results:YourObject) =>
this.objService.SetObjData(results);
)
How to observe changes on other component
// inside another component
ngOnInit() {
this.objService.GetAccountStatements().subscribe((results) =>
...
)
}
Normally Observables and Subjects are meant to be streams of data, not an assignment of data. BehaviorSubjects are different because they hold their last emitted value.
Normally Subjects or BehaviorSubjects inside of a contained class (like a Service) do not want to expose themselves publicly to any other classes, so it's best practice to access their properties with getters or methods. This keeps the data stream cold to all subscribers.
However, since the BehaviorSubject holds the last emitted value, there's a few options here. If all subscribers need a concatenated stream of data from every emission, you could access the last emitted value and append to it:
userDataSource = BehaviorSubject<any[]>([]);
userData = this.userDataSource.asObservable();
updateUserData(data) {
this.userDataSource.next(this.userDataSource.value.push(data));
}
...or, in what might be considered better practice, Subscribers to this Subject could do their own transformation on the stream:
this.api.userData()
.scan((prev, current) => prev.push(current). [])
.subscribe((data) => {
this.concatenatedUserData = data;
});
Use concat to add object
userDataSource = BehaviorSubject<Array<any>>([]);
updateUserData(data) {
this.userDataSource.next(this.userDataSource.value.concat(data));
}
Use filter to remove object
removeUserData(data) {
this.userDataSource.next(this.userDataSource.value.filter(obj => obj !== data));
}

Pattern for Retrieving Single Value from Angular 4 based Service

I am attempting to build an Angular 4 based service (backed by a C#-based RESTful API) which will allow for storing and retrieval of web-application wide settings. Something like a key-value pair based lookup for all common application settings.
The idea is this:
Retrieve all settings on start of the application from the C# WebApi based RESTful service into a client-side JavaScript array and stored in the Angular 4 service.
If any specific setting is needed, first look in the locally retrieved array for said setting and return that.
If said setting is not found, make a call to the previously mentioned WebAPI service for that specific setting to see if it is available and retrieve that. Also, push said retrieved setting in the client-side array so I don't have to make the call again until needed.
The problem I am having is this:
I want to return an Observable, even if I have the setting in the array locally, so that I can handle the situation of the web application having to wait for the setting to be retrieved.
I also want to handle situations where the API call for the specific setting fails.
See below for what I have now, any help appreciated.
'use strict';
import { Injectable } from '#angular/core';
import { Http, Response, Headers, RequestOptions } from '#angular/http';
import { Observable } from 'rxjs/Observable';
import { EmptyObservable } from 'rxjs/observable/EmptyObservable';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/share';
import { Subject } from 'rxjs/Subject';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { ReplaySubject } from 'rxjs/ReplaySubject';
import { SettingAPIURLs } from '../global.urls';
import * as Interfaces from "../interfaces/models.interface";
import * as Classes from "../classes/models.classes";
#Injectable()
export class SettingsService {
private _settings: BehaviorSubject<Classes.Setting[]>;
private settingDataStore: {
settings: Classes.Setting[]
}
constructor(private http: Http) {
this.settingDataStore = { settings: [] }
}
loadSettings() {
this.http.get(SettingAPIURLs.GetSettings)
.map(response => response.json())
.subscribe(data => {
this.settingDataStore.settings = data;
this._settings.next(Object.assign({}, this.settingDataStore).settings);
}, error => {
console.log("There were errors in attempting to retrieve the web application's settings: " + error);
});
}
get CurrentSettings() {
return this._settings.asObservable().share();
}
retrieveSetting(SettingName: string): Observable<Classes.Setting> {
/*
I am lost as to what to do here.
*/
let returnedObservable: Observable<Classes.Setting> = new Observable<Classes.Setting>();
if (typeof (SettingName) === "undefined" || SettingName === null) {
return new EmptyObservable();
}
this.http.get(SettingAPIURLs.GetSetting + "?SettingName=" + SettingName)
.map(response => response.json())
.first();
}
}
Angular has a built in solution for your problem called resolve. This article explains how to use it:
https://blog.thoughtram.io/angular/2016/10/10/resolving-route-data-in-angular-2.html
The idea is that you have the data for the page loaded before even running the logic for the page.
The basic implementation is that you need to write a resolver that will go ahead and make your settings api call. Then you just need to hookup your routing to start using the resolver.
If I understood correctly , you want to send the setting if it exists else do http call . I am assuming your settings object is a key-value pair object. I am not sure why you have it is an array.
You can do something like this
// extract fetch part separately . If settings exist return it or do http call
fetchSettings(){
return this._settings.asObservable
.flatMap(settings => settings ?
Observable.of(settings) :
this.http.get(SettingAPIURLs.GetSettings)
.map(response => response.json())
.do(data => {
this.settingDataStore.settings = data;
this._settings.next(
Object.assign(
{},
this.settingDataStore).settings);
})
.catch(error => {
console.log("..... " + error);
});
}
loadSettings(){
this.fetchSettings.subscribe(data => {
console.log('load Settings success', data);
});
}
retrieveSetting(SettingName: string): Observable<Classes.Setting> {
this.fetchSettings()
.flatMap(settings => settings[SettingName]?
Observable.of(settings[SettingName]) :
this.http.get(SettingAPIURLs.GetSetting +
"?SettingName=" + SettingName)
.map(response => response.json())
.do(res=> this._settings.value[SettingName] = res)
);
}
If any of the above HTTP calls fail you will get an exception which you can handle in the component like this :
this.settingsService.retrieveSetting().subscribe(successFunction, failureFunction);
You can either show error message to user or consider it as blank based on your site requirements.

Categories

Resources