我有一个父组件employees.component.ts
和子组件employee.component.ts
。父模板分为两部分——宽度的20%是PrimeNG树组件(其节点表示不同的雇员),其余部分用于子模板。子模板是一个表单,您可以在其中更改所选员工的数据或添加新员工。当从树中选择一个节点时,员工的id作为输入从父节点发送给子节点。子组件中的输入更改触发ngOnChanges
,其中为员工的数据发出HTTP请求。
现在,当我尝试保存一个新员工时,我将该输入设置为undefined
,以指示当前显示一个空表单,以便可以创建一个新员工。保存新员工后,我从响应中获得创建的员工。我将输入设置为新员工的id,并用其数据填充表单。
同样,在创建新员工之后,我向父节点发出一个输出,通知它必须向树中添加一个新员工节点来表示这个新员工,并在表单中填充该员工的数据时选择它。问题是,当父节点设置为选中时,员工的id作为输入发送给子节点(保存过程后当前为undefined
),再次触发ngOnChanges
并发出HTTP请求,但由于我已经从POST请求的响应中获得了数据,所以我不想发出这个新请求。为了避免这种情况,我在得到响应后立即手动设置输入。然而,即使现在输入的值正确地是树中所选员工的id, ngOnChanges仍然会被触发。当我将changes: SimpleChanges
对象记录到控制台时,我看到输入的前一个值显示为undefined
,其当前值是正确的id。
为什么前一个值显示为undefined
,即使我已经设置了它之前的任何东西(输出到父)?我怎么做才能使ngOnChanges不被触发?
你有两个问题:
- 为什么前一个值是
undefined
- 在新员工创建后,如何不触发
ngOnChanges
中的呼叫
为什么前一个值是undefined
你所描述的行为是正确的。
在创建新员工时,没有员工ID (undefined
),然后在填写表单后执行调用来创建新员工并在响应中接收员工ID。
如果你发出新的ID到父组件,它将设置为你的孩子的@Input
,然后ngOnChanges
将被调用,你将正确地有previousValue
设置为undefined
和currentValue
设置为新的ID,这就是SimpleChange应该如何工作。
如何在创建新员工后不触发ngOnChanges
中的呼叫
为了避免呼叫您刚刚创建的员工,您可以在ngOnChanges
中添加额外的检查。如果您从父级收到的ID与上次创建的员工的ID相同,请跳过load调用:
@Component({
selector: 'app-employee',
templateUrl: './app-employee.component.html',
styleUrls: ['./app-employee.component.scss']
})
export class EmployeeComponent implements OnChanges {
@Input()
selectedEmployeeId: string;
@Output()
createdEmployee = new EventEmitter<string>();
private createdEmployeeId: string;
constructor(private http: HttpClient) {}
ngOnChanges(changes: SimpleChanges): void {
if (typeof changes.selectedEmployeeId.currentValue === 'undefined') {
// there is no ID, cannot load employee data
return;
}
if (changes.selectedEmployeeId.currentValue === this.createdEmployeeId) {
// skip the call and reset createdEmployeeId to null
this.createdEmployeeId = null;
} else {
// do a call to load selectedEmployeeId data
this.http
.get(...)
.subscribe(...);
}
}
createEmployee(): void {
this.http
.post(...)
.subscribe(
employeeId => {
this.createdEmployeeId = employeeId;
this.createdEmployee.next(employeeId);
}
)
}
}
在组件中重写@Input属性是一种反模式,因为它会导致各种难以跟踪的、意大利式的信息流流经应用程序。
要解决这个问题,可以添加类型为BehaviorSubject
的中间类成员,如下面的(简化的)示例所示。
TS
@Component({ .. })
export class MyComponent implements OnChanges {
/** single source of truth of your employeeId */
private readonly employeeId_ = new BehaviorSubject<string>("");
/** expose observable, that can be used in html with simple async pipe for efficient change detection */
public readonly employeeId$ = this.employeeId_.asObservable();
/** primary input, will detect changes from upstream parent */
@Input() public employeeId(newValue: string) {
this.setEmployeeIdValue(newValue);
}
/** lifecycle hook that detects changes from upstream and propagates them internally */
ngOnChanges(changes: SimpleChanges) {
if (changes.employeeId) {
this.employeeId_.next(changes.employeeId.currentValue);
}
}
/** method to synchronously evaluate latest value */
public getEmployeeIdValue(): string {
return this.employeeId_.getValue();
}
/** call from your html or from this component's code to handle your business/ui logic */
public setEmployeeIdValue(newValue: string) {
this.employeeId_.next(newValue);
}
}