Observable / Subject loosing subscribers in Angular - javascript

I'm using a Static variable in my Class to store an initialised BehaviourSubject, so that I can provide a default, while I load the user's settings from the server.
(have put a cut down example version below)
#Injectable
export class AppSettings {
// Using a static to globalize our variable to get
// around different instances making lots of requests.
static readonly currency: Subject<string> = new BehaviorSubject('USD');
// Return a property for general consumption, but using
// a global/static variable to ensure we only call once.
get currency(): Observable<string> { return AppSettings.currency; }
loadFromServer():any {
// Broadcast the currency once we get back
// our settings data from the server.
this.someService.getSettings().subscribe(settings => {
// this is called lastly, but AppSettings.currency.observers
// seems to show as an empty array in the Inspector??
AppSettings.currency.next(settings.currency);
});
}
}
When I subscribe to it later in my code, it will run through it once (since it's a BehaviorSubject), but it won't fire after that.
export class myComponent {
public currency: string;
constructor(settings: AppSettings) {
// Called once with the default 'USD'
settings.currency.subscribe(currency => {
// only gets here once, before loadFromServer
console.log(currency);
this.currency = currency;
});
// Load from the server and have our subscription
// update our Currency property.
settings.loadFromServer();
}
}
The loadFromServer() is working exactly as expected, and the AppSettings.currency.next(settings.currency) line is being called, and after the first event. What is interesting however, is at this point, the AppSettings.currency.observables[] is empty, when it was previously filled in.
My thoughts we're initially an issue of different instances, but I'm using a static variable (have even tried a global one) to avoid different instances.
This is the current workflow...
myComponent.constructor subscribes
that subscription fires, giving the default 'USD'
the server data is loaded, and AppSettings.currency.next(settings.currency) is called
...then...nothing....
I'm expecting that at part 4 the Observer that subscribed in part 1 would be fired again, but it isn't, making my glorified Observer a constant. :(
Am I missing something?

Well I feel sheepish....
Figured out the issue was due to my import statement having the (wrong) file suffix on the file reference. So, in the myComponent file I had...
import { AppSettings } from './settings.js';
While everywhere else I have been using (the correct)
import { AppSettings } from './settings';
which was causing WebPack to compiling two versions of the class, the TypeScript and the (compiled) Javascript version, thus creating two different instances. I managed to see an AppSettings_1 somewhere, that lead me down the rabbit hole to finally gave it away.

Related

Initializing object when strictNullChecks used in Typescript

I have an interface called IUser and uses strictNullChecks in the typescript setting. I want to use a variable of IUser in the class like below:
class UserAccount {
user: IUser;
}
The above code will generate the error:
Property 'user' has no initializer and is not definitely assigned in the constructor.
I can fix this with:
class UserAccount {
user: IUser;
constructor() {
user = new User();
}
}
But from a run time perspective, the user can be set only after a while after the display is complete. There is a latency for getting the data for the user from the server. The setting of the user is done through an RxJS observable which works only reactively.
What type of initialization I can do for user so that it can be set only later time. Setting user to null or undefined may not be correct because I use strictNullChecks and showing the intention that the object will be present from the beginning. But a valid object for user can't exist until the server call is complete.
*** UPDATE ****
This is a little more better example than the above:
export class AddPersonDialogComponent {
selectedPerson1: IPersonForTypeahead;
selectedPerson2: IPersonForTypeahead;
selectedPerson3: IPersonForTypeahead;
...
}
The 3 selectedPerson objects are assigned values when the user select them from three 3 dropdowns in the dialog. These three properties start with nothing but if the user select people from a drop down in the dialog, they are set.
You could make your usage of UserAccount to be an observable, like this:
userAccount$: Observable<UserAccount> = this.service.getUser().pipe(
map(user => new UserAccount(user))
);
The idea here is to not construct your UserAccount until you have all the data needed to do so. This goes along with your idea "But a valid object for user can't exist until the server call is complete" .

Caching observables causing problem with mergeMap

I have a caching method in a container:
get(): Observable<T[]> {
if (!this.get$) {
this.get$ = merge(
this.behaviorSubject.asObservable(),
this._config.get().pipe(shareReplay(1), tap(x => this.behaviorSubject.next(x))));
}
return this.get$;
}
This works fine with normal observables, however when I cache the bellow in a myContainer2 (e.g using cached observable's result to create another cached observable) method like:
// get is assigned to _config.get in the above function
const myContainer2 = new Container({get: () => myContainer1.get().pipe(mergeMap(res1 => getObs2(res1))});
// please note, the end goal is to resolve the first observable on the first subscription
// and not when caching it in the above method (using cold observables)
myContainer2.get().subscribe(...) // getObs2 gets called
myContainer2.get().subscribe(...) // getObs2 gets called again
myContainer2.get().subscribe(...) // getObs2 gets called for a third time, and so on
every time when the second cache is subscribed to getObs2 gets called (it caches nothing).
I suspect my implementation of get is faulty, since I am merging an behavior subject (which emits at the beginning), but I cant think of any other way to implement it (in order to use cold observables).
Please note that if I use normal observable instead of myContainer.get() everything works as expected.
Do you know where the problem lies?
Using a declarative approach, you can handle caching as follows:
// Declare the Observable that retrieves the set of
// configuration data and shares it.
config$ = this._config.get().pipe(shareReplay(1));
When subscribed to config$, the above code will automatically go get the configuration if it's not already been retrieved or return the retrieved configuration.
I'm not clear on what the BehaviorSubject code is for in your example. If it was to hold the emitted config data, it's not necessary as the config$ will provide it.

Vue data not available until JSON.stringify() is called

I'm not sure how to tackle this issue because there's quite a bit into it, and the behavior is one I've never seen before from JavaScript or from Vue.js
Of course, I will try to keep the code minimal to the most critical and pieces
I'm using vue-class-component(6.3.2), so my Vue(2.5.17) components look like classes :)
This particular component looks like so:
import GameInterface from '#/GameInterface';
class GameComponent extends Vue {
public gameInterface = GameInterface();
public mounted() {
this.gameInterface.launch();
}
}
GameInterface return an object with a launch method and other game variables.
In the game interface file to method looks something like this:
const GameInterface = function () {
const obj = {
gameState: {
players: {},
},
gameInitialized: false,
launch() => {
game = createMyGame(obj); // set gameInitialized to true
},
};
return obj;
}
export default GameInterface;
Great, it works, the object is passed onto my Phaser game :) and it is also returned by the method, meaning that Vue can now use this object.
At some point I have a getter method in my Vue class that looks like so:
get currentPlayer() {
if (!this.gameInterface.gameInitialized) return null;
if (!this.gameInterface.gameState.players[this.user.id]) {
return null;
}
return this.gameInterface.gameState.players[this.user.id];
}
And sure enough, null is returned even though the player and id is clearly there.
When I console.log this.user.id I get 4, and gameInterface.gameState.players returns an object with getters for players like so:
{
4: { ... },
5: { ... },
}
Alright, so it does not return the player even though the object and key are being passed correctly...
But I found an extremely strange way to "FIX" this issue: By adding JSON.parse(JSON.stringify(gameState)) like so
get currentPlayer() {
// ...
if (!this.gameInterface.gameState.players[this.user.id]) {
// add this line
JSON.stringify(this.gameInterface.gameState);
return null;
}
return this.gameInterface.gameState.players[this.user.id];
}
It successfully returns the current player for us... Strange no?
My guess is that when we do this, we "bump" the object, Vue notices some change because of this and updates the object correctly. Does anyone know what I'm missing here?
After working on the problem with a friend, I found the underlying issue being a JavaScript-specific one involving Vue's reactive nature.
https://v2.vuejs.org/v2/guide/reactivity.html#Change-Detection-Caveats
In this section of the documentation, a caveat of Vue's change detection is discussed:
Vue cannot detect property addition or deletion. Since Vue performs the getter/setter conversion process during instance initialization, a property must be present in the data object in order for Vue to convert it and make it reactive.
When, in my game run-time, I set players like so:
gameObj.gameState.players[user.id] = {...playerData}
I am adding a new property that Vue has not converted on initialization, and Vue does not detect this change. This is a simple concept I failed to take into account when developing my game run-time.
In order to correctly set a new player, I've decided to use the spread operator to change the entirety of the players object, which Vue is reacting to, and in turn, Vue will detect my player being added like so:
gameObj.gameState.players = {
...gameObj.gameState.players,
[user.id]: {...playerData}
}
Vue also discusses another method called $set, which you can read on the same page.

Angular - Cannot see how Angular2 auto updates data from service

I am an Angular2 beginner, creating a test note-taking app. The app is simple: I have a NotesContianerComponent which connects to a NoteService which has the getNotes() and addNote() method.
In the NotesContainerComponent, I am storing the data returned from service in a member myNotes and on ngOnInit event, refreshing the array. I am not touching that array anywhere in that code as of now.
Now, when I add a new note using the form and call the service's addNote() method from the NotesContainerComponent, the note does get added to the backend mock notes array, but at the same time the UI (i.e. NotesContainerComponent) gets updated with the new note automatically! I am not doing anything to refresh it manually.
To investigate, I added a console.log() to the getNotes method of the service, however it is being only called the first time, possibly by ngOnInit hook.
What I cannot figure out is, how does Angular2 know about the new note without even querying the service automatically? And how to stop this? Any clues will be appreciated.
NotesContainerComponent code for reference:
export class NotesContainerComponent implements OnInit {
constructor(
private noteService: NoteService
) { }
addNote(newNote):void {
this.noteService.add(newNote);
}
myNotes: Notes[];
ngOnInit() {
this.noteService.getNotes()
.then(notes => this.myNotes = notes);
}
}
If you are storing your mock data in an Object or an Array and this.myNotes's reference is that Objects reference. When your mock datas content changes, all the references will change too. This is because objects are mutable in javascript.

Aurelia binding: property-getter called repeatedly

I'm currently learning and using Aurelia and something kind of weird (maybe normal) is happening.
When using the following code
export class NavBar {
get username() {
console.log('o_o')
return 'name' + Date.now()
}
}
And in the template ${username}, the username is always updating, several times per seconds (and console.log are logged several times as well of course).
The workaround is to simply use a function and not a getter and call ${username()} in the template. But is this behaviour normal? So should I use sometimes getter sometimes not?
Thanks!
This is normal, Aurelia polls your property for changes because it has no way of knowing when your property-getter will return a different value.
If it were a simple property (without a getter), Aurelia could observe the property directly, no polling would be needed.
To avoid the polling you could tell Aurelia's binding system what to observe:
import {computedFrom} from 'aurelia-framework';
export class Foo {
_username = 'hello';
#computedFrom('_username')
get username() {
return this._username;
}
}
Another option would be to use a one-time binding:
${username & oneTime}

Categories

Resources