1# Custom Placeholder Nodes
2
3ArkUI provides two types of custom placeholder nodes: the built-in component [NodeContainer](../../application-dev/reference/apis-arkui/arkui-ts/ts-basic-components-nodecontainer.md) and [ContentSlot](../../application-dev/reference/apis-arkui/arkui-ts/ts-components-contentSlot.md). They are used to display custom nodes and custom node trees.
4
5Unlike [NodeContainer](../../application-dev/reference/apis-arkui/arkui-ts/ts-basic-components-nodecontainer.md), which acts as a container node with universal attributes, [ContentSlot](../quick-start/arkts-rendering-control-contentslot.md) is merely a semantic node and does not have universal attributes. It does not engage in layout and rendering processes. For hybrid development scenarios, the **ContentSlot** component is recommended when the container is an ArkTS component and the child component is created on the native side. For details, see [ContentSlot](../../application-dev/reference/apis-arkui/arkui-ts/ts-components-contentSlot.md).
6
7[NodeContainer](../reference/apis-arkui/arkui-ts/ts-basic-components-nodecontainer.md), a built-in component serving as a placeholder, comes with universal attributes, and its node layout follows the default top-left aligned [Stack](../reference/apis-arkui/arkui-ts/ts-container-stack.md) component.
8
9[NodeController](../reference/apis-arkui/js-apis-arkui-nodeController.md) provides a set of lifecycle callbacks, including a [makeNode](../reference/apis-arkui/js-apis-arkui-nodeController.md#makenode) callback that returns the root node of a [FrameNode](../reference/apis-arkui/js-apis-arkui-frameNode.md#framenode) tree. This [FrameNode](../reference/apis-arkui/js-apis-arkui-frameNode.md) tree is then mounted under the corresponding [NodeContainer](../reference/apis-arkui/arkui-ts/ts-basic-components-nodecontainer.md). In addition, NodeController provides the following callback methods: [aboutToAppear](../reference/apis-arkui/arkui-ts/ts-custom-component-lifecycle.md#abouttoappear), [aboutToDisappear](../reference/apis-arkui/arkui-ts/ts-custom-component-lifecycle.md#abouttodisappear), [aboutToResize](../reference/apis-arkui/js-apis-arkui-nodeController.md#abouttoresize), [onTouchEvent](../reference/apis-arkui/js-apis-arkui-nodeController.md#ontouchevent), and [rebuild](../reference/apis-arkui/js-apis-arkui-nodeController.md#rebuild), which are used to listen for the status of the associated [NodeContainer](../reference/apis-arkui/arkui-ts/ts-basic-components-nodecontainer.md).
10
11For details about the callbacks, see [NodeController](../reference/apis-arkui/js-apis-arkui-nodeController.md).
12
13> **NOTE**
14>
15> - Only custom FrameNodes and root nodes of component trees created by **BuilderNode** are supported under the **NodeContainer**.
16>
17> - Since API version 12, you can obtain a built-in component's proxy node through the query API of the FrameNode. This proxy node can be returned as the result of the **makeNode** callback, but it cannot be successfully mounted on the component tree, resulting in a failed display of the proxy node.
18>
19> - A node must be used as the child of only one parent node to avoid display or functional issues, particularly in page routing and animation scenarios. For example, if a single node is mounted on multiple **NodeContainer**s through **NodeController**, only one of the **NodeContainer**s will display the node. In addition, any updates to attributes such as visibility and opacity in any of these **NodeContainer**s, which can affect the child component state, will all influence the mounted child node.
20
21## Using NodeContainer to Mount Custom Nodes
22
23You can mount custom nodes under a **NodeContainer** using **NodeController**.
24
25```ts
26// common.ets
27import { BuilderNode, UIContext } from '@kit.ArkUI'
28
29class Params {
30  text: string = "this is a text"
31}
32
33let buttonNode: BuilderNode<[Params]> | null = null;
34
35@Builder
36function buttonBuilder(params: Params) {
37  Column() {
38    Button(params.text)
39      .fontSize(12)
40      .borderRadius(8)
41      .borderWidth(2)
42      .backgroundColor(Color.Orange)
43  }
44}
45
46export function createNode(uiContext: UIContext) {
47  buttonNode = new BuilderNode<[Params]>(uiContext);
48  buttonNode.build(wrapBuilder(buttonBuilder), { text: "This is a Button" });
49  return buttonNode;
50}
51
52export function getOrCreateNode(uiContext: UIContext): BuilderNode<[Params]> | null {
53  if (buttonNode?.getFrameNode() && buttonNode?.getFrameNode()?.getUniqueId() != -1) {
54    return buttonNode;
55  } else {
56    return createNode(uiContext);
57  }
58}
59```
60```ts
61// Index.ets
62import { FrameNode, NodeController, Size, UIContext } from '@kit.ArkUI'
63import { getOrCreateNode } from "./common"
64
65class MyNodeController extends NodeController {
66  private isShow: boolean = false;
67
68  constructor(isShow: boolean) {
69    super();
70    this.isShow = isShow;
71  }
72
73  makeNode(uiContext: UIContext): FrameNode | null {
74    if (!this.isShow) {
75      return null;
76    }
77    let frameNode = getOrCreateNode(uiContext)?.getFrameNode();
78    return frameNode ? frameNode : null;
79  }
80
81  aboutToResize(size: Size) {
82    console.log("aboutToResize width : " + size.width + " height : " + size.height)
83  }
84
85  aboutToAppear() {
86    console.log("aboutToAppear")
87  }
88
89  aboutToDisappear() {
90    console.log("aboutToDisappear");
91  }
92
93  onTouchEvent(event: TouchEvent) {
94    console.log("onTouchEvent");
95  }
96
97  toShow() {
98    this.isShow = true;
99    this.rebuild();
100  }
101
102  toHide() {
103    this.isShow = false;
104    this.rebuild();
105  }
106}
107
108@Entry
109@Component
110struct Index {
111  private myNodeController1: MyNodeController = new MyNodeController(true);
112  private myNodeController2: MyNodeController = new MyNodeController(false);
113
114  build() {
115    Column() {
116      NodeContainer(this.myNodeController1)
117        .width("100%")
118        .height("40%")
119        .backgroundColor(Color.Brown)
120      NodeContainer(this.myNodeController2)
121        .width("100%")
122        .height("40%")
123        .backgroundColor(Color.Gray)
124      Button("Change the place of button")
125        .onClick(() => {
126          // First, remove the node from the original placeholder node.
127          // Then, add the node to the new placeholder node.
128          // This ensures that the custom node exists only as the child of one node.
129          this.myNodeController1.toHide();
130          this.myNodeController2.toShow();
131        })
132    }
133    .padding({ left: 35, right: 35, top: 35 })
134    .width("100%")
135    .height("100%")
136  }
137}
138```
139
140## Layout Differences Between Child Nodes Added Using NodeContainer and ContentSlot
141
142[NodeContainer](../../application-dev/reference/apis-arkui/arkui-ts/ts-basic-components-nodecontainer.md) acts as a standard container that manages the layout of its child nodes. The child nodes added using **NodeContainer** follows the layout rules of the default top-left aligned [Stack](../reference/apis-arkui/arkui-ts/ts-container-stack.md) component, instead of those of the parent container. On the other hand, [ContentSlot](../../application-dev/reference/apis-arkui/arkui-ts/ts-components-contentSlot.md) is a semantic node and does not engage in the layout process. Any child nodes added will be arranged according to the layout rules of the parent container.
143
144```ts
145import { FrameNode, NodeContent, NodeController, typeNode, UIContext } from '@kit.ArkUI';
146
147class NodeContentCtrl {
148  content: NodeContent
149  textNode: Array<typeNode.Text> = new Array();
150  uiContext: UIContext
151  width: number
152
153  constructor(uiContext: UIContext) {
154    this.content = new NodeContent()
155    this.uiContext = uiContext
156    this.width = Infinity
157  }
158
159  AddNode() {
160    let node = typeNode.createNode(this.uiContext, "Text")
161    node.initialize("ContentText:" + this.textNode.length).fontSize(20)
162    this.textNode.push(node)
163    this.content.addFrameNode(node)
164  }
165
166  RemoveNode() {
167    let node = this.textNode.pop()
168    this.content.removeFrameNode(node)
169  }
170
171  RemoveFront() {
172    let node = this.textNode.shift()
173    this.content.removeFrameNode(node)
174  }
175
176  GetContent(): NodeContent {
177    return this.content
178  }
179}
180
181class MyNodeController extends NodeController {
182  public rootNode: FrameNode | null = null;
183  textNode: Array<typeNode.Text> = new Array();
184  makeNode(uiContext: UIContext): FrameNode {
185    this.rootNode = new FrameNode(uiContext);
186    return this.rootNode;
187  }
188
189  AddNode(frameNode: FrameNode | null, uiContext: UIContext) {
190    let node = typeNode.createNode(uiContext, "Text")
191    node.initialize("ControllerText:" + this.textNode.length).fontSize(20)
192    this.textNode.push(node)
193    frameNode?.appendChild(node)
194  }
195
196  RemoveNode(frameNode: FrameNode | null) {
197    let node = this.textNode.pop()
198    frameNode?.removeChild(node)
199  }
200
201  RemoveFront(frameNode: FrameNode | null) {
202    let node = this.textNode.shift()
203    frameNode?.removeChild(node)
204  }
205}
206
207@Entry
208@Component
209struct Index {
210  @State message: string = 'Hello World';
211  controller = new NodeContentCtrl(this.getUIContext());
212  myNodeController = new MyNodeController();
213  build() {
214    Row() {
215      Column() {
216        ContentSlot(this.controller.GetContent())
217        Button("AddToSlot")
218          .onClick(() => {
219            this.controller.AddNode()
220          })
221        Button("RemoveBack")
222          .onClick(() => {
223            this.controller.RemoveNode()
224          })
225        Button("RemoveFront")
226          .onClick(() => {
227            this.controller.RemoveFront()
228          })
229      }
230      .width('50%')
231      Column() {
232        NodeContainer(this.myNodeController)
233        Button("AddToNodeContainer")
234          .onClick(() => {
235            this.myNodeController.AddNode(this.myNodeController.rootNode, this.getUIContext())
236          })
237        Button("RemoveBack")
238          .onClick(() => {
239            this.myNodeController.RemoveNode(this.myNodeController.rootNode)
240          })
241        Button("RemoveFront")
242          .onClick(() => {
243            this.myNodeController.RemoveFront(this.myNodeController.rootNode)
244          })
245      }
246      .width('50%')
247    }
248    .height('100%')
249  }
250}
251```
252
253![en-us_image_user-defined-node-01](figures/user-defined-node-01.gif)
254