How to receive the input values of variable number of input boxes?
I searched a long time in Google but I'm not able to do it.
ParentComponent.html:
<my-Input [interfaces]="interfaces"> {{ title }}</my-Input>
ParentComponent.ts:
interface single_input{
title: string,
get_value(event: string): void;
}
interfaces: single_input[] = [
{
title: "something",
get_value(event){ console.log(event)}
}
];
ChildComponent.html:
<div *ngFor="let i of interfaces">
<Input (keyup)="i.get_value($event.target.value)"> {{ title }}</Input>
</div>
To log this in the console works, but I just want to bind the input values to the given interface and give it back by eventemitter to the parent.
How to do this? Do I need a class for it?
I got it now.
But it looks like a workaround. is there a better solution than following code?
Code-Update:
ParentComponent.ts:
interface single_input{
title: string;
value: string;
}
interfaces: single_input[] = [
{
title: "something",
}
];
ChildComponent.html:
<div *ngFor="let i of interfaces">
<Input (keyup)="i.value = $event.target.value"> {{ title }}</Input>
</div>
It is far simpler than what you are trying to do. Angular have specific mechanism to achieve this goal, one of them being the decorators #Input and #Output. You are already using the #Input decorator to pass in the data to the child component, now you just need to use the #Output decorator to pass the data back to the parent. I recommend looking at the docs and this example to have a better understanding on how this works. Below is an example for your use case.
ChildComponent.html
<div *ngFor="let i of interfaces">
<input (keyup)="onKeyUp(i, $event.target.value)"> {{ title }}</input>
</div>
ChildComponent.ts
import { Component, EventEmitter } from '#angular/core';
#Component({ ... })
export class ChildComponent {
...
#Input() interfaces;
#Output() childValue: EventEmitter<any> = new EventEmitter<any>();
// ^ Use a proper datatype ^
onKeyUp(source, value) {
this.childValue.emit({
source: source,
value: value
});
}
...
}
ParentComponent.html
<my-input [interfaces]="interfaces" (childValue)="onChildValueChange($event)"> {{ title }}</my-input>
ParentComponent.ts
import { Component} from '#angular/core';
#Component({ ... })
export class ParentComponent {
...
onChildValueChange(event) {
// Do what you want with it
const interface = this.interfaces.find(interface =>
interface.title === event.source.title);
console.log('[ParentComponent] Value from child component:', event);
console.log('[ParentComponent] Changed interface:', interface);
console.log('[ParentComponent] Changed value:', event.value);
}
...
}
Extra tip: Just for completeness, an equally popular approach, would be using a service to contain the data, where the child changes the data on the service and the parent read from it.
Problem
Create the checkbox as Vue component, herewith:
No logic inside checkbox component allowed: all event handlers and also checked property are fully depends on external logic, which could be the vuex store.
We should not watch the checkbox "checked" state: checked it or not, it depends on, again, external logic, e. g. vuex state or getter.
Try 1
Concept
The checkbox component has checked and onClick properties, which value are off course, could be dynamic.
Component
Template in Pug language:
label.SvgCheckbox-LabelAsWrapper(:class="rootElementCssClass" #click.prevent="onClick")
input.SvgCheckbox-InvisibleAuthenticCheckbox(
type="checkbox"
:checked="checked"
:disabled="disabled"
)
svg(viewbox='0 0 24 24').SvgCheckbox-SvgCanvas
path(
v-if="!checked"
d='M19,3H5C3.89,3 3,3.89 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5C21,3.89 20.1,3 19,3M19,5V19H5V5H19Z'
).SvgCheckbox-SvgPath.SvgCheckbox-SvgPath__Unchecked
path(
v-else
d='M10,17L5,12L6.41,10.58L10,14.17L17.59,6.58L19,8M19,3H5C3.89,3 3,3.89 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5C21,3.89 20.1,3 19,3Z'
).SvgCheckbox-SvgPath.SvgCheckbox-SvgPath__Checked
span(v-if="text").SvgCheckbox-AppendedText {{ text }}
import { Vue, Component, Prop } from 'vue-property-decorator';
#Component
export default class SimpleCheckbox extends Vue {
#Prop({ type: Boolean, required: true }) private readonly checked!: boolean;
#Prop({ type: Boolean, default: false }) private readonly disabled!: boolean;
#Prop({ type: String }) private readonly text?: string;
#Prop({ type: String }) private readonly parentElementCssClass?: string;
#Prop({ type: Function, default: () => {} }) private readonly onClick!: () => void;
}
Store module
import { VuexModule, Module, Mutation } from "vuex-module-decorators";
import store, { StoreModuleNames } from "#Store/Store";
#Module({ name: StoreModuleNames.example, store, dynamic: true, namespaced: true })
export default class ExampleStoreModule extends VuexModule {
private _doNotPreProcessMarkupEntryPointsFlag: boolean = true;
public get doNotPreProcessMarkupEntryPointsFlag(): boolean {
return this._doNotPreProcessMarkupEntryPointsFlag;
}
#Mutation
public toggleDoNotPreProcessMarkupEntryPointsFlag(): void {
this._doNotPreProcessMarkupEntryPointsFlag = !this._doNotPreProcessMarkupEntryPointsFlag;
}
}
Usage
SimpleCheckbox(
:checked="relatedStoreModule.doNotPreProcessMarkupEntryPointsFlag"
:onClick="relatedStoreModule.toggleDoNotPreProcessMarkupEntryPointsFlag"
parentElementCssClass="RegularCheckbox"
)
import { Component, Vue } from "vue-property-decorator";
import { getModule } from "vuex-module-decorators";
import ExampleStoreModule from "#Store/modules/ExampleStoreModule";
import template from "#Templates/ExampleTemplate.pug";
import SimpleCheckbox from "#Components/Checkboxes/MaterialDesign/SimpleCheckbox.vue";
#Component({ components: { SimpleCheckbox } })
export default class MarkupPreProcessingSettings extends Vue {
private readonly relatedStoreModule: ExampleStoreModule = getModule(ExampleStoreModule);
}
Warings
Appears if click the checkbox. Checkbox works as we need, but some of Vue concept has been violated.
vue.common.dev.js:630 [Vue warn]: $attrs is readonly.
found in
---> <SimpleCheckbox> at hikari-frontend/UiComponents/Checkboxes/MaterialDesign/SimpleCheckbox.vue
<MarkupPreProcessingSettings>
<Application> at ProjectInitializer/ElectronRendererProcess/RootComponent.vue
<Root>
vue.common.dev.js:630 [Vue warn]: $listeners is readonly.
found in
---> <SimpleCheckbox> at hikari-frontend/UiComponents/Checkboxes/MaterialDesign/SimpleCheckbox.vue
<MarkupPreProcessingSettings>
<Application> at ProjectInitializer/ElectronRendererProcess/RootComponent.vue
<Root>
vue.common.dev.js:630 [Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "checked"
found in
---> <SimpleCheckbox> at hikari-frontend/UiComponents/Checkboxes/MaterialDesign/SimpleCheckbox.vue
<MarkupPreProcessingSettings>
<Application> at ProjectInitializer/ElectronRendererProcess/RootComponent.vue
<Root>
Musings
This warning are being emitted frequent cause is new value to some vue-property has been assign inside component. Explicitly, I did not the manipulations like this.
The problem is in :onClick="relatedStoreModule.toggleDoNotPreProcessMarkupEntryPointsFlag". It looks like it compiles to something like <component>.$props.onClick="<vuex store manipulations ...>" - if it so, it is implicit property mutation inside component.
Try 2
Concept
Based Vue documentation, Customizing Component section:
Vue.component('base-checkbox', {
model: {
prop: 'checked',
event: 'change'
},
props: {
checked: Boolean
},
template: `
<input
type="checkbox"
v-bind:checked="checked"
v-on:change="$emit('change', $event.target.checked)"
>
`
})
The equivalent for TypeScript with vue-property-decorator will be:
import { Vue, Component, Model } from 'vue-property-decorator'
#Component
export default class YourComponent extends Vue {
#Model('change', { type: Boolean }) readonly checked!: boolean
}
Component
label.SvgCheckbox-LabelAsWrapper(:class="rootElementCssClass")
input.SvgCheckbox-InvisibleAuthenticCheckbox(
type="checkbox"
:checked="checked"
:disabled="disabled"
#change="$emit('change', $event.target.checked)"
)
svg(viewbox='0 0 24 24').SvgCheckbox-SvgCanvas
// ...
import { Vue, Component, Prop, Model } from "vue-property-decorator";
#Component
export default class SimpleCheckbox extends Vue {
#Model('change', { type: Boolean }) readonly checked!: boolean;
#Prop({ type: Boolean, default: false }) private readonly disabled!: boolean;
#Prop({ type: String }) private readonly text?: string;
#Prop({ type: String }) private readonly rootElementCssClass?: string;
}
Usage
SimpleCheckbox(
v-model="doNotPreProcessMarkupEntryPointsFlag"
rootElementCssClass="RegularCheckbox"
)
In TypeScript, to use the v-model, we need to declare getter and same-name setter:
#Component({
template,
components: {
SimpleCheckbox,
// ...
}
})
export default class MarkupPreProcessingSettings extends Vue {
private readonly relatedStoreModule: MarkupPreProcessingSettingsStoreModule =
getModule(MarkupPreProcessingSettingsStoreModule);
//...
private get doNotPreProcessMarkupEntryPointsFlag(): boolean {
return this.relatedStoreModule.doNotPreProcessMarkupEntryPointsFlag;
}
private set doNotPreProcessMarkupEntryPointsFlag(_newValue: boolean) {
this.relatedStoreModule.toggleDoNotPreProcessMarkupEntryPointsFlag();
}
}
Warnings
Same errors set:
Limitations
First, we need to create new getter and setter in Vue Component class. It will be cool if possible to avoid id. Unfortunately, for vuex class (by vuex-module-decorators), TypeScript setters are not available, we need use the #Mutation-decorated method instead.
Also, this solution will not work for elements rendered by v-for. It make this solution useless.
Try 3
Concept
Event emitter and custom event listener usage. This solution also works properly, but Vue emits warnings.
Component
label.SvgCheckbox-LabelAsWrapper(:class="rootElementCssClass" #click.prevent="$emit('toggled')")
// ...
Usage
SimpleCheckbox(
:checked="relatedStoreModule.doNotPreProcessMarkupEntryPointsFlag"
#toggled="relatedStoreModule.toggleDoNotPreProcessMarkupEntryPointsFlag"
rootElementCssClass="RegularCheckbox"
)
Warnings
Update
There are some riddles left yet, however problem has been solved. See my answer below.
This warnings occurred in Electron application. The SimpleCheckbox is from node_modules, however this library is still in development, so it has been provided by npm link.
When I tried to make reproduction, I created the SPA for browser and place SimpleCheckbox to same project (did not get from node_modules). The first solution works! (I don't care about second and third ones - I need refined from peel elegant solutions only).
I suggested that cause is npm link, publish my libraries and installed it via npm install. The warnings has disappeared!
Conclusion
It was not the first time when npm link causes the problem like this. Here is another case.
I still does not understand this this case in depth - I just published some experimental data. The "So, what if library is in development yet?" question still has not answer. I tried Lerna - at the first time warnings has disappeared, but when I move my project to Lerna, the warnings appears again - the regularity is unclear yet for me.
I am not sure it is typescript-based issue.
According to your warning messages and your codes, I can notice that you used prop as a input model.
In default, prop is not allowed to be mutated.
It could be a bad idea to mutate prop even though it is an Object or Array. ( if prop is Object or Array, it can be mutated in children. but It is not recommended)
To avoid this warnings, you can use data which is a clone of prop in the children like below:
props: {
checked: {
type: Boolean,
default: false,
},
change: {
type: Function,
default: () => {},
}
},
data: {
checkedModel: false,
},
mounted() {
this.checkedModel = this.checked; // init model value as prop
}
I have a component with an #Input property of type Foo:
#Component({
...
})
export class MyComponent {
#Input() Options: Foo;
...
}
And in the ParentComponent.html literal class pass to it as input value:
<My [Options]="{prop1:true, prop2:false, prop3:1, ... }"></My>
Then type of Options is not Foo anymore. It changes to anonymous object and therefore methods of Foo are not accessible any longer.
One way to prevent this, is to create an instance of Foo in ParentComponent .ts and pass it as variable:
#Component({
...
})
export class ParentComponent {
Options: new Foo(true, true, 1, ...);
...
}
And in the ParentComponent.html use:
<My [Foo]="options"></My>
Another way would be to somehow cast anonymous object to the newly created Foo object:
#Component({
...
})
export class MyComponent {
#Input() set Options(value: Foo){
//Somehow cast anonymous to Foo.
}
private options : Foo;
...
}
Is there any better or built-in way to do that?
If not, then how can I cast anonymous object to Foo?
At some point you need to create an instance of your class Foo with new if you want to use the class methods using one of the two ways you described. If you want to pass in the plain object as your #Input, the input cannot be of (class-)type Foo because it is not an instance of that class. Within the class, you need to call new Foo(fooInput) and then e. g. assign the newly created instance to another member variable.
In my opinion, it would be better to have your data in a plain data object instead of a class: Define Foo as an interface instead of a class for type safety. Then, put the methods from the class into a FooService, which manipulates Foo objects. With this, you don't have to bother with class instantiation.
Example
class User {
constructor(public firstName: string, public lastName: string) {}
getFullName(): string {
return `${this.firstName} ${this.lastName}`;
}
}
becomes:
interface User {
firstName: string;
lastName: string;
}
class UserService {
getFullName(user: User): string {
return `${user.firstName} ${user.lastName}`;
}
}
Following what is documented here: Dynamic Component Loader.
I want to know how is it possible to handle the data inside this HeroJobAdComponent class:
import { Component, Input } from '#angular/core';
import { AdComponent } from './ad.component';
#Component({
template: `
<div class="job-ad">
<h4>{{data.headline}}</h4>
{{data.body}}
</div>
`
})
export class HeroJobAdComponent implements AdComponent {
#Input() data: any;
}
As you can see, data is the object holding the data received. I want to be able to define a constructor for my HeroJobAdComponent class but if I do, the object data is undefined inside my constructor. I tried using ngOnChange instead which supposedly executes once input is changed from undefined to defined but it also did not execute at all.
Can someone please explain first why is the object undefined even though the data is defined in my main component calling it, and what's the workaround for this issue?
This is the constructor I am using:
constructor()
{
this.values = this.data.values;
this.spec_name = this.data.spec_name;
}
if you want to use any operation when you receive data in your component , you can use setter
export class HeroJobAdComponent implements AdComponent {
_data;
#Input() set data (data: any){
//operation on data goes here
this._data=data
};
get data() {
return this._data;
}
}
I have an interface which I want to be populated automatically with some default data when I assign variables to it. The interface is saved in a module and I want to use it on other modules. How do I assign data to interface properties ?
export interface NameAge {
valueList : Array<{ 'Name', 'Age' }>;
}
So I create let's say some variables in multiple modules that implement this interface.
How do I assign some default value to the NameAge property. I can do it with a class containing listUsers as public static property but can't replace interface with class. Also I want the interface and the listUsers array of objects to be in the same module so I when I import the module, I get both the interface and listUsers with the default data already populated. Thanks :))
Interfaces will not allow you to do that.
However you can do it with classes.
class NameAge {
public valueList: Array<{ 'Name', 'Age' }> = [{
'Name': 'J',
'Age': 'ABC'
}];
}
class NameAgeChild extends NameAge {
public child: boolean
public adults() {
return this.valueList.filter(v => v.Age >= 18);
}
}
This however makes sense only if you need a class for this value (code + behaviour together) in your app.
If not, just make a constant with the your values and use it when you create new instances.