5

我复制了一个简单的stackblitz 来展示我遇到的问题。问题是我有一个将布尔值传递给子组件的父组件。此布尔值是子组件上的 @Input。需要注意的是,父组件使用 ChangeDetectionStrategy.OnPush。子组件没有明确设置。

当父组件在订阅方法中更改子组件的布尔输入属性时,子组件最初不会检测到更改。总是需要点击 2 次才能让子组件检测到更改。

但是,当我在订阅方法之外更改子组件的布尔输入属性时,正确检测到更改的子组件,并且一切都按预期工作(1 单击子组件以识别更改)。

App.Component.ts(父组件)

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.css' ],
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AppComponent  {
  constructor(private http: HttpClient) {

  }
  public isHelloVisible: boolean;
  public useHttpGet: boolean;

  showHello() {
    if (this.useHttpGet) {
    this.http.get('https://cors-anywhere.herokuapp.com/https://api.darksky.net/forecast/cc0e3799790b0b34bdeb6fef28c3daf7/17.447409200000003,-78.3724573?units=si').subscribe(data => {
      this.isHelloVisible = true;  
    });
    } else {
      this.isHelloVisible = true;
    }
  }

  closeHello() {
    this.isHelloVisible = false;    
  }

子组件 (Hello.component.ts)

@Component({
  selector: 'hello',
  template: `<div *ngIf="showHello">
    Hello
    <div (click)="closeHello()">Click me to close Hello</div>
  </div>
  
  `,
  styles: []
})
export class HelloComponent  {
  @Input() showHello: boolean;
  @Output() close: EventEmitter<any> = new EventEmitter();

  ngOnChanges(changes: SimpleChanges): void {
    console.log(this.showHello);
  }

  closeHello() {
    this.close.emit(null);
  }

如果 useHttpGet 为 true,则它不起作用,如果为 false,则一切正常。

我意识到可能有不同的方法可以做到这一点或手动触发更改检测,但我更感兴趣的是为什么这不起作用,因为这对我没有任何意义。

看到这一点的最佳方式可能是跟随 stackblitz 演示。

4

4 回答 4

2

父级设置为 OnPush,这意味着它仅在某些情况下运行更改检测:

  1. 输入参考发生变化。您在父级中没有任何 @Input
  2. 显式运行更改检测。你没有那样做。
  3. 如果通过异步管道链接到模板的 observable 发出一个新值。没有异步管道的迹象。
  4. 事件源自组件或其子组件之一。您通过单击来执行此操作。

因此,当您使用 http 调用并单击按钮时,它会触发请求并运行更改检测。但是api调用是异步的,需要时间。当它回来时,变更检测已经运行并且不会再次运行,因为上面的 4 个标准都不满足。

当您不使用 http 调用时,单击按钮会将值更改为 true 并运行更改检测。一切都是同步的,因此它会立即更新视图。

因此,您的示例与子组件无关。您可以将 hello 标签更改为普通 div 并在 div 之间粘贴一些字母,您将看到相同的行为。

我使用异步管道更新了您的StackBlitz ,采用了更具反应性的方法。

于 2020-09-03T22:16:29.833 回答
0

你试过了吗?

this.close.next(null);
于 2020-12-03T12:37:06.990 回答
0

如果使用onPush你应该切换到使用BehaviorSubject,它发生了,因为你的父组件也有onPush,这就是你应该使用Subject或手动调用detectChanges的原因

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.css' ],
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AppComponent  {
  constructor(private http: HttpClient) {

  }
  public isHelloVisibleSubject: BehaviorSubject<boolean> = new BehaviorSubject(false);
  public useHttpGet: boolean;

  showHello() {
    if (this.useHttpGet) {
    this.http.get('https://cors-anywhere.herokuapp.com/https://api.darksky.net/forecast/cc0e3799790b0b34bdeb6fef28c3daf7/17.447409200000003,-78.3724573?units=si').subscribe(data => {
      this.isHelloVisibleSubject.next(true);  
    });
    } else {
      this.isHelloVisibleSubject.next(true);
    }
  }

  closeHello() {
    this.isHelloVisibleSubject.next(false);
  }



<hello [showHello]="isHelloVisibleSubject | async"> </hello

于 2020-07-10T23:12:39.763 回答
0

ChangeDetectionStrategy.OnPush您更改时,您的组件仅在 @input 属性更改时才检测更改。

在 subscribe 方法中,您只是在更改组件的内部状态。一些你必须如何告诉角度来检测变化所以使用ChangeDetectorRef.markforcheck()

了解更多信息

另一件事是使用 BehaviorSubject 和异步管道进行更改检测。

但在这种情况下,最好使用第一个。这很简单

于 2020-07-10T23:19:42.293 回答