Angular Styling Made Easy: Leveraging the Power of CSS Variables
Tags: angular, css
Published: 2023-01-10 09:30:00

**Css variables** are a very powerful tool for creating highly customizable, scalable, and maintainable components in Angular or any other JavaScript framework.

In this example, we will demonstrate how to use CSS variables to simplify style customization for shareable UI components in Angular.

To begin, let's create a component tree with a depth of 2:

```typescript
@Component({
  selector: 'child-level-2',
  standalone: true,
  template: `
    <h2>Some Text</h2>
    <p class="custom-paragraph">A big paragraph</p>
  `,
  styles: [
    `
      .custom-paragraph {
        font-size: 20px;
        color: blue;
      }
    `,
  ],
})
export class ChildLevel2Component {}

@Component({
  selector: 'child-level-1',
  standalone: true,
  imports: [ChildLevel2Component],
  template: ` <child-level-2></child-level-2> `,
})
export class ChildLevel1Component {}

@Component({
  selector: 'parent',
  standalone: true,
  imports: [ChildLevel1Component],
  template: ` <child-level-1></child-level-1> `,
})
export class ParentComponent {}
```
In this example, we have a `ParentComponent` that calls a `ChildLevel1Component` which in turn has a `ChildLevel2Component` . We want to customize the font and color of a paragraph within `ChildLevel2Component` from `ParentComponent`.

This is a common pattern in many applications. We often want to alter  the appearance of a deep component in the component tree.

We will examine some common solutions and the pitfalls of each approach.

## @Inputs()
The naive way to pass desired styles would be though `@Input()`.

```typescript
@Component({
  selector: 'child-level-2',
  standalone: true,
  template: `
    <h2>Some Text</h2>
    <p style="color: {{ color }}; font-size: {{ font }}px">A big paragraph</p>
  `
})
export class ChildLevel2Component {
  @Input() font = 20;
  @Input() color = 'blue';
}

@Component({
  selector: 'child-level-1',
  standalone: true,
  imports: [ChildLevel2Component],
  template: ` <child-level-2 [color]="color" [font]="font"></child-level-2> `,
})
export class ChildLevel1Component {
  @Input() font = 25;
  @Input() color = 'orange';
}

@Component({
  selector: 'parent',
  standalone: true,
  imports: [ChildLevel1Component],
  template: ` <child-level-1 color="red" [font]="30"></child-level-1> `,
})
export class ParentComponent {}
```
**Issue: **
- We will need to add an `@Inputs()` for each new property we want to customize.
- Worst, this quickly becomes unwieldy if we have a deeply nester component structure, as we would need to add the same inputs to every component down the tree. 
- If we want to customize a component from a third party library that we don't own, it won't be possible if the library's author has not provided `@Inputs` for the desired properties. 
- Personally, I prefer to separate the functional aspect of the code from the appearance of a component.

## ng-deep
The next common way to customize style is `::ng-deep` . 

```typescript
@Component({
  selector: 'child-level-2',
  standalone: true,
  template: `
    <h2>Some Text</h2>
    <p class="custom-paragraph">A big paragraph</p>
  `,
  styles: [
    `
      .custom-paragraph {
        font-size: 20px;
        color: blue;
      }
    `,
  ],
})
export class ChildLevel2Component {}

@Component({
  selector: 'child-level-1',
  standalone: true,
  imports: [ChildLevel2Component],
  template: ` <child-level-2></child-level-2> `,
})
export class ChildLevel1Component {}

@Component({
  selector: 'parent',
  standalone: true,
  imports: [ChildLevel1Component],
  template: ` <child-level-1></child-level-1> `,
  styles: [
    `
      :host child-level-1 ::ng-deep .custom-paragraph {
        font-size: 30px;
        color: red;
      }
    `,
  ],
})
export class ParentComponent {}
```
At first sight, this is doing exactly what we want. `::ng-deep` allows us to bypass the style encapsulation provided by Angular and its Shadow DOM. However ::ng-deep is **not recommended** and should be used carefully.

The main reason `::ng-deep` is considered bad is because it undermines the purpose of **style encapsulation** in Angular. Style encapsulation is an important feature of Angular components because it helps to ensure that styles defined in one component do not affect the styles of other components. By using `::ng-deep`, you can apply styles to elements that are not in the template of the component, which can potentially cause unexpected side effects and make it more difficult to understand and maintain the styles in your application.

> _**Side note:** Currently, `::ng-deep` pseudo-class is the only way to customize UI components in a third party library that has not provided other customization methods._

## ViewEncapsulation
By default, Angular creates an **Emulated Shadow DOM** for each component. All Css properties declared within a component via `styles` or `styleUrls` are local to that component and are not shareable.

However we can change the encapsulation of our component to `ViewEncapsulation.NONE`, which means that all styles declared in that component can be applied to any other HTML elements within the entire application.

```typescript
@Component({
  selector: 'child-level-2',
  standalone: true,
  template: `
    <h2>Some Text</h2>
    <p class="custom-paragraph">A big paragraph</p>
  `,
  styles: [
    `
      .custom-paragraph {
        font-size: 20px;
        color: blue;
      }
    `,
  ],
})
export class ChildLevel2Component {}

@Component({
  selector: 'child-level-1',
  standalone: true,
  imports: [ChildLevel2Component],
  template: ` <child-level-2></child-level-2> `,
})
export class ChildLevel1Component {}

@Component({
  selector: 'parent',
  standalone: true,
  imports: [ChildLevel1Component],
  template: ` <child-level-1></child-level-1>`,
  encapsulation: ViewEncapsulation.None, // 👈
  styles: [
    `
      .custom-paragraph { /* 👈 we override the css class */
        font-size: 30px !important;
        color: red !important;
      }
    `,
  ],
})
export class ParentComponent {}
```
- I find this option very dangerous. I prefer to keep my global styles inside the `style.scss` file at the root of my application. Defining global styles inside a component can cause unexpected behavior and can break already existing components. Use this with extreme cautious and only if you know what your are doing. Be very specific with your CSS class names. 

## Css variables
Css variables are like Angular's `@Input()` decorator, but for styles. They allow us to keep full control over what can be changed and can be extended outside the scope of component's encapsulation.

Css variables are defined in the following way:

```CSS
.custom-class {
  --my-custom-font: 20px;
}
```
And used like the following:

```CSS
font-size: var(--my-custom-font, /*default value*/)

/* ex: */ 
font-size: var(--my-custom-font, 10px)
font-size: var(--my-custom-font, var(--my-global-font, 5px))

/* invalid */
font-size: var(--my-custom-font, --my-global-font)
```
Let's apply it to our example, which gives:

```typescript
@Component({
  selector: 'child-level-2',
  standalone: true,
  template: `
    <h2>Some Text</h2>
    <p class="custom-paragraph">A big paragraph</p>
  `,
  styles: [
    `
      .custom-paragraph {
        font-size: var(--child-level-2-font-size, 20px);
        color: var(--child-level-2-color, blue);
      }
    `,
  ],
})
export class ChildLevel2Component {}

@Component({
  selector: 'child-level-1',
  standalone: true,
  imports: [ChildLevel2Component],
  template: ` <child-level-2></child-level-2> `,
})
export class ChildLevel1Component {}

@Component({
  selector: 'parent',
  standalone: true,
  imports: [ChildLevel1Component],
  template: ` <child-level-1></child-level-1> `,
  styles: [
    `
      :host child-level-1 {
        --child-level-2-font-size: 30px;
        --child-level-2-color: red;
      }
    `,
  ],
})
export class ParentComponent {}
```
That's it, we can customize our application as we please and bypass the style encapsulation of Angular.

However there is still one caveat. If we want to set a default color inside our `ChildLevel1Component` using a CSS variable, we won't be able to override it inside `ParentComponent`. The rule for CSS variables is that the last one defined will be applied.

The solution is to create a new CSS variable that the parent component of ChildLevel1Component can use.

```CSS
:host child-level-2 {
  --child-level-2-font: var(--child-level-1-font, 10px);
  --child-level-2-color: var(--child-level-1-color, yellow);
}
```

---

That's it for this article! You should now master and styles any component easily in Angular _(or any other JS framework)_.

I hope you learned new Angular concept. If you liked it, you can find me on [Twitter](https://twitter.com/laforge_toma) or [Github](https://github.com/tomalaforge).

> 👉 If you want to accelerate your Angular and Nx learning journey, come and check out [Angular challenges](https://github.com/tomalaforge/angular-challenges).