问题
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