Error when importing AngularFireDatabaseModule - angularfire

I'm trying to add angularfire database module to a new angular project but when i add the line :
import { AngularFireDatabaseModule } from '#angular/fire/compat/database';
i get this error :
Error: node_modules/#angular/fire/compat/database/interfaces.d.ts:47:18 - error TS2430: Interface 'DatabaseSnapshotExists<T>' incorrectly extends interface 'DataSnapshot'.
Types of property 'forEach' are incompatible.
Type '(action: (a: DatabaseSnapshot<T>) => boolean) => boolean' is not assignable to type '(action: (a: DataSnapshot & { key: string; }) => boolean | void) => boolean'.
Types of parameters 'action' and 'action' are incompatible.
Types of parameters 'a' and 'a' are incompatible.
Type 'DatabaseSnapshot<T>' is not assignable to type 'DataSnapshot & { key: string; }'.
Type 'DatabaseSnapshotExists<T>' is not assignable to type 'DataSnapshot & { key: string; }'.
Type 'DatabaseSnapshotExists<T>' is not assignable to type 'DataSnapshot'.
Types of property 'forEach' are incompatible.
Type '(action: (a: DatabaseSnapshot<T>) => boolean) => boolean' is not assignable to type '(action: (a: DataSnapshot & { key: string; }) => boolean | void) => boolean'.
Types of parameters 'action' and 'action' are incompatible.
Types of parameters 'a' and 'a' are incompatible.
Type 'DatabaseSnapshot<T>' is not assignable to type 'DataSnapshot & { key: string; }'.
Type 'DatabaseSnapshotDoesNotExist<T>' is not assignable to type 'DataSnapshot &
{ key: string; }'.
Type 'DatabaseSnapshotDoesNotExist<T>' is not assignable to type '{ key: string; }'.
Types of property 'key' are incompatible.
Type 'string | null' is not assignable to type 'string'.
Type 'null' is not assignable to type 'string'.
47 export interface DatabaseSnapshotExists<T> extends firebase.database.DataSnapshot {
~~~~~~~~~~~~~~~~~~~~~~
Error: node_modules/#angular/fire/compat/database/interfaces.d.ts:52:18 - error TS2430: Interface 'DatabaseSnapshotDoesNotExist<T>' incorrectly extends interface 'DataSnapshot'.
Types of property 'forEach' are incompatible.
Type '(action: (a: DatabaseSnapshot<T>) => boolean) => boolean' is not assignable to type '(action: (a: DataSnapshot & { key: string; }) => boolean | void) => boolean'.
Types of parameters 'action' and 'action' are incompatible.
Types of parameters 'a' and 'a' are incompatible.
Type 'DatabaseSnapshot<T>' is not assignable to type 'DataSnapshot & { key: string; }'.
Type 'DatabaseSnapshotDoesNotExist<T>' is not assignable to type 'DataSnapshot & { key: string; }'.
Type 'DatabaseSnapshotDoesNotExist<T>' is not assignable to type 'DataSnapshot'.
Types of property 'forEach' are incompatible.
Type '(action: (a: DatabaseSnapshot<T>) => boolean) => boolean' is not assignable to type '(action: (a: DataSnapshot & { key: string; }) => boolean | void) => boolean'.
Types of parameters 'action' and 'action' are incompatible.
Types of parameters 'a' and 'a' are incompatible.
Type 'DatabaseSnapshot<T>' is not assignable to type 'DataSnapshot & { key: string; }'.
Type 'DatabaseSnapshotExists<T>' is not assignable to type 'DataSnapshot & { key: string; }'.
Type 'DatabaseSnapshotExists<T>' is not assignable to type '{ key: string; }'.
Types of property 'key' are incompatible.
Type 'string | null' is not assignable to type 'string'.
Type 'null' is not assignable to type 'string'.
52 export interface DatabaseSnapshotDoesNotExist<T> extends firebase.database.DataSnapshot {
here's my complete app.module.ts file :
import { NgModule } from '#angular/core';
import { BrowserModule } from '#angular/platform-browser';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { environment } from '../environments/environment';
import {AngularFireModule} from '#angular/fire/compat';
import { AngularFireDatabaseModule } from '#angular/fire/compat/database';
import { BrowserAnimationsModule } from '#angular/platform-browser/animations';
#NgModule({
declarations: [
AppComponent,
],
imports: [
BrowserModule,
AppRoutingModule,
AngularFireModule.initializeApp(environment.firebase),
AngularFireDatabaseModule,
BrowserAnimationsModule,
],
providers: [ ],
bootstrap: [AppComponent]
})
export class AppModule { }
I already tried to delete and reinstall the package but that didn't work.
Also, i have an older project with exactly the same line of code and the same version of angular and angular/fire ("#angular/core": "^14.0.0",
"#angular/fire": "^7.4.1") which work but i can't create a new one.
Any ideas ?

Reason
The reason of this error is change of forEach() signature of firebase.database.DataSnapshot in firebase#9.9.2. Probably in your old project you have an older version (you can check in package-lock.json or in your node_modules) and the error will appear if you run npm update --save there.
These are interfaces from #angular/fire:
export interface DatabaseSnapshotExists<T> extends firebase.database.DataSnapshot {
exists(): true;
val(): T;
forEach(action: (a: DatabaseSnapshot<T>) => boolean): boolean;
}
export interface DatabaseSnapshotDoesNotExist<T> extends firebase.database.DataSnapshot {
exists(): false;
val(): null;
forEach(action: (a: DatabaseSnapshot<T>) => boolean): boolean;
}
In firebase#9.9.2 firebase.database.DataSnapshot has such method:
forEach(
action: (
a: firebase.database.DataSnapshot & { key: string }
) => boolean | void
): boolean;
In firebase#9.9.1 it was:
forEach(
action: (a: firebase.database.DataSnapshot) => boolean | void
): boolean;
Solution
You can install firebase#9.9.1 explicitly to get rid of this error (npm install firebase#9.9.1 --save --save-exact). But remember to update it later when this issue is fixed: https://github.com/angular/angularfire/issues/3255
Update
The problem is solved with firebase#9.9.3. firebase.database.DataSnapshot again has the same forEach signature as in firebase#9.9.1. So just use firebase#9.9.3 (or newer) or remove it from package.json and use version picked up by #angular/fire.

I got the same error message after I updated lots of Node modules using npm update, including updating Firebase 9.9.4 to Firebase 9.10.0. Reverting to Firebase 9.9.1 didn't fix the problem. I reverted my project to the backup and updated the Node modules one by one, checking if Angular started up. The culprit turned out to be TypeScript, updated from 4.7.4 to 4.8.3. Staying with TypeScript 4.7.4 with project runs fine.

angular/fire 7.5.0 firebase 9.16.0
open the interfaces.d.ts the file that generates the error
add at the end of the row <T>
export interface QueryDocumentSnapshot<T> extends firebase.firestore.QueryDocumentSnapshot<T>

I am Facing the same problem, so i back to the version #9.9.1 the issue has been resolved after that, please enter the below code for resolving this issue-
npm install firebase#9.9.1 --save --save-exact
Hope it works.

I changed the interfaces.d.ts with this code, then i can get any collection from firestore.
export interface DocumentSnapshotExists<DocumentData> extends firebase.firestore.DocumentSnapshot {
readonly exists: true;
data(options?: SnapshotOptions): any;
}
export interface DocumentSnapshotDoesNotExist extends firebase.firestore.DocumentSnapshot {
readonly exists: false;
data(options?: SnapshotOptions): undefined;
get(fieldPath: string | FieldPath, options?: SnapshotOptions): undefined;
}
export interface QueryDocumentSnapshot<DocumentData> extends firebase.firestore.QueryDocumentSnapshot {
data(options?: SnapshotOptions): any;
}

Just open following file:
node_modules/#angular/fire/compat/firestore/interfaces.d.ts
You can see below that the subclasses have the wrong return value
Change it to this
Basically you just need to add <T> on the end.

Related

Call SQL linter's API from Codemirror with Typescript

I am trying to call an API to lint a SQL query written in Codemirror (actually I use Angular and the wrapper ngx-codemirror)
Unfortunately, I could not call the API because this is considered undefined:
data-analyzer.component.html:81 ERROR TypeError: Cannot read property 'analyzerService' of undefined
at testA (data-analyzer.component.ts:624)
at lintAsync (lint.js:134)
at startLinting (lint.js:152)
at Object.lint (lint.js:248)
at new CodeMirror (codemirror.js:7885)
at CodeMirror (codemirror.js:7831)
at Function.fromTextArea (codemirror.js:9682)
at ctrl-ngx-codemirror.js:64
at ZoneDelegate.invoke (zone-evergreen.js:365)
at Zone.run (zone-evergreen.js:124)
My code is as follow:
<ngx-codemirror
#ref
name="query"
[options]="config"
[(ngModel)]="item.query"
(keypress)="CMonKeyPress($event)"
>
</ngx-codemirror>
config = {
mode: 'text/x-mysql',
showHint: true,
lint: {
lintOnChange: true,
getAnnotations: this.lintSQL
},
gutters: [
'CodeMirror-linenumbers',
'CodeMirror-lint-markers'
]
};
constructor(
private analyzerService: DataAnalyzerService
) {}
lintSQL(a: string, b: LintStateOptions, cm: Editor) {
const found: Annotation[] = [];
// The error occurs here
this.analyzerService.lint(this.item.query).subscribe(
(r: any) => {
console.log(r.data);
},
(err) => {
console.log(err);
}
);
// So far I return an empty array, the focus is to get the results from the service
return found;
}
I would like to know how could I access to the service in the linting function.
Found from this question, the solution is to bind (this) as follow:
getAnnotations: this.lintSQL.bind(this)

Why is code completion for subclasses not working in angular templates?

For example, when we have 2 array properties on our component:
array: an ordinary Array
anonymousArray a subclass of Array
export class AppComponent {
readonly array = new Array<{
text: string;
value: string;
}>();
readonly anonymousArray = new class extends Array<{
text: string;
value: string;
}> {
add(text: string, value: string) {
this.push({
text,
value
});
}
}();
constructor() {
this.array.push({
text: "text1",
value: "value1"
});
this.anonymousArray.add("text", "value");
}
}
Then code-completion in the template works for the ordinary Array:
but not for the sub-class:
Here's a full Stackblitz example
IntelliJ will even show errors:
I wonder how this is possible in the first place: i.e. since Array.isArray(this.anonymousArray) is true, how/why does the template even see a difference?
Is this maybe a bug in Ivy or the angular language service?
Works fine for me in the most recent IDEA version:
Edit: appears to be specific to libraries versions being used, tracked at WEB-49995

Replacing the query causing NavigationDuplicated error in Vue-router

I need to remove the value from arrayed query parameter. Suppose, when query is
{
item_ids: [ "12", "13" ],
other_param: [ "alpha", "bravo" ]
}
my function removeElementFromArrayedQueryParameter('item_ids', 13) must turn query to:
{
item_ids: [ "12" ],
other_param: [ "alpha", "bravo" ]
}
Implementation (TypeScript):
function removeElementFromArrayedQueryParameter(key: string, value: string): void {
/** 〔Theory〕 Preventing 'NavigationDuplicated: Navigating to current location ("/〇〇") is not allowed' */
if (isEmptyObject(RoutingHelper.router.currentRoute.query)) {
return;
}
if (!Array.isArray(RoutingHelper.router.currentRoute.query[key])) {
return;
}
const updatedQuery: QueryParameters = {
...RoutingHelper.router.currentRoute.query as object
};
removeSingleElementFromArrayByPredicateMutably(
updatedQuery[key] as Array<string>, (arrayElement: string): boolean => arrayElement === value
);
console.log(JSON.stringify(updatedQuery, null, 2)); // I checked: the element has been romoved
// it's the router instance created by new VueRouter({})
RoutingHelper.router.push({
query: updatedQuery
})
.catch((error: Error): void => {
console.error(error)
});
}
function isEmptyObject(potentialObject: unknown): potentialObject is object {
if (typeof potentialObject !== "object" || potentialObject === null) {
return false;
}
return Object.entries(potentialObject as {[key: string]: unknown}).length === 0;
}
Although the removing of target element from updatedQuery successful (checked by manual testing), I have console error:
{
"_name": "NavigationDuplicated",
"name": "NavigationDuplicated",
"message": "Navigating to current location (\"/page?item_ids=12\") is not allowed"
}
"message" in console error contains right target location, but actually one of item_ids has not been removed from URI.
The console error is right about route name is same, but I don't going to redirect on same page: I just want to remove one query parameter. router.push casts similar error.
Update
Please note that TypeScript does not allow to write as
this.$router.replace({
...this.$router.currentRoute,
query
});
TS2769: No overload matches this call.
Overload 1 of 2, '(location: RawLocation): Promise<Route>', gave the following error.
Argument of type '{ query: Dictionary<string | (string | null)[] | null | undefined>; path: string; name?: string |
null | undefined; hash: string; params: Dictionary<string>; fullPath: string; matched: RouteRecord[]; redirectedFrom?: s
tring | undefined; meta?: any; }' is not assignable to parameter of type 'RawLocation'.
Type '{ query: Dictionary<string | (string | null)[] | null | undefined>; path: string; name?: string | null | und
efined; hash: string; params: Dictionary<string>; fullPath: string; matched: RouteRecord[]; redirectedFrom?: string | un
defined; meta?: any; }' is not assignable to type 'Location'.
Types of property 'name' are incompatible.
Type 'string | null | undefined' is not assignable to type 'string | undefined'.
Type 'null' is not assignable to type 'string | undefined'.
Overload 2 of 2, '(location: RawLocation, onComplete?: Function | undefined, onAbort?: ErrorHandler | undefined): void
', gave the following error.
Argument of type '{ query: Dictionary<string | (string | null)[] | null | undefined>; path: string; name?: string |
null | undefined; hash: string; params: Dictionary<string>; fullPath: string; matched: RouteRecord[]; redirectedFrom?: s
tring | undefined; meta?: any; }' is not assignable to parameter of type 'RawLocation'.
Type '{ query: Dictionary<string | (string | null)[] | null | undefined>; path: string; name?: string | null | und
efined; hash: string; params: Dictionary<string>; fullPath: string; matched: RouteRecord[]; redirectedFrom?: string | un
defined; meta?: any; }' is not assignable to type 'Location'.
Types of property 'name' are incompatible.
Type 'string | null | undefined' is not assignable to type 'string | undefined'.
Type 'null' is not assignable to type 'string | undefined'.
If the are no mistake in TypeScript types, above solution is not safe.
this.$router.replace({
...this.$router.name === null ? {} : RoutingHelper.router.currentRoute,
query: updatedQuery
})
does not fix it.
You should update your new route like this
function removeFromQuery(route, queryName, queryValue)
{
const query = Object.assign({}, route.query);
if (queryName in query)
{
const idx = query[queryName].indexOf(queryValue);
if (idx !== -1)
{
query[queryName].splice(idx, 1);
this.$router.replace({
...this.$router.currentRoute,
query
});
}
}
}
The updatedQuery query is not the deep clone of RoutingHelper.router.currentRoute.query. Below code is not enough to create the deep copy of query:
const updatedQuery: QueryParameters = {
...RoutingHelper.router.currentRoute.query as object
};
So, when execute
RoutingHelper.router.push({
query: updatedQuery
})
we don't subtitute query to new value. That why error occurs.
Use lodash or other libraries provides deep cloning, or use own implementation of deep cloning.

setting up a one to many relationship for a self referencing table

I have a Project entity with a non-autogenerated id field and a successor field. This successor is the project that follows next. But maybe there is no following project so this might be null.
#Entity()
export class Project extends BaseEntity {
#PrimaryColumn({ unique: true })
public id: string;
#OneToMany(() => Project, project => project.id, { nullable: true })
public successorId?: string;
}
When creating a new project via
public createProject(id: string, successorId?: string): Promise<Project> {
const project: Project = new Project();
project.id = id;
project.successorId = successorId;
return project.save();
}
there are multiple cases I have to take care for.
Passing in an id that already exists:
This will not throw an error. It just overrides the existing entity.
Passing in undefined for the successorId:
The code works fine then but it does not create a successorId column with null then. The column simply does not exist in the database.
Passing in the same id for id and successorId (this should be possible):
TypeORM throws the error
TypeError: Cannot read property 'joinColumns' of undefined
Passing in a successorId of another existing project:
I'm getting the same error as above
Passing in a successorId of a project that doesn't exist:
I'm getting the same error as above
So how can I fix that? I think my entity design seems to be wrong. Basically it should be
One project might have one successor
A project can be the successor of many projects
Would be awesome if someone could help!
Update
I also tried this
#OneToMany(() => Project, project => project.successorId, { nullable: true })
#Column()
public successorId?: string;
but whenever I want to call the createProject method I'm getting this error
QueryFailedError: null value in column "successorId" violates not-null
constraint
and this
#OneToMany(() => Project, project => project.successorId, { nullable: true })
public successorId?: string;
but then I'm getting this error
TypeError: relatedEntities.forEach is not a function
Please try this solution
#Entity()
export class Project extends BaseEntity {
#PrimaryColumn({ unique: true })
public id: string;
#Column({ nullable: true })
public successorId?: string;
#ManyToOne(() => Project, project => project.id)
#JoinColumn({ name: "successorId" })
public successor?: Project;
}
public successor?: Project; - property is used for building relation between entities (same entity in this case). Related entity must be specified as a property type, because TypeORM uses this type to determine target entity and build relation metadata. You can read more about TypeORM relations here
public successorId?: string; - property is just an "extracted" join column. When you use ManyToOne relation, TypeORM automatically creates a column in the database named propertyName + referencedColumnName (successorId in this case). But you cannot use this column in your code, because it is defined only in table and not in your class. And if you need this column defined in class (for further saving or displaying) you can create a property and mark it with a #Column decorator. Property name must be the same as the join column name in the table. Described in more detail here
Creating an entity with the same id just overrides the existing one
this is an expected behaviour. When you trying to save entity with an existing Id, TypeORM recognizes this as an update, not a create
You can try this
export class RolesPermission {
#PrimaryGeneratedColumn('uuid')
#PrimaryColumn({ type: 'varchar', length: 36, default: 'UUID()' })
entityId?: string;
#Column({ unique: true })
name: string;
#OneToMany(() => RolesPermission, (rolePermission) => rolePermission.parent)
rolePermissions?: RolesPermission[];
#ManyToOne(() => RolesPermission, (rolePermission) => rolePermission.rolePermissions, { nullable: true, createForeignKeyConstraints: false })
parent?: RolesPermission;
#Column({ type: 'datetime', default: () => 'CURRENT_TIMESTAMP' })
createdAt?: Date;
#Column({ type: 'datetime', default: () => 'CURRENT_TIMESTAMP', onUpdate: 'CURRENT_TIMESTAMP' })
updatedAt?: Date;}

Accessing interface/class of a nested module in TypeScript

I've got a TS module that holds an inner module, such as:
module abc.customer.ratings {
module abc.customer.ratings.bo {
export interface RatingScale {
id: number;
name: string;
type: string;
}
}
var scale: ??? // how to name the inner interface here?
}
I've tried to use:
RatingScale, just the name - failed
bo.RatingScale - the inner module name (like a relative path) + just the name - failed
abc.customer.ratings.bo.RatingScale - full module path from the beginning of the world - worked
My question is - can I use this in any shorter way, because the one that works is really verbose.
In this code:
module abc.customer.ratings {
module abc.customer.ratings.bo {
export interface RatingScale {
id: number;
name: string;
type: string;
}
}
var scale: ??? // how to name the inner interface here?
}
The fully-qualified name of RatingScale is abc.customer.ratings.abc.customer.ratings.bo.RatingScale. What you probably wanted to write was:
module abc.customer.ratings {
module bo {
export interface RatingScale {
id: number;
name: string;
type: string;
}
}
var scale: bo.RatingScale;
}