简体   繁体   English

从API为Angular 2动态表单设置值

[英]Setting Values From API for Angular 2 Dynamic Form

I am trying to get started with creating a dynamic form in Angular 2, and I am using the setup from the Angular cookbook here as my starting point. 我试图开始建立在2角一个动态的形式,我使用从角菜谱设置在这里为我的出发点。 I didn't have any issues with their setup, which just hard codes the data in the service as apposed to an api call. 我的设置没有任何问题,只是将服务中的数据硬编码为api调用。 My issue is that when I try to use an api call the values don't seem to get set correctly. 我的问题是,当我尝试使用api调用时,值似乎没有正确设置。

In the Angular cookbook they have the question.service.ts file hard coded as: 在Angular食谱中,他们将question.service.ts文件硬编码为:

getQuestions() {
let questions: QuestionBase<any>[] = [
  new DropdownQuestion({
    key: 'brave',
    label: 'Bravery Rating',
    options: [
      {key: 'solid',  value: 'Solid'},
      {key: 'great',  value: 'Great'},
      {key: 'good',   value: 'Good'},
      {key: 'unproven', value: 'Unproven'}
    ],
    order: 3
  }),
  new TextboxQuestion({
    key: 'firstName',
    label: 'First name',
    value: 'Bombasto',
    required: true,
    order: 1
  }),
  new TextboxQuestion({
    key: 'emailAddress',
    label: 'Email',
    type: 'email',
    order: 2
  })
 ];
   return questions.sort((a, b) => a.order - b.order);
 }
}

And then from the app.component.ts file its simply called from the constructor as: 然后从app.component.ts文件中简单地从构造函数调用:

 constructor(service: QuestionService) {
 this.questions = service.getQuestions();
 }

Which "questions" then binds to this in the app.component.ts template 然后哪个“问题”在app.component.ts模板中与此绑定

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

I made changes to the question.service.ts to make an api call (now currently from a json file cause I don't have access to the api at home) 我对question.service.ts进行了更改以进行api调用(现在来自json文件,因为我无法在家中访问api)

 getFirstQuestion() {
    return this._http.get(this.__questionJSONApiBaseUrl)
        .map(data => data.json())
        .do(data => console.log('All: ' + JSON.stringify(data)))
        .catch(this.handleError);
}

Which is called from the app.component.ts as 这是从app.component.ts调用的

ngOnInit() {
    this.service.getFirstQuestion()
        .subscribe(data => {
            this.data = data;

            if (data.Type == 'TextBox') {
                let questions: QuestionBase<any>[] = [
                    new TextboxQuestion({
                        key: data.Title,
                        label: data.Text,
                        value: '',
                        required: true,                          
                        order: data.Id
                    })];
            }              
        }
        );
 }

As you see I set the properties inside of the .subscribe(), but it doesn't seem to be working correctly because when it binds to [questions] in the template I get a "Cannot read property 'forEach' of undefined" error which comes from the question-control.service file which transforms the question to a FormGroup. 如你所见,我在.subscribe()中设置属性,但它似乎没有正常工作,因为当它绑定到模板中的[问题]时,我得到一个“无法读取属性'forEach'未定义”错误它来自question-control.service文件,它将问题转换为FormGroup。

I know that the data is coming in because I can set an alert inside the if statement and see the data from the api call successfully. 我知道数据正在进入,因为我可以在if语句中设置警报并成功查看api调用中的数据。 I believe my issue is that [questions] is binding before the data is ready. 我相信我的问题是[问题]在数据准备好之前具有约束力。 Can someone tell me a better way to do this or please provide any suggestions to what I'm doing wrong please? 有人可以告诉我一个更好的方法来做这个或请提供任何建议,我做错了吗? Is there a way I could set the properties in the api first? 有没有办法可以先在api中设置属性?

The issue here, as @silentsod pointed out, is that you're doing an async operation and trying to store that as your questions. 正如@silentsod指出的那样,这里的问题是你正在进行异步操作并试图将其存储为你的问题。 You need to handle the async. 您需要处理异步。

You can go about this two ways...either, from the component class do: 您可以通过以下两种方式...从组件类中执行以下操作:

service.getQuestions((questions) => this.questions = questions);

Or, you could, from the template, use the async pipe: 或者,您可以从模板中使用异步管道:

<dynamic-form [form]="questions | async"></dynamic-form>

The async pipe subscribes to the observable, in this case questions , and returns the value. async管道订阅了observable,在这种情况下是questions ,并返回值。

I was able to get things working using ideas from this example with modifications to suit my needs. 我能够使用这个例子中的想法进行修改以适应我的需要。

The way I have it setup now is that the page loads and a user will have to click a start button to get their first question. 我现在设置的方式是页面加载,用户必须单击开始按钮才能获得第一个问题。 So I am no longer calling for my first question inside of ngOnInt, but inside of my buttons click event method like so: 所以我不再在ngOnInt中调用我的第一个问题,但是在我的按钮内单击事件方法如下:

 getFirstQuestion() {
    //hide the begin survey button once clicked
    this.clicked = true;
    this.service.getQuestion()
        .subscribe(
        q => {
            this.questions = q
        });
}

Which getQuestion() in my service looks like this: 我的服务中的getQuestion()如下所示:

  getQuestion() {
    return this.http.get(this._questionApiBaseUrl + 'getFirstQuestion' + '?'              'questionId=' + this.questionId)
        .map(res => [res.json()])
        .map(data => {
            this.data = data;            
            return this.buildQuestions(data);
        })            
}

Which returns this buildQuestions() method: 返回此buildQuestions()方法:

 buildQuestions(data: any[]) {
    let questions: any[] = [];
    data.forEach((item: QuestionsGroup) => {
        console.log("my item control in buildQuestion: " + item.controlType);
        if (item.controlType == 'group') {
            let group = new QUESTION_MODELS[item.controlType](item);
            group.questions = this.buildQuestions(item.questions);
            questions.push(group);
        }
        else if (item.controlType == 'RadioButtons') {
            item.controlType = 'radio';
            questions.push(new QUESTION_MODELS[item.controlType](item));
        }
        else if (item.controlType == 'TextBox'){
            item.controlType = 'textbox'; 
            item.type = 'text'               
            questions.push(new QUESTION_MODELS[item.controlType](item));
        }
        else if (item.controlType == 'Datepicker') {
            item.controlType = 'textbox';
            item.type = 'date'
            questions.push(new QUESTION_MODELS[item.controlType](item));
        }
        //TODO add any remaining question types
        else {
            questions.push(new QUESTION_MODELS[item.controlType](item));
        }
    });
    return questions;
}

The buildQuestions() method above will get refactored later as there is currently a mismatch of property values coming from the api to the client side. 上面的buildQuestions()方法稍后会被重构,因为当前存在从api到客户端的属性值不匹配。

The way I was trying to do things before was to manipulate the data once it was returned from my call, but the binding to "questons" had already happened. 我之前尝试做事的方式是一旦从我的调用中返回数据就操纵数据,但是对“questons”的绑定已经发生了。 Now when "questions" is returned from the http get call its in the correct format already when I subscribe to it. 现在,当我订阅它时,从http get调用返回“问题”时,它已经以正确的格式返回。 From here a user will submit an answer and get another question dynamically based on their answer. 从这里开始,用户将根据他们的答案动态提交答案并获得另一个问题。

Below is my service method to post the answer and get the next question (had to leave a few details out but you get the idea): 下面是我发布答案并提出下一个问题的服务方法(不得不留下一些细节,但你明白了):

 submitAnswer(answer: any) {
    //Interface to formulate my answer 
    var questionAnswer: IAnswer = <any>{};     

   //set answer properties interface here

    let headers = new Headers({ 'Content-Type': 'application/json; charset=utf-8' });
    let options = new RequestOptions({ headers: headers });
    let myAnswerObj = JSON.stringify(questionAnswer);

    return this.http.post(this._questionApiBaseUrl + 'submitanswer', myAnswerObj, options)
        .map(res => [res.json()])
        .map(data => {
            this.data = data;
            //return the next question to the user
            return this.buildQuestions(data);
        });          
}

The ngOnInit() function of dynamic-form.component.ts runs before we receive response from api. dynamic- form.component.ts的ngOnInit()函数在我们收到api的响应之前运行。

below is the file 下面是文件

dynamic-form.component.ts 动态form.component.ts

 import { Component, Input, OnInit } from '@angular/core'; import { FormGroup } from '@angular/forms'; import { QuestionBase } from './question-base'; import { QuestionControlService } from './question-control.service'; @Component({ selector: 'dynamic-form', templateUrl: './dynamic-form.component.html', providers: [ QuestionControlService ] }) export class DynamicFormComponent implements OnInit { @Input() questions: QuestionBase<any>[] = []; form: FormGroup; payLoad = ''; constructor(private qcs: QuestionControlService) { } ngOnInit() { this.form = this.qcs.toFormGroup(this.questions); } onSubmit() { this.payLoad = JSON.stringify(this.form.value); } } 

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM