Skip to content

Commit 538bea1

Browse files
gnbmmarlon-ionic
andauthored
docs(extends): add extends guide for versions 4.37+ (#1580)
Co-authored-by: Marlon Harrison <92547717+marlon-ionic@users.noreply.github.com>
1 parent 3af8a2d commit 538bea1

File tree

4 files changed

+444
-0
lines changed

4 files changed

+444
-0
lines changed
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
---
2+
title: Extends & Mixins
3+
sidebar_label: Extends & Mixins
4+
description: Using extends and Mixin() to organize component logic with controllers
5+
slug: /extends
6+
---
7+
8+
# Extends & Mixins
9+
10+
As of v4.37.0, Stencil supports class inheritance (`extends`) and mixin composition (`Mixin()`). These features let you move logic out of components into reusable controller classes.
11+
12+
Instead of putting all your logic directly in components, you can extract it into separate controller classes. This makes your code reusable, testable, and easier to maintain.
13+
14+
## Two Approaches
15+
16+
### Inheritance (Mixins)
17+
18+
Use `Mixin()` to compose multiple controllers into your component:
19+
20+
```typescript
21+
@Component({ tag: 'my-component' })
22+
export class MyComponent extends Mixin(
23+
ValidationControllerMixin,
24+
FocusControllerMixin
25+
) {
26+
componentDidLoad() {
27+
super.componentDidLoad(); // Required!
28+
// Your logic here
29+
}
30+
31+
// All mixin methods are directly available
32+
private onBlur = () => {
33+
this.handleBlur();
34+
this.validate(this.values);
35+
};
36+
}
37+
```
38+
39+
:::note
40+
With mixins, watch out for API collisions. Methods or properties with the same name can exist at different levels of the inheritance hierarchy, which can cause unexpected behavior.
41+
:::
42+
43+
### Composition
44+
45+
Extend `ReactiveControllerHost` and add controller instances:
46+
47+
```typescript
48+
@Component({ tag: 'my-component' })
49+
export class MyComponent extends ReactiveControllerHost {
50+
private validationController = new ValidationController(this);
51+
private focusController = new FocusController(this);
52+
53+
constructor() {
54+
super();
55+
this.addController(this.validationController);
56+
this.addController(this.focusController);
57+
}
58+
59+
componentDidLoad() {
60+
super.componentDidLoad(); // Required!
61+
// Your logic here
62+
}
63+
64+
// Only expose what you need
65+
getValidationState() {
66+
return this.validationController.getValidationState();
67+
}
68+
}
69+
```
70+
71+
## ReactiveControllerHost
72+
73+
When using the composition pattern, components extend `ReactiveControllerHost`, which automatically manages the lifecycle methods. Here's what it looks like:
74+
75+
```typescript
76+
export interface ReactiveController {
77+
hostConnected?(): void;
78+
hostDisconnected?(): void;
79+
hostWillLoad?(): Promise<void> | void;
80+
hostDidLoad?(): void;
81+
hostWillRender?(): Promise<void> | void;
82+
hostDidRender?(): void;
83+
hostWillUpdate?(): Promise<void> | void;
84+
hostDidUpdate?(): void;
85+
}
86+
87+
export class ReactiveControllerHost implements ComponentInterface {
88+
controllers = new Set<ReactiveController>();
89+
90+
addController(controller: ReactiveController) {
91+
this.controllers.add(controller);
92+
}
93+
94+
componentDidLoad() {
95+
this.controllers.forEach((controller) => controller.hostDidLoad?.());
96+
}
97+
// ... other lifecycle methods
98+
}
99+
```
100+
101+
## Quick Comparison
102+
103+
| Aspect | Mixins | Composition |
104+
|--------|--------|-------------|
105+
| API Surface | More prone to collisions | Controlled, explicit |
106+
| Method Discovery | Harder to find where methods come from | Clear, explicit delegation |
107+
| Setup | Simple, direct access | More boilerplate |
108+
| Testing | Harder to test in isolation | Controllers are independent and testable |
109+
| Maintenance | Gets complex with many mixins | Easier to maintain and extend |
110+
111+
For more examples, see the [test cases in the Stencil repository](https://github.com/stenciljs/core/tree/main/test/wdio/ts-target).
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
---
2+
title: Extends & Mixins
3+
sidebar_label: Extends & Mixins
4+
description: Using extends and Mixin() to organize component logic with controllers
5+
slug: /extends
6+
---
7+
8+
# Extends & Mixins
9+
10+
As of v4.37.0, Stencil supports class inheritance (`extends`) and mixin composition (`Mixin()`). These features let you move logic out of components into reusable controller classes.
11+
12+
Instead of putting all your logic directly in components, you can extract it into separate controller classes. This makes your code reusable, testable, and easier to maintain.
13+
14+
## Two Approaches
15+
16+
### Inheritance (Mixins)
17+
18+
Use `Mixin()` to compose multiple controllers into your component:
19+
20+
```typescript
21+
@Component({ tag: 'my-component' })
22+
export class MyComponent extends Mixin(
23+
ValidationControllerMixin,
24+
FocusControllerMixin
25+
) {
26+
componentDidLoad() {
27+
super.componentDidLoad(); // Required!
28+
// Your logic here
29+
}
30+
31+
// All mixin methods are directly available
32+
private onBlur = () => {
33+
this.handleBlur();
34+
this.validate(this.values);
35+
};
36+
}
37+
```
38+
39+
:::note
40+
With mixins, watch out for API collisions. Methods or properties with the same name can exist at different levels of the inheritance hierarchy, which can cause unexpected behavior.
41+
:::
42+
43+
### Composition
44+
45+
Extend `ReactiveControllerHost` and add controller instances:
46+
47+
```typescript
48+
@Component({ tag: 'my-component' })
49+
export class MyComponent extends ReactiveControllerHost {
50+
private validationController = new ValidationController(this);
51+
private focusController = new FocusController(this);
52+
53+
constructor() {
54+
super();
55+
this.addController(this.validationController);
56+
this.addController(this.focusController);
57+
}
58+
59+
componentDidLoad() {
60+
super.componentDidLoad(); // Required!
61+
// Your logic here
62+
}
63+
64+
// Only expose what you need
65+
getValidationState() {
66+
return this.validationController.getValidationState();
67+
}
68+
}
69+
```
70+
71+
## ReactiveControllerHost
72+
73+
When using the composition pattern, components extend `ReactiveControllerHost`, which automatically manages the lifecycle methods. Here's what it looks like:
74+
75+
```typescript
76+
export interface ReactiveController {
77+
hostConnected?(): void;
78+
hostDisconnected?(): void;
79+
hostWillLoad?(): Promise<void> | void;
80+
hostDidLoad?(): void;
81+
hostWillRender?(): Promise<void> | void;
82+
hostDidRender?(): void;
83+
hostWillUpdate?(): Promise<void> | void;
84+
hostDidUpdate?(): void;
85+
}
86+
87+
export class ReactiveControllerHost implements ComponentInterface {
88+
controllers = new Set<ReactiveController>();
89+
90+
addController(controller: ReactiveController) {
91+
this.controllers.add(controller);
92+
}
93+
94+
componentDidLoad() {
95+
this.controllers.forEach((controller) => controller.hostDidLoad?.());
96+
}
97+
// ... other lifecycle methods
98+
}
99+
```
100+
101+
## Quick Comparison
102+
103+
| Aspect | Mixins | Composition |
104+
|--------|--------|-------------|
105+
| API Surface | More prone to collisions | Controlled, explicit |
106+
| Method Discovery | Harder to find where methods come from | Clear, explicit delegation |
107+
| Setup | Simple, direct access | More boilerplate |
108+
| Testing | Harder to test in isolation | Controllers are independent and testable |
109+
| Maintenance | Gets complex with many mixins | Easier to maintain and extend |
110+
111+
For more examples, see the [test cases in the Stencil repository](https://github.com/stenciljs/core/tree/main/test/wdio/ts-target).
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
---
2+
title: Extends & Mixins
3+
sidebar_label: Extends & Mixins
4+
description: Using extends and Mixin() to organize component logic with controllers
5+
slug: /extends
6+
---
7+
8+
# Extends & Mixins
9+
10+
As of v4.37.0, Stencil supports class inheritance (`extends`) and mixin composition (`Mixin()`). These features let you move logic out of components into reusable controller classes.
11+
12+
Instead of putting all your logic directly in components, you can extract it into separate controller classes. This makes your code reusable, testable, and easier to maintain.
13+
14+
## Two Approaches
15+
16+
### Inheritance (Mixins)
17+
18+
Use `Mixin()` to compose multiple controllers into your component:
19+
20+
```typescript
21+
@Component({ tag: 'my-component' })
22+
export class MyComponent extends Mixin(
23+
ValidationControllerMixin,
24+
FocusControllerMixin
25+
) {
26+
componentDidLoad() {
27+
super.componentDidLoad(); // Required!
28+
// Your logic here
29+
}
30+
31+
// All mixin methods are directly available
32+
private onBlur = () => {
33+
this.handleBlur();
34+
this.validate(this.values);
35+
};
36+
}
37+
```
38+
39+
:::note
40+
With mixins, watch out for API collisions. Methods or properties with the same name can exist at different levels of the inheritance hierarchy, which can cause unexpected behavior.
41+
:::
42+
43+
### Composition
44+
45+
Extend `ReactiveControllerHost` and add controller instances:
46+
47+
```typescript
48+
@Component({ tag: 'my-component' })
49+
export class MyComponent extends ReactiveControllerHost {
50+
private validationController = new ValidationController(this);
51+
private focusController = new FocusController(this);
52+
53+
constructor() {
54+
super();
55+
this.addController(this.validationController);
56+
this.addController(this.focusController);
57+
}
58+
59+
componentDidLoad() {
60+
super.componentDidLoad(); // Required!
61+
// Your logic here
62+
}
63+
64+
// Only expose what you need
65+
getValidationState() {
66+
return this.validationController.getValidationState();
67+
}
68+
}
69+
```
70+
71+
## ReactiveControllerHost
72+
73+
When using the composition pattern, components extend `ReactiveControllerHost`, which automatically manages the lifecycle methods. Here's what it looks like:
74+
75+
```typescript
76+
export interface ReactiveController {
77+
hostConnected?(): void;
78+
hostDisconnected?(): void;
79+
hostWillLoad?(): Promise<void> | void;
80+
hostDidLoad?(): void;
81+
hostWillRender?(): Promise<void> | void;
82+
hostDidRender?(): void;
83+
hostWillUpdate?(): Promise<void> | void;
84+
hostDidUpdate?(): void;
85+
}
86+
87+
export class ReactiveControllerHost implements ComponentInterface {
88+
controllers = new Set<ReactiveController>();
89+
90+
addController(controller: ReactiveController) {
91+
this.controllers.add(controller);
92+
}
93+
94+
componentDidLoad() {
95+
this.controllers.forEach((controller) => controller.hostDidLoad?.());
96+
}
97+
// ... other lifecycle methods
98+
}
99+
```
100+
101+
## Quick Comparison
102+
103+
| Aspect | Mixins | Composition |
104+
|--------|--------|-------------|
105+
| API Surface | More prone to collisions | Controlled, explicit |
106+
| Method Discovery | Harder to find where methods come from | Clear, explicit delegation |
107+
| Setup | Simple, direct access | More boilerplate |
108+
| Testing | Harder to test in isolation | Controllers are independent and testable |
109+
| Maintenance | Gets complex with many mixins | Easier to maintain and extend |
110+
111+
For more examples, see the [test cases in the Stencil repository](https://github.com/stenciljs/core/tree/main/test/wdio/ts-target).

0 commit comments

Comments
 (0)