I am working on a MVC project with Vue.js(Vue3) front-end.
For some reason when I am trying to use any directive that contains camel-case arguments, like in the example below, they are automatically turned to all lower-case, rendering my directive completely ineffective.
<label for="Name" class="required" v-init:camelCaseAttribute="'variableValue"> labelValue </label>
This also happens when I define said atributes with the use of a dictionary:
#Html.TextBoxFor(m => m.Registration.Section03.Address.ZipCode, htmlAttributes: new Dictionary<string, object> { { "v-init:camelCaseAttribute", "'variableValue'" } })
I know this is a HTML thing, but are there any known work-arounds for this?
Should have provided more context to the question:
const myComponent = { // my component
data: function () {
return {
camelCaseProperty: '' // property I am trying to initialise with some value
}
},
directives: {
init: { // custom init directive
mounted(el, binding, vnode) {
console.log(binding.arg)
binding.instance[binding.arg] = binding.value;
}
}
}
};
"init" is indeed not a built in Vue directive, so as I am trying to migrate the project from angularjs, which has an init directive, I tried to come up with something that offers a similar functionality to some extent, as seen in the snippet.
Writing the directive argument in kebab-case (v-init:camel-case-property="something") has no effect, as logging the binding.arg that actually reaches the directive is still in kebab-case.
This can happen; it is therefore recommended in to use kebab-case for all html attributes in Vue, including props.
HTML attribute names are case-insensitive, so browsers will interpret any uppercase characters as lowercase
source
Related
It is 'illegal' to use new when creating an instance of Template, where Template extends an HTMLTemplateElement.
To overcome this limitation, I get and return an HTMLTemplateElement using document.getElementById(id) from the Template constructor as below:
export class Template {
private htmlTemplateElement: HTMLTemplateElement;
constructor(id: string) {
this.htmlTemplateElement = document.getElementById(id) as HTMLTemplateElement;
return Object.assign(this.htmlTemplateElement, this)
}
public test = () => this.htmlTemplateElement.innerHTML
}
Providing an HTML Template Element exist in the DOM,
I can create a new instance of Template and use the extension method test() as illustrated below:
const template = new Template(id)
console.log(template.test())
console.log(template.innerHTML)
Both console.log() works just fine and prints the correct text to the console.
HOWEVER, the typescript compiler complains about template.innerHTML.
The error I get, saying innerHTML does not exist on type Template
Question: How can I add type information so I do not get a compiler error?
I have tried to use export class Template extends HTMLTemplateElement.
That does not work since it is illegal to create an instance using new.
I love typescript, but sometimes the type checking gets in my way.
Help me out here, please.
Although not ideal, I was able to keep the typescript compiler happy by implementing the following interface:
export interface ITemplate {
[key:string]: any;
test(): string;
}
and then using the interface:
export Template implements ITemplate {
...
}
Note:
Why did I not use customElements.define(<tag-name>,Template)?
I do not intend to create a new custom instance of HTMLTemplateElement, I just want to return an existing HTMLTemplateElement with additional utility extension methods.
Also, It might very well be possible that my approach is completely wrong.
However, that is a different topic than the question asked here.
Problem
It's pretty common, that an Angular project wants to use some web-components, (aka. custom html tags). Of course, the compiler won't recognize these custom html tags and will complain:
1. If 'si-radio' is an Angular component, then verify that it is part of this module. 2. If 'si-radio' is a Web Component then add 'CUSTOM_ELEMENTS_SCHEMA' to the '#NgModule.schemas' of this component to suppress this message.
If you put 'CUSTOM_ELEMENTS_SCHEMA' into play, your templates won't be validated anymore in that module and your code become extremely error-prone.
Solution (?)
It would be much reasonable, if you could define the tags and properties, which you really want to use, and they could pass the validation process.
As far as I know, Angular is using the ElementSchemaRegistry. If you use a web-component-library, where all the custom tags are prefixed like <custom-component-.. you could write a CustomSchemaRegistry like this:
export class CustomSchemaRegistry extends DomElementSchemaRegistry {
constructor() {
super();
}
hasElement(tagName: string, schemaMetas: SchemaMetadata[]): boolean {
const elementExists = tagName.includes('custom-component-') || super.hasElement(tagName, schemaMetas);
console.log(tagName, elementExists);
return elementExists;
}
hasProperty(tagName: string, propName: string, schemaMetas: SchemaMetadata[]): boolean {
return tagName.includes('custom-component-') || super.hasProperty(tagName, propName, schemaMetas);
}
}
And at bootstrapping you could provide it as a compilerOption:
platformBrowserDynamic()
.bootstrapModule(AppModule, {
providers: [{ provide: ElementSchemaRegistry, useValue: new CustomElementSchemaRegistry() }],
})
The problem is, it just doesn't work. And I don't even see any recommendation in the official Angular documentation for such cases. Looks like such a use-case wasn't foreseen, although it's extremely common.
#NgModule.schemas is an array of SchemaMetadata, which seems to be a pretty useless interface with a single name: string property...
https://angular.io/api/core/SchemaMetadata
Summing it up, is it actually possible to add a custom schema to Angular, allowing the usage of custom tags, while still preserving the template validation?
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.
Say we have:
class MyClass {
myProperty: string
}
Is there any built in function or easy way to get JSON like this?:
{
"myProperty": "string"
}
EDIT: My end goal is I want to dynamically print typed class definitions to a web view, in some kind of structured object syntax like JSON. I'm trying to make a server API that will return the schema for various custom classes - for example http://myserver.com/MyClass should return MyClass's properties and their types as a JSON string or other structured representation.
Evert is correct, however a workaround can look like this
class MyClass {
myProperty: string = 'string'
}
JSON.stringify(new MyClass) // shows what you want
In other words, setting a default property value lets TS compile properties to JS
If the above solution is not acceptable, then I would suggest you parsing TS files with your classes with https://dsherret.github.io/ts-simple-ast/.
Typescript class properties exist at build-time only. They are removed from your source after compiling to .js. As such, there is no run-time way to get to the class properties.
Your code snippet compiles to:
var MyClass = /** #class */ (function () {
function MyClass() {
}
return MyClass;
}());
As you can see, the property disappeared.
Based on your update, I had this exact problem. This is how I solved it.
My JSON-based API uses json-schema across the board for type validation, and also exposes these schemas for clients to re-use.
I used an npm package to automatically convert json-schema to Typescript.
This works brilliantly.
I found the following example in the Function.name documentation
const o = {
foo(){}
};
o.foo.name; // "foo";
The problem in typescript (typed here):
const o: { foo: () => void } = {
foo: () => {
}
};
o.foo.name;
comes when I want to retrieve
o.foo.name, where I will get an error
TS2339 (property "name" does not exist)
How can I deal with it, keeping the object typing?
I want to avoid having to cast the property "foo" like (<any>o.foo).name
PS: The use case is to keep the typing for further refactoring. For instance the following is safe to be refactored:
spyOn(o, (<any>o.foo).name)
While this one is not
spyOn(o, "foo")
PS 2: It seems retrieving function name could be problematic on ts: Get name of function in typescript
The problem is that this code only works for newer versions of Javascript. If you change the target on the typescript compiler settings to es2015 the problem goes away. If you target es5 the definitions for that version do not include the name property because it might not work on older Javascript runtimes.
If you are ok with targeting es2015, that is ok, if not you should come up with a different solution that works for es5.
If you are targeting an environment that supports this property but you don't yet trust the es2015 implementation for all features, you could just add the the Function interface the missing property. At the top level in one of your files you can redefine the Function interface, and this will be merged into the default definition, adding the extra property:
interface Function {
/**
* Returns the name of the function. Function names are read-only and can not be changed.
*/
readonly name: string;
}
Post ES2015, this:
const o: { foo: () => void } = {
foo: () => { }
};
console.log(o.foo.name);
should work just fine.
Check it in the Typescript Playground, and observe the produced JavaScript. You will see the common sections with the foo example you mentioned.
Here is the console, nice and clean:
Pre-ES2015, this wouldn't work and I think you would have to cast it, if targeting post-ES2015 is not an option.