angular 21 升级使用 signals 方案笔记

Fork Me On Github

angular 21 升级使用 signals 方案笔记

signals 提供新的数据绑定和变更检测机制。从 angular 17 就开始引入了,

优点:

  1. 更精细的响应检测。

缺点:

  1. 变化太大,

signal

基本的绑定,可读可写。

angular 17 之前:

             
@Component({
    standalone: false,
    selector: 'app-example',
    template: `{{ text }}`,
    styleUrls: ['./example.component.scss']
})
export class ExampleComponent {
    public text = 'hi';

    public tap() {
        this.text = 'hello';
    }
}
12345678910111213

angular 17 之后:

                
@Component({
    standalone: false,
    selector: 'app-example',
    template: `{{ text() }}`,
    styleUrls: ['./example.component.scss']
})
export class ExampleComponent {
    public readonly text = signal<string>('hi');

    public tap() {
        this.text.set('hello');
        this.text.update(v => {
            return 'hello';
        });
    }
}
12345678910111213141516

注意

  1. 使用 setupdate 更新值。
  2. 当值是对象时,使用 update 更新某个属性,必须生成新的对象,才能有效检测
              
    
    public readonly data = signal({
     a: 1,
     b: 2
    });
    // 错误示范
    this.data.update(v => {
     v.a = 3
     return v;
    });
    12345678910

// 正确使用 this.data.update(v => { v.a = 3; return {...v}; // 或者 return {...v, a: 3}; });

                 
3. 当值时数组时,使用 `update` 新增或删除某一项
```ts
public readonly items = signal<number[]>([]);
// 错误示范
this.items.update(v => {
    v.push(1);
    v.pop();
    return v;
});

// 正确使用
this.items.update(v => {
    v.push(1);
    return [...v];
    // 或者
    return [...v, 1];
});
1234567891011121314151617

input、output、model

input 相当于 @Input(), 但是可读不可写,增加了自定义转换

output 相当于 @Output()

model 相对于 @Input() + @Output(),可读可写

angular 17 之前:

          
@Component({
    standalone: false,
    selector: 'app-example',
    template: `{{ text }}`,
    styleUrls: ['./example.component.scss']
})
export class ExampleComponent {
    @Input() public text = 'hi';
    @Output() public textChange = new EventEmitter();
}
12345678910

angular 17 之后:

             
@Component({
    standalone: false,
    selector: 'app-example',
    template: `{{ text() }}`,
    styleUrls: ['./example.component.scss']
})
export class ExampleComponent {
    public readonly text = input('hi', {tranform: parseInt});
    public readonly textChange = output();

    public readonly text = model('');

}
12345678910111213

form

               
@Component({
    standalone: false,
    selector: 'app-example',
    template: `<input type="number" [field]="form.a">`,
    styleUrls: ['./example.component.scss']
})
export class ExampleComponent {
    public readonly form = form(sinal({
        a: 1
    }), schemaPath => {
        required(schemaPath.a);
    })

}
123456789101112131415

使用 [field] 精选表单绑定,不能自定义 name 属性,

注意

  1. 表单 name 属性自动生成 类似于 [项目名].form1.a,如果介意 name,则推荐使用 原本 FormBuilder 方式
  2. 一些值限制的变化, select 的值为 stringinput type=checkboxboolean, type=numbernumber, 其他则必须为 string

effect

检测 值的变化,替代 ngOnChanges

                  
@Component({
    standalone: false,
    selector: 'app-example',
    template: `{{ text() }}`,
    styleUrls: ['./example.component.scss']
})
export class ExampleComponent {
    public readonly text = model('');

    constructor() {
        let previousText = '';
        effect(() => {
            this.text();
            // 当 text 发生变化时,TODO
            previousText = this.text(); // 使用此方法实现值的前后变化检测
        });
    }
}
123456789101112131415161718

computed

关联变化,提供值变化前后对比

angular 17 之前:

                 
@Component({
    standalone: false,
    selector: 'app-example',
    template: `{{ text }} {{ twoText }}`,
    styleUrls: ['./example.component.scss']
})
export class ExampleComponent {
    public text = 'hi';

    public get twoText() {
        return this.text + ', two';
    }

    public tap() {
        this.text = 'hello';
    }
}
1234567891011121314151617

angular 17 之后:

                    
@Component({
    standalone: false,
    selector: 'app-example',
    template: `{{ text() }} {{ twoText() }}`,
    styleUrls: ['./example.component.scss']
})
export class ExampleComponent {
    public readonly text = signal<string>('hi');

    public readonly twoText = computed(() => {
        return this.text() + ', two';
    });

    public tap() {
        this.text.set('hello');
        this.text.update(v => {
            return 'hello';
        });
    }
}
1234567891011121314151617181920

与 signals 使用时失效的旧方法

@HostBinding 无法与 signal 等使用

  
@HostBinding('class.open') // 无效
public readonly toggle = model(false);
12

可以使用 effect 同步或

               

@Component({
    standalone: false,
    selector: 'app-example',
    template: ``,
    styleUrls: ['./example.component.scss'],
    host: {
        '[class.open]': 'toggle()'
    }
})
export class ExampleComponent {
    public readonly toggle = model(false);


}
123456789101112131415
点击查看全文
标签: angular
0 2 0
在 angular 项目中实现对页面的访问控制
按下回车键,焦点移动到下一个表单或提交表单
使用ng-template 显示tree结构数据
使用 ViewContainerRef.createComponent 替代 ComponentFactoryResolver
angular 12使用 KaTex 显示 AsciiMath 格式的公式