mean-expert-official / loopback-sdk-builder

Tool for auto-generating Software Development Kits (SDKs) for LoopBack
Other
399 stars 175 forks source link

TypeError: Cannot read property 'cookies' of undefined - Angular 2 Universal #512

Open benlegault opened 7 years ago

benlegault commented 7 years ago

What type of issue are you creating?

What version of this module are you using?

Write other if any: I'm using Angular 2 (with angular-cli) Universal

Please add a description for your issue:

Hi, I'm trying to get my Angular 2 project to work in Universal mode. I've read some issues here and it seems that I should be able to get this working. Although I am not sure if my setup is fine. I'm using: 1- Angular-cli, setup as a univesal project (works fine, it's a basic project, nothing in it and it compiles without any issue). 2- I have a loopback server and.. 3- Loopback-sdk-builder, version 2.1.0-rc.13.5

My understanding is that in an Angular Universal Project, I must use SDKNodeModule has the library. I've created a basic find() on a model but when I call it, I get the following error:

TypeError: Cannot read property 'cookies' of undefined screen shot 2017-10-12 at 09 25 34

I know that it's a pretty particular setup using Angular Universal but according to the docs, it should work? So I need some help on this.

Thank you for your great help!!!!

benlegault commented 7 years ago

Just to add more to this issue...basically, I'm not sure how to setup this for angular universal. My project works pretty well with SDKBrowserModule, in a non-universal environment. But when using it in universal environment, I believe that I must use SDKNodeModule. When I switch environment, I get the above error. I'm pretty sure that it's a configuration mistake but If I could get an example on how to setup this (similar to Angular 2 in native mode) would be really great. This error is related to the following line. What am I missing? capture d ecran 2017-10-18 a 15 24 18

djabif commented 6 years ago

@benlegault did you solve this?

agustinhaller commented 6 years ago

@jonathan-casarrubias @JonnyBGod I'm also experiencing this issue when upgrading to Angular 5. https://github.com/mean-expert-official/loopback-sdk-builder/blob/5d41f36fd6934f5dca4c70ba231fa5691da47450/lib/angular2/shared/storage/cookie.node.ts#L22 Sorry to bother you guys, but this simple issue is the only thing that's preventing this awesome project to work as expected in Angular 5.

Do you know what may be the issue/solution here? Do you think it's the way Zone is declared?

I don't understand why in https://github.com/mean-expert-official/loopback-sdk-builder/blob/5d41f36fd6934f5dca4c70ba231fa5691da47450/lib/angular2/shared/sockets/connections.ts#L50 you inject the zone and in the cookie.node.ts file you declare it differently.

Or maybe the issue is that the request object should be injected "the angular way" instead of trying to get it from the Zone. Here's something that may fix the issue:

https://github.com/angular/universal-starter/issues/271#issuecomment-318810979

app.engine('html',  (_, options, callback) => {
  let engine = ngExpressEngine({
    bootstrap: ServerAppModule,
    providers: [ { provide: 'request', useFactory: () => options.req } ]
  });

  engine(_, options, callback)
})

and

import { Inject, Injector, PLATFORM_ID } from '@angular/core';
import { isPlatformServer } from '@angular/common';
...
constructor(..., private injector: Injector, @Inject(PLATFORM_ID) private platformId: Object) {
  if (isPlatformServer(this.platformId)) {
    console.log(this.injector.get('request'))
  } else {
    console.log('we\'re rendering from the browser, so there is no request object.');
  }
}

What do you think guys? Can you point me in the right direction?

agustinhaller commented 6 years ago

UPDATE ...

I tried to inject the NgZone, but it doesn't have the .current property as it seems to be something different as the zone that's used in the sockets/connections.ts file.

I also tried the other approach, both providing the options.req object from ngExpressEngine:

app.engine('html',  (_, options, callback) => {
    const engine = ngExpressEngine({
      bootstrap: serverModule,
      providers: [
        { provide: 'request', useFactory: () => options.req, deps: [] }
      ]
    });

    engine(_, options, callback);
  });

And injecting the req object from Zone.current:

app.engine('html',  (_, options, callback) => {
    const engine = ngExpressEngine({
      bootstrap: serverModule,
      providers: [
        { provide: 'request', useFactory: () => Zone.current.get('req') || {}, deps: [] }
      ]
    });

    engine(_, options, callback);
  });

In both cases I solved the TypeError: Cannot read property 'cookies' of undefined error, but got a different one: ERROR { Error: Uncaught (in promise): TypeError: Cannot read property '$LoopBackSDK$id' of undefined. I think this error is tied to the auth service implementation of the SDK: https://github.com/mean-expert-official/loopback-sdk-builder/blob/e2e46cecfc74b43ebd0efdd7fead36dd6ae3d51a/lib/angular2/shared/services/core/auth.ts#L24

Any hints on how to fix this?

jonathan-casarrubias commented 6 years ago

Hey guys thanks for reaching out, not sure what is causing this issue.... @agustinhaller I think you was able to walk through the typing error but it seems that there is actually nothing returned, because for me it sounds like the SDK is trying to get the data from an undefined cookie.

I'm a little overwhelmed since I have a lot of issues on the line to address, so I don't think i will be able to check this issue in the following days, but....

I think you might be able to help by reviewing the current Angular Universal examples, that is how I implemented a while ago, is just matter of verify how they get the backend cookie and implement it within the cookie.node.ts etc

If you guys find how to fix it and send a PR I will publish that ASAP so you can get your projects going.

Regards Jon

agustinhaller commented 6 years ago

Hi @jonathan-casarrubias! Thanks for the quick reply.

I reviewed the implementation and the request object is there, but the cookies aren't. I checked the universal-starter repo and they removed the cookie-parser dependency when migrating from angular 2 to angular 4/5. That seems to be the root of the issue. I need to deploy my project to production ASAP and I have just one view that depends on the state of the user, so I'm forced to delay fixing the root of the problem for now. If I found the solution I will do the PR ASAP.

blue-cp commented 6 years ago

I am facing the same issue. @agustinhaller were you able to resolve the issue?

SpeedoPasanen commented 6 years ago

Same issue. Got rid of the error by wrapping the engine call in Zone.current.fork with req & res.

Req never seems to have cookies at this point though, so we always pass an empty object. If anyone can tell us how to access cookies properly at this stage, would be golden. In my current use case it doesn't matter, no need to prerender auth-guarded views, but at some point or to someone else it might be an issue.

I do this in boot/universal.js but I'm sure you can adapt it to server.js too if you want to do it there.

'use strict';
require('zone.js/dist/zone-node');
const express = require('express');
const ngUniversal = require('@nguniversal/express-engine');
const path = require('path');
const fs = require('fs');
const { enableProdMode } = require('@angular/core');
module.exports = function (server, cb) {
    enableProdMode();
    const loopback = server.loopback;
    const distPath = path.join(__dirname, '..', '..', 'client-server'); // Server build
    const distClientPath = path.join(__dirname, '..', '..', 'client-client'); // Browser build for views
    // I want to keep those hashes, cause hashes are phun.
    const hash = fs.readdirSync(path.resolve(distPath))
        .filter(file => file.startsWith('main'))
        .pop()
        .split('.')[1]; 
    const appServer = require(path.join(distPath, 'main.' + (hash === 'bundle' ? '' : hash + '.') + 'bundle'));

    server.engine('html', (_, options, callback) => {
        const engine = ngUniversal.ngExpressEngine({
            bootstrap: appServer.AppServerModuleNgFactory,
            providers: [ ]
        });
        Zone.current.fork({
            name: 'universal',
            properties: {
                req: Object.assign({ cookies: {} }, options.req),
                res: options.res,
            }
        }).run(async () => {
            await engine(_, options, callback);
        });
    });

    server.set('view engine', 'html');
    server.set('view options', { doctype: 'html' });
    server.set('views', distClientPath);
    server.use(loopback.static(distClientPath, { index: false }));
    server.middleware('routes', (req, res, next) => {
        res.render('index', { req, res });
    });
    return cb();
}
SergeNarhi commented 6 years ago

Same problem. Deps: "@mean-expert/loopback-sdk-builder": "~2.3.1"

Angular CLI: 6.0.8
Node: 8.11.2
OS: darwin x64
Angular: 6.0.4
... animations, common, compiler, compiler-cli, core, forms
... http, language-service, platform-browser
... platform-browser-dynamic, platform-server, router

Package                           Version
-----------------------------------------------------------
@angular-devkit/architect         0.6.8
@angular-devkit/build-angular     0.6.8
@angular-devkit/build-optimizer   0.6.8
@angular-devkit/core              0.6.8
@angular-devkit/schematics        0.6.8
@angular/cdk                      6.2.1
@angular/cli                      6.0.8
@angular/flex-layout              6.0.0-beta.16
@angular/material                 6.2.1
@ngtools/webpack                  6.0.8
@schematics/angular               0.6.8
@schematics/update                0.6.8
rxjs                              6.2.0
typescript                        2.7.2
webpack                           4.8.3

Fixed by combination of answers from https://github.com/angular/universal-starter/issues/271

server.ts

// ...
app.engine('html', (_, options, callback) => {
  let engine = ngExpressEngine({
                                 bootstrap: AppServerModuleNgFactory,
                                 providers: [
                                   provideModuleMap(LAZY_MODULE_MAP),
                                   {
                                     provide: 'request',
                                     useFactory: () => Object.assign({ cookies: {} }, options.req),
                                     deps: [],
                                   },
                                   { provide: 'response', useFactory: () => options.req.res, deps: [] },
                                 ],

                               });

  engine(_, options, callback);
});
// ...

cookie.node.ts

// ...
export class CookieNode {

constructor(private injector: Injector) { }
  get(key: string) {
    let cookies: { [ key: string ]: number } = this.injector.get('request').cookies;
    return cookies[ key ];
  }

  set(key: string, value: any): any {
    this.injector.get('response')
        .cookies(key, value)
        .send('Cookie is set');
  }

  remove(key: string, value: any): any {
    this.injector.get('response')
        .cookies(key, '; expires=Thu, 01 Jan 1970 00:00:01 GMT;')
        .send('Cookie is removed');
  }

}

@jonathan-casarrubias pr coming soon.