1e41f4b71Sopenharmony_ci# Custom Placeholder Node
2e41f4b71Sopenharmony_ci
3e41f4b71Sopenharmony_ciArkUI provides ArkTS built-in components as placeholder nodes for custom nodes. These placeholder nodes have universal component attributes.
4e41f4b71Sopenharmony_ci
5e41f4b71Sopenharmony_ci## NodeContainer and NodeController
6e41f4b71Sopenharmony_ci
7e41f4b71Sopenharmony_ci[NodeContainer](../reference/apis-arkui/arkui-ts/ts-basic-components-nodecontainer.md), as a built-in component, only has universal component attributes, and its node layout follows the default top-left aligned [Stack](../reference/apis-arkui/arkui-ts/ts-container-stack.md) component. As a placeholder container, **NodeContainer** is primarily used for displaying custom nodes and for the display and reuse of custom node trees.
8e41f4b71Sopenharmony_ci
9e41f4b71Sopenharmony_ci[NodeController](../reference/apis-arkui/js-apis-arkui-nodeController.md) provides a set of lifecycle callbacks, including a **makeNode** callback that returns the root node of a **FrameNode** tree. This [FrameNode](../reference/apis-arkui/js-apis-arkui-frameNode.md) tree is then mounted under the corresponding **NodeContainer**. In addition, **NodeController** provides the following callback methods: **aboutToAppear**, **aboutToDisappear**, **aboutToResize**, **onTouchEvent**, and **rebuild**, which are used to listen for the status of the associated **NodeContainer**.
10e41f4b71Sopenharmony_ci
11e41f4b71Sopenharmony_ciFor details about the callbacks, see [NodeController](../reference/apis-arkui/js-apis-arkui-nodeController.md).
12e41f4b71Sopenharmony_ci
13e41f4b71Sopenharmony_ci> **NOTE**
14e41f4b71Sopenharmony_ci> 
15e41f4b71Sopenharmony_ci> - Only custom FrameNodes and root nodes of component trees created by **BuilderNode** are supported under the **NodeContainer**.
16e41f4b71Sopenharmony_ci> 
17e41f4b71Sopenharmony_ci> - 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.
18e41f4b71Sopenharmony_ci> 
19e41f4b71Sopenharmony_ci> - 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.
20e41f4b71Sopenharmony_ci
21e41f4b71Sopenharmony_ci```ts
22e41f4b71Sopenharmony_ciimport { BuilderNode, FrameNode, NodeController, Size, UIContext } from '@kit.ArkUI'
23e41f4b71Sopenharmony_ci
24e41f4b71Sopenharmony_ciclass Params {
25e41f4b71Sopenharmony_ci  text: string = "this is a text"
26e41f4b71Sopenharmony_ci}
27e41f4b71Sopenharmony_ci
28e41f4b71Sopenharmony_ci@Builder
29e41f4b71Sopenharmony_cifunction buttonBuilder(params: Params) {
30e41f4b71Sopenharmony_ci  Column() {
31e41f4b71Sopenharmony_ci    Button(params.text)
32e41f4b71Sopenharmony_ci      .fontSize(12)
33e41f4b71Sopenharmony_ci      .borderRadius(8)
34e41f4b71Sopenharmony_ci      .borderWidth(2)
35e41f4b71Sopenharmony_ci      .backgroundColor(Color.Orange)
36e41f4b71Sopenharmony_ci  }
37e41f4b71Sopenharmony_ci}
38e41f4b71Sopenharmony_ci
39e41f4b71Sopenharmony_cilet buttonNode: BuilderNode<[Params]> | null = null;
40e41f4b71Sopenharmony_ci
41e41f4b71Sopenharmony_ciclass MyNodeController extends NodeController {
42e41f4b71Sopenharmony_ci  private wrapBuilder: WrappedBuilder<[Params]> = wrapBuilder(buttonBuilder);
43e41f4b71Sopenharmony_ci  private isShow: boolean = false;
44e41f4b71Sopenharmony_ci
45e41f4b71Sopenharmony_ci  constructor(isShow: boolean) {
46e41f4b71Sopenharmony_ci    super();
47e41f4b71Sopenharmony_ci    this.isShow = isShow;
48e41f4b71Sopenharmony_ci  }
49e41f4b71Sopenharmony_ci
50e41f4b71Sopenharmony_ci  makeNode(uiContext: UIContext): FrameNode | null {
51e41f4b71Sopenharmony_ci    if (!this.isShow) {
52e41f4b71Sopenharmony_ci      return null;
53e41f4b71Sopenharmony_ci    }
54e41f4b71Sopenharmony_ci    if (buttonNode == null) {
55e41f4b71Sopenharmony_ci      buttonNode = new BuilderNode<[Params]>(uiContext);
56e41f4b71Sopenharmony_ci      buttonNode.build(this.wrapBuilder, { text: "This is a Button" })
57e41f4b71Sopenharmony_ci    }
58e41f4b71Sopenharmony_ci    let frameNode = buttonNode?.getFrameNode();
59e41f4b71Sopenharmony_ci    return frameNode ? frameNode : null;
60e41f4b71Sopenharmony_ci  }
61e41f4b71Sopenharmony_ci
62e41f4b71Sopenharmony_ci  aboutToResize(size: Size) {
63e41f4b71Sopenharmony_ci    console.log("aboutToResize width : " + size.width + " height : " + size.height)
64e41f4b71Sopenharmony_ci  }
65e41f4b71Sopenharmony_ci
66e41f4b71Sopenharmony_ci  aboutToAppear() {
67e41f4b71Sopenharmony_ci    console.log("aboutToAppear")
68e41f4b71Sopenharmony_ci  }
69e41f4b71Sopenharmony_ci
70e41f4b71Sopenharmony_ci  aboutToDisappear() {
71e41f4b71Sopenharmony_ci    console.log("aboutToDisappear");
72e41f4b71Sopenharmony_ci  }
73e41f4b71Sopenharmony_ci
74e41f4b71Sopenharmony_ci  onTouchEvent(event: TouchEvent) {
75e41f4b71Sopenharmony_ci    console.log("onTouchEvent");
76e41f4b71Sopenharmony_ci  }
77e41f4b71Sopenharmony_ci
78e41f4b71Sopenharmony_ci  toShow() {
79e41f4b71Sopenharmony_ci    this.isShow = true;
80e41f4b71Sopenharmony_ci    this.rebuild();
81e41f4b71Sopenharmony_ci  }
82e41f4b71Sopenharmony_ci
83e41f4b71Sopenharmony_ci  toHide() {
84e41f4b71Sopenharmony_ci    this.isShow = false;
85e41f4b71Sopenharmony_ci    this.rebuild();
86e41f4b71Sopenharmony_ci  }
87e41f4b71Sopenharmony_ci}
88e41f4b71Sopenharmony_ci
89e41f4b71Sopenharmony_ci@Entry
90e41f4b71Sopenharmony_ci@Component
91e41f4b71Sopenharmony_cistruct Index {
92e41f4b71Sopenharmony_ci  private myNodeController1: MyNodeController = new MyNodeController(true);
93e41f4b71Sopenharmony_ci  private myNodeController2: MyNodeController = new MyNodeController(false);
94e41f4b71Sopenharmony_ci
95e41f4b71Sopenharmony_ci  build() {
96e41f4b71Sopenharmony_ci    Column() {
97e41f4b71Sopenharmony_ci      NodeContainer(this.myNodeController1)
98e41f4b71Sopenharmony_ci        .width("100%")
99e41f4b71Sopenharmony_ci        .height("40%")
100e41f4b71Sopenharmony_ci        .backgroundColor(Color.Brown)
101e41f4b71Sopenharmony_ci      NodeContainer(this.myNodeController2)
102e41f4b71Sopenharmony_ci        .width("100%")
103e41f4b71Sopenharmony_ci        .height("40%")
104e41f4b71Sopenharmony_ci        .backgroundColor(Color.Gray)
105e41f4b71Sopenharmony_ci      Button("Change the place of button")
106e41f4b71Sopenharmony_ci        .onClick(() => {
107e41f4b71Sopenharmony_ci          // First, remove the node from the original placeholder node.
108e41f4b71Sopenharmony_ci          // Then, add the node to the new placeholder node.
109e41f4b71Sopenharmony_ci          // Ensure that the custom node only exists as the child of one node.
110e41f4b71Sopenharmony_ci          this.myNodeController1.toHide();
111e41f4b71Sopenharmony_ci          this.myNodeController2.toShow();
112e41f4b71Sopenharmony_ci        })
113e41f4b71Sopenharmony_ci    }
114e41f4b71Sopenharmony_ci    .padding({ left: 35, right: 35, top: 35 })
115e41f4b71Sopenharmony_ci    .width("100%")
116e41f4b71Sopenharmony_ci    .height("100%")
117e41f4b71Sopenharmony_ci  }
118e41f4b71Sopenharmony_ci}
119e41f4b71Sopenharmony_ci```
120