sveltejs - static properties on components - javascript

I have a scenario where I need to provide information to a component class such that downstream instantiations can make use of that info.
For instance:
import { AComponent } from 'AComponent.svelte'
...
AComponent.classInfo = {something: somedata}
And then, the component could access that information as in:
<script>
let something = AComponent.classInfo.something
</script>
There seemed to be some effort in providing this kind of functionality in V2 (I'm using 3) that was discussed in these issues: Support Component Static Method #480, which resulted in Added setup function which can supply static methods/properties. #572.
However, scanning the current docs reveals no such setup method. So, did this survive from V2 to 3 & if not, is there some way to do this?

You can define static properties that are not instance specific in the module script block
<script context="module">
export const someValue = 123
</script>
<script>
// Normal component stuff
</script>
and then import it directly from the component file:
import { someValue } from './MyComponent.svelte'
Note that this is a value shared among all instances of this component.

At least in version v3.32, it's not possible to define static properties in a Svelte Component. Only named exports are possible.
The only workaround I known is using a custom webpack loader/rollup plugin, and the implementation is never pretty.

Related

What is the role and usage of the setup function provided by vue3's Composition API

I need to know the correct usage and the best practice of the setup function provided by vue3's Composition API.
I checked in my current project where developers actually use the setup function instead of creating the component with the traditional approach.
If it is just a design principle or improvement something then where we should apply these. I read the official documentation but instead, they didn't explain the concept, they just provided the list of arguments available in this function.
MyBook.vue
<template>
<span>Warning:- {{warning}}</span>
<button #click="warning = !warning">toggle</button>
</template>
<script>
import { ref } from 'vue'
export default {
props: ['warning'],
setup(props, context) {
const warning = ref(props.warning)
return {
warning,
}
},
}
</script>
<MyBook
:warning="true"
/>
As you can see above, I can't use the same name of a property to data attribute for a component but in the case of setup, we can do this and update the value. (as property should not change within component).
The Vue devtool is also showing the setup as a different category.
setup sets up an instance and returns properties that it should have. The purpose of Composition API, which setup is a part of, is to replace Options API, where an instance is determined by component options. So setup is the replacement for data, methods, computed, watch and lifecycle hooks.
As the reference explains, setup also replaces beforeCreate and created lifecycle hooks, the rest of hooks are set inside of it.
There is no conflict between data and props in setup function because props is accessible as setup parameter, i.e. warning and props.warning are accessible at the same time. In a template, they aren't and shouldn't be distinguished, they instance properties, the solution is to not allow name conflicts. They have been previously available with $data.warning and $props.warning magic keywords but their use wasn't encouraged. If warning value differs from a prop of the same name, and both should be available in a template, it should have a different name.

create my own global variable in nuxt.js (with vuetify)

I'm developing an app with nuxt and I'm tired of having to write an if statement in pages using this.$Vuetify.breakpoint.name == 'xs' for responsiveness every time. So I would like to create my own variable and call this long variable.here is my code↓
(mynuxtapp/plugins/createCustomVar.js)
import Vue from "vue";
Vue.prototype.$bp = Vue.prototype.$vuetify.breakpoint.name;
console.log(Vue.prototype.$bp);
I have already set nuxtconfig to run the plugin.
But it returns an error:
TypeError: Cannot read property'breakpoint' of undefined.
Apparently I can't access vuetify using "$", even though I can do it in pages.
What should I do? and Are there any easier ways or best practices?
Thank you for reading the question at the end!
The $vuetify property exists on the Nuxt context, not the Vue prototype.
Each module under plugins/ should return a function that receives the Nuxt context as the first argument. That context contains the $vuetify object setup by the #nuxtjs/vuetify plugin.
The second argument of the Plugin API is inject, which allows you to inject globals into the Nuxt context.
So your plugin should look similar to this:
// ~/plugins/myGlobals.js
export default ({ $vuetify }, inject) => {
// inject makes `$bp` available in context (the `$` prefix is automatically prefixed)
inject('bp', $vuetify.breakpoint.name)
}
demo
How about to start with you simply log out the context object and see what you have. I think you'll probably find that the Vuetify object is lying somewhere else.
export default (context, inject) => {
console.log('context:', context)
}
Docs for setting plugins can be found here

Write Vue plugin with custom options

I'm trying to write a vue plugin with custom options. I followed the official vue guidelines (https://v2.vuejs.org/v2/guide/plugins.html) on doing so but can't find a way to define custom options. These options should be read by normal javascript which then exports an object that is used by a vue component.
My folder structure is like this:
/src
factory.js
CustomComponent.vue
factory.js
import Vue from "vue";
import ImgixClient from "imgix-core-js";
var imgixClient = new ImgixClient({
domain: CUSTOM_OPTION_URL <-- important bit
domain: Vue.prototype.$imgixBaseUrl //tried it like this
});
export { imgixClient };
I already tried to set this custom bit by utilizing Vue.prototype in the install method like this but can't seem to get it working
export function install(Vue, options) {
if (install.installed) return;
install.installed = true;
Vue.prototype.$imgixBaseUrl = options.baseUrl;
Vue.component("CustomComponent", component);
}
I'm afraid this isn't going to be the simple answer you might have been hoping for... there's a lot to unpick here.
Let's start with factory.js. That is not a factory. It's a singleton. Singletons have problems around dependencies, configuration and the timing of instantiation and that's precisely the problem you're hitting. More on that later.
Now let's take a look at the plugin. First up, these two lines:
if (install.installed) return;
install.installed = true;
That shouldn't be necessary. Vue already does this automatically and should ensure your plugin is only installed once. Perhaps this came from an old tutorial? Take a look at the source code for Vue.use, there's not a lot to it:
https://github.com/vuejs/vue/blob/4821149b8bbd4650b1d9c9c3cfbb539ac1e24589/src/core/global-api/use.js
Digging into the Vue source code is a really good habit to get into. Sometimes it will melt your mind but there are some bits, like this, that aren't particularly difficult to follow. Once you get used to it even the more opaque sections start to become a little clearer.
Back to the the plugin.
Vue.prototype.$imgixBaseUrl = options.baseUrl;
It is not clear why you are adding this to the prototype.
I'm going to assume you are already familiar with how JavaScript function prototypes work.
Component instances are actually instances of Vue. So any properties added to Vue.prototype will be inherited by your components with almost no overhead. Consider the following simple component:
<template>
<div #click="onClick">
{{ $imgixBaseUrl }}
</div>
</template>
<script>
export default {
methods: {
onClick () {
const url = this.$imgixBaseUrl
// ...
}
}
}
</script>
As $imgixBaseUrl is an inherited property it is available within onClick via this.$imgixBaseUrl. Further, templates resolve identifiers as properties of the current Vue instance, so {{ $imgixBaseUrl }} will also access this.$imgixBaseUrl.
However, if you don't need to access $imgixBaseUrl within a component then there is no need to put it on the Vue prototype. You might as well just dump it directly on Vue:
Vue.imgixBaseUrl = options.baseUrl;
In the code above I've ditched the $ as there's no longer a risk of colliding with component instance properties, which is what motivates the $ when using the prototype.
So, back to the core problem.
As I've already mentioned, singletons have major problems around creation timing and configuration. Vue has its own built-in solution for these 'do it once at the start' scenarios. That's what plugins are. However, the key feature is that plugins don't do anything until you call install, allowing you to control the timing.
The problem with your original code is that the contents of factory.js will run as soon as the file is imported. That will be before your plugin is installed, so Vue.prototype.$imgixBaseUrl won't have been set yet. The ImgixClient instance will be created immediately. It won't wait until something tries to use it. When Vue.prototype.$imgixBaseUrl is subsequently set to the correct value that won't have any effect, it's too late.
One way (though not necessarily the best way) to fix this would be to lazily instantiate ImgixClient. That might look something like this:
import Vue from "vue";
import ImgixClient from "imgix-core-js";
var imgixClient = null;
export function getClient () {
if (!imgixClient) {
imgixClient = new ImgixClient({
domain: Vue.prototype.$imgixBaseUrl
});
}
return imgixClient;
}
So long as nothing calls getClient() before the plugin is installed this should work. However, that's a big condition. It'd be easy to make the mistake of calling it too soon. Besides the temporal coupling that this creates there's also the much more direct coupling created by sharing the configuration via Vue. While the idea of having the ImgixClient instantiation code in its own little file makes perfect sense it only really stands up to scrutiny if it is independent of Vue.
Instead I'd probably just move the instantiation to within the plugin, something like this:
import ImgixClient from "imgix-core-js";
export default {
install (Vue, options) {
Vue.imgixClient = Vue.prototype.$imgixClient = new ImgixClient({
domain: options.baseUrl
});
Vue.component("CustomComponent", component);
}
}
I've made a few superficial changes, using a default export and wrapping the function in an object, but you can ignore those if you prefer the way you had it in the original code.
If the client is needed within a component it can be accessed via the property $imgixClient, inherited from the prototype. For any other code that needs access to the client it can either be passed from the component or (more likely) grabbed directly from Vue.imgixClient. If either of these use cases doesn't apply then you can remove the relevant section of the plugin.

Scope of an imported variable in ionic v3 [duplicate]

I have a constants file constants.ts:
export const C0NST = "constant";
I access it in a service some.service.ts like so:
import { C0NST } from './constants';
console.log(C0NST); // "constant"
However, when I access it in a component template:
some.component.ts:
import { C0NST } from './constants';
some.component.html:
{{ C0NST }} <!-- Outputs nothing -->
However defining a member in the component class works:
some.component.ts
public const constant = C0NST;
some.component.html
{{ constant }} <!-- constant -->
I don't understand why I was able to access the imported constant directly in the service class but not in the component template even though I imported it in the component class.
In Angular2, the template can only access fields and methods of the component class. Everything else is off-limits. This includes things which are visible to the component class.
The way to go around this is to have a field inside the component, which just references the constant, and use that instead.
It's one limitation of the design, but perhaps you should think a bit more about why you need a constant in the template in the first place. Usually these things are used by components themselves, or services, but not the template.
Since in the Component's template you can only use attributes of the Component's class, you can't directly use any external constants (or external variables).
The most elegant way that I've found so far is the following:
import { MY_CONSTANT } from '../constants';
#Component({
// ...
})
export class MyTestComponent implements OnInit {
readonly MY_CONSTANT = MY_CONSTANT;
// ...
}
which basically just creates a new attribute MY_CONSTANT inside the component class. Using readonly we make sure that the new attribute cannot be modified.
Doing so, in your template you can now use:
{{ MY_CONSTANT }}
The scope of Angular2 template bindings is the component instance. Only what's accessible there can be used in bindings.
You can make it available like
class MyComponent {
myConst = CONST;
}
{{myConst}}
There are two best directions in my opinion:
Wrapping constants as internal component property
enum.ts
export enum stateEnum {
'DOING' = 0,
'DONE',
'FAILED'
}
component.ts
import { stateEnum } from './enum'
export class EnumUserClass {
readonly stateEnum : typeof stateEnum = stateEnum ;
}
Example uses enum, but this can be any type of defined constant. typeof operator gives you all of benefits of TypeScript typing features. You can use then this variable directly in templates:
component.html
<p>{{stateEnum.DOING}}<p>
This solution is less efficient in memory usage context, because you are basically duplicating data (or references to constants) in each component you wish to use it. Beside that, syntax
readonly constData: typeof constData = constData
in my opinion introduce a lot of syntax noise and may be confusing to newcommers
Wrapping external constant in component function
Second option is to wrap your external variable/constant with component function and use that function on template:
enum.ts
export enum stateEnum {
'DOING' = 0,
'DONE',
'FAILED'
}
component.ts
import { stateEnum } from './enum'
export class EnumUserClass {
getEnumString(idx) {
return stateEnum[stateEnum[idx]];
}
}
component.html
<p>{{getEnumString(1)}}</p>
Good thing is that data is not duplicated in controller but other major downside occur. According to Angular team, usage of functions in templates is not recommended due to change detection mechanism, which works way less efficient in case of functions returning values to templates: change detection have no idea does value return by a function has changed, so it will be called way often than needed (and assuming you returning const from it, it's actually needed only once, when populating template view. It may be just a bit efficiency killing to your application (if you are lucky) or it may totally break it down if function resolves with Observable for instance, and you use async pipe to subscribe to results. You can refer to my short article on that HERE
You can create a BaseComponent , it is a place where you should create your constant instances and then you can create your FooComponent extends BaseComponent and you can use your constants.

how to make global variable and functions which can be accessible in all the components in angular 4

I am struggling with global variables as I want some variables which I need to access in all the components so what should i do in angular 4.
I've tried something like this:
Created one file with the name of global.ts and made a class with the name GlobalComponent something like this
export class GlobalComponent {
globalVar:string = "My Global Value";
}
and am using it on HeaderComponent by importing and making instance and it's working fine but this is very long process to import in each and every files to get it available.
So I want it to be available on all the components without importing it.
Is there any way or trick to achieve this? Any suggestion would be appreciated :)
As #bgraham is suggesting, it is definitely better to let angular injector to instantiate the service.
But you could also export just a simple object with key-value pairs, including functions. Then you can simply import it and use it (without the instantiation part).
globals.ts file:
export const GLOBALS = {
globalVar: 'My Global Value',
globalFunc: () => alert('123')
};
your.component.ts file:
import { GLOBALS } from './globals.ts';
console.log(GLOBALS.globalVar);
GLOBALS.globalFunc();
Btw I don't think there is a way how to get rid of the import statement - that is part of how typescript works...
I don't think what you want to do is possible and it's probably not a good idea.
The import statements are how webpack (or other bundlers) are able to build a tree to figure out which files to include. So it might be tricky to get global files built into all your bundles.
Also I would add, I'm not sure just importing the static file is the way to go either. It's kind of quick and dirty, which maybe is what you want I guess, but for production apps I would recommend making an angular service and injecting it.
export class GlobalVariablesService {
globalVar:string = "My Global Value";
}
This way you can mock these for unit tests or potentially pass in different variables depending on your changing needs in the future.
If you need these to update and push that into lots of components throughout your app, you might look into Redux. Its pretty handy for that kind of thing.
Sorry, perhaps not the answer you were hoping for
Simply create constant file - constant.ts under src folder.
Now import constant.ts file whenever you require parameter to be called
It will look like so
export const constant = {
COSNT_STRING : 'My Global Value',
}
how to use constant:
1) Import file.
2) constant.CONST_STRING
Also this is a good practice from future prospective, if you want to modify response just made change in one file not in 800 files.

Categories

Resources