I have a function like so:
type Entry = PageBasic | PageHome | PostCollection | PostProduct;
export default (entry: Entry): Params => {
const contentType = entry.sys.contentType.sys.id;
if (contentType ==='pageBasic') {
return { slug: entry.fields.slug };
}
if (contentType === 'pageCollection') {
return { collection: entry.fields.slug };
}
if (contentType === 'postProduct') return {
collection: entry.fields.collection.fields.slug,
product: entry.fields.slug,
};
return {};
};
My types for PageBasic, PostCollection and PostProduct are all different. Some have the fields.slug and fields.collection data and some don't. My if statements confirm to me which entry type I'm dealing with e.g. if the contentType === 'pageCollection' then I'm dealing with PostCollection type. However Typescript doesn't know that. Is there a way for me to tell it if my condition is true, then the type of the entry param is PostCollection or something?
The only other way I know to do this is adding lots more conditional statements to check if the keys exist, which seems pointless when I already know they will based on my conditional statements. e.g:
export default (entry: Entry): Params => {
const contentType = entry.sys.contentType.sys.id;
if (contentType === CT_PAGE_BASIC) {
if ('slug' in entry.fields) return { slug: entry.fields.slug };
}
if (contentType === CT_POST_COLLECTION) {
if ('slug' in entry.fields) return { collection: entry.fields.slug };
}
if (contentType === CT_POST_PRODUCT) return {
...'collection' in entry.fields && { collection: entry.fields.collection.fields.slug },
...'slug' in entry.fields && { product: entry.fields.slug },
};
return {};
};
Yes you can - use a type assertion:
if (contentType ==='pageBasic') {
return { slug: <PageBasic>(entry).fields.slug };
}
You could also return an object with conditionals:
export default (entry: Entry): Params => {
const contentType = entry.sys.contentType.sys.id;
return {
...(contentType ==='pageBasic' ? { slug: (<PageBasic>entry).fields.slug } : 0),
...(contentType === 'pageCollection' ? { collection: (<PostCollection>entry).fields.slug } : 0),
...(contentType === 'postProduct' ? { collection: (<PostProduct>entry).fields.collection.fields.slug, product: (<PostProduct>entry).fields.slug } : 0)
};
};
Related
So I have a problem while doing the search filtering features for searching through all columns in the Datagrid table; I've implemented the search based on the selected column field, which is working just fine, and there is an option for the search input to search through the All columns.
I get all the column data dynamically from the MUI Grid API and the valueTwo array is the representation of the column data that I got from the MUI Grid API.
const valueOne = [
{
name: 'Jacqueline',
reference: 'PRD-143',
active: true,
},
{
name: 'Jacqueline',
reference: 'PRD-143',
active: true,
},
{
name: 'Jacqueline',
reference: 'PRD-143',
active: true,
}
]
const valueTwo = [
{
id: '01',
field: 'name',
headerName: 'Name'
},
{
id: '02',
field: 'reference',
headerName: 'Reference'
},
{
id: '01',
field: 'active',
headerName: 'Status'
},
]
This is the application, image
This the hooks that I write
export function useSearchTable<T>(
tableData: T[],
setTableItems: Dispatch<SetStateAction<T[]>>,
searchState: string,
) {
const [selectedColumnHeader, setSelectedColumnHeader] = useState<
ColumnHeaderInterface | any
>({
field: `allColumns`,
headerName: `All Columns`,
});
const [filteredColumnHeaders, setFilteredColumnHeaders] = useState<
GridStateColDef[] | any
>();
const debouncedSearchState = useDebounce(searchState, 500) as string;
const requestSearch = (dataItems: T[]) => {
switch (selectedColumnHeader.field) {
case `allColumns`: {
let allColumnFilter: T[] = [];
const columnTempData = [] as any[];
if (filteredColumnHeaders) {
for (const columnHeader of filteredColumnHeaders) {
columnTempData.push(columnHeader.field);
}
}
// TODO: something is broken here
columnTempData.forEach((columnName) => {
allColumnFilter = dataItems.filter((itemData: any) => {
if (
typeof itemData[columnName] === `boolean` ||
typeof itemData[columnName] === `number`
) {
String(itemData[columnName])
.toLowerCase()
.includes(debouncedSearchState.toLowerCase());
}
if (typeof itemData[columnName] === `object`) {
itemData[columnName]?.name
.toLowerCase()
.includes(debouncedSearchState.toLowerCase());
}
return itemData;
});
});
console.log(columnTempData, `itemTempData`);
console.log(allColumnFilter, `columnFilterSearch`);
setTableItems(allColumnFilter);
break;
}
default: {
const filteredSearchData = dataItems.filter((itemData: any) => {
let data = itemData[selectedColumnHeader.field];
if (typeof data === `boolean` || typeof data === `number`) {
data = String(data);
}
// TODO: quick fix, but need to research on other API data regarding the resources: vehicles, machines, standalones
if (typeof data === `object`) {
data = data?.name;
}
console.log(
data?.toLowerCase().includes(debouncedSearchState.toLowerCase()),
`data`,
);
return data
?.toLowerCase()
.includes(debouncedSearchState.toLowerCase());
});
setTableItems(filteredSearchData);
break;
}
}
};
useEffect(() => {
if (tableData) {
requestSearch(tableData);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
debouncedSearchState,
tableData,
filteredColumnHeaders,
selectedColumnHeader,
]);
return {
setFilteredColumnHeaders,
setSelectedColumnHeader,
selectedColumnHeader,
};
}
columnTempData is basically an array of string containing the field name from the column header data that I set in filteredColumnHeaders. And things that needs to be fix is on the // TODO comment
The code that I expect is to get all the filtered data based on the search through all the columns with the column data that comes from the MUI Grid API.
I'm a one-man engineer and I really need your assistance here :)
I've tried to loop through the column field data and then looping the table list data and access each key based on the column field data, something like this:
columnTempData.forEach((columnName) => {
allColumnFilter = dataItems.filter((itemData: any) => {
if (
typeof itemData[columnName] === `boolean` ||
typeof itemData[columnName] === `number`
) {
String(itemData[columnName])
.toLowerCase()
.includes(debouncedSearchState.toLowerCase());
}
if (typeof itemData[columnName] === `object`) {
itemData[columnName]?.name
.toLowerCase()
.includes(debouncedSearchState.toLowerCase());
}
return itemData;
});
});
It should have been returning the all filtered data based on the input given. so no matter what column is being search the data is going to filtered based on the input.
Use map instead of forEach as forEach doesn't return anything
columnTempData.map((columnName) => {
allColumnFilter = dataItems.filter((itemData: any) => {
if (
typeof itemData[columnName] === `boolean` ||
typeof itemData[columnName] === `number`
) {
String(itemData[columnName])
.toLowerCase()
.includes(debouncedSearchState.toLowerCase());
}
if (typeof itemData[columnName] === `object`) {
itemData[columnName]?.name
.toLowerCase()
.includes(debouncedSearchState.toLowerCase());
}
return itemData;
});
return allColumnFilter
});
I've found the solution! Basically, I was wrong in choosing the right array method for my case.
allColumnFilter = dataItems.filter((itemData: any) => {
return columnTempData.find((columnName) => {
let itemDataTemp = itemData[columnName];
if (
typeof itemDataTemp === `boolean` ||
typeof itemDataTemp === `number`
) {
itemDataTemp = String(itemDataTemp);
}
if (typeof itemDataTemp === `object`) {
itemDataTemp = itemDataTemp?.name;
}
return itemDataTemp
?.toLowerCase()
.includes(debouncedSearchState.toLowerCase());
});
});
I want to check if there is a value in a certain variable I have. Below I put an example of a logic that I want to achieve.
No matter how hard I tried, I was able to write a very sketchy code as a result of 3 hours of work and research, but it has nothing to do with what I want to achieve.
My Code:
const Files = [
{
Name: 'System',
Type: 'directory',
Value: [
{
Name: 'Main',
Type: 'directory',
Value: [
{
Name: 'Drivers',
Type: 'directory',
Value: [
{
Name: 'Startup',
Type: 'file',
Value: new FileSystem.File('Startup', 0x1, 'test blah blah'),
},
],
},
],
},
],
},
];
BlahBlah.has(Files, 'System->Main->Drivers');
// [File]
BlahBlah.has(Files, 'System->Main->Drivers->Startup');
// File
BlahBlah.has(Files, 'System->Main->Drivers->AnyWhere');
// undefined
BlahBlah.has(Files, 'System->Main->AnyRandomDirectory');
// NaN
My Function:
function text2Binary(str: string, spliter: string = ' '): string {
return str
.split('')
.map(function (char) {
return char.charCodeAt(0).toString(2);
})
.join(spliter);
}
export function FileTypeFromNumber(e: number) {
if (typeof e != 'number')
try {
e = Number(e);
} catch (_) {
return null;
}
return {
0x1: {
Name: 'Executable File',
Extension: 'exe',
},
0x2: {
Name: 'Text Document',
Extension: 'txt',
},
}[e];
}
export type FileTypes =
| 0x1
| 0x2
| 0x3
| 0x4
| 0x5
| 0x6
| 0x7
| 0x8
| 0x9
| null;
export class File {
Name: string;
Type: {
Name: string;
Extension: string;
};
Content: string;
Size: number;
constructor(name: string, type: FileTypes, content: string) {
this.Name = name;
this.Type = FileTypeFromNumber(type);
this.Content = content;
this.Size = text2Binary(content, '').length;
}
}
export class Directory {
public Name: string;
public Files: (File | Directory)[] = [];
constructor(name: string) {
this.Name = name;
}
addFile(file: File | Directory) {
this.Files.push(file);
}
getFile(name: string): null | (File | Directory)[] {
if (typeof name != 'string')
try {
name = String(name);
} catch (_) {
return null;
}
const Result = this.Files.filter((e) => e.Name == name);
return Result.length == 0 ? null : Result;
}
getSize() {
return this.Files.map((e) =>
e instanceof Directory ? e.getSize() : e.Size
).reduce((a, b) => a + b, 0);
}
has(name) {
return this.Files.some((e) => e.Name == name);
}
getJSON() {
return this.Files.map((e) => ({ ...e }));
}
}
interface x {
Content: string;
Name: string;
Size: number;
Type: string;
}
export function ConvertFromJSONtoDirectory(json: any[]) {
return json.map((value) => {
const isDirectory = value.Type == 'directory';
if (!isDirectory) {
return value.Value;
}
const self = new Directory(value.Name);
ConvertFromJSONtoDirectory(value.Value).map((e) => self.addFile(e));
return self;
});
}
export default class DirectorySystem {
Memory: Map<any, any>;
Current: string | null;
constructor(Current = null) {
this.Memory = new Map();
this.Current = Current;
}
addDirectory(directory: Directory): null | true {
if (!(directory instanceof Directory)) return null;
if (this.Memory.has(directory.Name)) return null;
this.Memory.set(directory.Name, directory);
return true;
}
getDirectory(DirectoryName: string): boolean | Directory {
if (typeof DirectoryName != 'string')
try {
DirectoryName = String(DirectoryName);
} catch (_) {
return null;
}
const Result = this.Memory.has(DirectoryName);
return Result ? this.Memory.get(DirectoryName) : Result;
}
getDirectoryCurrent() {
if (this.Current == null) return this;
}
changeDirectory(by: -1 | 1, value: string) {
if (by == -1) {
if (this.Current == null) return null;
if (this.Current.includes('->')) {
this.Current = this.Current.split('->').slice(0, -1).join('->');
} else {
this.Current = null;
}
return this.Current;
} else if (by == 1) {
let Position = [this.Current, value].join('->');
if (this.Current == null) {
Position = Position.split('->').slice(1).join('->');
}
let Result = this.has(Position);
console.log(Result);
}
}
has(query: string) {
try {
return query.split('->').reduce((a, b) => {
if (Array.isArray(a)) {
const f = a.filter((e) => e['Name'] == b);
if (a.length > 0) {
return f['Files'];
} else {
return a;
}
}
return a['Files'];
}, this.getJSON());
} catch (_) {
return false;
}
}
getJSON(): x[][] {
return [...this.Memory.values()].reduce((a, b) => {
a[b.Name] = b.getJSON();
return a;
}, {});
}
}
Result: (Thanks Michael M. and chill 389cc for helping me understand the error)
has(
query: string,
overwrite = null
) {
// If overwrite argument is not null, we are going use it.
let files = overwrite == null ? this.getJSON() : overwrite;
// Split string for getting more usable type with converting string to Array.
const QueryParams = query.split('->').filter(String);
// If we dont have no query, we can return current status.
if (QueryParams.length == 0) return overwrite;
if (Array.isArray(files)) {
const SearchFor = QueryParams.shift();
const Result = files.filter((e) => {
if (e instanceof Directory) {
const x = e.Name == SearchFor;
return x ? e : false;
}
return e.Name == SearchFor;
})[0];
// If we cant find any indexing result
if (!Result) return false;
// We found a file and if we dont have any query this is mean we found it!
if (Result instanceof File) return QueryParams.length == 0;
// We found a Directory and we doesnt have any Query now, so we can return true.
if (Result instanceof Directory && QueryParams.length == 0) return true;
if (
Result.Name != SearchFor ||
(QueryParams.length != 0 && Result.Files.length == 0)
)
// If name not suits or still we has Query and not enough file for indexing.
return false;
// If nothing happens on upper section, return rescyned version of this function.
return this.has(QueryParams.join('->'), Result.Files);
} else {
// If value is Object, Try Search param in object, and return it.
const Result = files[QueryParams.shift()];
return !Result ? false : this.has(QueryParams.join('->'), Result);
}
}
I can't replicate all of your code, but does this help?
interface Entry {
Name: string,
Type: string,
Value: Array<Entry> | any,
};
const Files = [
{
Name: "System",
Type: "directory",
Value: [
{
Name: "Main",
Type: "directory",
Value: [
{
Name: "Drivers",
Type: "directory",
Value: [
{
Name: "Startup",
Type: "file",
Value: "test", // change this to anything
},
],
},
],
},
],
},
];
function getEl(files: Array<Entry>, path: String) {
let path_walk = path.split("->");
let obj = files;
for (let part of path_walk) {
let found = false;
for (let entry of obj) {
if (entry.Name == part) {
obj = entry.Value;
found = true;
}
}
if (!found) return undefined;
}
return obj;
}
console.log(getEl(Files, "System->Main->Drivers")); // => [ { Name: 'Startup', Type: 'file', Value: 'test' } ]
console.log(getEl(Files, "System->Main->Drivers->Startup")); // => "test"
console.log(getEl(Files, "System->Main->Drivers->AnyWhere")); // => undefined
console.log(getEl(Files, "System->Main->AnyRandomDirectory")); // => undefined
There are some obvious problems, such as the fact that your example shows .has() being called with two arguments but it is defined in the class to only take in one. That being said, here is a function that, given a string query as you have and an array of objects like you have, would read the query and return if the array works for that query.
function has(fileSystem, query) {
const arrayOfArgs = query.split('->')
if (Array.isArray(fileSystem)) {
for (let i = 0; i < fileSystem.length; i++) {
if (fileSystem[i]['Name'] === arrayOfArgs[0]) {
if (arrayOfArgs.length === 1) {
// element found
return true; // replace this to return an actual value if that is desired.
}
if (fileSystem[i]['Type'] === 'directory') {
// if not, recurse in if it is a directory
return has(fileSystem[i]['Value'], arrayOfArgs.slice(1).join('->'));
} else {
// if it isn't a directory, don't try to recurse in
return false;
}
}
}
}
return false;
}
console.log(has(Files, 'System->Main->Drivers')); // true
console.log(has(Files, 'System->Main->Drivers->Startup')); // true
console.log(has(Files, 'System->Main->Drivers->AnyWhere')); // false
console.log(has(Files, 'System->Main->AnyRandomDirectory')); // false
You'll have to add your own types to get it back to TypeScript and obviously I pulled it out of the class for easier testing but it should be pretty easy to re-implement.
I'm working on loopback4-soft-delete component. I need to add deletedBy field to SoftDeleteEntity model. deletedBy should be propagated with the current logged-in user.
Can someone tell me, how can I get current user or user-id in my soft-delete-component's SoftCrudRepository method?
Here is my model:
export abstract class SoftDeleteEntity extends Entity {
#property({
type: 'boolean',
default: false,
})
deleted?: boolean;
#property({
type: 'date',
name: 'deleted_on',
})
deletedOn: Date;
#property({
type: 'string',
name: 'deleted_by',
})
deletedBy: number;
constructor(data?: Partial<SoftDeleteEntity>) {
super(data);
}
}
Here is my repository:
export abstract class SoftCrudRepository<
T extends SoftDeleteEntity,
ID,
Relations extends object = {}
> extends DefaultCrudRepository<T, ID, Relations> {
constructor(
entityClass: typeof SoftDeleteEntity & {
prototype: T;
},
dataSource: juggler.DataSource,
) {
super(entityClass, dataSource);
}
find(filter?: Filter<T>, options?: Options): Promise<(T & Relations)[]> {
// Filter out soft deleted entries
if (
filter?.where &&
(filter.where as AndClause<T>).and &&
(filter.where as AndClause<T>).and.length > 0
) {
(filter.where as AndClause<T>).and.push({
deleted: false,
} as Condition<T>);
} else if (
filter?.where &&
(filter.where as OrClause<T>).or &&
(filter.where as OrClause<T>).or.length > 0
) {
filter = {
where: {
and: [
{
deleted: false,
} as Condition<T>,
{
or: (filter.where as OrClause<T>).or,
},
],
},
};
} else {
filter = filter ?? {};
filter.where = filter.where ?? {};
(filter.where as Condition<T>).deleted = false;
}
// Now call super
return super.find(filter, options);
}
findOne(
filter?: Filter<T>,
options?: Options,
): Promise<(T & Relations) | null> {
// Filter out soft deleted entries
if (
filter?.where &&
(filter.where as AndClause<T>).and &&
(filter.where as AndClause<T>).and.length > 0
) {
(filter.where as AndClause<T>).and.push({
deleted: false,
} as Condition<T>);
} else if (
filter?.where &&
(filter.where as OrClause<T>).or &&
(filter.where as OrClause<T>).or.length > 0
) {
filter = {
where: {
and: [
{
deleted: false,
} as Condition<T>,
{
or: (filter.where as OrClause<T>).or,
},
],
},
};
} else {
filter = filter ?? {};
filter.where = filter.where ?? {};
(filter.where as Condition<T>).deleted = false;
}
// Now call super
return super.findOne(filter, options);
}
findById(
id: ID,
filter?: Filter<T>,
options?: Options,
): Promise<T & Relations> {
// Filter out soft deleted entries
if (
filter?.where &&
(filter.where as AndClause<T>).and &&
(filter.where as AndClause<T>).and.length > 0
) {
(filter.where as AndClause<T>).and.push({
deleted: false,
} as Condition<T>);
} else if (
filter?.where &&
(filter.where as OrClause<T>).or &&
(filter.where as OrClause<T>).or.length > 0
) {
filter = {
where: {
and: [
{
deleted: false,
} as Condition<T>,
{
or: (filter.where as OrClause<T>).or,
},
],
},
};
} else {
filter = filter ?? {};
filter.where = filter.where ?? {};
(filter.where as Condition<T>).deleted = false;
}
// Now call super
return super.findById(id, filter, options);
}
updateAll(
data: DataObject<T>,
where?: Where<T>,
options?: Options,
): Promise<Count> {
// Filter out soft deleted entries
if (
where &&
(where as AndClause<T>).and &&
(where as AndClause<T>).and.length > 0
) {
(where as AndClause<T>).and.push({
deleted: false,
} as Condition<T>);
} else if (
where &&
(where as OrClause<T>).or &&
(where as OrClause<T>).or.length > 0
) {
where = {
and: [
{
deleted: false,
} as Condition<T>,
{
or: (where as OrClause<T>).or,
},
],
};
} else {
where = where ?? {};
(where as Condition<T>).deleted = false;
}
// Now call super
return super.updateAll(data, where, options);
}
count(where?: Where<T>, options?: Options): Promise<Count> {
// Filter out soft deleted entries
if (
where &&
(where as AndClause<T>).and &&
(where as AndClause<T>).and.length > 0
) {
(where as AndClause<T>).and.push({
deleted: false,
} as Condition<T>);
} else if (
where &&
(where as OrClause<T>).or &&
(where as OrClause<T>).or.length > 0
) {
where = {
and: [
{
deleted: false,
} as Condition<T>,
{
or: (where as OrClause<T>).or,
},
],
};
} else {
where = where ?? {};
(where as Condition<T>).deleted = false;
}
// Now call super
return super.count(where, options);
}
delete(entity: T, options?: Options): Promise<void> {
// Do soft delete, no hard delete allowed
(entity as SoftDeleteEntity).deleted = true;
(entity as SoftDeleteEntity).deletedOn = new Date();
return super.update(entity, options);
}
deleteAll(where?: Where<T>, options?: Options): Promise<Count> {
// Do soft delete, no hard delete allowed
return this.updateAll(
{
deleted: true,
deletedOn: new Date(),
} as DataObject<T>,
where,
options,
);
}
deleteById(id: ID, options?: Options): Promise<void> {
// Do soft delete, no hard delete allowed
return super.updateById(
id,
{
deleted: true,
deletedOn: new Date(),
} as DataObject<T>,
options,
);
}
/**
* Method to perform hard delete of entries. Take caution.
* #param entity
* #param options
*/
deleteHard(entity: T, options?: Options): Promise<void> {
// Do hard delete
return super.delete(entity, options);
}
/**
* Method to perform hard delete of entries. Take caution.
* #param entity
* #param options
*/
deleteAllHard(where?: Where<T>, options?: Options): Promise<Count> {
// Do hard delete
return super.deleteAll(where, options);
}
/**
* Method to perform hard delete of entries. Take caution.
* #param entity
* #param options
*/
deleteByIdHard(id: ID, options?: Options): Promise<void> {
// Do hard delete
return super.deleteById(id, options);
}
static getUser(name: string): string {
return name;
}
}
Hello from the LoopBack team 👋
Assuming you are using LoopBack's builtin authentication & security layer (see https://loopback.io/doc/en/lb4/Authentication-overview.html), the currently logged-in user can be injected using SecurityBindings.USER.
The simplest option is to uses property injection as follows:
import {SecurityBindings, UserProfile} from '#loopback/security';
export abstract class SoftCrudRepository<
T extends SoftDeleteEntity,
ID,
Relations extends object = {}
> extends DefaultCrudRepository<T, ID, Relations> {
#inject(SecurityBindings.USER, {optional: true})
public currentUser?: UserProfile;
// Your implementation goes here.
}
Use this.currentUser to access the currently logged user from your repository methods. Just keep in mind the value is set after the class is instantiated (you cannot access it from the constructor) and the current user is undefined for anonymous requests.
I would like to use a BehaviorSubject to store an Array of objects and have a way to easily update (next?) a single item of that array without having to update the whole array.
I would also like for an easy way to subscribe to changes to an specific item of that array. I know it could be done with filter, but an easier way would be nice...
Is that possible?
I am currently using this version I created (which I don't know if it is the best way or not) that also persists its contents to localstorage:
export class LocalStorageBehaviorSubject<T, Y = T> {
private _data: BehaviorSubject<T>;
public asObservable() {
return this._data.asObservable();
}
public next(data: T) {
if(this.expirationFn !== null) {
data = this.expirationFn(data);
}
localStorage.setItem(this.key, JSON.stringify(data));
this._data.next(data);
}
public nextItem(item: Y) {
if (!Array.isArray(this._data.getValue())) {
throw "Type is not an Array";
}
let dados: any = (<any>this._data.getValue()).slice();
if (dados.some(r => r[this.id] === item[this.id])) {
dados = dados.map(r => r[this.id] === item[this.id] ? item : r);
} else {
dados.push(item);
}
if(this.expirationFn !== null) {
dados = this.expirationFn(dados);
}
localStorage.setItem(this.key, JSON.stringify(dados));
this._data.next(<any>dados);
}
public removeItem(id) {
if (!Array.isArray(this._data.getValue())) {
throw "Type is not an Array";
}
let dados: any = (<any>this._data.getValue()).slice();
dados = dados.filter(r => r[this.id] !== id);
localStorage.setItem(this.key, JSON.stringify(dados));
this._data.next(<any>dados);
}
public removeExpiredData(){
let data = this.loadFromStorage();
if (data) {
if(this.expirationFn !== null) {
data = this.expirationFn(data);
}
this._data.next(data);
}
}
public getValue() {
this.removeExpiredData();
return this._data.getValue();
}
public getItem(id): Y {
if (!Array.isArray(this._data.getValue())) {
throw "Type is not an Array";
}
this.removeExpiredData();
return (<any>this._data.getValue()).slice().find(t => t[this.id] == id);
}
constructor(private key: string, private id: string, defaultValue: any = null, private expirationFn: (dados: T) => T = null) {
this._data = new BehaviorSubject<T>(defaultValue);
this.removeExpiredData();
}
private loadFromStorage(): T {
let dadosStr = localStorage.getItem(this.key);
if (dadosStr) {
return JSON.parse(dadosStr);
}
return null;
}
}
I hoped that would be an simpler way...
Thanks
I would also like for an easy way to subscribe to changes to an
specific item of that array. I know it could be done with filter, but
an easier way would be nice...
You can use map operator and inside lambda array.find
Example
const mockStorage = {
values: {},
setItem(key, value) {
this.values[key] = value;
},
getItem(key) {
return this.values[key]
},
clearItem(key) {
this.values[key] = undefined;
}
}
class LocalStorageBehaviorSubject {
constructor(key, defaultValue) {
this.key = key;
this._data = new rxjs.BehaviorSubject(defaultValue);
}
nextItem(item) {
const list = this._data.value;
const itemIndex = list.findIndex(pr => pr.id === item.id);
this._data.next([
...list.slice(0, itemIndex),
{
...(list[itemIndex] || {}),
...item
},
...list.slice(itemIndex + 1)
]);
}
removeItem(id) {
this._data.next(this._data.value.filter(pr => pr.id !== id));
}
getItem(id) {
return this.asObservable()
.pipe(
rxjs.operators.map(values => values.find(pr => pr.id === id) || null),
rxjs.operators.distinctUntilChanged());
}
asObservable() {
return this._data.asObservable().pipe(
rxjs.operators.tap(values => {
if (values && values.length) {
mockStorage.setItem(this.key, JSON.stringify(values));
}
else {
mockStorage.clearItem(this.key);
}
}))
}
}
const localStorageBehaviorSubject = new LocalStorageBehaviorSubject('items', []);
localStorageBehaviorSubject
.getItem(1)
.subscribe(item => {
console.log(item);
})
localStorageBehaviorSubject.nextItem({id: 1, value: 'test'})
localStorageBehaviorSubject.nextItem({id: 1, value: 'test1'})
localStorageBehaviorSubject.nextItem({id: 2, value: 'test2'})
localStorageBehaviorSubject.nextItem({id: 3, value: 'test3'})
localStorageBehaviorSubject.removeItem(2);
localStorageBehaviorSubject.removeItem(1);
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.5.5/rxjs.umd.js"></script>
I have to check if some items object follow a certain format as below. These items are input to a component and I want to check the validity of the input.
I already wrote some code to check for the validity of the items, but I want to know if there could be a better way to write it?
Thanks!
{
main: {
id: string,
name: string,
},
drilldowns: [
{
id: string,
name: string,
elements: [
{
id: string,
name: string,
}
],
}
],
}
export const isValidItem = (item) => {
if (!item.main || (item.main && !item.main.id))
return false;
if (item.drilldowns) {
const invalidDrilldowns = item.drilldowns.filter(drilldown => {
const invalidDrilldownElements =
drilldown.elements &&
drilldown.elements.filter(element => {
return !element.id;
});
return (
!drilldown.id &&
!drilldown.elements &&
invalidDrilldownElements.length !== 0
);
});
return invalidDrilldowns.length === 0;
}
return true;
};