0

I've got a question about a race case I'm seeing with my Observable. This is probably more of a general observable/angular problem. When transitioning from one route to the other, I'm seeing a cannot read 'property' of undefined error from a controller that is destroying as we transition to a new route. In my controller, I've got a method like so:

// this.setting$: Observable<object>;

get name$: Observable<string> {
    return this.settings$.pipe(map(settings => settings.name));
}

That above function is throwing the error. In the same components template, I've got:

<some-component [name]="name$ | async"></some-component>

When I transition to the new page, the this.settings$ selector returns undefined and my controller's code throws an error. The odd thing is my component has already destroyed when the console error occurs (tested via console logging ngOnDestroy).

I've found two solutions that fix my problem. solution A:

// Parse the name property in the template - which I think looks ugly
<some-component [name]="(settings$ | async).name"></some-component>

Or, solution B:

// Pass entire settings object into component and parse out name in some-component controller - yuck
<some-component [settings]="settings$ | async"></some-component>

I dont like either solution. Solution A I dont like because it looks clunky in the template- but maybe I should just deal with it. Solution B I dont like because its not exactly an elegant solution.

I dont quite understand why either solution fixes the problem. Can anyone pin point why this problem exists? Especially considering the component has been destroyed. This feels like an orphaned subscription, but I dont see how.

EDIT: I've created a stackblitz that reproduces the problem. https://stackblitz.com/edit/ngxs-repro-ndaco6 . There are three routes: /home, /profile, /settings. /profile and /settings depend on an account id being in the url. /home does not. When you route from /profile to /home, note there is no console error. However, when you route from /settings to /home, there is a console error. Can somebody help me understand why? Note, the SettingsComponent that throws the error is already destroyed when the error is thrown.

2
  • You could try saving the Observable to a member variable this.name$ = this.settings$.pipe(map(settings => settings.name)); instead of using a getter. Maybe creating a new Observable with pipe() each time you call the getter causes the problem. Commented Nov 2, 2019 at 1:27
  • Thank you for your comment. I gave this a try, but it still throws the error :( Commented Nov 2, 2019 at 19:34

2 Answers 2

1

Use

name$ = this.settings$.pipe(map(settings => settings ? settings.name : null));

It does a check to see if the settings has a value and reuses the same observable, no need to recreate the observable each time you access the getter.

Sign up to request clarification or add additional context in comments.

1 Comment

This is useful. I missed that I was creating a new observable with each get. However, I'm still struggling to understand why the problem exists in the first place. I know I can null check the settings object, but the mystery is why this logic is being invoked when this component is already destroyed. I've created a stackblitz with that reproduces the problem. I will add it to my question
0

account$ is emitting undefined before the component is destroyed, this is happening in your route selector during routing before the component is destroyed.

Try

age$ = this.account$.pipe(
  tap(val => {
    console.log('Account obs val is', val);
  }),
  map(account => account.age)
);

https://stackblitz.com/edit/ngxs-repro-ceetsi?file=src/app/components/settings.component.ts

You can see account$ emits undefined before the component is destroyed.

It only happens when you nav from settings to home, it is your route selector causing it to emit undefined.

3 Comments

Thank you for your answer. Do you know why the ProfileSettings component doesn't have an error? That component is also displaying values off of the settings object but seems to correctly unsubscribe before an error is shown
There was a typo in the last comment. I meant, "Do you know why the ProfileComponent doesn't have the same error?" I've set up the ProfileComponent and SettingsComponent up almost identically to help pin point the problem. The only difference between those components is how I'm piping values out of the account observable. Do you know why the ProfileComponent does not have an error but the SettingsComponent does?
I would guess that because the async pipe manages the subscription and has not yet unsubscribed the pipe function still gets run when account$ emits but in profile the component is in shutdown stage and change detection never tries to bind to the the {{ (account$ | async ).name }}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.