diff --git a/client/src/app/+about/about-follows/about-follows.component.ts b/client/src/app/+about/about-follows/about-follows.component.ts
index d335cbf45..1dcb6396c 100644
--- a/client/src/app/+about/about-follows/about-follows.component.ts
+++ b/client/src/app/+about/about-follows/about-follows.component.ts
@@ -91,8 +91,8 @@ export class AboutFollowsComponent implements OnInit {
     const pagination = this.restService.componentPaginationToRestPagination(this.followersPagination)
 
     this.followService.getFollowers({ pagination: pagination, sort: this.sort, state: 'accepted' })
-        .subscribe(
-          resultList => {
+        .subscribe({
+          next: resultList => {
             if (reset) this.followers = []
 
             const newFollowers = resultList.data.map(r => r.follower.host)
@@ -101,16 +101,16 @@ export class AboutFollowsComponent implements OnInit {
             this.followersPagination.totalItems = resultList.total
           },
 
-          err => this.notifier.error(err.message)
-        )
+          error: err => this.notifier.error(err.message)
+        })
   }
 
   private loadMoreFollowings (reset = false) {
     const pagination = this.restService.componentPaginationToRestPagination(this.followingsPagination)
 
     this.followService.getFollowing({ pagination, sort: this.sort, state: 'accepted' })
-        .subscribe(
-          resultList => {
+        .subscribe({
+          next: resultList => {
             if (reset) this.followings = []
 
             const newFollowings = resultList.data.map(r => r.following.host)
@@ -119,8 +119,8 @@ export class AboutFollowsComponent implements OnInit {
             this.followingsPagination.totalItems = resultList.total
           },
 
-          err => this.notifier.error(err.message)
-        )
+          error: err => this.notifier.error(err.message)
+        })
   }
 
 }
diff --git a/client/src/app/+about/about-instance/contact-admin-modal.component.ts b/client/src/app/+about/about-instance/contact-admin-modal.component.ts
index 37e9feacb..cbc759881 100644
--- a/client/src/app/+about/about-instance/contact-admin-modal.component.ts
+++ b/client/src/app/+about/about-instance/contact-admin-modal.component.ts
@@ -83,18 +83,18 @@ export class ContactAdminModalComponent extends FormReactive implements OnInit {
     const body = this.form.value[ 'body' ]
 
     this.instanceService.contactAdministrator(fromEmail, fromName, subject, body)
-        .subscribe(
-          () => {
+        .subscribe({
+          next: () => {
             this.notifier.success($localize`Your message has been sent.`)
             this.hide()
           },
 
-          err => {
+          error: err => {
             this.error = err.status === HttpStatusCode.FORBIDDEN_403
               ? $localize`You already sent this form recently`
               : err.message
           }
-        )
+        })
   }
 
   private prefillForm (prefill: Prefill) {
diff --git a/client/src/app/+accounts/accounts.component.ts b/client/src/app/+accounts/accounts.component.ts
index 5b59f3cd0..25eb13588 100644
--- a/client/src/app/+accounts/accounts.component.ts
+++ b/client/src/app/+accounts/accounts.component.ts
@@ -71,11 +71,11 @@ export class AccountsComponent implements OnInit, OnDestroy {
                             HttpStatusCode.NOT_FOUND_404
                           ]))
                         )
-                        .subscribe(
-                          videoChannels => this.videoChannels = videoChannels.data,
+                        .subscribe({
+                          next: videoChannels => this.videoChannels = videoChannels.data,
 
-                          err => this.notifier.error(err.message)
-                        )
+                          error: err => this.notifier.error(err.message)
+                        })
 
     this.links = [
       { label: $localize`CHANNELS`, routerLink: 'video-channels' },
@@ -174,11 +174,12 @@ export class AccountsComponent implements OnInit, OnDestroy {
 
     const user = this.authService.getUser()
     if (user.hasRight(UserRight.MANAGE_USERS)) {
-      this.userService.getUser(account.userId).subscribe(
-        accountUser => this.accountUser = accountUser,
+      this.userService.getUser(account.userId)
+        .subscribe({
+          next: accountUser => this.accountUser = accountUser,
 
-        err => this.notifier.error(err.message)
-      )
+          error: err => this.notifier.error(err.message)
+        })
     }
   }
 
diff --git a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts
index 1e8cfb021..538fa6f14 100644
--- a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts
+++ b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts
@@ -267,8 +267,8 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit {
       this.configService.updateCustomConfig(omit(value, 'instanceCustomHomepage')),
       this.customPage.updateInstanceHomepage(value.instanceCustomHomepage.content)
     ])
-      .subscribe(
-        ([ resConfig ]) => {
+      .subscribe({
+        next: ([ resConfig ]) => {
           const instanceCustomHomepage = {
             content: value.instanceCustomHomepage.content
           }
@@ -284,8 +284,8 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit {
           this.notifier.success($localize`Configuration updated.`)
         },
 
-        err => this.notifier.error(err.message)
-      )
+        error: err => this.notifier.error(err.message)
+      })
   }
 
   hasConsistentOptions () {
@@ -339,8 +339,8 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit {
     forkJoin([
       this.configService.getCustomConfig(),
       this.customPage.getInstanceHomepage()
-    ])
-      .subscribe(([ config, homepage ]) => {
+    ]).subscribe({
+      next: ([ config, homepage ]) => {
         this.customConfig = { ...config, instanceCustomHomepage: homepage }
 
         this.updateForm()
@@ -348,21 +348,21 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit {
         this.forceCheck()
       },
 
-      err => this.notifier.error(err.message)
-    )
+      error: err => this.notifier.error(err.message)
+    })
   }
 
   private loadCategoriesAndLanguages () {
     forkJoin([
       this.serverService.getVideoLanguages(),
       this.serverService.getVideoCategories()
-    ]).subscribe(
-      ([ languages, categories ]) => {
+    ]).subscribe({
+      next: ([ languages, categories ]) => {
         this.languageItems = languages.map(l => ({ label: l.label, id: l.id }))
         this.categoryItems = categories.map(l => ({ label: l.label, id: l.id + '' }))
       },
 
-      err => this.notifier.error(err.message)
-    )
+      error: err => this.notifier.error(err.message)
+    })
   }
 }
diff --git a/client/src/app/+admin/follows/followers-list/followers-list.component.ts b/client/src/app/+admin/follows/followers-list/followers-list.component.ts
index 4a312f6aa..b867b4ba5 100644
--- a/client/src/app/+admin/follows/followers-list/followers-list.component.ts
+++ b/client/src/app/+admin/follows/followers-list/followers-list.component.ts
@@ -35,17 +35,17 @@ export class FollowersListComponent extends RestTable implements OnInit {
     follow.state = 'accepted'
 
     this.followService.acceptFollower(follow)
-      .subscribe(
-        () => {
+      .subscribe({
+        next: () => {
           const handle = follow.follower.name + '@' + follow.follower.host
           this.notifier.success($localize`${handle} accepted in instance followers`)
         },
 
-        err => {
+        error: err => {
           follow.state = 'pending'
           this.notifier.error(err.message)
         }
-      )
+      })
   }
 
   async rejectFollower (follow: ActorFollow) {
@@ -54,19 +54,19 @@ export class FollowersListComponent extends RestTable implements OnInit {
     if (res === false) return
 
     this.followService.rejectFollower(follow)
-        .subscribe(
-          () => {
+        .subscribe({
+          next: () => {
             const handle = follow.follower.name + '@' + follow.follower.host
             this.notifier.success($localize`${handle} rejected from instance followers`)
 
             this.reloadData()
           },
 
-          err => {
+          error: err => {
             follow.state = 'pending'
             this.notifier.error(err.message)
           }
-        )
+        })
   }
 
   async deleteFollower (follow: ActorFollow) {
@@ -75,27 +75,27 @@ export class FollowersListComponent extends RestTable implements OnInit {
     if (res === false) return
 
     this.followService.removeFollower(follow)
-        .subscribe(
-          () => {
+        .subscribe({
+          next: () => {
             const handle = follow.follower.name + '@' + follow.follower.host
             this.notifier.success($localize`${handle} removed from instance followers`)
 
             this.reloadData()
           },
 
-          err => this.notifier.error(err.message)
-        )
+          error: err => this.notifier.error(err.message)
+        })
   }
 
   protected reloadData () {
     this.followService.getFollowers({ pagination: this.pagination, sort: this.sort, search: this.search })
-                      .subscribe(
-                        resultList => {
+                      .subscribe({
+                        next: resultList => {
                           this.followers = resultList.data
                           this.totalRecords = resultList.total
                         },
 
-                        err => this.notifier.error(err.message)
-                      )
+                        error: err => this.notifier.error(err.message)
+                      })
   }
 }
diff --git a/client/src/app/+admin/follows/following-list/follow-modal.component.ts b/client/src/app/+admin/follows/following-list/follow-modal.component.ts
index dc6909200..c55fc8d81 100644
--- a/client/src/app/+admin/follows/following-list/follow-modal.component.ts
+++ b/client/src/app/+admin/follows/following-list/follow-modal.component.ts
@@ -57,13 +57,14 @@ export class FollowModalComponent extends FormReactive implements OnInit {
   private async addFollowing () {
     const hostsOrHandles = splitAndGetNotEmpty(this.form.value['hostsOrHandles'])
 
-    this.followService.follow(hostsOrHandles).subscribe(
-      () => {
-        this.notifier.success($localize`Follow request(s) sent!`)
-        this.newFollow.emit()
-      },
+    this.followService.follow(hostsOrHandles)
+      .subscribe({
+        next: () => {
+          this.notifier.success($localize`Follow request(s) sent!`)
+          this.newFollow.emit()
+        },
 
-      err => this.notifier.error(err.message)
-    )
+        error: err => this.notifier.error(err.message)
+      })
   }
 }
diff --git a/client/src/app/+admin/follows/following-list/following-list.component.ts b/client/src/app/+admin/follows/following-list/following-list.component.ts
index ba62dfa23..cf0225098 100644
--- a/client/src/app/+admin/follows/following-list/following-list.component.ts
+++ b/client/src/app/+admin/follows/following-list/following-list.component.ts
@@ -49,25 +49,26 @@ export class FollowingListComponent extends RestTable implements OnInit {
     )
     if (res === false) return
 
-    this.followService.unfollow(follow).subscribe(
-      () => {
-        this.notifier.success($localize`You are not following ${follow.following.host} anymore.`)
-        this.reloadData()
-      },
+    this.followService.unfollow(follow)
+      .subscribe({
+        next: () => {
+          this.notifier.success($localize`You are not following ${follow.following.host} anymore.`)
+          this.reloadData()
+        },
 
-      err => this.notifier.error(err.message)
-    )
+        error: err => this.notifier.error(err.message)
+      })
   }
 
   protected reloadData () {
     this.followService.getFollowing({ pagination: this.pagination, sort: this.sort, search: this.search })
-                      .subscribe(
-                        resultList => {
+                      .subscribe({
+                        next: resultList => {
                           this.following = resultList.data
                           this.totalRecords = resultList.total
                         },
 
-                        err => this.notifier.error(err.message)
-                      )
+                        error: err => this.notifier.error(err.message)
+                      })
   }
 }
diff --git a/client/src/app/+admin/follows/shared/redundancy-checkbox.component.ts b/client/src/app/+admin/follows/shared/redundancy-checkbox.component.ts
index 729b7f599..47c402510 100644
--- a/client/src/app/+admin/follows/shared/redundancy-checkbox.component.ts
+++ b/client/src/app/+admin/follows/shared/redundancy-checkbox.component.ts
@@ -18,14 +18,14 @@ export class RedundancyCheckboxComponent {
 
   updateRedundancyState () {
     this.redundancyService.updateRedundancy(this.host, this.redundancyAllowed)
-        .subscribe(
-          () => {
+        .subscribe({
+          next: () => {
             const stateLabel = this.redundancyAllowed ? $localize`enabled` : $localize`disabled`
 
             this.notifier.success($localize`Redundancy for ${this.host} is ${stateLabel}`)
           },
 
-          err => this.notifier.error(err.message)
-        )
+          error: err => this.notifier.error(err.message)
+        })
   }
 }
diff --git a/client/src/app/+admin/follows/video-redundancies-list/video-redundancies-list.component.ts b/client/src/app/+admin/follows/video-redundancies-list/video-redundancies-list.component.ts
index 3cd65dd6e..4c691269a 100644
--- a/client/src/app/+admin/follows/video-redundancies-list/video-redundancies-list.component.ts
+++ b/client/src/app/+admin/follows/video-redundancies-list/video-redundancies-list.component.ts
@@ -142,14 +142,14 @@ export class VideoRedundanciesListComponent extends RestTable implements OnInit
     if (res === false) return
 
     this.redundancyService.removeVideoRedundancies(redundancy)
-      .subscribe(
-        () => {
+      .subscribe({
+        next: () => {
           this.notifier.success($localize`Video redundancies removed!`)
           this.reloadData()
         },
 
-        err => this.notifier.error(err.message)
-      )
+        error: err => this.notifier.error(err.message)
+      })
 
   }
 
@@ -161,14 +161,14 @@ export class VideoRedundanciesListComponent extends RestTable implements OnInit
     }
 
     this.redundancyService.listVideoRedundancies(options)
-                      .subscribe(
-                        resultList => {
+                      .subscribe({
+                        next: resultList => {
                           this.videoRedundancies = resultList.data
                           this.totalRecords = resultList.total
                         },
 
-                        err => this.notifier.error(err.message)
-                      )
+                        error: err => this.notifier.error(err.message)
+                      })
   }
 
   private loadSelectLocalStorage () {
diff --git a/client/src/app/+admin/moderation/video-block-list/video-block-list.component.ts b/client/src/app/+admin/moderation/video-block-list/video-block-list.component.ts
index 4fe5ec441..adef16975 100644
--- a/client/src/app/+admin/moderation/video-block-list/video-block-list.component.ts
+++ b/client/src/app/+admin/moderation/video-block-list/video-block-list.component.ts
@@ -62,14 +62,14 @@ export class VideoBlockListComponent extends RestTable implements OnInit {
           handler: videoBlock => {
             this.videoBlocklistService.unblockVideo(videoBlock.video.id).pipe(
               switchMap(_ => this.videoBlocklistService.blockVideo(videoBlock.video.id, undefined, true))
-            ).subscribe(
-              () => {
+            ).subscribe({
+              next: () => {
                 this.notifier.success($localize`Video ${videoBlock.video.name} switched to manual block.`)
                 this.reloadData()
               },
 
-              err => this.notifier.error(err.message)
-            )
+              error: err => this.notifier.error(err.message)
+            })
           },
           isDisplayed: videoBlock => videoBlock.type === VideoBlacklistType.AUTO_BEFORE_PUBLISHED
         }
@@ -94,13 +94,11 @@ export class VideoBlockListComponent extends RestTable implements OnInit {
             if (res === false) return
 
             this.videoService.removeVideo(videoBlock.video.id)
-              .subscribe(
-                () => {
-                  this.notifier.success($localize`Video deleted.`)
-                },
+              .subscribe({
+                next: () => this.notifier.success($localize`Video deleted.`),
 
-                err => this.notifier.error(err.message)
-              )
+                error: err => this.notifier.error(err.message)
+              })
           }
         }
       ]
@@ -136,14 +134,15 @@ export class VideoBlockListComponent extends RestTable implements OnInit {
     const res = await this.confirmService.confirm(confirmMessage, $localize`Unblock`)
     if (res === false) return
 
-    this.videoBlocklistService.unblockVideo(entry.video.id).subscribe(
-      () => {
-        this.notifier.success($localize`Video ${entry.video.name} unblocked.`)
-        this.reloadData()
-      },
+    this.videoBlocklistService.unblockVideo(entry.video.id)
+      .subscribe({
+        next: () => {
+          this.notifier.success($localize`Video ${entry.video.name} unblocked.`)
+          this.reloadData()
+        },
 
-      err => this.notifier.error(err.message)
-    )
+        error: err => this.notifier.error(err.message)
+      })
   }
 
   getVideoEmbed (entry: VideoBlacklist) {
@@ -164,8 +163,8 @@ export class VideoBlockListComponent extends RestTable implements OnInit {
       sort: this.sort,
       search: this.search
     })
-      .subscribe(
-        async resultList => {
+      .subscribe({
+        next: async resultList => {
           this.totalRecords = resultList.total
 
           this.blocklist = resultList.data
@@ -178,7 +177,7 @@ export class VideoBlockListComponent extends RestTable implements OnInit {
           }
         },
 
-        err => this.notifier.error(err.message)
-      )
+        error: err => this.notifier.error(err.message)
+      })
   }
 }
diff --git a/client/src/app/+admin/moderation/video-comment-list/video-comment-list.component.html b/client/src/app/+admin/moderation/video-comment-list/video-comment-list.component.html
index 9d9283536..0fd0588ba 100644
--- a/client/src/app/+admin/moderation/video-comment-list/video-comment-list.component.html
+++ b/client/src/app/+admin/moderation/video-comment-list/video-comment-list.component.html
@@ -34,13 +34,13 @@
 
   <ng-template pTemplate="header">
     <tr>
-      <th style="width: 40px">
+      <th style="width: 40px;">
         <p-tableHeaderCheckbox ariaLabel="Select all rows" i18n-ariaLabel></p-tableHeaderCheckbox>
       </th>
-      <th style="width: 40px"></th>
+      <th style="width: 40px;"></th>
       <th style="width: 150px;"></th>
-      <th style="width: 300px" i18n>Account</th>
-      <th style="width: 300px" i18n>Video</th>
+      <th style="width: 300px;" i18n>Account</th>
+      <th style="width: 300px;" i18n>Video</th>
       <th i18n>Comment</th>
       <th style="width: 150px;" i18n pSortableColumn="createdAt">Date <p-sortIcon field="createdAt"></p-sortIcon></th>
     </tr>
diff --git a/client/src/app/+admin/moderation/video-comment-list/video-comment-list.component.ts b/client/src/app/+admin/moderation/video-comment-list/video-comment-list.component.ts
index e2ae993b0..4904bcc25 100644
--- a/client/src/app/+admin/moderation/video-comment-list/video-comment-list.component.ts
+++ b/client/src/app/+admin/moderation/video-comment-list/video-comment-list.component.ts
@@ -1,5 +1,5 @@
 import { SortMeta } from 'primeng/api'
-import { AfterViewInit, Component, OnInit } from '@angular/core'
+import { Component, OnInit } from '@angular/core'
 import { ActivatedRoute, Router } from '@angular/router'
 import { AuthService, ConfirmService, MarkdownService, Notifier, RestPagination, RestTable } from '@app/core'
 import { AdvancedInputFilter } from '@app/shared/shared-forms'
@@ -117,45 +117,46 @@ export class VideoCommentListComponent extends RestTable implements OnInit {
       pagination: this.pagination,
       sort: this.sort,
       search: this.search
-    }).subscribe(
-        async resultList => {
-          this.totalRecords = resultList.total
+    }).subscribe({
+      next: async resultList => {
+        this.totalRecords = resultList.total
 
-          this.comments = []
+        this.comments = []
 
-          for (const c of resultList.data) {
-            this.comments.push(
-              new VideoCommentAdmin(c, await this.toHtml(c.text))
-            )
-          }
-        },
+        for (const c of resultList.data) {
+          this.comments.push(
+            new VideoCommentAdmin(c, await this.toHtml(c.text))
+          )
+        }
+      },
 
-        err => this.notifier.error(err.message)
-      )
+      error: err => this.notifier.error(err.message)
+    })
   }
 
   private async removeComments (comments: VideoCommentAdmin[]) {
     const commentArgs = comments.map(c => ({ videoId: c.video.id, commentId: c.id }))
 
-    this.videoCommentService.deleteVideoComments(commentArgs).subscribe(
-      () => {
-        this.notifier.success($localize`${commentArgs.length} comments deleted.`)
-        this.reloadData()
-      },
+    this.videoCommentService.deleteVideoComments(commentArgs)
+      .subscribe({
+        next: () => {
+          this.notifier.success($localize`${commentArgs.length} comments deleted.`)
+          this.reloadData()
+        },
 
-      err => this.notifier.error(err.message),
+        error: err => this.notifier.error(err.message),
 
-      () => this.selectedComments = []
-    )
+        complete: () => this.selectedComments = []
+      })
   }
 
   private deleteComment (comment: VideoCommentAdmin) {
     this.videoCommentService.deleteVideoComment(comment.video.id, comment.id)
-      .subscribe(
-        () => this.reloadData(),
+      .subscribe({
+        next: () => this.reloadData(),
 
-        err => this.notifier.error(err.message)
-      )
+        error: err => this.notifier.error(err.message)
+      })
   }
 
   private async deleteUserComments (comment: VideoCommentAdmin) {
@@ -169,12 +170,12 @@ export class VideoCommentListComponent extends RestTable implements OnInit {
     }
 
     this.bulkService.removeCommentsOf(options)
-      .subscribe(
-        () => {
+      .subscribe({
+        next: () => {
           this.notifier.success($localize`Comments of ${options.accountName} will be deleted in a few minutes`)
         },
 
-        err => this.notifier.error(err.message)
-      )
+        error: err => this.notifier.error(err.message)
+      })
   }
 }
diff --git a/client/src/app/+admin/plugins/plugin-list-installed/plugin-list-installed.component.ts b/client/src/app/+admin/plugins/plugin-list-installed/plugin-list-installed.component.ts
index 968abcbe5..85f9879bf 100644
--- a/client/src/app/+admin/plugins/plugin-list-installed/plugin-list-installed.component.ts
+++ b/client/src/app/+admin/plugins/plugin-list-installed/plugin-list-installed.component.ts
@@ -61,16 +61,16 @@ export class PluginListInstalledComponent implements OnInit {
 
   loadMorePlugins () {
     this.pluginApiService.getPlugins(this.pluginType, this.pagination, this.sort)
-        .subscribe(
-          res => {
+        .subscribe({
+          next: res => {
             this.plugins = this.plugins.concat(res.data)
             this.pagination.totalItems = res.total
 
             this.onDataSubject.next(res.data)
           },
 
-          err => this.notifier.error(err.message)
-        )
+          error: err => this.notifier.error(err.message)
+        })
   }
 
   onNearOfBottom () {
@@ -113,16 +113,16 @@ export class PluginListInstalledComponent implements OnInit {
     if (res === false) return
 
     this.pluginApiService.uninstall(plugin.name, plugin.type)
-      .subscribe(
-        () => {
+      .subscribe({
+        next: () => {
           this.notifier.success($localize`${plugin.name} uninstalled.`)
 
           this.plugins = this.plugins.filter(p => p.name !== plugin.name)
           this.pagination.totalItems--
         },
 
-        err => this.notifier.error(err.message)
-      )
+        error: err => this.notifier.error(err.message)
+      })
   }
 
   async update (plugin: PeerTubePlugin) {
@@ -143,8 +143,8 @@ export class PluginListInstalledComponent implements OnInit {
 
     this.pluginApiService.update(plugin.name, plugin.type)
         .pipe()
-        .subscribe(
-          res => {
+        .subscribe({
+          next: res => {
             this.updating[updatingKey] = false
 
             this.notifier.success($localize`${plugin.name} updated.`)
@@ -152,8 +152,8 @@ export class PluginListInstalledComponent implements OnInit {
             Object.assign(plugin, res)
           },
 
-          err => this.notifier.error(err.message)
-        )
+          error: err => this.notifier.error(err.message)
+        })
   }
 
   getShowRouterLink (plugin: PeerTubePlugin) {
diff --git a/client/src/app/+admin/plugins/plugin-search/plugin-search.component.ts b/client/src/app/+admin/plugins/plugin-search/plugin-search.component.ts
index 0a6e57904..803777eb3 100644
--- a/client/src/app/+admin/plugins/plugin-search/plugin-search.component.ts
+++ b/client/src/app/+admin/plugins/plugin-search/plugin-search.component.ts
@@ -84,8 +84,8 @@ export class PluginSearchComponent implements OnInit {
     this.isSearching = true
 
     this.pluginApiService.searchAvailablePlugins(this.pluginType, this.pagination, this.sort, this.search)
-        .subscribe(
-          res => {
+        .subscribe({
+          next: res => {
             this.isSearching = false
 
             this.plugins = this.plugins.concat(res.data)
@@ -94,13 +94,13 @@ export class PluginSearchComponent implements OnInit {
             this.onDataSubject.next(res.data)
           },
 
-          err => {
+          error: err => {
             console.error(err)
 
             const message = $localize`The plugin index is not available. Please retry later.`
             this.notifier.error(message)
           }
-        )
+        })
   }
 
   onNearOfBottom () {
@@ -139,8 +139,8 @@ export class PluginSearchComponent implements OnInit {
     this.installing[plugin.npmName] = true
 
     this.pluginApiService.install(plugin.npmName)
-        .subscribe(
-          () => {
+        .subscribe({
+          next: () => {
             this.installing[plugin.npmName] = false
             this.pluginInstalled = true
 
@@ -149,7 +149,7 @@ export class PluginSearchComponent implements OnInit {
             plugin.installed = true
           },
 
-          err => this.notifier.error(err.message)
-        )
+          error: err => this.notifier.error(err.message)
+        })
   }
 }
diff --git a/client/src/app/+admin/plugins/plugin-show-installed/plugin-show-installed.component.ts b/client/src/app/+admin/plugins/plugin-show-installed/plugin-show-installed.component.ts
index c3d14d2b3..10fb52911 100644
--- a/client/src/app/+admin/plugins/plugin-show-installed/plugin-show-installed.component.ts
+++ b/client/src/app/+admin/plugins/plugin-show-installed/plugin-show-installed.component.ts
@@ -50,13 +50,13 @@ export class PluginShowInstalledComponent extends FormReactive implements OnInit
     const settings = this.form.value
 
     this.pluginAPIService.updatePluginSettings(this.plugin.name, this.plugin.type, settings)
-        .subscribe(
-          () => {
+        .subscribe({
+          next: () => {
             this.notifier.success($localize`Settings updated.`)
           },
 
-          err => this.notifier.error(err.message)
-        )
+          error: err => this.notifier.error(err.message)
+        })
   }
 
   hasRegisteredSettings () {
@@ -83,8 +83,8 @@ export class PluginShowInstalledComponent extends FormReactive implements OnInit
           return this.pluginAPIService.getPluginRegisteredSettings(plugin.name, plugin.type)
             .pipe(map(data => ({ plugin, registeredSettings: data.registeredSettings })))
         }))
-        .subscribe(
-          async ({ plugin, registeredSettings }) => {
+        .subscribe({
+          next: async ({ plugin, registeredSettings }) => {
             this.plugin = plugin
 
             this.registeredSettings = await this.translateSettings(registeredSettings)
@@ -94,8 +94,8 @@ export class PluginShowInstalledComponent extends FormReactive implements OnInit
             this.buildSettingsForm()
           },
 
-          err => this.notifier.error(err.message)
-        )
+          error: err => this.notifier.error(err.message)
+        })
   }
 
   private buildSettingsForm () {
diff --git a/client/src/app/+admin/system/debug/debug.component.ts b/client/src/app/+admin/system/debug/debug.component.ts
index a88d837f3..1f4e71e8a 100644
--- a/client/src/app/+admin/system/debug/debug.component.ts
+++ b/client/src/app/+admin/system/debug/debug.component.ts
@@ -22,10 +22,10 @@ export class DebugComponent implements OnInit {
 
   load () {
     this.debugService.getDebug()
-        .subscribe(
-          debug => this.debug = debug,
+        .subscribe({
+          next: debug => this.debug = debug,
 
-          err => this.notifier.error(err.message)
-        )
+          error: err => this.notifier.error(err.message)
+        })
   }
 }
diff --git a/client/src/app/+admin/system/jobs/jobs.component.ts b/client/src/app/+admin/system/jobs/jobs.component.ts
index 4b02e1bc1..b12d7f80a 100644
--- a/client/src/app/+admin/system/jobs/jobs.component.ts
+++ b/client/src/app/+admin/system/jobs/jobs.component.ts
@@ -119,14 +119,14 @@ export class JobsComponent extends RestTable implements OnInit {
         pagination: this.pagination,
         sort: this.sort
       })
-      .subscribe(
-        resultList => {
+      .subscribe({
+        next: resultList => {
           this.jobs = resultList.data
           this.totalRecords = resultList.total
         },
 
-        err => this.notifier.error(err.message)
-      )
+        error: err => this.notifier.error(err.message)
+      })
   }
 
   private loadJobStateAndType () {
diff --git a/client/src/app/+admin/system/logs/logs.component.ts b/client/src/app/+admin/system/logs/logs.component.ts
index 48318f07b..865ab80a2 100644
--- a/client/src/app/+admin/system/logs/logs.component.ts
+++ b/client/src/app/+admin/system/logs/logs.component.ts
@@ -52,8 +52,8 @@ export class LogsComponent implements OnInit {
     this.loading = true
 
     this.logsService.getLogs({ isAuditLog: this.isAuditLog(), level: this.level, startDate: this.startDate })
-        .subscribe(
-          logs => {
+        .subscribe({
+          next: logs => {
             this.logs = logs
 
             setTimeout(() => {
@@ -61,10 +61,10 @@ export class LogsComponent implements OnInit {
             })
           },
 
-          err => this.notifier.error(err.message),
+          error: err => this.notifier.error(err.message),
 
-          () => this.loading = false
-        )
+          complete: () => this.loading = false
+        })
   }
 
   isAuditLog () {
diff --git a/client/src/app/+admin/users/user-edit/user-create.component.ts b/client/src/app/+admin/users/user-edit/user-create.component.ts
index c26ad1208..8403db91a 100644
--- a/client/src/app/+admin/users/user-edit/user-create.component.ts
+++ b/client/src/app/+admin/users/user-edit/user-create.component.ts
@@ -71,14 +71,15 @@ export class UserCreateComponent extends UserEdit implements OnInit {
     userCreate.videoQuota = parseInt(this.form.value['videoQuota'], 10)
     userCreate.videoQuotaDaily = parseInt(this.form.value['videoQuotaDaily'], 10)
 
-    this.userService.addUser(userCreate).subscribe(
-      () => {
-        this.notifier.success($localize`User ${userCreate.username} created.`)
-        this.router.navigate([ '/admin/users/list' ])
-      },
+    this.userService.addUser(userCreate)
+      .subscribe({
+        next: () => {
+          this.notifier.success($localize`User ${userCreate.username} created.`)
+          this.router.navigate([ '/admin/users/list' ])
+        },
 
-      err => this.error = err.message
-    )
+        error: err => this.error = err.message
+      })
   }
 
   isCreation () {
diff --git a/client/src/app/+admin/users/user-edit/user-password.component.ts b/client/src/app/+admin/users/user-edit/user-password.component.ts
index 05d52b17f..7c42b9241 100644
--- a/client/src/app/+admin/users/user-edit/user-password.component.ts
+++ b/client/src/app/+admin/users/user-edit/user-password.component.ts
@@ -35,13 +35,12 @@ export class UserPasswordComponent extends FormReactive implements OnInit {
 
     const userUpdate: UserUpdate = this.form.value
 
-    this.userService.updateUser(this.userId, userUpdate).subscribe(
-      () => {
-        this.notifier.success($localize`Password changed for user ${this.username}.`)
-      },
+    this.userService.updateUser(this.userId, userUpdate)
+      .subscribe({
+        next: () => this.notifier.success($localize`Password changed for user ${this.username}.`),
 
-      err => this.error = err.message
-    )
+        error: err => this.error = err.message
+      })
   }
 
   togglePasswordVisibility () {
diff --git a/client/src/app/+admin/users/user-edit/user-update.component.ts b/client/src/app/+admin/users/user-edit/user-update.component.ts
index 1527508f7..2128ba4fd 100644
--- a/client/src/app/+admin/users/user-edit/user-update.component.ts
+++ b/client/src/app/+admin/users/user-edit/user-update.component.ts
@@ -59,11 +59,12 @@ export class UserUpdateComponent extends UserEdit implements OnInit, OnDestroy {
 
     this.paramsSub = this.route.params.subscribe(routeParams => {
       const userId = routeParams['id']
-      this.userService.getUser(userId, true).subscribe(
-        user => this.onUserFetched(user),
+      this.userService.getUser(userId, true)
+        .subscribe({
+          next: user => this.onUserFetched(user),
 
-        err => this.error = err.message
-      )
+          error: err => this.error = err.message
+        })
     })
   }
 
@@ -83,14 +84,15 @@ export class UserUpdateComponent extends UserEdit implements OnInit, OnDestroy {
 
     if (userUpdate.pluginAuth === 'null') userUpdate.pluginAuth = null
 
-    this.userService.updateUser(this.user.id, userUpdate).subscribe(
-      () => {
-        this.notifier.success($localize`User ${this.user.username} updated.`)
-        this.router.navigate([ '/admin/users/list' ])
-      },
+    this.userService.updateUser(this.user.id, userUpdate)
+      .subscribe({
+        next: () => {
+          this.notifier.success($localize`User ${this.user.username} updated.`)
+          this.router.navigate([ '/admin/users/list' ])
+        },
 
-      err => this.error = err.message
-    )
+        error: err => this.error = err.message
+      })
   }
 
   isCreation () {
@@ -106,13 +108,14 @@ export class UserUpdateComponent extends UserEdit implements OnInit, OnDestroy {
   }
 
   resetPassword () {
-    this.userService.askResetPassword(this.user.email).subscribe(
-      () => {
-        this.notifier.success($localize`An email asking for password reset has been sent to ${this.user.username}.`)
-      },
+    this.userService.askResetPassword(this.user.email)
+      .subscribe({
+        next: () => {
+          this.notifier.success($localize`An email asking for password reset has been sent to ${this.user.username}.`)
+        },
 
-      err => this.error = err.message
-    )
+        error: err => this.error = err.message
+      })
   }
 
   private onUserFetched (userJson: UserType) {
diff --git a/client/src/app/+admin/users/user-list/user-list.component.ts b/client/src/app/+admin/users/user-list/user-list.component.ts
index e3ae68a93..d4406549a 100644
--- a/client/src/app/+admin/users/user-list/user-list.component.ts
+++ b/client/src/app/+admin/users/user-list/user-list.component.ts
@@ -173,14 +173,14 @@ export class UserListComponent extends RestTable implements OnInit {
     if (res === false) return
 
     this.userService.unbanUsers(users)
-        .subscribe(
-          () => {
+        .subscribe({
+          next: () => {
             this.notifier.success($localize`${users.length} users unbanned.`)
             this.reloadData()
           },
 
-          err => this.notifier.error(err.message)
-        )
+          error: err => this.notifier.error(err.message)
+        })
   }
 
   async removeUsers (users: User[]) {
@@ -195,25 +195,27 @@ export class UserListComponent extends RestTable implements OnInit {
     const res = await this.confirmService.confirm(message, $localize`Delete`)
     if (res === false) return
 
-    this.userService.removeUser(users).subscribe(
-      () => {
-        this.notifier.success($localize`${users.length} users deleted.`)
-        this.reloadData()
-      },
+    this.userService.removeUser(users)
+      .subscribe({
+        next: () => {
+          this.notifier.success($localize`${users.length} users deleted.`)
+          this.reloadData()
+        },
 
-      err => this.notifier.error(err.message)
-    )
+        error: err => this.notifier.error(err.message)
+      })
   }
 
   async setEmailsAsVerified (users: User[]) {
-    this.userService.updateUsers(users, { emailVerified: true }).subscribe(
-      () => {
-        this.notifier.success($localize`${users.length} users email set as verified.`)
-        this.reloadData()
-      },
+    this.userService.updateUsers(users, { emailVerified: true })
+      .subscribe({
+        next: () => {
+          this.notifier.success($localize`${users.length} users email set as verified.`)
+          this.reloadData()
+        },
 
-      err => this.notifier.error(err.message)
-    )
+        error: err => this.notifier.error(err.message)
+      })
   }
 
   isInSelectionMode () {
@@ -227,13 +229,13 @@ export class UserListComponent extends RestTable implements OnInit {
       pagination: this.pagination,
       sort: this.sort,
       search: this.search
-    }).subscribe(
-      resultList => {
+    }).subscribe({
+      next: resultList => {
         this.users = resultList.data
         this.totalRecords = resultList.total
       },
 
-      err => this.notifier.error(err.message)
-    )
+      error: err => this.notifier.error(err.message)
+    })
   }
 }
diff --git a/client/src/app/+login/login.component.ts b/client/src/app/+login/login.component.ts
index 9731383af..16876afd6 100644
--- a/client/src/app/+login/login.component.ts
+++ b/client/src/app/+login/login.component.ts
@@ -107,17 +107,17 @@ export class LoginComponent extends FormReactive implements OnInit, AfterViewIni
     const { username, password } = this.form.value
 
     this.authService.login(username, password)
-      .subscribe(
-        () => this.redirectService.redirectToPreviousRoute(),
+      .subscribe({
+        next: () => this.redirectService.redirectToPreviousRoute(),
 
-        err => this.handleError(err)
-      )
+        error: err => this.handleError(err)
+      })
   }
 
   askResetPassword () {
     this.userService.askResetPassword(this.forgotPasswordEmail)
-      .subscribe(
-        () => {
+      .subscribe({
+        next: () => {
           const message = $localize`An email with the reset password instructions will be sent to ${this.forgotPasswordEmail}.
 The link will expire within 1 hour.`
 
@@ -125,8 +125,8 @@ The link will expire within 1 hour.`
           this.hideForgotPasswordModal()
         },
 
-        err => this.notifier.error(err.message)
-      )
+        error: err => this.notifier.error(err.message)
+      })
   }
 
   openForgotPasswordModal () {
@@ -149,14 +149,14 @@ The link will expire within 1 hour.`
     this.isAuthenticatedWithExternalAuth = true
 
     this.authService.login(username, null, token)
-    .subscribe(
-      () => this.redirectService.redirectToPreviousRoute(),
+      .subscribe({
+        next: () => this.redirectService.redirectToPreviousRoute(),
 
-      err => {
-        this.handleError(err)
-        this.isAuthenticatedWithExternalAuth = false
-      }
-    )
+        error: err => {
+          this.handleError(err)
+          this.isAuthenticatedWithExternalAuth = false
+        }
+      })
   }
 
   private handleError (err: any) {
diff --git a/client/src/app/+my-account/my-account-applications/my-account-applications.component.ts b/client/src/app/+my-account/my-account-applications/my-account-applications.component.ts
index 5e9525fbb..6873c7d40 100644
--- a/client/src/app/+my-account/my-account-applications/my-account-applications.component.ts
+++ b/client/src/app/+my-account/my-account-applications/my-account-applications.component.ts
@@ -1,5 +1,5 @@
 import { Component, OnInit } from '@angular/core'
-import { AuthService, Notifier, ConfirmService, ScopedTokensService } from '@app/core'
+import { AuthService, ConfirmService, Notifier, ScopedTokensService } from '@app/core'
 import { VideoService } from '@app/shared/shared-main'
 import { FeedFormat } from '@shared/models'
 import { ScopedToken } from '@shared/models/users/user-scoped-token'
@@ -27,13 +27,11 @@ export class MyAccountApplicationsComponent implements OnInit {
   ngOnInit () {
     this.feedUrl = this.baseURL
     this.scopedTokensService.getScopedTokens()
-      .subscribe(
-        tokens => this.regenApplications(tokens),
+      .subscribe({
+        next: tokens => this.regenApplications(tokens),
 
-        err => {
-          this.notifier.error(err.message)
-        }
-      )
+        error: err => this.notifier.error(err.message)
+      })
   }
 
   async renewToken () {
@@ -43,17 +41,15 @@ export class MyAccountApplicationsComponent implements OnInit {
     )
     if (res === false) return
 
-    this.scopedTokensService.renewScopedTokens().subscribe(
-      tokens => {
-        this.regenApplications(tokens)
-        this.notifier.success($localize`Token renewed. Update your client configuration accordingly.`)
-      },
-
-      err => {
-        this.notifier.error(err.message)
-      }
-    )
+    this.scopedTokensService.renewScopedTokens()
+      .subscribe({
+        next: tokens => {
+          this.regenApplications(tokens)
+          this.notifier.success($localize`Token renewed. Update your client configuration accordingly.`)
+        },
 
+        error: err => this.notifier.error(err.message)
+      })
   }
 
   private regenApplications (tokens: ScopedToken) {
diff --git a/client/src/app/+my-account/my-account-settings/my-account-change-email/my-account-change-email.component.ts b/client/src/app/+my-account/my-account-settings/my-account-change-email/my-account-change-email.component.ts
index b2b7849c2..08bc5b425 100644
--- a/client/src/app/+my-account/my-account-settings/my-account-change-email/my-account-change-email.component.ts
+++ b/client/src/app/+my-account/my-account-settings/my-account-change-email/my-account-change-email.component.ts
@@ -45,8 +45,8 @@ export class MyAccountChangeEmailComponent extends FormReactive implements OnIni
       this.serverService.getConfig(),
       this.userService.changeEmail(password, email)
     ]).pipe(tap(() => this.authService.refreshUserInformation()))
-      .subscribe(
-        ([ config ]) => {
+      .subscribe({
+        next: ([ config ]) => {
           this.form.reset()
 
           if (config.signup.requiresEmailVerification) {
@@ -56,7 +56,7 @@ export class MyAccountChangeEmailComponent extends FormReactive implements OnIni
           }
         },
 
-        err => {
+        error: err => {
           if (err.status === 401) {
             this.error = $localize`You current password is invalid.`
             return
@@ -64,6 +64,6 @@ export class MyAccountChangeEmailComponent extends FormReactive implements OnIni
 
           this.error = err.message
         }
-      )
+      })
   }
 }
diff --git a/client/src/app/+my-account/my-account-settings/my-account-change-password/my-account-change-password.component.ts b/client/src/app/+my-account/my-account-settings/my-account-change-password/my-account-change-password.component.ts
index e034aedef..f91b2f37b 100644
--- a/client/src/app/+my-account/my-account-settings/my-account-change-password/my-account-change-password.component.ts
+++ b/client/src/app/+my-account/my-account-settings/my-account-change-password/my-account-change-password.component.ts
@@ -43,22 +43,23 @@ export class MyAccountChangePasswordComponent extends FormReactive implements On
     const currentPassword = this.form.value[ 'current-password' ]
     const newPassword = this.form.value[ 'new-password' ]
 
-    this.userService.changePassword(currentPassword, newPassword).subscribe(
-      () => {
-        this.notifier.success($localize`Password updated.`)
+    this.userService.changePassword(currentPassword, newPassword)
+      .subscribe({
+        next: () => {
+          this.notifier.success($localize`Password updated.`)
 
-        this.form.reset()
-        this.error = null
-      },
+          this.form.reset()
+          this.error = null
+        },
 
-      err => {
-        if (err.status === 401) {
-          this.error = $localize`You current password is invalid.`
-          return
+        error: err => {
+          if (err.status === 401) {
+            this.error = $localize`You current password is invalid.`
+            return
+          }
+
+          this.error = err.message
         }
-
-        this.error = err.message
-      }
-    )
+      })
   }
 }
diff --git a/client/src/app/+my-account/my-account-settings/my-account-danger-zone/my-account-danger-zone.component.ts b/client/src/app/+my-account/my-account-settings/my-account-danger-zone/my-account-danger-zone.component.ts
index 387e9e7cd..5005cb630 100644
--- a/client/src/app/+my-account/my-account-settings/my-account-danger-zone/my-account-danger-zone.component.ts
+++ b/client/src/app/+my-account/my-account-settings/my-account-danger-zone/my-account-danger-zone.component.ts
@@ -27,15 +27,16 @@ export class MyAccountDangerZoneComponent {
     )
     if (res === false) return
 
-    this.userService.deleteMe().subscribe(
-      () => {
-        this.notifier.success($localize`Your account is deleted.`)
+    this.userService.deleteMe()
+      .subscribe({
+        next: () => {
+          this.notifier.success($localize`Your account is deleted.`)
 
-        this.authService.logout()
-        this.redirectService.redirectToHomepage()
-      },
+          this.authService.logout()
+          this.redirectService.redirectToHomepage()
+        },
 
-      err => this.notifier.error(err.message)
-    )
+        error: err => this.notifier.error(err.message)
+      })
   }
 }
diff --git a/client/src/app/+my-account/my-account-settings/my-account-notification-preferences/my-account-notification-preferences.component.ts b/client/src/app/+my-account/my-account-settings/my-account-notification-preferences/my-account-notification-preferences.component.ts
index b94e6ad82..1eac06234 100644
--- a/client/src/app/+my-account/my-account-settings/my-account-notification-preferences/my-account-notification-preferences.component.ts
+++ b/client/src/app/+my-account/my-account-settings/my-account-notification-preferences/my-account-notification-preferences.component.ts
@@ -89,13 +89,13 @@ export class MyAccountNotificationPreferencesComponent implements OnInit {
 
   private savePreferencesImpl () {
     this.userNotificationService.updateNotificationSettings(this.user.notificationSettings)
-      .subscribe(
-        () => {
+      .subscribe({
+        next: () => {
           this.notifier.success($localize`Preferences saved`, undefined, 2000)
         },
 
-        err => this.notifier.error(err.message)
-      )
+        error: err => this.notifier.error(err.message)
+      })
   }
 
   private loadNotificationSettings () {
diff --git a/client/src/app/+my-account/my-account-settings/my-account-profile/my-account-profile.component.ts b/client/src/app/+my-account/my-account-settings/my-account-profile/my-account-profile.component.ts
index 80e4446c8..f395ad73f 100644
--- a/client/src/app/+my-account/my-account-settings/my-account-profile/my-account-profile.component.ts
+++ b/client/src/app/+my-account/my-account-settings/my-account-profile/my-account-profile.component.ts
@@ -50,15 +50,16 @@ export class MyAccountProfileComponent extends FormReactive implements OnInit {
 
     this.error = null
 
-    this.userService.updateMyProfile({ displayName, description }).subscribe(
-      () => {
+    this.userService.updateMyProfile({ displayName, description })
+    .subscribe({
+      next: () => {
         this.user.account.displayName = displayName
         this.user.account.description = description
 
         this.notifier.success($localize`Profile updated.`)
       },
 
-      err => this.error = err.message
-    )
+      error: err => this.error = err.message
+    })
   }
 }
diff --git a/client/src/app/+my-account/my-account-settings/my-account-settings.component.ts b/client/src/app/+my-account/my-account-settings/my-account-settings.component.ts
index a0f2f28f8..fc7635f38 100644
--- a/client/src/app/+my-account/my-account-settings/my-account-settings.component.ts
+++ b/client/src/app/+my-account/my-account-settings/my-account-settings.component.ts
@@ -39,31 +39,31 @@ export class MyAccountSettingsComponent implements OnInit, AfterViewChecked {
 
   onAvatarChange (formData: FormData) {
     this.userService.changeAvatar(formData)
-      .subscribe(
-        data => {
+      .subscribe({
+        next: data => {
           this.notifier.success($localize`Avatar changed.`)
 
           this.user.updateAccountAvatar(data.avatar)
         },
 
-        (err: HttpErrorResponse) => genericUploadErrorHandler({
+        error: (err: HttpErrorResponse) => genericUploadErrorHandler({
           err,
           name: $localize`avatar`,
           notifier: this.notifier
         })
-      )
+      })
   }
 
   onAvatarDelete () {
     this.userService.deleteAvatar()
-      .subscribe(
-        data => {
+      .subscribe({
+        next: data => {
           this.notifier.success($localize`Avatar deleted.`)
 
           this.user.updateAccountAvatar()
         },
 
-        (err: HttpErrorResponse) => this.notifier.error(err.message)
-      )
+        error: (err: HttpErrorResponse) => this.notifier.error(err.message)
+      })
   }
 }
diff --git a/client/src/app/+my-library/+my-video-channels/my-video-channel-create.component.ts b/client/src/app/+my-library/+my-video-channels/my-video-channel-create.component.ts
index 433475f66..d983aacd9 100644
--- a/client/src/app/+my-library/+my-video-channels/my-video-channel-create.component.ts
+++ b/client/src/app/+my-library/+my-video-channels/my-video-channel-create.component.ts
@@ -59,15 +59,15 @@ export class MyVideoChannelCreateComponent extends MyVideoChannelEdit implements
       .pipe(
         switchMap(() => this.uploadAvatar()),
         switchMap(() => this.uploadBanner())
-      ).subscribe(
-        () => {
+      ).subscribe({
+        next: () => {
           this.authService.refreshUserInformation()
 
           this.notifier.success($localize`Video channel ${videoChannelCreate.displayName} created.`)
           this.router.navigate(['/my-library', 'video-channels'])
         },
 
-        err => {
+        error: err => {
           if (err.status === HttpStatusCode.CONFLICT_409) {
             this.error = $localize`This name already exists on this instance.`
             return
@@ -75,7 +75,7 @@ export class MyVideoChannelCreateComponent extends MyVideoChannelEdit implements
 
           this.error = err.message
         }
-      )
+      })
   }
 
   onAvatarChange (formData: FormData) {
diff --git a/client/src/app/+my-library/+my-video-channels/my-video-channel-update.component.ts b/client/src/app/+my-library/+my-video-channels/my-video-channel-update.component.ts
index eb24a60c5..e8bfbf9ce 100644
--- a/client/src/app/+my-library/+my-video-channels/my-video-channel-update.component.ts
+++ b/client/src/app/+my-library/+my-video-channels/my-video-channel-update.component.ts
@@ -52,21 +52,22 @@ export class MyVideoChannelUpdateComponent extends MyVideoChannelEdit implements
     this.paramsSub = this.route.params.subscribe(routeParams => {
       const videoChannelId = routeParams['videoChannelId']
 
-      this.videoChannelService.getVideoChannel(videoChannelId).subscribe(
-        videoChannelToUpdate => {
-          this.videoChannel = videoChannelToUpdate
+      this.videoChannelService.getVideoChannel(videoChannelId)
+        .subscribe({
+          next: videoChannelToUpdate => {
+            this.videoChannel = videoChannelToUpdate
 
-          this.oldSupportField = videoChannelToUpdate.support
+            this.oldSupportField = videoChannelToUpdate.support
 
-          this.form.patchValue({
-            'display-name': videoChannelToUpdate.displayName,
-            description: videoChannelToUpdate.description,
-            support: videoChannelToUpdate.support
-          })
-        },
+            this.form.patchValue({
+              'display-name': videoChannelToUpdate.displayName,
+              description: videoChannelToUpdate.description,
+              support: videoChannelToUpdate.support
+            })
+          },
 
-        err => this.error = err.message
-      )
+          error: err => this.error = err.message
+        })
     })
   }
 
@@ -85,77 +86,78 @@ export class MyVideoChannelUpdateComponent extends MyVideoChannelEdit implements
       bulkVideosSupportUpdate: body.bulkVideosSupportUpdate || false
     }
 
-    this.videoChannelService.updateVideoChannel(this.videoChannel.name, videoChannelUpdate).subscribe(
-      () => {
-        this.authService.refreshUserInformation()
+    this.videoChannelService.updateVideoChannel(this.videoChannel.name, videoChannelUpdate)
+      .subscribe({
+        next: () => {
+          this.authService.refreshUserInformation()
 
-        this.notifier.success($localize`Video channel ${videoChannelUpdate.displayName} updated.`)
+          this.notifier.success($localize`Video channel ${videoChannelUpdate.displayName} updated.`)
 
-        this.router.navigate([ '/my-library', 'video-channels' ])
-      },
+          this.router.navigate([ '/my-library', 'video-channels' ])
+        },
 
-      err => this.error = err.message
-    )
+        error: err => this.error = err.message
+      })
   }
 
   onAvatarChange (formData: FormData) {
     this.videoChannelService.changeVideoChannelImage(this.videoChannel.name, formData, 'avatar')
-        .subscribe(
-          data => {
+        .subscribe({
+          next: data => {
             this.notifier.success($localize`Avatar changed.`)
 
             this.videoChannel.updateAvatar(data.avatar)
           },
 
-          (err: HttpErrorResponse) => genericUploadErrorHandler({
+          error: (err: HttpErrorResponse) => genericUploadErrorHandler({
             err,
             name: $localize`avatar`,
             notifier: this.notifier
           })
-        )
+        })
   }
 
   onAvatarDelete () {
     this.videoChannelService.deleteVideoChannelImage(this.videoChannel.name, 'avatar')
-                            .subscribe(
-                              data => {
+                            .subscribe({
+                              next: () => {
                                 this.notifier.success($localize`Avatar deleted.`)
 
                                 this.videoChannel.resetAvatar()
                               },
 
-                              err => this.notifier.error(err.message)
-                            )
+                              error: err => this.notifier.error(err.message)
+                            })
   }
 
   onBannerChange (formData: FormData) {
     this.videoChannelService.changeVideoChannelImage(this.videoChannel.name, formData, 'banner')
-        .subscribe(
-          data => {
+        .subscribe({
+          next: data => {
             this.notifier.success($localize`Banner changed.`)
 
             this.videoChannel.updateBanner(data.banner)
           },
 
-          (err: HttpErrorResponse) => genericUploadErrorHandler({
+          error: (err: HttpErrorResponse) => genericUploadErrorHandler({
             err,
             name: $localize`banner`,
             notifier: this.notifier
           })
-        )
+        })
   }
 
   onBannerDelete () {
     this.videoChannelService.deleteVideoChannelImage(this.videoChannel.name, 'banner')
-                            .subscribe(
-                              data => {
+                            .subscribe({
+                              next: () => {
                                 this.notifier.success($localize`Banner deleted.`)
 
                                 this.videoChannel.resetBanner()
                               },
 
-                              err => this.notifier.error(err.message)
-                            )
+                              error: err => this.notifier.error(err.message)
+                            })
   }
 
   get maxAvatarSize () {
diff --git a/client/src/app/+my-library/+my-video-channels/my-video-channels.component.ts b/client/src/app/+my-library/+my-video-channels/my-video-channels.component.ts
index b6a2f592d..8b665fd58 100644
--- a/client/src/app/+my-library/+my-video-channels/my-video-channels.component.ts
+++ b/client/src/app/+my-library/+my-video-channels/my-video-channels.component.ts
@@ -54,14 +54,14 @@ channel with the same name (${videoChannel.name})!`,
     if (res === false) return
 
     this.videoChannelService.removeVideoChannel(videoChannel)
-      .subscribe(
-        () => {
+      .subscribe({
+        next: () => {
           this.loadVideoChannels()
           this.notifier.success($localize`Video channel ${videoChannel.displayName} deleted.`)
         },
 
-        error => this.notifier.error(error.message)
-      )
+        error: err => this.notifier.error(err.message)
+      })
   }
 
   private loadVideoChannels () {
diff --git a/client/src/app/+my-library/my-history/my-history.component.ts b/client/src/app/+my-library/my-history/my-history.component.ts
index ad83db7ab..fe6e86346 100644
--- a/client/src/app/+my-library/my-history/my-history.component.ts
+++ b/client/src/app/+my-library/my-history/my-history.component.ts
@@ -107,8 +107,8 @@ export class MyHistoryComponent implements OnInit, DisableForReuseHook {
 
   onVideosHistoryChange () {
     this.userService.updateMyProfile({ videosHistoryEnabled: this.videosHistoryEnabled })
-      .subscribe(
-        () => {
+      .subscribe({
+        next: () => {
           const message = this.videosHistoryEnabled === true ?
             $localize`Videos history is enabled` :
             $localize`Videos history is disabled`
@@ -118,8 +118,8 @@ export class MyHistoryComponent implements OnInit, DisableForReuseHook {
           this.authService.refreshUserInformation()
         },
 
-        err => this.notifier.error(err.message)
-      )
+        error: err => this.notifier.error(err.message)
+      })
   }
 
   async deleteHistory () {
@@ -130,14 +130,14 @@ export class MyHistoryComponent implements OnInit, DisableForReuseHook {
     if (res !== true) return
 
     this.userHistoryService.deleteUserVideosHistory()
-        .subscribe(
-          () => {
+        .subscribe({
+          next: () => {
             this.notifier.success($localize`Videos history deleted`)
 
             this.reloadData()
           },
 
-          err => this.notifier.error(err.message)
-        )
+          error: err => this.notifier.error(err.message)
+        })
   }
 }
diff --git a/client/src/app/+my-library/my-ownership/my-accept-ownership/my-accept-ownership.component.ts b/client/src/app/+my-library/my-ownership/my-accept-ownership/my-accept-ownership.component.ts
index 7889d0985..b92377bca 100644
--- a/client/src/app/+my-library/my-ownership/my-accept-ownership/my-accept-ownership.component.ts
+++ b/client/src/app/+my-library/my-ownership/my-accept-ownership/my-accept-ownership.component.ts
@@ -64,14 +64,14 @@ export class MyAcceptOwnershipComponent extends FormReactive implements OnInit {
     const videoChangeOwnership = this.videoChangeOwnership
     this.videoOwnershipService
       .acceptOwnership(videoChangeOwnership.id, { channelId: channel })
-      .subscribe(
-        () => {
+      .subscribe({
+        next: () => {
           this.notifier.success($localize`Ownership accepted`)
           if (this.accepted) this.accepted.emit()
           this.videoChangeOwnership = undefined
         },
 
-        err => this.notifier.error(err.message)
-      )
+        error: err => this.notifier.error(err.message)
+      })
   }
 }
diff --git a/client/src/app/+my-library/my-ownership/my-ownership.component.ts b/client/src/app/+my-library/my-ownership/my-ownership.component.ts
index aaf028474..7ea940ceb 100644
--- a/client/src/app/+my-library/my-ownership/my-ownership.component.ts
+++ b/client/src/app/+my-library/my-ownership/my-ownership.component.ts
@@ -53,16 +53,16 @@ export class MyOwnershipComponent extends RestTable implements OnInit {
 
   refuse (videoChangeOwnership: VideoChangeOwnership) {
     this.videoOwnershipService.refuseOwnership(videoChangeOwnership.id)
-      .subscribe(
-        () => this.reloadData(),
-        err => this.notifier.error(err.message)
-      )
+      .subscribe({
+        next: () => this.reloadData(),
+        error: err => this.notifier.error(err.message)
+      })
   }
 
   protected reloadData () {
     return this.videoOwnershipService.getOwnershipChanges(this.pagination, this.sort)
-      .subscribe(
-        resultList => {
+      .subscribe({
+        next: resultList => {
           this.videoChangeOwnerships = resultList.data.map(change => ({
             ...change,
             initiatorAccount: new Account(change.initiatorAccount),
@@ -71,7 +71,7 @@ export class MyOwnershipComponent extends RestTable implements OnInit {
           this.totalRecords = resultList.total
         },
 
-        err => this.notifier.error(err.message)
-      )
+        error: err => this.notifier.error(err.message)
+      })
   }
 }
diff --git a/client/src/app/+my-library/my-subscriptions/my-subscriptions.component.ts b/client/src/app/+my-library/my-subscriptions/my-subscriptions.component.ts
index 1f4a931a0..f676aa014 100644
--- a/client/src/app/+my-library/my-subscriptions/my-subscriptions.component.ts
+++ b/client/src/app/+my-library/my-subscriptions/my-subscriptions.component.ts
@@ -41,8 +41,8 @@ export class MySubscriptionsComponent {
 
   private loadSubscriptions (more = true) {
     this.userSubscriptionService.listSubscriptions({ pagination: this.pagination, search: this.search })
-        .subscribe(
-          res => {
+        .subscribe({
+          next: res => {
             this.videoChannels = more
               ? this.videoChannels.concat(res.data)
               : res.data
@@ -51,7 +51,7 @@ export class MySubscriptionsComponent {
             this.onDataSubject.next(res.data)
           },
 
-          error => this.notifier.error(error.message)
-        )
+          error: err => this.notifier.error(err.message)
+        })
   }
 }
diff --git a/client/src/app/+my-library/my-video-imports/my-video-imports.component.ts b/client/src/app/+my-library/my-video-imports/my-video-imports.component.ts
index 68254526a..914785bf7 100644
--- a/client/src/app/+my-library/my-video-imports/my-video-imports.component.ts
+++ b/client/src/app/+my-library/my-video-imports/my-video-imports.component.ts
@@ -64,13 +64,13 @@ export class MyVideoImportsComponent extends RestTable implements OnInit {
 
   protected reloadData () {
     this.videoImportService.getMyVideoImports(this.pagination, this.sort)
-        .subscribe(
-          resultList => {
+        .subscribe({
+          next: resultList => {
             this.videoImports = resultList.data
             this.totalRecords = resultList.total
           },
 
-          err => this.notifier.error(err.message)
-        )
+          error: err => this.notifier.error(err.message)
+        })
   }
 }
diff --git a/client/src/app/+my-library/my-video-playlists/my-video-playlist-create.component.ts b/client/src/app/+my-library/my-video-playlists/my-video-playlist-create.component.ts
index 8606a875a..3e3c3c878 100644
--- a/client/src/app/+my-library/my-video-playlists/my-video-playlist-create.component.ts
+++ b/client/src/app/+my-library/my-video-playlists/my-video-playlist-create.component.ts
@@ -71,14 +71,15 @@ export class MyVideoPlaylistCreateComponent extends MyVideoPlaylistEdit implemen
       thumbnailfile: body.thumbnailfile || null
     }
 
-    this.videoPlaylistService.createVideoPlaylist(videoPlaylistCreate).subscribe(
-      () => {
-        this.notifier.success($localize`Playlist ${videoPlaylistCreate.displayName} created.`)
-        this.router.navigate([ '/my-library', 'video-playlists' ])
-      },
+    this.videoPlaylistService.createVideoPlaylist(videoPlaylistCreate)
+      .subscribe({
+        next: () => {
+          this.notifier.success($localize`Playlist ${videoPlaylistCreate.displayName} created.`)
+          this.router.navigate([ '/my-library', 'video-playlists' ])
+        },
 
-      err => this.error = err.message
-    )
+        error: err => this.error = err.message
+      })
   }
 
   isCreation () {
diff --git a/client/src/app/+my-library/my-video-playlists/my-video-playlist-elements.component.ts b/client/src/app/+my-library/my-video-playlists/my-video-playlist-elements.component.ts
index 86fe70710..6aff5dbd7 100644
--- a/client/src/app/+my-library/my-video-playlists/my-video-playlist-elements.component.ts
+++ b/client/src/app/+my-library/my-video-playlists/my-video-playlist-elements.component.ts
@@ -85,13 +85,13 @@ export class MyVideoPlaylistElementsComponent implements OnInit, OnDestroy {
     this.playlistElements.splice(newIndex, 0, element)
 
     this.videoPlaylistService.reorderPlaylist(this.playlist.id, oldPosition, insertAfter)
-      .subscribe(
-        () => {
+      .subscribe({
+        next: () => {
           this.reorderClientPositions()
         },
 
-        err => this.notifier.error(err.message)
-      )
+        error: err => this.notifier.error(err.message)
+      })
   }
 
   onElementRemoved (element: VideoPlaylistElement) {
@@ -129,14 +129,14 @@ export class MyVideoPlaylistElementsComponent implements OnInit, OnDestroy {
     if (res === false) return
 
     this.videoPlaylistService.removeVideoPlaylist(videoPlaylist)
-      .subscribe(
-        () => {
+      .subscribe({
+        next: () => {
           this.router.navigate([ '/my-library', 'video-playlists' ])
           this.notifier.success($localize`Playlist ${videoPlaylist.displayName} deleted.`)
         },
 
-        error => this.notifier.error(error.message)
-      )
+        error: err => this.notifier.error(err.message)
+      })
   }
 
   /**
diff --git a/client/src/app/+my-library/my-video-playlists/my-video-playlist-update.component.ts b/client/src/app/+my-library/my-video-playlists/my-video-playlist-update.component.ts
index c554d3772..a3f54279b 100644
--- a/client/src/app/+my-library/my-video-playlists/my-video-playlist-update.component.ts
+++ b/client/src/app/+my-library/my-video-playlists/my-video-playlist-update.component.ts
@@ -64,16 +64,16 @@ export class MyVideoPlaylistUpdateComponent extends MyVideoPlaylistEdit implemen
                              ])
                            })
                          )
-                         .subscribe(
-                           ([ videoPlaylistToUpdate, videoPlaylistPrivacies]) => {
+                         .subscribe({
+                           next: ([ videoPlaylistToUpdate, videoPlaylistPrivacies]) => {
                              this.videoPlaylistToUpdate = videoPlaylistToUpdate
                              this.videoPlaylistPrivacies = videoPlaylistPrivacies
 
                              this.hydrateFormFromPlaylist()
                            },
 
-                           err => this.error = err.message
-                         )
+                           error: err => this.error = err.message
+                         })
   }
 
   ngOnDestroy () {
@@ -92,14 +92,15 @@ export class MyVideoPlaylistUpdateComponent extends MyVideoPlaylistEdit implemen
       thumbnailfile: body.thumbnailfile || undefined
     }
 
-    this.videoPlaylistService.updateVideoPlaylist(this.videoPlaylistToUpdate, videoPlaylistUpdate).subscribe(
-      () => {
-        this.notifier.success($localize`Playlist ${videoPlaylistUpdate.displayName} updated.`)
-        this.router.navigate([ '/my-library', 'video-playlists' ])
-      },
+    this.videoPlaylistService.updateVideoPlaylist(this.videoPlaylistToUpdate, videoPlaylistUpdate)
+      .subscribe({
+        next: () => {
+          this.notifier.success($localize`Playlist ${videoPlaylistUpdate.displayName} updated.`)
+          this.router.navigate([ '/my-library', 'video-playlists' ])
+        },
 
-      err => this.error = err.message
-    )
+        error: err => this.error = err.message
+      })
   }
 
   isCreation () {
diff --git a/client/src/app/+my-library/my-video-playlists/my-video-playlists.component.ts b/client/src/app/+my-library/my-video-playlists/my-video-playlists.component.ts
index d90102693..f0e4c348b 100644
--- a/client/src/app/+my-library/my-video-playlists/my-video-playlists.component.ts
+++ b/client/src/app/+my-library/my-video-playlists/my-video-playlists.component.ts
@@ -37,16 +37,16 @@ export class MyVideoPlaylistsComponent {
     if (res === false) return
 
     this.videoPlaylistService.removeVideoPlaylist(videoPlaylist)
-      .subscribe(
-        () => {
+      .subscribe({
+        next: () => {
           this.videoPlaylists = this.videoPlaylists
                                     .filter(p => p.id !== videoPlaylist.id)
 
           this.notifier.success($localize`Playlist ${videoPlaylist.displayName}} deleted.`)
         },
 
-        error => this.notifier.error(error.message)
-      )
+        error: err => this.notifier.error(err.message)
+      })
   }
 
   isRegularPlaylist (playlist: VideoPlaylist) {
diff --git a/client/src/app/+my-library/my-videos/modals/video-change-ownership.component.ts b/client/src/app/+my-library/my-videos/modals/video-change-ownership.component.ts
index 84237dee1..8c1f94bf3 100644
--- a/client/src/app/+my-library/my-videos/modals/video-change-ownership.component.ts
+++ b/client/src/app/+my-library/my-videos/modals/video-change-ownership.component.ts
@@ -48,11 +48,11 @@ export class VideoChangeOwnershipComponent extends FormReactive implements OnIni
   search (event: { query: string }) {
     const query = event.query
     this.userService.autocomplete(query)
-      .subscribe(
-        usernames => this.usernamePropositions = usernames,
+      .subscribe({
+        next: usernames => this.usernamePropositions = usernames,
 
-        err => this.notifier.error(err.message)
-      )
+        error: err => this.notifier.error(err.message)
+      })
   }
 
   changeOwnership () {
@@ -60,10 +60,10 @@ export class VideoChangeOwnershipComponent extends FormReactive implements OnIni
 
     this.videoOwnershipService
       .changeOwnership(this.video.id, username)
-      .subscribe(
-        () => this.notifier.success($localize`Ownership change request sent.`),
+      .subscribe({
+        next: () => this.notifier.success($localize`Ownership change request sent.`),
 
-        err => this.notifier.error(err.message)
-      )
+        error: err => this.notifier.error(err.message)
+      })
   }
 }
diff --git a/client/src/app/+my-library/my-videos/my-videos.component.ts b/client/src/app/+my-library/my-videos/my-videos.component.ts
index 1e4a4406d..4f9b71cc6 100644
--- a/client/src/app/+my-library/my-videos/my-videos.component.ts
+++ b/client/src/app/+my-library/my-videos/my-videos.component.ts
@@ -126,14 +126,14 @@ export class MyVideosComponent implements OnInit, DisableForReuseHook {
 
     concat(...observables)
       .pipe(toArray())
-      .subscribe(
-        () => {
+      .subscribe({
+        next: () => {
           this.notifier.success($localize`${toDeleteVideosIds.length} videos deleted.`)
           this.selection = {}
         },
 
-        err => this.notifier.error(err.message)
-      )
+        error: err => this.notifier.error(err.message)
+      })
   }
 
   async deleteVideo (video: Video) {
@@ -144,14 +144,14 @@ export class MyVideosComponent implements OnInit, DisableForReuseHook {
     if (res === false) return
 
     this.videoService.removeVideo(video.id)
-        .subscribe(
-          () => {
+        .subscribe({
+          next: () => {
             this.notifier.success($localize`Video ${video.name} deleted.`)
             this.removeVideoFromArray(video.id)
           },
 
-          error => this.notifier.error(error.message)
-        )
+          error: err => this.notifier.error(err.message)
+        })
   }
 
   changeOwnership (video: Video) {
diff --git a/client/src/app/+reset-password/reset-password.component.ts b/client/src/app/+reset-password/reset-password.component.ts
index ce9144170..a1c04147b 100644
--- a/client/src/app/+reset-password/reset-password.component.ts
+++ b/client/src/app/+reset-password/reset-password.component.ts
@@ -42,14 +42,14 @@ export class ResetPasswordComponent extends FormReactive implements OnInit {
 
   resetPassword () {
     this.userService.resetPassword(this.userId, this.verificationString, this.form.value.password)
-      .subscribe(
-        () => {
+      .subscribe({
+        next: () => {
           this.notifier.success($localize`Your password has been successfully reset!`)
           this.router.navigate([ '/login' ])
         },
 
-        err => this.notifier.error(err.message)
-      )
+        error: err => this.notifier.error(err.message)
+      })
   }
 
   isConfirmedPasswordValid () {
diff --git a/client/src/app/+search/search.component.ts b/client/src/app/+search/search.component.ts
index 7425b7016..81d1006f8 100644
--- a/client/src/app/+search/search.component.ts
+++ b/client/src/app/+search/search.component.ts
@@ -73,36 +73,37 @@ export class SearchComponent implements OnInit, OnDestroy {
   ngOnInit () {
     this.serverConfig = this.serverService.getHTMLConfig()
 
-    this.subActivatedRoute = this.route.queryParams.subscribe(
-      async queryParams => {
-        const querySearch = queryParams['search']
-        const searchTarget = queryParams['searchTarget']
+    this.subActivatedRoute = this.route.queryParams
+      .subscribe({
+        next: async queryParams => {
+          const querySearch = queryParams['search']
+          const searchTarget = queryParams['searchTarget']
 
-        // Search updated, reset filters
-        if (this.currentSearch !== querySearch || searchTarget !== this.advancedSearch.searchTarget) {
-          this.resetPagination()
-          this.advancedSearch.reset()
+          // Search updated, reset filters
+          if (this.currentSearch !== querySearch || searchTarget !== this.advancedSearch.searchTarget) {
+            this.resetPagination()
+            this.advancedSearch.reset()
 
-          this.currentSearch = querySearch || undefined
-          this.updateTitle()
-        }
+            this.currentSearch = querySearch || undefined
+            this.updateTitle()
+          }
 
-        this.advancedSearch = new AdvancedSearch(queryParams)
-        if (!this.advancedSearch.searchTarget) {
-          this.advancedSearch.searchTarget = this.getDefaultSearchTarget()
-        }
+          this.advancedSearch = new AdvancedSearch(queryParams)
+          if (!this.advancedSearch.searchTarget) {
+            this.advancedSearch.searchTarget = this.getDefaultSearchTarget()
+          }
 
-        this.error = this.checkFieldsAndGetError()
+          this.error = this.checkFieldsAndGetError()
 
-        // Don't hide filters if we have some of them AND the user just came on the webpage, or we have an error
-        this.isSearchFilterCollapsed = !this.error && (this.isInitialLoad === false || !this.advancedSearch.containsValues())
-        this.isInitialLoad = false
+          // Don't hide filters if we have some of them AND the user just came on the webpage, or we have an error
+          this.isSearchFilterCollapsed = !this.error && (this.isInitialLoad === false || !this.advancedSearch.containsValues())
+          this.isInitialLoad = false
 
-        this.search()
-      },
+          this.search()
+        },
 
-      err => this.notifier.error(err.text)
-    )
+        error: err => this.notifier.error(err.text)
+      })
 
     this.userService.getAnonymousOrLoggedUser()
       .subscribe(user => this.userMiniature = user)
@@ -140,33 +141,35 @@ export class SearchComponent implements OnInit, OnDestroy {
       this.getVideoChannelObs(),
       this.getVideoPlaylistObs(),
       this.getVideosObs()
-    ]).subscribe(results => {
-      for (const result of results) {
-        this.results = this.results.concat(result.data)
+    ]).subscribe({
+      next: results => {
+        for (const result of results) {
+          this.results = this.results.concat(result.data)
+        }
+
+        this.pagination.totalItems = results.reduce((p, r) => p += r.total, 0)
+        this.lastSearchTarget = this.advancedSearch.searchTarget
+
+        this.hasMoreResults = this.results.length < this.pagination.totalItems
+      },
+
+      error: err => {
+        if (this.advancedSearch.searchTarget !== 'search-index') {
+          this.notifier.error(err.message)
+          return
+        }
+
+        this.notifier.error(
+          $localize`Search index is unavailable. Retrying with instance results instead.`,
+          $localize`Search error`
+        )
+        this.advancedSearch.searchTarget = 'local'
+        this.search()
+      },
+
+      complete: () => {
+        this.isSearching = false
       }
-
-      this.pagination.totalItems = results.reduce((p, r) => p += r.total, 0)
-      this.lastSearchTarget = this.advancedSearch.searchTarget
-
-      this.hasMoreResults = this.results.length < this.pagination.totalItems
-    },
-
-    err => {
-      if (this.advancedSearch.searchTarget !== 'search-index') {
-        this.notifier.error(err.message)
-        return
-      }
-
-      this.notifier.error(
-        $localize`Search index is unavailable. Retrying with instance results instead.`,
-        $localize`Search error`
-      )
-      this.advancedSearch.searchTarget = 'local'
-      this.search()
-    },
-
-    () => {
-      this.isSearching = false
     })
   }
 
diff --git a/client/src/app/+signup/+register/register.component.ts b/client/src/app/+signup/+register/register.component.ts
index 241ca04c6..056442107 100644
--- a/client/src/app/+signup/+register/register.component.ts
+++ b/client/src/app/+signup/+register/register.component.ts
@@ -122,8 +122,8 @@ export class RegisterComponent implements OnInit {
       'filter:api.signup.registration.create.params'
     )
 
-    this.userService.signup(body).subscribe(
-      () => {
+    this.userService.signup(body).subscribe({
+      next: () => {
         this.signupDone = true
 
         if (this.requiresEmailVerification) {
@@ -133,16 +133,16 @@ export class RegisterComponent implements OnInit {
 
         // Auto login
         this.authService.login(body.username, body.password)
-            .subscribe(
-              () => {
-                this.success = $localize`You are now logged in as ${body.username}!`
-              },
+          .subscribe({
+            next: () => {
+              this.success = $localize`You are now logged in as ${body.username}!`
+            },
 
-              err => this.error = err.message
-            )
+            error: err => this.error = err.message
+          })
       },
 
-      err => this.error = err.message
-    )
+      error: err => this.error = err.message
+    })
   }
 }
diff --git a/client/src/app/+signup/+verify-account/verify-account-ask-send-email/verify-account-ask-send-email.component.ts b/client/src/app/+signup/+verify-account/verify-account-ask-send-email/verify-account-ask-send-email.component.ts
index afb0e6d6c..83c24a251 100644
--- a/client/src/app/+signup/+verify-account/verify-account-ask-send-email/verify-account-ask-send-email.component.ts
+++ b/client/src/app/+signup/+verify-account/verify-account-ask-send-email/verify-account-ask-send-email.component.ts
@@ -34,15 +34,13 @@ export class VerifyAccountAskSendEmailComponent extends FormReactive implements
   askSendVerifyEmail () {
     const email = this.form.value['verify-email-email']
     this.userService.askSendVerifyEmail(email)
-      .subscribe(
-        () => {
+      .subscribe({
+        next: () => {
           this.notifier.success($localize`An email with verification link will be sent to ${email}.`)
           this.redirectService.redirectToHomepage()
         },
 
-        err => {
-          this.notifier.error(err.message)
-        }
-      )
+        error: err => this.notifier.error(err.message)
+      })
   }
 }
diff --git a/client/src/app/+signup/+verify-account/verify-account-email/verify-account-email.component.ts b/client/src/app/+signup/+verify-account/verify-account-email/verify-account-email.component.ts
index acc688ab3..457a0abe0 100644
--- a/client/src/app/+signup/+verify-account/verify-account-email/verify-account-email.component.ts
+++ b/client/src/app/+signup/+verify-account/verify-account-email/verify-account-email.component.ts
@@ -38,8 +38,8 @@ export class VerifyAccountEmailComponent implements OnInit {
 
   verifyEmail () {
     this.userService.verifyEmail(this.userId, this.verificationString, this.isPendingEmail)
-      .subscribe(
-        () => {
+      .subscribe({
+        next: () => {
           if (this.authService.isLoggedIn()) {
             this.authService.refreshUserInformation()
           }
@@ -47,11 +47,11 @@ export class VerifyAccountEmailComponent implements OnInit {
           this.success = true
         },
 
-        err => {
+        error: err => {
           this.failed = true
 
           this.notifier.error(err.message)
         }
-      )
+      })
   }
 }
diff --git a/client/src/app/+videos/+video-edit/video-add-components/video-go-live.component.ts b/client/src/app/+videos/+video-edit/video-add-components/video-go-live.component.ts
index db25dc6be..30c79594d 100644
--- a/client/src/app/+videos/+video-edit/video-add-components/video-go-live.component.ts
+++ b/client/src/app/+videos/+video-edit/video-add-components/video-go-live.component.ts
@@ -8,7 +8,7 @@ import { FormValidatorService } from '@app/shared/shared-forms'
 import { Video, VideoCaptionService, VideoEdit, VideoService } from '@app/shared/shared-main'
 import { LiveVideoService } from '@app/shared/shared-video-live'
 import { LoadingBarService } from '@ngx-loading-bar/core'
-import { LiveVideo, LiveVideoCreate, LiveVideoUpdate, PeerTubeProblemDocument, ServerErrorCode, VideoPrivacy } from '@shared/models'
+import { LiveVideo, LiveVideoCreate, LiveVideoUpdate, PeerTubeProblemDocument, ServerErrorCode } from '@shared/models'
 import { VideoSend } from './video-send'
 
 @Component({
@@ -74,33 +74,34 @@ export class VideoGoLiveComponent extends VideoSend implements OnInit, AfterView
     const toPatch = Object.assign({}, video, { privacy: this.firstStepPrivacyId })
     this.form.patchValue(toPatch)
 
-    this.liveVideoService.goLive(video).subscribe(
-      res => {
-        this.videoId = res.video.id
-        this.videoUUID = res.video.uuid
-        this.isInUpdateForm = true
+    this.liveVideoService.goLive(video)
+      .subscribe({
+        next: res => {
+          this.videoId = res.video.id
+          this.videoUUID = res.video.uuid
+          this.isInUpdateForm = true
 
-        this.firstStepDone.emit(name)
+          this.firstStepDone.emit(name)
 
-        this.fetchVideoLive()
-      },
+          this.fetchVideoLive()
+        },
 
-      err => {
-        this.firstStepError.emit()
+        error: err => {
+          this.firstStepError.emit()
 
-        let message = err.message
+          let message = err.message
 
-        const error = err.body as PeerTubeProblemDocument
+          const error = err.body as PeerTubeProblemDocument
 
-        if (error?.code === ServerErrorCode.MAX_INSTANCE_LIVES_LIMIT_REACHED) {
-          message = $localize`Cannot create live because this instance have too many created lives`
-        } else if (error?.code === ServerErrorCode.MAX_USER_LIVES_LIMIT_REACHED) {
-          message = $localize`Cannot create live because you created too many lives`
+          if (error?.code === ServerErrorCode.MAX_INSTANCE_LIVES_LIMIT_REACHED) {
+            message = $localize`Cannot create live because this instance have too many created lives`
+          } else if (error?.code === ServerErrorCode.MAX_USER_LIVES_LIMIT_REACHED) {
+            message = $localize`Cannot create live because you created too many lives`
+          }
+
+          this.notifier.error(message)
         }
-
-        this.notifier.error(message)
-      }
-    )
+      })
   }
 
   updateSecondStep () {
@@ -123,19 +124,19 @@ export class VideoGoLiveComponent extends VideoSend implements OnInit, AfterView
       this.updateVideoAndCaptions(video),
 
       this.liveVideoService.updateLive(this.videoId, liveVideoUpdate)
-    ]).subscribe(
-      () => {
+    ]).subscribe({
+      next: () => {
         this.notifier.success($localize`Live published.`)
 
         this.router.navigateByUrl(Video.buildWatchUrl(video))
       },
 
-      err => {
+      error: err => {
         this.error = err.message
         scrollToTop()
         console.error(err)
       }
-    )
+    })
   }
 
   getMaxLiveDuration () {
@@ -148,15 +149,15 @@ export class VideoGoLiveComponent extends VideoSend implements OnInit, AfterView
 
   private fetchVideoLive () {
     this.liveVideoService.getVideoLive(this.videoId)
-      .subscribe(
-        liveVideo => {
+      .subscribe({
+        next: liveVideo => {
           this.liveVideo = liveVideo
         },
 
-        err => {
+        error: err => {
           this.firstStepError.emit()
           this.notifier.error(err.message)
         }
-      )
+      })
   }
 }
diff --git a/client/src/app/+videos/+video-edit/video-add-components/video-import-torrent.component.ts b/client/src/app/+videos/+video-edit/video-add-components/video-import-torrent.component.ts
index 62aaeb019..fef1f5d65 100644
--- a/client/src/app/+videos/+video-edit/video-add-components/video-import-torrent.component.ts
+++ b/client/src/app/+videos/+video-edit/video-add-components/video-import-torrent.component.ts
@@ -88,40 +88,41 @@ export class VideoImportTorrentComponent extends VideoSend implements OnInit, Af
 
     this.loadingBar.useRef().start()
 
-    this.videoImportService.importVideoTorrent(torrentfile || this.magnetUri, videoUpdate).subscribe(
-      res => {
-        this.loadingBar.useRef().complete()
-        this.firstStepDone.emit(res.video.name)
-        this.isImportingVideo = false
-        this.hasImportedVideo = true
+    this.videoImportService.importVideoTorrent(torrentfile || this.magnetUri, videoUpdate)
+      .subscribe({
+        next: res => {
+          this.loadingBar.useRef().complete()
+          this.firstStepDone.emit(res.video.name)
+          this.isImportingVideo = false
+          this.hasImportedVideo = true
 
-        this.video = new VideoEdit(Object.assign(res.video, {
-          commentsEnabled: videoUpdate.commentsEnabled,
-          downloadEnabled: videoUpdate.downloadEnabled,
-          privacy: { id: this.firstStepPrivacyId },
-          support: null,
-          thumbnailUrl: null,
-          previewUrl: null
-        }))
+          this.video = new VideoEdit(Object.assign(res.video, {
+            commentsEnabled: videoUpdate.commentsEnabled,
+            downloadEnabled: videoUpdate.downloadEnabled,
+            privacy: { id: this.firstStepPrivacyId },
+            support: null,
+            thumbnailUrl: null,
+            previewUrl: null
+          }))
 
-        hydrateFormFromVideo(this.form, this.video, false)
-      },
+          hydrateFormFromVideo(this.form, this.video, false)
+        },
 
-      err => {
-        this.loadingBar.useRef().complete()
-        this.isImportingVideo = false
-        this.firstStepError.emit()
+        error: err => {
+          this.loadingBar.useRef().complete()
+          this.isImportingVideo = false
+          this.firstStepError.emit()
 
-        let message = err.message
+          let message = err.message
 
-        const error = err.body as PeerTubeProblemDocument
-        if (error?.code === ServerErrorCode.INCORRECT_FILES_IN_TORRENT) {
-          message = $localize`Torrents with only 1 file are supported.`
+          const error = err.body as PeerTubeProblemDocument
+          if (error?.code === ServerErrorCode.INCORRECT_FILES_IN_TORRENT) {
+            message = $localize`Torrents with only 1 file are supported.`
+          }
+
+          this.notifier.error(message)
         }
-
-        this.notifier.error(message)
-      }
-    )
+      })
   }
 
   updateSecondStep () {
@@ -135,19 +136,19 @@ export class VideoImportTorrentComponent extends VideoSend implements OnInit, Af
 
     // Update the video
     this.updateVideoAndCaptions(this.video)
-        .subscribe(
-          () => {
+        .subscribe({
+          next: () => {
             this.isUpdatingVideo = false
             this.notifier.success($localize`Video to import updated.`)
 
             this.router.navigate([ '/my-library', 'video-imports' ])
           },
 
-          err => {
+          error: err => {
             this.error = err.message
             scrollToTop()
             console.error(err)
           }
-        )
+        })
   }
 }
diff --git a/client/src/app/+videos/+video-edit/video-add-components/video-import-url.component.ts b/client/src/app/+videos/+video-edit/video-add-components/video-import-url.component.ts
index 3243b4d38..e1893b28f 100644
--- a/client/src/app/+videos/+video-edit/video-add-components/video-import-url.component.ts
+++ b/client/src/app/+videos/+video-edit/video-add-components/video-import-url.component.ts
@@ -6,7 +6,7 @@ import { getAbsoluteAPIUrl, scrollToTop } from '@app/helpers'
 import { FormValidatorService } from '@app/shared/shared-forms'
 import { VideoCaptionService, VideoEdit, VideoImportService, VideoService } from '@app/shared/shared-main'
 import { LoadingBarService } from '@ngx-loading-bar/core'
-import { VideoPrivacy, VideoUpdate } from '@shared/models'
+import { VideoUpdate } from '@shared/models'
 import { hydrateFormFromVideo } from '../shared/video-edit-utils'
 import { VideoSend } from './video-send'
 
@@ -86,8 +86,8 @@ export class VideoImportUrlComponent extends VideoSend implements OnInit, AfterV
                 )
           })
         )
-        .subscribe(
-          ({ video, videoCaptions }) => {
+        .subscribe({
+          next: ({ video, videoCaptions }) => {
             this.loadingBar.useRef().complete()
             this.firstStepDone.emit(video.name)
             this.isImportingVideo = false
@@ -117,13 +117,13 @@ export class VideoImportUrlComponent extends VideoSend implements OnInit, AfterV
             hydrateFormFromVideo(this.form, this.video, true)
           },
 
-          err => {
+          error: err => {
             this.loadingBar.useRef().complete()
             this.isImportingVideo = false
             this.firstStepError.emit()
             this.notifier.error(err.message)
           }
-        )
+        })
   }
 
   updateSecondStep () {
@@ -137,19 +137,19 @@ export class VideoImportUrlComponent extends VideoSend implements OnInit, AfterV
 
     // Update the video
     this.updateVideoAndCaptions(this.video)
-        .subscribe(
-          () => {
+        .subscribe({
+          next: () => {
             this.isUpdatingVideo = false
             this.notifier.success($localize`Video to import updated.`)
 
             this.router.navigate([ '/my-library', 'video-imports' ])
           },
 
-          err => {
+          error: err => {
             this.error = err.message
             scrollToTop()
             console.error(err)
           }
-        )
+        })
   }
 }
diff --git a/client/src/app/+videos/+video-edit/video-add-components/video-upload.component.ts b/client/src/app/+videos/+video-edit/video-add-components/video-upload.component.ts
index 189bc9669..b8cb4fa1e 100644
--- a/client/src/app/+videos/+video-edit/video-add-components/video-upload.component.ts
+++ b/client/src/app/+videos/+video-edit/video-add-components/video-upload.component.ts
@@ -240,8 +240,8 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy
     this.isUpdatingVideo = true
 
     this.updateVideoAndCaptions(video)
-        .subscribe(
-          () => {
+        .subscribe({
+          next: () => {
             this.isUpdatingVideo = false
             this.isUploadingVideo = false
 
@@ -249,12 +249,12 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy
             this.router.navigateByUrl(Video.buildWatchUrl(video))
           },
 
-          err => {
+          error: err => {
             this.error = err.message
             scrollToTop()
             console.error(err)
           }
-        )
+        })
   }
 
   private getInputVideoFile () {
diff --git a/client/src/app/+videos/+video-edit/video-update.component.ts b/client/src/app/+videos/+video-edit/video-update.component.ts
index 1534eee82..95336dc75 100644
--- a/client/src/app/+videos/+video-edit/video-update.component.ts
+++ b/client/src/app/+videos/+video-edit/video-update.component.ts
@@ -47,34 +47,35 @@ export class VideoUpdateComponent extends FormReactive implements OnInit {
 
     this.route.data
         .pipe(map(data => data.videoData))
-        .subscribe(({ video, videoChannels, videoCaptions, liveVideo }) => {
-          this.video = new VideoEdit(video)
-          this.videoDetails = video
+        .subscribe({
+          next: ({ video, videoChannels, videoCaptions, liveVideo }) => {
+            this.video = new VideoEdit(video)
+            this.videoDetails = video
 
-          this.userVideoChannels = videoChannels
-          this.videoCaptions = videoCaptions
-          this.liveVideo = liveVideo
+            this.userVideoChannels = videoChannels
+            this.videoCaptions = videoCaptions
+            this.liveVideo = liveVideo
 
-          this.schedulePublicationPossible = this.video.privacy === VideoPrivacy.PRIVATE
+            this.schedulePublicationPossible = this.video.privacy === VideoPrivacy.PRIVATE
 
-          // FIXME: Angular does not detect the change inside this subscription, so use the patched setTimeout
-          setTimeout(() => {
-            hydrateFormFromVideo(this.form, this.video, true)
+            // FIXME: Angular does not detect the change inside this subscription, so use the patched setTimeout
+            setTimeout(() => {
+              hydrateFormFromVideo(this.form, this.video, true)
 
-            if (this.liveVideo) {
-              this.form.patchValue({
-                saveReplay: this.liveVideo.saveReplay,
-                permanentLive: this.liveVideo.permanentLive
-              })
-            }
-          })
-        },
+              if (this.liveVideo) {
+                this.form.patchValue({
+                  saveReplay: this.liveVideo.saveReplay,
+                  permanentLive: this.liveVideo.permanentLive
+                })
+              }
+            })
+          },
 
-        err => {
-          console.error(err)
-          this.notifier.error(err.message)
-        }
-      )
+          error: err => {
+            console.error(err)
+            this.notifier.error(err.message)
+          }
+        })
   }
 
   @HostListener('window:beforeunload', [ '$event' ])
@@ -150,8 +151,8 @@ export class VideoUpdateComponent extends FormReactive implements OnInit {
             return this.liveVideoService.updateLive(this.video.id, liveVideoUpdate)
           })
         )
-        .subscribe(
-          () => {
+        .subscribe({
+          next: () => {
             this.updateDone = true
             this.isUpdatingVideo = false
             this.loadingBar.useRef().complete()
@@ -159,13 +160,13 @@ export class VideoUpdateComponent extends FormReactive implements OnInit {
             this.router.navigateByUrl(Video.buildWatchUrl(this.video))
           },
 
-          err => {
+          error: err => {
             this.loadingBar.useRef().complete()
             this.isUpdatingVideo = false
             this.notifier.error(err.message)
             console.error(err)
           }
-        )
+        })
   }
 
   hydratePluginFieldsFromVideo () {
diff --git a/client/src/app/+videos/+video-watch/shared/action-buttons/video-rate.component.ts b/client/src/app/+videos/+video-watch/shared/action-buttons/video-rate.component.ts
index ecb5a9281..48d48f33f 100644
--- a/client/src/app/+videos/+video-watch/shared/action-buttons/video-rate.component.ts
+++ b/client/src/app/+videos/+video-watch/shared/action-buttons/video-rate.component.ts
@@ -90,16 +90,16 @@ export class VideoRateComponent implements OnInit, OnChanges, OnDestroy {
     if (this.isUserLoggedIn === false) return
 
     this.videoService.getUserVideoRating(this.video.id)
-        .subscribe(
-          ratingObject => {
+        .subscribe({
+          next: ratingObject => {
             if (!ratingObject) return
 
             this.userRating = ratingObject.rating
             this.userRatingLoaded.emit(this.userRating)
           },
 
-          err => this.notifier.error(err.message)
-        )
+          error: err => this.notifier.error(err.message)
+        })
   }
 
   private setRating (nextRating: UserVideoRateType) {
@@ -110,16 +110,16 @@ export class VideoRateComponent implements OnInit, OnChanges, OnDestroy {
     }
 
     ratingMethods[nextRating].call(this.videoService, this.video.id)
-          .subscribe(
-            () => {
+          .subscribe({
+            next: () => {
               // Update the video like attribute
               this.updateVideoRating(this.userRating, nextRating)
               this.userRating = nextRating
               this.rateUpdated.emit(this.userRating)
             },
 
-            (err: { message: string }) => this.notifier.error(err.message)
-          )
+            error: err => this.notifier.error(err.message)
+          })
   }
 
   private updateVideoRating (oldRating: UserVideoRateType, newRating: UserVideoRateType) {
diff --git a/client/src/app/+videos/+video-watch/shared/comment/video-comment-add.component.ts b/client/src/app/+videos/+video-watch/shared/comment/video-comment-add.component.ts
index 78efe1684..ac65f7260 100644
--- a/client/src/app/+videos/+video-watch/shared/comment/video-comment-add.component.ts
+++ b/client/src/app/+videos/+video-watch/shared/comment/video-comment-add.component.ts
@@ -137,19 +137,19 @@ export class VideoCommentAddComponent extends FormReactive implements OnChanges,
       obs = this.addCommentThread(commentCreate)
     }
 
-    obs.subscribe(
-      comment => {
+    obs.subscribe({
+      next: comment => {
         this.addingComment = false
         this.commentCreated.emit(comment)
         this.form.reset()
       },
 
-      err => {
+      error: err => {
         this.addingComment = false
 
         this.notifier.error(err.text)
       }
-    )
+    })
   }
 
   isAddButtonDisplayed () {
diff --git a/client/src/app/+videos/+video-watch/shared/comment/video-comment.component.ts b/client/src/app/+videos/+video-watch/shared/comment/video-comment.component.ts
index 0e1c4c207..f858f4750 100644
--- a/client/src/app/+videos/+video-watch/shared/comment/video-comment.component.ts
+++ b/client/src/app/+videos/+video-watch/shared/comment/video-comment.component.ts
@@ -149,11 +149,11 @@ export class VideoCommentComponent implements OnInit, OnChanges {
     const user = this.authService.getUser()
     if (user.hasRight(UserRight.MANAGE_USERS)) {
       this.userService.getUserWithCache(account.userId)
-          .subscribe(
-            user => this.commentUser = user,
+          .subscribe({
+            next: user => this.commentUser = user,
 
-            err => this.notifier.error(err.message)
-          )
+            error: err => this.notifier.error(err.message)
+          })
     }
   }
 
diff --git a/client/src/app/+videos/+video-watch/shared/comment/video-comments.component.ts b/client/src/app/+videos/+video-watch/shared/comment/video-comments.component.ts
index 2c39e63fb..72866b874 100644
--- a/client/src/app/+videos/+video-watch/shared/comment/video-comments.component.ts
+++ b/client/src/app/+videos/+video-watch/shared/comment/video-comments.component.ts
@@ -90,22 +90,22 @@ export class VideoCommentsComponent implements OnInit, OnChanges, OnDestroy {
       'filter:api.video-watch.video-thread-replies.list.result'
     )
 
-    obs.subscribe(
-        res => {
-          this.threadComments[commentId] = res
-          this.threadLoading[commentId] = false
-          this.hooks.runAction('action:video-watch.video-thread-replies.loaded', 'video-watch', { data: res })
+    obs.subscribe({
+      next: res => {
+        this.threadComments[commentId] = res
+        this.threadLoading[commentId] = false
+        this.hooks.runAction('action:video-watch.video-thread-replies.loaded', 'video-watch', { data: res })
 
-          if (highlightThread) {
-            this.highlightedThread = new VideoComment(res.comment)
+        if (highlightThread) {
+          this.highlightedThread = new VideoComment(res.comment)
 
-            // Scroll to the highlighted thread
-            setTimeout(() => this.commentHighlightBlock.nativeElement.scrollIntoView(), 0)
-          }
-        },
+          // Scroll to the highlighted thread
+          setTimeout(() => this.commentHighlightBlock.nativeElement.scrollIntoView(), 0)
+        }
+      },
 
-        err => this.notifier.error(err.message)
-      )
+      error: err => this.notifier.error(err.message)
+    })
   }
 
   loadMoreThreads () {
@@ -123,8 +123,8 @@ export class VideoCommentsComponent implements OnInit, OnChanges, OnDestroy {
       'filter:api.video-watch.video-threads.list.result'
     )
 
-    obs.subscribe(
-      res => {
+    obs.subscribe({
+      next: res => {
         this.comments = this.comments.concat(res.data)
         this.componentPagination.totalItems = res.total
         this.totalNotDeletedComments = res.totalNotDeletedComments
@@ -133,8 +133,8 @@ export class VideoCommentsComponent implements OnInit, OnChanges, OnDestroy {
         this.hooks.runAction('action:video-watch.video-threads.loaded', 'video-watch', { data: this.componentPagination })
       },
 
-      err => this.notifier.error(err.message)
-    )
+      error: err => this.notifier.error(err.message)
+    })
   }
 
   onCommentThreadCreated (comment: VideoComment) {
@@ -181,8 +181,8 @@ export class VideoCommentsComponent implements OnInit, OnChanges, OnDestroy {
     if (res === false) return false
 
     this.videoCommentService.deleteVideoComment(commentToDelete.videoId, commentToDelete.id)
-      .subscribe(
-        () => {
+      .subscribe({
+        next: () => {
           if (this.highlightedThread?.id === commentToDelete.id) {
             commentToDelete = this.comments.find(c => c.id === commentToDelete.id)
 
@@ -193,8 +193,8 @@ export class VideoCommentsComponent implements OnInit, OnChanges, OnDestroy {
           this.softDeleteComment(commentToDelete)
         },
 
-        err => this.notifier.error(err.message)
-      )
+        error: err => this.notifier.error(err.message)
+      })
 
     return true
   }
diff --git a/client/src/app/+videos/+video-watch/shared/metadata/video-description.component.ts b/client/src/app/+videos/+video-watch/shared/metadata/video-description.component.ts
index 870c7ae3f..e002b3c22 100644
--- a/client/src/app/+videos/+video-watch/shared/metadata/video-description.component.ts
+++ b/client/src/app/+videos/+video-watch/shared/metadata/video-description.component.ts
@@ -50,8 +50,8 @@ export class VideoDescriptionComponent implements OnChanges {
     this.descriptionLoading = true
 
     this.videoService.loadCompleteDescription(this.video.descriptionPath)
-        .subscribe(
-          description => {
+        .subscribe({
+          next: description => {
             this.completeDescriptionShown = true
             this.descriptionLoading = false
 
@@ -61,11 +61,11 @@ export class VideoDescriptionComponent implements OnChanges {
             this.updateVideoDescription(this.completeVideoDescription)
           },
 
-          error => {
+          error: err => {
             this.descriptionLoading = false
-            this.notifier.error(error.message)
+            this.notifier.error(err.message)
           }
-        )
+        })
   }
 
   onTimestampClicked (timestamp: number) {
diff --git a/client/src/app/+videos/+video-watch/shared/playlist/video-watch-playlist.component.ts b/client/src/app/+videos/+video-watch/shared/playlist/video-watch-playlist.component.ts
index 8b3ed4964..f0f7966b1 100644
--- a/client/src/app/+videos/+video-watch/shared/playlist/video-watch-playlist.component.ts
+++ b/client/src/app/+videos/+video-watch/shared/playlist/video-watch-playlist.component.ts
@@ -1,4 +1,3 @@
-
 import { Component, EventEmitter, Input, Output } from '@angular/core'
 import { Router } from '@angular/router'
 import { AuthService, ComponentPagination, LocalStorageService, Notifier, SessionStorageService, UserService } from '@app/core'
@@ -196,12 +195,14 @@ export class VideoWatchPlaylistComponent {
         autoPlayNextVideoPlaylist: this.autoPlayNextVideoPlaylist
       }
 
-      this.userService.updateMyProfile(details).subscribe(
-        () => {
-          this.auth.refreshUserInformation()
-        },
-        err => this.notifier.error(err.message)
-      )
+      this.userService.updateMyProfile(details)
+        .subscribe({
+          next: () => {
+            this.auth.refreshUserInformation()
+          },
+
+          error: err => this.notifier.error(err.message)
+        })
     }
   }
 
diff --git a/client/src/app/+videos/+video-watch/shared/recommendations/recommended-videos.component.ts b/client/src/app/+videos/+video-watch/shared/recommendations/recommended-videos.component.ts
index 89b9c01b6..7f3703c08 100644
--- a/client/src/app/+videos/+video-watch/shared/recommendations/recommended-videos.component.ts
+++ b/client/src/app/+videos/+video-watch/shared/recommendations/recommended-videos.component.ts
@@ -84,12 +84,14 @@ export class RecommendedVideosComponent implements OnInit, OnChanges {
         autoPlayNextVideo: this.autoPlayNextVideo
       }
 
-      this.userService.updateMyProfile(details).subscribe(
-        () => {
-          this.authService.refreshUserInformation()
-        },
-        err => this.notifier.error(err.message)
-      )
+      this.userService.updateMyProfile(details)
+        .subscribe({
+          next: () => {
+            this.authService.refreshUserInformation()
+          },
+
+          error: err => this.notifier.error(err.message)
+        })
     }
   }
 }
diff --git a/client/src/app/+videos/+video-watch/video-watch.component.ts b/client/src/app/+videos/+video-watch/video-watch.component.ts
index ccb9c5e71..85100b653 100644
--- a/client/src/app/+videos/+video-watch/video-watch.component.ts
+++ b/client/src/app/+videos/+video-watch/video-watch.component.ts
@@ -238,8 +238,8 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
     )
 
     forkJoin([ videoObs, this.videoCaptionService.listCaptions(videoId)])
-      .subscribe(
-        ([ video, captionsResult ]) => {
+      .subscribe({
+        next: ([ video, captionsResult ]) => {
           const queryParams = this.route.snapshot.queryParams
 
           const urlOptions = {
@@ -260,23 +260,23 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
               .catch(err => this.handleGlobalError(err))
         },
 
-        err => this.handleRequestError(err)
-      )
+        error: err => this.handleRequestError(err)
+      })
   }
 
   private loadPlaylist (playlistId: string) {
     if (this.isSameElement(this.playlist, playlistId)) return
 
     this.playlistService.getVideoPlaylist(playlistId)
-      .subscribe(
-        playlist => {
+      .subscribe({
+        next: playlist => {
           this.playlist = playlist
 
           this.videoWatchPlaylist.loadPlaylistElements(playlist, !this.playlistPosition, this.playlistPosition)
         },
 
-        err => this.handleRequestError(err)
-      )
+        error: err => this.handleRequestError(err)
+      })
   }
 
   private isSameElement (element: VideoDetails | VideoPlaylist, newId: string) {
diff --git a/client/src/app/+videos/video-list/overview/video-overview.component.ts b/client/src/app/+videos/video-list/overview/video-overview.component.ts
index 14532ca1e..b32e8f381 100644
--- a/client/src/app/+videos/video-list/overview/video-overview.component.ts
+++ b/client/src/app/+videos/video-list/overview/video-overview.component.ts
@@ -68,8 +68,8 @@ export class VideoOverviewComponent implements OnInit {
     this.isLoading = true
 
     this.overviewService.getVideosOverview(this.currentPage)
-        .subscribe(
-          overview => {
+        .subscribe({
+          next: overview => {
             this.isLoading = false
 
             if (overview.tags.length === 0 && overview.channels.length === 0 && overview.categories.length === 0) {
@@ -85,10 +85,10 @@ export class VideoOverviewComponent implements OnInit {
             this.overviews.push(overview)
           },
 
-          err => {
+          error: err => {
             this.notifier.error(err.message)
             this.isLoading = false
           }
-        )
+        })
   }
 }
diff --git a/client/src/app/+videos/video-list/video-user-subscriptions.component.ts b/client/src/app/+videos/video-list/video-user-subscriptions.component.ts
index 6aabb93a5..a1498e797 100644
--- a/client/src/app/+videos/video-list/video-user-subscriptions.component.ts
+++ b/client/src/app/+videos/video-list/video-user-subscriptions.component.ts
@@ -56,8 +56,8 @@ export class VideoUserSubscriptionsComponent extends AbstractVideoList implement
 
     this.authService.userInformationLoaded
       .pipe(switchMap(() => this.scopedTokensService.getScopedTokens()))
-      .subscribe(
-        tokens => {
+      .subscribe({
+        next: tokens => {
           const feeds = this.videoService.getVideoSubscriptionFeedUrls(user.account.id, tokens.feedToken)
           feedUrl = feedUrl + feeds.find(f => f.format === FeedFormat.RSS).url
 
@@ -74,10 +74,10 @@ export class VideoUserSubscriptionsComponent extends AbstractVideoList implement
           })
         },
 
-        err => {
+        error: err => {
           this.notifier.error(err.message)
         }
-      )
+      })
   }
 
   ngOnDestroy () {
diff --git a/client/src/app/core/auth/auth.service.ts b/client/src/app/core/auth/auth.service.ts
index ef5a3808e..5da66c981 100644
--- a/client/src/app/core/auth/auth.service.ts
+++ b/client/src/app/core/auth/auth.service.ts
@@ -80,8 +80,8 @@ export class AuthService {
     // Fetch the client_id/client_secret
     this.http.get<OAuthClientLocal>(AuthService.BASE_CLIENT_URL)
         .pipe(catchError(res => this.restExtractor.handleError(res)))
-        .subscribe(
-          res => {
+        .subscribe({
+          next: res => {
             this.clientId = res.client_id
             this.clientSecret = res.client_secret
 
@@ -91,18 +91,18 @@ export class AuthService {
             console.log('Client credentials loaded.')
           },
 
-          error => {
-            let errorMessage = error.message
+          error: err => {
+            let errorMessage = err.message
 
-            if (error.status === HttpStatusCode.FORBIDDEN_403) {
-              errorMessage = $localize`Cannot retrieve OAuth Client credentials: ${error.text}.
+            if (err.status === HttpStatusCode.FORBIDDEN_403) {
+              errorMessage = $localize`Cannot retrieve OAuth Client credentials: ${err.text}.
 Ensure you have correctly configured PeerTube (config/ directory), in particular the "webserver" section.`
             }
 
             // We put a bigger timeout: this is an important message
             this.notifier.error(errorMessage, $localize`Error`, 7000)
           }
-        )
+        })
   }
 
   getRefreshToken () {
@@ -168,15 +168,15 @@ Ensure you have correctly configured PeerTube (config/ directory), in particular
     const headers = new HttpHeaders().set('Authorization', authHeaderValue)
 
     this.http.post<{ redirectUrl?: string }>(AuthService.BASE_REVOKE_TOKEN_URL, {}, { headers })
-    .subscribe(
-      res => {
-        if (res.redirectUrl) {
-          window.location.href = res.redirectUrl
-        }
-      },
+      .subscribe({
+        next: res => {
+          if (res.redirectUrl) {
+            window.location.href = res.redirectUrl
+          }
+        },
 
-      err => console.error(err)
-    )
+        error: err => console.error(err)
+      })
 
     this.user = null
 
@@ -215,9 +215,9 @@ Ensure you have correctly configured PeerTube (config/ directory), in particular
                                              this.logout()
                                              this.router.navigate([ '/login' ])
 
-                                             return observableThrowError({
+                                             return observableThrowError(() => ({
                                                error: $localize`You need to reconnect.`
-                                             })
+                                             }))
                                            }),
                                            share()
                                          )
@@ -234,14 +234,14 @@ Ensure you have correctly configured PeerTube (config/ directory), in particular
     }
 
     this.mergeUserInformation(obj)
-        .subscribe(
-          res => {
+        .subscribe({
+          next: res => {
             this.user.patch(res)
             this.user.save()
 
             this.userInformationLoaded.next(true)
           }
-        )
+        })
   }
 
   private mergeUserInformation (obj: UserLoginWithUsername): Observable<UserLoginWithUserInformation> {
diff --git a/client/src/app/core/confirm/confirm.service.ts b/client/src/app/core/confirm/confirm.service.ts
index 6e042c16b..338b8762c 100644
--- a/client/src/app/core/confirm/confirm.service.ts
+++ b/client/src/app/core/confirm/confirm.service.ts
@@ -1,6 +1,5 @@
-import { first } from 'rxjs/operators'
+import { firstValueFrom, Subject } from 'rxjs'
 import { Injectable } from '@angular/core'
-import { Subject } from 'rxjs'
 
 type ConfirmOptions = {
   title: string
@@ -18,16 +17,12 @@ export class ConfirmService {
   confirm (message: string, title = '', confirmButtonText?: string) {
     this.showConfirm.next({ title, message, confirmButtonText })
 
-    return this.confirmResponse.asObservable()
-               .pipe(first())
-               .toPromise()
+    return firstValueFrom(this.confirmResponse.asObservable())
   }
 
   confirmWithInput (message: string, inputLabel: string, expectedInputValue: string, title = '', confirmButtonText?: string) {
     this.showConfirm.next({ title, message, inputLabel, expectedInputValue, confirmButtonText })
 
-    return this.confirmResponse.asObservable()
-               .pipe(first())
-               .toPromise()
+    return firstValueFrom(this.confirmResponse.asObservable())
   }
 }
diff --git a/client/src/app/core/plugins/plugin.service.ts b/client/src/app/core/plugins/plugin.service.ts
index 16108e17a..774c03964 100644
--- a/client/src/app/core/plugins/plugin.service.ts
+++ b/client/src/app/core/plugins/plugin.service.ts
@@ -1,4 +1,4 @@
-import { Observable, of } from 'rxjs'
+import { firstValueFrom, Observable, of } from 'rxjs'
 import { catchError, map, shareReplay } from 'rxjs/operators'
 import { HttpClient } from '@angular/common/http'
 import { Inject, Injectable, LOCALE_ID, NgZone } from '@angular/core'
@@ -164,18 +164,20 @@ export class PluginService implements ClientHook {
       getSettings: () => {
         const path = PluginService.BASE_PLUGIN_API_URL + '/' + npmName + '/public-settings'
 
-        return this.authHttp.get<PublicServerSetting>(path)
+        const obs = this.authHttp.get<PublicServerSetting>(path)
                    .pipe(
                      map(p => p.publicSettings),
                      catchError(res => this.restExtractor.handleError(res))
                    )
-                   .toPromise()
+
+        return firstValueFrom(obs)
       },
 
       getServerConfig: () => {
-        return this.server.getConfig()
+        const obs = this.server.getConfig()
           .pipe(catchError(res => this.restExtractor.handleError(res)))
-          .toPromise()
+
+        return firstValueFrom(obs)
       },
 
       isLoggedIn: () => {
@@ -216,10 +218,11 @@ export class PluginService implements ClientHook {
       },
 
       translate: (value: string) => {
-        return this.translationsObservable
+        const obs = this.translationsObservable
             .pipe(map(allTranslations => allTranslations[npmName]))
             .pipe(map(translations => peertubeTranslate(value, translations)))
-            .toPromise()
+
+        return firstValueFrom(obs)
       }
     }
   }
diff --git a/client/src/app/core/rest/rest-extractor.service.ts b/client/src/app/core/rest/rest-extractor.service.ts
index 2a926e68f..29a56ba39 100644
--- a/client/src/app/core/rest/rest-extractor.service.ts
+++ b/client/src/app/core/rest/rest-extractor.service.ts
@@ -89,7 +89,7 @@ export class RestExtractor {
       errorObj.body = err.error
     }
 
-    return observableThrowError(errorObj)
+    return observableThrowError(() => errorObj)
   }
 
   redirectTo404IfNotFound (obj: { status: number }, type: 'video' | 'other', status = [ HttpStatusCode.NOT_FOUND_404 ]) {
@@ -98,6 +98,6 @@ export class RestExtractor {
       this.router.navigate([ '/404' ], { state: { type, obj }, skipLocationChange: true })
     }
 
-    return observableThrowError(obj)
+    return observableThrowError(() => obj)
   }
 }
diff --git a/client/src/app/menu/notification.component.ts b/client/src/app/menu/notification.component.ts
index b7d9e9abb..ac9c79991 100644
--- a/client/src/app/menu/notification.component.ts
+++ b/client/src/app/menu/notification.component.ts
@@ -36,14 +36,14 @@ export class NotificationComponent implements OnInit, OnDestroy {
 
   ngOnInit () {
     this.userNotificationService.countUnreadNotifications()
-        .subscribe(
-          result => {
+        .subscribe({
+          next: result => {
             this.unreadNotifications = Math.min(result, 99) // Limit number to 99
             this.subscribeToNotifications()
           },
 
-          err => this.notifier.error(err.message)
-        )
+          error: err => this.notifier.error(err.message)
+        })
 
     this.routeSub = this.router.events
                         .pipe(filter(event => event instanceof NavigationEnd))
diff --git a/client/src/app/modal/instance-config-warning-modal.component.ts b/client/src/app/modal/instance-config-warning-modal.component.ts
index 8bf7672be..e40958976 100644
--- a/client/src/app/modal/instance-config-warning-modal.component.ts
+++ b/client/src/app/modal/instance-config-warning-modal.component.ts
@@ -50,10 +50,10 @@ export class InstanceConfigWarningModalComponent {
     peertubeLocalStorage.setItem(this.LOCAL_STORAGE_KEYS.NO_INSTANCE_CONFIG_WARNING_MODAL, 'true')
 
     this.userService.updateMyProfile({ noInstanceConfigWarningModal: true })
-        .subscribe(
-          () => console.log('We will not open the instance config warning modal again.'),
+        .subscribe({
+          next: () => console.log('We will not open the instance config warning modal again.'),
 
-          err => this.notifier.error(err.message)
-        )
+          error: err => this.notifier.error(err.message)
+        })
   }
 }
diff --git a/client/src/app/modal/quick-settings-modal.component.ts b/client/src/app/modal/quick-settings-modal.component.ts
index 99859a1a5..7a53179cb 100644
--- a/client/src/app/modal/quick-settings-modal.component.ts
+++ b/client/src/app/modal/quick-settings-modal.component.ts
@@ -31,20 +31,20 @@ export class QuickSettingsModalComponent extends FormReactive implements OnInit
   ngOnInit () {
     this.user = this.userService.getAnonymousUser()
     this.localStorageService.watch()
-      .subscribe(
-        () => this.user = this.userService.getAnonymousUser()
-      )
+      .subscribe({
+        next: () => this.user = this.userService.getAnonymousUser()
+      })
 
     this.userInformationLoaded.next(true)
 
     this.authService.loginChangedSource
       .pipe(filter(status => status !== AuthStatus.LoggedIn))
-      .subscribe(
-        () => {
+      .subscribe({
+        next: () => {
           this.user = this.userService.getAnonymousUser()
           this.userInformationLoaded.next(true)
         }
-      )
+      })
   }
 
   isUserLoggedIn () {
diff --git a/client/src/app/modal/welcome-modal.component.ts b/client/src/app/modal/welcome-modal.component.ts
index 72ae20b43..06fe4cba5 100644
--- a/client/src/app/modal/welcome-modal.component.ts
+++ b/client/src/app/modal/welcome-modal.component.ts
@@ -37,10 +37,10 @@ export class WelcomeModalComponent {
     peertubeLocalStorage.setItem(this.LOCAL_STORAGE_KEYS.NO_WELCOME_MODAL, 'true')
 
     this.userService.updateMyProfile({ noWelcomeModal: true })
-      .subscribe(
-        () => console.log('We will not open the welcome modal again.'),
+      .subscribe({
+        next: () => console.log('We will not open the welcome modal again.'),
 
-        err => this.notifier.error(err.message)
-      )
+        error: err => this.notifier.error(err.message)
+      })
   }
 }
diff --git a/client/src/app/shared/shared-abuse-list/abuse-list-table.component.ts b/client/src/app/shared/shared-abuse-list/abuse-list-table.component.ts
index a7932ebab..e3bf9e1fb 100644
--- a/client/src/app/shared/shared-abuse-list/abuse-list-table.component.ts
+++ b/client/src/app/shared/shared-abuse-list/abuse-list-table.component.ts
@@ -145,23 +145,24 @@ export class AbuseListTableComponent extends RestTable implements OnInit {
     const res = await this.confirmService.confirm($localize`Do you really want to delete this abuse report?`, $localize`Delete`)
     if (res === false) return
 
-    this.abuseService.removeAbuse(abuse).subscribe(
-      () => {
-        this.notifier.success($localize`Abuse deleted.`)
-        this.reloadData()
-      },
+    this.abuseService.removeAbuse(abuse)
+      .subscribe({
+        next: () => {
+          this.notifier.success($localize`Abuse deleted.`)
+          this.reloadData()
+        },
 
-      err => this.notifier.error(err.message)
-    )
+        error: err => this.notifier.error(err.message)
+      })
   }
 
   updateAbuseState (abuse: AdminAbuse, state: AbuseState) {
     this.abuseService.updateAbuse(abuse, { state })
-      .subscribe(
-        () => this.reloadData(),
+      .subscribe({
+        next: () => this.reloadData(),
 
-        err => this.notifier.error(err.message)
-      )
+        error: err => this.notifier.error(err.message)
+      })
   }
 
   onCountMessagesUpdated (event: { abuseId: number, countMessages: number }) {
@@ -198,55 +199,55 @@ export class AbuseListTableComponent extends RestTable implements OnInit {
       ? this.abuseService.getAdminAbuses(options)
       : this.abuseService.getUserAbuses(options)
 
-    return observable.subscribe(
-        async resultList => {
-          this.totalRecords = resultList.total
+    return observable.subscribe({
+      next: async resultList => {
+        this.totalRecords = resultList.total
 
-          this.abuses = []
+        this.abuses = []
 
-          for (const a of resultList.data) {
-            const abuse = a as ProcessedAbuse
+        for (const a of resultList.data) {
+          const abuse = a as ProcessedAbuse
 
-            abuse.reasonHtml = await this.toHtml(abuse.reason)
+          abuse.reasonHtml = await this.toHtml(abuse.reason)
 
-            if (abuse.moderationComment) {
-              abuse.moderationCommentHtml = await this.toHtml(abuse.moderationComment)
-            }
-
-            if (abuse.video) {
-              abuse.embedHtml = this.sanitizer.bypassSecurityTrustHtml(this.getVideoEmbed(abuse))
-
-              if (abuse.video.channel?.ownerAccount) {
-                abuse.video.channel.ownerAccount = new Account(abuse.video.channel.ownerAccount)
-              }
-            }
-
-            if (abuse.comment) {
-              if (abuse.comment.deleted) {
-                abuse.truncatedCommentHtml = abuse.commentHtml = $localize`Deleted comment`
-              } else {
-                const truncated = truncate(abuse.comment.text, { length: 100 })
-                abuse.truncatedCommentHtml = await this.markdownRenderer.textMarkdownToHTML(truncated, true)
-                abuse.commentHtml = await this.markdownRenderer.textMarkdownToHTML(abuse.comment.text, true)
-              }
-            }
-
-            if (abuse.reporterAccount) {
-              abuse.reporterAccount = new Account(abuse.reporterAccount)
-            }
-
-            if (abuse.flaggedAccount) {
-              abuse.flaggedAccount = new Account(abuse.flaggedAccount)
-            }
-
-            if (abuse.updatedAt === abuse.createdAt) delete abuse.updatedAt
-
-            this.abuses.push(abuse)
+          if (abuse.moderationComment) {
+            abuse.moderationCommentHtml = await this.toHtml(abuse.moderationComment)
           }
-        },
 
-        err => this.notifier.error(err.message)
-      )
+          if (abuse.video) {
+            abuse.embedHtml = this.sanitizer.bypassSecurityTrustHtml(this.getVideoEmbed(abuse))
+
+            if (abuse.video.channel?.ownerAccount) {
+              abuse.video.channel.ownerAccount = new Account(abuse.video.channel.ownerAccount)
+            }
+          }
+
+          if (abuse.comment) {
+            if (abuse.comment.deleted) {
+              abuse.truncatedCommentHtml = abuse.commentHtml = $localize`Deleted comment`
+            } else {
+              const truncated = truncate(abuse.comment.text, { length: 100 })
+              abuse.truncatedCommentHtml = await this.markdownRenderer.textMarkdownToHTML(truncated, true)
+              abuse.commentHtml = await this.markdownRenderer.textMarkdownToHTML(abuse.comment.text, true)
+            }
+          }
+
+          if (abuse.reporterAccount) {
+            abuse.reporterAccount = new Account(abuse.reporterAccount)
+          }
+
+          if (abuse.flaggedAccount) {
+            abuse.flaggedAccount = new Account(abuse.flaggedAccount)
+          }
+
+          if (abuse.updatedAt === abuse.createdAt) delete abuse.updatedAt
+
+          this.abuses.push(abuse)
+        }
+      },
+
+      error: err => this.notifier.error(err.message)
+    })
   }
 
   private buildInternalActions (): DropdownAction<ProcessedAbuse>[] {
@@ -351,15 +352,15 @@ export class AbuseListTableComponent extends RestTable implements OnInit {
         isDisplayed: abuse => abuse.video && !abuse.video.deleted && !abuse.video.blacklisted,
         handler: abuse => {
           this.videoBlocklistService.blockVideo(abuse.video.id, undefined, abuse.video.channel.isLocal)
-            .subscribe(
-              () => {
+            .subscribe({
+              next: () => {
                 this.notifier.success($localize`Video blocked.`)
 
                 this.updateAbuseState(abuse, AbuseState.ACCEPTED)
               },
 
-              err => this.notifier.error(err.message)
-            )
+              error: err => this.notifier.error(err.message)
+            })
         }
       },
       {
@@ -367,15 +368,15 @@ export class AbuseListTableComponent extends RestTable implements OnInit {
         isDisplayed: abuse => abuse.video && !abuse.video.deleted && abuse.video.blacklisted,
         handler: abuse => {
           this.videoBlocklistService.unblockVideo(abuse.video.id)
-            .subscribe(
-              () => {
+            .subscribe({
+              next: () => {
                 this.notifier.success($localize`Video unblocked.`)
 
                 this.updateAbuseState(abuse, AbuseState.ACCEPTED)
               },
 
-              err => this.notifier.error(err.message)
-            )
+              error: err => this.notifier.error(err.message)
+            })
         }
       },
       {
@@ -389,15 +390,15 @@ export class AbuseListTableComponent extends RestTable implements OnInit {
           if (res === false) return
 
           this.videoService.removeVideo(abuse.video.id)
-            .subscribe(
-              () => {
+            .subscribe({
+              next: () => {
                 this.notifier.success($localize`Video deleted.`)
 
                 this.updateAbuseState(abuse, AbuseState.ACCEPTED)
               },
 
-              err => this.notifier.error(err.message)
-            )
+              error: err => this.notifier.error(err.message)
+            })
         }
       }
     ]
@@ -424,15 +425,15 @@ export class AbuseListTableComponent extends RestTable implements OnInit {
           if (res === false) return
 
           this.commentService.deleteVideoComment(abuse.comment.video.id, abuse.comment.id)
-            .subscribe(
-              () => {
+            .subscribe({
+              next: () => {
                 this.notifier.success($localize`Comment deleted.`)
 
                 this.updateAbuseState(abuse, AbuseState.ACCEPTED)
               },
 
-              err => this.notifier.error(err.message)
-            )
+              error: err => this.notifier.error(err.message)
+            })
         }
       }
     ]
@@ -440,25 +441,25 @@ export class AbuseListTableComponent extends RestTable implements OnInit {
 
   private muteAccountHelper (account: Account) {
     this.blocklistService.blockAccountByInstance(account)
-      .subscribe(
-        () => {
+      .subscribe({
+        next: () => {
           this.notifier.success($localize`Account ${account.nameWithHost} muted by the instance.`)
           account.mutedByInstance = true
         },
 
-        err => this.notifier.error(err.message)
-      )
+        error: err => this.notifier.error(err.message)
+      })
   }
 
   private muteServerHelper (host: string) {
     this.blocklistService.blockServerByInstance(host)
-      .subscribe(
-        () => {
+      .subscribe({
+        next: () => {
           this.notifier.success($localize`Server ${host} muted by the instance.`)
         },
 
-        err => this.notifier.error(err.message)
-      )
+        error: err => this.notifier.error(err.message)
+      })
   }
 
   private toHtml (text: string) {
diff --git a/client/src/app/shared/shared-abuse-list/abuse-message-modal.component.ts b/client/src/app/shared/shared-abuse-list/abuse-message-modal.component.ts
index 9abb4c094..6c8dc6d35 100644
--- a/client/src/app/shared/shared-abuse-list/abuse-message-modal.component.ts
+++ b/client/src/app/shared/shared-abuse-list/abuse-message-modal.component.ts
@@ -61,8 +61,8 @@ export class AbuseMessageModalComponent extends FormReactive implements OnInit {
     this.sendingMessage = true
 
     this.abuseService.addAbuseMessage(this.abuse, this.form.value['message'])
-      .subscribe(
-        () => {
+      .subscribe({
+        next: () => {
           this.form.reset()
           this.sendingMessage = false
           this.countMessagesUpdated.emit({ abuseId: this.abuse.id, countMessages: this.abuseMessages.length + 1 })
@@ -70,25 +70,25 @@ export class AbuseMessageModalComponent extends FormReactive implements OnInit {
           this.loadMessages()
         },
 
-        err => {
+        error: err => {
           this.sendingMessage = false
           console.error(err)
           this.notifier.error('Sorry but you cannot send this message. Please retry later')
         }
-      )
+      })
   }
 
   deleteMessage (abuseMessage: AbuseMessage) {
     this.abuseService.deleteAbuseMessage(this.abuse, abuseMessage)
-      .subscribe(
-        () => {
+      .subscribe({
+        next: () => {
           this.countMessagesUpdated.emit({ abuseId: this.abuse.id, countMessages: this.abuseMessages.length - 1 })
 
           this.abuseMessages = this.abuseMessages.filter(m => m.id !== abuseMessage.id)
         },
 
-        err => this.notifier.error(err.message)
-      )
+        error: err => this.notifier.error(err.message)
+      })
   }
 
   isMessageByMe (abuseMessage: AbuseMessage) {
@@ -105,8 +105,8 @@ export class AbuseMessageModalComponent extends FormReactive implements OnInit {
 
   private loadMessages () {
     this.abuseService.listAbuseMessages(this.abuse)
-      .subscribe(
-        async res => {
+      .subscribe({
+        next: async res => {
           this.abuseMessages = []
 
           for (const m of res.data) {
@@ -124,8 +124,8 @@ export class AbuseMessageModalComponent extends FormReactive implements OnInit {
           })
         },
 
-        err => this.notifier.error(err.message)
-      )
+        error: err => this.notifier.error(err.message)
+      })
   }
 
 }
diff --git a/client/src/app/shared/shared-abuse-list/moderation-comment-modal.component.ts b/client/src/app/shared/shared-abuse-list/moderation-comment-modal.component.ts
index 876aeea8a..06f1555ea 100644
--- a/client/src/app/shared/shared-abuse-list/moderation-comment-modal.component.ts
+++ b/client/src/app/shared/shared-abuse-list/moderation-comment-modal.component.ts
@@ -53,16 +53,16 @@ export class ModerationCommentModalComponent extends FormReactive implements OnI
     const moderationComment: string = this.form.value[ 'moderationComment' ]
 
     this.abuseService.updateAbuse(this.abuseToComment, { moderationComment })
-        .subscribe(
-          () => {
+        .subscribe({
+          next: () => {
             this.notifier.success($localize`Comment updated.`)
 
             this.commentUpdated.emit(moderationComment)
             this.hide()
           },
 
-          err => this.notifier.error(err.message)
-        )
+          error: err => this.notifier.error(err.message)
+        })
   }
 
 }
diff --git a/client/src/app/shared/shared-custom-markup/custom-markup.service.ts b/client/src/app/shared/shared-custom-markup/custom-markup.service.ts
index c9d33980e..63f2f76f5 100644
--- a/client/src/app/shared/shared-custom-markup/custom-markup.service.ts
+++ b/client/src/app/shared/shared-custom-markup/custom-markup.service.ts
@@ -1,4 +1,4 @@
-import { first } from 'rxjs/operators'
+import { firstValueFrom } from 'rxjs'
 import { ComponentRef, Injectable } from '@angular/core'
 import { MarkdownService } from '@app/core'
 import {
@@ -85,7 +85,7 @@ export class CustomMarkupService {
             const component = this.execAngularBuilder(selector, e)
 
             if (component.instance.loaded) {
-              const p = component.instance.loaded.pipe(first()).toPromise()
+              const p = firstValueFrom(component.instance.loaded)
               loadedPromises.push(p)
             }
 
diff --git a/client/src/app/shared/shared-custom-markup/peertube-custom-tags/channel-miniature-markup.component.ts b/client/src/app/shared/shared-custom-markup/peertube-custom-tags/channel-miniature-markup.component.ts
index 5bb045a82..8c1357d7a 100644
--- a/client/src/app/shared/shared-custom-markup/peertube-custom-tags/channel-miniature-markup.component.ts
+++ b/client/src/app/shared/shared-custom-markup/peertube-custom-tags/channel-miniature-markup.component.ts
@@ -44,14 +44,14 @@ export class ChannelMiniatureMarkupComponent implements CustomMarkupComponent, O
         tap(html => this.descriptionHTML = html),
         switchMap(() => this.loadVideosObservable()),
         finalize(() => this.loaded.emit(true))
-      ).subscribe(
-        ({ total, data }) => {
+      ).subscribe({
+        next: ({ total, data }) => {
           this.totalVideos = total
           this.video = data[0]
         },
 
-        err => this.notifier.error($localize`Error in channel miniature component: ${err.message}`)
-      )
+        error: err => this.notifier.error($localize`Error in channel miniature component: ${err.message}`)
+      })
   }
 
   getVideoChannelLink () {
diff --git a/client/src/app/shared/shared-custom-markup/peertube-custom-tags/playlist-miniature-markup.component.ts b/client/src/app/shared/shared-custom-markup/peertube-custom-tags/playlist-miniature-markup.component.ts
index 5a5c34867..07fa6fd2d 100644
--- a/client/src/app/shared/shared-custom-markup/peertube-custom-tags/playlist-miniature-markup.component.ts
+++ b/client/src/app/shared/shared-custom-markup/peertube-custom-tags/playlist-miniature-markup.component.ts
@@ -41,10 +41,10 @@ export class PlaylistMiniatureMarkupComponent implements CustomMarkupComponent,
   ngOnInit () {
     this.findInBulkService.getPlaylist(this.uuid)
       .pipe(finalize(() => this.loaded.emit(true)))
-      .subscribe(
-        playlist => this.playlist = playlist,
+      .subscribe({
+        next: playlist => this.playlist = playlist,
 
-        err => this.notifier.error($localize`Error in playlist miniature component: ${err.message}`)
-      )
+        error: err => this.notifier.error($localize`Error in playlist miniature component: ${err.message}`)
+      })
   }
 }
diff --git a/client/src/app/shared/shared-custom-markup/peertube-custom-tags/video-miniature-markup.component.ts b/client/src/app/shared/shared-custom-markup/peertube-custom-tags/video-miniature-markup.component.ts
index 84c936ee7..56b43d85e 100644
--- a/client/src/app/shared/shared-custom-markup/peertube-custom-tags/video-miniature-markup.component.ts
+++ b/client/src/app/shared/shared-custom-markup/peertube-custom-tags/video-miniature-markup.component.ts
@@ -53,10 +53,10 @@ export class VideoMiniatureMarkupComponent implements CustomMarkupComponent, OnI
 
     this.findInBulk.getVideo(this.uuid)
       .pipe(finalize(() => this.loaded.emit(true)))
-      .subscribe(
-        video => this.video = video,
+      .subscribe({
+        next: video => this.video = video,
 
-        err => this.notifier.error($localize`Error in video miniature component: ${err.message}`)
-      )
+        error: err => this.notifier.error($localize`Error in video miniature component: ${err.message}`)
+      })
   }
 }
diff --git a/client/src/app/shared/shared-custom-markup/peertube-custom-tags/videos-list-markup.component.ts b/client/src/app/shared/shared-custom-markup/peertube-custom-tags/videos-list-markup.component.ts
index 6473e9ba0..856e43681 100644
--- a/client/src/app/shared/shared-custom-markup/peertube-custom-tags/videos-list-markup.component.ts
+++ b/client/src/app/shared/shared-custom-markup/peertube-custom-tags/videos-list-markup.component.ts
@@ -71,11 +71,11 @@ export class VideosListMarkupComponent implements CustomMarkupComponent, OnInit
 
     return this.getVideosObservable()
       .pipe(finalize(() => this.loaded.emit(true)))
-      .subscribe(
-        ({ data }) => this.videos = data,
+      .subscribe({
+        next: ({ data }) => this.videos = data,
 
-        err => this.notifier.error($localize`Error in videos list component: ${err.message}`)
-      )
+        error: err => this.notifier.error($localize`Error in videos list component: ${err.message}`)
+      })
   }
 
   getVideosObservable () {
diff --git a/client/src/app/shared/shared-instance/instance-about-accordion.component.ts b/client/src/app/shared/shared-instance/instance-about-accordion.component.ts
index 8e7bf2021..855c06aa1 100644
--- a/client/src/app/shared/shared-instance/instance-about-accordion.component.ts
+++ b/client/src/app/shared/shared-instance/instance-about-accordion.component.ts
@@ -36,8 +36,8 @@ export class InstanceAboutAccordionComponent implements OnInit {
 
   ngOnInit (): void {
     this.instanceService.getAbout()
-      .subscribe(
-        async about => {
+      .subscribe({
+        next: async about => {
           this.about = about
 
           this.aboutHtml = await this.instanceService.buildHtml(about)
@@ -45,8 +45,8 @@ export class InstanceAboutAccordionComponent implements OnInit {
           this.init.emit(this)
         },
 
-        err => this.notifier.error(err.message)
-      )
+        error: err => this.notifier.error(err.message)
+      })
   }
 
   getAdministratorsPanel () {
diff --git a/client/src/app/shared/shared-main/auth/auth-interceptor.service.ts b/client/src/app/shared/shared-main/auth/auth-interceptor.service.ts
index a75c8a25c..51248c7b2 100644
--- a/client/src/app/shared/shared-main/auth/auth-interceptor.service.ts
+++ b/client/src/app/shared/shared-main/auth/auth-interceptor.service.ts
@@ -36,7 +36,7 @@ export class AuthInterceptor implements HttpInterceptor {
                      return this.handleNotAuthenticated(err)
                    }
 
-                   return observableThrowError(err)
+                   return observableThrowError(() => err)
                  })
                )
   }
diff --git a/client/src/app/shared/shared-main/users/user-notifications.component.ts b/client/src/app/shared/shared-main/users/user-notifications.component.ts
index 96b141543..50005b855 100644
--- a/client/src/app/shared/shared-main/users/user-notifications.component.ts
+++ b/client/src/app/shared/shared-main/users/user-notifications.component.ts
@@ -56,8 +56,8 @@ export class UserNotificationsComponent implements OnInit {
     }
 
     this.userNotificationService.listMyNotifications(options)
-        .subscribe(
-          result => {
+        .subscribe({
+          next: result => {
             this.notifications = reset ? result.data : this.notifications.concat(result.data)
             this.componentPagination.totalItems = result.total
 
@@ -66,8 +66,8 @@ export class UserNotificationsComponent implements OnInit {
             this.onDataSubject.next(result.data)
           },
 
-          err => this.notifier.error(err.message)
-        )
+          error: err => this.notifier.error(err.message)
+        })
   }
 
   onNearOfBottom () {
@@ -84,26 +84,26 @@ export class UserNotificationsComponent implements OnInit {
     if (notification.read) return
 
     this.userNotificationService.markAsRead(notification)
-        .subscribe(
-          () => {
+        .subscribe({
+          next: () => {
             notification.read = true
           },
 
-          err => this.notifier.error(err.message)
-        )
+          error: err => this.notifier.error(err.message)
+        })
   }
 
   markAllAsRead () {
     this.userNotificationService.markAllAsRead()
-        .subscribe(
-          () => {
+        .subscribe({
+          next: () => {
             for (const notification of this.notifications) {
               notification.read = true
             }
           },
 
-          err => this.notifier.error(err.message)
-        )
+          error: err => this.notifier.error(err.message)
+        })
   }
 
   changeSortColumn (column: string) {
diff --git a/client/src/app/shared/shared-moderation/account-blocklist.component.ts b/client/src/app/shared/shared-moderation/account-blocklist.component.ts
index 1146aeec0..36f2a591b 100644
--- a/client/src/app/shared/shared-moderation/account-blocklist.component.ts
+++ b/client/src/app/shared/shared-moderation/account-blocklist.component.ts
@@ -62,13 +62,13 @@ export class GenericAccountBlocklistComponent extends RestTable implements OnIni
         search: this.search
       })
 
-    return operation.subscribe(
-      resultList => {
+    return operation.subscribe({
+      next: resultList => {
         this.blockedAccounts = resultList.data
         this.totalRecords = resultList.total
       },
 
-      err => this.notifier.error(err.message)
-    )
+      error: err => this.notifier.error(err.message)
+    })
   }
 }
diff --git a/client/src/app/shared/shared-moderation/report-modals/account-report.component.ts b/client/src/app/shared/shared-moderation/report-modals/account-report.component.ts
index cc8875f77..4ec02f11a 100644
--- a/client/src/app/shared/shared-moderation/report-modals/account-report.component.ts
+++ b/client/src/app/shared/shared-moderation/report-modals/account-report.component.ts
@@ -77,14 +77,14 @@ export class AccountReportComponent extends FormReactive implements OnInit {
       account: {
         id: this.account.id
       }
-    }).subscribe(
-      () => {
+    }).subscribe({
+      next: () => {
         this.notifier.success($localize`Account reported.`)
         this.hide()
       },
 
-      err => this.notifier.error(err.message)
-    )
+      error: err => this.notifier.error(err.message)
+    })
   }
 
   isRemote () {
diff --git a/client/src/app/shared/shared-moderation/report-modals/comment-report.component.ts b/client/src/app/shared/shared-moderation/report-modals/comment-report.component.ts
index c7395c7b7..7c0907ce4 100644
--- a/client/src/app/shared/shared-moderation/report-modals/comment-report.component.ts
+++ b/client/src/app/shared/shared-moderation/report-modals/comment-report.component.ts
@@ -77,14 +77,14 @@ export class CommentReportComponent extends FormReactive implements OnInit {
       comment: {
         id: this.comment.id
       }
-    }).subscribe(
-      () => {
+    }).subscribe({
+      next: () => {
         this.notifier.success($localize`Comment reported.`)
         this.hide()
       },
 
-      err => this.notifier.error(err.message)
-    )
+      error: err => this.notifier.error(err.message)
+    })
   }
 
   isRemote () {
diff --git a/client/src/app/shared/shared-moderation/report-modals/video-report.component.ts b/client/src/app/shared/shared-moderation/report-modals/video-report.component.ts
index e509ac88f..278d60ac6 100644
--- a/client/src/app/shared/shared-moderation/report-modals/video-report.component.ts
+++ b/client/src/app/shared/shared-moderation/report-modals/video-report.component.ts
@@ -108,14 +108,14 @@ export class VideoReportComponent extends FormReactive implements OnInit {
         startAt: hasStart && startAt ? startAt : undefined,
         endAt: hasEnd && endAt ? endAt : undefined
       }
-    }).subscribe(
-      () => {
+    }).subscribe({
+      next: () => {
         this.notifier.success($localize`Video reported.`)
         this.hide()
       },
 
-      err => this.notifier.error(err.message)
-    )
+      error: err => this.notifier.error(err.message)
+    })
   }
 
   isRemote () {
diff --git a/client/src/app/shared/shared-moderation/server-blocklist.component.ts b/client/src/app/shared/shared-moderation/server-blocklist.component.ts
index 274d8f6e9..da09b1d0e 100644
--- a/client/src/app/shared/shared-moderation/server-blocklist.component.ts
+++ b/client/src/app/shared/shared-moderation/server-blocklist.component.ts
@@ -88,13 +88,13 @@ export class GenericServerBlocklistComponent extends RestTable implements OnInit
         search: this.search
       })
 
-    return operation.subscribe(
-      resultList => {
+    return operation.subscribe({
+      next: resultList => {
         this.blockedServers = resultList.data
         this.totalRecords = resultList.total
       },
 
-      err => this.notifier.error(err.message)
-    )
+      error: err => this.notifier.error(err.message)
+    })
   }
 }
diff --git a/client/src/app/shared/shared-moderation/user-ban-modal.component.ts b/client/src/app/shared/shared-moderation/user-ban-modal.component.ts
index afc69a1b8..0a2d5e93a 100644
--- a/client/src/app/shared/shared-moderation/user-ban-modal.component.ts
+++ b/client/src/app/shared/shared-moderation/user-ban-modal.component.ts
@@ -47,8 +47,8 @@ export class UserBanModalComponent extends FormReactive implements OnInit {
     const reason = this.form.value['reason'] || undefined
 
     this.userService.banUsers(this.usersToBan, reason)
-      .subscribe(
-        () => {
+      .subscribe({
+        next: () => {
           const message = Array.isArray(this.usersToBan)
             ? $localize`${this.usersToBan.length} users banned.`
             : $localize`User ${this.usersToBan.username} banned.`
@@ -59,8 +59,8 @@ export class UserBanModalComponent extends FormReactive implements OnInit {
           this.hide()
         },
 
-          err => this.notifier.error(err.message)
-      )
+        error: err => this.notifier.error(err.message)
+      })
   }
 
 }
diff --git a/client/src/app/shared/shared-moderation/user-moderation-dropdown.component.ts b/client/src/app/shared/shared-moderation/user-moderation-dropdown.component.ts
index 8c5a48d42..9f1e1bf9b 100644
--- a/client/src/app/shared/shared-moderation/user-moderation-dropdown.component.ts
+++ b/client/src/app/shared/shared-moderation/user-moderation-dropdown.component.ts
@@ -67,14 +67,14 @@ export class UserModerationDropdownComponent implements OnInit, OnChanges {
     if (res === false) return
 
     this.userService.unbanUsers(user)
-        .subscribe(
-          () => {
+        .subscribe({
+          next: () => {
             this.notifier.success($localize`User ${user.username} unbanned.`)
             this.userChanged.emit()
           },
 
-          err => this.notifier.error(err.message)
-        )
+          error: err => this.notifier.error(err.message)
+        })
   }
 
   async removeUser (user: User) {
@@ -87,137 +87,139 @@ export class UserModerationDropdownComponent implements OnInit, OnChanges {
     const res = await this.confirmService.confirm(message, $localize`Delete`)
     if (res === false) return
 
-    this.userService.removeUser(user).subscribe(
-      () => {
-        this.notifier.success($localize`User ${user.username} deleted.`)
-        this.userDeleted.emit()
-      },
+    this.userService.removeUser(user)
+      .subscribe({
+        next: () => {
+          this.notifier.success($localize`User ${user.username} deleted.`)
+          this.userDeleted.emit()
+        },
 
-      err => this.notifier.error(err.message)
-    )
+        error: err => this.notifier.error(err.message)
+      })
   }
 
   setEmailAsVerified (user: User) {
-    this.userService.updateUser(user.id, { emailVerified: true }).subscribe(
-      () => {
-        this.notifier.success($localize`User ${user.username} email set as verified`)
-        this.userChanged.emit()
-      },
+    this.userService.updateUser(user.id, { emailVerified: true })
+      .subscribe({
+        next: () => {
+          this.notifier.success($localize`User ${user.username} email set as verified`)
+          this.userChanged.emit()
+        },
 
-      err => this.notifier.error(err.message)
-    )
+        error: err => this.notifier.error(err.message)
+      })
   }
 
   blockAccountByUser (account: Account) {
     this.blocklistService.blockAccountByUser(account)
-        .subscribe(
-          () => {
+        .subscribe({
+          next: () => {
             this.notifier.success($localize`Account ${account.nameWithHost} muted.`)
 
             this.account.mutedByUser = true
             this.userChanged.emit()
           },
 
-          err => this.notifier.error(err.message)
-        )
+          error: err => this.notifier.error(err.message)
+        })
   }
 
   unblockAccountByUser (account: Account) {
     this.blocklistService.unblockAccountByUser(account)
-        .subscribe(
-          () => {
+        .subscribe({
+          next: () => {
             this.notifier.success($localize`Account ${account.nameWithHost} unmuted.`)
 
             this.account.mutedByUser = false
             this.userChanged.emit()
           },
 
-          err => this.notifier.error(err.message)
-        )
+          error: err => this.notifier.error(err.message)
+        })
   }
 
   blockServerByUser (host: string) {
     this.blocklistService.blockServerByUser(host)
-        .subscribe(
-          () => {
+        .subscribe({
+          next: () => {
             this.notifier.success($localize`Instance ${host} muted.`)
 
             this.account.mutedServerByUser = true
             this.userChanged.emit()
           },
 
-          err => this.notifier.error(err.message)
-        )
+          error: err => this.notifier.error(err.message)
+        })
   }
 
   unblockServerByUser (host: string) {
     this.blocklistService.unblockServerByUser(host)
-        .subscribe(
-          () => {
+        .subscribe({
+          next: () => {
             this.notifier.success($localize`Instance ${host} unmuted.`)
 
             this.account.mutedServerByUser = false
             this.userChanged.emit()
           },
 
-          err => this.notifier.error(err.message)
-        )
+          error: err => this.notifier.error(err.message)
+        })
   }
 
   blockAccountByInstance (account: Account) {
     this.blocklistService.blockAccountByInstance(account)
-        .subscribe(
-          () => {
+        .subscribe({
+          next: () => {
             this.notifier.success($localize`Account ${account.nameWithHost} muted by the instance.`)
 
             this.account.mutedByInstance = true
             this.userChanged.emit()
           },
 
-          err => this.notifier.error(err.message)
-        )
+          error: err => this.notifier.error(err.message)
+        })
   }
 
   unblockAccountByInstance (account: Account) {
     this.blocklistService.unblockAccountByInstance(account)
-        .subscribe(
-          () => {
+        .subscribe({
+          next: () => {
             this.notifier.success($localize`Account ${account.nameWithHost} unmuted by the instance.`)
 
             this.account.mutedByInstance = false
             this.userChanged.emit()
           },
 
-          err => this.notifier.error(err.message)
-        )
+          error: err => this.notifier.error(err.message)
+        })
   }
 
   blockServerByInstance (host: string) {
     this.blocklistService.blockServerByInstance(host)
-        .subscribe(
-          () => {
+        .subscribe({
+          next: () => {
             this.notifier.success($localize`Instance ${host} muted by the instance.`)
 
             this.account.mutedServerByInstance = true
             this.userChanged.emit()
           },
 
-          err => this.notifier.error(err.message)
-        )
+          error: err => this.notifier.error(err.message)
+        })
   }
 
   unblockServerByInstance (host: string) {
     this.blocklistService.unblockServerByInstance(host)
-        .subscribe(
-          () => {
+        .subscribe({
+          next: () => {
             this.notifier.success($localize`Instance ${host} unmuted by the instance.`)
 
             this.account.mutedServerByInstance = false
             this.userChanged.emit()
           },
 
-          err => this.notifier.error(err.message)
-        )
+          error: err => this.notifier.error(err.message)
+        })
   }
 
   async bulkRemoveCommentsOf (body: BulkRemoveCommentsOfBody) {
@@ -226,13 +228,13 @@ export class UserModerationDropdownComponent implements OnInit, OnChanges {
     if (res === false) return
 
     this.bulkService.removeCommentsOf(body)
-        .subscribe(
-          () => {
+        .subscribe({
+          next: () => {
             this.notifier.success($localize`Will remove comments of this account (may take several minutes).`)
           },
 
-          err => this.notifier.error(err.message)
-        )
+          error: err => this.notifier.error(err.message)
+        })
   }
 
   getRouterUserEditLink (user: User) {
diff --git a/client/src/app/shared/shared-moderation/video-block.component.ts b/client/src/app/shared/shared-moderation/video-block.component.ts
index fb47989dc..bc6952620 100644
--- a/client/src/app/shared/shared-moderation/video-block.component.ts
+++ b/client/src/app/shared/shared-moderation/video-block.component.ts
@@ -55,8 +55,8 @@ export class VideoBlockComponent extends FormReactive implements OnInit {
     const unfederate = this.video.isLocal ? this.form.value[ 'unfederate' ] : undefined
 
     this.videoBlocklistService.blockVideo(this.video.id, reason, unfederate)
-        .subscribe(
-          () => {
+        .subscribe({
+          next: () => {
             this.notifier.success($localize`Video blocked.`)
             this.hide()
 
@@ -66,7 +66,7 @@ export class VideoBlockComponent extends FormReactive implements OnInit {
             this.videoBlocked.emit()
           },
 
-          err => this.notifier.error(err.message)
-        )
+          error: err => this.notifier.error(err.message)
+        })
   }
 }
diff --git a/client/src/app/shared/shared-search/find-in-bulk.service.ts b/client/src/app/shared/shared-search/find-in-bulk.service.ts
index 61dd2cbc5..962e374a5 100644
--- a/client/src/app/shared/shared-search/find-in-bulk.service.ts
+++ b/client/src/app/shared/shared-search/find-in-bulk.service.ts
@@ -1,5 +1,5 @@
 import * as debug from 'debug'
-import { Observable, Subject, throwError } from 'rxjs'
+import { Observable, Subject } from 'rxjs'
 import { first, map } from 'rxjs/operators'
 import { Injectable, NgZone } from '@angular/core'
 import { buildBulkObservable } from '@app/helpers'
diff --git a/client/src/app/shared/shared-user-settings/user-interface-settings.component.ts b/client/src/app/shared/shared-user-settings/user-interface-settings.component.ts
index b721604e5..bd1cb0353 100644
--- a/client/src/app/shared/shared-user-settings/user-interface-settings.component.ts
+++ b/client/src/app/shared/shared-user-settings/user-interface-settings.component.ts
@@ -65,18 +65,21 @@ export class UserInterfaceSettingsComponent extends FormReactive implements OnIn
     }
 
     if (this.authService.isLoggedIn()) {
-      this.userService.updateMyProfile(details).subscribe(
-        () => {
-          this.authService.refreshUserInformation()
+      this.userService.updateMyProfile(details)
+        .subscribe({
+          next: () => {
+            this.authService.refreshUserInformation()
 
-          if (this.notifyOnUpdate) this.notifier.success($localize`Interface settings updated.`)
-        },
+            if (this.notifyOnUpdate) this.notifier.success($localize`Interface settings updated.`)
+          },
 
-        err => this.notifier.error(err.message)
-      )
-    } else {
-      this.userService.updateMyAnonymousProfile(details)
-      if (this.notifyOnUpdate) this.notifier.success($localize`Interface settings updated.`)
+          error: err => this.notifier.error(err.message)
+        })
+
+      return
     }
+
+    this.userService.updateMyAnonymousProfile(details)
+    if (this.notifyOnUpdate) this.notifier.success($localize`Interface settings updated.`)
   }
 }
diff --git a/client/src/app/shared/shared-user-settings/user-video-settings.component.ts b/client/src/app/shared/shared-user-settings/user-video-settings.component.ts
index 8b0eaea4f..4aac60c2b 100644
--- a/client/src/app/shared/shared-user-settings/user-video-settings.component.ts
+++ b/client/src/app/shared/shared-user-settings/user-video-settings.component.ts
@@ -162,15 +162,16 @@ export class UserVideoSettingsComponent extends FormReactive implements OnInit,
   }
 
   private updateLoggedProfile (details: UserUpdateMe) {
-    this.userService.updateMyProfile(details).subscribe(
-      () => {
-        this.authService.refreshUserInformation()
+    this.userService.updateMyProfile(details)
+      .subscribe({
+        next: () => {
+          this.authService.refreshUserInformation()
 
-        if (this.notifyOnUpdate) this.notifier.success($localize`Video settings updated.`)
-      },
+          if (this.notifyOnUpdate) this.notifier.success($localize`Video settings updated.`)
+        },
 
-      err => this.notifier.error(err.message)
-    )
+        error: err => this.notifier.error(err.message)
+      })
   }
 
   private updateAnonymousProfile (details: UserUpdateMe) {
diff --git a/client/src/app/shared/shared-user-subscription/subscribe-button.component.ts b/client/src/app/shared/shared-user-subscription/subscribe-button.component.ts
index 2261e07dd..796493bd9 100644
--- a/client/src/app/shared/shared-user-subscription/subscribe-button.component.ts
+++ b/client/src/app/shared/shared-user-subscription/subscribe-button.component.ts
@@ -102,8 +102,8 @@ export class SubscribeButtonComponent implements OnInit, OnChanges {
       .map(handle => this.userSubscriptionService.addSubscription(handle))
 
     forkJoin(observableBatch)
-      .subscribe(
-        () => {
+      .subscribe({
+        next: () => {
           this.notifier.success(
             this.account
               ? $localize`Subscribed to all current channels of ${this.account.displayName}. You will be notified of all their new videos.`
@@ -113,8 +113,8 @@ export class SubscribeButtonComponent implements OnInit, OnChanges {
           )
         },
 
-        err => this.notifier.error(err.message)
-      )
+        error: err => this.notifier.error(err.message)
+      })
   }
 
   unsubscribe () {
@@ -182,11 +182,11 @@ export class SubscribeButtonComponent implements OnInit, OnChanges {
       merge(
         this.userSubscriptionService.listenToSubscriptionCacheChange(handle),
         this.userSubscriptionService.doesSubscriptionExist(handle)
-      ).subscribe(
-        res => this.subscribed.set(handle, res),
+      ).subscribe({
+        next: res => this.subscribed.set(handle, res),
 
-        err => this.notifier.error(err.message)
-      )
+        error: err => this.notifier.error(err.message)
+      })
     }
   }
 }
diff --git a/client/src/app/shared/shared-video-miniature/abstract-video-list.ts b/client/src/app/shared/shared-video-miniature/abstract-video-list.ts
index 33061a837..d8b2ee17d 100644
--- a/client/src/app/shared/shared-video-miniature/abstract-video-list.ts
+++ b/client/src/app/shared/shared-video-miniature/abstract-video-list.ts
@@ -204,28 +204,29 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy, AfterConte
   }
 
   loadMoreVideos (reset = false) {
-    this.getVideosObservable(this.pagination.currentPage).subscribe(
-      ({ data }) => {
-        this.hasDoneFirstQuery = true
-        this.lastQueryLength = data.length
+    this.getVideosObservable(this.pagination.currentPage)
+      .subscribe({
+        next: ({ data }) => {
+          this.hasDoneFirstQuery = true
+          this.lastQueryLength = data.length
 
-        if (reset) this.videos = []
-        this.videos = this.videos.concat(data)
+          if (reset) this.videos = []
+          this.videos = this.videos.concat(data)
 
-        if (this.groupByDate) this.buildGroupedDateLabels()
+          if (this.groupByDate) this.buildGroupedDateLabels()
 
-        this.onMoreVideos()
+          this.onMoreVideos()
 
-        this.onDataSubject.next(data)
-      },
+          this.onDataSubject.next(data)
+        },
 
-      error => {
-        const message = $localize`Cannot load more videos. Try again later.`
+        error: err => {
+          const message = $localize`Cannot load more videos. Try again later.`
 
-        console.error(message, { error })
-        this.notifier.error(message)
-      }
-    )
+          console.error(message, { err })
+          this.notifier.error(message)
+        }
+      })
   }
 
   reloadVideos () {
diff --git a/client/src/app/shared/shared-video-miniature/video-actions-dropdown.component.ts b/client/src/app/shared/shared-video-miniature/video-actions-dropdown.component.ts
index 3c2b46d16..2ba091438 100644
--- a/client/src/app/shared/shared-video-miniature/video-actions-dropdown.component.ts
+++ b/client/src/app/shared/shared-video-miniature/video-actions-dropdown.component.ts
@@ -183,8 +183,8 @@ export class VideoActionsDropdownComponent implements OnChanges {
     if (res === false) return
 
     this.videoBlocklistService.unblockVideo(this.video.id)
-        .subscribe(
-          () => {
+        .subscribe({
+          next: () => {
             this.notifier.success($localize`Video ${this.video.name} unblocked.`)
 
             this.video.blacklisted = false
@@ -193,8 +193,8 @@ export class VideoActionsDropdownComponent implements OnChanges {
             this.videoUnblocked.emit()
           },
 
-          err => this.notifier.error(err.message)
-        )
+          error: err => this.notifier.error(err.message)
+        })
   }
 
   async removeVideo () {
@@ -209,40 +209,40 @@ export class VideoActionsDropdownComponent implements OnChanges {
     if (res === false) return
 
     this.videoService.removeVideo(this.video.id)
-        .subscribe(
-          () => {
+        .subscribe({
+          next: () => {
             this.notifier.success($localize`Video ${this.video.name} deleted.`)
             this.videoRemoved.emit()
           },
 
-          error => this.notifier.error(error.message)
-        )
+          error: err => this.notifier.error(err.message)
+        })
   }
 
   duplicateVideo () {
     this.redundancyService.addVideoRedundancy(this.video)
-        .subscribe(
-          () => {
+        .subscribe({
+          next: () => {
             const message = $localize`This video will be duplicated by your instance.`
             this.notifier.success(message)
           },
 
-          err => this.notifier.error(err.message)
-        )
+          error: err => this.notifier.error(err.message)
+        })
   }
 
   muteVideoAccount () {
     const params = { nameWithHost: Actor.CREATE_BY_STRING(this.video.account.name, this.video.account.host) }
 
     this.blocklistService.blockAccountByUser(params)
-        .subscribe(
-          () => {
+        .subscribe({
+          next: () => {
             this.notifier.success($localize`Account ${params.nameWithHost} muted.`)
             this.videoAccountMuted.emit()
           },
 
-          err => this.notifier.error(err.message)
-        )
+          error: err => this.notifier.error(err.message)
+        })
   }
 
   onVideoBlocked () {
diff --git a/client/src/app/shared/shared-video-miniature/video-download.component.ts b/client/src/app/shared/shared-video-miniature/video-download.component.ts
index 355ce8be3..28fefb9dd 100644
--- a/client/src/app/shared/shared-video-miniature/video-download.component.ts
+++ b/client/src/app/shared/shared-video-miniature/video-download.component.ts
@@ -1,4 +1,5 @@
 import { mapValues, pick } from 'lodash-es'
+import { firstValueFrom } from 'rxjs'
 import { tap } from 'rxjs/operators'
 import { Component, ElementRef, Inject, LOCALE_ID, ViewChild } from '@angular/core'
 import { AuthService, HooksService, Notifier } from '@app/core'
@@ -265,6 +266,6 @@ export class VideoDownloadComponent {
     const observable = this.videoService.getVideoFileMetadata(file.metadataUrl)
       .pipe(tap(res => file.metadata = res))
 
-    return observable.toPromise()
+    return firstValueFrom(observable)
   }
 }
diff --git a/client/src/app/shared/shared-video-miniature/video-miniature.component.ts b/client/src/app/shared/shared-video-miniature/video-miniature.component.ts
index 67e0de6a2..cb81ba3bd 100644
--- a/client/src/app/shared/shared-video-miniature/video-miniature.component.ts
+++ b/client/src/app/shared/shared-video-miniature/video-miniature.component.ts
@@ -214,11 +214,12 @@ export class VideoMiniatureComponent implements OnInit {
   addToWatchLater () {
     const body = { videoId: this.video.id }
 
-    this.videoPlaylistService.addVideoInPlaylist(this.watchLaterPlaylist.id, body).subscribe(
-      res => {
-        this.watchLaterPlaylist.playlistElementId = res.videoPlaylistElement.id
-      }
-    )
+    this.videoPlaylistService.addVideoInPlaylist(this.watchLaterPlaylist.id, body)
+      .subscribe(
+        res => {
+          this.watchLaterPlaylist.playlistElementId = res.videoPlaylistElement.id
+        }
+      )
   }
 
   removeFromWatchLater () {
diff --git a/client/src/app/shared/shared-video-playlist/video-add-to-playlist.component.ts b/client/src/app/shared/shared-video-playlist/video-add-to-playlist.component.ts
index 7069fa8fd..df3aeddb7 100644
--- a/client/src/app/shared/shared-video-playlist/video-add-to-playlist.component.ts
+++ b/client/src/app/shared/shared-video-playlist/video-add-to-playlist.component.ts
@@ -198,15 +198,16 @@ export class VideoAddToPlaylistComponent extends FormReactive implements OnInit,
       privacy: VideoPlaylistPrivacy.PRIVATE
     }
 
-    this.videoPlaylistService.createVideoPlaylist(videoPlaylistCreate).subscribe(
-      () => {
-        this.isNewPlaylistBlockOpened = false
+    this.videoPlaylistService.createVideoPlaylist(videoPlaylistCreate)
+      .subscribe({
+        next: () => {
+          this.isNewPlaylistBlockOpened = false
 
-        this.cd.markForCheck()
-      },
+          this.cd.markForCheck()
+        },
 
-      err => this.notifier.error(err.message)
-    )
+        error: err => this.notifier.error(err.message)
+      })
   }
 
   onVideoPlaylistSearchChanged () {
@@ -268,17 +269,15 @@ export class VideoAddToPlaylistComponent extends FormReactive implements OnInit,
     }
 
     this.videoPlaylistService.updateVideoOfPlaylist(playlist.id, element.playlistElementId, body, this.video.id)
-        .subscribe(
-          () => {
+        .subscribe({
+          next: () => {
             this.notifier.success($localize`Timestamps updated`)
           },
 
-          err => {
-            this.notifier.error(err.message)
-          },
+          error: err => this.notifier.error(err.message),
 
-          () => this.cd.markForCheck()
-        )
+          complete: () => this.cd.markForCheck()
+        })
   }
 
   private isOptionalRowDisplayed (playlist: PlaylistSummary) {
@@ -302,17 +301,15 @@ export class VideoAddToPlaylistComponent extends FormReactive implements OnInit,
 
   private removeVideoFromPlaylist (playlist: PlaylistSummary, elementId: number) {
     this.videoPlaylistService.removeVideoFromPlaylist(playlist.id, elementId, this.video.id)
-        .subscribe(
-          () => {
+        .subscribe({
+          next: () => {
             this.notifier.success($localize`Video removed from ${playlist.displayName}`)
           },
 
-          err => {
-            this.notifier.error(err.message)
-          },
+          error: err => this.notifier.error(err.message),
 
-          () => this.cd.markForCheck()
-        )
+          complete: () => this.cd.markForCheck()
+        })
   }
 
   private listenToVideoPlaylistChange () {
@@ -371,8 +368,8 @@ export class VideoAddToPlaylistComponent extends FormReactive implements OnInit,
     if (element.stopTimestamp && element.stopTimestamp !== this.video.duration) body.stopTimestamp = element.stopTimestamp
 
     this.videoPlaylistService.addVideoInPlaylist(playlist.id, body)
-      .subscribe(
-        res => {
+      .subscribe({
+        next: res => {
           const message = body.startTimestamp || body.stopTimestamp
             ? $localize`Video added in ${playlist.displayName} at timestamps ${this.formatTimestamp(element)}`
             : $localize`Video added in ${playlist.displayName}`
@@ -382,12 +379,10 @@ export class VideoAddToPlaylistComponent extends FormReactive implements OnInit,
           if (element) element.playlistElementId = res.videoPlaylistElement.id
         },
 
-        err => {
-          this.notifier.error(err.message)
-        },
+        error: err => this.notifier.error(err.message),
 
-        () => this.cd.markForCheck()
-      )
+        complete: () => this.cd.markForCheck()
+      })
   }
 
   private formatTimestamp (element: PlaylistElement) {
diff --git a/client/src/app/shared/shared-video-playlist/video-playlist-element-miniature.component.ts b/client/src/app/shared/shared-video-playlist/video-playlist-element-miniature.component.ts
index 2e495ec26..8d1e14f94 100644
--- a/client/src/app/shared/shared-video-playlist/video-playlist-element-miniature.component.ts
+++ b/client/src/app/shared/shared-video-playlist/video-playlist-element-miniature.component.ts
@@ -88,14 +88,14 @@ export class VideoPlaylistElementMiniatureComponent implements OnInit {
     const videoId = this.playlistElement.video ? this.playlistElement.video.id : undefined
 
     this.videoPlaylistService.removeVideoFromPlaylist(this.playlist.id, playlistElement.id, videoId)
-        .subscribe(
-          () => {
+        .subscribe({
+          next: () => {
             this.notifier.success($localize`Video removed from ${this.playlist.displayName}`)
             this.elementRemoved.emit(playlistElement)
           },
 
-          err => this.notifier.error(err.message)
-        )
+          error: err => this.notifier.error(err.message)
+        })
 
     this.moreDropdown.close()
   }
@@ -107,8 +107,8 @@ export class VideoPlaylistElementMiniatureComponent implements OnInit {
     body.stopTimestamp = this.timestampOptions.stopTimestampEnabled ? this.timestampOptions.stopTimestamp : null
 
     this.videoPlaylistService.updateVideoOfPlaylist(this.playlist.id, playlistElement.id, body, this.playlistElement.video.id)
-        .subscribe(
-          () => {
+        .subscribe({
+          next: () => {
             this.notifier.success($localize`Timestamps updated`)
 
             playlistElement.startTimestamp = body.startTimestamp
@@ -117,8 +117,8 @@ export class VideoPlaylistElementMiniatureComponent implements OnInit {
             this.cdr.detectChanges()
           },
 
-          err => this.notifier.error(err.message)
-        )
+          error: err => this.notifier.error(err.message)
+        })
 
     this.moreDropdown.close()
   }
diff --git a/client/src/root-helpers/plugins-manager.ts b/client/src/root-helpers/plugins-manager.ts
index f919db8af..d14ac4acd 100644
--- a/client/src/root-helpers/plugins-manager.ts
+++ b/client/src/root-helpers/plugins-manager.ts
@@ -1,5 +1,5 @@
 import * as debug from 'debug'
-import { ReplaySubject } from 'rxjs'
+import { firstValueFrom, ReplaySubject } from 'rxjs'
 import { first, shareReplay } from 'rxjs/operators'
 import { RegisterClientHelpers } from 'src/types/register-client-option.model'
 import { getHookType, internalRunHook } from '@shared/core-utils/plugins/hooks'
@@ -102,9 +102,10 @@ class PluginsManager {
   ensurePluginsAreLoaded (scope: PluginClientScope) {
     this.loadPluginsByScope(scope)
 
-    return this.pluginsLoaded[scope].asObservable()
+    const obs = this.pluginsLoaded[scope].asObservable()
                .pipe(first(), shareReplay())
-               .toPromise()
+
+    return firstValueFrom(obs)
   }
 
   async reloadLoadedScopes () {