Как обновить дочерние компоненты после получения данных от вызова ajax в parent - PullRequest
0 голосов
/ 18 декабря 2018

моя структура приложения такая, как показано ниже, мой вопрос состоит в том, как обновить представление дочерних компонентов при получении начальных или будущих данных. Представьте, что у меня есть только одна служба, у которой есть событие OnDataUpdate, все дочерние компоненты получают один и тот же экземпляр службыпоскольку он объявлен в разделе поставщиков модулей приложения, с другой стороны, я попробовал все эти способы и не работал:

  1. ApplicationRef.tick ()
  2. ChangeDetectionRef.markForCheck ()
  3. ChangeDetectionStrategy
  4. Общая служба среди компонентов с событием OnDataRectained, которое выглядит следующим образом

@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)));
    }
}

, а в корневом компоненте приложения это похоже на следующий код

@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, нет необходимости инициировать обнаружение изменений, но пользовательский интерфейс и дочерние компоненты не обновляются!

Ответы [ 2 ]

0 голосов
/ 06 января 2019

Давайте начнем с общего совета:

  1. Обычно вам не нужен сервис для передачи данных от компонента к его (внучатым) дочерним элементам.Для этого используйте @Input привязок.

  2. При использовании службы для управления потоками данных используйте обычный RxJS, то есть экземпляры Observable, Subject, BehaviorSubject и т. Д.EventEmitter - это спецификация Angular, предназначенная для обработки выходных событий от компонентов и директив.Отметьте этот ответ , если вы хотите увидеть подобное решение, используя 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 с примером реализации.

0 голосов
/ 06 января 2019

Я понял, как решить эту проблему, структура этих компонентов является иерархической, и я передал модель каждого компонента через @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
}

вы можете сделать то же самое для всех других компонентов, и даже вы можете создавать больше событий в службе общего доступа и запускать их по своему желанию

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...