How to properly handle a prop change with a LitElement component? - polymer

My scenario:
I have a web component created with LitElement which has an object property.
In order to initialise it, i am waiting for the element to get created:
<script>
document.addEventListener("DOMContentLoaded", () => {
const instance = document.querySelector("#myInstance");
instance.config = {
data: [
{ firstName: "John", lastName: "Doe", age: 32 },
{ firstName: "Jane", lastName: "Boe", age: 30 },
]
};
});
</script>
<my-component id="myInstance"></my-component>
Let's say that i am going to change this data prop from time to time.
I would like to check the value of the data once it is changed and then decide what i should do next. In order to do that, this is the code of my web component:
#customElement("my-component")
export class MyComponent extends LitElement {
#property({ type: Object }) data: Object;
protected updated(_changedProperties: Map<PropertyKey, unknown>): void {
console.log(_changedProperties);
}
render() {
return html`
<div>blabla</div>
`;
}
}
The problem is that _changedProperties map has data but it is still undefined.
What am i missing here?

changedProperties is a Map of all the property names that changed... e.g. you can use it to check what changed - if you want to access the actual value you can use the property directly.
it look like this
if (_changedProperties.has('myProperty')) {
// react to a myProperty change
console.log('The new value of myProperty is', this.myProperty);
}

Related

How to set json file to object's array in Angular

how to set json file to my "Client" object's array?
I have json, which looks like this:
Clients.json
{
"clients": [
{
"firstName": "nameA",
"lastName": "lastA",
"doctorsName": "domm",
"procedure": "consultation",
"registrationDate": "new Date(2019, 10, 12)",
"isAlreadyServed": "false"
},
{
"firstName": "nameB",
"lastName": "lastB",
"doctorsName": "domm",
"procedure": "procedureA",
"registrationDate": "new Date(2019, 10, 12)",
"isAlreadyServed": "false"
},
{
"firstName": "nameC",
"lastName": "lastC",
"doctorsName": "doctorA",
"procedure": "procedureC",
"registrationDate": "new Date(2019, 10, 12)",
"isAlreadyServed": "false"
},
...
...
...
And how can I set it to object's array Client.service.ts
clients: Client[] = this.http.get('path.Clients.json') // ??
Since your data is in the client-side as a JSON file. Though you can use HttpClient, here is another solution for the same.
You can directly import the JSON as a constant and assign it to clients as of TS 2.9 (docs).
import Clients from 'path/to/client/json';
clients: ClientsJson = Clients;
where the ClientsJson interface would look like this.
export interface ClientsJson {
clients: Client[];
}
export interface Client {
firstName: string;
lastName: string;
doctorsName: string;
procedure: string;
registrationDate: Date;
isAlreadyServed: boolean;
}
Here is a working example on StackBlitz.
You first need to define an interface that matches your object structure:
public interface ClientConfiguration {
firstName: string;
lastName: string;
doctorsName: string;
// And so on...
}
Then, just like the code you have shown, you need to type the http.get method in order to obtain the correct output.
public getConfiguration(): Observable<Array<ClientConfiguration>> {
return this.http.get<Array<ClientConfiguration>>('path/to/client/json');
}
Since my getConfiguration() method returns an observable, you will need to subscribe to it in your components or services:
#Component({ ... })
export class MyComponent implements OnInit {
public ngOnInit(): void {
this.getConfiguration().subscribe(result: Array<ClientConfiguration> => {
this.clientConfiguration = result;
});
}
public getConfiguration(): Observable<Array<ClientConfiguration>> {
return this.http.get<Array<ClientConfiguration>>('path/to/client/json');
}
}
You can read more here about HttpClient at Angular's official documentation: https://angular.io/guide/http
Hope it helps.

Assign Correctly json Object to class

I have a class with an interface :
from another component in the code I read a json of my list of hero, and want to create a class for each hero (so it is easier to manipulate their data)
MY shop list CLASS =>
interface ShopItemInterface {
name: string;
image: string;
price: number;
description: string;
}
export class ShopItem implements ShopItemInterface {
public name: string;
public image: string;
public price: number;
public description: string;
constructor(obj: object) {
for (let key in obj) {
// this doesn't work and I don't know why, it is always false
if (this.hasOwnProperty(key)) {
this[key] = obj[key];
}
}
}
}
LOADER COMPONENT CLASS =>
ngOnInit() {
this.http.get(this.jsonShopLocationFile).subscribe(res => {
for (let i = 0; i < res['items'].length; i++) {
this.shopItems.push(new ShopItem(res['items'][i]));
}
console.log(this.shopItems[0].name);
});
}
I can't find a way to correctly bind the json data to an object without listing all the parameters manually. ( which would be a mess and with 0 reusability)
How would you achieve that correctly ? Should I create a class and then directly call a function like hero.FromJSON(jsonObj) to manually set all the property? can I do this in some way in the constructor ?
thank you !
Because when you are constructing the object it does not have those properties, they are going to be undefined, just remove the test and it will work. Remember that interfaces are a TypeScript construct for the compiler and that you are running JavaScipt in your browser.
for (let key in obj) {
this[key] = obj[key];
}

TypeError: Cannot read property 'File' of undefined

I want to upload file to my firebase storage and have the url stored in database. I followed an example that i found on this link. I have two different ts classes. I got an error in my pushFileToStorage function that said TypeError: Cannot read property 'file' of undefined when i hit the submit button. Can anyone help me solve this please?.
//these are from two different class files
//fileUpload.ts
export class FileUpload {
name: string;
Url: string;
File: File;
constructor(prdFile: File) {
this.prdFile = prdFile;
}
}
//product.ts
export class Product {
$prdKey: string;
prdName: string;
prdImage: string;
prdDescription: string;
}
//product.service.ts
basePath = '/Product';
pushFileToStorage(fileUpload: FileUpload, Product: Product, progress: {
percentage: number
}) {
const storageRef = firebase.storage().ref();
const uploadTask = storageRef.child(`${this.basePath}/${fileUpload.File.name}`).put(fileUpload.File);
uploadTask.on(firebase.storage.TaskEvent.STATE_CHANGED,
(snapshot) => {
// in progress
const snap = snapshot as firebase.storage.UploadTaskSnapshot
progress.percentage = Math.round((snap.bytesTransferred / snap.totalBytes) * 100)
},
(error) => {
// fail
console.log(error)
},
() => {
// success
this.productList.push({
prdName: Product.prdName,
prdImage: Product.prdImage = fileUpload.Url = uploadTask.snapshot.downloadURL,
prdDescription: Product.prdDescription,
})
this.saveFileData(fileUpload)
}
);
}
private saveFileData(fileUpload: FileUpload) {
this.firebase.list(`${this.basePath}/`).push(fileUpload);
}
//product.component.ts
currentFileUpload: FileUpload;
onSubmit(form: NgForm) {
this.ProductService.pushFileToStorage(this.currentFileUpload, this.ProductService.selectedProduct, this.progress);
}
When you do currentFileUpload: FileUpload; you are not providing any value for it, you are only providing the type for currentFileUpload. The value will still be undefined. To initialize set it to an instance:
currentFileUpload: FileUpload = fileUploadInstance; // Get the instance somehow

Arrays in Angular 4 components. Unrecognized input parameters

I have a problem with the array declaration and the string interpolation in Angular 4 with TypeScript.
If I create this classes:
export class MyArrayProperty {
property1: string;
property2: string;
}
export class MyComponent {
#Input() object: ComplexObject;
myArray: MyArrayProperty[];
}
The ComplexObject is an Object with a lot of property:
ComplexObject {
myNumber: number;
myString: string;
// etc..
}
If I tried to create an array of instances of MyArrayProperty inside the component MyComponent, in this way:
export class MyComponent {
#Input() object: ComplexObject;
myArray: MyArrayProperty[] = [{
property1: 'hello',
property2: this.object.myString
}];
}
The field property1 is displayed correctly on the HTML page with the string interpolation: {{myArrayInstance.property1}} (myArrayInstance is obtained by ngFor).
But the property2 does not appear with string interpolation {{myArrayInstance.property2}}, even though this.object.myString is actually a string and I received an input object.
How does this happen and how can I solve this problem?
First of all why you're using a class for MyArrayProperty? You're just using it as a type, so you should go for an interface.
export interface MyArrayProperty {
property1: string;
property2: string;
}
Second, your code will fail because your're trying to access the property myString of the member object on component creation. But object is undefined before ngOnInit. Therefore you're accessing a property of undefined.
To solve your problem you could use a getter which transforms the object member to be displayed in your template.
export class MyComponent {
#Input()
public object: ComplexObject;
public get myArray() {
return Object.keys(this.object || {}).map(key => {
return {
property1: 'hello',
property2: this.object[key]
} as MyArrayProperty;
})
}
}
The reason for your error is that, the moment you are assigning the this.object.myString, the object is not available.
you can use ngOnChanges() which is executed everytime the #input property changes which in your case is object being inject in you component.
You can do something like this,
ngOnChanges (changes: SimpleChanges) {
if(this.object) {
myArray = [{
property1: 'hello',
property2: this.object.myString
}];
}}
To use ngOnChanges() you need to implement OnChanges interface to your component.
More on OnChanges() here
and
Angular LifeCycle Hooks

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.