18

I am learning Angular2, after working with Angular1 for a couple of years. I'm creating a credit card form component, with the main goal to learn a couple of key concepts in Angular2. The component should handle all formatting, and return a Stripe token through a callback. I realised that I can handle callbacks in two ways.

Using an @Output parameter

In my component I define a output variable and use it like in this example:

export class CreditCardForm{
    ....
    @Output () callback = new EventEmitter();
    ....

    doCallback(){
        this.callback.emit({data: 123});
    }
}

// Parent view
<credit-card-form (callback)="creditCardCallback($event)"></credit-card-form>

Using an @Input variable

However, I could just pass the callback method (creditCardCallback, used in the parent template) to an input variable, like this:

export class CreditCardForm{
    ....
    @Input () callback;
    ....

    doCallback(){
        this.callback({data: 123});
    }
}

// Parent view
<credit-card-form [callback]="creditCardCallback"></credit-card-form>

The question

Why would I want to use @Output over @Input? What do I achieve by using @Output variables instead? As far as I can see, this just adds an overhead by having to utilise the EventEmitter class.

Anton Gildebrand
  • 3,641
  • 12
  • 50
  • 86

2 Answers2

14

There are always more than one way to skin the cat. However, in my opinion, using @Output has these benefits:

  • Code readability: It's easier to know the flow of data if using the recommended style.

  • De-coupling: For example, for normal @Output event, in your ParentComponent, you can have more flexibility of handling the dispatched event:

  • Last but not least - it enables banana in the box syntax: Say in your ChildComponent you have:

@Input() creditCardValue: string; @Output() creditCardValueChange: EventEmitter<string>;

Then you can have two-way binding in your ParentComponent easily:

<credit-card-form [(creditCardValue)]="creditCardVal"></credit-card-form>
Harry Ninh
  • 16,288
  • 5
  • 60
  • 54
  • Thank you very much for a great reply! This convinces me to use `@Output` for callbacks. Your reply also taught me that there is such a phrase as "banana in the box syntax", which leads to some fun pictures when Googling :) – Anton Gildebrand Oct 16 '16 at 14:09
  • I also assume that since with `@Output` you're using *Message Coupling* as opposed to *External Coupling* with `@Input`. So if in some case the `@Input` parameter is undefined in the child (has no parent?), then you don't have to worry about any `this.callback && this.callback.call(...)` or `noOp` methods. But I can **only** ever see using `@Output` for event-emissions -- *but I think its poor design to define your event-types at the component scale as opposed to the App-Scale; it'll create entropy for your app & its engineers*. – Cody Jan 05 '17 at 19:24
  • Thanks harry! Just amazed cause I was looking exaclty for it. My little problem is: How to swap from here to your nicer style? – gtamborero May 09 '17 at 14:38
  • I have it! Now i'm using banana style ;) @Input() data: string; @Output(this.data) dataChange = new EventEmitter(); – gtamborero May 09 '17 at 14:48
  • @gtamborero It always feels good when you can resolve your own problem, right? ;) – Harry Ninh May 10 '17 at 00:47
1

Imho, @Output is a HUGE design flaw, as you can't react to the result, or handling of it. The number one example of this flaw is the click of a button. When clicked, the button should be disabled 99% of the cases when the action is taking place. Once the action is done, the button should be enabled again. This is something you want to handle in the element itself, not in the parent. With @Output, you can't do this. Using an action function as @Input, you can do this! And if your action function returns an Observable or Promise, you can easily wait for it. Creating a custom directive that handles this disabling is easy, and thus you can use [click] everywhere and it will behave that way.

EDIT: I don't say @Output doesn't have it's uses, but not for actions you need to wait on etc.... Naming your @Inputs good, also clarifies the flow, like onDelete etc...

  • I'm not a fan of the semantics and personally prefer react-style callbacks, but to specifically respond to your concerns, there's no reason your source event needs to directly reference the emitter. Check out the docs example here: https://angular.io/guide/component-interaction#parent-listens-for-child-event. The child component binds a click event to a handler function local to the child. The child in turn calls `emit` on the emitter. This would certainly be a good place to add any type of validation you can imagine to short-circuit the event propagation. – FLGMwt Jul 30 '17 at 17:06
  • 2
    Yes, true, but you can't control the result of the function. I always inject actions, which are functions, in a component, so the component can do them, but don't have to know what exactly will be done. Such actions return an observable, because there can be an async action being done. So we need a way to disable the button etc..... based on the state of the action, and you can't do that with Output. With Output you always have to keep vars on your component to do it, with Input and [click], you can have this in a directive to handle it for you. – Dieter Geerts Jul 31 '17 at 18:43