NDEFReader in TypeScript - javascript

I'm trying to use NDEFReader() for NFC scan/write in React.
This feature works from Chrome 81 (you can try it on your mobile in Chrome beta today on links below).
GoogleChromeNfcSample,
WhatWebCanDoTodayNfc
To enable this feature, you need to go into chrome://flags/ and enable Experimental Web Platform features.
The problem is that I can't make this work in React. I use create-react-app with TypeScript and console output:
Cannot find name 'NDEFReader'
I think that this causes a webpack check. I already tried change some settings in tsconfig.json but nothing worked. Does anyone know, how to enable experimental js/ts compilation, to enable this feature?

Web NFC folks provide TypeScript definitions at https://github.com/w3c/web-nfc/blob/gh-pages/web-nfc.d.ts
// Type definitions for Web NFC
// Project: https://github.com/w3c/web-nfc
// Definitions by: Takefumi Yoshii <https://github.com/takefumi-yoshii>
// TypeScript Version: 3.9
// This type definitions referenced to WebIDL.
// https://w3c.github.io/web-nfc/#actual-idl-index
interface Window {
NDEFMessage: NDEFMessage
}
declare class NDEFMessage {
constructor(messageInit: NDEFMessageInit)
records: ReadonlyArray<NDEFRecord>
}
declare interface NDEFMessageInit {
records: NDEFRecordInit[]
}
declare type NDEFRecordDataSource = string | BufferSource | NDEFMessageInit
interface Window {
NDEFRecord: NDEFRecord
}
declare class NDEFRecord {
constructor(recordInit: NDEFRecordInit)
readonly recordType: string
readonly mediaType?: string
readonly id?: string
readonly data?: DataView
readonly encoding?: string
readonly lang?: string
toRecords?: () => NDEFRecord[]
}
declare interface NDEFRecordInit {
recordType: string
mediaType?: string
id?: string
encoding?: string
lang?: string
data?: NDEFRecordDataSource
}
declare type NDEFMessageSource = string | BufferSource | NDEFMessageInit
interface Window {
NDEFReader: NDEFReader
}
declare class NDEFReader extends EventTarget {
constructor()
onreading: (this: this, event: NDEFReadingEvent) => any
onreadingerror: (this: this, error: Event) => any
scan: (options?: NDEFScanOptions) => Promise<void>
write: (
message: NDEFMessageSource,
options?: NDEFWriteOptions
) => Promise<void>
}
interface Window {
NDEFReadingEvent: NDEFReadingEvent
}
declare class NDEFReadingEvent extends Event {
constructor(type: string, readingEventInitDict: NDEFReadingEventInit)
serialNumber: string
message: NDEFMessage
}
interface NDEFReadingEventInit extends EventInit {
serialNumber?: string
message: NDEFMessageInit
}
interface NDEFWriteOptions {
overwrite?: boolean
signal?: AbortSignal
}
interface NDEFScanOptions {
signal: AbortSignal
}

This isn't about Webpack checks or your tsconfig, or "experimental JavaScript".
It's just that there are no types for NDEFReader() available, so TypeScript thinks you have a typo.
You can stub in a type for NDEFReader with a file like extra-globals.d.ts (the name doesn't matter so long as it's a .d.ts) in your source tree. This basically tells TypeScript that the global Window interface has one extra field, NDEFReader, whose type you don't really know:
declare global {
interface Window {
NDEFReader: any;
}
}
export {};

Related

How to turn class properties into type for array of class properties as strings

How can I turn all class properties (excluding methods, ideally) into a type that represents an array of the class properties as strings?
This is what I have (the SomeTableFields type is what I'm trying to figure out).
// Objection.js Model
export class SomeTable implements Model {
public static get tableName(): string {
return 'someTable'
}
public id: string
public name: string
public age: int
}
// WHAT SHOULD THIS TYPE BE???
type SomeTableFields = (???)[]
// Function Definition
function getFromTable(id: string, fields?: SomeTableFields){
...
}
// Function Usage
getFromTable('1', ['id', 'name']) // should be OK
getFromTable('1', ['id', 'someNonExistentField']) // should be typecheck error
I tried (keyof SomeTable)[] but that results in Type 'string[]' is not assignable to type 'keyof SomeTable[]'. Wondered if I could do something like CastToString<keyof someTable>[] but I don't think that's a thing.
EDIT:
Guess I have a different problem, as #vera has pointed out that it is working. I also see it working if I use the type directly. But when I expose the function via this service and use it in another file I see the same Type 'string' is not assignable to type 'SomeTableFields'.`:
export interface SomeService {
getFromTable: (id: string, fields?: SomeTableFields[]) => GetFromTableResponse
}
export async function createSomeService(): Promise<WorkspaceService> {
return {
getFromTable: (userId, workspaceId, selectFields) => getFromTable(id, selectFields)
}
}
In exposing the function on a service and decalring the args, I added an extra [] here fields?: SomeTableFields[]. Removing it fixed it. Thank you #vera for verifying what I had and driving towards the real problem.

How to implement a Typescript interface that allows additional properties?

Ok, I have been struggling with this one as all information I find is about how to define interfaces that allow other properties, but not how to create a class that can implement the interface.
I have (or want to have) the following interface:
export interface IEnvironment {
value: string;
names: string[];
[x: string | 'value' | 'names']: (() => boolean) | string | string[]
};
Then I want a class that implements said interface, but I only want to implement the value and names properties.
For full disclosure, I want to create an environment object with value, names and one function per name in names. Like this:
export class Environment implements IEnvironment {
value: string;
names: Array<string>;
static defaultNames: string[] = ['Development', 'PreProduction', 'Production'];
constructor(value: string, names?: Array<string>) {
this.value = value;
this.names = names ?? Environment.defaultNames;
let currEnvFound = false;
this.names.forEach((name) => {
// Look at all the hoops I had to jump so TypeScript would not complain. Suggestions welcome.
(this as unknown as { [x: string]: () => boolean })[`is${name}`] = function () { return (this as unknown as Environment).value === name; };
currEnvFound = currEnvFound || name === value;
});
// Throw if the current environment value was not found.
if (!currEnvFound) {
throw new Error(`The provided environment value "${value}" was not found among the provided list of environments.`);
}
}
};
Now this works except for one error I get:
Class 'Environment' incorrectly implements interface 'IEnvironment'.
Index signature for type 'string' is missing in type 'Environment'.
So how can I do this? I'm a noob in the TypeScript arena, so I'd rather ask the experts.
If no solution, could this be worked around with another interface that extends IEnvironment? Like remove the extra properties thing and move it to another interface that I would use as consumer of the object so I get the correct Intellisense.
Thank you very much in advance.
You just need to declare this dynamic x property from interface as class property.
Add this line as your class property: [x: string]: string|(() => boolean)|string[];
Finally, your class looks like this:
class Environment implements IEnvironment {
value: string;
names: Array<string>;
static defaultNames: string[] = ['Development', 'PreProduction', 'Production'];
//ADD THIS
[x: string]: string|(() => boolean)|string[];
constructor(value: string, names?: Array<string>) {
this.value = value;
this.names = names ?? Environment.defaultNames;
let currEnvFound = false;
this.names.forEach((name) => {
// Look at all the hoops I had to jump so TypeScript would not complain. Suggestions welcome.
(this as unknown as { [x: string]: () => boolean })[`is${name}`] = function () { return (this as unknown as Environment).value === name; };
currEnvFound = currEnvFound || name === value;
});
// Throw if the current environment value was not found.
if (!currEnvFound) {
throw new Error(`The provided environment value "${value}" was not found among the provided list of environments.`);
}
}
};

Allow property to be an array or plain type

I was trying to be able to pass the property as an array of strings or just a string value.
class GetUsersDTO {
// #IsArray() | #IsString()
readonly status;
}
But it's not possible even though it's a common use case. Can we do it with a plain class-validator package?
You'll need to make your own custom validator for this. Typescript doesn't reflect generics (yes, a union type is a special generic) and because of that, class-validator won't be able to properly figure out which of the two decorators to use.
import { ValidatorConstraint, ValidatorConstraintInterface, ValidationArguments } from 'class-validator';
#ValidatorConstraint({ name: 'customText', async: false })
export class IsArrayOrString implements ValidatorConstraintInterface {
validate(val: string | string[], args: ValidationArguments) {
const isArray = Array.isArray(val);
const isString = typeof val === 'string';
return isArray || isString
}
}
And now you can use it like
class GetUsersDTO {
#Validate(IsArrayOrString)
readonly status: string | string[];
}
My personal word of warning: using multiple allowed inputs to the same endpoint usually brings nothing but trouble and headache. If possible, keep them separate.

Implement ng.IFilterService in Typescript

I have the following in a .ts file
module App.Filters {
export class SplitRangeFilter implements ng.IFilterService {
static $inject = ['$filter'];
public static factory(): Function {
return (input: string, splitIndex: number) => {
return input.split('-')[splitIndex];
}
}
}
angular.module("App.Filters", []).filter('SplitRange', () => SplitRangeFilter.factory);
}
and it is giving me a compiler error of:
Class SplitRangeFilter declared interface IFilterService but does not implement it: Types 'SplitRangeFilter' and 'IFilterService' have incompatible signatures. No matching signature for '<T>(name: string) => T'
I cannot find any such signature in angulars documentation. Any suggestions on how to get rid of this compiler error in VS2015
The IFilterService is only used to add type safety via TypeScript compiler to programmatic filter invokes.
The correct implementation based on your code would be:
module App.Filters {
// Add filter type to the filter service so that TypeScript can
// perform type checking on future programmatic invokes.
// This app filter service
export interface IAppFilterService extends angular.IFilterService {
(name: 'SplitRange'): (input: string | splitIndex: number) => string | undefined;
}
export class SplitRangeFilter {
constructor() {
return (input: string, splitIndex: number): string | undefined => {
return input.split('-')[splitIndex];
}
}
}
angular.module("App.Filters", []).filter('SplitRange', SplitRangeFilter);
}
This is how IAppFilterService will be used in your code:
// Usage for IAppFilterService
class AppController {
constructor(private $filter: IAppFilterService) {}
// This will pass without compile warnings
public splitString(val: string, index: number): string | undefined {
return this.$filter('SplitRange')(val, number);
}
// This will throw compile time TypeScript error
public splitNumber(val: number, index: number): string | undefined {
return this.$filter('SplitRange')(val, number);
}
}
If you just want it to work and aren't concerned about implementing the "right" interface then remove the implements ng.IFilterService and then change your Angular registration statement to execute the factory (i.e. add ())
angular.module("App.Filters", []).filter('SplitRange', () => SplitRangeFilter.factory());

Interfaces and Getters\Setters

I have the following:
interface IEngine {
type():string;
type(type:string):void;
}
class Engine implements IEngine {
private _type: string;
get type():string {
return this._type;
}
set type(type:string) {
this._type = type;
}
}
var engine = new Engine();
engine.type = 'foo';
The interface looks to me to be implemented, however, running tsc throws an exception:
F:\>tsc interfaces.ts --target "es5"
interfaces.ts(11,7): error TS2420: Class 'Engine' incorrectly implements interface 'IEngine'.
Types of property 'type' are incompatible.
Type 'string' is not assignable to type '{ (): string; (type: string): void; }'.
You are implementing property, so in interface it should be like this:
interface IEngine {
type:string;
}
Interfaces in Typescript are great for defining the "shape" of the object you want. In your example you are looking for an object that has a property called type. This can be done by specifying:
interface IEngine {
type: string;
}
The getters and setters are implementation details which are then defined in objects that implement the interface such the Engine type in your question.

Categories

Resources