Still trying to get the hang of dependency injection, I've run into a problem. I have three classes: Main, Store, Model. Store does as the name suggests: stores data which is accessible elsewhere. Model retrieves data from the DB, in my case local/Chrome storage. I'm trying to write the classes in a decoupled way so I can test/modify it later. But of course, I keep getting Maximum call stack size exceeded error because I think Store and Model just keep going round and round...circular dependancy. What's a possible solution?
store.js
export class Store { //ok there's also a config class but that's not the problem
constructor(config, model) {
this._config = config;
this._model = model;
}
//...
}
model.js
export class Model {
constructor(config, store) {
this._config = config;
this._store = store;
}
//...
}
main.js
import { Store } from './store.js'
import { Config } from './config.js'
import { Model } from './model.js'
export class Main {
constructor(config, store, model) {
this._store = store;
this._model = model;
}
//...
}
const config = new Config();
const model = new Model(config, ???);
const store = new Store(config, ???);
const main = new Main(config, model, store);
Is there a fix, workaround, anything? Thanks in advance!
Related
i created a vue base project.
What i am trying to do:
Create a global service function (For call API), in every component able to use this.$service.service.get() to call any API that i want, without need to inject.
Use the app.provide and declare service as a global constant
Here is my main.js
// main.js
const config = {
service: createRepository({httpClient})
}
// Vue.prototype.$repository = config.repository;
const app = createApp({
created() {
},
render: () => (
h(App)
)
});
app.provide('$service', config.service)
app.mixin(mixin)
app.use(router)
app.mount('#app');
// mixin.js
import serviceProvider from "./serviceProvider";
const mixins = [serviceProvider];
const mixin = {
install: function (Vue, options) {
mixins.forEach(m => Vue.mixin(m))
}
}
export default mixin;
//serviceProvider.js
export default {
beforeCreate() {
const options = this.$options;
const service = options.$service || (options.parent ? options.parent.$service : null);
if (!service) return;
this.$service = service;
Object.keys(service).forEach((key, index) => {
this[`$${key}`] = service[key]
})
}
}
What is my expected result:
Expect to see the function being call from the HomePage.vue
// HomePage.vue
async created(){
await this.$service.authService.get()
}
What is my current result:
authService is undefined
Please advice is that my current setup got any problem. Thanks.
provide is useless without inject
If you don't want to use inject, just use app.config.globalProperties (replacement of Vue.prototype in Vue 2)
app.config.globalProperties.$service = createRepository({httpClient})
this.$service will now be available in every component in the app...
Using Apollo server, have a pretty standard setup:
In an index.js file that manages my Apollo server state:
const { ApolloServer } = require('apollo-server-express');
const databaseAPI = require('../database/database.datasource');
...
module.exports = new ApolloServer({
...
dataSources: () => {
return {
databaseAPI: new databaseAPI()
};
},
...
});
And then my database.datasource.js file:
const { RESTDataSource } = require("apollo-datasource-rest");
// imports and config here
class databaseAPI extends RESTDataSource {
constructor() {
super();
}
// methods here like createUser({email, password, name})
}
module.exports = databaseAPI;
This database.datasource.js file is getting a bit lengthy, in part because it is managing the db layer for many types, so I was thinking about how to refactor it.
I want to break it apart by major types using a repository pattern, something like:
UserRepository.js (and similar abstract classes as reference and to enforce an internal API)
class UserRepository {
createUser({ email, password, name }) {
throw new Error("Not implemented");
}
...
}
export default UserRepository;
and then implementations of these in by-type directories:
/UserRepository/db.datasource.js
import UserRepository from "../UserRepository";
import utils from './utils';
class UserDatabaseAPI extends UserRepository {
constructor(db) {
super();
this.db = db;
}
createUser({ email, password, name }) {
// logic here
}
}
export default UserDatabaseAPI;
My question is how best to manage the multiple classes inside of my root database.datasource.js file with multiple inheritance sources.
I could see doing something like this:
const { RESTDataSource } = require("apollo-datasource-rest");
const UserDatabaseAPI = require("./UserRepository/db.datasource.js");
// imports and config here, including a db instance
const userDBAPI = new UserDatabaseAPI(db);
class databaseAPI extends RESTDataSource {
constructor() {
super();
}
createUser = userDBAPI.createUser;
...
}
Though I think that this might become kind of a headache. Is there another approach that might work better here, or is there at least a way to better map the methods in my userDBAPI instance to methods in my root database.datasource.js file?
Instead of having a single RESTDataSource for the endpoint you're wrapping, you can split it up into multiple RESTDataSource instances, one per resource. For example, https://swapi.co/api can be split into 6 difference RESTDataSource instances with the following baseURLs:
https://swapi.co/api/people
https://swapi.co/api/films
https://swapi.co/api/starships
https://swapi.co/api/vehicles
https://swapi.co/api/species
https://swapi.co/api/planets
And your config would look something like:
dataSources: () => ({
people: new PeopleAPI(),
films: new FilmsAPI(),
starships: new StarshipsAPI(),
vehicles: new VehiclesAPI(),
species: new SpeciesAPI(),
planets: new PlanetsAPI(),
})
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' });
I am trying to reuse some working code from AngularJS 1 services written in plain JavaScript in an Angular 2 environment.
The services look, for instance, like the following example:
(function () {
angular.module('myapp.mysubmodule').factory('myappMysubmoduleNormalService', ['someOtherService',
function (someOtherService) {
var internalState = {
someNumber: 0
};
var service = {};
service.someFunction = function () {
internalState.someNumber++;
};
someOtherService.getValues().forEach(function (v) {
service[v] = function () {
console.log(v + internalState.someNumber);
};
});
return service;
}]);
})();
I have found various examples of how to convert AngularJS 1 services to Angular 2 services (such as this one), all of which have in common that instead of the service factory, I have to export a class.
This should look roughly as follows:
import { Injectable } from '#angular/core';
#Injectable()
export class myappMysubmoduleNormalService {
someFunction: function () {
// ?
}
}
Now, the question is how to incorporate the internal state and the dynamically added properties.
Is it really the way to go to do all that in the constructor, i.e. fill each instance of the class upon initialization, like so:
import { Injectable } from '#angular/core';
#Injectable()
export class myappMysubmoduleNormalService {
constructor() {
var internalState = {
someNumber: 0
};
var service = {};
this.someFunction = function () {
internalState.someNumber++;
};
this.getValues().forEach(function (v) {
service[v] = function () {
console.log(v + internalState.someNumber);
};
});
}
}
Or is there any other way? The above probably works (save for the missing dependency injection, that I still have to find out about how to do in Angular 2). However, i am wondering whether it is a good way because I have not come across any samples that did much of a member initialization in their constructor.
You can use just the same approach in Angular with factory providers:
export function someServiceFactory(someOtherService) {
var internalState = {
someNumber: 0
};
var service = {};
service.someFunction = function () {
internalState.someNumber++;
};
someOtherService.getValues().forEach(function (v) {
service[v] = function () {
console.log(v + internalState.someNumber);
};
});
return service;
};
#NgModule({
providers: [
{
token: 'myappMysubmoduleNormalService',
useFactory: someServiceFactory,
deps: ['someOtherService']
}
]
})
Both in Angular and AngularJS the value returned by the factory function is cached.
A service is just a class that you can inject into components. It will create a singleton in the scope where it is named a provider.
import { Injectable. OnInit } from '#angular/core';
#Injectable()
export class myappMysubmoduleNormalService implements OnInit {
internalState: number;
constructor() {}
ngOnInit(){
this.internalState = 0;
}
incrementSomeNumber() {
this.internalState++;
console.log(this.internalState};
}
}
I realize this is not logging a distinct internal state for multiple functions but you get the idea.
Register this as a provider in the app.module (if you want a singleton for app scope)
When you import into a component and then inject in the constructor
constructor(private _myservice : myappMysubmoduleNormalService) {}
you can now use the _myservice methods
myNumber : number = 0 ;
componentFunction() {
_myservice.incrementSomeNumber();
this.myNumber = _myservice.internalState;
}
Of course you could have the service method return the incremented number (or data or a promise of data)
This is rough but gives you the idea. Very little code belongs in the constructor. A service should be injected. what is shown in component constructor is shorthand to a get private variable referencing the service. The service will be a singleton for the scope in which it is provided. (can be overridden within the scope but that seems a code smell to me)
To pass back a value :
In service
incrementSomeNumber(): number {
this._internalState++;
console.log(this._internalState};
return this._internalState;
}
In component:
mynumber: number;
componentFunction() {
this.mynumber = _myservice.incrementSomeNumber();
}
Not sure what you're trying to accomplish but just wanted to show example of getting information from services. Most common use of services for me is a dataservice, so the code would be a little more complex as it is asynch.
With the first router v.3 versions, I was doing that somewhere in my code :
let lastColors: Colors;
let node = this.router.routerState._root;
do {
if(node.value.component.hasOwnProperty('__colors')) {
lastColors = (<any>node.value.component).__colors;
}
} while(node = node.children[0]);
However, the _root: TreeNode<ActivatedRoute> property of RouterState is now private in alpha 8, breaking my code. It seems that now, RouterState isn't exposing the root TreeNode anymore. What's the proper way to explore the components tree now?
Have you tried this.router.routerState.root
export class RouterState extends Tree<ActivatedRoute> {
/**
* #internal
*/
constructor(
root: TreeNode<ActivatedRoute>, public queryParams: Observable<Params>,
public fragment: Observable<string>, public snapshot: RouterStateSnapshot) {
super(root);
}
toString(): string { return this.snapshot.toString(); }
}
Well in fact that was simple, I didn't read the router's .d.ts files with enough attention:
let lastColors: Colors;
let state = this.router.routerState.snapshot;
let route = state.root;
do {
// do something with route.component
} while(route = state.children(route)[0]);
Update: #angular/router is now stable and the API changed, again. Here is the new version :
let state = this.router.routerState.snapshot;
let route = state.root;
do {
// do something with route.component
} while(route = route.children[0]);