- April 3, 2020
- Patrick Jahr
- JavaScript, Web Components
Everyone knows it: encapsulating and reusing UI components on the web is challenging. It is usually copy and paste of HTML, CSS, and JavaScript, often spread over one or more files. If you forget a part, it either does not look as desired, or the interaction will not work. Enough of that! Web Components open up new ways on the web to implement and [re-]use UI components in a standardized manner and without any framework.
In this article:
Patrick Jahr is architect at Thinktecture and focuses on Blazor WebAssembly, .NET Core and Angular.
What to expect
- Learn how to create a native Web Component without using a framework
- Explanation of all steps and essentials points like HTML Templates, Custom Elements and shadow DOM to create a Web Component, based on a sample built with Visual Studio Code.
What are Web Components?
Web Components are a collection of three technologies:
- HTML Templates
- Custom Elements
- Shadow DOM
With these three technologies, it is possible to develop native Web Components. For the components part, this may lead us to a solution where we might not need to use full-blown frameworks like Angular, React, or Vue.js.
Create your Web Component template with HTML Templates
With HTML Templates, you can create your
template markup. In order to use a template, you simply have to write your markup inside the tag. But be careful, the template will be parsed but not rendered. This means that the template will appear in the DOM but not be presented in the browser window. Now we start our sample with the HTML Template. In the sample below, we see a template for a simple rating component.
But for our sample, we do not want to write the template in our HTML file. So we create a new JavaScript file rating.js
. In this JavaScript file, we add a template
tag via code and assign the HTML content to the innerHTML
property.
const template = document.createElement['template'];
template.innerHTML = `
Rating
`
Bring your Web Component to life with Custom Elements
With ES6 classes it is possible to create a Custom Element Custom Elements. Now we create a new class Finally, you need to register your Custom Element. You can do this with one line of code: In the example, the After the Custom Element is defined, you can use it in your HTML file. To add this Custom Element and use your Web Component, you must import your JavaScript file. Relevant here is that the script file is added with the type The lifecycle of a Custom Element has a constructor for the element class and four methods to implement. If you use properties and attributes in your class, you must be careful, because property value is not the same as the attribute value. If you wish to sync them, then you will need to implement it. Let’s demonstrate that with our example: After we have defined a template, created a Custom Element, and attached a shadow DOM, we have all done everything needed to create a Web Component. Now we can have a look at how we bring content from outside, inside our Web Component. Let’s start with markup from outside. To allow markup from outside, we can use slots. Slots have the HTML tag In our sample, we must change a little bit. If we want to have the same count on stars as the Rating
What has changed in the HTML Template: The next step is to render all rating stars. What has changed in the Custom Element: The last step is that we have to register for the event So now, we can
change the rating stars from default to our rating star. If we want to overwrite more than one item in our template, we must give the Rating
New Rating Title What is striking here is that the title adopts the style from the outside and ignores the style of the
Web Component. In the next passage, we have to look at how we can fix this and how we can adapt the style in a specific context. Let us take a look at how we can have access to the styles inside the Web Component. To
style the host of the Web Component, we have four options [see here]: To illustrate this, you will see a small example below, which shows how a Web Component looks in a specific context. If you want to style your Web Component from outside, you can set the CSS shadow Part In the sample above, you can see that the style for the title comes from outside and overwrites the style inside
the Web Component. The last step is to build your Web Component. Since there is no standardized approach to build it, you can use any tool you desire, like Webpack or Parcel, for instance. In my sample, I built the Web Component with Webpack. To build my
Web Component, I use the following To bundle the Web Component, I have set up an npm script At least I run In this article, we have looked into the world of Web Components based on a sample component. Web Components consist of the three specifications Custom Elements, shadow DOM, and HTML Templates. If we combine these specifications, it is possible to create own HTML elements that also hide their implementation details from their environment and encapsulate them.
This makes it possible to create components that can then be reused in other applications. When taking a look at the statistics of Can I Use on Custom Elements, shadow DOM and HTML Templates, it will become apparent that all three features already arrived in modern browsers like Chrome, Safari and Edge. To get a little bit more help to build Web Components and get a bit more browser compatibility, you can use LitElement. LitElement is a simple base class from the Polymer Project to create fast and lightweight Web Components. So try it out, fiddle around with them, and create your first small Web Components for your application. If you want to see all the code and try out the sample, you can find the demo here. Don’t
miss any content on Angular, .NET Core, Blazor, Azure, and Kubernetes and sign up for our free monthly dev newsletter.
The requirement to store additional fields, unknown at development time, in a
relational database is not new. Nonetheless, none of the projects I know of are willing to change the database structure at runtime. What if there is a project which needs dynamically created fields and doesn't want or cannot use entity–attribute–value model or switch to No-SQL databases?
The Roslyn Source Generator, implemented in the previous articles of the series, emits some C# code without looking at the dependencies of the current .NET [Core] project. In this article our
Making our Angular modules configurable is an important step in building a reusable architecture. Having used Angular for a while you might be
familiar with the commonly used
The name of your Custom Element must have a dash. In this sample is this . Standard browser tags like
,
all come without a dash, so you can easily recognize what a browser element is and what a Custom Element is. To create a Custom Element, you must inherit from the HTML Element or any other
HTML Element like
HTMLButtonElement
.Create a new Custom Element
Rating
which inherits from HTMLElement
and calls the base constructor
with the method super
inside our own constructor
method.
const template = document.createElement['template'];
// ....
export class Rating extends HTMLElement {
constructor[] {
super[];
}
}
Register your
Custom Element
window.customElements.define['my-rating', Rating];
CustomElementRegistry
will be called, to define and register the my-rating
Web Component as Custom Element in the global window
.
const template = document.createElement['template'];
/// …
export class Rating extends HTMLElement {
// …
}
window.customElements.define['my-rating', Rating];
module
.The
lifecycle model
connectedCallback
: This method will be called when the Custom Element is attached to the DOM.
// rating.js
connectedCallback[ {
console.log['Rating added to DOM'];
}
adpotedCallback
: This method will be called when the Custom Element is moved to a new document
.
//rating.js
adoptedCallback[] {
console.log['Rating was moved into a new DOM'];
}
disconnectedCallback
: This method will be called when the element has been removed from the DOM.
disconnectedCallback[] {
console.log['Rating removed from DOM'];
}
attributeChangedCallback
needs a little bit more explanation:
To use the attributeChangedCallback
, you have to define your attributes, which you want to listen on. To create your attributes, you have to define a static string array called observedAttributes
,
which contains the attributes’ names. When you have created the array, you can set the attributes on the Custom Element from outside, and the attributeChangedCallback
will be called.
In the sample, we need an attribute for rating
and max-rating
.
// rating.js
export class Rating extends HTMLElement {
static get observedAttributes[] {
return [ 'rating', 'max-rating' ];
}
constructor[] {
//...
}
// then will attibuteChangedCallback will be calles
attributeChangedCallback[name, oldVal, newVal] {
if [oldVal !== newVal] {
console.log[`${name} changed from ${oldVal} to ${newVal}`]
}
}
}
Attributes vs. Properties
//rating.js
export class Rating extends HTMLElement {
static get observedAttributes[] {
return [ 'rating', 'max-rating' ];
}
constructor[] {
//...
}
connectedCallback[] {
if [!this.rating] {
// Set default value to zero
this.rating = 0;
}
if [!this.maxRating || this.maxRating [HTMLElement]
Get your markup inside a
Web Component
.
max-rating
, we have to duplicate our star
// rating.js
const template = document.createElement['template'];
template.innerHTML = `
rating-star
but one. tag.
export class Rating extends HTMLElement {
//...
constructor[] {
super[];
const shadowRoot = this.attachShadow[{mode: 'closed'}];
shadowRoot.appendChild[template.content.cloneNode[true]];
// assign the div content to a class variable
this.element = shadowRoot.querySelector['div'];
const slot = this.element.querySelector['slot'];
// assign the rating star to a class variable, that the render class can duplicate them
this.slotNode = slot.querySelector['div'];
slot.addEventListener['slotchange', event => {
const node = slot.assignedNodes[][0];
if [node] {
// assign the new node to the slotNode and render the new stars
this.slotNode = node;
this.render[];
}
}];
}
// ...
}
element
tag to a class variable
slotNode
, that the new method render
has access to them.render[]
was created: the method duplicates the max-rating
attribute
indicates.slotchange
to find out when a slot has changed. The event will be thrown every time the content of a slot has been changed. With the function assignedNodes[]
, we can get the actual content.
export class Rating extends HTMLElement {
//...
constructor[] {
super[];
const shadowRoot = this.attachShadow[{mode: 'closed'}];
shadowRoot.appendChild[template.content.cloneNode[true]];
// assign the div content to a class variable
this.element = shadowRoot.querySelector['div'];
const slot = this.element.querySelector['slot'];
// assign the rating star to a class variable, that the render class can duplicate them
this.slotNode = slot.querySelector['div'];
slot.addEventListener['slotchange', event => {
const node = slot.assignedNodes[][0];
if [node] {
// assign the new node to the slotNode and render the new stars
this.slotNode = node;
this.render[];
}
}];
}
// ...
}
tag a
name
attribute.
const template = document.createElement['template'];
template.innerHTML = `
How to style your Web Component
Inside the shadow
:host
-> Selects the shadow host element.:host[context-name]
-> Selects the shadow host element only if it has a certain class.:host-context[context-tag-name]
-> Selects the shadow host element only, if the selector given as the function’s parameter matches the Shadow Host ancestor[s] in the place it sits
inside the DOM hierarchy.::slotted[]
-> Selects a slotted element if it matches the selector.CSS Shadow Parts
::part[]
. Also, let us take a look at this in our sample.Build a Web Component package
webpack.config.js
module.exports = {
mode: 'production',
entry: {
'rating': './src/rating.js',
},
output: {
filename: 'my-rating.js',
},
...
};
entry
I set the file path to my Custom Element class.output
I set the filename for the bundle file.build-wc
:
{
"name": "web-component-demo",
"version": "1.0.0",
"scripts": {
// ...
"build-wc": "npm run build-wc:clean && npm run build-wc:webpack",
"build-wc:clean": "rm -rf dist && mkdir dist",
"build-wc:webpack": "webpack"
}
// ...
}
npm run build-wc
and, the Web Component is packaged in the file my-rating.js
in the dist
folder.Conclusion
Current articles, screencasts and interviews by our experts
DemoSourceGenerator
should implement a JsonConverter
, but only if the corresponding library [e.g. Newtonsoft.Json] is referenced by the project.forRoot[]
and forChild[]
functions, that some modules provide you with. But what is the best way to provide configuration in these cases?Chủ Đề