Why does Webcomponent object value is shared between the different instances of the same webcomponent? - polymer

I created a web component called TestElement with Polymer as below. It simply has an object value and a function which adds values to this component.
<dom-module id="test-element">
<template>Test Element</template>
<script>
class TestElement extends Polymer.Element {
static get is() { return "test-element"; }
static get properties() {
return {
_myVar: {
type: Object,
value: {},
},
};
}
addNew(key, val) {
this._myVar[key] = val;
}
}
customElements.define(TestElement.is, TestElement);
</script>
</dom-module>
I created two instances of this in a parent element as follows:
class ParentElement extends Polymer.Element {
static get is() { return "parent-element"; }
ready() {
super.ready();
this.$.myElement.addNew('key1', 'val-1');
this.$.myElement2.addNew('key2', 'val2');
console.log(this.$.myElement._myVar); // Expected to have {'key1': 'val-1'} but got {'key1': 'val-1', 'key2': 'val2'}. Why?
}
}
In the console log above, I expect to have {'key1': 'val-1'} but got {'key1': 'val-1', 'key2': 'val2'}. Why?
If you want to have a working example please refer to this plunkr:
http://plnkr.co/edit/7jSwTTBbBxD4qCbUoCGI?p=preview

JavaScript objects are by reference and when you define the default value it's a single object.
static get properties() {
return {
_myVar: {
type: Object,
value: {},
},
};
}
To avoid this you can define a function that will return a new object for each component instance.
static get properties() {
return {
_myVar: {
type: Object,
value: function() { return {}; },
},
};
}

While thinking through it I found a solution.
Test Element
class TestElement extends Polymer.Element {
static get is() { return "test-element"; }
static get properties() {
return {
_myVar: {
type: Object,
value: {},
},
};
}
ready() {
super();
_myVar = {}; // if initialized like this, the objects are not shared anymore.
}
addNew(key, val) {
this._myVar[key] = val;
}
}
customElements.define(TestElement.is, TestElement);
So below, logs what I expected:
class ParentElement extends Polymer.Element {
static get is() { return "parent-element"; }
ready() {
super.ready();
this.$.myElement.addNew('key1', 'val-1');
this.$.myElement2.addNew('key2', 'val2');
// Expected to have {'key1': 'val-1'} and got {'key1': 'val-1'} check! :)
console.log(this.$.myElement._myVar);
}
}

Related

how to bind the values using object in polymer 3.x?

I'm trying to bind the values using an object reference like this:
this.set('state', tab1Data);
but values are not binding with respective input fields.
We are using the polymer 3.x version
It depends very much on where you bind your data and how you bind it.
As you provide not so much information on the concrete context, I would guess, that you bind your data, outside the element ths calls this.set('state', ...) .
Then your property needs to have the notify-flag been set to true, otherwise changes won't be propagated outside.
The following code shows this behavior:
<html>
<head>
<script src="https://unpkg.com/#webcomponents/webcomponentsjs#latest/webcomponents-loader.js"></script>
</head>
<body>
<script type="module">
import {PolymerElement, html} from 'https://unpkg.com/#polymer/polymer/polymer-element.js?module';
import 'https://unpkg.com/#polymer/paper-button/paper-button.js?module';
class MyElement extends PolymerElement {
static get properties() {
return {
someData: {
type: Object,
value: () => {return {}},
notify: true
},
otherData: {
type: Object,
value: () => {return {}},
notify: false
},
counter: Number
}
}
constructor() {
super();
this.counter = 0;
}
static get template() {
return html`
<style> :host { display: block; } .mood { color: green; } </style>
Mood: <span class="mood">[[someData.mood]] ([[otherData.mood]])</span>
<paper-button raised on-click="setNewObject">set object</paper-button>
<paper-button raised on-click="setNewValue">set value</paper-button>
`;
}
setNewObject() {
this.counter++
const mood = `good [${this.counter}]`
this.set('someData', {mood: mood});
this.set('otherData', {mood: mood});
}
setNewValue() {
this.counter++
const mood = `better [${this.counter}]`
this.set('someData.mood', mood);
this.set('otherData.mood', mood);
}
}
customElements.define('my-element', MyElement);
class MySecondElement extends PolymerElement {
static get properties() {
return {
mood: String
}
}
static get template() {
return html`Web Components are <span>[[mood]]</span>!`;
}
}
customElements.define('my-second-element', MySecondElement);
class MyApp extends PolymerElement {
static get template() {
return html`
<my-element some-data="{{someData}}" other-data="{{otherData}}" ></my-element>
<h2>notify true</h2>
<my-second-element mood="[[someData.mood]]"></my-second-element>
<h2>notify false</h2>
<my-second-element mood="[[otherData.mood]]"></my-second-element>
`;
}
}
customElements.define('my-app', MyApp);
</script>
<my-app></my-app>
</body>
</html>
You should put the code on the value which set on the localstorage. I show it here.
import '#polymer/iron-localstorage/iron-localstorage.js';
....
<iron-localstorage name="user-info" value="{{userInfo}}" on-iron-localstorage-load-empty="initializeUserInfo"></iron-localstorage>
<vaadin-text-field id="test">
........
initializeUserInfo(){
this.set('userInfo', []);
}
ready(){
super.ready();
this.set('userInfo', this.$.test.value);
}
This is able to set data in the localstorage.

Render Object using lit.html

Scenario :
Inside my webComponent - health-check.js file I have:
static get properties() {
return {
plan:Object,
};
}
<div class="body"><p class="title">${this.plan.title}</p></div>
and
Inside my index.html file I pass json like below:
<health-check plan='{ "title" : "Solution Architecure",
"status" : "Approved" }'></health-check>
but it doesn't render title inside my health-check component
Problem :
Render Object Value with using lit.html?
Render title inside my health-check component.
Any ideas?
I think you cannot pass the object from html file because it's just html not lit-element or lit-html and lit-html seems not try to parse value in this case (Update I just found another way to define property see below). But of course you can still pass string and parse it inside element.
In index.html
<health-check planString='{
"title": "Solution Architecure",
"status": "Approved"
}'>
</health-check>
In health-check.js
class HealthCheck extends LitElement {
static get properties () {
return {
planString: String
}
}
render () {
return html`
<div class='body'>
<p class='title'>${JSON.parse(this.planString).title}</p>
</div>
`
}
}
But I would recommend to wrap you code in single entrypoint like my-app element
In index.html
<my-app></my-app>
In app.js
class App extends LitElement {
render () {
return html`
<health-check .plan='${{
title: 'Solution Architecure',
status: 'Approved'
}}'>
</health-check>
`
}
}
In health-check.js
class HealthCheck extends LitElement {
static get properties () {
return {
plan: Object
}
}
constructor () {
super()
this.plan = {}
}
render () {
return html`
<div class='body'>
<p class='title'>${this.plan.title}</p>
</div>
`
}
}
Update
You can define property type to tell lit-element how to serialize and deserialize.
class HealthCheck extends LitElement {
static get properties () {
return {
plan: {
type: {
fromAttribute (value) {
return JSON.parse(value)
}
}
}
}
}
render () {
return html`
<div class='body'>
<p class='title'>${this.plan.title}</p>
</div>
`
}
}
Note: This code written in lit-element 0.6.x and lit-html 0.11.x

Need to navigate to next element in an array in Polymer on button click

I have an array of objects that contain emp info like name age etc and I have a page with previous and next buttons. I want to be able to navigate to the object in the array after clicking on the next button. Do I need use something like redux to achieve this or is there another way to do this. Is there a way to do this in polymer directly.
<dom-module id="my-quiz">
<template>
<div>Employee List</div>
<button on-click="prev">Previous</button>
<button on-click="next">Next</button>
</template>
<script>
class Quiz extends Polymer.Element {
static get is() { return 'my-quiz'; }
// set this element's employees property
constructor() {
super();
this.employees = [
{ name: 'Bob', id: 'b1' },
{ name: 'Ayesha', id: 'b2' },
{ name: 'Fatma', id: 'b3' },
{ name: 'Tony', id: 'b5' }
];
}
prev() {
console.log("prev");
}
next() {
console.log("next");
}
}
window.customElements.define(Quiz.is, Quiz);
</script>
</dom-module>
Here an example how to do this. There may other ways too. :
<div>Employee ID: [[_calculateId(index)]] Name: [[_calculateName(index)]]</div>
<button on-click="prev">Previous</button>
<button on-click="next">Next</button>on>
</template>
<script>
class MyTest extends Polymer.Element {
static get is() { return 'my-test'; }
static get properties() { return {
index:{
type:Number,
value:0
}
}}
// set this element's employees property
constructor() {
super();
this.employees = [
{ name: 'Bob', id: 'b1' },
{ name: 'Ayesha', id: 'b2' },
{ name: 'Fatma', id: 'b3' },
{ name: 'Tony', id: 'b5' }
];
}
_calculateName(i) { return this.employees[i].name }
_calculateId (i) { return this.employees[i].id }
prev() {
console.log("prev");
if (this.index>0) { this.index -= 1 }
}
next() {
console.log("next");
if (this.index < this.employees.length-1){this.index +=1}
}
}
customElements.define(MyTest.is, MyTest);
Demo

Polymer paper-input imperative key-bindings

I'm trying to bind the enter key on a paper-input by specifying:
<paper-input class="flex" key-bindings="[[keys]]"></paper-input>
and the following properties on the host element:
keys: {
type: Object,
value: function() {
return {
'enter': this._addVariant
};
}.bind(this)
}
also tried this version:
keys: {
type: Object,
value: function() {
return {
'enter': '_addVariant'
};
}.bind(this)
}
Both do not work and I can't find any references on google. Does somebody know what I'm missing? Maybe the keyEventTarget?
Maybe this is a different way then you look for. But it works, what is the pressed key is:
<paper-input value="{{vall::input}}" on-keydown="_keyEvets" label="Arıyorum">
</template>
<script>
class MyTest extends Polymer.Element {
static get is() { return 'test-component'; }
static get observers() {return ['checkVall(vall)']}
checkVall(v){
console.log('vall :'+ v);
}
_keyEvets(k) {
console.log('_keyEvets',k.code);
}
}
Here is link to a sample

Does core Polymer 2.0 support two-way binding?

I seem to be having trouble getting two-way binding to work with Polymer 2.0. I'm keeping things minimal, only using Polymer.Element.
I define a parent component:
<dom-module id="example1a-component">
<template>
<xtal-fetch req-url="generated.json" result="{{myFetchResult}}"></xtal-fetch>
<div>fetch result:
<span>{{myFetchResult}}</span>
</div>
</template>
<script>
class Example1a extends Polymer.Element{
static get is(){return 'example1a-component'}
static get properties(){
return {
myFetchResult :{
type: String
}
}
}
}
customElements.define(Example1a.is, Example1a)
</script>
</dom-module>
The fetch class looks like:
class XtalFetch extends Polymer.Element {
static get is() { return 'xtal-fetch'; }
static get properties() {
return {
debounceTimeInMs: {
type: Number
},
reqInfo: {
type: Object,
},
reqInit: {
type: Object
},
reqUrl: {
type: String,
observer: 'loadNewUrl'
},
/**
* The expression for where to place the result.
*/
result: {
type: String,
notify: true,
readOnly: true
},
};
}
loadNewUrl() {
debugger;
}
ready() {
if (this.reqUrl) {
const _this = this;
fetch(this.reqUrl).then(resp => {
resp.text().then(txt => {
_this['_setResult'](txt);
_this['result'] = txt;
_this.notifyPath('result');
});
});
}
}
}
elements.XtalFetch = XtalFetch;
customElements.define(xtal.elements.XtalFetch.is, xtal.elements.XtalFetch);
Note that I am trying everything I can think of:
_this['_setResult'](txt);
_this['result'] = txt;
_this.notifyPath('result');
I see the result of the the fetch going into the result property of the fetch element, not into the parent.
Am I doing something wrong?
Yes, it does. Make sure to call super when you're overriding a method:
ready() {
super.ready(); // <-- important!
...
}
That's enough to make your code work (demo).
This is easy to forget, so the polymer-linter was recently updated to warn users about this.