In Angular 7, I was trying to create a custom Aysnc validator which checks van item already exists or not and if exists it will show error as part of form validation.
validate(
ctrl: AbstractControl
): Promise<ValidationErrors | null> | Observable<ValidationErrors | null> {
return this.itemService.isItemExists(this.listId, ctrl.value).pipe(
map(isExists => (isExists) ? { alreadyExits: true } : null),
catchError(() => null)
);
}
And my .itemService.isItemExists() methods as below
isItemExists(listId: string, itemNumber: number) {
const url = "http://localhost:41469/isValid?listId='abc'&itemNumber=6";
return this.http.get<boolean>(url);
}
Don't mind URL, It is proper in my code. But getting error
"SyntaxError: Unexpected end of input, Array.map ()...."
How to fix this issue?
Related
I am learning TypeScript.
The contents of todoItem.ts:
export class TodoItem {
constructor(
public id: number,
public task: string,
public complete: boolean = false
) {}
printDetails(): void {
console.log(
`${this.id}\t${this.task} ${this.complete ? "\t(complete)" : ""}`
);
}
}
The contents of todoCollection.ts:
import { TodoItem } from "./todoItem";
export class TodoCollection {
private nextId: number = 1;
constructor(public userName: string, public todoItems: TodoItem[] = []) {}
addTodo(task: string): number {
while (this.getTodoById(this.nextId)) {
this.nextId++;
}
this.todoItems.push(new TodoItem(this.nextId, task));
return this.nextId;
}
getTodoById(id: number): TodoItem {
return this.todoItems.find((item) => item.id === id);
}
}
When compiling with tsc, I get the following error:
src/todoCollection.ts:17:5 - error TS2322: Type 'TodoItem | undefined'
is not assignable to type 'TodoItem'. Type 'undefined' is not
assignable to type 'TodoItem'.
17 return this.todoItems.find((item) => item.id === id);
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Found 1 error.
Question
How to fix this error? I really have no idea because I cannot spot any peculiarity. Everything looks fine.
Array#find may return undefined if the predicate function does not return a truthy value for any element of the array. Typescript is complaining because you've told it you will be returning a TodoItem in the method header, but you actually attempted to return something that might be a TodoItem or might be undefined (as far as static analysis can tell).
You have a few options on how to resolve this:
Add | undefined to your return type, eg
getTodoById(id: number): TodoItem | undefined {
Add some other flow control when the item isn't found, such as throwing an exception
getTodoById(id: number): TodoItem {
const result = this.todoItems.find(item => item.id === id)
if (!result) {
throw new Error("Not Found!");
}
return result;
}
Use the not-null assertion operator. If you know for a fact that this will always find a result, you can simply tell TS that the return value will never be null or undefind by using the ! postfix operator.
return this.todoItems.find(item => item.id === id)!
Disable strict null checks in your ts config. I don't recommend this option, but typescript can be used for as little or as much as you'd like by configuring options in tsconfig.json.
I am getting a typescript error from the following code:
if (this.$route?.query?.domainName) {
this.setDomain(this.$route.query.domainName);
}
The above code throws the following error:
Typescript - Argument of type 'string | (string | null)[]' is not
assignable to parameter of type 'string'
if (this.$route?.query?.domainName) {
this.setDomain(this.$route.query.domainName);
^
}
My setDomain function just accepts a parameter of type string like so:
setDomain(domain: string) {
this.domainName = domain;
}
I don't understand how the parameter could ever be null as I am checking the object property exists using the nested ? after the object properties in the if statement. Why would it be throwing this error?
In your code, domainName could still be something other than a string - an array ((string | null)[]). Your conditional guard is only verifying that it's not a falsey value, not that it's a string.
If you check that it's a string, it should work. Note that this example will allow an empty string, which your current code won't.
declare const $route: null | { query: null | { domainName: null | string | (string | null)[] } }
declare const setDomain: (domain: string) => void;
if ($route?.query?.domainName) {
// reproduces your error
setDomain($route.query.domainName);
}
if (typeof $route?.query?.domainName == "string") {
// no error
setDomain($route.query.domainName);
}
See in the typescript playground
What I can't figure how is how ParsedAuthorizationResponse is implemented and how I can set it's values differently. Eventually, I want to return a certain value/type as a promise.
I thought this would work but it doesn't
let PAR;
if (status === 'COMPLETED') {
PAR = new ParsedAuthorizationResponse('AUTHORIZED')
} else throw new Error('Authorization failed')
return Promise<PAR>;
}
//Also tried this but didn't work
type ParsedAuthorizationResponse PAR = 'AUTHORIZED';
return new Promise<PAR>;
ParsedAuthorizationResponse isn't a class (which is why new doesn't work), it's a type. In this case, it's a bunch of different possible object shapes. Let's have a look
export type ParsedAuthorizationResponse =
| IAuthResponse<
{ processorTransactionId: string },
'AUTHORIZED' | 'CANCELLED' | 'SETTLING' | 'SETTLED'
>
| IAuthResponse<{ declineReason: string }, 'DECLINED'>
| IAuthResponse<{ errorMessage: string }, 'FAILED'>;
Right, this tells us that ParsedAuthorizationResponse is just a synonym for any one of three different parametrizations of IAuthResponse. Unfortunately, that doesn't tell us much until we know what IAuthResponse looks like. Which we do, fortunately:
type IAuthResponse<T, U extends TransactionStatus> = T & {
transactionStatus: U;
};
OK, this gets more concrete. TransactionStatus is just a synonym for a couple of literal strings, and T & { transactionStatus: U; } is a type conjunction, meaning that the result is a type that is just like the type T, plus a field transactionStatus which is of type U (which with U extends TransactionStatus is constrained to be one or more of the strings we know as TransactionStatus).
So what does something like IAuthResponse<{ errorMessage: string }, 'FAILED'> mean? Well, looking at IAuthResponse, it expands to this:
{ errorMessage: string } & { transactionStatus: 'FAILED' }
which is the same as
{ errorMessage: string; transactionStatus: 'FAILED' }
Which means that ParsedAuthorizationResponse actually looks like this:
type ParsedAuthorizationResponse =
| { processorTransactionId: string; transactionStatus: 'AUTHORIZED' | 'CANCELLED' | 'SETTLING' | 'SETTLED' }
| { declineReason: string; transactionStatus: 'DECLINED' }
| { errorMessage: string; transactionStatus: 'FAILED' }
So, creating an object conforming to ParsedAuthorizationResponse is really easy, it's just object literals:
If it's failed, it's an object with transactionStatus: 'FAILED' and an arbitrary errorMessage string
If it's declined, you have transactionStatus: 'DECLINED' and an arbitrary declineMessage string
Else, it has transactionStatus set to one of 'AUTHORIZED', 'CANCELLED', 'SETTLING', or 'SETTLED', plus a processorTransactionId which is a string that I assume you get along with the status of the transaction.
Your example would end up something like this:
async authorize(
request: RawAuthorizationRequest<ClientIDSecretCredentials, PayPalOrder>,
): Promise<ParsedAuthorizationResponse> {
// The response needs to contain status and transaction ID
const { status, processorTransactionId } = JSON.parse(await response) as { status: string; processorTransactionId: string; }
switch (status) {
case 'COMPLETE':
return { transactionStatus: 'AUTHORIZED', processorTransactionId };
case 'VOIDED':
return { transactionStatus: 'CANCELLED', processorTransactionId };
// TODO: Fill in more cases here...
default:
// Unknown status, hopefully shouldn't happen, but for the sake of
// argument, let's pretend that failing the transaction is a reasonable
// thing to do here, just to show how to return an error message
return { transactionStatus: 'FAILED', errorMessage: 'Unknown status: ' + status };
}
}
Returning a promise type is very easy and is not different then returning a promise of a primitive.
Any value you return from an async function will be wrapped with a promise, so:
const returnsANumber = async (): Promise<number> => {
return 42
}
type Foo = {
foo: true
}
const returnsAFoo = async (): Promise<Foo> => {
return {
foo: true
}
}
TS playground
In your case just return a ParsedAuthorizationResponse
First, as #thedude said, because your function is already async there is no need to return a Promise, whatever you return will be automatically turned into a Promise.
Now to answer: "How to return a ParsedAuthorizationResponse".
Starting with the (part of) defintion:
export type ParsedAuthorizationResponse =
| IAuthResponse<
{ processorTransactionId: string },
'AUTHORIZED' | 'CANCELLED' | 'SETTLING' | 'SETTLED'
>
(I'm only going to do this logic with one example of ParsedAuthorisationResponse in the hope you can do the rest)
This says that it's an IAuthResponse<T, U> where T is { processorTransactionId: string } and U is 'AUTHORIZED' | 'CANCELLED' | 'SETTLING' | 'SETTLED'
Slotting this into the defintion of IAuthResponse gives us
type IAuthResponse<T, U extends TransactionStatus> = {
processorTransactionId: string,
transactionStatus: 'AUTHORIZED' | 'CANCELLED' | 'SETTLING' | 'SETTLED';
};
Meaning an example of ParsedAuthorizationResponse is
{
processorTransactionId: "any old string",
transactionStatus: 'AUTHORIZED',
}
So to return it from your function use something like:
async authorize(
request: RawAuthorizationRequest<ClientIDSecretCredentials, PayPalOrder>,
):ParsedAuthorizationResponse {
//just imagine I'm gonna get a response that has a value of status in it
const { status } = JSON.parse(await response)
return {
processorTransactionId: status, // or whatever
transactionStatus: 'AUTHORIZED',
};
}
'run' gives error when adding the effects. Tried returning string, number etc. manually as well, but it does not work.
Effects code:
#Effect()
getRoles$: Observable<Roles[]> = this.dataPersistence.fetch(CreateActionTypes.GetRoles, {
run: (action: GetRoles) => {
return this.formService
.getRoles()
.pipe(map(roles => {
return new RolesLoaded(roles);
}));
},
onError: (action: GetRoles, error) => {
console.error(error);
return new RolesLoadError(error);
}
});
Error message:
ERROR in apps/app/src/app/+state/create.effects.ts(32,5):
error TS2322:
Type '(action: GetRoles) => Observable' is not
assignable to type '(a: GetRoles, state?: CreatePartialState) => void
| Action | Observable'.
Type 'Observable' is not assignable to type 'void | Action | Observable'.
An #Effectshould return an observable of Action not a Action[], just Action.
In your case, it seems to be a mix up between multiples concepts.
Here is an example of #Effect
createProject$ = createEffect(() => this.actions$.pipe(
ofType(CREATE_PROJECT),
switchMap(({ name, id }) => this.myService.createProject(name, id).pipe(
concatMap(project => [
CREATE_PROJECT_SUCCESS(project),
NAVIGATE({ path: ['foo', project.id] })
]),
catchHttpError()
)),
));
it depends on what getRoles() returns . I had a similar error. if getRoles() returns promise don't use pipe.
return this.formService
.getRoles().then(()=>{
return {type: TYPEACTION}
})
I want to create a core dialog class, so that when adding a new dialog, TS
will resolve a dialog type (and it's return types) automatically, basing on the input given. I was able to achieve most of it, but I failed when it comes to the return values.
Each dialog return a Promise, promise result should base on the dialog type passed, eg.
when T is PromptDialogOptions return Promise<string | number>,
when T is ConfirmDialogOptions return Promise<boolean>,
whereas when T is MessageDialogOptions then return Promise<void>.
My actual code for creating a dialog (I have marked the lines that produce errors and explained them below due to the length):
let dialogs: DialogOptions[] = [];
newDialog<T extends DialogOptions, R extends InnerDialogType<T>>(dialog: T) : Promise<R> => {
const promise = new Promise<R>(res => {
// Set dialog resolver
dialog.resolver = res; // error #1 (see below)
});
// Create dialog close handler
dialog.closeHandler = (result: R) => { // error #2 (see below)
// Resolve a promise
dialog.resolver(result);
// Close the dialog
// this.closeDialog(dialog);
};
// Store dialog
dialogs = [...dialogs, dialog];
return promise;
}
This code produces two errors:
#1 line dialog.resolver = res;
Type '(value?: R | PromiseLike | undefined) => void' is not
assignable to type '((value?: void | undefined) => void) | ((value?:
string | number | undefined) => void) | ((value?: boolean | undefined)
=> void) | undefined'. Type '(value?: R | PromiseLike | undefined) => void' is not assignable to type '(value?: void |
undefined) => void'.
#2 line dialog.closeHandler = (result: R) => {
Type '(result: R) => void' is not assignable to type '((result: void)
=> void) | ((result: string | number) => void) | ((result: boolean) => void)'.
There's clearly an issue with the wrong types being used for handling result in the BaseDialog.
Question
How can I make BaseDialog.resolver and BaseDialog.closeHandler accept the generic type of R, that is inferred depending on the dialog result type being passed?
Some examples of what I want to achieve:
const confirmDialog : ConfirmDialogOptions = {
title: "Hello",
message: "I'm stuck. Will you help?",
type: DialogType.DIALOG_CONFIRM
};
newDialog(confirmDialog);
Expected result: Promise<boolean>
const dialog = {
title: "Invalid",
message: "Invalid dialog is bad!",
type: DialogType.DIALOG_MESSAGE
}
newDialog(dialog);
Expected result: error, since dialog doesn't inherit from BaseDialog
const promptDialog : PromptDialogOptions = {
title: "Hello",
message: "Say hello",
maxLength: 10,
type: DialogType.DIALOG_PROMPT
};
newDialog(promptDialog);
Expected result: Promise<string | number>
All types used
export const enum DialogType {
DIALOG_MESSAGE,
DIALOG_CONFIRM,
DIALOG_PROMPT
}
export interface BaseDialog<T> {
title: string;
message: string;
type: DialogType;
resolver?: (value?: T) => void;
}
export interface MessageDialogOptions extends BaseDialog<void> { }
export interface ConfirmDialogOptions extends BaseDialog<boolean> { }
export interface PromptDialogOptions extends BaseDialog<string | number> {
maxLength: number;
}
// Union dialogs
export type DialogOptions = MessageDialogOptions | PromptDialogOptions | ConfirmDialogOptions;
Why not simply?
function newDialog<R>(dialog: BaseDialog<R>): Promise<R> {
...
This would make it clear to the compiler that R and T are for the same dialog subtype (which your current function declaration fails to express, causing the compiler error you mention).
If you chose the union type to enforce that the dialog is actually one of the known types, you can better accomplish this with overloads:
function newDialog(dialog: MessageDialog): Promise<void>;
function newDialog(dialog: ConfirmDialog): Promise<boolean>;
function newDialog(dialog: PromptDialog): Promise<string | number>;
function newDialog<R>(dialog: BaseDialog<R>): Promise<R> {
// implementation here
}
BTW, as a caller, I'd probably prefer separate named methods to specifying the type with an enum:
newConfirmDialog({
title: 'Really launch rockets?',
text: 'Once launched, they can not be recalled.'
})