Angular2: How do you properly bind to nested data? - angularjs

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?

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>

Need to flatten the objects,
Params :
Objects : at least n>0 array off JSON objects (dose not matter is circular)
target : {}
path : ""
Note : make sure the Objects Array passed in is n>0 at least
flatten(objects, target, path) {
let me = this;
let retArray = [];
for(let x=0; x < objects.length; x++) {
let object = objects[x];
path = path || '';
target={};
target = me.flattenHelper(object, target, path);
retArray.push(target);
}
return retArray;}
..
flattenHelper(object, target, path){
let me = this;
Object.keys(object).forEach(function (key) {
console.log("key : "+ key + " : object : " + (object[key] && typeof object[key] === 'object') + " path : " + path);
if (object[key] && typeof object[key] === 'object') {
me.flattenHelper(object[key], target, path + key);
}
target[path + key] = object[key];
console.log(target);
});
return target;}

Related

vue: access a specific array from an object inside an array

I want to show only one of the questions array in a single page, depending on which category the user picks.
faqData = [
{
name: "first-category",
questions: [
{
id: 1,
question: "First question?",
answer: "First Answer"
},
{
id: 2,
question: "Second question?",
answer: "blablablabla"
}
]
},
{
name: "second-category",
questions: [
{
id: 1,
question: "First question?",
answer: "First Answer"
},
{
id: 2,
question: "Second question?",
answer: "blablablabla"
}
]
},
{
name: "third-category",
questions: [
{
id: 1,
question: "First question?",
answer: "First Answer"
}
]
}
];
vue file
<div class="accordion" role="tablist" v-for="eachQuestion in questionList.questions" :key="eachQuestion.id">
<FAQCollapses v-bind:eachQuestion="eachQuestion" />
</div>
//script
data () {
return {
questionList: faqData
}
}
My template code shows a blank space and there's nothing in the console so I'm confused where the mistake is. The problem is I don't know how to specifically get only one category from the faqData array, depending on what category the user clicks. Can someone tell me what is the best practice to achieve my goal? I have read all the similar questions in StackOverflow but it didn't work in my case. Thank you so much.
Best way (and a best practice, I guess), it to implement computed prop with name like, for example selectedFaqQuestion:
computed: {
selectedFaqQuestion () {
return selectedCategory ? this.faqData.find(category => category.name === this.selectedCategory).questions : []
}
}
and use it into v-for:
<div v-for="eachQuestion in selectedFaqQuestion" :key="eachQuestion.id">
<FAQCollapses v-bind:eachQuestion="eachQuestion" />
</div>
Of course, to do so, you need to implement new data prop selectedCategory, where you are going to store selected category, on user's click:
data () {
return {
questionList: faqData,
selectedCategory: null
}
}
So, as you mentioned, you need to handle user click, when going to see any questions based on selected category. To handle its click, you need to use v-on:click. To pass new value of selected category you need to update it: selectedCategory = 'somecategoryname'
'somecategoryname' means something from your faqData prop 'name', for example first-category:
<div> Please, peek FAQ category to show answers:
<span v-on:click="selectedCategory = 'first-category'"> First category </span>
<span v-on:click="selectedCategory = 'second-category'"> Second category </span>
</div>

dynamic import of images react

I am trying to dynamically render images from a local folder within my project
The object structure looks like this
{
id: 2,
text: 'How would you describe your level of influence in management of the farm?',
type: 'cardChoice',
choices: [
{
id: 1,
text: 'I am the primary decision maker',
questionId: 'Hm?',
value: 'I am the primary decision maker',
image: '1.jpg',
},
{
id: 2,
text: 'I hent',
questionId: 'Hrm?',
value: 'I',
image: '2.jpg',
},
{
id: 3,
text: 'I arm',
questionId: '?',
value: 'Irm',
image: '3.jpg',
},
],
},
In my component I select the folder that contains the images const baseUrl = '../../assets/images/CNA'
After that, in my return I try to render the images
<img src={`${baseUrl}'${questionChoice.image}'`} alt={questionChoice.text} />
The page renders, but my image isn't loading and it's showing my alt instead
Heres my full component
const CardChoiceQuestions = ({ cardChoiceArray, currentAnswer, updateCurrent, submitAnswer }) => {
const { id, value } = currentAnswer
const baseUrl = '../../assets/images/CNA'
return (
<ButtonContainer>
{cardChoiceArray.map(questionChoice => {
return (
<Button
active={questionChoice.id === id}
type="button"
key={questionChoice.id}
onClick={() => {
const answer = { id: questionChoice.id, value: questionChoice.value }
updateCurrent(answer)
submitAnswer()
}}
>
<p>{questionChoice.text}</p>
<img src={`${baseUrl}${questionChoice.image}`} alt={questionChoice.text} />
</Button>
)
})}
</ButtonContainer>
)
}
I don't have my laptop in front of me but a few things I noticed. Do you need a slash "/" after your base url? Also, the string concatenation should be completed in one set of brackets after the $ sign. Not sure if that's the issue try a few console.log(string path) amd verify it is going where you think it is. It looks like the path may be wrong. You may be better off conditional rendering images as opposed to building a dynamic url but either way it should render on change.
<img src={`${baseUrl}/${questionChoice.image}`} alt={questionChoice.text} />
use like this
Leaving this here in case someone comes across this...
<img src={require(`../../assets/images/CNA/${questionChoice.image}`)} alt={questionChoice.text} />
Not sure why I can't use ${baseUrl} but this works for now.

how to access an object element in a vue js list (nuxt)

I have an array of object like that :
{ id: 33617,
datePublication: 1532266465,
dateUpdate: 1532266574,
headline: 'An headline title here',
images: [ [Object] ] },
{ id: 33614,
datePublication: 1532265771,
dateUpdate: 1532271769,
headline: 'another super headline article',
images: [ [Object], [Object], [Object], [Object], [Object] ] }
so i iterate that way on my vue js template :
<v-flex v-for="(ip, i) in ips" :key="i" xs12 sm6 >
<v-card>
<v-card-media
:src="ip.images[0].url"
height="200px"
/>
<v-card-title primary-title>
<div>
<h3 class="headline mb-0">{{ ip.headline }}</h3>
<div>Located two hours south of Sydney in the <br>Southern Highlands of New South Wales, ...</div>
</div>
</v-card-title>
<v-card-actions>
<v-btn flat color="orange">Share</v-btn>
<v-btn flat color="orange">Explore</v-btn>
</v-card-actions>
</v-card>
</v-flex>
And here is my associated script :
var api = "https://my api.org/news/fr-FR/2018-25-07";
export default {
data() {
return {};
},
layout: "no-live",
async asyncData({ app }) {
const ips = await app.$axios.$get(api);
console.log(ips);
return { ips };
}
};
Each object in the images array should return me an id and an url, so i want to take that url as a source in my image but i have this error : Cannot read property '0' of undefined
it seems i need to do another loop over the images array, is there a proper way to do that with vue JS ?
if you have any objects in ips that doesn't have images, you would get that error,
you can try to add a conditional to not have an error in rendering
<v-card-media
v-if="ip.images && ip.images[0]"
:src="ip.images[0].url"
height="200px"
/>
I usually add a <pre>{{ip}}</pre> in these cases to see what's going on.
Try put v-if="ip.images" on v-card-media component. You will assure the images are not empty and loaded.

VUEjs templates multiple selectboxes

So, I'm assigned to work with vue at work, but VUE and I aren't friends yet. Currently I'm facing an issue that I don't know how to resolve - I'll explain it the best I can with the limited VUE knowledge I possess.
Simplistically I have a vue component, which looks like this:
Vue.component('input-checkboxes', {
template: '#input_checkboxes',
props: ['id', 'label', 'list', 'required', 'readonly']
});
Then I have a template that looks like this:
<template id="input_checkboxes">
<div>
<div>{{ label }}</div>
<div>
<label v-for="list_item in list">
<input type="checkbox" v-model="value" :name="id" :required="required" :readonly="readonly" value="{{ list_item.name }}"> {{ list_item.name }}
</label>
</div>
</div>
</template>
Then I have a rather large vue instance that I'll paste the relevant parts of here.
This variable is being created:
var some_form = {
form : {
Endless: '',
Amounts: '',
Of: '',
Names: '',
In: '',
The: '',
Form: '',
THIS-ONE: ''
}
};
var vm = new Vue({
el: '#form_product',
data: $.extend({
someStuff : 'some values',
someLists : {}
}, some_form),
ready: function() {
this.getLists(); // Fetches alot of lists
},
methods: {
this.$http.get(
framework.url('api','getLookupLists')
).then(function (response) {
this.lists = response.body;
this.pageLoading = false;
}.bind(this));
}
In the end I have my html page that amongst loads of other fields, that works very well, has this:
<input-checkboxes
id="THIS-ONE"
label="A Fitting Label"
:value.sync="form.SomeID"
:list="lists.AnAppropriateList">
</input-checkboxes>
So, that's the gist of the setup. I have numerous other components like input-text, that works just fine (someone else made it before I did), I even created other components by copying his way, and just changing some elements.
I cannot get checkboxes to work, I think my problem is that there are numerous inputs, and that I don't know how to bind the results of those inputs to my VUE instance.
I really hope this makes sense, because I would really like some pointers on how to get on... Maybe if someone duplicated this setup really simplistic and showed how the array of values from the checkboxes could be bound to the vue instance?
There are a couple of mistakes you are (or might be) making.
First of all, the value prop you pass down has to be an array (seems
like it's a string from your example)
value is not correctly set, you need to set it by doing :value="someValue"; you can't have curlies in an attribute.
Lastly, value should probably be the id of the item and not the name. You have a chance of a collision if you use the name.
Bonus: you don't need to use :name at all (unless you are submitting the form server side...? But I can't see why you would do that.)
Here's a simple working example to sum this up:
HTML
<label v-for="list_item in list">
<input type="checkbox" v-model="value" :required="required" :readonly="readonly" :value="list_item.id"> {{ list_item.name }}
</label>
JS
var app = new Vue({
el: 'main',
data: function () {
return {
value: [],
label: 'Label name',
readonly: false,
required: true,
list: [
{
name: 'Item 1',
id: 'item1'
},
{
name: 'Item 2',
id: 'item2'
}
]
}
}
})
I've also made a bin for you to try it out.

Filtering an array in angular2

I am looking into how to filter an array of data in Angular2.
I looked into using a custom pipe, but I feel this is not what I am looking for, as it seems more geared towards simple presentation transformations rather then filtering large sets of data.
The array is set out as follows:
getLogs(): Array<Logs> {
return [
{ id: '1', plate: 'plate1', time: 20 },
{ id: '1', plate: 'plate2', time: 30 },
{ id: '1', plate: 'plate3', time: 30 },
{ id: '2', plate: 'plate4', time: 30 },
{ id: '2', plate: 'plate5', time: 30 },
{ id: '2', plate: 'plate6', time: 30 }
];
}
I want to filter this by id. So when I enter "1" into a search bar, it updates to display the corresponding values.
If there is a method on how to do this, I would love to know!
There is no way to do that using a default pipe. Here is the list of supported pipes by default: https://github.com/angular/angular/blob/master/modules/angular2/src/common/pipes/common_pipes.ts.
That said you can easily add a pipe for such use case:
import {Injectable, Pipe} from 'angular2/core';
#Pipe({
name: 'myfilter'
})
#Injectable()
export class MyFilterPipe implements PipeTransform {
transform(items: any[], args: any[]): any {
return items.filter(item => item.id.indexOf(args[0]) !== -1);
}
}
And use it:
import { MyFilterPipe } from './filter-pipe';
(...)
#Component({
selector: 'my-component',
pipes: [ MyFilterPipe ],
template: `
<ul>
<li *ngFor="#element of (elements | myfilter:'123')">(...)</li>
</ul>
`
})
Hope it helps you,
Thierry
I have a similar scenario in one of my samples
<input "(keyup)="navigate($event)" />
<div *ngFor="#row of visibleRows"></div>
......
navigate($event){
this.model.navigate($event.keyCode);
this.visibleRows = this.getVisibleRows();
}
getVisibleRows(){
return this.model.rows.filter((row) => row.rowIndex >= this.model.start && row.rowIndex < this.model.end);
}
My approach is to recalculate the array on some qualifying event. In my case it's keyup. It may seem convenient to bind to a function or filter, but it's recommended to bind to the array directly instead. This is because the change tracking will get confused since the function/filter will return a new array instance every time change tracking is triggered - regardless of what triggered it.
Here is the full source: https://github.com/thelgevold/angular-2-samples/tree/master/components/spreadsheet
I also have a demo: http://www.syntaxsuccess.com/angular-2-samples/#/demo/spreadsheet

Resources