View Encapsulation After the Ionic 4 Upgrade

August 30th 2019 Ionic 4 Angular

Angular supports several different modes of encapsulating styles in components so that styles from one component don't affect elements in other components. By default, Angular uses ViewEncapsulation.Emulated to emulate native Shadow DOM behavior without actually using Shadow DOM because it isn't yet supported widely enough in browsers. Ionic 4 uses the same default which can cause problems when upgrading applications from Ionic 3 where ViewEncapsulation.None was used.

Style Encapsulation in Ionic 3

Instead of using style encapsulation as implemented in Angular, Ionic 3 guided developers towards its own limited implementation by having a selector matching the component element name in every new .scss file:

sample {
  div {
    font-weight: bold;
  }
}

Any styles placed inside that selector only affected the matching elements inside the component:

<div>
  <ng-content></ng-content>
</div>

The same elements outside the component where not affected:

<ion-content padding>
  <div>Normal</div>
  <sample>Bold</sample>
</ion-content>

This type of style encapsulation didn't work in the other direction. The host page/component could still affect the style of child component's elements:

page-home {
  sample {
    div {
      color: red;
    }
  }
}
<ion-content padding>
  <sample>Red</sample>
</ion-content>

Taking advantage of this might not be a good practice but since it worked, developers used it at least to some extent.

Disabling Style Encapsulation in Ionic 4

Ionic 4 takes the approach of interfering with Angular as little as possible. This means that it defaults to ViewEncapsulation.Emulated. As a result, after upgrading a project from Ionic 3 to Ionic 4 most of styles won't get applied as they did before. The simplest way to fix the problem is to change style encapsulation back to ViewEncapsulation.None for every component (and page):

@Component({
  selector: 'sample',
  templateUrl: './sample.component.html',
  styleUrls: ['./sample.component.scss']
  styleUrls: ['./sample.component.scss'],
  encapsulation: ViewEncapsulation.None
})

As long as you make sure that the selectors for all components (and pages) remain the same as they were in Ionic 3, the styles should again work as they did. You should be aware that if you upgraded the project by regenerating the files and copying the code across as some upgrade guides suggest, selectors won't automatically be the same because they will now by default all have the app- prefix. Keep in mind also that even with the same selectors, the old styles will only work without changes for elements placed directly inside your components (and pages). If you also styled the internal elements of Ionic components, you will mostly get unpredictable results because they were all reimplemented in Ionic 4 as web components and now use Shadow DOM.

Instead of changing the encapsulation mode individually for every component, you can also change the default by passing an option to the bootstrapModule call in main.ts:

platformBrowserDynamic()
  .bootstrapModule(AppModule, { defaultEncapsulation: ViewEncapsulation.None })
  .catch(err => console.log(err));

The result will be the same.

Embracing Angular's Emulated Style Encapsulation

If you want to keep Angular's default ViewEncapsulation.Emulated mode and use its advantages, you'll have to make some changes to your CSS files:

  • First, you'll need to remove all the selector's matching the component/page element name. Instead of:

    sample {
      div {
        font-weight: bold;
      }
    }
    

    You'll keep only:

    div {
      font-weight: bold;
    }
    
  • If you were setting any properties at that level, e.g.:

    page-home {
      font-style: italic;
    }
    

    You will have to use the :host pseudo-class for those:

    :host {
      font-style: italic;
    }
    
  • Applying styles to component child elements form its host page/component won't be possible anymore:

    page-home {
      sample {
        div {
          color: red;
        }
      }
    }
    

    The component will have to explicitly implement the supported variations in style which can be triggered from its host. The trigger can be a class set on the component element, for example:

    <ion-content padding>
      <sample class="red">Red</sample>
    </ion-content>
    

    The component can specify the corresponding style using the :host() pseudo-class function:

    :host(.red) {
      div {
        color: red;
      }
    }
    
  • Ionic 3 also allowed the component styles to depend on a class set at a parent level, e.g.:

    <ion-content padding>
      <div class="large">
        <sample>Large</sample>
      </div>
    </ion-content>
    

    The .scss could simply add a matching selector around selectors for its own elements:

    .large {
      sample {
        div {
          font-size: 20px;
        }
      }
    }
    

    In Ionic 4, the same result can be achieved by using the :host-context() pseudo-class function instead:

    :host-context(.large) {
      div {
        font-size: 20px;
      }
    }
    

From my experience, these rules should cover most cases. The changes aren't difficult to implement either. The big challenge in fixing the appearance of the application after the upgrade to Ionic 4 will be all the style changes that were applied to Ionic components.

Copyright
Creative Commons License