14.0.2 • Published 2 years ago

@mlakomy/class-binder v14.0.2

Weekly downloads
-
License
MIT
Repository
github
Last release
2 years ago

ClassBinder

ClassBinder gracefully overcomes one of my oldest issues with Angular – styling host element.

This is something that bugged me since my very first days with Angular, after switching from Vue.js. This library is a result of years of struggle to come up with a lightweight, decent solution.

With something more robust than HostBinding() 😉

Table of Contents

Installation

NPM

  npm install @mlakomy/class-binder

Example

import { ClassBinder } from '@mlakomy/class-binder';

@Component({
  selector: 'app-root',
  template: '',
})
export class Component extends ClassBinder('root') {}

Extending ClassBinder class will result in adding class .root to app-root element.

Styling root component with ViewEncapsulation.Emulated

Styling host element with ViewEncapsulation.Enabled slightly differs from writing your usual SCSS. In order to target your element properly, you have to do it like so:

:host.root {
  display: flex;
  color: black;
}

Styling root component with ViewEncapsulation.None

When you don't use any kind of encapsulation, you can add your class to SCSS in very straightforward way:

.root {
  display: flex;
  color: black;
}

BindClass()

In order to make our life easier ClassBinder also comes with some magic 🧙 in form of custom decorator – BindClass(). Basically it is a neat way of assigning classes dynamically to your host element.

Note: I've written all examples below with usage of Input() decorator in mind, but of course it is not necessary. Also, visibility of your property doesn't matter too.

Static class name

import { ClassBinder, BindClass } from '@mlakomy/class-binder';

@Component()
export class Component extends ClassBinder('root') {
  @Input() @BindClass('root--disabled') public disabled: boolean = false;
}

Based on value of disabled property, ClassBinder will dynamically add or remove .root--disabled class from host element.

Type of your property doesn't have to be a boolean. It can be any type, BindClass() decides whether it should add or remove the class based on truthy / falsy values.

Factory class name

import { ClassBinder, BindClass } from '@mlakomy/class-binder';

const classFactory = (value: number): string | null => {
  if (value < 0) {
    return `root--negative`;
  } else if (value > 0) {
    return `root--positive`;
  }

  return null;
};

@Component()
export class Component extends ClassBinder('root') {
  @Input() @BindClass(classFactory) public value: number = 0;
}

Each time the value property change, BindClass() will evaluate class name all over again (and of course remove the old one). It is really powerful tool that gives you a way to organize your dynamic classes in a very clean and reusable way.

Property class name

import { ClassBinder, BindClass } from '@mlakomy/class-binder';

type Color = 'primary' | 'secondary' | 'error';

@Component()
export class Component extends ClassBinder('root') {
  @Input() @BindClass() public color: Color | null = 'primary';
}

The most simple approach to bind classes to the host element. BindClass() will use current value of the property. In this case it would give following results: .root .primary on your host element.

It is important to note, that value of this property must be either of type string | null | undefined. Each different type will result in runtime error.

Limitations

  • The most obvious limitation of this is that it requires extending ClassBinder class, and as we are all aware, currently in TypeScript you can only extend from one class.

    My ideal solution would be to make ClassBinder as a class decorator, but as I'm aware Angular won't treat your component as injectable, if you extend its constructor via decorator.

Future

  • At some point I would like to bring support for binding classes based on Observable value.