In our previous post Ionic 4 vs Ionic 3 — What you need to know about Ionic 4 we surfaced the main differences between Ionic 3 and Ionic 4.

Then, when Ionic 5 was released, we created the post What's new in Ionic 5 - Migration and Free Starter where we explain how to take advantage of the new benefits from Ionic 5.

In this series of posts we are going to go deeper on the structure and core concepts of Ionic apps and explore more advanced topics, so if you are new to Ionic, I strongly recommend that you first read our Introduction to Ionic tutorial.

Although there were some important changes, the big picture hasn't changed much from Ionic 4 to Ionic 5, so you shouldn't be afraid to make the change.

Having said that, it's also worth mentioning that you may have to invest some time getting used to new development habits introduced in Ionic 4. For me these were a bit shocking at first, but now that I see the benefits of the new approach I'm starting to enjoy them (by the way, that was the main reason I started writing this post, I feel some of the new concepts introduced in Ionic 4 - and still present in Ionic 5 - need to be explained so that developers don't freak out and get intimidated).

Without further ado, we wrote three posts for this series covering:

Part 1 (this tutorial) - Web Components? Shadow DOM? CSS Variables? Stencil? Understanding the new components architecture presented in Ionic 4.
Covering everything from styling and customizing Ionic components, to building custom Web Components with Stencil and then use them in your Ionic projects (or any other framework, as Web Components are framework agnostic!).

Part 2 - Ionic Navigation in depth

From Ionic 4 onwards, the routing is handled by the Angular router (if you are building an Ionic angular app), this has many advantages like URL discoverability, route resolves, route guards and the ability to lazy load modules. However, there are also some user perceived performance gotchas that need to be tackled.

Part 3 - The state of PWA in Ionic

PWAs are the big bet of Ionic. I agree on this and believe the near future will be all about progressive web apps as they present significant benefits to most app use cases. Ionic Framework is a great tool to build PWAs, but there are still some issues to be polished.

At IonicThemes we are big fans of Learning by example, that's why all our Ionic tutorials include a complete and free Ionic code example that you can reuse in your Ionic projects. We strive to create the best content for Ionic Framework, both tutorials and templates to help the community succeed.

We work towards empowering developers to achieve a free life through coding. That's what motivates us at IonicThemes everyday. Hope we can help you creating better apps with our detailed and professional templates, crafted with love and dedication.

ionic templates

Web Components are a big step forward towards the future of the web.

Yes, I know, there's a lot of buzz around this lately. I believe Web Components represent a tipping point and as I mentioned before there may be some developers that experience the transition refusing and resisting instead of embracing the improvements.

I say this because I experienced it myself when I started playing around with the new Ionic Components.

The big thing about Web Components is encapsulation, this has a lot of benefits, but also enforce you to follow a more strict interface, leaving behind the anarchy and flexibility of Ionic 3 non Web Component elements.

Shadow DOM is for you to blame

You may be wondering what is Shadow DOM?
Shadow DOM defines how to use encapsulated styles and markup within a Web Component. Before this approach, we relied on the global nature of HTML, CSS, and JS.

Non-encapsulation, disguised as flexibility, enabled us to easily style stuff by just specifying the correct css selectors. A major drawback from non-encapsulation is that CSS specificity becomes a huge issue (using !important everywhere), style selectors grow out of control, and performance can suffer. The list goes on.

With the release of Ionic Framework 4, at the beginning of 2019, the Ionic team migrated all their components to become a distributed set of Web Components. Web Components follow the Shadow DOM specification in order to encapsulate styles and markup. So, from Ionic 4 onwards, these are the new rules.

We need to learn how to use the Shadow DOM

Take this Ionic Web Component:

<ion-button class="host-element">
  <span class="host-child-element">span</span>
</ion-button>

We can change and add new styles to both the host element and the child elements you explicitly define inside the host element. So far nothing different.

In this case, <ion-button> is the host element, and the <span> explicitly defined inside is the host child element. These elements are governed by the global nature of CSS and you can change or add new styles the way you are used to.

However, as the <ion-button> is a Web Component, that html we typed isn't exactly what gets injected into the DOM tree. This is what we see when inspecting the DOM:

<ion-button class="host-element">
  #shadow-root (open)
  <style>...</style>
  <button type="button" class="button-native">
    <span class="button-inner">
      <slot name="icon-only"></slot>
      <slot name="start"></slot>
      <slot>
        span
      </slot>
      <slot name="end"></slot>
    </span>
  </button>
  <span class="host-child-element">span</span>
</ion-button>

Inside the shadow-root node we see a <style> element and a <button> element. These are the inner workings of the <ion-button> Web Component.

Every element inside the shadow-root is governed by the encapsulated scope within it. This means we can't add new styles to those elements and we can't modify styles directly on those elements. We will only be able to change properties (styles) that the Web Component defined through CSS variables.

You can think of this as an API to interact with the inner structure of a Web Component. For example, if we look at the source code of the <ion-button> we get to see the different properties of the <button type="button" class="button-native"> inside the shadow-root that are defined with CSS variables:

.button-native {
  ...
  height: var(--height);
  transition: var(--transition);
  border-width: var(--border-width);
  border-style: var(--border-style);
  border-color: var(--border-color);
  background: var(--background);
  box-shadow: var(--box-shadow);
  opacity: var(--opacity);
  ...
  line-height: 1;
  cursor: pointer;
  ...
}

You can find the list of the available properties for the Ionic button UI component in the documentation.

Considering the group of elements inside the shadow-root, in our example <button type="button" class="button-native"> and it's childs (<span class="button-inner">, etc), all the properties of those elements defined using CSS variables are part of that "public API" that defines the limited stuff we can change about the inners of that Web Component.

If an element inside the shadow-root doesn't have some property defined with CSS variables, then there's no way for us to modify that property.

In the example above height, border, background and opacity properties of the <button type="button" class="button-native"> are part of the "public API" and we can adjust their values, but line-height and cursor properties are not part of it, so we won't be able to adjust their values.

Go ahead and play around with the following embedded snippet to see how to style the Ion Button component using the CSS Properties.


While I was writing this I realized it's very difficult to explain because there is so much technical terminology involved. I really hope the example I set up helped illustrating the situation, but if you don't find it clear enough please show me how can I improve the explanation in the comments section below.

Need help styling your Ionic mobile app? At IonicThemes we want to help you. That's why we teamed up with design experts to assure top quality in our Ionic Templates. Use our ready-made Ionic apps as a base and bump your Ionic CSS styling skills!

ionic templates

What's the difference between CSS variables and SASS variables?

In the past versions of Ionic (v3 and backwards), the theming and styling of Ionic depended mostly on SASS, where we would define .scss files that would be compiled down into standard CSS at runtime. From Ionic 4 onwards, we still be making use of SASS variables, but also of CSS variables.

One of the key differences between using something like SASS variables and CSS variables is that SASS is a preprocessor while CSS variables make use of standard CSS that is supported by browsers.

Think of SASS like TypeScript. The TypeScript and Sass code we write doesn't actually run in browsers; it gets compiled down to JavaScript and CSS respectively everytime you build your app.

The key benefit of using CSS variables is that they can be modified at runtime. Since CSS variables are supported natively by the browser, there is no compililation step required to get the variables working.

What this means, is that you could just open up the browser debugger, change some variables, and see the changes take effect in your application immediately (this is something you won't be able to do with SASS variables as SASS involves a compiler before generating the css code that the browser understands).

This also means that you can change from Javascript these CSS variables dynamically in your Ionic framework app. This would make it quite easy to build functionality like a dark/light theme switcher, where a user could toggle a switch and instantly change the theme of the entire application.

If you experience frustration when styling Ionic Components, that's because you are not used to having rules on what you can and cannot style. Just try to change your mental map, follow the new rules, and it will be straightforward.

Ionic Platform specific styles

Adding platform specific styles to your app is another common use case that changed in Ionic 4. You may want platform-specific styles for the whole Ionic app or just for certain Ionic pages. Let's see how to achieve this.

The first scenario is easy; you just need to add those platform-specific styles inside the src/global.scss file and you are done.

html.md {
  color: red;
}

html.ios {
  color: blue;
}

On the other hand, if you want platform-specific styles just for certain pages, due to encapsulation, you can't target the html node from the page. Fortunately, Angular has a way to apply styles based on some condition outside of a component's view. To achieve this, you need to use the :host-context() pseudo-class selector on your page stylesheet, for example in app/home/home.page.scss file:

@import "~@ionic/angular/dist/themes/ionic.theme.default.md";
@import "~@ionic/angular/dist/themes/ionic.theme.default.ios";

:host-context(.ios) {
  ion-grid {
    height: calc(100vh - #{$toolbar-ios-height});
  }
  }

  :host-context(.md) {
  ion-grid {
    height: calc(100vh - #{$toolbar-md-height});
  }
}

In this example I go a bit further and also import @ionic/angular theme variables to use them inside my platform specific-styles.

Sometimes we need to change the layout or some UX behaviour that goes beyond simple CSS styling adjustments.

In my experience, I found three alternatives and each of them has pros and cons.
First alternative: clone the original Ionic Component and adjust the code to our needs.
Second alternative: wrap the original Ionic Component with a custom element that enhances the Ionic Component the way we need.
Third alternative: use CSS Shadow Parts.

The first alternative will give you more flexibility as you would be able to deeply modify the code and behaviour of the component. You may also find it not that trivial as the original Ionic Component may rely and depend on other Ionic Components, Services and Utils to work. Also, if you follow this approach, you may miss future updates and improvements that the Ionic team does to the Component you cloned. Not recommended.

The second alternative is not that flexible but you won't get dirty changing Ionic Framework components that already work. This is the approach we are going to explore in this Ionic Web Components tutorial.

The third alternative is quite new, and was added in Ionic 5.2.0. This post explains very good the motivation behind adding shadow parts to Ionic UI Components and how to use them. Currently (October 2020), this is the recommended approach to follow. However, as CSS Shadow Parts are kind of new, keep an eye on the browser support.

Shadow Parts in Ionic Components

Shadow Parts are very powerful and a HUGE win for customization in Ionic Components. Being able to customize any property on an element inside of a shadow root (as long as it has been exposed as a part) takes away the need for CSS variables for every property possible.

CSS Shadow Parts give users the ability to target an element inside of a shadow tree from outside of it. In order for the element to be targeted, it has to be exposed by the library author, in this case, Ionic Framework.

Although this post doesn't go into details about the shadow parts, I really encourage you to learn more about them and try to use them in order to customize the Ionic components.

Adding new functionalities to existing Ionic Components

I love the <ion-img> component, and especially the new IntersectionObserver feature the Ionic Team added to it, which enables loading the image only when it's on the viewport.

One thing I don't like is that before the image loads the user can experience a flick.

Ionic Custom Component

We found this easy to fix by using some css techniques to ensure the image will always respect some aspect ratio preventing the content that's below the image to jump while the image is loading.

This technique is something I have use a lot in all the templates we do at IonicThemes, so it's a great example to illustrate how to enhance an existing Ionic Component without modifying the inner Web Component.

Customizing ion-img Ionic Component

The idea is to build a simple wrapper component with two inputs. The image src, that we will pass it through directly to the <ion-img> component, and the ratio. We are going to use both ratio width and height in combination with some css to achieve the desired behaviour.

This is the proposed markup for the wrapper component:

<c-preload-image [ratio]=”{w: 2, h: 1}” [src]="'https://placeimg.com/200/100/any'">
</c-preload-image>

We will also add a spinner to indicate the user that we are loading the image. This way we will improve the user experience of the <ion-image> component.

The wrapper component

The idea is to add functionality to an existing Ionic component (the <ion-img> component). For this purpose we are going to follow this css technique to ensure the image conserves a defined aspect ratio all the time to prevent content jumping around while images load.

We achieve this by calculating the aspect ratio height and use it as the padding-bottom value:

src/app/components/preload-image/preload-image.component.ts
import { Component, Input, ElementRef, Renderer2, ViewEncapsulation, ViewChild, OnChanges, PLATFORM_ID, Inject } from '@angular/core';

@Component({
  selector: 'preload-image',
  templateUrl: './preload-image.component.html',
  styleUrls: [
    './preload-image.component.scss'
  ],
  encapsulation: ViewEncapsulation.None
})
export class PreloadImageComponent implements OnChanges {
  _src = '';
  _ratio: { w: number, h: number };

  constructor(
    private _elementRef: ElementRef,
    private _renderer: Renderer2
  ) {}

  @Input() set src(val: string) {
    this._src = (val !== undefined && val !== null) ? val : '';
  }

  @Input() set ratio(ratio: { w: number, h: number }) {
    this._ratio = ratio || {w: 1, h: 1};
  }

  ngOnChanges() {
    const ratio_height = (this._ratio.h / this._ratio.w * 100) + '%';
    // Conserve aspect ratio (see: https://stackoverflow.com/questions/1495407/maintain-the-aspect-ratio-of-a-div-with-css/10441480#10441480)
    this._renderer.setStyle(this._elementRef.nativeElement, 'padding', '0px 0px ' + ratio_height + ' 0px');
    this._update();
  }

  _update() {
    this._loaded(false);
  }

  _loaded(isLoaded: boolean) {
    if (isLoaded) {
      setTimeout(() => {
        this._renderer.addClass(this._elementRef.nativeElement, 'img-loaded');
      }, 500);
    } else {
      this._renderer.removeClass(this._elementRef.nativeElement, 'img-loaded');
    }
  }
}

Finally, we also need to get notified when the <ion-img> finishes loading in order to hide the spinner. This is easy because the <ion-img> component exposes the ionImgDidLoad() method to handle that:

src/app/components/preload-image/preload-image.component.html
<ion-spinner class="spinner"></ion-spinner>
<ion-img [src]="_src" (ionImgDidLoad)="_loaded(true)"></ion-img>

Finally some css where we apply the css technique mentioned before and some styles to properly show and hide loading indicators while the image loads:

src/app/components/preload-image/preload-image.component.scss
$white: #FFFFFF;
$grey: #9e9e9e;

$preload-image-bg: rgba(darken($white, 10), .25);

$spinner-size: 28px;
$spinner-color: $grey;

preload-image {
  display: block;
  background-color: $preload-image-bg;
  overflow: hidden;
  position: relative;
  width: 100%;

  .spinner {
    position: absolute;
    top: calc(50% - #{ ($spinner-size/2) });
    left: calc(50% - #{ ($spinner-size/2) });
    width: $spinner-size;
    height: $spinner-size;
    font-size: $spinner-size;
    line-height: $spinner-size;
    color: $spinner-color;
  }

  ion-img {
    position: absolute;
    top: 0;
    left: 0;
    transition: visibility 0s linear, opacity .5s linear;
    opacity: 0;
    visibility: hidden;
    width: 100%;
    height: 100%;
  }

  &.img-loaded {
    background-color: transparent;
    border: 0;

    ion-img {
      opacity: 1;
      visibility: visible;
    }

    .spinner {
      display: none;
      visibility: hidden;
    }
  }
}

The result:

Ionic Custom Component

Awesome! We improved the user experience by preventing content jumping around the page and by showing a loading indicator while the image loads.

If you want to dive into even more advanced stuff, then follow me while we build a custom Web Component with Ionic Stencil.

Stencil is a Web Component compiler for building fast, reusable UI components. It was built and is maintained by the Ionic Framework Team. Compared to alternatives like Polymer, Stencil is not a framework nor a library; it's just a compiler that turns classes with decorators into standards-based Web Components. This means that you can generate a collection of stencil components and use them in Angular, React, Vue or Polymer without any problem.

We really like the lightweight and straightforward approach of Stencil.

Creating a Web Component with Stencil

In this Ionic tutorial I want to explain how to build a simple Web Component using Stencil to show you how easy is to build, distribute, and integrate Stencil Web Components into a front-end framework.

I really like the idea and possibilities of multi-colored SVG icons, and Stencil is the perfect tool to create one.

So, let's go ahead with this Stencil Web Component example so you can get the chance to see more examples about Web Components, shadow DOM, and CSS Variables to reinforce what we have learned so far.

Icon images vs Font icons vs SVG icons

Long gone are the times of using images and CSS sprites to make icons for the web. With the popularity of web fonts, the icon fonts have turned into the main solution for displaying icons in web projects.

Because fonts are vectors, you don't have to worry about resolution. They derived the same benefit from the same CSS properties as text. As a result, you have full control over color, size and styles. You can add effects, transforms, and decorations such as rotations, shadows or underlines.

You should also know that Icon fonts aren't perfect, that's why a growing number of people prefer using inline SVG images.

Yet, there's one thing that remains absolutely impossible with icon fonts: multicolor support. Only SVG can do this.

I think this is very important because icons and illustrations in general are super powerful micro-interactions, and if we can enhance them, then we will see gains in the user experience.

The problem

We need multi-color support for our icons to increase expressiveness and improve our user experience. A great example of multi-colored icons (and inspiration for this example) is the Iconic icon set.

Another great project that inspired me is Ionicons - created by the Ionic Framework team - as they introduced the "icons as Web Components" concept.

An advantage to the font icon file is that all of the icons are in one file.
A disadvantage to the font icon file is that all of the icons are in one file.
Moreover, large font files negatively affect a webpage's time to first paint.

Most of the time, it might be smarter to ask for few svgs utilizing the Web Component method, although if a website page needs to demonstrate numerous icons at once, the font icon might be a better choice.

The solution

Let's combine the benefits of both projects and create a multi-color svg icon Web Component using Stencil.

Stencil has a barebones starter project that will help you getting started building Web Components in a breeze. This stencil starter includes all the boilerplate, project structure and major configs you will need.

This is the multi-color svg icon Web Component we are going to build using Stencil:

Ionic Stencil Component

I started by cloning the Stencil component starter repo which is a starter project for building a standalone Web Component using Stencil.

git clone https://github.com/ionic-team/stencil-component-starter.git c-coffee-icon
cd c-coffee-icon
git remote rm origin

c-coffee-icon is the name of my new component.

I also added @stencil/sass as I wanted to use SASS in this example.

npm install @stencil/sass --save-dev

After installing the dependency we need to add the SASS plugin in the stencil.config.js file.

import { Config } from '@stencil/core';
import { sass } from '@stencil/sass';

export const config: Config = {
  namespace: 'c-coffee-icon',
  outputTargets:[
    {
      type: 'dist'
    },
    {
      type: 'www',
      serviceWorker: null
    }
  ],
  plugins: [
    sass()
  ]
};

Building the Stencil Web Component

In order to achieve multi-color SVG's, we need to rely on composing our SVG with multiple paths in order to color each part of the SVG independently. This awesome post helped me grasping the technique above.

After that, coding the component was straightforward:

import { Component } from '@stencil/core';

@Component({
  tag: 'c-coffee-icon',
  styleUrl: 'c-coffee-icon.scss',
  shadow: true
})
export class CustomCoffeeIconComponent {
  render() {
    return ([
      <svg xmlns="http://www.w3.org/2000/svg" class="hidden">
        <symbol id="icon-coffee" viewBox="0 0 20 20">
          <title>icon-coffee</title>
          <path fill="var(--handle-color)" d="..."/>
          <rect fill="var(--cup-color)" x="1" y="7" width="15" height="12" rx="3" ry="3"/>
          <path fill="var(--smoke-color)" d="..."/>
          <path fill="var(--smoke-color)" d="..."/>
          <path fill="var(--smoke-color)" d="..."/>
        </symbol>
      </svg>,
      <svg class="coffee-icon" aria-hidden="true">
        <use href="#icon-coffee" />
      </svg>
    ]);
  }
}

As I mentioned before I also used css variables in order to enable adjusting the component from outside the shadow root content (explanation of these concepts were mentioned earlier in this post).

:host {
  --fallback-color: #000000;
  --handle-color: #c13127;
  --cup-color: #ef5b49;
  --smoke-color: #cacaea;

  display: inline-block;
}

.hidden {
  display: none;
  visibility: hidden;
}

.coffee-icon {
  width: 100px;
  height: 100px;
  fill: var(--fallback-color);
}

Notice that we need to define the css variables under the :host selector to enable adjusting the properties from outside the shadow DOM. If we define the css variables under the .coffee-icon selector, then the variables will work but won't be possible to adjust them from outside as they will be defined inside the encapsulated scope of the shadow DOM.

Let's reinforce this: every property you want to allow to be modified must be defined under the :host selector.

Testing the Stencil Web Component

Add a simple index.html file inside the src folder to test the component:

<!DOCTYPE html>
<html>
  <head>
    <title>My App</title>
    <script src="build/c-coffee-icon.js"></script>
  </head>
  <body>
    <c-coffee-icon></c-coffee-icon>
  </body>
</html>

And then run npm start to start the dev server on http://localhost:3333.

Distributing your Stencil Web Component

Luckily, Stencil has documentation about distributing the components. However, most of the job required for distribution is already done in the stencil component starter we are using.

Besides that basic configuration to bundle and prepare our Web Component to be used as a javascript module, we need to publish it to a software registry. Among the most used ones it's NPM.

It's very easy to publish open source software to NPM; I just followed some easy steps detailed in this guide.

You can find the published stencil Web Component we just built here.

Stencil has good documentation on how to use our standard Web Components with major front-end frameworks. As we are using @ionic/angular for this Ionic Stencil example, let's see what we need to do in order to use our Web Component inside an Angular project.

First, we need to install the c-coffee-icon Web Component module we published to npm in the previous step. So we will go back to our Ionic app and run npm install c-coffee-icon --save

Then, we need to include the CUSTOM_ELEMENTS_SCHEMA in the modules that use our custom Web Component.

We also need to define our custom element by calling the defineCustomElements() function exposed by the module generated by Stencil (the one we just installed using npm).

I went a step further and created a dedicated module to include all custom Web Components that we may need in our Ionic app:

import { APP_INITIALIZER, ModuleWithProviders, CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { appInitialize } from './app-initialize';
import { CoffeeIconComponent } from './coffee-icon/coffee-icon.component';

@NgModule({
  imports: [
    CommonModule
  ],
  declarations: [
    CoffeeIconComponent
  ],
  exports: [
    CoffeeIconComponent
  ],
  entryComponents: [],
  schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class WebComponentsModule {
  static forRoot(): ModuleWithProviders {
    return {
      ngModule: WebComponentsModule,
      providers: [
        {
          provide: APP_INITIALIZER,
          useFactory: appInitialize,
          multi: true
        }
      ]
    };
  }
}

As I mentioned before, a component collection built with Stencil includes a main function that is used to load all the components in the collection. That function is called defineCustomElements() and it needs to be called once during the bootstrapping of your Ionic angular application. One convenient place to do this is in a custom APP_INITIALIZER function that is guaranteed to be executed when the application is initialized:

import { defineCustomElements } from 'c-coffee-icon';

export function appInitialize() {
  return () => {
    const win = window as any;
    if (typeof win !== 'undefined') {
      // Define all of our custom elements
      defineCustomElements(win);
    }
  };
}

Just remember to include this module in your main app.module.ts and call the forRoot() function to ensure the APP_INITIALIZER function gets invoked:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RouterModule, RouteReuseStrategy, Routes } from '@angular/router';

import { IonicModule, IonicRouteStrategy } from '@ionic/angular';
import { SplashScreen } from '@ionic-native/splash-screen/ngx';
import { StatusBar } from '@ionic-native/status-bar/ngx';

import { WebComponentsModule } from './web-components/web-components.module';

import { AppComponent } from './app.component';
import { AppRoutingModule } from './app-routing.module';

@NgModule({
  declarations: [AppComponent],
  entryComponents: [],
  imports: [
    BrowserModule,
    IonicModule.forRoot(),
    WebComponentsModule.forRoot(),
    AppRoutingModule
  ],
  providers: [
    StatusBar,
    SplashScreen,
    { provide: RouteReuseStrategy, useClass: IonicRouteStrategy }
  ],
  bootstrap: [AppComponent]
})
export class AppModule {}

Finally, I looked at the Ionic source code and noticed they created Angular Component proxies for each Ionic Web Component inside their ionic/core module. These are very simple Angular Components with almost no logic at all:

import 'c-coffee-icon';
import { ChangeDetectionStrategy, Component, ViewEncapsulation } from '@angular/core';

@Component({
  selector: 'c-coffee-icon',
  changeDetection: ChangeDetectionStrategy.OnPush,
  encapsulation: ViewEncapsulation.None,
  template: '<ng-content></ng-content>'
})
export class CoffeeIconComponent {}

The advantage of adding Angular proxy components is that by doing so we allow developers to get references to components in an idiomatic way:

@ViewChild(CoffeeIconComponent) coffeeIcon: CoffeeIconComponent;
this.coffeeIcon.method();

Instead of:

@ViewChild('id') coffeeIcon: ElementRef;
this.coffeeIcon.nativeElement.method();

Proxies also help to provide TS typings and AOT checks at compiler time.

By using proxies you also avoid including the CUSTOM_ELEMENTS_SCHEMA in every module that uses our custom Web Component (<c-coffee-icon>). By defining a proxy Angular Component wrapper we just need to include the CUSTOM_ELEMENTS_SCHEMA in the web-components.module.ts. This way, whenever we use the <c-coffee-icon> in our Ionic Angular app, we will be using the Angular component wrapper instead, thus there's no need to include the CUSTOM_ELEMENTS_SCHEMA multiple times.

Testing the custom Web Component inside our Ionic app

Just serve the Ionic app as you would normally do by running ionic serve

Ionic Stencil Web Component

Yeeey, we have our custom Web Component built with Stencil working like a charm inside our Ionic Framework application!

Let's ensure our custom Web Components shadow DOM encapsulation works correctly. Go ahead and try to re-define the css variables we set for the component in the home.page.scss file and let's see what happens:

c-coffee-icon {
  --handle-color: #1f2bac;
  --cup-color: #2f3fff;
  --smoke-color: #a5acbd;
}
Ionic Stencil Web Component

Congratulations, you created and used your own Stencil Web Component!

Congratulations

If you followed me up until the end of this Web Components Ionic tutorial, then you are a step closer to master Ionic web and mobile app development. Just start exploring and trying stuff using the tools and techniques we just learnt.

On this post I explained what are Web Components and what role do they have in Ionic Framework apps. Also I explained what is the Shadow DOM and how to customize the Ionic Components using the CSS Properties.

On the second part of this tutorial we introduced Ionic Stencil and explained how to create a Web Component with Stencil, how to distribute it and how to use it from an Ionic Angular app.

Go ahead and download the Ionic example app that we built in this tutorial and see the code we used to style and customize an Ionic Component.

I encourage you to go a step forward and build your own Web Component using Stencil and use it inside your Ionic app!

We know that building beautifully designed Ionic apps from scratch can be frustrating and very time-consuming. That's why we created Ionic 6 Full Starter App - The most complete Ionic Starter App to help you save hundreds of hours of design and development.

This template has lots of Ionic Custom Components that you can reuse in your own Ionic applications.

ionic starter app

As I mentioned at the beginning of this Ionic tutorial, this post is part of a series of three posts. The other posts are about:

  • Part 2 - Ionic Routing and Navigation in depth
    As you have probably heard, from Ionic 4 onwards the routing is handled by the Angular router. This has many advantages like URL discoverability, route resolves, route guards and the ability to lazy load modules. However, there are also some user perceived performance gotchas that need to be tackled.
  • Part 3 - Building a PWA with Ionic Framework
    PWAs are the big bet of Ionic Framework. I agree on this and believe the near future will be all about progressive web apps as they present significant benefits to most app use cases. Ionic is a great tool to build PWAs, but there are still some issues to be polished. Let's build together a PWA with Ionic!

It was really a pleasure to share my knowledge with you, I hope you feel the same and please let me know your feedback by leaving a comment below.

Hope you now feel confident to start building your own custom Web Components with Stencil.