nguniversal快速引擎请求nullinjector

时间:2019-01-21 14:04:20

标签: angular express serverside-rendering angular-universal

我已成功使用@nguniversal/express-engine运行有角度的通用应用程序。我无法工作的部分是在角度应用程序中注入了节点/表达式req对象。遵循official documentation并不能解决我的问题。错误是

  

NullInjectorError:没有提供InjectionToken REQUEST!

在服务器上,我用以下摘录设置了快速引擎

app.engine('html', ngExpressEngine({
  bootstrap: AppServerModuleNgFactory, // Give it a module to bootstrap,
  providers: [ provideModuleMap(LAZY_MODULE_MAP) ]
}));
app.set('view engine', 'html');
app.set('views', join(DIST_FOLDER, 'browser'));
app.get('*.*', express.static(join(DIST_FOLDER, 'browser')));

并使用(1)呈现索引文件

app.get('*', (req: Request, res) => {
  res.render(join(DIST_FOLDER, 'browser', 'index.html'), { req });
});

向应用程序组件中的注入看起来像

constructor (
  @Inject(REQUEST) private request: Request
) {}

REQUEST是从 @ nguniversal / express-engine / tokens 导入的。我还测试了渲染部分(1)中以下条目的添加,但这也不起作用。

providers: [ { provide: REQUEST, useValue: req } ]

还尝试注入@Optional()装饰器,并使用PLATFORM_ID来检查平台是否为server,请求对象是否未被注入并null。 您是否对如何成功使用angular本身访问req对象有任何建议?

2 个答案:

答案 0 :(得分:1)

您无法在浏览器中访问REQUEST,它是从expressjs传递到ng-universal的服务器端对象。因此,在最坏的情况下,您的代码应该执行类似的操作

constructor(
  @Inject(PLATFORM_ID) private platformId,
  @Optional() @Inject(REQUEST) private request
) {
    doSomethingWithRequestIfServer();
}

someOtherMethod() {
    doSomethingWithRequestIfServer();
}

doSomethingWithRequestIfServer() {
    if (isPlatformServer(this.platformId)) {
        // should see this in stdout of node process, or wherever node logs
        console.log('rendering server side for request:', req);
        /* use req */
    } else {
        // browser console should print null
        console.log('working browser side, request should be null', req);
    }
}    

然后,在浏览器中时,您的应用程序将不会查看“请求”,而在服务器端呈现时,它将显示。即就像您的服务器端渲染将一次运行您的角度应用程序 ,因为渲染结果将HTML发送到浏览器。然后浏览器将再次引导Angular应用程序,并从服务器渲染完成的位置提取

在最佳情况下,为避免if和平台检查,我建议对应用程序进行布局,以便仅在服务器端提供服务器端代码,而在浏览器中提供浏览器端代码。这肯定需要更多的代码,但是从维护的角度来看将更加直接。 (在下面键入快速的猴子)

公共接口声明

// common.ts
export abstract class MyServiceBase {
  abstract doSomething(): void;
}

// if implementations of service will be provided only in Angular implementation, InjectionToken can be used.
export const MY_SERVICE = new InjectionToken<MyServiceBase>('MY_SERVICE');
// otherwise, if server side implementation will be injected by from node process, then should be string only. (For alternative illustrated below in the end).
// export const MY_SERVICE = 'MY_SERVICE';

应该在服务器和浏览器上呈现的通用应用程序组件。

// app.component.ts
@Component({
  // skipped
})
export class AppComponent {
  constructor(@Inject(MY_SERVICE) myService: MyServiceBase) {
    myService.doSomething();
  }
}

适用于服务器端和浏览器端的通用应用程序代码。

// app.module.ts
@NgModule({
  declarations: [AppComponent],
  bootstrap: [AppComponent]
})
export class AppModule {}

// app-browser.service.ts
@Injectable()
export class MyServiceForBrowser extends MyServiceBase {
  constructor() {
    console.log('MyService browser implementation');
  }

  doSomthing(): void {
    // do something meaningful in browser 
  }
}

引导此操作,而不是默认的AppModule来分隔实施设置。

// app-browser.module.ts
@NgModule({
  imports: [AppModule],
  providers: [{provide: MY_SERVICE, useClass: MyServiceForBrowser}],
  bootstrap: [AppComponent]
})
export class AppBrowserModule {
}

角度应用程序的服务器端 如果您可以立即在Angular应用程序中实现服务器端功能,那么。

// app-server.service.ts
@Injectable()
export class MyServiceForServer extends MyServiceBase {
  constructor(@Inject(REQUEST) private request) {
    console.log('MyService server implementation');
  }

  doSomthing(): void {
    // do something meaningful on server
    console.log('request is', this.request);
  }
}

要从ngExpressEngine引导的服务器端模块。

// app-server.module.ts
@NgModule({
  imports: [AppModule],
  providers: [{provide: MY_SERVICE, useClass: MyServiceForServer}],
  bootstrap: [AppComponent]
})
export class AppServerModule {}

服务器的备用服务器端

或者,甚至可以从服务器代码中提供MyServiceForServer。在这种情况下,无需在角度实现中提供实现:

// app-server.module.ts
@NgModule({
  imports: [AppModule],
  bootstrap: [AppComponent]
})
export class AppServerModule {}

相反,将其编写为普通的服务器端代码:

export class MyServiceForServer extends MyService {
  constructor(private request) {
    console.log('MyService server implementation');
  }

  doSomthing(): void {
    // do something meaningful on server
    console.log('request is', this.request);
  }
}

并注入为external值:

app.get('*', (req: Request, res) => {
  // construct the server side service instance
  const myService = new MyServiceForServer(req);
  // render server side with service instance provided
  res.render(join(DIST_FOLDER, 'browser', 'index.html'), {
    providers: [{provide: MY_SERVICE, useValue: myService}],
    req
  });
});

在这种情况下,请确保您的MY_SERVICE令牌是纯字符串,而不是InjectionToken实例。

答案 1 :(得分:0)

在package.json中设置版本:

"@types/express": "^4.17.4",
"@types/node": "^12.11.1",