如何在 Angular 的 ngOnChanges 上访问 @Input 参数以外的变量?

How to access variables other than @Input params on ngOnChanges in Angular?

我遇到了一个场景,我需要对 ngOnchanges 上的数据进行一些处理(因为数据来自父组件的 @Input 数据绑定 属性)。在此转换过程中,我需要使用在 ngOnInit 中初始化的 属性。我试图直接使用它,但值未定义。有人可以让我知道我怎样才能做到这一点。下面是我的场景的示例代码。

ParentComp HTML
<myParent [data]="list$ | async"></myParent>

ChildComp TS
export class {
  @Input() data : any ; //data from parent
  breakPoint$: Observable<number>

  constructor(){}
  ngOnInit(){
   this.breakPoint$ = fromEvent(this.window, 'resize').pipe(
      map(() => this.numOfCols())
    );
  }

ngOnChanges(changes: SimpleChanges) {
    if (changes['data']) {
       // need to do some data processing here based on numOfColumns value, so trying to access
       this.breakPoint$.pipe(

       ) // Here i get cannot read property pipe of undefined error
      );

 numOfCols(): number {
    return this.breakpointObserver.isMatched(`(max-width: 1008px)`) ? 4 : 5;
  }

ngOnChanges 在 ngOnInit 之前首先被触发。因为 Inputs 是在 ngOnInit 之前填充的。
如果您需要来自 ngOnInit 的值,您可以在 ngOnChanges 中使用布尔值 "isInitDone"。并且不要忘记在您的 ngOnInit 中将此布尔值设置为 true。

但是,您还应该触发在 ngOnChanges 方法中完成的操作。所以也许把它放在一个方法中,ngOnChanges 和 ngOnInit 都会调用...

另一个解决方案是将 ngOnInit 代码移动到 ngOnChanges,在 if (this.breakpoint$)

我建议在这里使用 withLatestFrom rxjs 运算符。每当它更新时,我都会为 numOfColumns 创建一个 Observable 最终订阅将触发。但是:它不会触发,直到你有来自 ngOnInit() (第二个 Observable)的数据,并且只会触发 如果 numOfColumns 在此之后更新,即 BreakPoint 的后续更改不会触发订阅,如果您希望它在两者上都被触发,那么您可以使用 zip 代替。

我在 setter 中这样做,你也可以使用 ngOnChanges

_numOfColumns;
numOfColumnsObs$ = new BehaviorSubject(false);

set numOfColumns(val) {
    this._numOfColumns = val;
    this.numOfColumnsObs$.next(val);
}

get numOfColumns() {
    return this._numOfColumns
}

ngOnInit() {
    this.breakPoint$ = fromEvent(this.window, 'resize').pipe(
        map(() => this.numOfCols())
        );

        // new code here.
        this.numOfColumnsObs$.pipe(withLatestFrom(this.breakPoint$))
        .pipe(filter(([columns]) => columns !== false))
        .subscribe(([columns, breakPoint]) => {
            // do the changes you wanted in `ngOnChanges`;
        })
}

这里你需要一个 BehaviorSubject,因为 observable 会在订阅之前发出。既然有BehaviorSubject,就有hack,我用false初始化了。假设 numOfColumns 永远不会出现 false。如果可以,您可以使用任何不可能出现的值对其进行初始化,并在以后通过管道对其进行过滤。

Angular的组件生命周期钩子按以下顺序触发:

  1. ngOnChanges
  2. ngOnInit
  3. ngDoCheck
  4. ngAfterContentInit
  5. ngAfterContentChecked
  6. ngAfterViewInit
  7. ngAfterViewChecked
  8. ngOnDestroy

如您所见,您正在尝试访问第一个 ngOnChanges 中未定义的 属性。您可以像这样在 if 语句中简单地添加一个检查:

ngOnChanges(changes: SimpleChanges) {
    if (changes['data'] && !!this.breakPoint$) {
....

或者您甚至可以在 ngOnInit 之外定义 breakPoint$,如下所示:

   @Input() data : any ; //data from parent
   breakPoint$: Observable<number> = fromEvent(this.window, 'resize').pipe(
      map(() => this.numOfCols())
    );

您可以添加另一个输入 属性 并将 numOfCols$ 移动到 parent 组件中。

父母补偿HTML

<myParent [data]="list$ | async"
          [numOfCols]="numOfCols$ | async>
</myParent>

ParentCom TS

numOfCols$: Observable<number>;

constructor(private breakpointObserver: BreakpointObserver) {
   this.numOfCols$ = breakpointObserver.observe('(max-width: 1008px)').pipe(
      map(({matches}) => matches ? 4 : 5)
   );
}

ChildComp TS

@Input() data: any;
@Input() numOfCols: number;

ngOnChanges(changes: SimpleChanges) {
    if (changes['data']) {
        // You can use this.numOfCols here
    }
}