Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Subscribing to Observable Input

Tags:

angular

Is there a way to subscribe to an Observable which is an @Input?

For example:

export class MyComponent implements OnInit {
  @Input() results: Observable<string[]>;
  constructor() {
     this.results.subscribe(value => ...);
  }    
}

In this example I can't subscribe to the observable because it is null...

like image 311
bobthedeveloper Avatar asked Feb 02 '17 19:02

bobthedeveloper


1 Answers

I'm really not a fan of passing an Observable as an @Input itself, but I'm a big fan of turning a standard @Input() into an Observable. This is a much more flexible approach and makes it easier to keep a cleaner boundary between your component and the outside. As soon as you start subscribing to observables passed in from the parent component you open up a whole lot of unnecessary complexity.

This is the best way I've found to achieve this, and I'm increasingly using RxJS and seeing the power of what can be done. It's a little klunky, but I'm really holding out for better framework support for something like this in future.

// 'floating' input parameter, with default value of 'false'
@Input('floating') set floating(floating: boolean) { this.floating$.next(floating); };
private floating$ = new BehaviorSubject<boolean>(false);

Then in the parent component you can pass in a boolean:

<app-magic-box [floating]="true"></app-magic-box>
<app-magic-box [floating]="shouldFloatMagicBox"></app-magic-box>

If what you're sending in is stored 'outside' as an Observable, then by all means send it in using the async pipe. This leaves the subscribing and cleanup for this particular property up to the parent component (and the framework) instead of trying to handle it inside the magic box component.

<app-magic-box [floating]="float$ | async"></app-magic-box>

Inside your magic box component you can now use this observable however you want. Note that it has a default of false, so if we never set floating it still can be used.

 public showRabbit$: Observable<boolean>;

 ngOnInit()
 {
     // (only run this initialization once)

     // subscribe directly if you wish, but keep a reference to dispose in ngOnDestroy
     this.floating$.subscribe(floating => ...);

     // we can also create a new observable based on our input parameters
     // with whatever RxJS you want. if a new input value is set it will 
     // propagate through all the pipes

     // show a rabbit when not floating (why not!)
     this.showRabbit$ = this.floating$.pipe(map(floating => floating == false));
 }

Now you have another observable inside your magic box that you can use in the template. You'll find the 'async' pipe propagating around, but that's fine.

<img *ngIf="showRabbit$ | async" src="./rabbit.png"/>

Important note: If you want to use this as a @HostBinding you'll have to do something like this:

@HostBinding("attr.floating")
get attr_floating() { return this.floating$.value; }

If you really have a problem about accessing value on a BehaviorSubject then give me a better solution ;-) In the context of what I'm doing here I have no issue with it.

I'm really hoping in future the framework will have something like an @InputObservable that does something similar a little more magically than we can achieve today.

There's a long standing open issue on Github - but I must warn you it's not for the faint of heart!

like image 125
Simon_Weaver Avatar answered Sep 21 '22 08:09

Simon_Weaver