Angular/Rxjs get sync data - javascript

Whether there method getting data sync from http.get ? I want use data inside component in order to set router.resetConfig().
Data from http.get = [{path: 'site/a', component: Component}, {path: 'site/b', component: Component}...].
I tried to use BehaviorSubject, next(), switchMap but always I must to use .subscribe().
class A {
data = new BehaviorSubject<any>();
url = 'url';
constructor() {
this.getUrl();
console.log(this.data.value);
}
getUrl() {
this.http.get(this.url).pipe(
map(result => {
this.data.next(result)
}));
}
}
If I use subscribe()
this.http.get(this.url).pipe(map(result => result)).subscribe(data => {this.data.next(data)});
If console.log(this.data) value exist, console.log(this.data.value) not exist.
I want to get:
router.resetConfig(this.data.value);

I think you're looking for APP_INITIALIZER. I can not found a good example but basically you defined a function in a service that return a promise
//in your service
loadData() {
return this.httpClient.get(...)
.toPromise()
.then(res=>{
this.data=res //<--here you store the response
}
in your module.ts
export function init_app(myService: MyService) {
return () => myService.loadData();
}
#NgModule({
declarations: [...],
imports: [...],
providers: [
...,
{ provide: APP_INITIALIZER, useFactory: init_app, deps: [MyService], multi: true }
],
bootstrap: [AppComponent]
})
So, you can use myService.data in any component that inject the service because the first time Angular get the data

The point is to download synchronized data, router.resetConfig({must be sync data}).
I was thinking about emitting the last value and saving it somehow. I'm trying to use .subscribeOn(), .observeOn() and Scheduler.

The data you get from your http.get is an array. You don't need to modify any data in the array instead you just want to pass the data as is. So what you need to do instead is just pass an observable from your getUrl function and subscribe to it where you want to get the data. Since http.get returns an Observable, you can just return your http.get in getUrl.
I would also suggest you move the logic from your constructor to your ngOnInit as that's the best practice. (Source: docs)
import { Router, Routes } from '#angular/router';
class A {
url = 'url';
constructor(
private router: Router
) { }
ngOnInit() {
this.getUrl().subscribe((data: Routes) => {
console.log(data);
this.router.resetConfig(data);
});
}
getUrl(): Observable<Routes> {
return this.http.get(this.url);
}
}
It would also be suggested to move all your http calls to a service. (Source: docs)
Here is a working example on StackBlitz.

Related

Angular Test Jest - TypeError: Cannot read property 'queryParams' of undefined

I've seen the same error in other posts but they didn't work for me.
I have an Angular component, where I need to read a queryParam from the url, for example in http://localhost:4200/sample-page?page=3 I want to stores the number 3 into a component local variable.
/**
* Set page by url parameter
*/
export const setPaginationMarkByUrlParam = (activatedRoute): number => {
// Get page param from Url to set pagination page
const pageParam = activatedRoute.snapshot.queryParams;
return pageParam.page ? Number(pageParam.page) : 1;
};
This function is in another file and I put as parameter the activeRoute, which comes from the ngOnInit of the component in which I want to get the queryParam.
ngOnInit() {
this.page = setPaginationMarkByUrlParam(this.activatedRoute);
}
This code works perfectly, but when the Jenkins pipeline runs the npx jest tests, I get the following message:
TypeError: Cannot read property 'queryParams' of undefined
My Spec.ts:
beforeEach(() => {
TestBed.configureTestingModule({
...,
providers: [
{
provide: ActivatedRoute,
useValue: {
data: {
subscribe: (fn: (value: Data) => void) =>
fn({
pagingParams: {
predicate: 'id',
reverse: false,
page: 0
}
})
}
}
}
]...
it('Should call load all on init', () => {
// GIVEN
const headers = new HttpHeaders().append('link', 'link;link');
spyOn(service, 'query').and.returnValue(
of(
new HttpResponse({
body: [new DataSource(123)],
headers
})
)
);
// WHEN
comp.ngOnInit();
// THEN
expect(service.query).toHaveBeenCalled();
expect(comp.dataSources[0]).toEqual(jasmine.objectContaining({ id: 123 }));
});
The test fails in comp.ngOnInit(); function.
I don't have any kind of private variables, the activeRoute that comes as a parameter, I tried it with public and private.
Looking at both StackOverflow and GitHub Issues I have not been able to fix this problem.
Thank you very much!
While you are mocking data, you are not mocking snapshot on ActivatedRoute. You have three choices to accomplish this:
First, you should consider using an ActivatedRouteStub as described in the docs. This then makes is as easy as: activatedRoute.setParamMap({page: 3}); to set any queryParameter you want to set. This option requires more test code
Next option: this would mock the queryParameter of page on the ActivatedRoute with an Observable:
provide: ActivatedRoute, useValue: {
snapshot: of(queryParams: { page: 3 }),
}
If, for a reason not disclosed in your question, you do not need an Observable from your ActivatedRoute, this would be the alternate code:
provide: ActivatedRoute, useValue: {
snapshot: { queryParams: { page: 3 } }
}
Finally, the code you provided doesn't have a call to inject for the ActivatedRoute provider nor does it show the test component creation. So at a minimum ensure you are doing that as well:
Either:
fixture = TestBed.createComponent(...);
Or:
activatedRoute = TestBed.inject(ActivatedRoute);
If none of these suggestions solve your problem, put a a minimal StackBlitz that demonstrates the problem and we'll get it working.
Generally for configuring/mocking different RouteOptions when you have ActivatedRoute with Jest you should use:
createRoutingFactory from #ngneat/spectator/jest
and pass one or more of the RouteOptions properties like (params, queryParams, parent etc.) directly on its constructor
For example:
const createComponent = createRoutingFactory({
component: Component,
imports: [RouterTestingModule],
parent: {
snapshot: {
queryParamMap: convertToParamMap({
//...
}),
paramMap: convertToParamMap({
//...
})
}
}
});

Testing an rxjs get method(service), on the component

The function call on the service is like so.
get getLayerToEdit(): BehaviorSubject<VectorLayer> {
return this.layerToEdit;
}
Which is then invoked on the ngOnInit like so.
ngOnInit() {
this.annoService.getLayerToEdit.subscribe((layerToEdit: any) => {
this.layer = layerToEdit;
this.layerId = layerToEdit.ol_uid;
});
So naturally in my test I'm wanting to see if the component.layer matches what this service returns.
Test File
let component: EditFeatureComponent;
let fixture: ComponentFixture<EditFeatureComponent>;
let mockAnnoService = jasmine.createSpyObj(['getLayerToEdit', 'getCurrentAction', 'setCurrentAction']);
let layer;
The first before each code block
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [EditFeatureComponent],
imports: [FormsModule, HttpClientModule],
providers: [
{ provide: AnnotationService, useValue: mockAnnoService}
],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
.compileComponents();
}));
beforeEach(() => {
// Fields required for the component
layer = {
layerName: 'testLayer',
ol_uid: 1
}
// Create the testbed
fixture = TestBed.createComponent(EditFeatureComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
And then the actual test
it('Should return a layer object and have a ol_uid of 1', ()=>{
mockAnnoService.getLayerToEdit.and.returnValue(of(layer))
fixture.detectChanges();
expect(component.layer).toBe(layer);
})
The test returns 'this.annoService.getLayerToEdit.subscribe is not a function'
I've also tried using subscribe rather than of.
mockAnnoService.getLayerToEdit.and.returnValue({ subscribe: () => {} })
I'm pretty ill informed on testing but this is the approach I'd normally take to test services but it's my first time testing a 'get' method from a service so I'm assuming that I'm going wrong somewhere.
Your mockAnnoService.getLayerToEdit is getter so you cannot mock it using jasmine.createSpyObj because it mocks only methods in that way.
You need to create plain object instead of mock and assign observable with needed data to the getLayerToEdit field.
Hope that helps.

angular load local file then make http calls to return observables

I am new to Angular, JS, and observables. I have a typescript class called DataService. I want it to load a list of URLs from a JSON formatted local file, and then have some way to call those URLs (to a handful of REST APIs) and return observables. The problem I am having is my code is not waiting for the config file to be loaded before the REST API functions get called.
I thought I could have the DataService constructor load the configuration file, and then have unique functions for each REST API call, but that isn't working
my code:
export class DataService {
configFile
constructor(private http: HttpClient) {
this.http.get('/assets/restApiUrlListConfig.json').subscribe(config => {
this.configFile = config;
});
}
getUrlFromConfigFile(name: string): string {
...
this returns the URL from the config file
...
}
getUrlAData(): Observable {
return this.http.get( getUrlFromConfigFile('A') )
}
}
My other components have code like this:
export class SomeComponent implements OnInit {
someComponentAData
constructor(private data: DataService) { }
ngOnInit() {
this.data.getUrlAData().subscribe(
data => {
this.someComponentAData = data
}
)
}
I am getting an error that the observable returned from the dataservice is undefined. Which I believe is because the constructor hasn't finished loading the config file, which I think is why the function getUrlAData isn't returning anything.
I feel like I'm not correctly handling these async calls, but I'm at a loss for how to tell my code to :
create the data service object
load the data file before anything else can be done
allow the other functions to be called asyncronously AFTER the config file is loaded
Angular CLI: 6.2.3
Node: 8.12.0
OS: win32 x64
Angular: 6.1.8
Edit 1: attempting to implement suggested solution
My DataService
configFile
configObservable: Observable<any>;
someSubscribeObj
constructor(private http: HttpClient) {
this.someSubscribeObj = this.http.get('/assets/restApiUrlListConfig.json').subscribe(config => {
this.someSubscribeObj = undefined;
this.configFile = config;
});
}
getObsFromConfigFile(name: string): Observable<any> {
//...
if (this.configFile != undefined) {
console.log('this.restApiUrlListConfig[name]',this.configFile[name])
return of(this.configFile[name])
}
else
return of(this.someSubscribeObj.pipe(map(c => c[name])))
//this.configObservable
//...
}
getUrlAData(): Observable<any> {
return this.getObsFromConfigFile('A').pipe(mergeMap(url => this.http.get(url)))
}
My other component:
constructor( private data: DataService ) { }
ngOnInit() {
//this.data.loggedIn.pipe((p) => p);
this.data.getUrlAData().subscribe(
data => {
this.urlAData = data
}
)
}
I was unable to store the "subscribe" into the observable, so I created a generic Any type varable, but at runtime I get a problem with the pipe command:
TypeError: this.someSubscribeObj.pipe is not a function
at DataService.push../src/app/services/data.service.ts.DataService.getObsFromConfigFile
(data.service.ts:67)
at DataService.push../src/app/services/data.service.ts.DataService.getUrlAData
(data.service.ts:74)
Edit 2: the unfortunate workaround
I am currently using two nested subscriptions to get the job done basically
http.get(config_file_url).subscribe(
config => {
http.get( config['A'] ).subscribe( adata => { do things };
http.get config['B'].subscribe( bdata => {do things };
}
)
I feel like I should be able to use a mergeMap of some sort, but I couldn't get them to work as I thought they would.
You need to wait on that async call, I would use a flatmap to get the value out of an observable.
export class DataService {
configFile
configObservable: Observable<any>;
constructor(private http: HttpClient) {
this.configObservable = this.http.get('/assets/restApiUrlListConfig.json').pipe(
map(config => {
this.configObservable = undefined;
this.configFile = config;
return configFile;
})
);
}
getUrlFromConfigFile(name: string): Observable<string> {
...
return of(configFile[name]) if configFile is set else return configObservable.pipe(map(c => c[name]));
...
}
getUrlAData(): Observable<string> {
return this.getUrlFromConfigFile('A').pipe(map(url => this.http.get(url)))
}
}
Basically you want to store the observable and keep using it till it completes, after it completes you can just wrap the config in an observable. The reason for wrapping it is to make the interface consistent, otherwise you have to have an if before every get.

error TS2314: Generic type 'Promise<T>' requires 1 type argument(s)

I have used Promise and observables logic to fetch data from server using "get".
It was working till yesterday. SUddenly it starts throwing the above error.
Please help me finding the error.
I am getting "Generic type 'Promise' requires 1 type argument(s)" error.
#Injectable()
export class myBlogService{
// Property to hold root server URL i.e host
private serverUrl:string = "app/data.json"
constructor(private http:Http) {}
// check function in service to check control is coming to service
check(){
alert("getting clicked from service");
}
// get function to get data from server
// basically blog datas
get(): Promise {
return this.http.get(this.serverUrl)
.map(response => response.json())
}
}
/**
*
* My Components
*
*/
#Component({
selector: 'my-app',
providers: [myBlogService],
styleUrls: ['app/css/app.css'],
template: `
<h1 (click)= check()>My First Angular 2 App</h1>
<button (click)=get()>Get My Name</button>
<h1>{{getResponse.name}}</h1>
`
})
export class myBlogApp {
// Property to hold blog data
public getResponse = {"name": "", "age": ""};
constructor(protected myblogservice:myBlogService){}
// check function to check control is going to service
check() {
this.myblogservice.check();
}
// get function calls service get function which return data from server
get(){
this.myblogservice.get().subscribe(data => {
this.getResponse = data;
});
}
}
/**
*
* NgModule Declaration
*
*/
#NgModule({
imports: [ BrowserModule, HttpModule ],
declarations: [ myBlogApp ],
providers: [ ],
bootstrap: [ myBlogApp ]
})
export class app{}
/**
*
* App engine entry point
*
*/
const platform = platformBrowserDynamic();
platform.bootstrapModule(app);
When "promise: " is given, still it gives issue like
"error TS2339: Property 'subscribe' does not exist on type 'Promise'".
I tried different solution but no luck yet.
You need to add the specific type.
If it contains no data and is being used purely for the resolve/reject functionality, use:
Promise<void>
Ultimately this is a type signature like any other, so you can use:
Promise<any>
https://basarat.gitbooks.io/typescript/content/docs/promise.html
Instead of using Promise try to use Observable, replace:
get(): Promise {
return this.http.get(this.serverUrl)
.map(response => response.json())
}
with
get(): Observable<any> {
return this.http.get(this.serverUrl)
.map(response => response.json())
}

How can I call a class method inside a promise in Angular 2?

If I have an Angular 2 component and I get data from a service that returns an async promise or observable how can I then call a method in the component to display that data?
#Component({
moduleId: module.id,
selector: 'charts',
templateUrl: 'charts.component.html',
providers: [DataService]
})
export class ChartsComponent implements OnInit {
constructor(private dataService:DataService)
ngOnInit() {
this.getData();
}
getData(){
this.dataService.getData().then(function (data) {
this.drawChart(data);
});
}
drawChart(){
//implement drawing chart
}
}
The problem is that inside a promise "this" in "this.drawChart()" no longer refers to the ChartsComponent class. How can I call a class method post promise?
Also, I cant put drawChart() inside the promise because it needs to use other class properties.
When you use Arrow functions, the this is kept:
getData(){
this.dataService.getData().then((data) => { // <-- changed here
this.drawChart(data);
});
}
There are 2 solutions:
1) using "self":
var self = this;
ngOnInit() {
self.getData();
}
getData(){
self.dataService.getData().then(function (data) {
self.drawChart(data);
});
}
2) using "bind method" (or something like that):
.then(function (data) {
this.drawChart(data);
}).bind(this)
you can find so much information about this method, for example: Use of the JavaScript 'bind' method
I prefer first solution, because it helps make code more transparent.

Categories