Passing data from service to Component --> Child Components

你说的曾经没有我的故事 提交于 2019-12-31 03:00:52

问题


To be very short i am using this Plunker I have a scenario where i have to create controls dynamically by reading the elements data from a service. So when i read the data from the service its asynchronous.But i have to create some concrete objects based on the data i receive from the service and pass it to the child components. So here is my logic

Html for the Main Component is as below.

   <ion-content padding class="container" *ngIf="questions"> 

   <app-dynamic-form [questions]="questions"></app-dynamic-form>

   </ion-content>

Class for the Main Component is below

Class ComponentMain{

   @Input() questions: QuestionBase<any>[]=[];
  constructor(public navCtrl: NavController, public navParams: NavParams,private qcs: Service)
    {
      qcs.getQuestions(this.parameter1.$key).subscribe(result => this.questions = result);
    }

}

Child Components Html is as follows.

<div *ngIf="form">
  <form (ngSubmit)="onSubmit()" [formGroup]="form">

    <div *ngFor="let question of questions" class="form-row">
      <div *ngIf="question">
        <app-question [question]="question" [form]="form"></app-question>
      </div>
   </div>
</form>
</div>

Child Component is as follows

Class ChildComponent implements AfterViewInit {

  @Input() questions: QuestionBase<any>[] = [];

 Constructor(){

  }

 ngAfterViewInit(){

 this.form = this.qcs.toFormGroup(this.questions);

 }

}

There is a 2nd child component which depends on childComponent to create the controls. So the controls are getting populated only in ngOnit of the 2nd child Component and due to which the controls are not getting created. I tried to use many life cycle hooks such as OnInit,OnChanges etc. But none of them actually gave me the result. I am sure i am missing something that i am unable to figure it out.

Class Service(){

questions: QuestionsData<any>[]=[];

getQuestions(FormKey: string) {

var dbQuestions = this.af.list('/elements', {
   query: {
   limitToLast: 200,
   orderByChild: 'formid',
   equalTo: FormKey
  }
})

  dbQuestions.subscribe(snapshots=>{
  snapshots.forEach(elementData => {
  this.questions.push(new TextboxQuestion({
        key: elementData.elementname,
        label: elementData.displaytext,
        value: elementData.elementvalue,
        required: false,
        order: elementData.sortorder
      }))
  }
 }
}

回答1:


Looking at this example Angular.io - Dynamic Forms, it essentially builds a form from metadata at runtime.

There's a couple of comments indicating that the example isn't quite finished.

@Injectable()
export class QuestionService {

  // Todo: get from a remote source of question metadata
  // Todo: make asynchronous
  getQuestions() {
  ...

These are the steps I took to finish it off and clean out the error messages.


question.service.ts

Changed getQuestions to asynchronously return questions.

Injectable()
export class QuestionService {

  constructor(
    private http: Http
  ) {}

  getQuestions$() {
    const url = 'https://api.myjson.com/bins/d0srd';
    return this.http.get(url)
      .map(response => response.json())
      .map(questionMetadata => this.metadataToQuestions(questionMetadata))
      .map(questions => questions.sort((a, b) => a.order - b.order))
  }

  private metadataToQuestions(questionMetadata) {
    return questionMetadata.questions.map(this.toQuestion)
  }

  private toQuestion(elementData) {
    // expand for additional control types
    return new TextboxQuestion({
      key: elementData.elementname,
      label: elementData.displaytext,
      value: elementData.elementvalue,
      required: false,
      order: elementData.sortorder
    })
  }
}


app.component.ts

Changed variable questions type to observable, added async pipe to template.

@Component({
  ...
  template: `
    <div>
      <h2>Job Application for Heroes</h2>
      <app-dynamic-form [questions]="(questions$ | async)"></app-dynamic-form>
    </div>
  `,
  ...
})
export class AppComponent implements OnInit {

  questions$: Observable<any>;

  constructor(
    private questionService: QuestionService
  ) {}

  ngOnInit() {
    this.questions$ = this.questionService.getQuestions$();
  }
}


dynamic-form.component.ts

Changed @Input variable questions to be set/get style, to handle initial null value.
Changed hook where form is created from ngOnInit to ngOnChanges to handle async arrival of questions.

export class DynamicFormComponent implements OnChanges {

  private _questions = [];
  @Input() 
  set questions(value: any[]) {
    this._questions = value || [];
  }
  get questions(): any[] {
    return this._questions;
  }

  ...

  ngOnChanges() {
    this.form = this.qcs.toFormGroup(this.questions);
  }

}


dynamic-form-question.component.ts

Add additional check to isValid getter to ensure the control being validated exists.

export class DynamicFormQuestionComponent {
  ...
  get isValid() { return this.form.controls[this.question.key] 
    ? this.form.controls[this.question.key].valid : true; }
}



回答2:


I'm not sure that I have understand your problem but when I need to pass data from service to a component I use subscription in this way.

Two component (a parent component and its children or other different component) can share a service whose interface enables bi-directional communication.

Like in the Observer pattern, in this case the scope of the service instance is the notification from a component (Publisher) and other componentents (Subscriber).

mycomponent.service.ts

import { Injectable } from '@angular/core';
import { Subject } from 'rxjs/Subject';

@Injectable()
export class MyComponentService{
    // Observable 
    private sampleObservable = new Subject<boolean>();
    // Observable boolean streams
    sampleSubscriber = this.sampleObservable.asObservable();
    // Event for notification from publisher to subscriber
    sampleEventChanged(value:boolean)
    {
        this.sampleObservable.next();
    }
}

In the component who wants to notify all subscribers a change of its state:

mycomponent-publisher.ts

import { Component } from '@angular/core';
import { MyService } from './mycomponent.service';

@Component({
  selector: 'app-my-control-publisher',
  template: `
  <h2>This is the publisher control</h2>
  <button (click)="announce()">Announce to subscriber</button>
  `,
  providers: [MyService]
})

export class MyControlPublisherComponent 
{
  constructor(private myService: MyService) { }

  announce() 
  {
    this.myService.sampleEventChanged(true);
  }
}

In the subscriber component who want to get the notification.

mycomponent-subscriber.ts

import { Component, OnDestroy } from '@angular/core';
import { MyService } from './mycomponent.service';
import { Subscription } from 'rxjs/Subscription';

@Component({
 selector: 'app-my-control-subscriber',
 template: `
 <h2>This is the subscriber control</h2>
 `,
})

export class MyControlSubscriberComponent 
{
  // Subscriptions
  private componentSubscription: Subscription;

  constructor(private myService: MyService) 
  {
    // Subscription of the notifications
    this.componentSubscription= this.myService.sampleSubscriber.subscribe(value =>
    {
      // Put the code for manage the notification here
    }
  }

  ngOnDestroy()
  {
    // Release subscription to avoid memory leaks when the component is destroyed
    this.componentSubscription.unsubscribe();
  }
}

I hope that this can help you.



来源:https://stackoverflow.com/questions/48372635/passing-data-from-service-to-component-child-components

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!