Require Example of Material AutoComplete with http get request - material-design

I am following the Angular Material AutoComplete example as in the Material documents.
It works fine, when using the options[] value as per the document. It is just that I am not able to get it to work when I get the data from api.
If I do not use the filter options, then the list of books are displayed. It is just that when filters are used it has issues.
The this.filterOptions complains
Type 'Observable' is not assignable to type
'Observable'. Type 'Book[]' is not assignable to type
'string[]'.
Type 'Book' is not assignable to type 'string'.
Why did it not complain when options[] was used.
export class AutocompleteComponent implements OnInit {
myControl = new FormControl();
//Works with these values
options = [
'One',
'Two',
'Three'
];
books: Book[] = [];
filteredOptions: Observable<string[]>
constructor(private bookService: BookService) {
this.bookService.getBooks()
.subscribe(
data => {
this.books = data;
},
(err: HttpErrorResponse) => {
console.log(err.statusText);
}
);
}
ngOnInit() {
this.filteredOptions = this.myControl.valueChanges
.startWith('')
.map(val => {
return this.filter(val)
})
}
filter(val: string): Book[] {
return this.books.filter(option =>
option.toLowerCase().indexOf(val.toLowerCase()) === 0);
}
}

Related

TS ERROR: Type 'AxiosResponse<IChat, any>' is not assignable to type 'IChat'

I have class with method:
export default class ChatService {
static async findDMChat(companionID: number) {
return $api.post<IChat>('/findDMChat', {companion_id: companionID})
}
}
In ChatStore.setCompanion( ) I want to assign result of that method to property:
export class ChatStore {
companion = {} as IUser;
chat = {} as IChat;
constructor() {
makeAutoObservable(this)
}
async setCompanion(companion: IUser) {
this.companion = companion;
this.chat = await ChatService.findDMChat(companion.id) // Type 'AxiosResponse<IChat, any>' is missing the following properties from type 'IChat': type, idts(2739)
}
Description of the error is indicated in the code comment.
Thanks in advance.
await ChatService.findDMChat(companion.id) return the result of response, not just data. If you look at AxiosResponse type you'll see what response includes
export interface AxiosResponse<T = any, D = any> {
data: T;
status: number;
statusText: string;
headers: AxiosResponseHeaders;
config: AxiosRequestConfig<D>;
request?: any;
}
so, in your case you should get data property from response
const { data} = await ChatService.findDMChat(companion.id);
this.chat = data;

How can I convert from a firebase document to a custom class in Node JS

In node.js I'm getting the error below. Any ideas why?
Conversion of type 'Promise' to type 'Member[]' may be a mistake
because neither type sufficiently overlaps with the other. If this was
intentional, convert the expression to 'unknown' first. Type
'Promise' is missing the following properties from type
'Member[]': length, pop, push, concat, and 26 more.
export async function getFamilyMembers(tenantId: string, familyCode: string): Promise<Member[]> {
return db.collection(`tenants/${tenantId}/members`)
.where('familyCode', '==', familyCode)
.get()
.then(snaps => {
snaps.docs.forEach(doc => {
return { id: doc.id, ...doc.data()}
});
}) as Member[];
}
EDIT:
If I remove the types and change it to
export async function getFamilyMembers(tenantId: string, familyCode: string) {
return db.collection(`tenants/${tenantId}/members`)
.where('familyCode', '==', familyCode)
.get()
.then(snaps => {
snaps.docs.forEach(doc => {
return { id: doc.id, ...doc.data()}
});
});
}
I just have to deal with the problem later.
I get the error
Property 'length' does not exist on type 'void'.
const familyMembers: Member[] | void = await getFamilyMembers(tenantId, familyCode);
if (familyMembers === null) {
isVerified = false;
verificationFailMessage = `Sorry we can't find this code. Please check it is correct.`;
} else if (familyMembers.length === 0) {
I needed to add Promise in front of Member.
export async function getFamilyMembers(tenantId: string, familyCode: string): Promise<Member[]> {
return db.collection(`tenants/${tenantId}/members`)
.where('familyCode', '==', familyCode)
.get()
.then(snaps => {
snaps.docs.forEach(doc => {
return { id: doc.id, ...doc.data()}
});
}) as Promise<Member[]>;
}

How to pass a child Interface to a parent class?

I have this:
LocationController.ts
import {GenericController} from './_genericController';
interface Response {
id : number,
code: string,
name: string,
type: string,
long: number,
lat: number
}
const fields = ['code','name','type','long','lat'];
class LocationController extends GenericController{
tableName:string = 'location';
fields:Array<any> = fields;
}
const locationController = new LocationController();
const get = async (req, res) => {
await locationController._get(req, res);
}
export {get};
GenericController.ts
interface Response {
id : number
}
export class GenericController{
tableName:string = '';
fields:Array<any> = [];
_get = async (req, res) => {
try{
const id = req.body['id'];
const send = async () => {
const resp : Array<Response> = await db(this.tableName).select(this.fields).where('id', id)
if (resp[0] === undefined) {
// some error handling
}
res.status(status.success).json(resp[0]);
}
await send();
}catch (error){
// some error handling
}
}
}
What I want to do is to pass the Response interface from LocationController to the GenericController parent, so that the response is typed accurately depending on how the child class has defined it. Clearly it doesn't work like this since the interface is defined outside of the class so the parent has no idea about the Response interface in the LocationController.ts file.
I've tried passing interface as an argument in the constructor, that doesn't work. So is there a way I can make this happen? I feel like I'm missing something really simple.
Typically, generics are used in a situation like this. Here's how I'd do it:
interface Response {
id: number;
}
// Note the generic parameter <R extends Response>
export class GenericController<R extends Response> {
tableName: string = "";
fields: Array<any> = [];
_get = async (req, res) => {
try {
const id = req.body["id"];
const send = async () => {
// The array is now properly typed. You don't know the exact type,
// but you do know the constraint - R is some type of `Response`
let resp: Array<R> = await db(this.tableName).select(this.fields).where("id", id);
if (resp[0] === undefined) {
// some error handling
}
res.status(status.success).json(resp[0]);
};
await send();
} catch (error) {
// some error handling
}
};
}
import { GenericController } from "./_genericController";
interface Response {
id: number;
code: string;
name: string;
type: string;
long: number;
lat: number;
}
const fields = ["code", "name", "type", "long", "lat"];
// Here we tell the GenericController exactly what type of Response it's going to get
class LocationController extends GenericController<Response> {
tableName: string = "location";
fields: Array<any> = fields;
}
const locationController = new LocationController();
const get = async (req, res) => {
await locationController._get(req, res);
};
export { get };
If this is not enough and you wish to somehow know the exact response type you're going to get, I believe the only way is a manual check. For example:
import { LocationResponse } from './locationController';
// ... stuff
// Manual runtime type check
if (this.tableName === 'location') {
// Manual cast
resp = resp as Array<LocationResponse>
}
// ...
You could also check the form of resp[0] (if (resp[0].hasOwnProperty('code')) { ... }) and cast accordingly. There are also nicer ways to write this, but the basic idea remains the same.
Generally, a properly written class should be unaware of any classes that inherit from it. Putting child-class-specific logic into your generic controller is a code smell. Though as always, it all depends on a particular situation.

This expression is not callable. Type 'string' has no call signatures

I'm trying to create a template file from an object where the key can be either a string or a function that returns a string:
export const createDynamicTemplate = (
templateParams: CreateDynamicTemplateParams
) => {
const { template, projectPath = '', param = '' } = templateParams
const updatedTemplateArr = Object.keys(template).map((key: string) => {
return {
[key]: {
filePath: `${projectPath}/${key}`,
template: typeof template[key] === 'function' ?
template[key](param) : template[key],
},
}
})
const updatedTemplate = Object.assign({}, ...updatedTemplateArr)
return updatedTemplate
}
My interfaces are:
export interface TemplateObject {
[key: string]: string
}
export interface FunctionalTemplateObject {
[key: string]: (param: string) => void
}
export interface CreateDynamicTemplateParams {
template: FunctionalTemplateObject | TemplateObject
projectPath: string
param: string
}
It keeps throwing this error in createDynamicTemplate though:
This expression is not callable.
Not all constituents of type 'string | ((param: string) => void)' are callable.
Type 'string' has no call signatures.
What am I doing wrong here?
Checking the type of a child property of a variable which references an object will not narrow the type of the variable-object. You can save the value at the key in a separate variable first, then check that value to narrow its type:
const updatedTemplateArr = Object.keys(template).map((key: string) => {
const item = template[key];
if (typeof item === 'function') {
return {
[key]: {
filePath: `${projectPath}/${key}`,
template: item(param),
},
}
}
})
Or, even better, use Object.entries to get the key and value at once. (Also note that there's no need to note the type of the .map parameters - TS can infer it automatically just fine)
const updatedTemplateArr = Object.entries(template).map(([key, value]) => {
if (typeof value === 'function') {
return {
[key]: {
filePath: `${projectPath}/${key}`,
template: value(param),
},
}
}
})

Operators invoked multiple times for merged Observable although only one source emits

I have a function in which I'm calling an instance of Manager's onSpecificData() to which I'm subscribing in order to update my application's state (I'm managing a state on the server-side as well).
The problem is that in the SomeManager's implementation of onSpecificData() I'm merging 3 different Observables using merge() operator, which for some reason triggers the invocation of all the underlying Observable's operators even though only 1 of the sources is the one that's emitting a value
SomeManager.ts
export class DerivedManager implements Manager {
private driver: SomeDriver;
constructor(...) {
this.driver = new SomeDriver(...);
}
public onSpecificData(): Observable<DataType> {
return merge(
this.driver.onSpecificData(Sources.Source1).map((value) => {
return {source1: value};
}),
this.driver.onSpecificData(Sources.Source2).map((value) => {
return {source2: value};
}),
this.driver.onSpecificData(Sources.Source3).map((value) => {
return {source3: value};
})
);
}
Manager.ts
export type DataType = Partial<{value1: number, value2: number, value3: number}>;
export interface Manager {
onSpecificData(): Observable<DataType>;
}
SomeDriver.ts
export const enum Sources {
Source1,
Source2,
Source3,
}
export class SomeDriver extends Driver {
private static specificDataId = 1337; // some number
private handler: Handler;
constructor(...) {
super(...);
this.handler = new Handler(this.connection, ...);
// ...
}
// ...
onSpecificData(source: Sources): Observable<number> {
return this.handler
.listenToData<SpecificDataType>(
SomeDriver.specificDataId,
(data) => data.source === source)
).map((data) => data.value);
}
}
Driver.ts
export abstract class Driver {
protected connection: Duplex;
constructor(...) {
// init connection, etc...
}
public abstract onSpecificData(source: number);
// some implementations and more abstract stuff...
}
Handler.ts
export class Handler {
private data$: Observable<Buffer>;
constructor(private connection: Duplex, ...) {
this.data$ = Observable.fromEvent<Buffer>(connection as any, 'data');
}
listenToData<T>(dataId: number, filter?: (data: T) => boolean) {
return this.data$
.map((data) => {
// decode and transform
})
.filter((decodedData) => !decodedData.error && decodedData.value.id)
.do((decodedData) => {
console.log(`Got ${decodedData.value.id}`);
})
.map((decodedData) => decodedData.value.value as T)
.filter(filter || () => true);
}
}
And finally, subscribe()-ing:
export default function(store: Store<State>, manager: Manager) {
// ...
manager.onSpecificData()
.subscribe((data) => {
// update state according to returned data
});
}
As you can see, there is only 1 underlying Observable (data$) but apparently the operator chain in listenToData<T>() is invoked 3 times for each value emitted by it. I already know this is because of SomeManager#onSpecificData()'s merge of those 3 Observables, but I don't know why this happens. I want it to be invoked once for each value.
Help will be much appreciated.
I solved this in a "hacky" way, in my opinion. I replaced data$ with a Subject, created an observable from stream's 'data' event, moving all the shared logic to that observable and emit a value from the subject, like so:
export class Handler {
private dataSrc = new Subject<DecodedData>();
constructor(private connection: Duplex, ...) {
Observable.fromEvent<Buffer>(connection as any, 'data')
.map((data) => {
// decode and transform
})
.filter((decodedData) => !decodedData.error)
.do((decodedData) => {
console.log(`Got ${decodedData.value.id}`);
})
.subscribe((decodedData) => {
this.dataSrc.next(decodedData);
});
}
listenToData<T>(dataId: number, filter?: (data: T) => boolean) {
return this.dataSrc
.filter((decodedData) => decodedData.value.id === dataId)
.map((decodedData) => decodedData.value.value as T)
.filter(filter || () => true);
}
}
Not exactly the solution I was looking for, but it works. If anyone has a better solution, which better suits the "Rx way" to do stuff, I'd love to hear it.

Resources