Angular 前前后后接触了一个多月了,完成了好几次用户管理的demo,写一下使用感触。我从最开始的:
- 在组件中使用原始变量+Default的变更检测策略实现用户管理的需求(贴个链接:gitee.com/zhang-rui-x… ),项目使用声明式编程写法。
学习了Angular基础的应该都会知道
Default策略不是一个好的选择,因为在任何一个组件触发变更检测的时候,整个组件树都会执行变更检测以更新状态。这样显然会造成一定的性能问题。
到后来
- 使用
OnPush策略+RxJS两者配合实现更优雅的局部变更检测。
可以查看git仓库的
master或者RxJS分支
- 再到最后的使用Angular新特性:Signals
总结一下 Signals
自从Angular引入了Signals,对比一下Vue3,就像对比原神与绝区零 (个人观点):
- signal:类似Vue3中的
ref。 - computed:跟Vue3计算属性类似,里面要写纯函数。
- effect:类似Vue3中的
watch(),Angular中的 effect 中,一般要以声明式的写法来获取并使用依赖的signal。 - input:实现自定义组件单项绑定。Angular也有@Input,能实现类似的效果,但是input可以直接从父组件传
signal到子组件,操作数据更加方便。 - model:实现自定义组件双向绑定。用法与input类似,只是数据是双向绑定的。Vue3实现双向绑定众所周知是使用
defineProps+defineEmits,个人认为Angular的实现更简单一点。
个人认为既然引入了响应式,那么他们的概念都大差不差,确实Angular使用Singals之后,用起来跟Vue3很像。
一些代码示例 Singals
组件结构:
- user-info(父组件)
- user-list(子组件1)
- user-detail(子组件2)
user-info(父组件)
Components
export class UserInfoComponent implements OnInit {
realName = signal<string>('')
pageNumber = signal<number>(1);
users = signal<User[]>([])
private pageSize = signal<number>(10);
constructor(private userService: UserService,
private injector: Injector) {
}
ngOnInit(): void {
effect(() => {
const pageNumber = this.pageNumber()
const pageSize = this.pageSize()
const realName = this.realName();
this.userService.getUserList(
{realName: realName},
pageNumber,
pageSize
).subscribe((res) => {
if (this.pageNumber() == 1) {
this.users.set(res.content)
} else {
this.users.update(users => [...users, ...res.content])
}
})
}, { injector: this.injector });
}
userInfoChange(updatedUser: User) {
this.users.update(users => {
let newUsers = [] as User[]
if (users.length > 0) {
newUsers = [ ...users ]
const index = users.findIndex(user => user.id == updatedUser.id)
newUsers[index] = {...users[index], ...updatedUser}
}
return newUsers
})
}
/**
* 滚轮致页码变化
*/
pageNumberChange() {
this.pageNumber.set(this.pageNumber() + 1)
}
/**
* 姓名搜索框发生变化
*/
realNameChange(realName: string) {
this.resetUsers()
this.realName.set(realName)
this.pageNumber.set(1)
this.pageSize.set(10)
}
private resetUsers() {
this.users.set([])
}
}
html
<div class="wrapper">
<div class="return-class">
<nz-row>
<nz-col nzFlex="1">
<span class="title">Ant Design Of Angular</span>
</nz-col>
<nz-col>
<button class="return-button-class" nz-button nzType="primary" routerLink="/home/user">返回</button>
</nz-col>
</nz-row>
</div>
<div class="content-wrapper">
<div nz-row>
<div class="list-wrapper" nz-col nzSpan="4">
<user-list [users]="users()" (onPageNumberChange)="pageNumberChange()" (onRealNameChange)="realNameChange($event)"></user-list>
</div>
<div nz-col nzSpan="18" nzOffset="1">
<user-detail (onRealNameChange)="userInfoChange($event)"></user-detail>
</div>
</div>
</div>
</div>
user-list 子组件1
Components
export class UserListComponent implements OnInit {
users = input<User[]>()
@Output() onPageNumberChange: EventEmitter<undefined> = new EventEmitter<undefined>();
@Output() onRealNameChange: EventEmitter<string> = new EventEmitter<string>();
realName = signal<string>('')
constructor(private router: Router,
private injector: Injector) {
}
ngOnInit(): void {
effect(() => {
const realName = this.realName()
this.onRealNameChange.emit(realName)
}, {injector: this.injector});
}
/**
* 搜索框输入姓名发生变化
*/
realNameChange(name: string) {
this.realName.set(name)
}
/**
* 滚动触发
*/
onScrollend() {
// 触发滚轮说明需要进行分页查询了
this.onPageNumberChange.emit()
}
/**
* 点击list-item查看用户详情
* @param user
*/
checkUserDetail(user: User) {
this.router.navigate([`/userInfo/${String(user.id)}`])
}
}
html
<cdk-virtual-scroll-viewport itemSize="73" class="infinite-container" (scrollend)="onScrollend()">
<nz-list [nzHeader]="header">
<ng-template #header>
<input (ngModelChange)="realNameChange($event)" [ngModel]="realName()" nz-input placeholder="输入姓名以查询">
</ng-template>
<nz-list-item class="list-item" *ngFor="let user of users()" (click)="checkUserDetail(user)">
<span style="margin-left: 10px" nz-typography>{{ user.realName }} </span>
</nz-list-item>
</nz-list>
</cdk-virtual-scroll-viewport>
user-detail 子组件2
Components
export class UserDetailComponent implements OnInit {
@Output() onRealNameChange: EventEmitter<User> = new EventEmitter<User>();
realName = signal<string>('');
username = signal<string>('');
address = signal<string>('');
age = signal<string>('');
private userId = signal<string>('');
constructor(private userService: UserService,
private route: ActivatedRoute,
private messageService: NzMessageService,
private injector: Injector) {
}
ngOnInit(): void {
this.route.params
.subscribe({
next: params => {
const userId = params['userId'];
this.userId.set(userId);
},
error: error => {
console.error('获取路由id失败:' + error.message);
}
});
effect(() => {
const userId = this.userId();
if (userId) {
this.userService.getUserById(userId)
.subscribe({
next: res => {
if (res.username) {
this.username.set(res.username);
}
if (res.realName) {
this.realName.set(res.realName);
}
if (res.age) {
this.age.set(String(res.age));
}
if (res.address) {
this.address.set(res.address);
}
},
error: error => {
this.messageService.error('获取用户详情失败:' + error.message);
}
});
}
}, {injector: this.injector});
}
realNameChange(realName: string): void {
this.userService.editUser({
id: this.userId(),
realName: realName
}).subscribe({
next: () => {
const newUser: User = {id: this.userId(), realName};
this.onRealNameChange.emit(newUser);
this.realName.set(realName);
this.messageService.success('修改成功');
},
error: error => {
this.messageService.error('修改失败:' + error.message);
}
});
}
ageChange(age: string): void {
this.userService.editUser({
id: this.userId(),
age: Number(age)
}).subscribe({
next: () => {
this.age.set(age);
this.messageService.success('修改成功');
},
error: error => {
this.messageService.error('修改失败:' + error.message);
}
});
}
addressChange(address: string): void {
this.userService.editUser({
id: this.userId(),
address: address
}).subscribe({
next: () => {
this.address.set(address);
this.messageService.success('修改成功');
},
error: error => {
this.messageService.error('修改失败:' + error.message);
}
});
}
}
html
<nz-card>
<nz-descriptions [nzTitle]="realName()">
<nz-descriptions-item nzTitle="姓名">
<p nz-typography nzEditable [nzContent]="realName()" (nzContentChange)="realNameChange($event)"></p>
</nz-descriptions-item>
<nz-descriptions-item nzTitle="年龄(岁)">
<p nz-typography nzEditable [nzContent]="age()" (nzContentChange)="ageChange($event)"></p>
</nz-descriptions-item>
<nz-descriptions-item nzTitle="用户名">{{ username() }}</nz-descriptions-item>
<nz-descriptions-item nzTitle="家庭住址">
<p nz-typography nzEditable [nzContent]="address()" (nzContentChange)="addressChange($event)"></p>
</nz-descriptions-item>
</nz-descriptions>
</nz-card>