Browse Source

feat(admin-ui): Create HTTP interceptor for errors & loading state

Michael Bromley 7 years ago
parent
commit
149cf7398c

+ 2 - 0
admin-ui/src/app/app.component.html

@@ -1 +1,3 @@
+<div class="progress loop" *ngIf="loading$ | async"><progress></progress></div>
 <router-outlet></router-outlet>
 <router-outlet></router-outlet>
+<vdr-overlay-host></vdr-overlay-host>

+ 0 - 1
admin-ui/src/app/core/components/app-shell/app-shell.component.html

@@ -25,4 +25,3 @@
         <vdr-main-nav></vdr-main-nav>
         <vdr-main-nav></vdr-main-nav>
     </div>
     </div>
 </clr-main-container>
 </clr-main-container>
-<vdr-overlay-host></vdr-overlay-host>

+ 4 - 1
admin-ui/src/app/core/core.module.ts

@@ -1,4 +1,4 @@
-import { HttpClientModule } from '@angular/common/http';
+import { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http';
 import { NgModule } from '@angular/core';
 import { NgModule } from '@angular/core';
 import { Apollo, APOLLO_OPTIONS, ApolloModule } from 'apollo-angular';
 import { Apollo, APOLLO_OPTIONS, ApolloModule } from 'apollo-angular';
 import { HttpLink, HttpLinkModule } from 'apollo-angular-link-http';
 import { HttpLink, HttpLinkModule } from 'apollo-angular-link-http';
@@ -14,6 +14,7 @@ import { MainNavComponent } from './components/main-nav/main-nav.component';
 import { UserMenuComponent } from './components/user-menu/user-menu.component';
 import { UserMenuComponent } from './components/user-menu/user-menu.component';
 import { BaseDataService } from './providers/data/base-data.service';
 import { BaseDataService } from './providers/data/base-data.service';
 import { DataService } from './providers/data/data.service';
 import { DataService } from './providers/data/data.service';
+import { DefaultInterceptor } from './providers/data/interceptor';
 import { AuthGuard } from './providers/guard/auth.guard';
 import { AuthGuard } from './providers/guard/auth.guard';
 import { LocalStorageService } from './providers/local-storage/local-storage.service';
 import { LocalStorageService } from './providers/local-storage/local-storage.service';
 import { OverlayHostComponent } from './components/overlay-host/overlay-host.component';
 import { OverlayHostComponent } from './components/overlay-host/overlay-host.component';
@@ -38,6 +39,7 @@ export function createApollo(httpLink: HttpLink, ngrxCache: InMemoryCache) {
     ],
     ],
     exports: [
     exports: [
         SharedModule,
         SharedModule,
+        OverlayHostComponent,
     ],
     ],
     providers: [
     providers: [
         {
         {
@@ -45,6 +47,7 @@ export function createApollo(httpLink: HttpLink, ngrxCache: InMemoryCache) {
             useFactory: createApollo,
             useFactory: createApollo,
             deps: [HttpLink, APOLLO_NGRX_CACHE],
             deps: [HttpLink, APOLLO_NGRX_CACHE],
         },
         },
+        { provide: HTTP_INTERCEPTORS, useClass: DefaultInterceptor, multi: true },
         BaseDataService,
         BaseDataService,
         LocalStorageService,
         LocalStorageService,
         DataService,
         DataService,

+ 50 - 0
admin-ui/src/app/core/providers/data/interceptor.ts

@@ -0,0 +1,50 @@
+/** Pass untouched request through to the next request handler. */
+import { HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest, HttpResponse } from '@angular/common/http';
+import { Injectable } from '@angular/core';
+import { Observable, throwError } from 'rxjs';
+import { catchError, tap } from 'rxjs/operators';
+
+import { ApiActions } from '../../../state/api/api-actions';
+import { NotificationService } from '../notification/notification.service';
+
+/**
+ * The default interceptor examines all HTTP requests & responses and automatically updates the requesting state
+ * and shows error notifications.
+ */
+@Injectable()
+export class DefaultInterceptor implements HttpInterceptor {
+
+    constructor(private apiActions: ApiActions,
+                private notification: NotificationService) {}
+
+    intercept(req: HttpRequest<any>, next: HttpHandler):
+        Observable<HttpEvent<any>> {
+        this.apiActions.startRequest();
+        return next.handle(req).pipe(
+            tap(event => {
+                    if (event instanceof HttpResponse) {
+                        this.notifyOnGraphQLErrors(event);
+                        this.apiActions.requestCompleted();
+                    }
+                },
+                err => {
+                    if (err instanceof HttpErrorResponse) {
+                        this.notification.error(err.message);
+                        this.apiActions.requestCompleted();
+                    }
+                }),
+        );
+    }
+
+    /**
+     * GraphQL errors still return 200 OK responses, but have the actual error message
+     * inside the body of the response.
+     */
+    private notifyOnGraphQLErrors(response: HttpResponse<any>): void {
+        const graqhQLErrors = response.body.errors;
+        if (graqhQLErrors && Array.isArray(graqhQLErrors)) {
+            const message = graqhQLErrors.map(err => err.message).join('\n');
+            this.notification.error(message);
+        }
+    }
+}