I'm trying to directly bind an array to a Polymer 2 component template. It works at first but then it doesn't detect changes. I'm using this.push method in order to allow Polymer to detect the changes, but it's not working. Binding to a dom-repeat template it's OK.
Here you can find a Plunker: http://plnkr.co/edit/MWO7i7m3GB5b7Eqri1yX?p=preview
Is it possible to do what I'm trying? What am I doing wrong?
Thank you for your help
No it's not possible to bind to an array like this.
[[json(data)]] if data is an array, it won't work.
The same way for the observers :
static get observers(){
return [
'_dataChanged(data)'
]
}
This won't work.
On a direct binding, the only way for it to work is to change the complete array value with another one.
class ParentElement extends Polymer.Element {
static get is() { return "parent-element"; }
static get properties(){
return {
data: {
type: Array,
notify: true,
value: function(){
return [{name: 'first'}]
}
}
}
}
constructor() {
super();
}
json(data){
return JSON.stringify(data);
}
addChild(){
this.data = [{name: 'first'},{name: 'second'}];
}
}
customElements.define(ParentElement.is, ParentElement);
In this case above, you can see that the data array is completely replaced and it will work because it's the data object that changed.
In your case you change the content of the array, then the binding won't work.
If you want to see something, in you plunker you can change the HTML part with :
[[json(data.*)]]
Then you will see the polymer binding object that changed.
But instead of binding an array like you did, the best is to use an observer and do our action in the function :
static get observers(){
return [
'_dataChanged(data.splices)'
]
}
_dataChanged(value){
//work to do here
}
For more details you can check the polymer doc here
Related
I am getting this error trying to bind my control to its data. Here is some relevant code.
Template.
<tree-control [nodes]="getData"></tree-control>
Component.
public getData(): Observable<Array<any>> {
const assets: any = this.service.get('url', headers);
return assets;
}
Anything I have found so far is not helping. Any idea what's wrong with my code?
Thanks
First of all, you assign a function (getData) to the nodes property. I assume you want to assign the data from getData to it instead.
Secondly, the call to this.service.get is probably not being executed. Reason for that is that you do not subscribe to, what I assume, is a http-call that returns an Observable.
To fix this, you can do the following:
export class Foo {
nodeData: Observable<any>;
constructor(
private readonly service: YourService,
) {
this.nodeData = this._getData();
}
private _getData() {
return this.service.get(...);
}
}
Inside your template you can then subscribe and unsubscribe to the data automatically by using the async pipe.
<tree-control [nodes]="nodeData | async"></tree-control>
For all that to work I assume your service.get method returns an Observable.
Update: Filed a bug at https://github.com/Polymer/lit-element/issues/411
I'm having trouble implementing the default and custom converter for properties given the information in the guide. With the current version (0.6.5), it says that you can assign the type Array to a property and it will automatically be parsed as JSON from the string value of the attribute, but that doesn't seem to be the case as described in the code sample.
Here's an illustration of the problem. In the console, it should report an array with three elements, yet it returns an array of one element, the string that contains the attribute value. The result of the render method also shows just one <p> element containing the single string.
<script type="module">
import { LitElement, html } from 'https://unpkg.com/#polymer/lit-element#0.6.5/lit-element.js?module';
class Histogram extends LitElement {
static get properties() {
return {
values: { type: Array }
};
}
constructor() {
super();
this.values = [];
}
render() {
console.log(Array.isArray(this.values), this.values);
return html`
<div>
The elements:
${this.values.map(item => html`<p>item: ${item}</p>`)}
</div>
`;
}
}
customElements.define('x-histogram', Histogram);
</script>
<x-histogram values="[1,2,3]"/>
I've also tried modifying the example by providing a converter, but that doesn't seem to get invoked, either. What am I doing wrong?
I had a look. The 0.6.5 updating-element.js file doesn't even have converting for array. And looking at the npm package of 0.6.5 it doesn't mention an array either.
But the master branch does map the array type, and it has it mentioned in the readme. It should work for you if you pull it and use it directly.
How should I parse this using lifecycle methods?
{"blocks":[{
"key":"33du7",
"text":"Hello there!",
"type":"unstyled",
"depth":0,
"inlineStyleRanges":[],
"entityRanges":[],
"data":{}}],
"entityMap":{}
}
I want to render the text in my component but I don't know why it throws undefined error. How should I call it?
This is my component:
class Blog extends Component{
constructor(props){
super(props);
this.blogContent = props.blogContent;
this.blogId = props.blogId;
this.handleRemoveBlog = this.handleRemoveBlog.bind(this);
this.state = {
blog__: '',
};
}
handleRemoveBlog(blogId){
this.props.removeBlog(blogId);
}
This is my lifecycle method , I would use this.setState but first of all it's giving undefined in console.
componentWillMount(){
this.state.blog__ = JSON.parse(this.blogContent);
console.log(this.state.blog__.text); // this gives undefined
}
This is the render part..
The data is coming from Firebase.
And {this.blogcontent} gives that json string that I previously mentioned.
render(props) {
return(
<div className = "blog header">
<p>{this.blog__.text}</p>
</div>
);
}
}
Blog.proptypes = {
blogContent: Proptypes.string
}
This would mostly depend on where you are getting this object from. If it is fetched over the network then the best place to pass it is in the componentDidMount. The reason for this is that the alternative lifecyle method (componentWillMount) does not guarantee a re-render of your component since it does not wait for async actions to finish execution before passing control down to your render method. Hence componentDidMount is best because as soon as new props are received or state is changed it will trigger a re-render. However, if this object is pulled from within the application then chances are, it will work just fine even if pulled within componentWillMount. This is because that operation would be much quicker, so much that control would be passed down to the render method with the new props. This is not guaranteed especially if you want to set state in the process (setting state is also async, so control might execute the rest of the code before all the required data is received).
In short, pass this to componentDidMount and in your render function, before accessing this prop, make sure that it exists. That is, instead of
render() {
return <div>{this.props.theObject.blocks[0].key}</div>
}
rather do:
render() {
return <div>{this.props.theObject && this.props.theObject.blocks[0].key}</div>
}
This is how you would do it (assuming you are getting the file over the network using axios)
componentDidMount() {
axios.get('url/to/the/file')
.then(fileData => this.setState({
data: fileData
});
}
render() {
// return whatever you want here and setting the inner html to what the state holds
}
You should not modify the state using
this.state.blog__ = JSON.parse(this.blogContent);
The proper way to do it is using the this.setState() method:
this.setState({blog__: JSON.parse(this.blogContent)})
Then, to ensure that the component will be re-rendered, use the method shouldComponentUpdate():
shouldComponentUpdate(nextProps,nextState) {
if(nextState != this.state) {
this.forceUpdate()
}
}
Take a look at the State and Lifecycle docs.
Other point: Use componentDidMount() instead of componentWillMount(), because it will get deprecated in the future.
Atention: The setState() is an asynchronous method. So, it won't instant update your state.
Use this.setState({}) in your componentWillMount function instead assign the data to the variable. Also I recommend to use componentDidMount instead of componentWillMount because it's getting deprecated in the future.
componentDidMount(){
let text = JSON.parse( this.blogContent );
this.setState({blog__: text });
}
Edit: Only use setState in componentDidMount according to #brandNew comment
I am working in Ionic3.
I have a function which calls another function from provider.ts and it returns an interface Object.
page.ts
getList(){
this.localdata = this.provider.getGlobalData();
}
provider.ts
getGlobalData(){
return this.gvData;
}
Now, any changes made to localdata are also changing gvData in provider.ts.
I don't want to copy the reference, just the value. How can I do it?
The following snippet will return a cloned version of your object.
provider.ts
getGlobalData(){
return JSON.parse(JSON.stringify(this.gvData));
}
You could also do it this way:
page.ts
getList(){
this.localdata = JSON.parse(this.provider.getGlobalData());
}
provider.ts
getGlobalData(){
return JSON.stringify(this.gvData);
}
Another idea is to use spread operator:
getGlobalData() {
return { ... this.gvData};
}
You can just do something like a Object.assing({},this.provider.getGlobalData()) to get a new copy of the object.
I seem to be struggling with what should be a simple issue regarding setting State. I'm learning React so I did this across several steps. Initially I setup my components so they are setting fields using props (e.g. this.propsoverview[0].ProfileImg) which was handed down State from Overview Pane, which was initially set using a this.SetState call in componentWillMount where data was pulled from a static file.
Next I worked on adding a function to pull the data in a more dynamic way (i.e. getPatient function). I'm calling this function from the componentWillMount and I am able to do a console.log to output the JSON as a string (using JSON.stringify). So I know the JSON is being returned.
Where I'm struggling is setting the State in componnetWillMount using the returned JSON from my getPatient call. I get the error Uncaught (in prmise) Type Error: Cannot read property 'setState' of undefined on the 4th line of my code (i.e. this.setState...)
Below is my code...
componentWillMount() {
getPatient().then(function(result) {
//console.log(JSON.stringify(result));
this.setState({PATIENT: result})
})
function getPatient() {
const urlGetPatient = 'url_that_gets_patient_here';
return fetch(urlGetPatient).then(function(response) {
return response.json();
}).then(function(json) {
return json;
});
}
render() {
return (
<App>
…
<OverviewPane overview={this.state.PATIENT} />
…
</App>
}
class OverviewPane extends React.Component {
constructor(props) {
super(props);
autoBind(this);
}
render () {
return (
<table>
<tbody>
<tr>
<td><Image src={this.props.overview[0].ProfileImg}/></td>
</tr>
</tbody>
</table>
);
}
}
Any help would really be appreciated.
You're getting cannot call setState of undefined because you're using a regular function as a callback instead of an arrow function (() => {}). When using a regular function, the this parameter (context) is set to the calling function, and not what you might think (the so-called lexical this).
Change your .then callback to an arrow function, and you should be good:
componentWillMount() {
getPatient().then((result) => {
this.setState({PATIENT: result});
})
}
The this in your code is bound within the promise, you need to bind this in order to get reacts this scope.
getPatient().then(function(result) {
//console.log(JSON.stringify(result));
this.setState({PATIENT: result})
}.bind(this))