Mobx - Object literal may only specify known properties - javascript

I recently started to learn how to use Mobx to manage my application's state and recently I came across the following error:
Object literal may only specify known properties, and "data" does not exist in type "AnnotatiosMap<this, never>".
This happens whenever I want to make a property of my class private. However, if it is public or protected, the problem does not occur.
This is a small snippet of my code:
import { makeObservable, observable } from "mobx";
class Base {
private data: string[];
constructor() {
this.data = [];
makeObservable(this, {
data: observable,
});
}
public getData = (): string[] => {
return this.data;
};
}
export default new Base();
What should I do to make my property private but still being watched?
Have a great day!

From the docs:
By default TypeScript will not allow you to annotate private fields. This can be overcome by explicitly passing the relevant private fields as generic argument, like this: makeObservable<MyStore, "privateField" | "privateField2">(this, { privateField: observable, privateField2: observable })

Related

Typescript static methods in interfaces

I'm looking for a way to require a class to own some static methods without having to implement them by myself (like an interface can define normal methods). Since interfaces do not support static methods, the following code does not work.
interface MyInterface {
static fromJSON(json: string): MyInterface
toJSON(): object
}
Abstract classes are not the thing I want, because they do not require the developer to write the method himself, but I have to implement it.
Is there something similar to this, without having to write lots of custom logic?
Using the interface from above, the following implementation should not be accepted:
class MyClass implements MyInterface {
// Missing static method "fromJSON"
toJSON() {
return {}
}
}
Neither should this one:
class MyClass implements MyInterface {
static fromJSON(json: string) {
return 123 // Wrong type
}
toJSON() {
return {}
}
}
But this one should be accepted:
class MyClass implements MyInterface {
static fromJSON(json: string) {
return new MyClass()
}
toJSON() {
return {}
}
}
There really isn't much support in TypeScript for constraining the static side of a class. It's a missing feature; see microsoft/TypeScript#14600 for an overall feature request, as well as microsoft/TypeScript#33892 for just the "support static implements on classes" part, and microsoft/TypeScript#34516 for just the "support abstract static class members" part.
One big blocker for something like static members of an interface of the form you've shown is that it's hard for the type system to make sense of it in a way that will actually do what you want. There's a longstanding open issue, microsoft/TypeScript#3841, asking that the constructor property of a class should be strongly typed. Currently it only has the type Function:
class Foo {
instanceProp: string = "i"
static staticProp: string = "s"
}
const foo = new Foo();
foo.constructor.staticProp; // error!
// -----------> ~~~~~~~~~~
// Property 'staticProp' does not exist on type 'Function'
There are some sticky reasons for why this cannot be easily done, spelled out in the issue, but essentially the problem is that subclass constructors are not required to be true subtypes of parent class constructors:
class Bar extends Foo {
subInstanceProp: string;
constructor(subInstanceProp: string) {
super();
this.subInstanceProp = subInstanceProp;
}
}
const bar = new Bar("hello");
Here, the Bar constructor is of type new (subInstanceProp: string) => Bar, which is not assignable to the type of the Foo constructor, which is new () => Foo. By extends, bar should be assignable to Foo. But if bar.constructor is not assignable to Foo['constructor'], everything breaks.
There might be ways around that, but nothing has been implemented so far.
All this means that there's no way to look at an object of type MyInterface and be sure that the thing that constructed it has a fromJSON method. So having static inside interface definitions doesn't really behave in any useful way.
The requests in microsoft/TypeScript#33892 and microsoft/TypeScript#34516, don't have this problem. If you could write this:
class MyClass implements MyInterface static implements MyInterfaceConstructor {
// not valid TS, sorry ------------> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
toJSON() { return "" };
static fromJSON(json: string) { return new MyClass() };
}
or this:
abstract class MyAbstractClass {
abstract toJSON(): string;
abstract static fromJSON(json: string): MyAbstractClass
// ------> ~~~~~~
// not valid TS, sorry
}
you'd have a way to do this. Alas, neither of those features have been implemented as of TS4.1, so the only way to proceed is with workarounds.
Let's take the MyInterface and MyInterfaceConstructor interfaces I wrote above and see what we can do with them. Right now we can only constrain the instance side via implements MyInterface:
class MyClass implements MyInterface {
toJSON() { return "" };
static fromJSON(json: string) { return new MyClass() };
}
We can't write static implements MyInterfaceConstructor. But we can make a no-op helper function called staticImplements and call it:
function staticImplements<T>(ctor: T) { }
staticImplements<MyInterfaceConstructor>(MyClass); // okay
The fact that this compiles with no error is your guarantee that MyClass's static side is acceptable. At runtime this is a no-op, but at compile time this is valuable information. Let's see what happens when we do it wrong:
class MyClassBad implements MyInterface {
toJSON() {
return ""
}
}
staticImplements<MyInterfaceConstructor>(MyClassBad); // error!
// ------------------------------------> ~~~~~~~~~~
// Property 'fromJSON' is missing in type 'typeof MyClassBad'
// but required in type 'MyInterfaceConstructor'.
class MyClassAlsoBad implements MyInterface {
static fromJSON(json: string) {
return 123 // Wrong type
}
toJSON() {
return ""
}
}
staticImplements<MyInterfaceConstructor>(MyClassAlsoBad); // error!
// ------------------------------------> ~~~~~~~~~~~~~~
// The types returned by 'fromJSON(...)' are incompatible between these types.
function validMyClass(ctor: MyInterfaceConstructor) { }
Those are the errors you were looking for. Yes, the static constraint and the errors are not located exactly where you want them in the code, but at least you can express this. It's a workaround.
There are other versions of this workaround, possibly using decorators (which are sort of deprecated or on hold until decorator support in JS is finalized), but this is the basic idea: try to assign the constructor type to the "static part" of your interface and see if anything fails.
Playground link to code

Adding abstract/inheritable static constructor helper method on TypeScript class

I have an abstract API class with some base api logic for our app that I am extending in various other classes:
interface Options {
authToken?: string;
ip?: string;
endpointPrefix?: string;
}
abstract class AbstractApi {
private authToken?: string;
private ip?: string;
private endpointPrefix?: string;
constructor({
ip = undefined,
authToken = undefined,
endpointPrefix = '',
}: Options) {
this.authToken = authToken;
this.ip = ip;
this.endpointPrefix = endpointPrefix;
}
protected async get() {
// ...
}
protected async post() {
// ...
}
}
class TodosApi extends BaseApi {
constructor() {
super({ endpointPrefix: '/todos' });
}
getTodos(/* ... */) {
this.post(/* ... */);
}
}
I would like to ensure that every instance of AbstractApi has a static convenience constructor: .new(). e.g. const todos = await TodosApi.new().getTodos().
How would I ensure that each child has this method automatically?
Some things that I have tried but that haven't worked:
static create(data) {
return new this.constructor(data);
}
static create(data) {
return new this(data)
}
It's hard to know exactly what you mean by "haven't worked". Do you mean you got a compiler error? Or that it didn't work at runtime? Or both? Anyway, I'll try to proceed and hopefully address the problem you're facing:
Since the this context of static methods will be a class constructor and not a class instance, you're going to need to use new this() and not new this.constructor(). Assuming that concrete subclasses will have different constructor argument list requirements, your static create() method should probably take a rest parameter. So at runtime you want it to look like this:
static create(...args) {
return new this(...args);
}
Now we just need to come up with appropriate typings for that method to satisfy the compiler and produce reasonable behavior when you try to use it. For instance methods, the obvious solution involves what's called polymorphic this, where the this type represents "whatever the current class instance type is", even for subclasses. Unfortunately, there is (as of TS3.7) no analogous polymorphic this for static methods. Instead, we need to use a workaround involving generics and a this parameter:
static create<A extends any[], R>(this: new (...args: A) => R, ...args: A) {
return new this(...args);
}
That means create will be called as a method on some concrete constructor type which takes some argument list A and produces some instance type R. (You could constrain R to extend AbstractApi but it's not needed here). Let's see if it works:
TodosApi.create().getTodos(); // okay
TodosApi.create("bad argument"); // error: expected 0 arguments, got 1
AbstractApi.create({}); // error: this context of create does not match
So your desired use works fine. You can see that TodosApi.create() requires the same argument list as the TodosApi constructor (namely, no arguments) so you get an error if you pass it the wrong thing. And the compiler gives you an error if you try to call create() directly on AbstractApi, since create() requires its this context to be a constructable thing and AbstractApi is abstract and therefore not constructable.
Does that work for you? Hope that helps; good luck!
Link to code

typing variable when using subclasses in TypeScript (RxJS)

I have a problem while writing tests for Angular using RxJS.
I have a variable which is used as a mocking provider (actions$) which is typed as "Observable". Now I assign an instance of a subclass to it (ReplaySubject). But now the method "next" is unknown (at least in Typescript) because it's not provided by "Observable" as it is from one of the subclasses "Subject".
How can I type my variable "actions$" correctly or how can I cast correctly to fix the error message?
online example: https://ngrx.io/guide/effects/testing
see line 12 vs 41
RxJS implementation
export declare class ReplaySubject<T> extends Subject<T> {
...
}
export declare class Subject<T> extends Observable<T> implements SubscriptionLike {
...
next(value?: T): void;
...
}
my code
let actions$: Observable<any>;
actions$ = new ReplaySubject(1);
// Property 'next' does not exist on type 'Observable<any>'.
actions$.next(new someThing());
You should type actions$ as Subject<any> or—if you want even more specificity—ReplySubject<any>. Subject extends Observable, so it will work for hot and cold functions if you're using jasmine-marbles.
As for the documentation you referenced, I suspect the actions being typed as Observable is a mistake in print.

Angular 2 service calls method from component

Is it even possible to let a service call an component Method?
myapp.component
export class MyAppComponent {
public value;
...
public setValue(payload){
this.value = payload;
}
}
myapp.service
#Injectable()
export class MyAppService {
private myAppComponent: MyAppComponent;
private apiClientService: ApiClientService
// ...
After i make an PUT http call, the body from the response is my new "value"
// ...
putValue(payload: JSON){
return this.apiClientService.putAPIObject(payload).then((response) => {
this.myAppComponent.setValue(response);
});
}
}
This results in an ERROR Error: Uncaught (in promise): TypeError: Cannot read property 'setValue' of undefined.
Can someone explain what im doing wrong?
Thanks in advance.
EDIT:
Since people complain about my approach, im totally fine to start from scratch if someone can explain me what is the best way to handle this problem.
I get values from an api, change them and i put them back to the api. I dont want to make a get call again, so i get the new data i want in the response of the Put call.
The call goes from component --> component service --> apiclient service
I guess the problem is that i have an extra service between the start and end point.
EDIT 2: I tried to avoid the component service and maked it work for me with only component --> apiclient service
Even this soultion is working for me at the moment I kind of dislike it, because I have to Copy and Paste a lot of code for the Same "Operation" with other objects from my api. For example I maked it work for the Picture Component, but I also need this for my Movie Component. Usally its a bad thing if I write the same code often in a project, or not?
There are at least a couple ways to solve this, but hopefully this gives you a start. Open to feedback and corrections.
Use an Observable
Let the service own knowledge of the value changes and emit changes. The component listens to an EventEmitter on1 the service to react to value changes. (See also: Creating and returning Observable from Angular 2 Service)
MyAppService
import { Subject } from 'rxjs/Subject';
#Injectable()
export class MyAppService {
private valueSource = new Subject<any>();
public valueUpdate$ = this.valueSource.asObservable();
putValue(payload: JSON){
return this.apiClientService.putAPIObject(payload).then((response) => {
/** here **/
this.valueUpdate$.next(response);
});
}
}
MyAppComponent
export class MyAppComponent {
public value;
private valueSubscription;
constructor(private _myAppService: MyAppService) {}
ngOnInit() {
/** and here **/
this._myAppService.valueUpdate$.subscribe((p) => this.setValue(p));
}
...
public setValue(payload){
this.value = payload;
}
}
Register the component
Answering the original question, the idea is to register the component with the service so that it can call the component as needed. You could pull a references through dependency injection but wouldn't recommend it (e.g. what if your original component reference is destroyed?)
MyAppService
#Injectable()
export class MyAppService {
private myAppComponent: MyAppComponent;
/** here **/
registerMyApp(myApp: MyAppComponent) {
this.myAppComponent = myApp;
}
putValue(payload: JSON){
return this.apiClientService.putAPIObject(payload).then((response) => {
this.myAppComponent.setValue(response);
});
}
}
MyAppComponent
export class MyAppComponent {
public value;
/** and here **/
constructor(myAppService: MyAppService) {
myAppService.registerMyApp(this);
}
...
public setValue(payload){
this.value = payload;
}
}
Thanks AJT_82 for noting that Angular does not want developers using EventEmitters on the service: What is the proper use of an EventEmitter?.

Creating TypeScript objects dynamically

Following a post regarding creating objects dynamically in TypeScript, I have the following code used as a factory to create an object from its name:
public createComponent(context: Object, componentName: string): ComponentRef<ComponentBase> {
this.viewContainer.clear();
var instance =
Object.create(context[componentName].prototype); // <-- fails
let componentFactory =
this.componentFactoryResolver.resolveComponentFactory(instance);
return <ComponentRef<ComponentBase>> this.viewContainer.createComponent(componentFactory);
}
I'm not entirely convinced I understand this window[suchAndSuch] syntax: what does it mean? Can't find any documentation for it.
In any event it seems that window[myClassName] is undefined, even though the class in question is defined in the same file. I have looked at the Javascript transpiled from the TypeScript, and the class in question is in scope.
I think.
Help!
-- UPDATE --
I have this code, which is part of an Angular 2 application, that is calling the above method to try to get an instance of a component, injectables and all:
export class CustomUserComponent implements OnChanges {
#Input() componentType: string;
#ViewChild(ComponentDirective) componentAnchor: ComponentDirective;
ref: ComponentRef<GalleriaComponentBase>;
ngAfterViewInit() {
this.ref = this.componentAnchor
.createComponent(window, this.componentType);
}
}

Categories

Resources