@Link Decorator: Implementing Two-Way Synchronization Between Parent and Child Components
An @Link decorated variable creates two-way synchronization with its corresponding data source in parent component.
Before reading this topic, you are advised to understand the basic usage of @State. For best practices, see State Management.
NOTE
This decorator can be used in ArkTS widgets since API version 9.
This decorator can be used in atomic services since API version 11.
Overview
An @Link decorated variable in a child component shares the same value with a variable in its parent component.
Usage Rules
| @Link Decorator | Description |
|---|---|
| Parameters | N/A |
| Synchronization type | Two-way: The state variable in the parent component can be synchronized with the child component @Link in a two-way manner. Changes to either will be synchronized to the other. |
| Allowed variable types | Object, class, string, number, Boolean, enum, and array of these types. API version 10 and later: Date type. API version 11 and later: Map, Set, undefined, null, union types defined by the ArkUI framework, for example, Length, ResourceStr, and ResourceColor. For details, see Using Union Types. For details about the scenarios of supported types, see Observed Changes. |
| Disallowed variable types | Function. |
| Initial value for the decorated variable | Local initialization is forbidden. |
Variable Transfer/Access Rules
| Transfer/Access | Description |
|---|---|
| Initialization and update from the parent component | Mandatory. An @Link decorated variable can be initialized from an @State, @Link, @Prop, @Provide, @Consume, @ObjectLink, @StorageLink, @StorageProp, @LocalStorageLink, or @LocalStorageProp decorated variable in the parent component, thereby establishing two-way binding. - Since API version 9, the syntax for initializing the child component @Link from the parent component @State is Comp({ aLink: this.aState }), and Comp({aLink: $aState}) is also supported. |
| Child component initialization | Supported; can be used to initialize a regular variable or @State, @Link, @Prop, or @Provide decorated variable in the child component. |
| Access from outside the component | Private, accessible only within the component. |
Figure 1 Initialization rules

Observed Changes and Behavior
Observed Changes
-
When the decorated variable is of the Boolean, string, or number type, its value change can be observed. For details, see Using @Link with Primitive and Class Types.
-
When the decorated variable is of the class or Object type, its value change and value changes of all its attributes, that is, the attributes that Object.keys(observedObject) returns, can be observed. For details, see Using @Link with Primitive and Class Types.
-
When the decorated variable is of the array type, the addition, deletion, and updates of array items can be observed. For details, see Using @Link with Array Types.
-
When the decorated object is of the Date type, the following changes can be observed: (1) complete Date object reassignment; (2) property changes caused by calling setFullYear, setMonth, setDate, setHours, setMinutes, setSeconds, setMilliseconds, setTime, setUTCFullYear, setUTCMonth, setUTCDate, setUTCHours, setUTCMinutes, setUTCSeconds, or setUTCMilliseconds. For details, see Decorating Variables of the Date Type.
-
When the decorated object is of the Map type, the following changes can be observed: (1) complete Map object reassignment; (2) changes caused by calling set, clear, or delete. For details, see Decorating Variables of the Map Type.
-
When the decorated object is of the Set type, the following changes can be observed: (1) complete Set object reassignment; (2) changes caused by calling add, clear, or delete. For details, see Decorating Variables of the Set Type.
Framework Behavior
An @Link decorated variable shares the lifecycle of its owning component.
To understand the value initialization and update mechanism of the @Link decorated variable, it is necessary to consider the parent component and the initial render and update process of the child component that owns the @Link decorated variable (in this example, the @State decorated variable in the parent component is used).
-
Initial render: The execution of the parent component's build() creates an instance of the child component. The initialization process is as follows:
- An @State decorated variable of the parent component is specified to initialize the child component's @Link decorated variable. The child component's @Link decorated variable value remains synchronized with its source variable, enabling two-way data synchronization.
- The parent component's @State wrapper variable instance is passed to the child component through the child's constructor. When the child component's @Link wrapper instance receives a reference to the parent's @State variable, it registers itself with that parent @State variable.
-
Update of the @Link source: When the state variable in the parent component is updated, the @Link decorated variable in the related child component is updated. Procedure:
- As indicated in the initial rendering step, the child component's @Link wrapper class registers the current this pointer with the parent component. When the parent component's @State variable is changed, all built-in components and state variables (such as the @Link wrapper instance) that depend on it are traversed and updated.
- After the @Link wrapper instance is updated, all built-in components that depend on the @Link decorated variable in the child component are notified of the update. In this way, the parent component has the state data of the child components synchronized.
-
Update of @Link: After the @Link decorated variable in the child component is updated, the following steps are performed (the @State decorated variable in the parent component is used):
- After the @Link decorated variable is updated, the set method of the parent component's @State wrapper instance is called to synchronize the new value back to the parent component.
- The @Link in the child component and @State in the parent component each update their respective dependent UI components through independent dependency traversals, achieving synchronized state while maintaining proper UI consistency.
Constraints
-
You are not advised to use the @Link decorator in custom components decorated with @Entry. Otherwise, a warning will be reported during compilation. If the custom component is used as the root node of a page, a runtime error will be reported.
-
Variables decorated with @Link do not support local initialization. Any attempt to perform local initialization on them will result in a compilation error.
// Incorrect usage. An error is reported during compilation. @Link count: number = 10; // Correct usage. @Link count: number; -
The type of a variable decorated with @Link must match the type of its data source. The compilation fails if the types do not match. In addition, the data source must be a state variable. Otherwise, the framework throws a runtime error.
Incorrect Usage
class Info { value: string = 'Hello'; } class Cousin { name: string = 'Hello'; } @Component struct Child { // Incorrect usage 1: The type of the @Link decorated variable is different from that of the @State decorated variable. @Link test: Cousin; // Incorrect usage 2: non-status variable of the data source @Link testStr: string; build() { Column() { Text(this.test.name) Text(this.testStr) } } } @Entry @Component struct LinkExample { @State info: Info = new Info(); build() { Column() { Child({ // Incorrect usage 1: The type of the @Link decorated variable is different from that of the @State decorated variable. test: this.info, // Incorrect usage 2: The data source is not a state variable. testStr: this.info.value }) } } }Correct Usage
class Info { value: string = 'Hello'; } @Component struct Child { // In the child component, use @Link to decorate the test variable of the Info type. @Link test: Info; build() { Text(this.test.value) } } @Entry @Component struct LinkExample { @State info: Info = new Info(); build() { Column() { //In the parent component, use the info variable decorated with @State to initialize the test variable of the Child component. Child({test: this.info}) } } } -
Variables decorated with @Link can be initialized only by state variables. Using regular variables for initialization causes compilation warnings and runtime crashes.
Incorrect Usage
class Info { info: string = 'Hello'; } @Component struct Child { @Link msg: string; @Link info: string; build() { Text(this.msg + this.info) } } @Entry @Component struct LinkExample { @State message: string = 'Hello'; @State info: Info = new Info(); build() { Column() { // Incorrect usage. Regular variables cannot initialize @Link decorated variables. Child({msg: 'World', info: this.info.info}) } } }Correct Usage
class Info { info: string = 'Hello'; } @Component struct Child { @Link msg: string; @Link info: Info; build() { Text(this.msg + this.info.info) } } @Entry @Component struct LinkExample { @State message: string = 'Hello'; @State info: Info = new Info(); build() { Column() { // Correct usage. Child({msg: this.message, info: this.info}) } } } -
Variables decorated with @Link cannot be of the function type. If a function-type variable is decorated with @Link, the framework throws a runtime error.
Use Scenarios
Using @Link with Primitive and Class Types
In the following example, after Parent View: Set yellowButton and Parent View: Set GreenButton of the parent component ShufflingContainer are clicked, the change in the parent component is synchronized to the child components.
-
After buttons of the child components GreenButton and YellowButton are clicked, the child components (@Link decorated variables) change accordingly. Due to the two-way synchronization relationship between @Link and @State, the changes are synchronized to the parent component.
-
When a button in the parent component ShufflingContainer is clicked, the parent component's @State decorated variable changes, and the changes are synchronized to the child components, which are then updated accordingly.
class GreenButtonState {
width: number = 0;
constructor(width: number) {
this.width = width;
}
}
@Component
struct GreenButton {
@Link greenButtonState: GreenButtonState;
build() {
Button('Green Button')
.width(this.greenButtonState.width)
.height(40)
.backgroundColor('#64bb5c')
.fontColor('#FFFFFF')
.onClick(() => {
if (this.greenButtonState.width < 700) {
// Update the attribute of the class. The change can be observed and synchronized back to the parent component.
this.greenButtonState.width += 60;
} else {
// Update the class. The change can be observed and synchronized back to the parent component.
this.greenButtonState = new GreenButtonState(180);
}
})
}
}
@Component
struct YellowButton {
@Link yellowButtonState: number;
build() {
Button('Yellow Button')
.width(this.yellowButtonState)
.height(40)
.backgroundColor('#f7ce00')
.fontColor('#FFFFFF')
.onClick(() => {
// The change of the decorated variable of a primitive type in the child component can be synchronized back to the parent component.
this.yellowButtonState += 40.0;
})
}
}
@Entry
@Component
struct ShufflingContainer {
@State greenButtonState: GreenButtonState = new GreenButtonState(180);
@State yellowButtonProp: number = 180;
build() {
Column() {
Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center }) {
// Primitive type @Link in the child component synchronized from @State in the parent component.
Button('Parent View: Set yellowButton')
.width(this.yellowButtonProp)
.height(40)
.margin(12)
.fontColor('#FFFFFF')
.onClick(() => {
this.yellowButtonProp = (this.yellowButtonProp < 700) ? this.yellowButtonProp + 40 : 100;
})
// Class type @Link in the child component synchronized from @State in the parent component.
Button('Parent View: Set GreenButton')
.width(this.greenButtonState.width)
.height(40)
.margin(12)
.fontColor('#FFFFFF')
.onClick(() => {
this.greenButtonState.width = (this.greenButtonState.width < 700) ? this.greenButtonState.width + 100 : 100;
})
// Initialize the class type @Link.
GreenButton({ greenButtonState: this.greenButtonState }).margin(12)
// Initialize the primitive type @Link.
YellowButton({ yellowButtonState: this.yellowButtonProp }).margin(12)
}
}
}
}

Using @Link with Array Types
@Component
struct Child {
@Link items: number[];
build() {
Column() {
Button(`Button1: push`)
.margin(12)
.width(312)
.height(40)
.fontColor('#FFFFFF')
.onClick(() => {
this.items.push(this.items.length + 1);
})
Button(`Button2: replace whole item`)
.margin(12)
.width(312)
.height(40)
.fontColor('#FFFFFF')
.onClick(() => {
this.items = [100, 200, 300];
})
}
}
}
@Entry
@Component
struct Parent {
@State arr: number[] = [1, 2, 3];
build() {
Column() {
Child({ items: $arr })
.margin(12)
ForEach(this.arr,
(item: number) => {
Button(`${item}`)
.margin(12)
.width(312)
.height(40)
.backgroundColor('#11a2a2a2')
.fontColor('#e6000000')
},
(item: ForEachInterface) => item.toString()
)
}
}
}

The ArkUI framework can observe the addition, deletion, and replacement of array items. In the preceding example, both @State and @Link decorated variables are declared as the number[] type. It is important to note that defining @Link as a number type (for example, @Link item: number) is not supported. In addition, the framework does not support creating child components in the parent component by using individual data items from the @State decorated array. For scenarios requiring this type of implementation, use @Prop and @Observed.
Decorating Variables of the Map Type
NOTE
Since API version 11, @Link supports the Map type.
In this example, the value variable is of the Map<number, string> type. When the button is clicked, the value of message changes, and the UI is re-rendered.
@Component
struct Child {
@Link value: Map<number, string>;
build() {
Column() {
ForEach(Array.from(this.value.entries()), (item: [number, string]) => {
Text(`${item[0]}`).fontSize(30)
Text(`${item[1]}`).fontSize(30)
Divider()
})
Button('child init map').onClick(() => {
this.value = new Map([[0, 'a'], [1, 'b'], [3, 'c']]);
})
Button('child set new one').onClick(() => {
this.value.set(4, 'd');
})
Button('child clear').onClick(() => {
this.value.clear();
})
Button('child replace the first one').onClick(() => {
this.value.set(0, 'aa');
})
Button('child delete the first one').onClick(() => {
this.value.delete(0);
})
}
}
}
@Entry
@Component
struct MapSample {
@State message: Map<number, string> = new Map([[0, 'a'], [1, 'b'], [3, 'c']]);
build() {
Row() {
Column() {
Child({ value: this.message })
}
.width('100%')
}
.height('100%')
}
}
Decorating Variables of the Set Type
NOTE
Since API version 11, @Link supports the Set type.
In this example, the message variable is of the Set<number> type. When the button is clicked, the value of message changes, and the UI is re-rendered.
@Component
struct Child {
@Link message: Set<number>;
build() {
Column() {
ForEach(Array.from(this.message.entries()), (item: [number, number]) => {
Text(`${item[0]}`).fontSize(30)
Divider()
})
Button('init set').onClick(() => {
this.message = new Set([0, 1, 2, 3, 4]);
})
Button('set new one').onClick(() => {
this.message.add(5);
})
Button('clear').onClick(() => {
this.message.clear();
})
Button('delete the first one').onClick(() => {
this.message.delete(0);
})
}
.width('100%')
}
}
@Entry
@Component
struct SetSample {
@State message: Set<number> = new Set([0, 1, 2, 3, 4]);
build() {
Row() {
Column() {
Child({ message: this.message })
}
.width('100%')
}
.height('100%')
}
}
Decorating Variables of the Date Type
In this example, the selectedDate variable is of the Date type. After the button is clicked, the value of selectedDate changes, and the UI is re-rendered.
@Component
struct DateComponent {
@Link selectedDate: Date;
build() {
Column() {
Button(`child increase the year by 1`)
.onClick(() => {
this.selectedDate.setFullYear(this.selectedDate.getFullYear() + 1);
})
Button('child update the new date')
.margin(10)
.onClick(() => {
this.selectedDate = new Date('2023-09-09');
})
DatePicker({
start: new Date('1970-1-1'),
end: new Date('2100-1-1'),
selected: this.selectedDate
})
}
}
}
@Entry
@Component
struct ParentComponent {
@State parentSelectedDate: Date = new Date('2021-08-08');
build() {
Column() {
Button('parent increase the month by 1')
.margin(10)
.onClick(() => {
this.parentSelectedDate.setMonth(this.parentSelectedDate.getMonth() + 1);
})
Button('parent update the new date')
.margin(10)
.onClick(() => {
this.parentSelectedDate = new Date('2023-07-07');
})
DatePicker({
start: new Date('1970-1-1'),
end: new Date('2100-1-1'),
selected: this.parentSelectedDate
})
DateComponent({ selectedDate:this.parentSelectedDate })
}
}
}
Using the Two-Way Synchronization Mechanism to Change Local Variables
Use @Watch to change local variables during two-way synchronization.
In the following example, the @Watch callback on an @Link variable modifies the @State decorated memberMessage variable, achieving variable synchronization between parent and child components. However, local modifications to the @State decorated memberMessage variable will not propagate changes back to the parent component.
@Entry
@Component
struct Parent {
@State sourceNumber: number = 0;
build() {
Column() {
Text(`sourceNumber of the parent component: ` + this.sourceNumber)
Child({ sourceNumber: this.sourceNumber })
Button('Change sourceNumber in Parent Component')
.onClick(() => {
this.sourceNumber++;
})
}
.width('100%')
.height('100%')
}
}
@Component
struct Child {
@State memberMessage: string = 'Hello World';
@Link @Watch('onSourceChange') sourceNumber: number;
onSourceChange() {
this.memberMessage = this.sourceNumber.toString();
}
build() {
Column() {
Text(this.memberMessage)
Text(`sourceNumber of the child component: ` + this.sourceNumber.toString())
Button('Change memberMessage in Child Component')
.onClick(() => {
this.memberMessage = 'Hello memberMessage';
})
}
}
}
Using Union Types
The @Link decorator supports union types, including undefined and null. In the following example, the name variable is declared with type string | undefined. After the button in the parent Index component is clicked to modify either the value or type of name, the Child component will automatically update to reflect these changes.
@Component
struct Child {
@Link name: string | undefined;
build() {
Column() {
Button('Child change name to Bob')
.onClick(() => {
this.name = 'Bob';
})
Button('Child change name to undefined')
.onClick(() => {
this.name = undefined;
})
}.width('100%')
}
}
@Entry
@Component
struct Index {
@State name: string | undefined = 'mary';
build() {
Column() {
Text(`The name is ${this.name}`).fontSize(30)
Child({ name: this.name })
Button('Parents change name to Peter')
.onClick(() => {
this.name = 'Peter';
})
Button('Parents change name to undefined')
.onClick(() => {
this.name = undefined;
})
}
}
}
Common Issues
Using the a.b(this.object) Pattern Fails to Trigger UI Re-rendering
In the build method, when a variable decorated with @Link is of the object type and is called using the a.b(this.object) pattern, the b method receives the original object of this.object, and modifying its properties will not trigger UI re-rendering. In the following example, when the static method Score.changeScore1 or this.changeScore2 is used to change this.score.value in the Child component, the UI is not re-rendered.
Incorrect Usage
class Score {
value: number;
constructor(value: number) {
this.value = value;
}
static changeScore1(score:Score) {
score.value += 1;
}
}
@Entry
@Component
struct Parent {
@State score: Score = new Score(1);
build() {
Column({space:8}) {
Text(`The value in Parent is ${this.score.value}.`)
.fontSize(30)
.fontColor(Color.Red)
Child({ score: this.score })
}
.width('100%')
.height('100%')
}
}
@Component
struct Child {
@Link score: Score;
changeScore2(score:Score) {
score.value += 2;
}
build() {
Column({space:8}) {
Text(`The value in Child is ${this.score.value}.`)
.fontSize(30)
Button(`changeScore1`)
.onClick(()=>{
// Static method calls will not trigger UI re-rendering.
Score.changeScore1(this.score);
})
Button(`changeScore2`)
.onClick(()=>{
// Internal component method calls using this will not trigger UI re-rendering.
this.changeScore2(this.score);
})
}
}
}
You can add a proxy for this.score to re-render the UI by assigning a value to the variable and then calling the variable.
Correct Usage
class Score {
value: number;
constructor(value: number) {
this.value = value;
}
static changeScore1(score:Score) {
score.value += 1;
}
}
@Entry
@Component
struct Parent {
@State score: Score = new Score(1);
build() {
Column({space:8}) {
Text(`The value in Parent is ${this.score.value}.`)
.fontSize(30)
.fontColor(Color.Red)
Child({ score: this.score })
}
.width('100%')
.height('100%')
}
}
@Component
struct Child {
@Link score: Score;
changeScore2(score:Score) {
score.value += 2;
}
build() {
Column({space:8}) {
Text(`The value in Child is ${this.score.value}.`)
.fontSize(30)
Button(`changeScore1`)
.onClick(()=>{
// Add a proxy by assigning a value.
let score1 = this.score;
Score.changeScore1(score1);
})
Button(`changeScore2`)
.onClick(()=>{
// Add a proxy by assigning a value.
let score2 = this.score;
this.changeScore2(score2);
})
}
}
}