Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can an @Input binding be an observable in Angular?

I'm creating a component that displays validation errors under input fields. If there is an error message shown, and the user submits the form I want to flash the message to draw their attention.

I was wondering if it's possible to use an observable as an input binding?

That way I can subscribe to the input and flash when any data is observed.

Here's an example of my idea:

@Component({..})
export class MessageComponent implement OnChanges {
   @Input()
   public flash: Observable<any>;

    public ngOnChanges(changes: SimpleChanges): void {
        if ('flash' in changes) {
            (<Observable<any>> changes['flash'].currentValue).subscribe(() => {
                // trigger the flash animation here
            });
        }
    }
}

What I can't figure out is if this will leak memory, and how/when should I unsubscribe (or is it even necessary).

Is this kind of practice allowed in Angular?

like image 637
Reactgular Avatar asked Jul 08 '17 01:07

Reactgular


2 Answers

Yes, you can use an observable as an input.

Whether or not you need to unsubscribe depends upon the observable in question. When an observable completes or errors, any subscribers are unsubscribed automatically. So, in general, if you know an observable completes, explicit unsubscription is not necessary.

However, looking at your snippet, this seems to be a secondary issue, as you've written code suggesting that you expect the input to change.

In that case, you should unsubscribe whenever a change occurs. Otherwise, you will have two subscribers - with the first still listening to the original flash observable.

import { Subscription } from 'rxjs/Subscription';

@Component({..})
export class MessageComponent implement OnChanges, OnDestroy {

    @Input()
    public flash: Observable<any>;
    private flashSubscription: Subscription;

    public ngOnChanges(changes: SimpleChanges): void {
        if ('flash' in changes) {
            if (this.flashSubscription) {
                this.flashSubscription.unsubscribe();
            }
            this.flashSubscription = (<Observable<any>> changes['flash'].currentValue).subscribe(() => {
                // trigger the flash animation here
            });
        }
    }

    public ngOnDestroy(): void {
        if (this.flashSubscription) {
            this.flashSubscription.unsubscribe();
        }
    }

I'd also call unsubscribe in ngOnDestroy - so that unsubscription will occur for flash observables that don't complete or error.

Note that is is safe to call a subscription's unsubscribe method multiple times.

like image 85
cartant Avatar answered Oct 13 '22 03:10

cartant


You are probably are better off using the async pipe if the source of the message is an observable.

<app-message [message]="message$ | async"></app-message>

This will handle the subscriptions for you, with regard to cleanup.

Then since you're already using ngOnChanges - which only triggers when there's a change - so you just trigger the animation when you get a change in message.

public ngOnChanges(changes: SimpleChanges): void {
    if ('message' in changes) {

        // trigger the flash animation here
    }
}

For this example as presented there's just no need to have your message control subscribing to things and expecting an observable as input.

Alternatively you could use animations and the :enter event to show an animation when a message is first displayed. Flashing everytime gets annoying.

If you really really want to have some kind of external trigger to cause a flash (and I realize you probably simplified your example) then you could investigate using a directive to do this so you could reuse it later. (Then you may need to use AnimationBuilder). This is a more complex route.

like image 29
Simon_Weaver Avatar answered Oct 13 '22 03:10

Simon_Weaver