Apollo-angular: link-http: cannot set headers

Created on 23 May 2018  路  8Comments  路  Source: kamilkisiela/apollo-angular

This is my shared module

import { environment } from './../../environments/environment';
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { HttpClientModule, HttpHeaders } from '@angular/common/http';
import { ApolloModule, Apollo } from 'apollo-angular';
import { HttpLinkModule, HttpLink } from 'apollo-angular-link-http';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { ApolloLink } from 'apollo-link';

@NgModule({
  imports: [CommonModule, HttpLinkModule],
  exports: [
    CommonModule,
    HttpClientModule,
    ApolloModule,
    HttpLinkModule
  ]
})
export class SharedModule {
  constructor(apollo: Apollo, httpLink: HttpLink) {
    const http = httpLink.create({ uri: environment.graphqlUrl });
    const middleware = new ApolloLink((operation, forward) => {
      operation.setContext({
        headers: new HttpHeaders().set(
          'Authorization',
          localStorage.getItem('token') || null,
        ),
      });
      return forward(operation);
    });
    const link = middleware.concat(http);

    apollo.create({
      link: link,
      cache: new InMemoryCache()
    });
  }
}

When I request query

import { Apollo } from 'apollo-angular';
import gql from 'graphql-tag';

@Injectable()
export class MoviesService {
  constructor(private apollo: Apollo) {}
}

const QUERY = gql`
  query getMovies {
    movies(orderBy:{column:"id" order:ASC}) { ... }
  }
`;
this.apollo.query({ query: QUERY }).subscribe( ... )

I get this error

core.js:1448 ERROR Error: Uncaught (in promise): Error: Network error: Cannot read property 'length' of null
Error: Network error: Cannot read property 'length' of null
    at new ApolloError (ApolloError.js:43)
    at eval (QueryManager.js:325)
    at eval (QueryManager.js:758)
    at Array.forEach (<anonymous>)
    at eval (QueryManager.js:757)
    at Map.forEach (<anonymous>)
    at QueryManager.broadcastQueries (QueryManager.js:752)
    at eval (QueryManager.js:252)
    at ZoneDelegate.invoke (zone.js:388)
    at Object.onInvoke (core.js:4749)
    at new ApolloError (ApolloError.js:43)
    at eval (QueryManager.js:325)
    at eval (QueryManager.js:758)
    at Array.forEach (<anonymous>)
    at eval (QueryManager.js:757)
    at Map.forEach (<anonymous>)
    at QueryManager.broadcastQueries (QueryManager.js:752)
    at eval (QueryManager.js:252)
    at ZoneDelegate.invoke (zone.js:388)
    at Object.onInvoke (core.js:4749)
    at resolvePromise (zone.js:814)
    at eval (zone.js:877)
    at ZoneDelegate.invokeTask (zone.js:421)
    at Object.onInvokeTask (core.js:4740)
    at ZoneDelegate.invokeTask (zone.js:420)
    at Zone.runTask (zone.js:188)
    at drainMicroTaskQueue (zone.js:595)
    at ZoneTask.invokeTask (zone.js:500)
    at ZoneTask.invoke (zone.js:485)
    at timer (zone.js:2054)

But without middleware (only with http link) it works fine.
Like that

apollo.create({
      link: http,
      cache: new InMemoryCache()
});

Any suggestions?

Versions

"apollo-angular": "^1.0.1",
"apollo-angular-link-http": "^1.0.3",
"apollo-cache-inmemory": "^1.1.12",
"apollo-client": "^2.2.8",
"apollo-link": "^1.2.2",

"@angular/cli": "~1.7.4",
bug

Most helpful comment

I was able to get around this by checking if a token exists first, and just forwarding the operation without attempting to apply any headers if it doesn't exist:

constructor(apollo: Apollo, httpLink: HttpLink) {
    const http = httpLink.create({ uri: environment.graphqlUrl });
    const middleware = new ApolloLink((operation, forward) => {

      // Check for token
      const token = localStorage.getItem('token');
      if (!token) return forward(operation);

      operation.setContext({
        headers: new HttpHeaders().set(
          'Authorization',
          token,
        ),
      });
      return forward(operation);
    });
    const link = middleware.concat(http);

    apollo.create({
      link: link,
      cache: new InMemoryCache()
    });
  }

All 8 comments

I'll try to reproduce it and fix it

The same error occurs when using this approach

    const auth = setContext((_, { headers }) => {
      // get the authentication token from local storage if it exists
      const token = localStorage.getItem('token');
      // return the headers to the context so httpLink can read them
      // in this example we assume headers property exists
      // and it is an instance of HttpHeaders
      if (!token) {
        return {};
      } else {
        return { headers: headers.append('Authorization', `Bearer ${token}`) };
      }
    });

Can you provide another way to set headers?

I was able to get around this by checking if a token exists first, and just forwarding the operation without attempting to apply any headers if it doesn't exist:

constructor(apollo: Apollo, httpLink: HttpLink) {
    const http = httpLink.create({ uri: environment.graphqlUrl });
    const middleware = new ApolloLink((operation, forward) => {

      // Check for token
      const token = localStorage.getItem('token');
      if (!token) return forward(operation);

      operation.setContext({
        headers: new HttpHeaders().set(
          'Authorization',
          token,
        ),
      });
      return forward(operation);
    });
    const link = middleware.concat(http);

    apollo.create({
      link: link,
      cache: new InMemoryCache()
    });
  }

I've implemented Http interceptor and works fine

import { Injectable } from '@angular/core';
import { HttpRequest, HttpHandler } from '@angular/common/http';

@Injectable()
export class TokenInterceptor {
  constructor() { }

  intercept(request: HttpRequest<any>, next: HttpHandler) {
    request = request.clone({
      setHeaders: {
        Authorization: `Bearer ${localStorage.getItem('token')}`
      }
    });
    return next.handle(request);
  }
}

I was trying to implement something similar, aka. update the Apollo object as soon as I log in with Auth header. No success. There's not even a method where I could say this.apollo.getClient().updateHeader or similar.

The approach with http-interceptor was finally a success for me too.

I tried to reproduce it and it works perfectly:

https://stackblitz.com/edit/simple-apollo-angular-example-headers

It would be good to update the docs with this information as the current docs for auth are incorrect.

@herkulano Maybe you could do it? I can help

Was this page helpful?
0 / 5 - 0 ratings

Related issues

patriknil90 picture patriknil90  路  7Comments

qortex picture qortex  路  4Comments

kobojsaren picture kobojsaren  路  6Comments

jkossis picture jkossis  路  5Comments

simonhaenisch picture simonhaenisch  路  3Comments