Help to understanding variable population through an Observable and subscription

I’m passing some data, which is an array of objects, from one component to a page. For this I’m using a service with a Subject and an Observable, like this:

myservice.service.ts

public mySubject = new Subject<{ data1:string, data2:number, data3: Object }[]>();
public myObservable = this.mySubject.asObservable();

I’m sending the data from the component to my page this way, which is the way I pass data from components and between pages from some time:

mycomponent.component.ts

this.myService.mySubject.next(myData);

And I’m receiving the data in my page this way:

mypage.page.ts

ngOnInit() {
	this.mySubscription = this.myService.myObservable.subscribe(data => {
		console.log(data); // I can see the data in console
		console.log(data.length); // Returns 0
	
		// process data
		...
	});
}

...

ngOnDestroy() {
	this.mySubscription.unsubscribe();
}

Well, my problem is that when I try to process the data, it is empty. If I check the data on the console, I can see that it is there, even though it appears with the empty array icon, but I can open it and see the info. Just below the size of this array is returned as 0, which suggests that the variable has not yet been populated at the time I am trying to process it, but is already populated by the time I check the console.

I would like to know a way to carry out the processing only when this variable is actually populated. This is the first time that something like this happens with this methodology, I imagine it is due to the size of the array (30 objects +).

One small stylistic thing first: I would expose some sort of update method in the service that internally calls next on the Subject and make the Subject itself private, because the point of exposing it only as an Observable is so that nobody outside the service can do Subject-y things to it. Also, there isn’t any need for asObservable, you can just return mySubject from a method declared to return Observable<Thingy[]>.

Now for what I think could be happening: object aliasing. When you pass objects around in JavaScript (and for the purposes of this discussion, arrays are objects), modifications to that object made anywhere are felt everywhere. Reasonable people can disagree on whether this should be embraced in certain circumstances, but this might be a situation in which you want to guard against it. And fortunately enough, if you made the changes recommended in the first paragraph, you have already built the perfect bottleneck for doing this: that update method. It could call next after using something like lodash’s clone or cloneDeep on its argument, which would decouple all consumers from the copy of the array being used by the source. You would still have the issue that all consumers must treat data as read-only, or else modifications made by one are seen by all. The next step down this road would be some library like Redux or Immutable.js that attempt to deal with this language misfeature more comprehensively; I personally haven’t found their buy-in cost worth it to date, but YMMV.

1 Like

Actually, what I do is pass the data to a service function that then calls the next() method of the Subject, I ended up writing this more briefly to make the algorithm smaller, which I now realize was a mistake, but it’s good that you pointed this out, because anyone else who comes in here will take this into account.

Regarding the absence of the need for asObservable, I admit that I did not know this until now. Unfortunately I didn’t understand how I can return mySubject the way you mentioned, because I need it in the form of Observable in order to perform a subscription(), right? Is it possible for you to show a practical example of how this would be, considering the code I showed you?

The exact function as it stands now looks like this:
component

this.myService.changeDataFunction(myData);

service

private mySubject = new Subject<{ data1:string, data2:number, data3: Object }[]>();
public myObservable = this.mySubject.asObservable();

changeDataFunction(data: { data1:string, data2:number, data3: Object }[]) {
	this.mySubject.next(data);
}

page
Same as before

Defining an interface for your business object is going to make this more readable, so:

export interface Katamari {
  data1: string;
  data2: number;
  data3: Object;
}

class Service {
  private mySubject = new Subject<Katamari[]>();
  myObservable(): Observable<Katamari[]> { return this.mySubject; }
}

TypeScript doesn’t have the runtime-level language support capable of doing proper access control, so by the time this gets transpiled down, anybody with a reference to the Observable could next it. However, at compile time, this fig leaf is enough for tsc to catch anybody trying to do that, simply because of the typing declaration of myObservable()'s return value.

1 Like

That was really informative, I will definitely use this methodology from now on. Unfortunately the main problem, which is the receipt of an empty variable by this Observable and that only after a moment is populated, keeps happening.

class Service {
	private mySubject = new Subject<MultiGeometryFeature[]>();

	changeDataFunction(data: MultiGeometryFeature[]) {
		this.mySubject.next(data);
	}
	
	myObservable(): Observable<MultiGeometryFeature[]> {
		return this.mySubject;
	}
}

export interface MultiGeometryFeature {
	data1: string;
	data2: number;
	data3: Object;
}

What I showed is according to what you mean by the updated method you mentioned? The way used to send the data to the service using a function, which then uses the next method, is it in line with what you also suggested? If everything is right by then I will do the next step you mentioned, which would be to use the clone method of the lodash library, but it seems at least a little strange to need to depend on a library to complete the functioning of this type of interaction.


EDIT
I believe that the source of the problem is linked to the formation of the original data that is sent to the service, because if I log the the data.length and the data in the component, before sending to the service, I see the same thing as I described before, the data.length with length = 0 but the correct populated array in the data log. I can then conclude that the problem is related to the asynchronicity of the formation of the original variable data in the component. I’ll try to fix this, perhaps encapsulating the formation of this array in a Promise.

Indeed this was the problem. Using Promise.all() to handle the proper data formation solved the issue. Thank you @rapropos for all the information you provided so far!