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
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.
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
})
}
}
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$();
}
}
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);
}
}
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; }
}
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.