I have an imported library from npm and some parts of it need to be initialized before use, a simplified version of the code in the library:
export let locale = () => { throw new Error("Must init locale"); }
export initLocale(userLocaleFunction) {
locale = userLocaleFunction;
}
export checkLocale() {
console.log(locale());
}
But when calling the library in the following way:
lib = require("lib");
lib.initLocale(() => { return "en" });
lib.checkLocale(); // works as expected: "en"
lib.locale(); // Throws "Must init locale";
lib.locale acts as if it's not been initialized. I can't have initLocale() return the locale, I need it to be on the variable lib.locale
Is it possible to initialize a variable in this way?
It seems that when initializing a variable inside a library it's only in the libraries scope.
In my first solution I simply returned the value:
export initLocale(userLocaleFunction) {
locale = userLocaleFunction;
return locale;
}
But then realized that this creates a new problem: What if locale gets modified inside the library, or worse, outside of it?
In the spirit of avoiding 2 sources of truth I ended up going with this:
locale = undefined;
export initLocale(userLocaleFunction) {
locale = userLocaleFunction;
}
export getLocale() {
if (locale === undefined) {
throw new Error("Uninitialized locale");
}
return locale;
}
This code performs the is initialized check I needed at first and gives the value with one source of truth.
Related
I'm new to typescript to bear with me here if this is not how things are supposed to work.
I have a couple of goals in converting this js to ts.
Item = {}
Item.buy = function (id) {}
Item.sell = function (id) {}
I'm trying to get intellisense to autocomplete on Item. either buy or sell. I would also want to use dot notation to create these methods in arbitrary files without putting everything in the initial bracket. So I have something like this:
interface Item {}
const Item: Item = {};
interface Item {
buy?: Function
}
Item.buy = function () {
Item.render()
return "bought"
}
interface Item {
sell?: Function
}
Item.sell = function () {
Item.render()
return "sold"
}
interface Item {
render?: Function
}
Item.render = function () {
return 1
}
The problem here now is that render is an optional property and hence I get this error:
Cannot invoke an object which is possibly 'undefined'.
How can I make ts not check for this error? Since Item is not a class there's only ever going to be 1 item and it'll definitely have the render method, there is not ever going to be an instance where that error checking is useful. Or to put it another way, it's not actually optional, I only set it to be optional to work around const Item: Item = {}; erroring if I don't have it be optional.
Is there a way to let ts know that or use a different pattern in the first place?
SOLUTION 1:
Since you have not defined any method inside Item
interface Item {}
So you can check whether render method exist or not on Item as:
Item.buy = function () {
if(Item.render) Item.render(); // CHANGE
return "bought";
}
SOLUTION 2:
Best solution would be to add type of render on interface Item as:
interface Item {
render: () => void;
}
and then you can use it as:
Item.buy = function () {
Item.render();
return "bought";
}
My inclination here would be to use namespaces instead of an interface to hold these functions. It could look like this:
namespace Item {
export const buy = function () {
Item.render()
return "bought"
}
}
namespace Item {
export const sell = function () {
Item.render()
return "sold"
}
}
namespace Item {
export const render = function () {
return 1
}
}
Then you'd be able to access them the same way, as methods on the singleton Item value:
// elsewhere
console.log(Item.sell()); // "sold"
Note that namespace is a TypeScript specific feature, and nowadays new code is generally encouraged to use modules instead where possible. I don't really know if there's a good way to get this sort of behavior with modules, because the part we're using, merging different things into a common JS value, is not really how modules works. Maybe declaration merging and importing would give this to you, but I don't know.
Anyway, as long as you're okay with a TS-specific feature, then namespace would be an idiomatic way to represent this sort of gradual building of a singleton.
Playground link to code
I have an old IIFE that is injected into legacy pages via <script src.
However, I want to use all these old libraries in a react app. I just need to use the global function exposed.
I figure loading dependencies that will work both via script or via react's import or nodejs require
Here is an example of an example IIFE
example.js :
var $ = $;
var geocomplete = $.fn.geocomplete;
var OtherExternalLib = OtherExternalLib;
var Example = (function() {
return {
init: function () {
// stuff
}
}
)();
Where the legacy code is calling Example.init(), and likewise the react code will call the same function.
Where $ (jQuery), $.fn.geocomplete, and OtherExternalLib are all dependencies that must be loaded, either they should be loaded on-demand or just throw a big loud error message.
I suspect if the solution loads dynamically example.js would look something like
var $ = load("\libs\jquery.js");
var geocomplete = load("\libs\$.fn.geocomplete.js");
var OtherExternalLib = load("\libs\OtherExternalLib.js");
var Example = (function() {
return {
init: function () {
// stuff
}
}
)();
And the legacy application can still use <script src=example.js and React can use
import {Example} from example
Understandably this is somewhat a round-about way to of using legacy code in new applications, so I am open to other ideas on how best to expose an IIFE (with or without dependencies) and using it in React
I am using react+typescript in my project with some limitations which is why I had to dynamically import my package (my project runs in a shell project with AMD module, not having my own startup, and change the way project files get bundled).
Since I could only use the dependent modules on the fly during the run time, I had to assume them were valid while building and bundling . Most of them were IIFE.
So I used the lazy dynamic import .
something like this
import("somePolyfill");
This would be translated by TSC
new Promise(function (resolve_3, reject_3) { require(["arrayPolyfill"], resolve_3, reject_3); });
This would call the IIFE and execute the polyfills or initializing any window or global variable, so the rest of the code is aware of that.
If it returns a module or throughs error can be handled like normal promise then and catch.
So I created a wrapper
export class DepWrap {
public static Module: any = {};
public constructor() {
this.getPI();
this.getSomeModule();
}
public async getPI() {
DepWrap.Module["PI"] = 3.14;
}
public async getSomeModule() {
await import('somepath/somemodule').then(($package) => {
DepWrap.Module["somemodule"] = $package;
}).catch(() => {
window.console.log("Some Module error");
});
}
}
this got compiled to
define(["require", "exports", "tslib"], function (require, exports, tslib_1) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var DepWrap = /** #class */ (function () {
function DepWrap() {
this.getPI();
this.getSomeModule();
}
DepWrap.prototype.getPI = function () {
return tslib_1.__awaiter(this, void 0, void 0, function () {
return tslib_1.__generator(this, function (_a) {
DepWrap.Module["PI"] = 3.14;
return [2 /*return*/];
});
});
};
DepWrap.prototype.getSomeModule = function () {
return tslib_1.__awaiter(this, void 0, void 0, function () {
return tslib_1.__generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, new Promise(function (resolve_1, reject_1) { require(['somepath/somemodule'], resolve_1, reject_1); }).then(function ($package) {
DepWrap.Module["somemodule"] = $package;
}).catch(function () {
window.console.log("Some Module error");
})];
case 1:
_a.sent();
return [2 /*return*/];
}
});
});
};
DepWrap.Module = {};
return DepWrap;
}());
exports.DepWrap = DepWrap;
});
with this I could use all the dependency modules from my wrapper and every time i need to import a new one I would create another function to add that to my wrap module.
import { DepWrap } from "wrapper/path";
const obj = new DepWrap(); // initialize once in the beginning of project so it would import all the dependencies one by one .
Afterwards in all file, I can import my module from the wrapper
import { DepWrap } from "wrapper/path";
const { PI, somemodule} = DepWrap.Module;
I am not sure if the code will work for your scenario as well, but I guess tweaking it a bit might come in handy for your useCase .
Plus : if you are writing unit test case it will help jest to just ignore the modules and can create a mock for this so that you can test your actual code .
Hi team im have a very small question, i have this code:
constructor(private mapper: any, private applyCallback: (arg0: INode) => void) {
super()
/** #type {GanttMapper} */
this.mapper = new GanttMapper(datamodel);
/** #type {function(INode):void} */
this.applyCallback = applyCallback;
// create the dummy node
this.dummyNode = new SimpleNode();
// switch off the default template
//type RouterClass = typeof ;
//interface RouterDerived extends RouterClass { }
console.log("routerClass");
this.template = new IVisualTemplate({
createVisual() {
return null;
},
updateVisual() {
return null;
}
});}
but in the part:
this.template = new IVisualTemplate({
createVisual() {
return null;
},
updateVisual() {
return null;
}
});
im have an error and the error is this: Cannot create an instance of an abstrac class
in JS this code is working fine but im trying to migrate it in angular and is not working.
im read the other part of same issues but i cant solved it......
im try all tipes of replays but is not working.
Thanks all.
This is because Angular (or rather TypeScript) is more strict than JavaScript. There are things you can do in JavaScript that cannot properly be expressed in a TypeScript definition file, without weakening the type-checks.
This is one of those things. It will work at runtime, but the TypeScript compiler does not understand this feature and will warn you not to use that unrecognized construct.
It's a feature, unique to the yFiles class library and it's called quick interface implementation.
The documentation also states this:
Quick Interface Implementation in TypeScript
The TypeScript .d.ts file doesn’t contain typings that allow quick interface implementations. They still work at runtime, but the TypeScript compiler will produce an error nonetheless. To work around this issue, you can either tell the compiler to ignore the offending line, or use an anonymous implementation of the interface:
// #ts-ignore
const customHitTestable = new IHitTestable((context, location) => location.x > 25)
const customVisualCreator = new class extends BaseClass<IVisualCreator>(IVisualCreator) {
createVisual(context: IRenderContext): Visual {
return new SvgVisual(document.createElementNS("http://www.w3.org/2000/svg", "g"))
}
updateVisual(context: IRenderContext, oldVisual: Visual): Visual {
return oldVisual
}
}
In newer version of yFiles for HTML (since 2.3), there is now a convenient short-hand that works great with typescript:
const customHitTestable = IHitTestable.create((context, location) => location.x > 25)
This does not require the ts-ignore and works for all interfaces: If there is only one method in the interface, all you need to do is pass its implementation to the .create method on the interface object. If there are more members, they need to be passed in via an option object with the names of the members. The typescript definition file provides full completion for this usecase, now.
{
createVisual() {
return null;
},
updateVisual() {
return null;
}
}
This is not valid JavaScript, try
{
createVisual: () => {
return null;
},
updateVisual: () => {
return null;
}
}
here is the code of IVisualTempleate
I'm using the excellent JS i18n library lit-translate for a LitElement-project. If I understand correctly, get("foo.bar") is supposed to get the value of that key from a JSON file that contains the translations.
In my project, however, get("foo.bar") doesn't return the value of the key, but returns [foo.bar] instead. translate("foo.bar") works, but only inside a LitElement's HTML structure like <p>${translate("foo.bar")}</p>. Outside of such a structure, for example in the LitElement's constructor() part or simply outside of the LitElement class definition, translate("foo.bar") only returns part => { partCache.set(part, cb); updatePart(part, cb); }.
How can I simply get the value of a key through get()? I'm still a JavaScript novice and probably overlooking something, but what? Help is appreciated!
Here's my code (the relevant parts of it):
// app-shell.js
// … several module imports …
import {
registerTranslateConfig,
use,
get,
translate
} from "#appnest/lit-translate";
// … the LitElement class and other stuff …
// the lit-element contains the following code, which works like a charm:
// render() {
// return html`
// <p>${translate("header.title")}</p> // RETURNS REAL RESULT
// `;
// }
// registers AppShell as as web component named app-shell with the browser
customElements.define("app-shell", AppShell);
// registers the translation loader
registerTranslateConfig({
loader: lang =>
fetch(`../../_locales/${lang}/i18n.json`).then(res => res.json())
});
// sets the app's language
use(store.getState().app_shell.language); // store… returns "en", "de" or "fr"
// updates the application bar's title and the local storage entry
const title = get("header.title"); // RETURNS "[header.title]". WHY?
document.getElementById("title").innerText = title;
localStorage.setItem("title", title);
The problem has been identified (see comments to my question). Here's what was missing in my LitElement:
//
constructor() {
super();
// defers the first update of the component until the strings have been loaded
// to avoid empty strings being shown
this.hasLoadedStrings = false;
}
// overwrites default updating behavior
shouldUpdate(changedProperties) {
return this.hasLoadedStrings && super.shouldUpdate(changedProperties);
}
// loads the initial language and marks that the strings have been loaded
async connectedCallback() {
// sets the app's language;
await use(store.getState().app_shell.language);
this.hasLoadedStrings = true;
super.connectedCallback();
}
I have a node js script that invokes a Yeoman generator that I wrote and I would like to skip the prompting step since I'm passing the data for the generator from the script. I searched the documentation but I didn't find anything relevant for this. Is it possible at all?
My script looks like this
const yeoman = require('yeoman-environment');
const env = yeoman.createEnv();
env.lookup(() => {
env.run('mygenerator:subgenerator --moduleName Test3', {'skip-install': true, 'skip-prompting': true }, err => {
console.log('done');
});
});
And my generator has nothing special:
const BaseGenerator = require('./../base/index.js');
module.exports = class extends BaseGenerator {
constructor(args, opts) {
super(args, opts);
this.props = opts;
const destinationFolder = args.destinationFolder || '';
const moduleName = args.moduleName || '';
this.props = {
moduleName,
destinationFolder,
};
}
prompting() {
//...
}
writing() {
//...
}
};
I know that the generator gets the data I'm passing from the script. I potentially I could have a generator which deals with input and another one only for writing the files. But it'd be nice to have only one code and be able to skip some steps.
I saw in some stackoverflow answers that people pass the { 'skip-install': true } option to the generator. Then I tried to pass { 'skip-prompting': true }, but it doesn't do anything.
Thank you!
EDIT
The way that I solved this is the following:
All my sub generators extend a BaseGenerator that I wrote, which is the one that extends from Yeoman. In my BaseGenerator I added this method:
shouldPrompt() {
return typeof this.props.options === 'undefined' ||
(typeof this.props.options.moduleName === 'undefined' &&
typeof this.props.options.destinationFolder === 'undefined');
}
I only use 2 parameters in my generators, moduleName and destinationFolder. So, that's all I want to check. Then, in the sub generators I added this:
prompting() {
if (this.shouldPrompt()) {
this.log(chalk.red('Reducer generator'));
const prompts = [ /*...*/ ];
return this.prompt(prompts).then((props) => { this.props.options = props; });
}
}
You'll want to define options or arguments to accept these arguments from the terminal: http://yeoman.io/authoring/user-interactions.html
Then, just use JavaScript to run or not the this.prompt() call (with if/else structure or any other conditional that works for your use case)
Remember that Yeoman is still only JS code :)