5
votes

My application is using Angular and ngrx store. I am using a selector in the store to get the username property of the app state in my component constructor:

test-component.ts

export class TestComponent {
    username$: Observable<string>;
    constructor(private store: Store<fromRoot.AppState>) {
        this.username$ = this.store.select(fromRegistration.getUsername);
    }

    testMethod() {
         //I need the value of the username observable here as a string
    }

}

This is working fine. I need to print the username in the component's template, which I am doing by using the async pipe:

test-component.html

<div>
{{username | async}}
</div>

This is also working correctly.

Now, I need to call a service from a method in TestComponent that sends the username as an argument. How can I achieve this? Do I need to stop using the async pipe and subscribe to the selector to then assign the username property (which would then be declared as a string) to the value of the observable?

To note: the username observable will only have 1 value, once I get 1 value I can stop watching it.

I understand by using the async pipe the component unsubscribe from the username observable automatically, when does this happen and how does the component knows I don't need to watch it anymore?

If I can't use the async pipe, how and when do I unsubscribe?

2

2 Answers

3
votes

I usually go with Maciej Treder's First thought as I try to use dumb components whenever possible, but in some cases when I need the new value from a selector to perform extra stuff inside my component that I'm going to async pipe it to my template anyway (so I don't need any subscribing), I simply use rxjs tap operator which is the pipeable operator name of do. So I replace this:

ngOnInit() {
  this.selected$ = this.store.pipe(select(fromItems.getSelectedBrand));
}

By this:

import { Store, select } from '@ngrx/store';
import { tap } from 'rxjs/operators';

...

ngOnInit() {
  this.selected$ = this.store.pipe(
    select(fromItems.getSelectedBrand),
    tap((brand: Brand) => {
      /**
       * do logic here
       */
    })
  );
}

Now if for some reasons you do need to subscribe to it without async piping it to any template as done in Maciej Treder's second solution, I'd suggest using ngOnDestroy to unsubscribe from it. You'll find more about it here:

2
votes

First thought

The best way to solve this problem in your case would be create a component input variable and pass it asynchronously from parent.

export class TestComponent {
    @Input()
    username: string;

    testMethod() {
         //I need the value of the username observable here as a string
    }

}

And here's how parent component should look:

@Component({
     template: '<test-component [username]="username$ | async">'
})
export class ParentComponent {
    username$: Observable<string>;
    constructor(private store: Store<fromRoot.AppState>) {
        this.username$ = this.store.select(fromRegistration.getUsername);
    }
}

Another solution

You can also set up local variable in your child component:

export class TestComponent {
    usernameString: string
    username$: Observable<string>;
    constructor(private store: Store<fromRoot.AppState>) {
        this.username$ = this.store.select(fromRegistration.getUsername);
        this.username$.subscribe(name -> this.usernameString = name);
    }

    testMethod() {
        if (this.usernameString != undefined) {
            //do something
        }
    }
}