I'm struggling to get #Method in stenciljs working - any help would be appreciated.
Here's my component code with a function called setName that I want to expose on my component:
import { Component, Prop, Method, State } from "#stencil/core";
#Component({
tag: "my-name",
shadow: true
})
export class MyComponent {
#Prop() first: string;
#Prop() last: string;
#State() dummy: string;
#Method() setName(first: string, last: string): void {
this.first = first;
this.last = last;
this.dummy = first + last;
}
render(): JSX.Element {
return (
<div>
Hello, World! I'm {this.first} {this.last}
</div>
);
}
}
Here's the html and script that references the component:
<!DOCTYPE html>
<html dir="ltr" lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=5.0">
<title>Stencil Component Starter</title>
<script src="/build/mycomponent.js"></script>
</head>
<body>
<my-name />
<script>
var myName = document.querySelector("my-name");
myName.setName('Bob', 'Smith');
</script>
</body>
</html>
Here's a screen shot of the error I'm getting which is Uncaught TypeError: myName.setName is not a function:
Methods are not immediately available on a component; they have to be loaded/hydrated by Stencil before you can use them.
Components have a componentOnReady function that resolve when the component is ready to be used. So something like:
var myName = document.querySelector("my-name");
myName.componentOnReady().then(() => {
myName.setName('Bob', 'Smith');
});
Just posting another answer because this has since changed, with Stencil One.
All #Method decorated methods are now immediately available on the component, but they are required to be async, so that you can immediately call them (and they resolve once the component is ready). The use of componentOnReady for this is now obsolete.
However, you should make sure that the component is already defined in the custom element registry, using the whenDefined method of the custom element registry.
<script>
(async () => {
await customElements.whenDefined('my-name');
// the component is registered now, so its methods are immediately available
const myComp = document.querySelector('my-name');
if (myComp) {
await myComp.setName('Bob', 'Smith');
}
})();
</script>
Here you should not use #Method , it is not a best practice. We should always minimize the usage of #Method. This helps us to scale the app easily.
Instead pass data through #Prop and #Watch for it.
Ok , in your case , Please add async before the method name
Related
so im trying to create a react webcomponent. I wrap it and on VSCode looks fine, but when I'm trying to load it, it gives me the error: Unexpected token '<' on the line:
ReactDOM.render(<Counter/>, mountPoint);
Does anyone know why and how to fix it? thanks
This is my WebComponent:
import React from 'react';
import * as ReactDOM from 'react-dom';
import Counter from './counter';
class CounterWC extends HTMLElement {
connectedCallback() {
// Create a ShadowDOM
const root = this.attachShadow({ mode: 'open' });
// Create a mount element
const mountPoint = document.createElement('div');
root.appendChild(mountPoint);
// You can directly use shadow root as a mount point
ReactDOM.render(<Counter/>, mountPoint);
}
}
customElements.define('counter-wc', CounterWC)
And this is my html:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-9" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<h1>React webcomponent:</h1>
<counter-wc></counter-wc>
<script type="module" src="./counterWC.js"></script>
</body>
</html>
Re: comments
The fact that you name your file *.js doesn't mean it is JavaScript
ReactDOM.render(<Counter/>, mountPoint); is JSX, not JavaScript, it needs to be converted in a Build step to JavaScript.
Or do not use React at all:
class CounterWC extends HTMLElement {
constructor(){
super()
.attachShadow({mode: 'open'})
.append(this.div = document.createElement('div'));
}
connectedCallback() {
this.div.innerHTML = `Am I a counter?`;
}
}
customElements.define('counter-wc', CounterWC);
<counter-wc></counter-wc>
I'm trying to create custom component using Stencil with input. My intention is to make component with input. After change input value It should emit It to my Vue instance and console log this event (later it will update value in Vue instance). But after change input value in Stencil nothing happen.
Learning how Stencil components works I used:
https://medium.com/#cindyliuyn/create-a-stencil-form-input-component-for-angular-and-vue-js-22cb1c4fdec3
Trying to solve problem I tried also:
https://medium.com/sharenowtech/using-stenciljs-with-vue-a076244790e5
HTML and Vue code:
<!DOCTYPE html>
<html dir="ltr" lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=5.0" />
<title>Stencil Component Starter</title>
<script src="https://unpkg.com/vue"></script>
<script type="module" src="/build/vox-wc-research.esm.js"></script>
<script nomodule src="/build/vox-wc-research.js"></script>
</head>
<body>
<div id="app">
<test-component :placeholder="placeholder" :label="label" :value="value" #valueChange="e => onValueChange"></test-component>
{{value}}
</div>
</body>
<script>
var app = new Vue({
el: '#app',
data: {
label: 'Nazwa Użytkownika',
value: '',
placeholder: 'Wpisz nazwę użytkownika',
},
methods: {
onValueChange(e) {
console.log(e);
},
},
});
</script>
</html>
Stencil Component:
import { h, Component, Element, Event, EventEmitter, Prop /* PropDidChange */ } from '#stencil/core';
#Component({
tag: 'test-component',
styleUrl: 'test-component.css',
//shadow: true,
})
export class FormInputBase {
#Element() el: HTMLElement;
#Prop() type: string = 'text';
#Prop() label: string;
#Prop() placeholder: string;
#Prop({ mutable: true }) value: string;
#Event() valueChange: EventEmitter;
handleChange(event) {
const val = event.target.value;
console.log(val);
this.value = val;
this.valueChange.emit(val);
}
render() {
return (
<div>
<label>
{this.label}
<div>
<input placeholder={this.placeholder} value={this.value} onInput={event => this.handleChange(event)}></input>
{this.value}
</div>
</label>
</div>
);
}
}
Vue doesn't support camel-case event names because all v-on: event listeners are converted to lower-case (see https://v2.vuejs.org/v2/guide/components-custom-events.html#Event-Names).
However when you load your component(s), you can use the options of Stencil's defineCustomElements to "transform" all your event names:
import { applyPolyfills, defineCustomElements } from 'my-component/loader';
applyPolyFills().then(() => {
defineCustomElements({
ce: (eventName, opts) => new CustomEvent(eventName.toLowerCase(), opts)
});
});
For a more full-blown example have a look at Ionic Framework's source:
https://github.com/ionic-team/ionic-framework/blob/b064fdebef14018b77242b791914d5bb10863d39/packages/vue/src/ionic-vue.ts
I have an array of nationalities in and a function that returns the name of nationality from the CountryCode. Both of them are in a file called nationality.js.
Here is the function
map_code_to_nationality = (code) => {
return nationalities.filter((data) => {
return data.CountryCode == code
})[0].Nationality
}
Now I want to export the function and the list of nationality (i.e. an array of nationalities). I tried to export both of them like this.
export const map_code_to_nationality
export const nationalities
Now if I use function keyword for map_code_to_nationality in the export statement the editor shows a syntax error and the export statements stated above gives an error in the browser that
Attempted import error: 'map_code_to_nationality' is not exported from
'../../static_data/nationality_list'.
I have imported it in other file like this
import { map_code_to_nationality, nationalities } from '../../static_data/nationality_list'
How do I use both the function and array by exporting them?
You should export them in an object, like so:
export const obj = {
map_code_to_nationality,
nationalities
}
And then use it in your import like so
import { obj } from '../../static_data/nationality_list';
obj.map_code_to_nationality
obj.nationalities
Alternatively, if you want somewhat more elegant syntax (with default export):
export default {
map_code_to_nationality,
nationalities
}
And then in the driver code, use the following:
import obj from '../../static_data/nationality_list';
obj.map_code_to_nationality
obj.nationalities
you can do :
export {map_code_to_nationality, nationalities};
and then use them in other modules, like :
import { map_code_to_nationality, nationalities } from '../../static_data/nationality_list';
//In module.js add below code
export function multiply() {
return 2 * 3;
}
// Consume the module in calc.js
import { multiply } from './modules.js';
const result = multiply();
console.log(`Result: ${result}`);
// Module.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Module</title>
</head>
<body>
<script type="module" src="./calc.js"></script>
</body>
</html>
Its a design pattern same code can be found below, please use a live server to test it else you will get CORS error
https://github.com/rohan12patil/JSDesignPatterns/tree/master/Structural%20Patterns/module
Here you can also export an array as well, but remember to import it from the module
I am using global script declaration inside index.html
<!DOCTYPE html>
<html>
<head>
<script src='https://js.espago.com/espago-1.1.js' type='text/javascript'></script>
...
</head>
<body>
...
</body>
Now I want to use it inside the component.
import * as React from "react";
import * as $ from "jquery";
//how to import Espago?
export default class EspagoPayment extends React.Component<any, any> {
componentDidMount() {
$("#espago_form").submit(function(event){
var espago = new Espago({public_key: 'xxx', custom: true, live: false, api_version: '3'});
espago.create_token({
...
});
});
}
render() {
return (
...
);
}
}
Webpack gives an error on build.
error TS2304: Cannot find name 'Espago'
How to get Espago visible inside the component?
Maybe there is other way to link to online js resource?
You have to tell TypeScript that it's defined somewhere else.
declare var Espago: any;
See https://stackoverflow.com/a/13252853/227299
I am trying to setup my first angular2 application as an experiment and am using the latest beta release.
I am facing a weird issue where the variable i am using in my view is not being updated after setting a timeout.
#Component({
selector: "my-app",
bindings: []
})
#View({
templateUrl: "templates/main.component.html",
styleUrls: ['styles/out/components/main.component.css']
})
export class MainComponent {
public test2 = "initial text";
constructor() {
setTimeout(() => {
this.test2 = "updated text";
}, 500);
}
}
As you can see i have a variable named test2 and in the constructor i set a timeout of 500 ms where i am updating the value to "updated text".
Then in my view main.component.html i simply use:
{{ test2 }}
But the value will never be set to "updated text" and stays on "initial text" forever even though the update part is being hit. If i follow the angular2 tutorial they dont really give me an answer to this solution. Was wondering if anyone would have an idea of what i am missing here.
edit: my full code i am using including the bootstrap and html etc
<html>
<head>
<title>Angular 2</title>
<script src="/node_modules/systemjs/dist/system.src.js"></script>
<script src="/node_modules/reflect-metadata/reflect.js"></script>
<script src="/node_modules/angular2/bundles/angular2.dev.js"></script>
<script src="/node_modules/q/q.js"></script>
<script src="/node_modules/jquery/dist/jquery.js"></script>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="/bower_components/breeze-client/breeze.debug.js"></script>
<script src="/bower_components/datajs/datajs.js"></script>
<script src="/bower_components/bootstrap-less/js/collapse.js"></script>
<script src="/bower_components/bootstrap-less/js/modal.js"></script>
<script src="/bower_components/signalr/jquery.signalR.js"></script>
<script src="http://localhost:64371/signalr/js"></script>
<link href="styles/out/main.css" type="text/css" rel="stylesheet" />
<script>
System.config({
map: {
rxjs: '/node_modules/rxjs' // added this map section
},
packages: {'scripts/out': {defaultExtension: 'js'}, 'rxjs': {defaultExtension: 'js'}}
});
System.import('scripts/out/main');
</script>
</head>
<body>
<my-app>loading...</my-app>
</body>
</html>
main.ts with the bootstrap:
import {Component} from 'angular2/core';
import {bootstrap} from 'angular2/platform/browser'
import {COMMON_DIRECTIVES} from './constants';
import {MainComponent} from './components/main.component'
bootstrap(MainComponent);
main-component.html
{{ test2 }}
As Vlado said, it should work ;-)
I think that the angular2-polyfills.js library should be included into your page. I can't see it. This file is essentially a mashup of zone.js and reflect-metadata. Zones take part of the detection of updates.
You could have a look at this video where Bryan Ford explains what it is: https://www.youtube.com/watch?v=3IqtmUscE_U.
Hope it helps you,
Thierry
That should work. Do you have any other errors in console?
#Component({
selector: 'my-app',
template: `<h1>Hello {{title}}</h1>`
})
export class App {
public title: string = "World";
constructor() {
setTimeout(() => {
this.title = "Brave New World"
}, 1000);)
}
}
Look at this Plunker:
http://plnkr.co/edit/XaL4GoqFd9aisOYIhuXq?p=preview
I had a very similar problem to the OP where even in a basic Angular2 setup changes to bound properties would not be reflected by the view automatically. At this point in time we're using Angular2 2.0.0-rc.6.
There was no error message.
In the end I found the culprit to be a reference to es6-promise.js, which was 'required' by a third party component we use. Somehow this interfered with the core-js reference we are using which is suggested with rc6 in some of the Angular2 tutorials.
As soon as I got rid of the es6-promise.js reference, the view updated correctly after changing a property on my component (via Promise or timeout).
Hope this helps somebody some day.
In Angular2 (~2.1.2) another way to make it work is through the ChangeDetectorRef class. The original question code would look like this:
import {
ChangeDetectorRef
// ... other imports here
} from '#angular/core';
#Component({
selector: "my-app",
bindings: []
})
#View({
templateUrl: "templates/main.component.html",
styleUrls: ['styles/out/components/main.component.css']
})
export class MainComponent {
public test2 = "initial text";
constructor(private cd: ChangeDetectorRef) {
setTimeout(() => {
this.test2 = "updated text";
// as stated by the angular team: the following is required, otherwise the view will not be updated
this.cd.markForCheck();
}, 500);
}
}