I have 2 services. CarsController and CarsFetcherService
I also have a class called Car.
The CarsController has a method createCar() that creates an instance of Car everytime it is called.
Each Car instance needs to be provided with the CarsController service.
With the code below, each Car instances gets undefined instead of the CarsController dependency.
I use the library typedi to illustrate the problem I want to know in general how this is done. An answer with other library link inversify or Nestjs or even Angular would also be helpful.
export class Car {
#Inject() // this is not working here
private carsController: CarsController; // this becomes undefined
private carRecipe: string;
constructor(carRecipe: string) {
this.carRecipe = carRecipe;
this.carsController.notifyNetwork('HelloWorld, I am created!');
}
}
#Service()
export class CarsController {
#Inject()
private carsNetworkService: CarsFetcherService;
private cars: Car[] = [];
constructor() {
}
createCar() {
const carRecipe = this.carsNetworkService.getRequestSomething();
const car = new Car(carRecipe);
this.cars.push(car);
}
notifyNetwork(msg: string) {
this.carsNetworkService.postRequestSomething(msg);
}
}
#Service()
export class CarsFetcherService {
getRequestSomething() {
return 'this is how to make a car';
}
postRequestSomething(msg:string) {
console.log(msg)
}
}
const carsController = Container.get(CarsController)
// Creating 4 cars
carsController.createCar()
carsController.createCar()
carsController.createCar()
carsController.createCar()
I think, that when using typedi, and other similar DI frameworks, you should separate your classes into two categories. First categories would be services: typedi would guarantee that there's only one instance of each, typedi would create those instances and inject everything. Second category would be ordinary objects, that you create and manage on your own.
So, if you want to create as many instances of Car as you like, don't use DI for it - you already create it in a service that has direct references to everything Car needs:
export class Car {
// deleted inject decorator
constructor(private carsController, private carRecipe: string) {
this.carsController.notifyNetwork('HelloWorld, I am created!');
}
}
#Service()
export class CarsController {
#Inject()
private carsNetworkService: CarsFetcherService;
private cars: Car[] = [];
constructor() {
}
createCar() {
const carRecipe = this.carsNetworkService.getRequestSomething();
const car = new Car(this, carRecipe);
this.cars.push(car);
}
notifyNetwork(msg: string) {
this.carsNetworkService.postRequestSomething(msg);
}
}
Related
I have a main TypeScript project distributed as a package that can be enriched by plugins (which are just other TypeScript packages).
My main project exposes an API with a definition like below
class Dealer {
public buyCar (model: string, options: any): void {
// ...
}
}
So let's suppose the main project allows to buy cars from a dealer, and that the plugins can add new car models available to buy. How can I extend the buyCar definition from the plugin package in order to add more specific types?
Something like
class Dealer {
public buyCar (model: "Toyota", options: ToyotaOptions): void;
}
I know I can do this from the main project, but the point is that the main project shouldn't be aware of the plugins, so plugins must extend the interfaces.
You can try to use generics to specify what type of models and options the class expects to receive.
interface Dealer<M extends string> {
buyCar<T = unknown>(model: M, options: T): void;
}
type ToyotaModel = "Yaris" | "Auris" | "Camry";
interface ToyotaOptions {
engine?: "petrol" | "diesel" | "hybrid";
}
class ToyotaDealer implements Dealer<ToyotaModel> {
public buyCar<ToyotaOptions>(model: ToyotaModel, options: ToyotaOptions) {
console.log(model, options);
}
}
const toyota = new ToyotaDealer();
toyota.buyCar("Auris", { engine: "petrol" });
The entire class is annotated by M generic to make all the methods within the class to access this type and use the same model set. The buyCar method is annotated by another generic T to allow to specify the interface for "buy car" options for this method.
TypeScript playground with the example is here.
I am not user but I think you can combine namespace & declaration merging to achieve it. Something like this:
type ToyotaOptions = "Toyota";
namespace Dealer {
export function buyCar(model: string, options: ToyotaOptions): void;
export function buyCar(model: string, options: any): void {
Dealer.buyCar(model, options);
}
}
I'm not sure if I understood you correctly, but I think there is some misconception about how you described the plugin architecture. You shouldn't have to extend/modify/override the plugin's code to use it (e.g., by using namespace & declaration merging because it violates the open-closed principle). To make plugins closed for modification, I would introduce a shared interface that all the plugin packages will use. e.g.
// package: purchases
export interface CarPurchase {
buy():void
}
The package that provides details for purchasing Toyota cars could look like this.
// package: toyota
import { CarPurchase } from "purchases"
export type ToyotaYarisPurchaseOptions {
engine?: "petrol" | "diesel" | "hybrid";
}
export class ToyotaYarisPurchase implements CarPurchase {
constructor(options: ToyotaYarisPurchaseOptions){}
buy():void {
//implementations details goes here
}
}
export type ToyotaCamryPurchaseOptions {
engine?: "petrol" | "diesel" | "hybrid";
}
export class ToyotaCamryPurchase implements CarPurchase {
constructor(options: ToyotaCamryPurchaseOptions){}
buy():void {
//implementations details goes here
}
}
Then, in your main app, I could implement the Dealer class that runs purchases.
import { CarPurchase } from "purchases"
import { ToyotaYarisPurchase } from "toyota"
class Dealer {
makeTransaction(purchase: Purchase):void {
// additional behavior for a dealer - e.g. logging all purchases
purchase.buy();
}
}
const dealer = new Dealer();
const toyotaPurchase = new ToyotaYarisPurchase({engine: 'diesel'})
dealer.makeTransaction(toyotaPurchase);
At this point you could also create a factory for purchases in order to hide instantiation details of a purchase:
// purchasesFactory.ts
import { ToyotaYarisPurchaseOptions } from "toyota"
import { CarPurchase } from "purchases"
export class PurchasesFactory {
create(car: 'yaris', options: ToyotaYarisPurchaseOptions): ToyotaYarisPurchase
create(car: 'camry', options: ToyotaCamryPurchaseOptions):ToyotaCamryPurchase
create(car: 'yaris' | 'camry', options: ToyotaYarisPurchaseOptions | ToyotaCamryPurchaseOptions): CarPurchase {
switch(car) {
case 'camry':
return new ToyotaCamryPurchase(options);
// other cases
}
}
}
so your final app code looks like
import { CarPurchase } from "purchases"
import { ToyotaYarisPurchase } from "toyota"
import { PurchasesFactory } from './purchasesFactory';
class Dealer {
makeTransaction(purchase: Purchase):void {
// additional behavior for a dealer - e.g. logging all purchases
purchase.buy();
}
}
const dealer = new Dealer();
const purchases = new PurchasesFactory();
const toyotaYarisPurchase = purchases.create('yaris', { engine: 'diesel' });
dealer.makeTransaction(toyotaYarisPurchase);
The presented example illustrates roughly the usage of a command pattern. It may be suitable for your problem. I used OOP on purpose to clearly present the responsibilities of particular classes, but you could write it more concisely using functions with generic types. (Beware that, injecting additional dependencies to purchase functions and dealer's makeTransaction would require using more advanced concepts).
// shared
export type Purchase<TOptions extends object> = <TOptions>(options:TOptions) => void;
// toyota package
export const buyToyotaYaris: Purchase<ToyotaYarisPurchaseOptions> = options => {
// some logic here
}
export const buyToyotaCamry: Purchase<ToyotaCamryPurchaseOptions> = options => {
// some logic here
}
// main app
import { buyToyotaCamry } from 'toyota';
const makeTransaction = <TPurchaseOptions extends object>(options: TPurchaseOptions, purchase: Purchase<T>) => {
// dealer logic here
purchase(options)
}
makeTransaction({engine: 'diesel'}, buyToyotaCamry);
I am the opinion you are trying to address your Software Design problem with language specifics constructs. You can solve this in a more general way, applicable to any OOP Language, and then translate it to TypeScript.
Maybe you can get a better answer if you post this on software engineering site.
In any case, I would do as follow:
Define your very basic concepts (needed for a Dealer to work) through interfaces in a base package.
Car Model common aspects
Car Options commonalities
Car Dealer. Make car dealer work with this very basic concepts you already have
Define your Plugin architecture. Maybe this help
Plugin register mechanismus to register new car models with its options
Allow your main application to request car models to your car (plugin) register
Make your car dealer work with these new added (more specific) car models from register
This way, lets say, you can install an npm package with new car definition, then in your application register this new car definition to your car registry, then the dealer can later on use it.
You might want to do something like this for better readability and maintainability.
import { Dealer as BaseDealer } from './Dealer';
export class Dealer extends BaseDealer {
public buyCar(model: string, options: any): void {
super.buyCar(model, options);
}
}
Demo: https://stackblitz.com/edit/typescript-tpdfyv?file=index.ts
Here is the next JavaScript class structure:
// data.service.ts
export class DataService {
public url = environment.url;
constructor(
private uri: string,
private httpClient: HttpClient,
) { }
getAll() {}
getOne(id: number) {}
create(data: any) {}
// etc...
}
Next is the general data model what can use the DataService's methods to communicate the server:
// Model.model.ts
import './data.service';
export class Model extends DataService {
all() {}
get() {
// parse and make some basic validation on the
// DataService.getOne() JSON result
}
// etc...
}
And finally I create a specific data model based on Model.model.ts:
// User.model.ts
import './Model.model.ts';
export class User extends Model {
id: number;
name: string;
email: string;
init() {
// make specific validation on Model.get() result
}
}
If I use the User class in my code, I can call the DataService's getAll() function directly if I want. But this is not a good thing, because in this case I miss the built-in validations.
How can I block the method inheritance on a class?
I'm looking for something like PHP's static method. The child class can use the methods, but his child can't.
I want something like this:
const dataService = new DataService();
dataService.getAll(); // void
const model = new Model();
model.getAll(); // undefined
model.all(); // void
const user = new User();
user.getAll(); // undefined
user.all(); // void
Is there any way to do this?
You can prevent it to be built when you call it by adding private keyword to the function private getAll() {}. But private is a TypeScript feature, not Javascript, so if you force it to be built, it is still callable. There'll be no way to totally prevent it this moment.
So if you want it to be prevented in TypeScript, just add private keyword there. But it is not buildable, not return undefined as you expect. Otherwise, just replace the function with an undefined-returned function on children classes
With your code as shown and use case as stated, the only way to get the behavior you want is not to make Model extend DataService at all. There is a subsitution principle which says that if Model extends DataService, then someone should be able to treat a Model instance exactly as they would treat a DataService instance. Indeed, if a Model is a special type of DataService, and someone asks for a DataService instance, you should be able to give them a Model. It's no fair for you to tell them that they can't call the getAll() method on it. So the inheritance tree can't work the way you have it. Instead you could do something like this:
// ultimate parent class of both DataService and Model/User
class BaseDataService {
getOne(id: number) { }
create(data: any) { }
// etc...
}
// subclass with getAll()
class DataService extends BaseDataService {
getAll() {}
}
// subclass without getAll()
class Model extends BaseDataService {
all() { }
get() { }
// etc...
}
class User extends Model {
id!: number;
name!: string;
email!: string;
init() { }
}
const dataService = new DataService();
dataService.getAll(); // void
const model = new Model();
model.getAll(); // error
model.all(); // okay
const user = new User();
user.getAll(); // error
user.all(); // okay
That works exactly as you've specified. Perfect, right?
Well, I get the sinking feeling that you will try this and get upset that you cannot call this.getAll() inside the implementation of Model or User... probably inside the suspiciously-empty body of the all() method in Model. And already I'm getting the urge to defensively point to the Minimum, Complete, and Verifiable Example article, since the question as stated doesn't seem to require this.
If you do require that, you still can't break the substitution principle. Instead I'd suggest making getAll() a protected method, and expose an all() method on DataService:
class DataService {
getOne(id: number) { }
create(data: any) { }
protected getAll() { }
all() {
this.getAll();
}
// etc...
}
class Model extends DataService {
all() {
this.getAll();
}
get() { }
// etc...
}
class User extends Model {
id!: number;
name!: string;
email!: string;
init() { }
}
const dataService = new DataService();
dataService.getAll(); // error
dataService.all(); // okay
const model = new Model();
model.getAll(); // error
model.all(); // okay
const user = new User();
user.getAll(); // error
user.all(); // okay
and live with the fact that getAll() is a purely internal method never meant to see the light of day.
Okay, hope one of those helps; good luck!
I am trying to figure out testing with Jest for my MobX stores.
I am using Mobx, React, and Jest.
class ConfigStore {
constructor(RootStore) {
this.rootStore = RootStore;
this.config = {};
}
}
class DataStore {
constructor(RootStore) {
this.config = RootStore.config;
}
}
class UIStore {
constructor(RootStore) {
this.config = RootStore.config;
this.data = RootStore.data;
}
}
class RootStore {
constructor() {
this.config = new ConfigStore(this);
this.ui = new UIStore(this);
this.data = new DataStore(this);
}
}
Did I set my stores up correctly?
If so, what is the best way to test the stores before they get passed to Provider?
Your question is very unclear. What exactly do you want to test about these stores in unit tests? You can't really test data itself.
My suggestions:
link to stores
instead of using setting a single property just keep the whole store:
class DataStore {
constructor(RootStore) {
this.configStore = RootStore;
}
}
this way you can besure properties are always properly updated and observed.
if you want you can always have property on your lower level stores:
class DataStore {
constructor(RootStore) {
this.configStore = RootStore;
}
get config() {
return this.configStore.config;
}
}
Abstract
if you use typescript abstract your stores with interfaces so the stores are way easilier tested:
class DataStore {
constructor(store: IConfigStore) {
this.configStore = store;
}
}
interface IConfigStore {
config: Config;
}
Use a repository pattern
For every store make a repository injectable so that all api calls done by the store are actually done in this repository:
class RootStore {
constructor(repository) {
this.repostiory = repository;
this.config = new ConfigStore(this);
this.ui = new UIStore(this);
this.data = new DataStore(this);
this.initializeData();
}
async initializeData(){
this.config = await this.repository.getConfig();
}
}
This way you can easily mock the repository to give static date so you dont need to do any api calls.
Keep your react components pure
The react components that you really want to unit test. make sure they dont use mobx stores directly but you use the inject() function instead to make a second class: https://github.com/mobxjs/mobx-react#inject-as-function
this way your components are way easilier testable and useable stand alone:
const PureReactComponent = ({ name }) => <h1>{name}</h1>
const SimpleMobxComponent = inject(stores => ({
name: stores.userStore.name
}))(PureReactComponent)
// usage:
render() {
return <SimpleMobxComponent/>
}
I found a lot of examples and also tried myself to split a module into several files. So I get that one, very handy. But it's also practical sometimes to split a class for the same reason. Say I have a couple of methods and I don't want to cram everything into one long file.
I'm looking for something similar to the partial declaration in C#.
Lately I use this pattern:
// file class.ts
import { getValue, setValue } from "./methods";
class BigClass {
public getValue = getValue;
public setValue = setValue;
protected value = "a-value";
}
// file methods.ts
import { BigClass } from "./class";
function getValue(this: BigClass) {
return this.value;
}
function setValue(this: BigClass, value: string ) {
this.value = value;
}
This way we can put methods in a seperate file. Now there is some circular dependency thing going on here. The file class.ts imports from methods.ts and methods.ts imports from class.ts. This may seem scary, but this is not a problem. As long as the code execution is not circular everything is fine and in this case the methods.ts file is not executing any code from the class.ts file. NP!
You could also use it with a generic class like this:
class BigClass<T> {
public getValue = getValue;
public setValue = setValue;
protected value?: T;
}
function getValue<T>(this: BigClass<T>) {
return this.value;
}
function setValue<T>(this: BigClass<T>, value: T) {
this.value = value;
}
You can't.
There was a feature request to implement partial classes, first on CodePlex and later on GitHub, but on 2017-04-04 it was declared out-of-scope. A number of reasons are given, the main takeaway seems to be that they want to avoid deviating from ES6 as much as possible:
TypeScript already has too many TS-specific class features [...] Adding yet another TS-specific class feature is another straw on the camel's back that we should avoid if we can. [...] So if there's some scenario that really knocks it out of the park for adding partial classes, then that scenario ought to be able to justify itself through the TC39 process.
I use this (works in typescript 2.2.2):
class BigClass implements BigClassPartOne, BigClassPartTwo {
// only public members are accessible in the
// class parts!
constructor(public secret: string) { }
// this is ugly-ish, but it works!
methodOne = BigClassPartOne.prototype.methodOne;
methodTwo = BigClassPartTwo.prototype.methodTwo;
}
class BigClassPartOne {
methodOne(this: BigClass) {
return this.methodTwo();
}
}
class BigClassPartTwo {
methodTwo(this: BigClass) {
return this.secret;
}
}
I use plain subclassing when converting large old multi file javascript classes which use 'prototype' into multiple typescript files:
bigclassbase.ts:
class BigClassBase {
methodOne() {
return 1;
}
}
export { BigClassBase }
bigclass.ts:
import { BigClassBase } from './bigclassbase'
class BigClass extends BigClassBase {
methodTwo() {
return 2;
}
}
You can import BigClass in any other typescript file.
A modified version of proposed pattern.
// temp.ts contents
import {getValue, setValue} from "./temp2";
export class BigClass {
// #ts-ignore - to ignore TS2564: Property 'getValue' has no initializer and is not definitely assigned in the constructor.
public getValue:typeof getValue;
// #ts-ignore - to ignore TS2564: Property 'setValue' has no initializer and is not definitely assigned in the constructor.
public setValue:typeof setValue;
protected value = "a-value";
}
BigClass.prototype.getValue = getValue;
BigClass.prototype.setValue = setValue;
//======================================================================
// temp2.ts contents
import { BigClass } from "./temp";
export function getValue(this: BigClass) {
return this.value;
}
export function setValue(this: BigClass, value: string ) {
this.value = value;
}
Pros
Doesn't create additional fields in class instances so there is no overhead: in construction, destruction, no additional memory used. Field declations in typescript are only used for typings here, they don't create fields in Javascript runtime.
Intellisence is OK (tested in Webstorm)
Cons
ts-ignore is needed
Uglier syntax than #Elmer's answer
The rest properties of solutions are same.
Modules let you extend a typescript class from another file:
user.ts
export class User {
name: string;
}
import './user-talk';
user-talk.ts
import { User } from './user';
class UserTalk {
talk (this:User) {
console.log(`${this.name} says relax`);
}
}
User.prototype.sayHi = UserTalk.prototype.sayHi;
declare module './user' {
interface User extends UserTalk { }
}
Usage:
import { User } from './user';
const u = new User();
u.name = 'Frankie';
u.talk();
> Frankie says relax
If you have a lot of methods, you might try this:
// user.ts
export class User {
static extend (cls:any) {
for (const key of Object.getOwnPropertyNames(cls.prototype)) {
if (key !== 'constructor') {
this.prototype[key] = cls.prototype[key];
}
}
}
...
}
// user-talk.ts
...
User.extend(UserTalk);
Or add the subclass to the prototype chain:
...
static extend (cls:any) {
let prototype:any = this;
while (true) {
const next = prototype.prototype.__proto__;
if (next === Object.prototype) break;
prototype = next;
}
prototype.prototype.__proto__ = cls.prototype;
}
You can use multi file namespaces.
Validation.ts:
namespace Validation {
export interface StringValidator {
isAcceptable(s: string): boolean;
}
}
LettersOnlyValidator.ts (uses the StringValidator from above):
/// <reference path="Validation.ts" />
namespace Validation {
const lettersRegexp = /^[A-Za-z]+$/;
export class LettersOnlyValidator implements StringValidator {
isAcceptable(s: string) {
return lettersRegexp.test(s);
}
}
}
Test.ts (uses both StringValidator and LettersOnlyValidator from above):
/// <reference path="Validation.ts" />
/// <reference path="LettersOnlyValidator.ts" />
// Some samples to try
let strings = ["Hello", "101"];
// Validators to use
let validators: { [s: string]: Validation.StringValidator; } = {};
validators["Letters only"] = new Validation.LettersOnlyValidator();
Why not just use Function.call that js already comes with.
class-a.ts
Class ClassA {
bitten: false;
constructor() {
console.log("Bitten: ", this.bitten);
}
biteMe = () => biteMe.call(this);
}
and in other file bite-me.ts
export function biteMe(this: ClassA) {
// do some stuff
// here this refers to ClassA.prototype
this.bitten = true;
console.log("Bitten: ", this.bitten);
}
// using it
const state = new ClassA();
// Bitten: false
state.biteMe();
// Bitten: true
For more information have a look at the definition of Function.call
Personally I use #partial decorator acts as a simplified syntax that may help divide functionality of a single class into multiple 🍬🍬🍬 class files ... https://github.com/mustafah/partials
// Declaration file
class BigClass {
declare method: (n: number, s: string) => string;
}
// Implementation file
BigClass.prototype.method = function (this: BigClass, n: number, s: string) {
return '';
}
The downside of this approach is that it is possible to declare a method but to forget to actually add its implementation.
We can extend class methods gradually with prototype and Interface definition:
import login from './login';
import staffMe from './staff-me';
interface StaffAPI {
login(this: StaffAPI, args: LoginArgs): Promise<boolean>;
staffsMe(this: StaffAPI): Promise<StaffsMeResponse>;
}
class StaffAPI {
// class body
}
StaffAPI.prototype.login = login;
StaffAPI.prototype.staffsMe = staffsMe;
export default StaffAPI;
This is how i have been doing it
Mixins approach
To add to #Elmer's solution, I added following to get it to work in separate file.
some-function-service-helper.ts
import { SomeFunctionService } from "./some-function-service";
export function calculateValue1(this: SomeFunctionService) {
...
}
some-function-service.ts
import * as helper from './some-function-service-helper';
#Injectable({
providedIn: 'root'
})
export class SomeFunctionService {
calculateValue1 = helper.calculateValue1; // helper function delcaration used for getNewItem
public getNewItem() {
var one = this.calculateValue1();
}
I am trying to define an API using TypeScript such that it can work like this:
// Create new user (Working)
var user : IUser = new Api.User({ firstName: "John", lastName: "Smith" });
// Delete existing user (Working)
Api.User.Delete(1);
// Load existing user (Unsure how to implement)
var user = Api.User(123);
My TypeScript:
module Api
{
export class User
{
constructor(id : number)
constructor(user : IUser)
constructor(user? : any)
{
// ...
}
static Delete(id : number) {}
}
}
I am not sure how to have a static method Api.User() i.e. not use new. I don't know what to call this type of construct, which makes it difficult to research. :(
I did try adding an unnamed static to the User class, but this isn't right.
static (id : number)
{
// ...
}
Option 1: export a function directly on the Api module
You can export a function on the Api module to retrieve/create a User instance:
module Api
{
export class User
{
}
export function GetUser(id: number):User {
return new User();
}
// or a slightly different syntax (which generates different JavaScript):
export var Delete = (id: number) => {
};
}
You can't have the class named User and the function also be User, so I've changed it to GetUser in the example.
You could then call:
Api.GetUser(1234)
or
Api.Delete(1234);
Option 2: Using an interface
You could also approach this by using an interface if you wanted to limit the ability of
calling code from being able to instantiate instances of the inner class by using an interface instead. Below I've created a simple ISuperUser interface and and implementation of the class called SuperUserImpl. As the SuperUserImpl isn't exported, it's not publicly creatable. This is nice in that you could use simple Api.SuperUser(2345) to return new instances of a class that implements the ISuperUser interface.
module Api {
export interface ISuperUser {
id: Number;
name: String;
}
class SuperUserImpl implements ISuperUser
{
constructor(public id: Number) {
}
public name: String;
}
export var SuperUser = (id:Number):ISuperUser => {
return new SuperUserImpl(id);
}
}
var su : Api.ISuperUser = Api.SuperUser(5432);
alert(su.id);
Option 3: JavaScript and instanceof
There's a trick that is often used in JavaScript class constructor wherein the function/constructor for a new object checks to see whether it is of the right type (was the function called or created), and if not created, returns a new instance:
if (!(this instanceof User)) {
return new User(id);
}
While correct, TypeScript when trying to call a constructor with that will cause a compiler warning. This will work, but with the compiler warning:
constructor(id: Number) {
if (!(this instanceof User)) {
return new User(id);
}
}
And later calling:
var u: Api.User = Api.User(543);
A compiler warning suggests, "did you forget to use 'new'?" It does produce valid JavaScript, just with a warning. I'd probably go with the static-like method approach to avoid the TypeScript compiler warning.