Angular component's interface ( @Output / @Input ) how do you ex

ghz 8months ago ⋅ 121 views

Angular component's interface ( @Output / @Input ) how do you expose methods? Or certain type of events?

In angular, we can encapsulate functionality in a component and define its interface via @Input and @Output Interfaces in other systems typically include properties, methods and events.

It is clear how to make a property part of your interface - decorate it with @Input. It's clear how to make an event part of your interface - decorate EventEmitter with @Output It is not clear to me how to make a method part of your interface. And also how to declare events where the event handlers can return a value.

Use cases: My component is a tree-table with buttons to delete add and edit rows. It has this:

 @Output() onDelete = new EventEmitter<TreeNode[]>();

this will notify the consumer that user wants to delete these nodes, but I want the consumer to be able to cancel that delete (optionally). It would be easy to do if event handlers were allowed to return values. I would declare an event where handler takes in the same parameter (TreeNode[]) and returns a Boolean. But event handlers must return void as per definition of EventEmitter's emit(e: T):void

Yes I know you can wrap the object you want to pass to a component in another object and include some boolean property in that, but to me that seems like a hack. The interface becomes unclear. Now the case for methods: I have in my component a method:

confirmEdit(node TreeNode) 

I want the consumer of my component to be able to call this method. How do I expose it via @Input or @Output?

Yes I know that the consumer can actually reach into my component via say @ViewChild and call any method it has, but that seems like a violation of encapsulation principle.

Is there a better way?

Answers

In Angular, there isn't a direct equivalent to @Input and @Output for exposing methods of a component. However, you can achieve similar functionality by using @ViewChild or by creating a service.

  1. Using @ViewChild: You can expose a method of your component by creating a public method in your component class and then accessing it using @ViewChild in the parent component.

    Example:

    import { Component, ViewChild } from '@angular/core';
    
    @Component({
      selector: 'app-child',
      template: `
        <button (click)="callMethod()">Call Method</button>
      `
    })
    export class ChildComponent {
      public confirmEdit(node: TreeNode): void {
        // Implementation
      }
    }
    
    @Component({
      selector: 'app-parent',
      template: `
        <app-child></app-child>
      `
    })
    export class ParentComponent {
      @ViewChild(ChildComponent) childComponent: ChildComponent;
    
      callChildMethod(): void {
        this.childComponent.confirmEdit(node);
      }
    }
    

    This approach allows the parent component to call the confirmEdit method of the child component directly.

  2. Using a Service: Another approach is to create a service that exposes the functionality you want to share between components. Components can then inject this service and call its methods.

    Example:

    import { Injectable } from '@angular/core';
    
    @Injectable({
      providedIn: 'root'
    })
    export class TreeTableService {
      public confirmEdit(node: TreeNode): void {
        // Implementation
      }
    }
    
    @Component({
      selector: 'app-parent',
      template: `
        <button (click)="callServiceMethod()">Call Service Method</button>
      `
    })
    export class ParentComponent {
      constructor(private treeTableService: TreeTableService) {}
    
      callServiceMethod(): void {
        this.treeTableService.confirmEdit(node);
      }
    }
    

    This approach decouples the functionality from the component itself and allows for better separation of concerns.

Both approaches have their pros and cons, so choose the one that best fits your use case and architectural preferences.