I have the following code that works, but the moment I change the if to p instanceof Address the condition is never met.
p is an object but I want to check it against its type that has already been defined:
export default class Address {
public addressLine1: string = ''
public addressLine2: string | null = null
public town: string = ''
public postcode: string = ''
}
const displayResults = (list: any) => {
const slicedList = list.slice(0, 3)
const html =
slicedList.map((p: string | Address) => {
console.log('p: ', p instanceof Address)
if (typeof p === 'object') {
return <span key={`id-${uniqid()}`}>{addressToString(p)}</span>
}
return <span key={`id-${uniqid()}`}>{p}</span>
})
if (list.length > 3) {
html.push(elipsis)
}
return html
}
Related
I have a problem. I need to write a common function (class) that can both accept HTMLElements and give them using typescript
An example of attempts
class Create {
protected element: HTMLElement;
constructor(parent: ParentNode, tag: string, classNames: string, value?: string, attr?: Record<string, unknown>) {
this.element = document.createElement(tag);
this.element.classList.add(...classNames.split(' '));
if (typeof value === 'string') {
this.element.innerHTML = value;
}
if ('appendChild' in parent) {
parent.appendChild(this.element);
}
if (attr) {
for (const key in attr) {
this.element.setAttribute(key, <string>attr[key]);
}
}
}
append(element: ElementNode) {
this.element.append(element);
}
}
This function will be auxiliary to create elements on the page
example of available elements
const wrapper = new Create(this.container, 'div', styles.wrapper);
const headerWrapper = document.createElement('div') as HTMLDivElement;
const headerNav = document.createElement('nav');
const headerList = document.createElement('ul');
const headerLogo = document.createElement('div');
const headerLogoLink = document.createElement('a');
const headerLogoTitle = document.createElement('h2');
headerLogo.classList.add(styles.headerWrapperLogo);
headerLogoLink.classList.add(styles.headerWrapperLogoLink);
headerLogoLink.href = '/';
headerLogoTitle.classList.add(styles.headerWrapperLogoTitle);
headerLogoTitle.innerHTML = 'Manga Store';
headerLogo.append(headerLogoLink);
headerLogoLink.append(headerLogoTitle);
headerWrapper.append(headerLogo);
headerWrapper.classList.add(styles.headerWrapper);
headerNav.classList.add(styles.headerWrapperNav);
headerList.classList.add(styles.headerWrapperNavList);
btnHeader.forEach((btns) => {
const headerListItem = document.createElement('li');
const headerListItemLink = document.createElement('a') as HTMLAnchorElement;
headerListItem.classList.add(styles.headerWrapperNavListItem);
headerListItemLink.classList.add(styles.headerWrapperNavListItemLink);
headerListItemLink.href = `#${btns.id}`;
headerListItemLink.innerText = btns.content;
wrapper.append(headerWrapper);
headerWrapper.append(headerNav);
headerNav.append(headerList);
headerList.append(headerListItem);
headerListItem.append(headerListItemLink);
Or tell me if it is possible to rewrite the "Abstract" class (see example) to get the desired result
abstract class Template {
protected container: HTMLElement;
static content = {};
protected constructor(id: string, classNames?: string) {
this.container = document.createElement('main');
if (typeof classNames === 'string') {
this.container.classList.add(classNames);
}
this.container.id = id;
}
protected header(content: string) {
const title = document.createElement('h1');
title.innerText = content;
return title;
}
render() {
return this.container;
}
}
When I tried this code:
const wrapper = new Create(document.body, 'div', '1')
const subWrap = new Create(wrapper, 'div', '2') // ???
I got this error:
If you want Create's append method to support being passed a ParentNode or an instance of Create, then you just need to type that argument with a union.
For example:
constructor(
parent: ParentNode | Create, // changed this type
tag: string,
classNames: string,
value?: string,
attr?: Record<string, unknown>
) {
//...
}
See playground
export type Values = string | null | unknown;
export type ParentElem = ParentNode | Create | null;
class Create {
element: Element;
constructor(tag: string, classNames: string, parent?: ParentElem, value?: Values, attr?: Record<string, unknown>) {
this.element = document.createElement(tag);
this.element.classList.add(...classNames.split(' '));
if (typeof value === 'string') {
this.element.innerHTML = value;
}
parent ? (!(parent instanceof Create) ? parent.appendChild(this.element) : null) : undefined;
if (attr) {
for (const key in attr) {
this.element.setAttribute(key, <string>attr[key]);
}
}
}
append(element: Create | Node | string) {
return element ? (!(element instanceof Create) ? this.element.append(element) : null) : undefined;
}
remove(element = this.element) {
return element ? (!(element instanceof Create) ? this.element.remove() : null) : undefined;
}
}
ternary operators helped me, and a few checks, to protect against surprises in the form of undefind and null, I also created two additional methods for adding and removing elements, maybe it will come in handy for someone)
I created a custom validation pipe where I want to add some custom logic.I want to know how can I extend it so that I can call it with the below options
Edited:
I have referred the below link of the github file and have developed my own pipe but there still seems to be something missing as its not validating as per the DTO
https://github.com/nestjs/nest/blob/1f6fca5f55e9e51705fa326654760736b254f4e5/packages/common/pipes/validation.pipe.ts#L48
I was able to solve it by creating my own pipe.
import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException, Logger, Optional } from '#nestjs/common';
import { ValidatorOptions, ValidationError, isObject, validate } from 'class-validator';
import { plainToClass, classToPlain, ClassTransformOptions } from 'class-transformer';
import { iterate } from 'iterare';
const isUndefined = (obj: any): obj is undefined =>
typeof obj === 'undefined';
const isNil = (val: any): val is null | undefined =>
isUndefined(val) || val === null;
interface ValidationPipeOptions extends ValidatorOptions {
transform?: boolean;
transformOptions?: ClassTransformOptions;
validateCustomDecorators?: boolean;
}
#Injectable()
export class ValidationPipe implements PipeTransform<any> {
private readonly logger = new Logger(ValidationPipe.name);
protected isTransformEnabled: boolean;
protected transformOptions: ClassTransformOptions;
protected validatorOptions: ValidatorOptions;
protected validateCustomDecorators: boolean;
constructor(#Optional() options?: ValidationPipeOptions) {
options = options || {};
const {
transform,
transformOptions,
validateCustomDecorators,
...validatorOptions
} = options;
this.isTransformEnabled = !!transform;
this.transformOptions = transformOptions;
this.validatorOptions = validatorOptions;
this.validateCustomDecorators = validateCustomDecorators || false;
}
async transform(value: any, metadata: ArgumentMetadata) {
const metatype = metadata.metatype;
if (!metatype || !this.toValidate(metadata)) {
return this.isTransformEnabled
? this.transformPrimitive(value, metadata)
: value;
}
const originalValue = value;
value = this.toEmptyIfNil(value);
const isNil = value !== originalValue;
const isPrimitive = this.isPrimitive(value);
this.stripProtoKeys(value);
let object = plainToClass(metatype, value, this.transformOptions);
const originalEntity = object;
const isCtorNotEqual = object.constructor !== metatype;
if (isCtorNotEqual && !isPrimitive) {
object.constructor = metatype;
} else if (isCtorNotEqual) {
// when "entity" is a primitive value, we have to temporarily
// replace the entity to perform the validation against the original
// metatype defined inside the handler
object = { constructor: metatype };
}
const errors = await this.validate(object, this.validatorOptions);
if (errors.length > 0) {
for (let error of errors) {
for (let key in error.constraints) {
this.logger.error(`${error.target.constructor.name}:${error.constraints[key]}`);
}
}
throw new BadRequestException('Invalid Input Parameters');
}
if (isPrimitive) {
// if the value is a primitive value and the validation process has been successfully completed
// we have to revert the original value passed through the pipe
object = originalEntity;
}
if (this.isTransformEnabled) {
return object;
}
if (isNil) {
// if the value was originally undefined or null, revert it back
return originalValue;
}
return Object.keys(this.validatorOptions).length > 0
? classToPlain(object, this.transformOptions)
: value;
}
protected stripProtoKeys(value: Record<string, any>) {
delete value.__proto__;
const keys = Object.keys(value);
iterate(keys)
.filter(key => isObject(value[key]) && value[key])
.forEach(key => this.stripProtoKeys(value[key]));
}
protected isPrimitive(value: unknown): boolean {
return ['number', 'boolean', 'string'].includes(typeof value);
}
protected transformPrimitive(value: any, metadata: ArgumentMetadata) {
if (!metadata.data) {
// leave top-level query/param objects unmodified
return value;
}
const { type, metatype } = metadata;
if (type !== 'param' && type !== 'query') {
return value;
}
if (metatype === Boolean) {
return value === true || value === 'true';
}
if (metatype === Number) {
return +value;
}
return value;
}
protected toEmptyIfNil<T = any, R = any>(value: T): R | {} {
return isNil(value) ? {} : value;
}
private toValidate(metadata: ArgumentMetadata): boolean {
const { metatype, type } = metadata;
if (type === 'custom' && !this.validateCustomDecorators) {
return false;
}
const types = [String, Boolean, Number, Array, Object, Buffer];
return !types.some(t => metatype === t) && !isNil(metatype);
}
protected validate(
object: object,
validatorOptions?: ValidatorOptions,
): Promise<ValidationError[]> | ValidationError[] {
return validate(object, validatorOptions);
}
}
I have a simple function to get the address as a string
interface AddressType1 {
city: string | null;
state: string | null;
postalCode: string null;
}
interface AddressType2 {
city: string | null;
region: string | null;
postalCode: string | null;
}
export const getAddressString = (
address: AddressType1 | AddressType2 | null,
): string => {
if (address != null) {
return `${address.city ?? ""}, ${address.region ?? address.state ?? ""} ${address.postalCode ?? ""}`;
}
return "";
};
but typescript return error Property 'region' does not exist on type 'RoadsideLocation' and Property 'state' does not exist on type 'LocationGeocodedAddress'.
You are probably looking for & intersection :
address: ( AddressType1 & AddressType2 )| null,
COnsider next example:
interface AddressType1 {
city: string | null;
state: string | null;
postalCode: string | null;
}
interface AddressType2 {
city: string | null;
region: string | null;
postalCode: string | null;
}
type Union = AddressType1 | AddressType2
const hasProperty = <Obj, Prop extends string>(obj: Obj, prop: Prop)
: obj is Obj & Record<Prop, unknown> =>
Object.prototype.hasOwnProperty.call(obj, prop);
const isType1 = (address: Union): address is AddressType1 => hasProperty(address, 'region');
type Nullable<T> = null | T
const isNull = (str: Nullable<string>): str is null => str === null
const computeAddress = (city: Nullable<string>, postalCode: Nullable<string>) => (stateRegion: Nullable<string>) =>
[city, stateRegion, postalCode].reduce<string>((acc, elem) => isNull(elem) ? acc : `${acc},${elem}`, '');
export const getAddressString = (
address: Union,
) => {
const { city, postalCode } = address;
const partial = computeAddress(city, postalCode)
if (isType1(address)) {
return partial(address.state)
}
return partial(address.region)
}
Just don't call the function is arguement is null.
computeAddress is partial application.
If you have a deal with union, try to use typeguards
I am trying to create pipe for filtering my response.
I did something like this:
import { Pipe, PipeTransform } from '#angular/core';
#Pipe({
name: 'filter',
})
export class FilterPipe implements PipeTransform {
transform(value: any, input: string) {
if (input) {
input = input.toLowerCase();
return value.filter(function (el: any) {
return el.recipients.toLowerCase().indexOf(input) > -1;
})
}
return value;
}
}
But I got error:
ERROR TypeError: value.filter is not a function
at FilterPipe.transform (FilterPipe.ts:10)
This my row
<tr
*ngFor="let r of recipients.recipients | FilterPipe: query | split; let i = index">
<td>{{r}}</td>
and my input
<input class="form-control form-control-alternative" placeholder="Szukaj odbiorcy" type="text"
[(ngModel)]="query">
My recipients output is:
console.log('this.recipients ' +JSON.stringify(this.recipients));
this.recipients {"recipients":"testee"}
How can I filter my object to get result?
You run on recipients.recipients via ngFor so it must be it Iterable (like Array) and I see that its string: "recipients":"tested".
In your pipe you already got each recipient so you need to change el.recipients.toLowerCase() to el.toLowerCase()
Array has filter method, but your value is type of object, so you've got the error:
value.filter is not a function
So you need to convert your object into array:
transform(value: any, input: string) {
if (input && typeof input ==='string') {
input = input.toLowerCase();
if (Array.isArray(value)) {
return value.filter(function (el: any) {
return el.recipients.toLowerCase().indexOf(input) > -1;
})
} else if (typeof value === 'object' && value !== null) {
const filtered = Object.entries(value)
.filter(([k, v])=> v.toLowerCase() === input.toLowerCase());
return Object.fromEntries(filtered);
}
}
return value;
}
An example:
let obj = {"recipients":"testee", 'foo': 'bar'};
let input = 'testee';
const filtered = Object.entries(obj).filter(([k, v])=> v.toLowerCase() === input.toLowerCase());
console.log(Object.fromEntries(filtered));
How do I serialize these classes to JSON?
As you can see from the example below JSON.stringify() does not serialize the list of Cache_Backend_LocalStorage_Tag inside the Cache_Backend_LocalStorage_TagThree object.
What am I missing?
interface Cache_Backend_LocalStorage_Tag_Interface {
_tag : string;
_keys : string[];
}
class Cache_Backend_LocalStorage_Tag implements Cache_Backend_LocalStorage_Tag_Interface {
public _tag : string;
public _keys : string[];
constructor(tag : string) {
this._tag = tag;
this._keys = [];
}
public add(key : string) : void {
this._keys.push(key);
}
public remove(key : string): boolean {
// Get the index of the key
var index = this._keys.indexOf(key, 0);
// Check if we found the keys index
if (index != undefined) {
this._keys.splice(index, 1);
return true;
}
return false;
}
public get tag(): string {
return this._tag;
}
public get keys(): string[] {
return this._keys;
}
}
interface Cache_Backend_LocalStorage_TagThree_Interface {
_tags : Cache_Backend_LocalStorage_Tag[];
}
class Cache_Backend_LocalStorage_TagThree implements Cache_Backend_LocalStorage_TagThree_Interface {
public _tags : Cache_Backend_LocalStorage_Tag[];
constructor(tags : Cache_Backend_LocalStorage_Tag[] = []) {
this._tags = tags;
}
public add(tag : Cache_Backend_LocalStorage_Tag) : void {
this.tags[tag.tag] = tag;
}
public get tags(): Cache_Backend_LocalStorage_Tag[] {
return this._tags;
}
public get(tagKey : string): Cache_Backend_LocalStorage_Tag {
return this.tags[tagKey];
}
public addKeyToTag(tagKey, key) {
this.tags[tagKey].add(key);
}
public removeKeyFromTag(tagKey, key) {
// Get the tag
var tag = this._tags[tagKey];
// Check if we found the tag index
if (tag != undefined) {
return tag.remove(key);
}
return false;
}
public clear(tagKey : string): void {
delete this._tags[tagKey];
}
public static fromObject(object): Cache_Backend_LocalStorage_TagThree {
return new Cache_Backend_LocalStorage_TagThree(object._tags);
}
}
Issue:
var tagThree = new Cache_Backend_LocalStorage_TagThree();
tagThree.add(new Cache_Backend_LocalStorage_Tag("stores"));
tagThree.addKeyToTag("stores", "store5");
tagThree.removeKeyFromTag("stores", "store5");
// {"_tags":[]}
console.log(JSON.stringify(tagThree));
// { _tags: [ stores: { _tag: 'stores', _keys: [Object] } ] }
console.log(tagThree);
Reason
It's because you're assigning properties to an array and array properties won't be included in JSON serialization. For example:
var a = [];
a["test"] = "some value";
JSON.stringify(a); // returns: []
You need to use a plain object:
var o = {};
o["test"] = "some value";
JSON.stringify(o); // returns: {"test":"some value"}
Solution
Change your Cache_Backend_LocalStorage_TagThree_Interface interface to use a dictionary like object:
interface Cache_Backend_LocalStorage_TagThree_Interface {
_tags : { [tag: string] : Cache_Backend_LocalStorage_Tag; };
}
Then update all areas of the code that will now have a compile error to use the same type. For example, change:
constructor(tags : Cache_Backend_LocalStorage_Tag[] = []) {
To:
constructor(tags : { [tag: string] : Cache_Backend_LocalStorage_Tag; } = {}) {
Just for fun - Changing default serialization behaviour (Not recommended)
If you really want to make this work with an array with your current setup (I'm not sure why), you can override how serialization is done. To do this, add a toJSON method to the _tags array in Cache_Backend_LocalStorage_TagThree. This allows you to control how the object is serialized when JSON.stringify is called on it. For example:
this._tags.toJSON = function() {
var values = [];
for (var v in this) {
if (this[v] instanceof Cache_Backend_LocalStorage_Tag) {
values.push(this[v]);
}
}
return JSON.stringify(values);
};