"subscribe()" is deprecated when using "of(false)" - javascript

I am trying to use of but my editor is saying that it is deprecated. How can I get this to working using of?
public save(): Observable<ISaveResult> | Observable<boolean> {
if (this.item) {
return this.databaseService.save(this.userId, this.item)
}
return of(false)
}
public componentSave() {
const sub = this.userService
.save()
.subscribe(val => {
// This callback shows the below error in the editor
})
}
When I use this, I get the following error in my editor:
#deprecated — Use an observer instead of a complete callback
'(next: null, error: (error: any) => void, complete: () => void): Subscription' is deprecated
Expected 2-3 arguments, but got 1.

Never mind I think I found the problem. Change the return type of the save function from
public save(): Observable<ISaveResult> | Observable<boolean>
to
public save(): Observable<ISaveResult | boolean>

Related

TypeScript Template Literals break generic constraints

I am trying to write a Sub-Pub class that supports events AND sub-events denoted by the following syntax:
publisher.on("message:general", ... ) // subscribe to all messages
publisher.on("message", ... ) // subscribe to messages in general
To do this, I am using TypeScript template literals.
The issue is that while it broadly works, it seems to break generic constraints. Am I doing something wrong?
Here is what it looks like so far:
TS Playground link for convenience
interface ChatEvents {
connect: {
user: string;
};
disconnect: {
user: string;
reason: "banned" | "timeout" | "leave";
};
}
declare type ChatEvent = keyof ChatEvents;
interface SubEvents extends Record<ChatEvent, string> {
connect: "general" | "watercooler" | "lobby";
disconnect: "voice" | "text";
}
declare type EventWithSubEvent<T extends ChatEvent> = `${T}${
| ""
| `:${SubEvents[T]}`}`;
// "connect" | "connect:general" | "connect:watercooler" | "connect:lobby"
declare type ChatConnectEvent = EventWithSubEvent<"connect">;
// "disconnect" | "disconnect:voice" | "disconnect:text"
declare type ChatDisconnectEvent = EventWithSubEvent<"disconnect">;
// So far so good!
// Let's write some generics:
declare function subscribeToEvent<E extends ChatEvent>(
event: E,
callback: (payload: ChatEvents[E]) => void
): void;
subscribeToEvent("connect", (payload) => {
// Correctly extracts matching type
type TPayload = typeof payload; // { user: string; }
});
declare function subscribeToSubEvent<E extends ChatEvent>(
event: EventWithSubEvent<E>,
callback: (payload: ChatEvents[E]) => void
): void;
subscribeToSubEvent("connect:general", (payload) => {
// Extracts all possible payload types instead of only the `connect` one
/*
{
user: string;
} | {
user: string;
reason: "banned" | "timeout" | "leave";
}
*/
type TPayload = typeof payload;
});
subscribeToSubEvent("connect:voice", () => {}) // Should fail but doesn't. `:voice` is a subevent of `disconnect`, not `connect`.
I think I found the solution.
The problem is that apparently template types do not distribute over unions
To work around the issue take a look at this simplified playground
The trick is to specify the template type with generic type extending from any (just to force the union distribution)
declare type EventWithSubEvent<T extends ChatEvent> = T extends any ? `${T}:${SubEvents[T]}` : never;
This is your fixed playground
Generally I've found that the less typescript has to work to deduce a generic type from arguments, the more generics just work (especially for providing decent intellisense aid). In this case if you setup the generic to be the exact text passed as the first argument (constrained to all valid options) then use a helper type to extract just the 'connect' | 'disconnect' part to use in the callback it will work much smoother. (playground)
interface ChatEvents {
connect: {
user: string;
};
disconnect: {
user: string;
reason: "banned" | "timeout" | "leave";
};
}
interface SubEvents extends Record<keyof ChatEvents, string> {
connect: "general" | "watercooler" | "lobby";
disconnect: "voice" | "text";
}
type AllChatEvents = {[K in keyof ChatEvents]: K | `${K}:${SubEvents[K]}`}[keyof ChatEvents]
// helper to extract the 'connect'|'disconnect' from an event
type ExtractEvt<T extends AllChatEvents> = T extends keyof ChatEvents ? T : T extends `${infer A}:${string}` ? A : never
declare function subscribeToSubEvent<E extends AllChatEvents>(
event: E,
callback: (payload: ChatEvents[ExtractEvt<E>]) => void
): void;
subscribeToSubEvent("disconnect:voice", (payload) => {
console.log(payload.reason) // detects that 'disconnect' is the payload type
});
subscribeToSubEvent("connect:voice", () => {}) // fails properly
As a bonus, because the generic constraint is all valid strings to be passed intellisense will give very useful results compared to other generics setups where it can't figure out what the correct behaviour is until you've already typed it:

Typescript: Usage of Map<> with strictNullChecks

Given the following simple class:
class Observer {
private subscribers: Map<string, Array<((data: any) => void)>> = new Map();
public subscribe(event: string, callback: (data: any) => void) {
if (!this.subscribers.has(event)) {
this.subscribers.set(event, []);
}
this.subscribers.get(event).push(callback); //tsc says: Object is possibly 'undefined'
}
}
Furthermore, in tsconfig.json, the flags strictNullChecks and strict are enabled.
Although subscribers is checked for a key of the current event, the typescript compiler complains with the error message shown above (this.subscribers.get(event) is possibly undefined).
If I'm not completely wrong, this.subscribers.get(event) can never be undefined in this case.
How can I get rid of that message?
Typing of Map explicitly states that get can result in undefined:
interface Map<K, V> {
...
get(key: K): V | undefined;
...
}
That's why you're getting error with strictNullChecks enabled.
You can use non-null assertion operator to inform the compiler that you're sure that it actually has value:
this.subscribers.get(event)!.push(callback);
Another option (the better one in my opinion) is to refactor your code in following way:
public subscribe(event: string, callback: (data: any) => void) {
let callbacks = this.subscribers.get(event);
if (!callbacks) {
callbacks = []
this.subscribers.set(event, callbacks);
}
callbacks.push(callback);
}

How to attach class method as jQuery EventHandler in TypeScript

I'm converting some JavaScript code to TypeScript and I can't figure out how the signature of an jQuery EventHandler should look like.
This is what I had in JavaScript but simplified to more generic terms (where I have some sort of pub-sub or observable pattern using custom events distributed via an element):
Observer.prototype._subscribe = function() {
this._row.bind('onItemChanged', this, this._onChangedHandler);
};
Observer.prototype._onChangedHandler= function(event, someString, someObject) {
var that = event.data;
if (someString === '42') {
that.coolMethod(someObject);
} else if (someString === '69') {
that.otherCoolMethod(someObject);
}
};
In another prototype I would call trigger to notify the observer with the event and at least 2 parameters of data (someString and someObject):
Subject.prototype.foo = function() {
// Trigger the event so Observer will be notified and pass in the string and the object (or any data whatsoever)
this._element.trigger("onItemChanged", ["42", this._data]);
};
Now I'm having a hard time to write this in TypeScript but this is what I thought it should lok like:
export class Observer {
private _subscribe (): void {
this._element.bind('onItemChanged', this, this._onChangedHandler);
}
private _onChangedHandler(event: JQueryEventObject, someString: string, someObject: FooBarClass) {
let that = event.data as Observer;
if (someString === '42') {
that.coolMethod(someObject);
} else if (someString === '69') {
that.otherCoolMethod(someObject);
}
}
}
This TypeScript doesn't compile but gives the following error:
Argument of type '(event: JQueryEventObject, someString: string, someObject: FooBarClass) => void' is not assignable to parameter of type 'EventHandler | EventHandlerBase>'.
Type '(event: JQueryEventObject, someString: string, someObject: FooBarClass) => void' is not assignable to type 'EventHandlerBase>'.
Types of parameters 'event' and 't' are incompatible.
Type 'Event' is not assignable to type 'JQueryEventObject'.
So how should the signature (parameter types) of the eventhandler look like?
P.S. If you like you can rewrite using jQuery::on() instead of jQuery::bind();
P.P.S. I'm not interested in the various ways how to get the proper 'this' unless I'm realy on the wrong path here.
Edit (after first replay):
As suggested I rewrote my TypeScript to make my problem more clear. I know of the fat arrow notation to get the right 'this' but like I previously said, I'm not interested in this. I want my method to be typesafe so, how should the signature of my handler method look like?
This indeed works:
private _subscribe (): void {
this._element.on('onItemChanged', () => this._onChangedHandler);
}
private _onChangedHandler(event: JQueryEventObject, someString: string, someObject: FooBarClass) {
}
But then I expected that this should work too:
private _subscribe(): void {
this._element.on('onItemChanged', (event: JQueryEventObject, someString: string, someObject: FooBarClass) => { this._onChangedHandler(event, someString, someObject); });
}
But I still can't get the correct Event type for the first parameter to get typesafety. With the 'any' type it does compile:
private _subscribe(): void {
this._element.on('onItemChanged', (event: any, someString: string, someObject: FooBarClass) => { this._onChangedHandler(event, someString, someObject); });
}
By putting the 'any' type and running in debugger I finally found out during runtime that the first parameter is of the 'JQuery.Event' type which also compiles correctly in TypeScript when using the type definitions of jQuery. Too bad tsc couldn't tell me this sooner at compile time (or I misinterpreted the error show in the question).
So this is what it should look like (regardless of using bind, fat arrow or your own way of preserving the right context/this):
export class Observer {
private _element: JQuery;
private _subscribe(): void {
this._element.on('onItemChanged', (event: JQuery.Event, someString: string, someObject: FooBarClass) => { this._onChangedHandler(event, someString, someObject); });
}
private _onChangedHandler(event: JQuery.Event, someString: string, someObject: FooBarClass): void {
}
}
Although this compiles and gives some sort of typesafety, I'm a bit dissapointed since I can attach handlers with the wrong signature and it still compiles. I guess that is just how attaching eventhandlers work. At least you can call your methods directly with typesafety and still use them as handlers.
So for others running into this issue the signature should look like this:
export class Observer {
private _element: JQuery;
private _subscribe(): void {
this._element.on('anyevent', () => this._yourOwnHandler);
}
private _yourOwnHandler(event: JQuery.Event, ...args: any[]): void {
}
}
Where the '...args' can be anything you like that will give you the number and types of parameters you want.
P.P.S. I'm not interested in the various ways how to get the proper
'this' unless I'm realy on the wrong path here.
I know you don't want to hear this, but when you are dealing with events, you really do need to take care of your scope.
this._element.bind('onItemChanged', this, () => {
this._onChangedHandler();
});
And I also recommend moving to on as bind is deprecated.
This feels like an odd answer as I think you suspected both of these already?

Typescript: Setting members of object via callback fails strangely

I have a very strange issue for which I did not find the cause yet. I try to show a textbox component in Angular 2, which you can give a message, a label for the button and a callback that is invoked, when the button is clicked.
Here is my component:
#Component({
selector: 'text-box',
templateUrl: './textbox.component.html',
styleUrls: ['./textbox.component.styl']
})
export default class TextBoxComponent implements AfterViewInit {
content: String;
btnCaption: String;
callback: () => void;
constructor(#Inject(TextBoxService) private service: TextBoxService) {
}
ngAfterViewInit(): void {
this.service.init(this.show);
}
public show(message: String, btnCaption: String, callback: () => void) {
this.content = message;
this.btnCaption = btnCaption;
this.callback = callback;
// (1)
// set opacity 1
}
public btnOnClick() {
// (2)
this.callback();
this.dismiss();
}
public dismiss() {
// set opacity 0
}
}
Components are Singletons and cannot be injected, so you cannot simply invoke show() on the component from the outside. Therefore I added a service and put a reference to the method into it (see the component's ngAfterViewInit() method):
#Injectable()
export default class TextBoxService {
private showCallback: (m: String, b: String, c: () => void) => void;
public init(showCallback: (m: String, b: String, c: () => void) => void) {
this.showCallback = showCallback;
}
public show(message: String, btnCaption: String, callback: () => void) {
this.showCallback(message, btnCaption, callback);
}
}
It is invoked by another service like this:
this.textBoxService.show(
'Wollen Sie einen Kredit aufnehmen?',
'Ja', () => {
ziel.activate();
this.activatedEvents.set(event, ziel);
this.inputService.kreditInput.summe = ziel.getPrice() - ziel.getLiquidFunds();
this.inputService.kreditInput.startdatum = date;
});
However, the text does not update when the button above is called, neither is the listener attached to the button (showing this.callback() is not a function). I debugged it (put console.log()s on (1) and (2) in the component) and found out, that both methods are correctly called. On (1) the members content, btnCaption and callback are correctly set - but on (2), these members are undefined!
I tried replacing the fat-arrow-syntax with function()-syntax but with no success. I also tried hard-coding the string inside show() but when accessing it via buttonClick, it's still undefined.
It seems like on (1) and (2) there are two different objects accessed. I have no idea what the reason for this behaviour could be. Any ideas?
Class prototype methods that are supposed to be passed as callbacks by design (as in this.service.init(this.show)) should be defined as arrow class properties:
public show = (message: String, btnCaption: String, callback: () => void) => { ... };
Or be bound on class construction:
constructor(#Inject(TextBoxService) private service: TextBoxService) {
this.show = this.show.bind(this);
}
Alternatively, decorators may be used for neater syntax, e.g. #autobind from core-decorators.

$resource Callback not Being Invoked Angular

I have a method which makes a call to Angular's $resource. I am trying to test the return value of this method. This method should be simple enough to test.
Method Code
getCountries(): returnTypeObject {
var countries = this.$resource(
this.apiEndpoint.baseUrl,
{
'get': {
method: 'GET', isArray: false
}
});
return countries;
}
Unit Test
describe('module', (): void => {
beforeEach((): void => {
angular.mock.module('ngResource');
});
describe('getCountries...', (): void => {
it("should...", inject(
['Builder', (Builder: IBuilder): void => {
Builder.getCountries().get(
(value: any) => {
console.log(value);
}, (error: any) => {
console.log(error);
});
}]));
});
});
Now in the above code when I run my test the none of the callbacks ever get called. Neither "value" nor "error" ever get printed. I've been looking for a solution for quite a while now but haven't been able to find anything.
Keep in mind that I do not want to mock $resource, instead I want to test what is returned by getCountries().get() and then I want to use the callbacks to test the return value.

Categories