I want to make my component more reusable. In the component I'm binding two values with ngModel: elem.key and elem.value. The problem is that wherever I want to use this component, the element has to have key and value properties, for example some data from Api might have name, and nickname etc. For now I can use my component repeatedly, but only if the values of object are key and value. My code:
html:
<button (click)="addNew()">Add</button>
<div *ngFor="let elem of elements">
<text-input [(ngModel)]="elem.key" type="text"></text-input>
<text-input [(ngModel)]="elem.value" type="text"></text-input>
</div>
ts:
#Input() elements: any[];
addNew() {
this.elements.push({
key: '',
value: ''
});
}
If I use my component in another:
<input-key-value [elements]="values">
It works fine if I only need to add to values array {key: '', value: ''} But sometimes I want to add for example {name: '', nickname: ''}, cause data in this format must be sent to the server.
I tried add another Input name inputs, {key: 'name', value: 'name'} And in html:
<text-input [(ngModel)]="elem[inputs.key]" type="text"></text-input>
<text-input [(ngModel)]="elem.[inputs.value]" type="text"></text-input>
But this is again pushing wrong data to my main array.
This worked for me.
input-key-value template:
<div *ngFor="let elem of elements">
<div *ngFor="let prop of keys(elem)" >
<text-input type="text" [(ngModel)]="elem[prop]"></text-input>
</div>
</div>
input-key-value ts:
keys(element) {
return Object.keys(element);
}
Depending on how many properties your object has, it renders as much text boxes. Hope this will help.
In your add new function you might need to do like below
#Input() elements: any[];
#Input() elementKey: string = 'key';
#Input() elementValue: string = 'value';
addNewe() {
const element = {};
element[this.elementKey] = '';
element[this.elementValue] = '';
this.elements.push(element);
}
and in your view, you should do like below
<button (click)="addNew()">Add</button>
<div *ngFor="let elem of elements">
<text-input [(ngModel)]="elem[elementKey]"
type="text"></text-input>
<text-input [(ngModel)]="elem[elementValue]"
type="text"></text-input>
</div>
you might need to pass element key and element values when when you are using this component
when you are using your component. If you have to pass key and value like below, based on example that you have provided in comment {name: '', nickname: ''}
<input-key-value [elements]="values" [elementKey]='name' [elementValue]='nickname'>
If you are passing elements like {key: '', value: ''} then there is no requirment to pass element key and element value inputs. you can directly use it
<input-key-value [elements]="values">
Related
I have an array with objects, each having a type.
I want to display only the objects with a specific type that i change by pressing some buttons.
For some reasons, for the "Food" type, it also renders the other two objects but without any content.
Here's the array and type
objects = [
{
title: 'Spartan Sandwich',
price: 4,
type: 'Food',
},
{
title: 'Math Lessons',
price: 10,
type: 'Necesities',
},
{
title: 'Ice Skating',
price: 10,
type: 'Misc',
},
];
type: string = 'Food';
Here's the HTML
<div class="objects">
<div class="object-container" *ngFor="let object of objects">
<app-object [object]="object" *ngIf="object.type === type"></app-object>
</div>
</div>
I know that it's normal for it to always render all the object-container divs, but how can i fix it so that they do not appear?
You can run the ngFor loop in ng-content and check the object.type in object-container div:
<div class="objects">
<ng-content *ngFor="let object of objects">
<div class="object-container" *ngIf="object.type === type">
<app-object [object]="object"></app-object>
</div>
</ng-content>
</div>
Note: ng-content doesn't render any HTML element.
Either you go with the code suggested by Shuvo or you manipulate the "objects" array using filter.
viewData = this.objects.filter(({ type }) => type === this.type);
The best way to do this is by putting the app-object component inside a virtual container like ng-container that way there will not be any need to use an extra div.
The code will look like this:
`
<div class="objects">
<div class="object-container" *ngFor="let object of objects">
<ng-container *ngIf="object.type === type">
<app-object [object]="object"></app-object>
</ng-container>
</div>
</div>
`
Here is the embedded JSX code:
<Col>
<h2> Found Files </h2>
{this.state.foundFiles.map(files => (
<div className = "file-box-search" key={files}>
<input type = "checkbox" name = {files.id}></input>
<p className = ""> <a href = {files.click}> {files.file} </a> </p>
<p> {files.description}</p>
</div>
))}
</Col>
Just for clarity on what's going on here:
this.state.foundFiles is an array object that holds the files I am mapping through, which values such as id and file. An example looks like this:
{
file: 'lesson 1',
id: '1qupvie1LqNdLj-1TZNu3x6-4bT411C4F2YYGSfpc7yk',
description: undefined,
type: 'application/vnd.google-apps.document',
properties: { subject: 'math', grade: 'pre-k' },
parents: [ '1kAzxwEgX5ftI-Sa4nUDK1Y5rJOSJ6VrU' ]
},
The .map() loops through this array and gives several different formatted divs dependent on the values of each file in the array.
Within this .map() method there is a checkbox. When the checkbox is checked, I want to invoke a function that can use the name attribute of that specific checkbox within the map method. I can't just target by id, because then every element within the method would have the same exact id.
What would be the best way to go about doing this?
That's where I suggest to define some constant value with the index:
.map((files, index) => {
// ${`checkbox-${index}-${files.id}`}
I have a wrapper component which ngFor a child component. The child component receives in #input an object to format and display.
The problem is that sometimes the object is complete and displayed correctly and sometimes the object is almost empty and I have 'x' of undefined in all formatting methods, which is normal. How and in what good way can we handle all cases of 'x' of undefined globally. Based on the TypeScript interface maybe ...?
The wrapper component:
<div>
<contact-card *ngFor="let contact of contacts"
[contact]="contact">
</contact-card>
</div>
The controller of the child component :
#Input() contact: Contact;
get fullName(): string {
return `${this.contact.collaborator.fullName} ${this.contact.collaborator.lastName}`;
}
get country(): string {
return this.contact.address.country;
}
hasAccess(): boolean {
return this.contact.access.edit
}
The template of the child component :
<div>
<p>{{ contact.id }}</p>
<p>{{ fullName }}</p>
<p>{{ country }}</p>
<div *ngIf="hasAccess">
<!-- -->
</div>
</div>
This is a minimalist example. The contact object may be in the correct case :
{
id: 1,
collaborator: {
firstName: 'Jean',
lastName: 'Pierre'
},
address: {
country: 'France'
},
access: {
delete: false,
edit: true
}
}
Or :
{
id: 1
}
I also have a pipe defaultValue which displays a default value if the value is null or undefined if it can help :
<p>{{ fullName | defaultValue }}</p>
mais
Your default value pipe can help in the template, but I guess the error is triggered in the class.
You could try to give your child component's input a default value like #Input() contact: Contact = new Contact();
This way, the input will never be undefined and its value will change when it is ready.
<div *ngFor="let lib of library">
<input type="text" [(ngModel)]="lib.item"></div>
<div>
<md-select [(ngModel)]="lib.title">
<md-option *ngFor="let book of books" [value]="book._id">{{book.bookname}}
<md-option>
</md-select>
</div>
</div>
In my controller I have
books=[
{_id: 1, bookname:'first book'},
{_id: 2, bookname:'second book'},
{_id: 3, bookname:'third book'}
]
Interface is
export interface Ixyz{
_id: string;
item: string;
title: ICat;
}
so when I push something like
var add:Ixyz={
_id: '',
item:'',
title: 2
}
I want to be able to have default value in the dropdown. I know how to find value using ngModel. But ngModel is being used for something different.
When I push new value using title=2 it doesnt show on dropdown because title: ICat is referencing another interface.
When you add a new item into the array, just do:
var add:Ixyz={
_id: '',
item:'',
title: this.books[1]
}
This will provide a default value that should match populate the dropdown.
I'm trying to make a set of components for repetitive use. The components I'm looking to create are various form fields like text, checkbox and so on.
I have all the data in data on my parent vue object, and want that to be the one truth also after the user changes values in those fields.
I know how to use props to pass the data to the component, and emits to pass them back up again. However I want to avoid having to write a new "method" in my parent object for every component I add.
<div class="vue-parent">
<vuefield-checkbox :vmodel="someObject.active" label="Some object active" #value-changed="valueChanged"></vuefield-checkbox>
</div>
My component is something like:
Vue.component('vuefield-checkbox',{
props: ['vmodel', 'label'],
data(){
return {
value: this.vmodel
}
},
template:`<div class="form-field form-field-checkbox">
<div class="form-group">
<label>
<input type="checkbox" v-model="value" #change="$emit('value-changed', value)">
{{label}}
</label>
</div>
</div>`
});
I have this Vue object:
var vueObject= new Vue({
el: '.vue-parent',
data:{
someNumber:0,
someBoolean:false,
anotherBoolean: true,
someObject:{
name:'My object',
active:false
},
imageAd: {
}
},
methods: {
valueChange: function (newVal) {
this.carouselAd.autoOrder = newVal;
}
}
});
See this jsfiddle to see example: JsFiddle
The jsfiddle is a working example using a hard-coded method to set one specific value. I'd like to eighter write everything inline where i use the component, or write a generic method to update the parents data. Is this possible?
Minde
You can use v-model on your component.
When using v-model on a component, it will bind to the property value and it will update on input event.
HTML
<div class="vue-parent">
<vuefield-checkbox v-model="someObject.active" label="Some object active"></vuefield-checkbox>
<p>Parents someObject.active: {{someObject.active}}</p>
</div>
Javascript
Vue.component('vuefield-checkbox',{
props: ['value', 'label'],
data(){
return {
innerValue: this.value
}
},
template:`<div class="form-field form-field-checkbox">
<div class="form-group">
<label>
<input type="checkbox" v-model="innerValue" #change="$emit('input', innerValue)">
{{label}}
</label>
</div>
</div>`
});
var vueObject= new Vue({
el: '.vue-parent',
data:{
someNumber:0,
someBoolean:false,
anotherBoolean: true,
someObject:{
name:'My object',
active:false
},
imageAd: {
}
}
});
Example fiddle: https://jsfiddle.net/hqb6ufwr/2/
As an addition to Gudradain answer - v-model field and event can be customized:
From here: https://v2.vuejs.org/v2/guide/components.html#Customizing-Component-v-model
By default, v-model on a component uses value as the prop and input as
the event, but some input types such as checkboxes and radio buttons
may want to use the value prop for a different purpose. Using the
model option can avoid the conflict in such cases:
Vue.component('my-checkbox', {
model: {
prop: 'checked',
event: 'change'
},
props: {
checked: Boolean,
// this allows using the `value` prop for a different purpose
value: String
},
// ...
})
<my-checkbox v-model="foo" value="some value"></my-checkbox>
The above will be equivalent to:
<my-checkbox
:checked="foo"
#change="val => { foo = val }"
value="some value">
</my-checkbox>