3

我的应用程序结构如下,我的问题是如何在接收初始或未来数据时更新子组件视图,假设我只有一个具有事件 OnDataUpdate 的服务,所有子组件都接收相同的服务实例,因为它有另一方面,在 App 模块提供程序部分中声明,我尝试了所有这些方法并且没有工作:

  1. ApplicationRef.tick()
  2. ChangeDetectionRef.markForCheck()
  3. 变化检测策略
  4. 使用 OnDataRecieved 事件在组件之间共享服务,如下所示

@Injectable()
export class ApiService {

  public OnDataRecieved: EventEmitter<Model>  = new EventEmitter<Model>();

  constructor(private http: HttpClient, private ngZone: NgZone) {
  }

  public getDataAsync(): Observable<Model> {
      return this.http
        .get<Model>('url')
        .pipe(catchError(er => throwError(er)));
    }
}

在 App 根组件中,这就像下面的代码

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss'],
  changeDetection: ChangeDetectionStrategy.Default
})
export class AppComponent implements DoCheck {

  model: BehaviorSubject<Model> = new BehaviorSubject<Model>(new Model()); //with default values
  subModel: BehaviorSubject<SubModel>; 


  constructor(private apiService: ApiService,
    private zone: NgZone) {

    this.apiService.getDashboard().subscribe((data) => {
      this.zone.run(() => {
          this.apiService.OnDataReceived.emit(data);
          this.model = new BehaviorSubject<Model>(data);
      });
    });

    this.model.subscribe((mdl) => {
      this.subModel = new BehaviorSubject<SubModel>(mdl.subModel));
    });
  }

  ngDoCheck() {
  }
}

想象模型在加载或更改数据时嵌套并通过子组件传播,结构可以是这样的

 __ AppRootComponent
|_____ Component1
|_________SubCompoent1-1
|_________SubCompoent1-2
|_____ Component2
|_________SubCompoent2-1
|____________SubCompoent2-1-1

我在 ngDoCheck 中收到数据变化,不需要触发检测变化,但是 UI 和子组件没有更新!

4

2 回答 2

2

让我们从一些一般性建议开始:

  1. 您通常不需要服务来使数据从组件流向其(孙)子级。为此使用@Input绑定。

  2. 当使用服务来管理数据流时,请使用普通的 RxJS,即 、 、 等的实例ObservableSubjectAngularBehaviorSubject特定EventEmitter的,用于处理来自组件和指令的输出事件。如果您使用BehaviorSubject.

  3. 你不需要告诉 Angular 在默认区域中运行代码。默认情况下它会这样做。

具体来说,您的服务可以简单如下:

@Injectable({
  providedIn: 'root'
})
export class ApiService {
  constructor(private http: HttpClient) { }

  getData(): Observable<Model> {
    return this.http.get<Model>('...');
  }
}

然后组件可以订阅它并将值存储在一个普通的同步属性中。以后可以将此属性传递@Input()给子组件:

@Component({
  selector: 'my-app',
  template: `
    <strong>AppComponent:</strong> {{model | json}}
    <child [model]="model"></child>
  `
})
export class AppComponent implements OnInit {
  model: Model;

  constructor(private apiService: ApiService) { }

  ngOnInit(): void {
    this.apiService.getData()
      .subscribe(model => this.model = model);
  }
}

您还可以随时更新model属性,并且更改将传播到子组件和孙子组件。 这是一个带有示例实现的 Stackblitz 。

于 2019-01-06T12:13:09.610 回答
1

我意识到如何解决这个问题,组件的结构是分层的,我通过 @Input() 传递了每个组件的模型,问题是初始请求是异步的,并且组件在接收到真正的父对象之前和之后呈现从服务器接收父对象 传递的 Input 对象没有对象引用,因此它们不会获得更改。

那么,我们该如何解决这个问题呢?简单的!删除所有输入并使用事件驱动编程如何?为所有其他对象所依赖的每个对象或父(根)对象创建一个事件,在全局服务中共享事件,在收到根对象后触发/发出事件,并在子组件中订阅该事件。让我在下面向您展示一个简单的片段:

import { HttpClient, HttpParams, HttpErrorResponse } from '@angular/common/http';
import { Injectable, EventEmitter } from '@angular/core';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';

import { RootDto } from 'src/app/model/root.dto.model';

@Injectable()
export class CoreApiService {

  public onDataReceived: EventEmitter<RootDto> = new EventEmitter<RootDto>();

  constructor(private http: HttpClient) {
  }

  public getRootObject(objectId: number): Observable<RootDto> {
     // const _params = new HttpParams().set('objectId', objectId);
      return this.http
        .get<RootDto>(`${Constants.ApiUrl}/root/${objectId}`)
        .pipe(catchError((err: HttpErrorResponse) => {
          return throwError(err);
        }));
    }
}

根组件如下所示

import {
  Component,
  OnInit
} from '@angular/core';

import { CoreApiService } from './core/services/core-api.service';
import { RootDto } from 'src/app/model/root.dto.model';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {

  constructor(private apiService: CoreApiService) {

  }

  ngOnInit() {
    this.apiService.getRootObject().subscribe((data: RootDto) => {
			// todo: do something here
          this.apiService.onDataReceived.emit(data);
        },
        (err: HttpErrorResponse) => {
          if (err.status === 401 || err.status === 403) {
            // not authorized
          }else {
          // todo: error happened!
		  }
        }
      );
  }
}

子组件如下所示

import {
  Component,
  OnInit,
  NgZone
} from '@angular/core';

import { CoreApiService } from '../core/services/core-api.service';
import { RootDto } from 'src/app/model/root.dto.model';
import { ChildDto } from '../model/child.dto.model';

@Component({
  selector: 'app-first-child',
  templateUrl: './firstChild.component.html',
  styleUrls: ['./firstChild.component.css']
})
export class FirstChildComponent implements OnInit {

  dto: ChildDto;
  isLoaded = false;

  constructor(private apiService: CoreApiService, private zone: NgZone) {
    this.apiService.onDataReceived.subscribe((rootDto: RootDto) => {
      this.zone.run(() => {
        this.dto = Utils.ObjectFactory.Create(rootDto.firstChildDto); // to make sure that we will have a new reference (so that change detction will be triggered) i use object instantiation
		// NOTICE:
		// for arrays don't simply assign or push new item to the array, because the reference is not changed the change detection is not triggered
		// if the array size is small before assigning new value, you can simply empty (myArray = [];) the array otherwise don't do that
        this.isLoaded = true;
      });
    });
  }

  ngOnInit() {
  }

  // the rest of logic
}

您可以对所有其他组件执行相同操作,甚至可以在共享服务中创建更多事件并根据需要触发它

于 2019-01-06T09:14:29.660 回答