I need to return a String but only have an Observable - javascript

Another Observable question.
I'm working with angular translation service and Kendo.UI components.
According to the Kendo.UI documentation, I can implement a MessageService to translate its components.
To do so, I have to extend an abstract class that contains a method that return a string:
#Injectable()
export class MyMessageService extends MessageService {
private messages = {'kendo.upload.select': 'initial value'}
constructor(private readonly translate: TranslateService) {
super();
this.translate.get('my_translation_key')
.subscribe((value) => {
this.messages['kendo.upload.select'] = value;
});
}
public get(key: string): string {
return this.messages[key];
}
}
The problem is, the moment kendo injects my custom service, the observables were not resolved yet and my button never get its value changed from "'initial value'" to the translated.
From my understatement, kendo's service should accept an observable, not a string. But I'm not very familiar with promises/subjects/observables.
Is there a workaround for this?
ps.: I know TranslateService has a .instant(key) method, but the same way, the values are not loaded yet.

It sounds like you are saying it the observable is pushed before kendo subscribes. If that is the case then you should use a ReplaySubject instead of an Observable as the Replaysubject will push all the values that were given to the observable to the subscriber when it subscribes.You can tell it how many observed values to replay.
Also you could try instead to call your server after view init or even OnInint. Calling the service in the constructor is not recommended as the view is not done rendering and any html async pipes may not be set yet. Worst case you can use the Replay subject.

Related

Trigger a function once 2 inputs are set and then afterwards if either of the value changes in Angular

I have a component with 2 inputs (or mor) and I want to:
Trigger a method X the first time, when both value are set and exists
Trigger a method X each time, if either one of the both value changes
<some-cmp [itemid]="activeItemId$ | async" [userId]="activeUserId$ | async"></some-cmp>
Both values can change at any time, so I figured using rxjs to build a stream lets me control everything. My current solution seems a bit hacky and is difficult to test. I use 2 BehaviourSubjects and combineLatest with a debounceTime.
#Input() set itemId (id){this.itemId$.next(id)};
#Input() set userId (id){this.userId$.next(id)};
itemId$ = new BehaviourSubject$(null);
userId$ = new BehaviourSubbject$(null);
ngOnInt(){
combineLatest([
this.itemId$.pipe(filter(item=>item!===null)),
this.userId$.pipe(filter(item=>item!===null))
]).pipe(
debounceTime(10),
switchMap(...)
).subscribe(...)
}
So my question are
Is there a more elegant way to achieve this behavior?
Is there a way to avoid the debounceTime, which makes testing difficult?
The debounceTime is used in case both value do arrive at the same time and I don't want combineLatest to trigger the method twice.
You are right in using combineLatest, it will only emit the first time after each source has emitted once and then will emit any time any source emits.
Is there a way to avoid the debounceTime. [It] is used in case both value do arrive at the same time and I don't want combineLatest to trigger the method twice.
Maybe debounceTime isn't necessary due to the initial behavior of combineLatest; it will not emit the first time until all sources emit. However if you typically receive subsequent emissions from both sources that occur within a short timeframe, use of debounceTime may be an appropriate optimization.
Is there a more elegant way to achieve this behavior?
I think your code is fine. However, it may not be necessary to use BehaviorSubject since you aren't really using the default value. You could use plain Subject or ReplaySubject(1).
You could assign the result of your combineLatest to another variable and subscribe to that inside ngOnInit or use the async pipe in the template:
#Input() set itemId (id){ this.itemId$.next(id) };
#Input() set userId (id){ this.userId$.next(id) };
itemId$ = new Subject<string>();
userId$ = new Subject<string>();
data$ = combineLatest([
this.itemId$.pipe(filter(i => !!i)),
this.userId$.pipe(filter(i => !!i))
]).pipe(
debounceTime(10),
switchMap(...)
);
ngOnInit() {
this.data$.subscribe(...);
}
Angular provides ngOnChanges hook which can be used in this scenario. It'll trigger ngOnChanges method whenever there's a change in any of the inputs of the component.
Below is an example of how this can be achieved:
export class SomeComponent implements OnChanges {
#Input() itemId: any;
#Input() userId: any;
ngOnChanges(changes: SimpleChanges) {
const change = changes.itemId || changes.userId;
if (change && change.currentValue !== change.previousValue) {
this.doSomething();
}
}
private doSomething() {
// Your logic goes here
}
}
Your HTML will now look clean and you can get rid of async as well:
<some-cmp [itemid]="itemId" [userId]="userId"></some-cmp>

Why isn't my component object getting updated itself when anything in my globally shared service updates? Angular

I have this service:
export class RecipeService{
selectedRecipe: Recipe = 'xyz';
}
I have this component using this service:
export class RecipesComponent implements OnInit {
selectedRecipe: Recipe;
constructor(private recipeService: RecipeService) { }
ngOnInit(): void {
this.selectedRecipe = this.recipeService.selectedRecipe;
}
}
The service is defined in app.module.ts for injection, which means all components get the same instance.
My question is, whenever I update the selectedRecipe variable in one of my components, it doesn't get updated back in other components although it is referenced and hence I expect a change immediately.
What am I doing wrong?
It doesn't get updated because the new value is not "sent" to the already initiated angular components.
Instead you should use observables.
for example:
/* service */
private recipe = "xyz";
public recipeSubject: BehaviorSubject<string> = new BehaviorSubject(this.recipe);
// when changing the recipe
recipeSubject.next(this.recipe);
/* component */
this.service.recipeSubject.subscribe(res => this.recipe = res);
I googled and found out in one of the posts that its because of my object. The object (Recipe) in my service contains a primitive type, i.e string. If your object contains a primitive type, it isn't passed as a reference, hence a change in service object won't be reflected in component because they are now different.
Although I must clear that in case of array it worked perfectly fine even when my array contained objects which had primitive types. Changes were still reflected.

How to pass a value from a service, to a component's method

I've got a service that I use to share data between 2 components. That part works flawlessly, but now I need to call a method of component A, when something triggers on the service (and pass a value to that component). How can I do this? I read on older questions that this is a wrong approach but since Im a noob I dont know what to search for a solution.
Do I need to use observables?
I think Joseph's idea is the way to go.
Here's how I'd implement it:
class FooService {
private _newEvents = new Subject();
newEvents$ = this._newEvents.asObservable();
addNewEvent (ev) {
this._newEvents.next(e);
}
}
// Allow `A` class to communicate with `B` class
class A {
addEvent (ev) {
this.fooService.addNewEvent(ev);
}
}
class B {
private subscription: Subscription;
ngOnInit () {
this.subscription = this.fooService.newEvents$
.subscribe(e => {})
}
ngOnDestroy () {
this.subscription.unsubscribe();
}
}
Note that if your B class subscribes to multiple observables, you should unsubscribe from them using, among other solutions, takeUntil.
Observables / Subjects are one way. You would have one Subject in the service, and would use .next(value) on it to exchange values. Each component which is interested in the value may subscribe to that subject.
Example: (taken from RxJS docs
//your Service
import { Subject } from 'rxjs';
const subject = new Subject<number>();
//Component A (and others as well)
service.subject.subscribe({
next: (num) => console.log(num)
});
//this should work as well with prettier syntax:
service.subject.subscribe(sum =>
console.log(num)
);
//Component B
service.subject.next(7) //passing number 7 to Component A
Whenever you create a subscription, make sure to always unsubscribe! Else you might end up with stacks of subscriptions, which will all get triggered simultaneously in the very same component.
From personal experience, I found it more helpful to outsource any functions and variables that could be considered as global into a dedicated service, if possible. If you directly read the variables of a service from your components (and modify them if necessary), you'll have the same effect. That works as long as you keep a proper service structure. Some examples of dedicated services with global use are:
Translations (TranslationService)
Rights Management (PermissionService)

RxJS Register event sources and buffer events dynamically

I'm currently trying to get a Register/Subscribe system to work with RxJs.
The situation is that I have component A with several sub components A1, A2, A3, ... The amount has to be dynamic. What I want to do now is that whenever an event I will call "somethingChanged" occurs (which is already distributed through an Observable) all sub components A1, ... will do some processing and then return some information (a state) as an event I'll call newStates to the parent action A probably using another observable. For this to work the sub components first have to register themselves to the "event manager" as children of A so that these events can be processed accordingly.
First idea
My first idea for this was to use a bufferCount on the newStates observable with the count being the amount of registered sub components. The problem is that the sub component registering and the parent component subscribing to the newStates observable is happening at almost the same time, the parent even being slightly faster which means the amountSub is usually 0 which breaks this attempt.
registerSubComponent() {
amountSub++;
}
getParentObservable() {
return newStates.bufferCount(amountSub).mergeMap();
}
Second idea
The second attempt was to use the somethingChanged Event and use that to initialize a takeLast to get the last items when they should be thrown. The problem is again as i will run into race condition as sub components take longer to throw their newStates events meaning I'll get old values.
registerSubComponent() {
amountSub++;
}
getParentObservable() {
return somethingChanged.map(() => newStates.takeLast(amountSub);
}
Third idea
So currently my only idea would be to catch the newStates event in the event manager, store the states in an array and check everytime if all registered components send them by looking at the array length. When all states are in i could then send the saved states and reset the array.
registerSubComponent() {
amountSub++;
}
getParentObservable() {
return newParentObservable;
}
newStates.subscribe(state => {
savedStates.push(state);
if(savedStates.length == amountSub) {
newParentObservable.next(savedStates);
savedStates = [];
}
});
Is this the only way or am I missing something so it could be done easier/with observables?
Btw: This is all pseudo code as my actual code also has to support multiple parent components in one manager making it cumbersome to read through.
It sounds like you want change detection up the tree. Using the following method with an angular service sounds like it might be what you need:
I found a solution on this guy Jason Watmore's blog that describes using rxjs Observables and Subjects. Using this method allows data changes to easily propagate up and down the angular inheritance tree to any component you want
Jason's Post
Accompanying Plunkr
Briefly:
You declare a service as a provider at the module.ts level with 3 methods:
sendMessage
clearMessage
getMessage
import { Injectable } from '#angular/core';
import { Observable } from 'rxjs';
import { Subject } from 'rxjs/Subject';
#Injectable()
export class MessageService {
private subject = new Subject();
sendMessage(message: string) {
this.subject.next({ text: message });
}
clearMessage() {
this.subject.next();
}
getMessage(): Observable<any> {
return this.subject.asObservable();
}
}
This service needs imports of Observable and Subject from rxjs
In each component you want to share data with:
create a subscription object in the constructor which calls the service.getMessage() function
call rxjs subscription.unsubscribe() in ngOnDestroy for each component so you aren't leaking memory
you can hook in a function to handle the incoming subscription updates
When you have data you want to share with your other components:
Create a public method which calls the service.sendMessage() method
This will send your updated data to each component and fire those functions you've hooked in to handle the changed data
I believe the blog post I linked to and the plunkr from the post say it best and have really helped me move data around efficiently in my own app but if you have any questions I'll do my best to answer them

Angular 2 transfer ajax call response to another component

I just started playing with angular 2 and i've ran into a small problem, that i ve searched for in various forms and also angulars documentation.
I've managed to make a service that makes a call and then i want in a component when i press a button to load another component with dynamicload component and have access to the ajax result.
The problem is that I can t figure out how to do that..
The question is how can I make the result accesible in other components using Observables or Promises method.
If I understood correctly your question, you are looking a way to insert a data from request to another nested component.
I hope this image will clarify for you the data flow for this case.
Your Root component is calling a service method which returns for you promise object.
Then you map needed data from response to the component model inside Root Component constructor.
And your Child component should be subscribed for the model which you was preparing in previous step.
ngOnInit() {
this.dataService.getSomeData()
.subscribe((data: IData) => {
this.data = data;
});
}
Just a short example above how to set model in the root component from the promise object to the local model.
New research:
There is another way to fill your components by data from api's. You can use EventEmitter to emit event from service, and then, you can subscribe for this event inside you created components, so they will get a data, each time there will be called the service. Here is nice example of this strategy in the first answer. Service Events
Hope it will help you, let me know if you will need additional info!
Just create a service, then inject the service where you want.
Here it's an example how to share a service ajax data across many components without making the request twice :
https://stackoverflow.com/a/36413003/2681823
the Service:
#Injectable()
export class DataService {
constructor(private http: Http) { }
private _dataObs = new ReplaySubject<request>(1);
getData(forceRefresh?: boolean) {
// On Error the Subject will be Stoped and Unsubscribed, if so, create another one
this._dataObs = this._dataObs.isUnsubscribed ? new ReplaySubject(1) : this._dataObs;
// If the Subject was NOT subscribed before OR if forceRefresh is requested
if (!this._dataObs.observers.length || forceRefresh) {
this.http.get('http://jsonplaceholder.typicode.com/posts/2')
.subscribe(
requestData => {
this._dataObs.next(requestData);
},
error => this._dataObs.error(error));
}
return this._dataObs;
}
}
the Component:
#Component({
selector: 'child',
template : `<button (click)="makeRequest()" class="btn">Click me!</button>`
})
export class Child {
constructor(private _dataService: DataService) { }
makeRequest() {
this._dataService.getData().subscribe(
requestData => {
console.log('ChildComponent', requestData);
}
}
}
A full working example/plunker can be found here : http://plnkr.co/edit/TR7cAqNATuygDAfj4wno?p=preview

Categories

Resources