I have interface and object which represents this interface:
MyInterface {
variableOne: string;
variableTwo: boolean;
variableThree: boolean;
...
}
Also I have function toggleFunction(x) which gets a key and toggles it in my object.
Which type do I need to set in param x in my toggle function to use it for toggle all boolean key in my object?
For example I need something like: toggleFunction(x: TYPE?)
Use keyof.
function toggleFunction(x: keyof MyInterface): void {
// ...
}
You can use a conditional mapped type that I usually call KeysMatching:
type KeysMatching<T, V> = { [K in keyof T]: T[K] extends V ? K : never }[keyof T];
And then toggleFunction() can be defined like this:
declare const o: MyInterface; // or wherever it comes from
function toggleFunction(x: KeysMatching<MyInterface, boolean>) {
o[x] = !o[x];
}
toggleFunction("variableTwo"); // okay
toggleFunction("variableThree"); // okay
toggleFunction("variableOne"); // error!
Hope that helps; good luck!
Link to code
Related
For example in my case I need to reuse the type of a component in React (React.FC) but I want it with optionals props by using Partial<T>.
Is there a way to "get" the Generic <T> from that component type (type TypeTargetComponent = React.FC<T>) ? I would like to do something like : React.FC<Partial<XXXX<typeof TargetComponent>>> where XXXXis the "method" to get what I want.
type ExtractPropsType<T> = T extends React.FC<infer Type> ? Type : never;
This should work if you type typeof TargetComponent
You can use the infer keyword.
Something like this should work:
Updated the answer to include example and fixed the type inferrence issue.
type FC<T> = { data: T }
type InferredValueOf<Comp extends FC<any>> = Comp extends FC<infer T> ? T : never
type Derived<TargetComponent> = TargetComponent extends FC<any> ? FC<Partial<InferredValueOf<TargetComponent>>> : never
type TestType = Derived<FC<{ testValue: "value" }>>
const test: TestType = {
data: {
// testValue is optional now
}
}
this is my Interface file :
export interface ListCount {
centre?: string;
cause?: string;
totalTime?: number;
}
I am trying to make reduce to array of objects with pipe transform :
export class SumPipe implements PipeTransform {
transform(items: ListCount[], attr: string): number {
return items.reduce((a, b) => a + b[attr], 0);
}
}
and in compent HTML I looking to make sum of totalTime
{{ (delayCount$ | async) | sum:'totalTime'}}
But I have this error :
error TS7053: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'ListCount'.
Then I change the param attr: string to attr: keyof ListCount
and still have this error :
error TS2365: Operator '+' cannot be applied to types 'number' and 'string | number'.
and
error TS2532: Object is possibly 'undefined'.
Any help please
I would suggest the following:
restrict attr to point to numeric properties
take into account that properties can be optional. Use zero for those.
alternatively filter out missing (undefined) values.
export interface ListCount {
centre?: string;
cause?: string;
totalTime?: number;
otherTime?: number;
}
type NumericKeys<T> = {
[P in keyof T]: T[P] extends number ? P : never;
}[keyof T];
type NumericListCountKey = NumericKeys<Required<ListCount>>;
class SumPipe {
transform(items: ListCount[], attr: NumericListCountKey): number {
const elemes = items.map(elem => elem[attr]);
return elemes.reduce((prev: number, currentVal) => prev + (currentVal ? currentVal : 0), 0);
}
}
// alternatively, with filter
class SumPipe2 {
static isNumber(n: number | undefined): n is number {
return n != null;
}
transform(items: ListCount[], attr: NumericListCountKey): number {
const elems = items.map(elem => elem[attr])
.filter(SumPipe2.isNumber);
return elems.reduce((prev: number, currentVal) => prev + currentVal, 0);
}
}
Playground link
Ok, so your code compiles without issues and works. Here is a working stackblitz. If it fails on your local environment, perhaps try checking TS version, try restarting the Angular CLI, look for other issues or at least provided information on which line exactly the error is thrown.
There are a few issues with your code though.
As others have noted, passing any attr to the pipe and using the bracket notation to get the property value makes no sense. Only object accepted by the pipe is ListCount[], so it HAVE to be totalTime that gets summed as it's the only numeric property. It would make sense if you made more generic pipe that would accept any[] though.
Your pipe is not guarded for your use-case. totalTime is an optional property, meaning that if any item doesn't have totalTime defined (which it can, according to your interface) the pipe will return NaN. Not sure if that's the desired behavior.
According to this article, reduce() has an optional index, which points to the current index in the array. Knowing this, I'd opt for the following solution, leaving out attr entirely.
return items.reduce((a, b, index) => a + b[index].totalTime, 0);
I'm trying to define a Map type using Typescript generics. what I want is something like this
EntityMap<U, V>, U can be only string or number
this is what I have done so far
export type EntityMapKey = string | number;
export type EntityMap<U, V> = { [K in EntityMapKey]: V};
but when we use it we can put anything as U like below
interface Jobs {
list: EntityMap<Array, JobFile>
}
I want to restrict using any type other than string or number as U, how can we achieve this?
am I missing anything?
U in your EntityMap is not used, the correct implementation should be
export type EntityMapKey = string | number;
export type EntityMap<U extends EntityMapKey, V> = { [K in U]: V};
interface Jobs {
list: EntityMap<string, any>
}
I have a generic reducer that's return type is TableState.
const reducerFactory = ..... = (): TableState => ....
TableState:
interface TableState {
filters: Filter;
data: TableData;
isLoading: boolean;
}
Each component's that uses this reducer factory implements TableState.
For example
interface SalesReportState implements TableState {
someCommonField: string;
}
App build fails as the types don't match. I could do someCommonField?: string; but someCommonField should be obligatory.
Is there a Typescript feature that the return type just checks if the table type implements Table State? So the return type would be some type that makes sure it is an instance of TableState, not exactly TableState type.
If you want to know if an object implements TableState, you can use:
instanceof
if (obj instanceof TableState) // returns true if object implements TableState
only works with abstract class
type-guards. Example:
let obj = {}; // an object you want to check
/**
* A discriminator is a property which determines if the object in question is
* a TableState
*/
function isTableState(obj: any): obj is TableState {
return obj &&
obj.filter &&
obj.data &&
obj.isLoading && typeof(obj.isLoading) === 'boolean';
}
// check phase
if (isTableState(obj)) {
// object is automatically casted to TableState
}
PS: If you want to extend an interface with another interface, use extends
EDIT: I don't know the Filter and TableData type, is that an interface or a class? The answer may change again, depending on the type
I have a mobx class which has an observable variable named dataObject. This observable variable is an object and its structure is:
{
name:string;
dataType:string;
value: string;
description:string;
.
.
.
.
.
..... total around 45 such properties
}
Each object property has an action method to update it. For example
#action updateName = (name:string) =>{
this.dataObject.name = name;
}
It is difficult to maintain all such 45 action methods in same class. Is there any way to write these methods in a separate ts file and import them to the current file?
EDIT: Adding a part of dataObject interface along with few of its action methods
interface IDataObject{
name:string;
dataType:string;
value:any;
valueType:VALUE_TYPE|any;
hasCustomValueType:boolean;
customValueType:string|undefined;
description:string;
isRegistered:boolean;
associatedVariableDetails:IVariableDetails[];
hasMultipleValues:boolean;
associatedService:SERVICE;
}
enum VALUE_TYPE{
MAJOR = 'major',
MED = 'med',
MINOR = 'minor'
}
enum SERVICE{
PUSH_IN = 'pushin',
PULL_OUT = 'pullout'
}
interface IVariableDetails{
variableName:string;
varirbleDataType:string;
variableValue:any;
hasArray:boolean;
isDeletable:boolean;
}
//////////////
method to update dataType
#action updateDataType = (dataType:string) =>{
this.dataObject.dataType = dataType;
if(dataType === 'string'){
this.dataObject.value = ''
}
else if(dataType === 'boolean'){
this.dataObject.value = false
}
else if(dataType === 'integer'){
this.dataObject.value = 0
}
}
methods to modify associatedVariableDetails
#action addVariableDetails = (variableDetails:IVariableDetails) =>{
this.dataObject.associatedVariableDetails.push(variableDetails);
}
#action updateMultipleValueState = (hasMultipleValues:boolean) =>{
this.dataObject.hasMultipleValues = hasMultipleValues;
if(!hasMultipleValues){
this.dataObject.associatedVariableDetails = this.dataObject.associatedVariableDetails[0];
}
}
Create an interface for the object that you want as the observable and add an index signature.
This looks something like this:
interface foo {
[key: string]: type;
bar: string;
}
Then pass the key to your update action and update the value like object[key] = newValue.
Example:
#action update = (key: string, value: type) => {
object[key] = value;
}
This will allow you to use one single function to patch any value into your observable object.
This is a good article that explains index signatures in depth: https://basarat.gitbooks.io/typescript/docs/types/index-signatures.html
To answer your original question though. You could create 45 functions and export all of them in an object and import them into this file, but that solution may be overkill and tedious to maintain if types or fields change. I can show an example of that if this solution does not work. The solution I have explained above is the more efficient way of handling object patches.
EDIT:
I apologize for the delay in editing my answer. I have made edits to your code above showing the approach I would be taking with this kind of object you have. Here is the edit:
interface IDataObject{
[key: string]: string | boolean | VALUE_TYPE | SERVICE | IVariableDetails[];
name: string;
dataType: string;
value: any; // THIS SHOULD BE STRICTLY DEFINED
valueType: VALUE_TYPE|any; // THIS SHOULD ALSO BE STRICTLY DEFINED
hasCustomValueType: boolean;
customValueType?: string;
description: string;
isRegistered:boolean;
associatedVariableDetails: IVariableDetails[];
hasMultipleValues: boolean;
associatedService: SERVICE;
}
enum VALUE_TYPE{
MAJOR = 'major',
MED = 'med',
MINOR = 'minor'
}
enum SERVICE{
PUSH_IN = 'pushin',
PULL_OUT = 'pullout'
}
interface IVariableDetails{
variableName:string;
varirbleDataType:string;
variableValue:any;
hasArray:boolean;
isDeletable:boolean;
}
#action updateField = (key: string, value: string | boolean | VALUE_TYPE | SERVICE) => {
this.dataObject[key] = value;
}
#action addVariableDetails = (variableDetails: IVariableDetails) => {
this.dataObject.associatedVariableDetails.push(variableDetails);
}
#action updateMultipleValueState = (hasMultipleValues: boolean) => {
this.dataObject.hasMultipleValues = hasMultipleValues;
if(!hasMultipleValues){
this.dataObject.associatedVariableDetails = this.dataObject.associatedVariableDetails[0];
}
}
What I have done here is exposed a single method to manipulate the semi primitive values that do not need extra work after, they just want new values. Then I would create more complex #actions as needed based on the type of work you need to do on your object. As shown the updateField action does not allow you to use it with a VariableDetails type of parameter which will make it so typescript complains if you try to send a variableDetails parameter to that action.
I have also noted that the any attributes should definitely be some type if the rest of the object has strict typings. If you are going to have any types in your object you might as well make an interface like interface IAny { [key: string]: any; } and use that everywhere.
I hope this can be of help, and again I apologize for the gap in responses.