I am trying to limit the number of unnecessary HTTP calls in my application but everytime I subscribe to an Observable there is a request being made to the server. Is there a way to subscribe to an observable without firing http request? My observable in service looks like that:
getServices(): Observable<Service[]> {
return this.http.get(this.serviceUrl).map(res => res.json())._catch(err => err);
}
Then in my component I subscribe to that observable like this:
this.serviceService.getServices().subscribe(services => this.services = services);
What I would like to achieve is to store data somehow on the service itself (so that I can use that data throughout the whole application without making requests on every component separately, but I would also like to know when that data is received by components (which I usually do by subscription).
Not sure if I understand your question correctly, but it seems that you want to cache the results of the first HTTP request in the service, so if the first component fetch the data, the second one (another subscriber) would retrieve the cached data. If that's the case, declare a service provider on the module level so Angular creates a singleton object. Then inside the service create a var that stores the data after the first retrieval.
#Injectable()
export class DataService{
mydata: Array<string>[];
constructor(private http:Http){}
getServices(): Observable<string[]> {
if (this.mydata){
return Observable.from(this.mydata); // return from cache
} else
{
return return this.http.get(this.serviceUrl).map(res => res.json())._catch(err => err);
}
}
}
Related
In my Angular service I am providing a public Observable called usageDoc$.
service.ts:
usageDoc$: Observable<IUsage>;
initializeUsageDoc() {
//gets called in app.component.ts on app load
...
//some async db querying
this.usageDoc$ = this.firestore.getUsageDoc(user.uid); //getUsageDoc(..) returns Observable<IUsage>
}
component.ts
localDoc: any;
ngOnInit() {
this.service.usageDoc$.subscribe(doc=>this.localDoc=doc);
}
This leads to the following error: cannot read property subscribe of undefined... as usageDoc$ is not yet set on the component init. Currently I am using a workaround by creating a second observable usageDocReady$ = new Subject<boolean> in service that emits as soon as usageDoc$ is set.
Is there better to solve this issue? Can I somehow initialize usageDoc$ with a default value?
I know I would not have this issue if I'd subscribe in template using the async pipe, but I need a normal variable for displaying matters, hence the use of localDoc.
I'd suggest using a ReplaySubject with buffer 1. It'd ensure future subscribers would get the last emission whilst avoiding any undefined errors.
private usageDocSrc = new ReplaySubject<IUsage>(1);
public usageDoc$ = this.usageDocSrc.asObservable();
initializeUsageDoc() {
//gets called in app.component.ts on app load
...
//some async db querying
this.firestore.getUsageDoc(user.uid).subscribe({
next: value => this.usageDocSrc.next(value)
});
}
If you don't wish to use a ReplaySubject, then I don't understand the need for the Observable. You could directly return the observable from getUsageDoc() function.
initializeUsageDoc(): Observable<IUsage> {
//gets called in app.component.ts on app load
...
//some async db querying
return this.firestore.getUsageDoc(user.uid);
}
Although this would invoke all the statements prior to getUsageDoc() for each subscription to initializeUsageDoc().
I do not understand the difference between a resolver and a service in a nestJS application using graphQl and mongoDB.
I found examples like this, where the resolver just calls a service, so the resolver functions are always small as they just call a service function. But with this usage I don't understand the purpose of the resolver at all...
#Resolver('Tasks')
export class TasksResolver {
constructor(
private readonly taskService: TasksService
) {}
#Mutation(type => WriteResult)
async deleteTask(
#Args('id') id: string,
) {
return this.taskService.deleteTask(id);
}
}
#Injectable()
export class TasksService {
deleteTask(id: string) {
// Define collection, get some data for any checking and then update dataset
const Tasks = this.db.collection('tasks')
const data = await Task.findOne({ _ id: id })
let res
if (data.checkSomething) res = Task.updateOne({ _id: id }, { $set: { delete: true } })
return res
}
}
On the other side I can put all the service logic into the resolver and just leave the mongodb part in the service, but then the services are small and just replacing a simple mongodb call. So why shouldn't I put that also to the resolver.
#Resolver('Tasks')
export class TasksResolver {
constructor(
private readonly taskService: TasksService
) {}
#Mutation(type => WriteResult)
async deleteTask(
#Args('id') id: string,
) {
const data = await this.taskService.findOne(id)
let res
if (data.checkSomething) {
const update = { $set: { delete: true } }
res = this.taskService.updateOne(id, update)
}
return res
}
}
#Injectable()
export class TasksService {
findOne(id: string) {
const Tasks = this.db.collection('tasks')
return Task.findOne({ _ id: id })
}
updateOne(id: string, update) {
const Tasks = this.db.collection('tasks')
return Task.updateOne({ _ id: id }, update)
}
}
What is the correct usage of the resolver and service? In both cases one part keeps nearly a one liner for each function, so why should I split that at all?
You're right that it's a pretty linear call and there isn't much logic behind it, but the idea is to separate the concerns of each class. The resolver, much like a REST or RPC controller, should act as a gateway to your business logic, so that the logic can be easily re-used or re-called in other parts of the server. If you have a hybrid server with RPC or a REST + GQL combo, you could re-use the service to ensure both REST and GQL get the same return.
In the end, it comes down to your choice on what you want to do, but separating the resovler from the service (having thin gateways and fat logic classes) is Nest's opinion on the right design.
Your service help you fetch the data from Database. Your Resolver help to delivery these data to user. Sometimes, the data that you delivery to user not the same with data from Database, so the Resolver will make up these data as user's demand before sending it to user.
In terms of best practice, the resolver or controller should be thought of as the manager. In this case (as is stereotypical), the manager shouldn't be doing any of the actual work except for telling the workers what to do. The Manager determines who (which worker/service) should do the work. Sometimes it might be two or more workers/services. They specialize in telling "who", "what" to do.
The workers on the other hand, execute on the actual task. In your case, another option would be to have a database "repository" for database commands like findOne, findOneByX, updateOne; and also a service to handle the actual logic of the task. So the service worker takes the instructions from the manager(resolver) and only uses their logic to tell their database fetching repository buddies what to fetch.
In this way, the manager manages who should do the task. The service contains the logic and tells the other repository methods focused on database fetches what to fetch.
// So you would have...
task.resolver.ts
task.service.ts
task.repository.ts
The task.resolver will contain one line that calls the task.service method
The task.service will contain the logic to manage the task
The task.repository will contain methods like what you have in your suggested task.service - essentially database only methods
I am creating a custom Angular ngx-translate Loader which tries to get translations from local storage, before making an API call and updating the local storage with the new translations.
Here's my getTranslation function:
getTranslation(lang: string): Observable<any> {
// get translations from local storage
const translations = new BehaviorSubject(
JSON.parse(this.localStorageService.getItem('translations'))
);
// get translations from API
this.myApiService.get('/translations/' + lang).subscribe(response => {
if (response.data) {
// update local Storage with new translations
this.localStorageService.setItem('translations', JSON.stringify(response.data));
translations.next(response.data);
}
});
return translations;
}
The problem is, that getTranslation doesn't seem to update the Observable on translations.next() and continues using the translations that were initially set with
const translations = new BehaviorSubject(
JSON.parse(this.localStorageService.getItem('translations'))
);
until I reload my app.
What exactly am I doing wrong here?
I dont know, I understand exactly you, but I think when you call the getTranslation() it reads the json from localStorage saving to translation constans... and after you calling the api but you are not update the translation variable just saving to localstorage.. then you are returning. So when you reload the page ofc it reads from the localstorage again BUT this time there is that translations what you get from the API on the first time.
So it seems like pushing two different values into the observable in the getTranslation function messes with ngx-translate - I found a work around though.
I'm not sure if this is the best way to do it, but it works and solves my problem, so I'm just gonna go ahead and use it.
CustomTranslateLoader:
getTranslation(lang: string): Observable<any> {
const translations = new BehaviorSubject(undefined);
translations.next(JSON.parse(this.localStorageService.getItem('translations')));
return translations;
}
loadTranslation(lang) {
return this.blockpitApiService.get('/translations/' + lang);
}
app.component.ts
constructor(
private translate: TranslateService,
private translateService: CustomTranslateLoader,
private localStorageService: LocalStorageService
) {
language = translate.getBrowserLang();
translate.setDefaultLang(language);
// load translations from server and update translations
this.translateService.loadTranslation(language).subscribe(response => {
this.localStorageService.setItem('translations', JSON.stringify(response.data));
translate.setTranslation(language, response.data);
translate.use(language);
});
}
Since ngx-translate simply ignores when we push new translations into the observable, we can simply call translate.setTranslation(language, response.data) and translate.use(language), to update the values used when translating.
By calling this as soon as our api call is finished, we initially see the translation that are first set in getTranslation which ngx-translation calls automatically and the api translations as soon as they are available when we .subscribe to the function in our app.component.ts.
In my angular project I currently have a service that uses http calls to retrieve data from my java code. Every 10 seconds the service calls the Java side and gets new data from it. I now have another component that needs the data in my service. I need this component to have a field called 'data' that just gets updated automatically when the service gets new information.
How can I set them up so that the service pushes the new information to my other component? I would like to be able to use {{data}} in my component's html and have that be automatically updated without having to reload the page.
My component does have the service 'Autowired' in already. So currently I can just call 'this.data = this.service.getData()' but that call is within my ngOnInit method so it only happens once, and the data field does not get updated when the service's data field gets updated.
You can create a messaging service that publishes data for subscribers or implement this functionality in your original service.
I would suggest having a separate messaging service and have relevant components or services publish/subscribe to it.
messaging.service.ts
import { Injectable } from '#angular/core';
import { Subject } from 'rxjs/Subject';
#Injectable()
export class MessagingService {
private sharedValue = new Subject<string>();
// Observable string streams
sharedValue$ = this.sharedValue.asObservable();
// Service message commands
publishData(data: string) {
this.sharedValue.next(data);
}
You would inject the service like this:
constructor(private messagingService: MessagingService ) {}
Publish to the service:
this.messagingService.publishData(sharedValue);
Subscribe to the service:
this.messagingService.sharedValue$.subscribe(
data => {
this.localSharedValue = data;
});
FROM ANSWER BELOW BY: DeborahK (who's courses on Pluralsight everyone should watch)
Actually, all you need is a getter.
Change your 'data' property to a getter and that will do the trick:
get data(): any {
return this.service.getData();
}
Angular change detection will detect any time that data is changed in
the service which will cause it to re-evaluate its bindings and call
this getter to re-get the data.
No need for a fancy service or Subject. :-)
Actually, all you need is a getter.
Change your 'data' property to a getter and that will do the trick:
get data(): any {
return this.service.getData();
}
Angular change detection will detect any time that data is changed in the service which will cause it to re-evaluate its bindings and call this getter to re-get the data.
No need for a fancy service or Subject. :-)
I’m trying to create a Ember service that calls an API to query data in a database. I want to create this service so I can inject a dataset (an array of objects) in various controllers, components and routes. I’m not sure if this is one of ember’s ‘best practices’ but it seems like it will solve an immediate issue while learning the framework. For this example I have a service called ‘get-data.js’ and I have a component called ‘responsive-table.js’ that i want to have access to an array of objects that I receive from my database. Should I be using a service to make a ajax request to the api every time i need this array? Should I be using ‘ember-data’ and calling to the ‘store’ and use the ‘findAll’ method? Whenever I try to call to the store and comment out the response from a ‘findAll’ I get a class object? Whats the best way to access your server data in components and controllers with ember.js and ‘ember-data’
service
// service
import Ember from 'ember';
const {
Service, inject: { service }, computed, run, set, get
} = Ember;
export default Service.extend({
ajax: service(),
usingJQueryAjax() {
let data = $.get('/api/data').then(function(result ) {
console.log("right here: ", result );
return result;
});
return data
}
});
componet
// component
import Ember from 'ember';
const {
Component, inject, get, set, $
} = Ember;
export default Component.extend({
store: inject.service(),
actions: {
usingStoreDoesntWork() {
var data = get(this, 'store').findAll('nba');
return data;
},
usingJQueryAjax() {
var data = $.getJSON('/api/data').then(function(result) {
console.log("Array I want to return: ", result );
});
return data;
}
}
});
Whats the best approach to get data into a component from an API ?
Ember Components are designed to have no connection to a source of data. This increases their reusability, since all data that a component uses must be passed in.
Typically, the bridge from your server application and the Ember model is the Route. Here, you do what is necessary to retrieve data and return it in the model() hook. This is true whether or not you use Ember Data.
Your route (which is configured in router.js), will be called when necessary to get a model. Your template will have access to that via the variable model. You would then include a component in your template and pass data to the model via attributes.
Route
model() {
// retrieve data here, using whatever technique you want. It could be
// constant data or retrieved via jQuery, or the Ember Data mechanism (e.g.
// the "store"
return {
someList: [1,2,3,4,5]
};
}
Template
Here's your data:
{{list-display list=model.someList}}
Component template
<ul>
{{#each list as |item|}}
<li>{{item}}</li>
{{/each}}
</ul>
The component, list-display can now display any list, so it is nicely reusable. The connection with data is made in the route template, and the data is retrieved by the Route. If you want to create a Service to retrieve data, then use the service in the Route to return data from the model() hook.
Hope this clarifies it for you.