Properties
Props are custom attributes/properties exposed publicly on an HTML element. They allow developers to pass data to a component to render or otherwise use.
The Prop Decorator (@Prop()
)
Props are declared on a component using Stencil's @Prop()
decorator, like so:
// First, we import Prop from '@stencil/core'
import { Component, Prop, h } from '@stencil/core';
@Component({
tag: 'todo-list',
})
export class TodoList {
// Second, we decorate a class member with @Prop()
@Prop() name: string;
render() {
// Within the component's class, its props are
// accessed via `this`. This allows us to render
// the value passed to `todo-list`
return <div>To-Do List Name: {this.name}</div>
}
}
In the example above, @Prop()
is placed before (decorates) the name
class member, which is a string. By adding
@Prop()
to name
, Stencil will expose name
as an attribute on the element, which can be set wherever the component
is used:
{/* Here we use the component in a TSX file */}
<todo-list name={"Tuesday's To-Do List"}></todo-list>
<!-- Here we use the component in an HTML file -->
<todo-list name="Tuesday's To-Do List"></todo-list>
In the example above the todo-list
component is used almost identically in TSX and HTML. The only difference between
the two is that in TSX, the value assigned to a prop (in this case, name
) is wrapped in curly braces. In some cases
however, the way props are passed to a component differs slightly between HTML and TSX.
Variable Casing
In the JavaScript ecosystem, it's common to use 'camelCase' when naming variables. The example component below has a
class member, thingToDo
that is camelCased.
import { Component, Prop, h } from '@stencil/core';
@Component({
tag: 'todo-list-item',
})
export class ToDoListItem {
// thingToDo is 'camelCased'
@Prop() thingToDo: string;
render() {
return <div>{this.thingToDo}</div>;
}
}
Since thingToDo
is a prop, we can provide a value for it when we use our todo-list-item
component. Providing a
value to a camelCased prop like thingToDo
is nearly identical in TSX and HTML.
When we use our component in a TSX file, an attribute uses camelCase:
<todo-list-item thingToDo={"Learn about Stencil Props"}></todo-list-item>
In HTML, the attribute must use 'dash-case' like so:
<todo-list-item thing-to-do="Learn about Stencil Props"></todo-list-item>
Data Flow
Props should be used to pass data down from a parent component to its child component(s).
The example below shows how a todo-list
component uses three todo-list-item
child components to render a ToDo list.
import { Component, Prop, h } from '@stencil/core';
@Component({
tag: 'todo-list',
})
export class TodoList {
render() {
return (
<div>
<h1>To-Do List Name: Stencil To Do List</h1>
<ul>
{/* Below are three Stencil components that are children of `todo-list`, each representing an item on our list */}
<todo-list-item thingToDo={"Learn about Stencil Props"}></todo-list-item>
<todo-list-item thingToDo={"Write some Stencil Code with Props"}></todo-list-item>
<todo-list-item thingToDo={"Dance Party"}></todo-list-item>
</ul>
</div>
)
}
}
import { Component, Prop, h } from '@stencil/core';
@Component({
tag: 'todo-list-item',
})
export class ToDoListItem {
@Prop() thingToDo: string;
render() {
return <li>{this.thingToDo}</li>;
}
}
Children components should not know about or reference their parent components. This allows Stencil to efficiently re-render your components. Passing a reference to a component as a prop may cause unintended side effects.
Mutability
A Prop is by default immutable from inside the component logic. Once a value is set by a user, the component cannot update it internally. For more advanced control over the mutability of a prop, please see the mutable option section of this document.
Types
Props can be a boolean
, number
, string
, or even an Object
or Array
. The example below expands the
todo-list-item
to add a few more props with different types.
import { Component, Prop, h } from '@stencil/core';
// `MyHttpService` is an `Object` in this example
import { MyHttpService } from '../some/local/directory/MyHttpService';
@Component({
tag: 'todo-list-item',
})
export class ToDoListItem {
@Prop() isComplete: boolean;
@Prop() timesCompletedInPast: number;
@Prop() thingToDo: string;
@Prop() myHttpService: MyHttpService;
}
Boolean Props
A property on a Stencil component that has a type of boolean
may be declared as:
import { Component, Prop, h } from '@stencil/core';
@Component({
tag: 'todo-list-item',
})
export class ToDoListItem {
@Prop() isComplete: boolean;
}
To use this version of todo-list-item
in HTML, we pass the string "true"
/"false"
to the component:
<!-- Set isComplete to 'true' -->
<todo-list-item is-complete="true"></todo-list-item>
<!-- Set isComplete to 'false' -->
<todo-list-item is-complete="false"></todo-list-item>
To use this version of todo-list-item
in TSX, true
/false
is used, surrounded by curly braces:
// Set isComplete to 'true'
<todo-list-item isComplete={true}></todo-list-item>
// Set isComplete to 'false'
<todo-list-item isComplete={false}></todo-list-item>
There are a few ways in which Stencil treats props that are of type boolean
that are worth noting:
- The value of a boolean prop will be
false
if provided the string"false"
in HTML
<!-- The 'todo-list-item' component will have an isComplete value of `false` -->
<todo-list-item is-complete="false"></todo-list-item>
- The value of a boolean prop will be
true
if provided a string that is not"false"
in HTML
<!-- The 'todo-list-item' component will have an isComplete value of -->
<!-- `true` for each of the following examples -->
<todo-list-item is-complete=""></todo-list-item>
<todo-list-item is-complete="0"></todo-list-item>
<todo-list-item is-complete="False"></todo-list-item>
- The value of a boolean prop will be
undefined
if it has no default value and one of the following applies:- the prop is not included when using the component
- the prop is included when using the component, but is not given a value
<!-- Both examples using the 'todo-list-item' component will have an -->
<!-- isComplete value of `undefined` -->
<todo-list-item></todo-list-item>
<todo-list-item is-complete></todo-list-item>
Number Props
A property on a Stencil component that has a type of number
may be declared as:
import { Component, Prop, h } from '@stencil/core';
@Component({
tag: 'todo-list-item',
})
export class ToDoListItem {
@Prop() timesCompletedInPast: number;
}
To use this version of todo-list-item
in HTML, we pass the numeric value as a string to the component:
<!-- Set timesCompletedInPast to '0' -->
<todo-list-item times-completed-in-past="0"></todo-list-item>
<!-- Set timesCompletedInPast to '23' -->
<todo-list-item times-completed-in-past="23"></todo-list-item>
To use this version of todo-list-item
in TSX, a number surrounded by curly braces is passed to the component:
// Set timesCompletedInPast to '0'
<todo-list-item timesCompletedInPast={0}></todo-list-item>
// Set timesCompletedInPast to '23'
<todo-list-item timesCompletedInPast={23}></todo-list-item>
String Props
A property on a Stencil component that has a type of string
may be declared as:
import { Component, Prop, h } from '@stencil/core';
@Component({
tag: 'todo-list-item',
})
export class ToDoListItem {
@Prop() thingToDo: string;
}
To use this version of todo-list-item
in HTML, we pass the value as a string to the component:
<!-- Set thingToDo to 'Learn about Stencil Props' -->
<todo-list-item thing-to-do="Learn about Stencil Props"></todo-list-item>
<!-- Set thingToDo to 'Write some Stencil Code with Props' -->
<todo-list-item thing-to-do="Write some Stencil Code with Props"></todo-list-item>
To use this version of todo-list-item
in TSX, we pass the value as a string to the component. Curly braces aren't
required when providing string values to props in TSX, but are permitted:
// Set thingToDo to 'Learn about Stencil Props'
<todo-list-item thingToDo="Learn about Stencil Props"></todo-list-item>
// Set thingToDo to 'Write some Stencil Code with Props'
<todo-list-item thingToDo="Write some Stencil Code with Props"></todo-list-item>
// Set thingToDo to 'Write some Stencil Code with Props' with curly braces
<todo-list-item thingToDo={"Learn about Stencil Props"}></todo-list-item>
Object Props
A property on a Stencil component that has a type of Object
may be declared as:
// TodoListItem.tsx
import { Component, Prop, h } from '@stencil/core';
import { MyHttpService } from '../path/to/MyHttpService';
@Component({
tag: 'todo-list-item',
})
export class ToDoListItem {
// Use `@Prop()` to declare the `httpService` class member
@Prop() httpService: MyHttpService;
}
// MyHttpService.ts
export class MyHttpService {
// This implementation intentionally left blank
}
In TypeScript, MyHttpService
is both an Object
and a 'type'. When using user-defined types like MyHttpService
, the
type must always be exported using the export
keyword where it is declared. The reason for this is Stencil needs to
know what type the prop httpService
is when passing an instance of MyHttpService
to TodoListItem
from a parent
component.
To set httpService
in TSX, assign the property name in the custom element's tag to the desired value like so:
// TodoList.tsx
import { Component, h } from '@stencil/core';
import { MyHttpService } from '../MyHttpService';
@Component({
tag: 'todo-list',
styleUrl: 'todo-list.css',
shadow: true,
})
export class ToDoList {
private httpService = new MyHttpService();
render() {
return <todo-list-item httpService={this.httpService}></todo-list-item>;
}
}
Note that the prop name is using camelCase
, and the value is surrounded by curly braces.
It is not possible to set Object
props via an HTML attribute like so:
<!-- this will not work -->
<todo-list-item http-service="{ /* implementation omitted */ }"></todo-list-item>
The reason for this is that Stencil will not attempt to serialize object-like strings written in HTML into a JavaScript object. Similarly, Stencil does not have any support for deserializing objects from JSON. Doing either can be expensive at runtime, and runs the risk of losing references to other nested JavaScript objects.
Instead, properties may be set via <script>
tags in a project's HTML:
<script>
document.querySelector('todo-list-item').httpService = { /* implementation omitted */ };
</script>
Array Props
A property on a Stencil component that is an Array may be declared as:
// TodoList.tsx
import { Component, Prop, h } from '@stencil/core';
@Component({
tag: 'todo-list-item',
})
export class ToDoListItem {
@Prop() itemLabels: string[];
}
To set itemLabels
in TSX, assign the prop name in the custom element's tag to the desired value like so:
// TodoList.tsx
import { Component, h } from '@stencil/core';
import { MyHttpService } from '../MyHttpService';
@Component({
tag: 'todo-list',
styleUrl: 'todo-list.css',
shadow: true,
})
export class ToDoList {
private labels = ['non-urgent', 'weekend-only'];
render() {
return <todo-list-item itemLabels={this.labels}></todo-list-item>;
}
}
Note that the prop name is using camelCase
, and the value is surrounded by curly braces.
It is not possible to set Array
props via an HTML attribute like so:
<!-- this will not work -->
<todo-list-item item-labels="['non-urgent', 'weekend-only']"></todo-list-item>
The reason for this is that Stencil will not attempt to serialize array-like strings written in HTML into a JavaScript object. Doing so can be expensive at runtime, and runs the risk of losing references to other nested JavaScript objects.
Instead, properties may be set via <script>
tags in a project's HTML:
<script>
document.querySelector('todo-list-item').itemLabels = ['non-urgent', 'weekend-only'];
</script>
Advanced Prop Types
any
Type
TypeScript's any
type is a special type
that may be used to prevent type checking of a specific value. Because any
is a valid type in TypeScript, Stencil
props can also be given a type of any
. The example below demonstrates three different ways of using props with type
any
:
import { Component, Prop, h } from '@stencil/core';
@Component({
tag: 'todo-list-item',
})
export class ToDoListItem {
// isComplete has an explicit type annotation
// of `any`, and no default value
@Prop() isComplete: any;
// label has an explicit type annotation of
// `any` with a default value of 'urgent',
// which is a string
@Prop() label: any = 'urgent';
// thingToDo has no type and no default value,
// and will be considered to be type `any` by
// TypeScript
@Prop() thingToDo;
render() {
return (
<ul>
<li>isComplete has a value of - {this.isComplete} - and a typeof value of "{typeof this.isComplete}"</li>
<li>label has a value of - {this.label} - and a typeof value of "{typeof this.label}"</li>
<li>thingToDo has a value of - {this.thingToDo} - and a typeof value of "{typeof this.thingToDo}"</li>
</ul>
);
}
}
When using a Stencil prop typed as any
(implicitly or explicitly), the value that is provided to a prop retains its
own type information. Neither Stencil nor TypeScript will try to change the type of the prop. To demonstrate, let's use
todo-list-item
twice, each with different prop values:
{/* Using todo-list-item in TSX using differnt values each time */}
<todo-list-item isComplete={42} label={null} thingToDo={"Learn about any-typed props"}></todo-list-item>
<todo-list-item isComplete={"42"} label={1} thingToDo={"Learn about any-typed props"}></todo-list-item>
The following will be rendered from the usage example above:
- isComplete has a value of - 42 - and a typeof value of "number"
- label has a value of - - and a typeof value of "object"
- thingToDo has a value of - Learn about any-typed props - and a typeof value of "string"
- isComplete has a value of - 42 - and a typeof value of "string"
- label has a value of - 1 - and a typeof value of "number"
- thingToDo has a value of - Learn about any-typed props - and a typeof value of "string"
In the first usage of todo-list-item
, isComplete
is provided a number value of 42, whereas in the second usage it
receives a string containing "42". The types on isComplete
reflect the type of the value it was provided, 'number' and
'string', respectively.
Looking at label
, it is worth noting that although the prop has a default value, it does
not narrow the type of label
to be of type 'string'. In the first usage of todo-list-item
, label
is provided a
value of null, whereas in the second usage it receives a number value of 1. The types of the values stored in label
are correctly reported as 'object' and 'number', respectively.