top of page

Angular components: I’ll add just one more input 🤞

Writer: ZsoltZsolt

Updated: Jan 28

Unmaintainable apps of the world, unite!

It’s always the most used, and common components that are the worst. It usually starts with a simple but reusable component that is expected to appear on many pages of the application. Say, a table. Simple enough, the team will probably implement it using a third party, but simplifying the API for easier use, or they will just go ahead and do a simple implementation themselves.

By the way here’s a checklist to help with the decision on using a third party or not:

Optimisation and Open Source and their major trade-offs




Most probably the basic implementation would essentially be like:

rows = input.required<TableRow[]>();columns = input.required<TableColumns[]>();

Then comes the PO 3 weeks later:

We need to search on the new page we’ll develop, can you add one to the table?

Then we add:

search = input<string>();searchResults = output<TableRow[]>();

Okay, but we don’t want a search field on ALL of our tables, do we? And so we add isSearchEnabled = input<boolean>(false);.


Right, but then one of the features requires us to enable the selection of multiple rows and save the selection.

isSelectionEnabled = input<boolean>(false);selectedRows = output<TableRow[]>();save = output<TableRow[]>();

But also the data is coming from the backend and it’s sometimes, turns out, huge.

loading = input<boolean>(false);

But then it’s indeed a huge dataset, so we need some sort of pagination. The problem is one of the screens suites infinite scroll really well, the other should use pagination.

usePagination = input<boolean>(false);useInfiniteScroll = input<boolean>(false);pageSize = input<number>();pageSelected = output<number>();

The cherry on top of the 💩 pile is when the CSS class inputs start to appear.

customHeadClass = input<string>();
customBodyClass = input<string>();

This is where it gets really dicey. This is only a dummy example, but feature-specific inputs sooner or later start to interact with each other. This creates complexity which leads to more inputs instead of refactoring the existing ones to suit the new requirements.

We are already at a dozen inputs and outputs and it’s not even that complex yet.

Solution

Some are evident, like instead of using a customClass input to tailor the looks for one feature and customClass2 for another, use configurable themes and styles for big projects and monorepos:


Or instead of loading, let the parent decide if they want a spinner, a placeholder, or whatever other solution. It is not an integral part of tables.

@if(loading()) {
  <spinner-or-whatever></spinner-or-whatever>
} @else {
  <common-datatable [rows]="rows()" [columns]="columns()"></common-datatable>
}

In some cases drawing the line and providing a solution is straightforward. In others, it requires design and planning to suit the specific project. Sometimes a service is the solution to avoid passing values through 5–10 components. Sometimes it’s wrapper components that decorate the generic table with something. Sometimes it’s some sort of content projection.

// not forgetting how this language can be used OO still
class CoreDatatableInputs {
  rows = input.required<TableRow[]>();
  columns = input.required<TableColumns[]>();
}

@Component(...)
export class CommonDatatable extends CoreDatatableInputs {...}

// wrapper example
@Component({
  selector: 'common-datatable-search',
  standalone: true,
  imports: [],
  template: `
<common-datatable-search-bar 
  [rows]="rows()" 
  (filteredRowsChange)="onFilteredRowsChange($event)">
</common-datatable-search-bar>
<common-datatable 
  [rows]="filteredRows() ?? rows()" 
  [columns]="columns()">
</common-datatable>
`,
})
export class TableWithSearch extends CoreDatatableInputs {
  filteredRows = signal<TableRow[] | undefined>(undefined);
  
  onFilteredRowsChange(newFilteredRows: TableRow[] | undefined) {
    this.filteredRows.set(newFilteredRows);
  }
}
Having a multi-purpose component is 200 hours and 24 bugs away from a having generic component.

One is serving a dozen features and that dozen features only, the other can be repurposed for those features and others fast.


 
 
 

Comments


SIGN UP AND STAY UPDATED!

Thanks for submitting!

© 2019 ZD Engineering. Proudly created with Wix.com

bottom of page