how to write common function in lit element - javascript

I have some web components created using lit elements. And Those includes some api requests to send and fetch data from another site and I am hoping to send some data through api request header. So I am hoping to create one common function to contain those header details incase if i need to change them in the future, i won't need to edit those component one by one.
I need a solution like below :
common_function.js
function api_request(url) {
// content
}
my_component.js
import '/common_function.js';
...
constructor(){
api_request('http://apiRequestUrl');
}
Please let me know a way to achieve this using lit element.
Thanks in advance.

How to create a common function which you can import and reuse has nothing to do with lit.
So when you have a common function in a file named 'common.js':
export default txt => {
console.log('Hello from a common function!', txt);
}
You can use it in another javascript file, including lit components, like this:
import commonfunction from '/path/to/your/common.js';
commonfunction('Wow!');
What may be a problem (but which is not your question) is that your browser does not import lit with the lit import you may have specified... because it may be a npm package on your server. Even when you serve the node_modules folder and you specify the exact path and filename, your lit import may include other other imports that brake because of how they are specified as resolved by node.
Therefore you may have to use something like RollupJS which can distribute your app resources with proper imports. See https://rollupjs.org/guide/en/
Hope this helps.

Ok.. I found a solution. But I don't know whether this is the perfect answer for this.
We can use lit Reactive Controllers to do the job.
Here is the example How i did it.
common_function.js
import {initialState, Task} from '#lit-labs/task';
import * as SETTINGS from "../../bmw_settings";
export class ApiRequestController {
host;
url;
id;
task;
data = '';
_bmw_send_api_request() {
this.task = new Task(
this.host,
async ([data]) => {
const response = await fetch(
`${SETTINGS.BASE_URL + this.url}`,
{
headers: {
"Content-Type": "application/json",
'Access-Control-Request-Headers': 'Api-key, Content-Type',
'Api-key': '123'
}
}
);
const result = await response.json();
const error = result.error;
if (error !== undefined) {
throw new Error(error);
}
return result;
},
() => [this.data]
);
}
constructor(host, url, id) {
this.host = host;
this.url = url;
this.id = id;
this._bmw_send_api_request();
}
set data(value) {
this.data = value;
this.host.requestUpdate();
}
get data() {
return this.data;
}
render(renderFunctions) {
return this.task.render(renderFunctions);
}
}
my_component.js
import { LitElement, html} from 'lit';
import {ApiRequestController} from '../../common_functions';
class search_bar extends LitElement {
static properties = {
provider_type : String,
reasons : Array,
}
constructor() {
super();
this._getAllReasons();
}
async _getAllReasons(){
this.reasons = await new ApiRequestController(this, '/api/v1/get-reasons', 'search_bar');
}
render() {
return html `
${this.reasons.render({
complete: (data) => html `
<p>Reasons List</p>
<select>
<option>Select Reason</option>
${data.data.map((val) =>
html `<option value="${val.id}">${val.reason}</option>`
)}
</select>
`,
})}
`;
}
}
customElements.define('search-bar', search_bar)
use this documentation if you need more details.
Thank you

Related

Load different classes and execute similar function name

I have a problem that I can't resolve.
Let's say we have some classes in a directory named services.
Each of theses classes contain a contructor() and send() method.
We can have differents classes such as Discord, Slack, SMS, etc.
Their goal is just to sent notification through external service.
I think I have to use an interface or an abstract class which contain contructor() and send() method but how can I instanciate every class and call send() in an elegantly way ?
My project structure :
services/
-> discord.js
-> slack.js
-> [...]
index.js
Regards.
I think what you are looking for is a kind of manager where you have a single send() function that chooses a specific service based on a parameter. Something like this:
services/index.js
import SlackService from 'slack.js';
import DiscordService from 'discord.js';
export const TYPES = {
SLACK: 'slack',
DISCORD: 'discord',
};
export class ServiceManager {
services;
constructor() {
this.services = {
[TYPES.DISCORD]: new DiscordService(/* discordConfig */),
[TYPES.SLACK]: new SlackService(/* slackConfig */),
};
}
send(type, data) {
return this.services[type].send(data);
}
}
index.js
import ServiceManager from 'services/index.js';
const serviceManager = new ServiceManager();
serviceManager.send(ServiceManager.TYPES.SLACK, { message: 'Sent to Slack' });
serviceManager.send(ServiceManager.TYPES.DISCORD, { message: 'Sent to Discord' });
Dynamically loading services from files
You can use require-dir to import all files from a directory and then map over those to create each service. The individual service files have to be written in a defined syntax for the manager to use them. Something like this:
services/slack.js (as example for all service files):
export const name = 'slack';
export class Service {
constructor() {
// Set up connection to slack
}
send() {
// Send something to slack
}
}
services/index.js
const requireDir = require('require-dir');
export class ServiceManager {
services;
constructor() {
const serviceObjects = requireDir('.');
this.services = Object.values(serviceObjects).reduce(
(services, { name, Service }) => {
services[name] = new Service();
return services;
}
)
}
getRegisteredServices() {
return Object.keys(this.services);
}
send(name, data) {
return this.services[name].send(data);
}
sendAll(data) {
Object.values(this.services).each(service => service.send(data));
}
}
index.js (stays pretty much the same)
import ServiceManager from 'ServiceManager.js';
const serviceManager = new ServiceManager();
console.log('Registered services are: ', serviceManager.getRegisteredServices());
serviceManager.send('slack', { message: 'Sent to Slack' });
serviceManager.send('discord', { message: 'Sent to Discord' });

Modifying Angular Contentful API response

I have a contentful service like so..
import { Injectable } from '#angular/core';
import { createClient, Entry } from 'contentful';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
const CONFIG = {
space: '<spaceid>',
accessToken: '<accesstoken>',
contentTypeIds: {
programItems: 'programItem'
}
};
#Injectable()
export class ContentfulService {
private cdaClient = createClient({
space: CONFIG.space,
accessToken: CONFIG.accessToken
});
public weekNumber = new BehaviorSubject<any>(1);
constructor() { }
// Get all the program items
getProgramItems(query?: object): Promise<Entry<any>[]> {
return this.cdaClient.getEntries(Object.assign({
content_type: CONFIG.contentTypeIds.programItems
}, query))
.then(res => res.items);
}
}
but I only want to bring in the programItems sys.ids in the contentful documentation.. you can modify api calls and return only certain values like this modify api calls
https://cdn.contentful.com/spaces/<space_id>/entries/
?select=fields.productName,fields.price
&content_type=<content_type_id>
but Im not sure how I would implement the same thing, the way they do angular calls.. I could just do a http request but I would prefer to keep it the same way as I have done above
any help would be appreciated
You add a select property to your getEntries call.
// Get all the program items
getProgramItems(query?: object): Promise<Entry<any>[]> {
return this.cdaClient.getEntries(Object.assign({
content_type: CONFIG.contentTypeIds.programItems,
select: 'sys.id'
}, query))
.then(res => res.items);
}
You can read the full documentation, including javascript snippets, here: https://www.contentful.com/developers/docs/references/content-delivery-api/#/reference/search-parameters/select-operator/query-entries/console/js

How to dynamically extend class with method in Typescript from remote js file?

I have TypeScript code with classes of components. And I want to use somehow remote js file to extend this classes remote. So I want when my app starts to get js file remote and use code this for extend of needed class.
How to extend class I know. For example:
Import { UsersBlocksMyOrders } from "../pages/users/blocks/myorders";
declare module "../pages/users/blocks/myorders" {
interface UsersBlocksMyOrders {
logit(): void;
}
}
UsersBlocksMyOrders.prototype.logit = function () { console.log(this); }
In component file the code is:
import { APP_CONFIG } from "../../../app/app.config";
#Component({
selector: 'menu-blocks-menupage',
templateUrl: APP_CONFIG.appDomain + '/mobilesiteapp/template/?path=pages/menu/blocks/menupage'
})
export class MenuBlocksMenuPage{
constructor(){
this.logit();
}
}
My problem is that I use the Webpack to compile code. Webpack create final file where name of function is different. That's why I can't access to class directly.
How to be in this situation?
Create service to get file and extend class. You need to have variable inside class which is would store object where have keys. Keys it is names of classes to extend. And values with imported classes. Inside init function we are loop extending with prototype method.
import { Http } from "#angular/http";
import { MenuBlocksMenuPage } from "../pages/menu/blocks/menupage";
export class ExtendService {
allModules = {
...
MenuBlocksMenuPage : MenuBlocksMenuPage,
...
};
constructor(public http: Http){ }
init()
{
//Load json map to extend class
this.http.get(APP_CONFIG.appDomain + '/modules/mobilesiteapp/view/js/extend.json').toPromise()
.then((res) => {
let json = res.json();
//Loop each class to extend
Object.keys(json).forEach((cl) => {
//Add new functions and methods
Object.keys(json[cl]).forEach((func) => {
this.allModules[cl].prototype[func] = eval(json[cl][func]);
});
});
}).catch((e) => {
console.error(e);
});
}
}
Request json file with functions to eval.
{
"MenuBlocksMenuPage": {
"logit": "(function (){console.log(this);})"
}
}

how do I make this class reuseable in typescript? meta programming? subclasses? injected module?

I've got this EventsStorage typescript class that is responsible for storing and retrieving Event objects in ionic-storage (wrapper for sqlite and indexedDB). It uses my Event class throughout.
I would like to reuse a lot of this logic for something other than an Event, like a Widget.
I come from a ruby background where it would be relatively simple to extract all the storage logic, set a ruby var that is literally the class Event and use that var wherever I use Event. Can I do something similar in typescript? Is there another mechanic I can use to reuse the bulk of this class for something else, like Widget?
Ideally, my EventsStorage class becomes really lightweight, and I'm not just wrapping calls to this.some_storage_module.get_ids() or this.some_storage_module.insert_new_objs() -- which would have to be copy/pasted to every other instance I needed this.
Something like this:
export class EventsStorage { // extends BaseStorage (maybe??)
constructor(){
super(Event, 'events'); // or some small set of magical args
}
}
Here's the existing class:
import { Injectable } from '#angular/core';
import { Storage } from '#ionic/storage';
import { Event } from '../classes/event';
// EventsStorage < EntityStorage
// - tracks local storage info
// - a key to an array of saved objects
// - a query() method that returns saved objects
#Injectable()
export class EventsStorage {
base_key: string;
ids_key: string;
constructor(
private storage: Storage
){
this.base_key = 'event';
this.ids_key = [this.base_key, 'ids'].join('_');
}
get_ids(): Promise<any>{
return this.storage.ready().then(() => {
return this.storage.get(this.ids_key).then((val) => {
if(val === null){
return [];
} else {
return val;
}
});
});
}
insert_new_objs(new_objs: any): Promise<any>{
return new_objs.reduce((prev: Promise<string>, cur: any): Promise<any> => {
return prev.then(() => {
return this.storage.set(cur._id, cur.event);
});
}, Promise.resolve()).then(() => {
console.log('saving event_ids');
return this.storage.set(this.ids_key, new_objs.map(obj => obj._id));
});
}
update(events: Event[]): Promise<any> {
let new_objs = events.map((event) => {
return {
_id: [this.base_key, event.id].join('_'),
event: event
};
});
return this.insert_new_objs(new_objs);
}
query(): Promise<Event[]>{
let events = [];
return this.get_ids().then((ids) => {
return ids.reduce((prev: Promise<string>, cur: string): Promise<any> => {
return prev.then(() => {
return this.get_id(cur).then((raw_event) => {
events = events.concat([raw_event as Event]);
return events;
});
});
}, Promise.resolve());
});
}
get_id(id: string): Promise<Event>{
return this.storage.get(id).then((raw_event) => {
return raw_event;
});
}
}
It looks to me like you want to use generics. You basically define some basic interface between all the things you'll want to store, and your code should depend on that interface. In your code as far as I can tell you only use the id property.
So it would look kinda like this
import { Event } from '...';
import { Widget } from '...';
interface HasId{
id: string;
}
class ItemsStorage<T extends HasId> {
....
get_id(id: string): Promise<T>{
...
}
}
const EventStorage = new ItemsStorage<Events>(storage);
const WidgetStorage = new ItemsStorage<Widget>(storage);
const ev = EventStorage.get_id('abc'); //type is Promise<Event>
const wd = WidgetStorage.get_id('def'); //type is Promise<Widget>
You can read more about generics here.
Edit:
1 - about subclassing - It's usually less preferable. If your ItemsStorage class need different behavior when dealing with Events vs Widgets, than subclassing is your solution. But if you have the same behavior for every class, one might call your code generic, and using generics is better.

Angular2 dependency injection, service not being injected during class inheritance

I'm using angular2 with Typescript. I'm trying to create a base class that can be inherited by other classes and within the base class, a service is injected. So far I can not get the ajaxService injected correctly into the base class that is being inherited into the user class. Specifically when a user is instantiated, and then the save() method is called from the user instance, the following line in the base class: return _this._ajaxService.send(options); doesn't work since _ajaxService is undefined.
Here is a user class that extends the base class:
import {Base} from '../utils/base';
export class User extends Base {
// properties
id = null;
email = null;
password = null;
first_name = null;
last_name = null;
constructor(source) {
_super.CopyProperties(source, this);
}
}
Here is the base class:
import {Component} from 'angular2/core';
import {AjaxService} from './ajax.service';
#Component({
providers: [AjaxService]
})
export class Base {
constructor(private _ajaxService: AjaxService) { }
// methods
public static CopyProperties(source:any, target:any):void {
for(var prop in source){
if(target[prop] !== undefined){
target[prop] = source[prop];
}
else {
console.error("Cannot set undefined property: " + prop);
}
}
}
save(options) {
const _this = this;
return Promise.resolve()
.then(() => {
const className = _this.constructor.name
.toLowerCase() + 's';
const options = {
data: JSON.stringify(_this),
url: className,
action: _this.id ? 'PATCH' : 'POST';
};
debugger;
return _this._ajaxService.send(options);
});
}
}
This works fine except that AjaxService is not being injected into the base class. I guess this makes sense since user is being instantiated not base.
So how can I use AjaxService in the Base module when when `Base module is being extended on another class?
I guess when I instantiate user, the constructor in the user class is called but the constructor in the base class that injects the service is not being called.
Here's the AjaxService:
import {Injectable} from 'angular2/core';
#Injectable()
export class AjaxService {
// methods
send(options) {
const endpoint = options.url || "";
const action = options.action || "GET";
const data = options.data || {};
return new Promise((resolve,reject) => {
debugger;
$.ajax({
url: 'http://localhost:3000' + endpoint,
headers: {
Authentication: "",
Accept: "application/vnd.app.v1",
"Content-Type": "application/json"
},
data: data,
method: action
})
.done((response) => {
debugger;
return resolve(response);
})
.fail((err) => {
debugger;
return reject(err);
});
});
}
}
It's ok to inject services in the base, but you have to pass it in from the User class regardless. You can't inherit the actual instantiation of the service from the Base, so you have to pass it down to the Base from User. This is not a limitation of TypeScript, but rather a feature of how DI works in general.
Something like this:
class User extends Base
constructor(service: AjaxService) {
super(service);
}
If the Base instantiated the service for you, you would not be able to affect the instantiation from User. This would negate a lot of the benefits of DI overall since you would lose control by delegating dependency control to a different component.
I understand that you might be trying to reduce code duplication by specifying this in the Base, but this goes against the principle of DI.
Each class in Angular 2 that you want to inject you must annotate. If it is not component, you must annotate it with #Injectable() annotation. If you inject class that already inject other class, you must create provider for that.
import {Injectable} from 'angular2/core';
import {Base} from './base';
#Injectable()
export class User extends Base {
}
I created Plunker for you, i hope that it will solve your problem:
http://plnkr.co/edit/p4o6w9GjWZWGfzA6cv41?p=preview
( look at console output )
PS. Please use Observable instead of Promises

Categories