Search code examples
angularjsangulartypescriptangular2-databinding

Angular2: How do you properly bind to nested data?


I have a component I'm going to use as a shell for multiple choice questions to load into. Here's how the component is set up so far

component

import { Component }    from '@angular/core';

export class Answers{
    id: string;
    answer: string;
}

const answers: Answers[] = [
        {
        id: 'exp01q',
        answer: 'Its fine as is.'
        },
        {
        id: 'exp02q',
        answer: 'I want to make minor adjustments.'
        },
        {
        id: 'exp03q',
        answer: 'I want to change my image'
        },
        {
        id: 'exp04q',
        answer: 'Ive never wanted to use a particular image until now.'
        }
    ];

@Component({
    moduleId: module.id,
    selector: 'multi-radio-btn',
    templateUrl: 'multi-rad-btn.component.html',
    styleUrls: ['multi-rad-btn.component.css']
})

export class MultiRadioBtnShell {

    question = 'How do you feel about your current image?';
    id = 'exp-img-q';
    name = 'exp-ques1';
    ans = answers;

}

HTML Template

<h3>radio button shell</h3>

<div class="row justify-content-center">
    <fieldset [attr.id]='id' class="card col-8 justify-content-center">

        <label class="ques-title">
            {{question}}
        </label>

        <div class="row answer-row-section justify-content-center">

            <div *ngFor="let answers of ans" class="col col-12 answer-row justify-content-center">
                <div class="col justify-content-center">

                    <input type="radio"
                        [attr.id]="answers.id"
                        [attr.name]="name"
                        [attr.value]="answers.answer" hidden />

                    <label [attr.for]="answers.id" class="col ques-ans-title" style="background-color: #4b73a0;">
                        {{answers.answer}}
                    </label>
                </div>
            </div>

        </div>

    </fieldset>
</div>

The reason it's set up like this now is because the way I was trying to do it at first wasn't working so I went to the Tour of Heroes tutorial to follow along with how they loaded all the heroes. The problem was coming from answer not being defined. So I rigged that part up the same way they did the heroes just for the sake of doing something I'm able to follow just to be sure I get the mechanics of how things load.

The original way I tried to do it was with this

// I had this right above the component
export class ExpQ{
    question: string;
    id: string;
    name: string;
    answers:[
        {
        id: string;
        answer: string;
        }
    ]
}

// I had this in the component's class
export const expq: ExpQ[] = [
    {
    question: 'How do you feel about your current image?',
    id: 'exp-img-q',
    name: 'exp-ques1',
    answers:[
        {
        id: 'exp01q',
        answer: 'Its fine as is.'
        },
        {
        id: 'exp02q',
        answer: 'I want to make minor adjustments.'
        },
        {
        id: 'exp03q',
        answer: 'I want to change my image'
        },
        {
        id: 'exp04q',
        answer: 'Ive never wanted to use a particular image until now.'
        }
        ]
    }
]

I was calling it in the html with

{{expq.question}}, {{expq.name}}, {{expq.answers.id}}, {{expq.answers.answer}}, etc.

at first with just loading the question it worked fine, but as I got to the answers: part it started breaking. I came across this https://scotch.io/tutorials/using-angular-2s-model-driven-forms-with-formgroup-and-formcontrol and seen the syntax for the addresses: part was pretty much the same as how I needed to structure my data. So I remade everything to resemble that. I still had no luck getting it to work.

Ultimately I'm going to be sending the questions through the parent component with @input and @output as well as a couple other tricks I came across. But before I can even think about that I need to get a handle on how to put the data all into one source so that it properly reads the nested bits of data. All the examples I come across are simple single tier bits of data, so I'm not sure on the syntax I need to use. How can I make this work?


Solution

  • You can define your model like so:

    export interface Answer {
      id: string;
      answer: string;
    }
    
    export interface Question {
      id: string;
      name: string;
      question: string;
      answers: Answer[];
    }
    

    Then your component could have this to test

    question1: Question = {
      id: 'q1',
      name: 'q1',
      question: 'Does TypeScript rule?',
      answers: [
        { id: 'a1', answer: 'Yes' },
        { id: 'a2', answer: 'Of Course' },
        { id: 'a3', answer: 'Duh' }
      ]
    };
    

    Of course the names don't have to be the same but I think this gives you a better idea of how to model nested data.

    Then to display it you will need to iterate over nested structures. Look up the *ngFor directive. You will want to iterate over your answers in this case. Ex:

    <div *ngFor="let answer of question1.answers">
      {{answer.id}} - {{answer.answer}}
    </div>