Angular自定义指令接收async管道传参为null的问题咨询
根因分析
你遇到的问题本质是两个时序不匹配的问题叠加:
- 你的
activeUserRoleNameSubject是在ngOnInit的异步数据库查询返回后才初始化的,模板渲染时这个Observable还不存在,async管道读取到undefined值会返回null,直接传给了指令。 - 你的指令只在
ngOnInit生命周期里执行一次权限判断,就算后续Observable返回了正确的角色值,Input参数更新后指令也不会重新执行逻辑,相当于第一次拿到null之后就不再处理了。
解决方案
方案1:修复现有逻辑(最低改动)
- 提前初始化Observable,不要等异步返回才创建Subject,组件初始化阶段就给初始值:
// 组件类里直接初始化,不要放在ngOnInit的await之后 public activeUserRoleNameSubject: BehaviorSubject<string> = new BehaviorSubject<string>(''); public activeUserRoleName$: Observable<string> = this.activeUserRoleNameSubject.asObservable(); async ngOnInit(): Promise<void> { const roleUid = (await this.userBaseGesRoleRepository.findOneByActiveUser().toPromise()).metadata.roleUid; this.role = (await this.roleRespository.findOne(roleUid).toPromise()).metadata.name; // 直接next新值,不需要重新创建Subject this.activeUserRoleNameSubject.next(this.role); }
- 指令监听Input变化,每次参数更新都重新判断权限,把逻辑从ngOnInit移到Input的setter里即可:
@Directive({ selector: '[ticketingPrimeCreateTicketButtonActive]' }) export class CreateTicketButtonActiveDirective { // 用setter监听输入值变化 @Input() set ticketingPrimeCreateTicketButtonActive(role: string) { if (role === 'Administrador' || role === 'Comercial') { this.renderer.removeAttribute(this.elRef.nativeElement, 'hidden'); } else { this.renderer.setAttribute(this.elRef.nativeElement, 'hidden', 'true'); } } constructor(private elRef: ElementRef, private renderer: Renderer2) {} }
方案2:等数据加载完成再渲染模板
如果不想让模板提前渲染,可以用*ngIf配合async管道,等角色值返回后再渲染按钮:
<ng-container *ngIf="activeUserRoleName$ | async as role"> <button [ticketingPrimeCreateTicketButtonActive]="role" class="col-1 add" [ngClass]="{ addInSmall: isSmallOrXSmall() }" (click)="addTicket()"> <span class="material-icons add-icon"> add_to_photos </span> </button> </ng-container>
这种方式渲染按钮时角色值已经存在,指令第一次拿到的就是正确值,不需要额外处理参数更新逻辑。
方案3:路由预加载数据(满足你要在模板加载前完成查询的需求)
如果你希望组件初始化前就拿到数据库的角色数据,可以使用Angular路由的Resolve守卫:
- 定义一个Resolve服务,提前拉取角色数据:
@Injectable({ providedIn: 'root' }) export class UserRoleResolve implements Resolve<string> { constructor(private userBaseGesRoleRepository: UserBaseGesRoleRepository, private roleRespository: RoleRepository) {} async resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<string> { const roleUid = (await this.userBaseGesRoleRepository.findOneByActiveUser().toPromise()).metadata.roleUid; return (await this.roleRespository.findOne(roleUid).toPromise()).metadata.name; } }
- 在路由配置里添加resolve:
{ path: 'ticketing-kanban', component: TicketingKanbanComponent, resolve: { userRole: UserRoleResolve } }
- 组件里直接从路由data取数据,不需要自己维护Observable:
export class TicketingKanbanComponent implements OnInit { public userRole: string; constructor(private route: ActivatedRoute) {} ngOnInit() { // 这里拿到的就是预加载好的角色值,组件初始化时已经存在 this.userRole = this.route.snapshot.data['userRole']; } }
这种方式完全规避了时序问题,组件渲染时数据已经准备完毕。
内容的提问来源于stack exchange,提问作者Marco Antonio Gallardo
相关产品推荐
相关产品推荐

