Unable to pull data from a data-attribute in Angular 7 - javascript

I am attempting to reuse a kendoDropDownListBox and set the data by using a data-attribute in the parent to query the proper data source, combined with a switch case statement. The switch case portion of the code is not included as it works when the proper data is passed to it, I'm just unable to pull the proper data from the data-attribute (if I use buttons to pass the data it works fine)
I have tried a number of methods to pull the attribute including the following
element.dataset[keyname]
element.getAttribute('keyname']
If I do a console.log('element') I can see the proper data, but either of the above two methods come up empty (either null or undefined).
The HTML:
<div [attr.data-message-id]="1"> Listbox Component
<app-listbox></app-listbox>
</div>
The Typescript:
import { Component, OnInit, ElementRef } from '#angular/core';
#Component({
selector: 'app-listbox',
styleUrls: ['./listbox.component.scss'],
template: `
<kendo-dropdownlist style="width:400px;"
[data]="data"
[filterable]="true"
[textField]="'text'"
[valueField]="'value'"
(filterChange)="handleFilter($event)"
>
<ng-template kendoDropDownListNoDataTemplate>
<div>
No data found.
<ng-container *ngIf="filter">Do you want to add new item - '{{ filter }}' ?</ng-container>
<br />
<button *ngIf="filter" class="k-button" (click)="addNew()">Add new item</button>
</div>
</ng-template>
</kendo-dropdownlist>
`
})
export class ListboxComponent {
public filter: string;
public source: Array<{ text: string, value: number }> = [
{ text: "Small", value: 1 },
{ text: "Medium", value: 2 },
{ text: "Large", value: 3 }
];
public data: Array<{ text: string, value: number }>;
messages = [
{
id: 1,
text: "Table1"
},
{
id: 2,
text: "Table2"
},
{
id: 3,
text: "Table3"
},
{
id: 4,
text: "Table4"
}
]
Table1 = [
{ id: 1, text: "small"},
{ id: 2, text: "med"},
{ id: 3, text: "large"},
{ id: 4, text: "XL"},
]
Table2 = [
{ id: 1, text: "ford"},
{ id: 2, text: "dodge"},
{ id: 3, text: "chevy"},
{ id: 4, text: "GM"},
]
Table3 = [
{ id: 1, text: "fiat"},
{ id: 2, text: "audi"},
{ id: 3, text: "Mercedes"},
{ id: 4, text: "BMW"},
]
Table4 = [
{ id: 1, text: "toyota"},
{ id: 2, text: "nissan"},
{ id: 3, text: "datsun"},
{ id: 4, text: "kia"},
]
constructor(private elRef: ElementRef) {
this.data = this.source.slice(0);
}
public addNew(): void {
this.source.push({
text: this.filter,
value: 0
});
this.handleFilter(this.filter);
}
public handleFilter(value) {
this.filter = value;
this.data = this.source.filter((s) => s.text.toLowerCase().indexOf(value.toLowerCase()) !== -1);
}
ngOnInit() {
console.log("OnInit");
console.log("el");
var el = this.elRef.nativeElement.parentElement.dataset;
console.log(el);
console.log("elatt");
var elatt = this.elRef.nativeElement.parentElement.attributes;
console.log(elatt);
console.log("elkey");
var elkey = this.elRef.nativeElement.parentElement.dataset['messageId'];
console.log(elkey);
console.log("att");
var att = this.elRef.nativeElement.parentElement.getAttribute(['data-message-id']);
console.log(att);
}
}
Using the above code, the el variable contains the following:
enter image description here
The elatt variable contains the following:
enter image description here
the elkey variable reports "undefined" and the att variable reports "null".
I'm sure I'm probably doing this the hard way, but being new to Angular, I'm not sure of a better way of doing this.
Ultimately what I'm looking for is a way to reuse the kendoDropdownBox as a component, and pass it the data it needs to display when it is used.

ngOnInit() :
Initialize the directive/component after Angular first displays the data-bound properties and sets the directive/component's input properties.
Called once, after the first ngOnChanges().
ngAfterViewInit() :
Respond after Angular initializes the component's views and child views / the view that a directive is in.
Called once after the first ngAfterContentChecked().
You are not able to retrieve the data attribute from parent because, you are trying to access the parent from ngOnInit event. It should be in ngAfterViewInit Lifecyle event.
Refer the example below.
ParentComponent.html
<div [attr.data-message-id]="1">
<app-test-component></app-test-component>
</div>
ChildComponent.ts
import { Component, OnInit, ElementRef } from '#angular/core';
#Component({
selector: 'app-test-component',
templateUrl: './test-component.component.html',
styleUrls: ['./test-component.component.css']
})
export class TestComponentComponent implements OnInit {
constructor(private elRef: ElementRef) {
}
ngOnInit() {
}
ngAfterViewInit(){
console.log(this.elRef.nativeElement.parentElement);
console.log('message id : ', this.elRef.nativeElement.parentElement.dataset['messageId']);
}
}
Output Log

Related

Pug hasn't access to computed properties in vue inside dynamic class attribute

So, i tried to dynamically toggle className, based on computed property, but it looks like pug doesn't have access to computed properties. I tried to manually set true to a className, then it's working.
I tried to reassign computed property to pug variable, but it still doesn't work
When using pure html, classes dynamically toggle correctly
Pug:
main#app.container
- var isPinkDefinitely = isPink ? 'pink' : 'gray';
div.section(
v-bind:class=[
'custom-section',
{
'custom-section--pink': isPink
}
]
v-bind:style=[
{
'background-color': isPinkDefinitely
}
]
) {{ isPink }}
form(#submit.prevent="addItem")
label.label Add another
div.field.has-addons
div.control
input.input(required, autofocus, v-model="newItem", placeholder="Remake this in React")
button(type="submit").button.is-info
i.fa.fa-plus
span Add
transition(name="slide")
div(v-show="items.length > 0")
hr
ul
transition-group(name="slide")
li(v-for="(item, index) in items", :key="item.id")
button(#click="removeItem(index)").button.is-danger
i.fa.fa-trash
span(v-text="item.desc")
hr
span(v-text="'Total: ' + items.length")
JS:
new Vue({
el: '#app',
data: {
items: [
{id: 1, desc: "Lorem"},
{id: 2, desc: "Ipsum"},
{id: 3, desc: "Dolor"},
],
newItem: '',
},
computed: {
isPink() {
return true;
}
},
methods: {
addItem () {
const id = this.items.length + 1
this.items.push({id, desc: this.newItem})
this.newItem = ''
},
removeItem (index) {
this.items.splice(index, 1)
},
},
})
https://codepen.io/itprogressuz/pen/qBoePob?editors=1010
UPD:
SO the basically solution was to just write all classes in one line inside just an object. see solution of #aykut
I just tried to solve your problem and I think i successed. You could use variable like me. If you want change it in computed function, it will change dynamically. You could also change it in methods functions when get users events. Here, my solution.
main#app.container
div.section(
class="default-style"
:class="{'bg-pink': isPink }"
) {{ setIsPink }}
form(#submit.prevent="addItem")
label.label Add another
div.field.has-addons
div.control
input.input(required, autofocus, v-model="newItem", placeholder="Remake
this in React")
button(type="submit").button.is-info
i.fa.fa-plus
span Add
transition(name="slide")
div(v-show="items.length > 0")
hr
ul
transition-group(name="slide")
li(v-for="(item, index) in items", :key="item.id")
button(#click="removeItem(index)").button.is-danger
i.fa.fa-trash
span(v-text="item.desc")
hr
span(v-text="'Total: ' + items.length")
// css file
.default-style{
background-color: gray;
}
.bg-pink{
background-color: pink;
}
// js file
new Vue({
el: '#app',
data: {
isPink: false,
items: [
{id: 1, desc: "Lorem"},
{id: 2, desc: "Ipsum"},
{id: 3, desc: "Dolor"},
],
newItem: '',
},
computed: {
setIsPink() {
this.isPink = !this.isPink;
return this.isPink;
}
},
methods: {
addItem () {
const id = this.items.length + 1
this.items.push({id, desc: this.newItem})
this.newItem = ''
},
removeItem (index) {
this.items.splice(index, 1)
},
},
})

Getting unwanted commas after div

`
Here's the code written in TypeScript.
This is code to build HTML table which display items from Nested objects. This code works fine but just there is an issue in printing like it should only create table with rows but it is also printing some coma's which are not even part of any line which is executed
import {Component} from '#angular/core';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'Angular';
data = {
id: '0001',
type: 'donut',
name: 'Cake',
ppu: 0.55,
batters: {
batter: [{
id: '1001',
type: 'Regular'
},
{
id: '1002',
type: 'Chocolate'
},
{
id: '1003',
type: 'Blueberry'
},
{
id: '1004',
type: "Devil's Food"
}
]
},
topping: [{
id: '5001',
type: 'None'
},
{
id: '5002',
type: 'Glazed'
},
{
id: '5005',
type: 'Sugar'
},
{
id: '5007',
type: 'Powdered Sugar'
},
{
id: '5006',
type: 'Chocolate with Sprinkles'
},
{
id: '5003',
type: 'Chocolate'
},
{
id: '5004',
type: 'Maple'
}
]
};
//Function which build HTML Table which get's dynamic values.
htmlStr = (data, wrapperClassName, tableClassName = 'table table-sm') => {
return `
<div class=${tableClassName}>
<table className=${tableClassName}>
<tbody>
${Object.keys(data).map( (k) => `
<tr>
${(!Array.isArray(data) && `
<td>${k.replace(/_/g, ' ')}</td>`) || ''} ${ data[k] && typeof data[k] === 'object' ? `
<td>
${this.htmlStr(data[k], wrapperClassName, tableClassName)}
</td>` : `
<td>
<span>${data[k] || ''}</span>
</td>` }
</tr>` )}
</tbody>
</table>
</div>`;
};
}
in your code the snippet Object.keys(data).map(.....) converts it to array. Now when you put this array in string literal JavaScript will try to convert it to a string so it will call .toString() on it which joins all the elements of array using , by default.
instead do this Object.keys(data).map(....).join("") this will join array with empty string

How to run a method using v-for in Vue.js?

I want to get the following output for the following data.
・3
・1
and sample data :
export const dummyData = [
{
id: "1",
name: "a",
sub: [
{
id: "1#1",
name: "b",
sub_sub: [
{ id: "1#1#1", name: "b-a" },
{ id: "1#1#2", name: "b-b" },
]
},
{
id: "1#2",
name: "c",
sub_sub: [
{ id: "1#2#1", name: "c-a" },
]
},
]
},
{
id: "2",
name: "d",
sub: [
{
id: "2#1",
name: "e",
sub_sub: [
{ id: "1#2#1", name: "e-a" },
]
}
]
},
]
I want to count how many elements of sub_sub are includes in object "a" and "d".
So, I made the following code.
<template>
<div>
<ul>
<li v-for="item in items" :key="item.i">{{rowSpanCalc(item.id)}}</li>
</ul>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'nuxt-property-decorator'
import { dummyData } from '~/store/dummy'
#Component({})
export default class extends Vue {
items: any = []
created() {
this.items = dummyData
}
rowSpanCalc(item: any) {
const count = item.sub.reduce(
(total: any, curr: any) => total + curr.sub_sub.length,
0
)
return count;
}
}
</script>
I ran my code and got an error in console like
  
  item.sub.reduce is not a function
Could anyone please advise me how to fix this errors?
Methods in the template are used as events handler not for rendering, try to use that method inside a computed property then use that property for render your items :
#Component({})
export default class extends Vue {
items: any = []
created() {
this.items = dummyData
}
get customItems(){
return this.items.map(item=>({...item,count:this.rowSpanCalc(item.id)}))
}
rowSpanCalc(item: any) {
const count = item.sub.reduce(
(total: any, curr: any) => total + curr.sub_sub.length,
0
)
return count;
}
}
template :
...
<li v-for="item in customItems" :key="item.id">{{item.count}}</li>
...

Simply return a value from another component

Wondering if you guys can help. I am trying to create a generic component which when called, will return a value.
The code currently stands as follows:
import React, {Component} from 'react'
class Clients extends Component {
render () {
var userEnum = {
SMALL: 1,
MEDIUM: 2,
LARGE: 3,
properties: {
1: {name: "Admin", value: 1},
2: {name: "Manager", value: 2},
3: {name: "Standard", value: 3}
}
};
const clientName = (value) => {
return userEnum.properties[value].name
}
return null
}
}
export default Clients
and in another component, I try calling the clientName function (done an import too).
import ClientHelper from '../../helpers/clients'
...
const test = ClientHelper.clientName(2)
console.log(test)
I should expect a return value of 'Manager' but I get
TypeError: WEBPACK_IMPORTED_MODULE_9__helpers_clients.a.clientName
is not a function
You are declaring the function clientName inside the render method of the class Clients. This function is only accessible inside it's scope, the render method.
To access the function like you would, by calling the class Clients static method clientName, you should write it like this:
import React, { Component } from 'react'
class Clients extends Component {
static userEnum = {
SMALL: 1,
MEDIUM: 2,
LARGE: 3,
properties: {
1: { name: "Admin", value: 1 },
2: { name: "Manager", value: 2 },
3: { name: "Standard", value: 3 }
}
};
static clientName(value) {
return Clients.userEnum.properties[value].name;
}
render() {
return null;
}
}
export default Clients
If you do not intend to render anything with this class, you do not need react, and can simply create a utility/static class like below:
export default class Clients {
static userEnum = {
SMALL: 1,
MEDIUM: 2,
LARGE: 3,
properties: {
1: { name: "Admin", value: 1 },
2: { name: "Manager", value: 2 },
3: { name: "Standard", value: 3 }
}
};
static clientName(value) {
return Clients.userEnum.properties[value].name;
}
}
the function clientName is not a property of your class, but a local function inside the render function and therefore not accessible from the outside.
To solve this, you have to make clientName as well as your userEnum properties of the Clients object, for example in the constructor:
import React, {Component} from 'react'
class Clients extends Component {
constructor(props){
super(props);
this.userEnum = {
SMALL: 1,
MEDIUM: 2,
LARGE: 3,
properties: {
1: {name: "Admin", value: 1},
2: {name: "Manager", value: 2},
3: {name: "Standard", value: 3}
}
};
}
function clientName (value) {
return this.userEnum.properties[value].name
}
function render () {
return null
}
}
export default Clients

Multiple select Vue.js and computed property

I'm using Vue.js 2.0 and the Element UI library.
I want to use a multiple select to attribute some roles to my users.
The list of all roles available is received and assigned to availableRoles. Since it is an array of object and the v-model accepts only an array with value, I need to extract the id of the roles trough the computed property computedRoles.
The current roles of my user are received and assigned to userRoles: [{'id':1, 'name':'Admin'}, {'id':3, 'name':'User'}].
computedRoles is then equals to [1,3]
The preselection of the select is fine but I can't change anything (add or remove option from the select)
What is wrong and how to fix it?
http://jsfiddle.net/3ra1jscx/3/
<div id="app">
<template>
<el-select v-model="computedRoles" multiple placeholder="Select">
<el-option v-for="item in availableRoles" :label="item.name" :value="item.id">
</el-option>
</el-select>
</template>
</div>
var Main = {
data() {
return {
availableRoles: [{
id: 1,
name: 'Admin'
}, {
id: 2,
name: 'Power User'
}, {
id: 3,
name: 'User'
}],
userRoles: [{'id':1, 'name':'Admin'}, {'id':3, 'name':'User'}]
}
},
computed : {
computedRoles () {
return this.userRoles.map(role => role.id)
}
}
}
I agree mostly with #wostex answer, but he doesn't give you the userRoles property back. Essentially you should swap computedRoles and userRoles. userRoles becomes a computed property and computedRoles is a data property. In my update, I changed the name of computedRoles to selectedRoles.
var Main = {
data() {
return {
availableRoles: [{
id: 1,
name: 'Admin'
}, {
id: 2,
name: 'Power User'
}, {
id: 3,
name: 'User'
}],
selectedRoles:[1,2]
}
},
computed : {
userRoles(){
return this.availableRoles.reduce((selected, role) => {
if (this.selectedRoles.includes(role.id))
selected.push(role);
return selected;
}, [])
}
}
}
var Ctor = Vue.extend(Main)
new Ctor().$mount('#app')
And here is the fiddle.
Check the solution: jsfiddle
The caveat here is that computed properties are getters mainly. You can define setter for computed property, but my approach is more vue-like in my opinion.
In short, instead of v-model on computed set v-model for data property.
Full code:
<script src="//unpkg.com/vue/dist/vue.js"></script>
<script src="//unpkg.com/element-ui/lib/index.js"></script>
<div id="app">
<template>
<el-select v-model="ids" multiple placeholder="Select" #change="logit()">
<el-option v-for="item in availableRoles" :label="item.name" :value="item.id">
</el-option>
</el-select>
</template>
</div>
var Main = {
data() {
return {
availableRoles: [{
id: 1,
name: 'Admin'
}, {
id: 2,
name: 'Power User'
}, {
id: 3,
name: 'User'
}],
userRoles: [{'id':1, 'name':'Admin'}, {'id':3, 'name':'User'}],
ids: []
}
},
mounted() {
this.ids = this.userRoles.map(role => role.id);
},
methods: {
logit: function() {
console.log(this.ids);
}
}
}
var Ctor = Vue.extend(Main)
new Ctor().$mount('#app')

Categories

Resources