!! Syntax: Enabling Two-Way Binding

In state management V1, $$ is recommended for implementing two-way binding for built-in components.

In state management V2, the !! syntax is recommended for unified two-way binding handling.

NOTE

The !! syntax is supported since API version 12.

Overview

The !! syntactic sugar enables two-way binding for components by initializing the @Param and @Event decorated variables of child components. For this to work, the @Event decorated method name must be declared as $ + the name of the @Param decorated attribute. For details, see Use Scenarios.

  • When !! is used, changes in the parent component are synchronized to the child component, and vice versa, achieving two-way synchronization.
  • If !! is not used, changes flow only from the parent to the child, which means one-way synchronization.

Use Scenarios

Two-Way Binding Between Custom Components

  1. In the Index component, construct a child Star component and use !! to enable two-way binding for the value attribute. This automatically initializes the child component's @Param value and @Event $value.

    Two-way binding syntax sugar used with the @Param and @Event decorators.

    Child({ value: this.value, $value: (val: number) => { this.value = val; } })
    
    The preceding syntax can be simplified as the **!!** syntactic sugar:
    Star({ value: this.value!! })
    
  2. Use the @Param value and @Event $value syntax to implement two-way binding of customized components.

    @Entry
    @ComponentV2
    struct Parent {
      @Local value: number = 0;
    
      build() {
        Column() {
          Text(`${this.value}`)
          // Click the button in Index to change the value. The text in the Parent component and Child component is updated synchronously.
          Button(`change value in parent component`).onClick(() => {
            this.value++;
          })
          // Use the @Param and @Event syntax to implement two-way binding of customized components.
          Child({ value: this.value, $value: (val: number) => { this.value = val; } })
          // ...
        // ···
        }
      }
    }
    
    @ComponentV2
    struct Child {
      @Param value: number = 0;
      @Event $value: (val: number) => void = (val: number) => {};
    
      build() {
        Column() {
          Text(`${this.value}`)
          // Click the button in Child to call the this.$value(10) method. The text in the Parent component and Child component is updated synchronously.
          Button(`change value in child component`).onClick(() => {
            this.$value(10);
          })
        }
      }
    }
    
  3. Use the !! syntax sugar to implement two-way binding of custom components.

    @Entry
    @ComponentV2
    struct Index {
      @Local value: number = 0;
    
      build() {
        Column() {
          Text(`${this.value}`)
          // Clicking the button in Index (parent) increments the value of value, and the Text components in both Index and Star (child) are updated.
          Button(`change value in parent component`).onClick(() => {
            this.value++;
          })
          // Use the !! syntax sugar to implement two-way binding of custom components.
          Star({ value: this.value!! })
          // ...
        }
      }
    }
    
    @ComponentV2
    struct Star {
      @Param value: number = 0;
      @Event $value: (val: number) => void = (val: number) => {};
    
      build() {
        Column() {
          Text(`${this.value}`)
          // Clicking the button in Star (child) calls this.$value(10), and the Text components in both Index and Star are updated.
          Button(`change value in child component`).onClick(() => {
            this.$value(10);
          })
        }
      }
    }
    

Constraints

  • !! does not support two-way binding across multiple layers of parent-child components.
  • !! cannot be used together with @Event. Since API version 18, using !! to pass parameters to a child component's @Event method causes a compilation error.
  • Using three or more exclamation marks (such as !!! and !!!!) does not enable two-way binding.

Two-Way Binding Between Built-in Component Parameters

The !! operator passes a TypeScript variable by reference to a built-in component, ensuring the variable's value and the component's internal state stay in sync. To use this operator, append it to the variable name, for example, isShow!!.

The specific meaning of the "internal state" is determined by the component implementation. For example, the isShow parameter in bindMenu controls menu visibility.

import { hilog } from '@kit.PerformanceAnalysisKit';

const TAG: string = 'click show Menu';
const DOMAIN = 0xFF00;

@Entry
@ComponentV2
struct BindMenuInterface {
  @Local isShow: boolean = false;

  build() {
    Column() {
      Row() {
        Text('click show Menu')
          .bindMenu(this.isShow!!, // Two-way binding.
            [
              {
                value: 'Menu1',
                action: () => {
                  hilog.info(DOMAIN, TAG, 'handle Menu1 click');
                }
              },
              {
                value: 'Menu2',
                action: () => {
                  hilog.info(DOMAIN, TAG, 'handle Menu2 click');
                }
              },
            ])
      }.height('50%')
      
      Text('isShow: ' + this.isShow).fontSize(18).fontColor(Color.Red)
      Row() {
        Button('Click')
          .onClick(() => {
            this.isShow = true;
          })
          .width(100)
          .fontSize(20)
          .margin(10)
      }
    }.width('100%')
  }
}

bindMenu

Rules of Use