Using cookies in Universal Rendering using angular-cli
Asked Answered
S

2

12

I have made an angular app using angular-cli and doing universal rendering in it by following steps given here . But it is mentioned that i can't use Global variables like window and document in my app if i am doing universal rendering, How can i set cookies at front end to maintain user session. Previously i was using JWT, but for doing that i need to set cookies in my browser.

Sulphathiazole answered 18/8, 2017 at 6:48 Comment(0)
E
15

Note: All the information below is only valid if you're using Node with Express to dynamically render (at the time a request hits the server) your Angular app on the server. I haven't looked into pre-rendering. And I don't know how you would go about it if you'd be using a framework other than Express, like Hapi or whatever other frameworks are out there. I can only guess that @nguniversal should offer a solution for those as well, but haven't looked into it.

That being said, here's the way I've just managed to make cookies available in my app when it is rendered server side:

app.server.module.ts:

// ...
import { Request } from 'express';
import { REQUEST } from '@nguniversal/express-engine/tokens';

@Injectable()
export class RequestCookies {
    constructor(@Inject(REQUEST) private request: Request) {}

    get cookies() {
        return !!this.request.headers.cookie ? this.request.headers.cookie : null;
    }
}

@NgModule({
    imports: [
        // ...
    ],
    providers: [
        CookieService,
        { provide: 'req', useClass: RequestCookies }
    ],
    bootstrap: [AppComponent]
})
export class AppServerModule {}

app.module.ts: (some might call it app.browser.module.ts)

@NgModule({
    declarations: [
        // ...
    ],
    imports: [
        // ...
    ],
    providers: [
        CookieService,
        {
            provide: 'req',
            useValue: null
        }
    ],
    bootstrap: [AppComponent]
})
export class AppModule {}

cookie.service.ts:

import { Inject, Injectable } from '@angular/core';


@Injectable()
export class CookieService {
    private cookieStore = {};

    constructor(
        @Inject('req') private readonly req: any,
    ) {
        if (this.req !== null) {
            this.parseCookies(this.req.cookies);
        } else {
            this.parseCookies(document.cookie);
        }
    }

    public parseCookies(cookies) {
        this.cookieStore = {};
        if (!!cookies === false) { return; }
        let cookiesArr = cookies.split(';');
        for (const cookie of cookiesArr) {
            const cookieArr = cookie.split('=');
            this.cookieStore[cookieArr[0]] = cookieArr[1];
        }
    }

    get(key: string) {
        return !!this.cookieStore[key] ? this.cookieStore[key] : null;
    }
}

any.component.ts:

// ...
constructor(
    private cookieService: CookieService
) {}

needMyCookie() {
    const userCookie = this.cookieService.get('user');
}

Make sure you > npm install express @nguniversal/express-engine

P.S. I started by looking at this repo: https://github.com/angular/universal-starter/tree/master/cli for the server-side-rendering, only did the cookie part myself (wasn't included there). That repo follows the universal story from your link but goes beyond. If you simply follow the universal story, it says:

Lazy loading is not yet supported

But if you try the repo I gave the link to, you'll discover that lazy loaded modules ARE RENDERED on the server also! I don't know what the key is, maybe it has to do with the code in the server.js file and @nguniversal/module-map-ngfactory-loader

I've tried it with: CLI version 1.4.2 and Angular version: 4.4.1

This is what I get with javascript disabled in the browser:

Home route - JavaScript disabled in the browser Lazy route - JavaScript disabled in the browser

My carousel items are spaced like that because they're actually scaled to 90%. I have a zoomIn animation on the item in onInit. And on the server-rendered view, I force-style them to 90% so they won't appear at 100% on the first load and then, after the client kicks in, have them animate 90 to 100.

Also, if anybody comes across this answer and notices a reference to the viewport width in the screenshot, in the codeblock below the main navigation links - in case they're wondering - this is my WindowService:

import { Injectable, PLATFORM_ID, Inject } from '@angular/core';
import { isPlatformBrowser, isPlatformServer } from '@angular/common';

@Injectable()
export class WindowService {

    constructor(
        @Inject(PLATFORM_ID) private readonly platformId: any,
    ) {}

    get width() {
        if (isPlatformBrowser(this.platformId)) {
            return window.innerWidth;
        } else {
            return 'Not available server-side!';
            // return null;
        }
    }

}

Simply add it to the providers: [] array in both app.module.ts and app.server.module.ts.

Another thing to be noted here is that I'm using Node with Express. I don't know about doing this if you're using other frameworks, like Hapi or whatever other frameworks that are out there.

Emblazonry answered 17/9, 2017 at 16:25 Comment(6)
Nice implementation, but i get the error:No provider for InjectionToken REQUEST Do you have an idea about this?Earlearla
@OkanAslankan Do you have the code publicly accessible somewhere? On my end, it still works exactly as I've posted the code above. The only difference is that I added the @Injectable() decorator to the RequestCookies class (I've also updated the answer)... But that wouldn't be needed unless you'd want to directly inject that class in another service, plus I think that the error would sound different.Emblazonry
Also, you can check out this example, maybe it helps: github.com/patrickmichalina/fusebox-angular-universal-starter/… Looking through Patrick's examples helped me a few times as well and realised some things I wasn't even thinking about.Emblazonry
I resolved it by manually injecting it in constructor. Saw a couple of issues about it in github about this issue. Thank youEarlearla
I tried and it doesn't work, here is the repo github.com/intergleam/universal-starter/tree/…Countercurrent
console.log('token',this.cookieService.get('token')); gives undefined in node console, although the cookie token is thereCountercurrent
M
0

I have had success using the npm package ng2-cache (I am using Angular 4 and it works just fine). I am using the local storage, so I have it in my module providers array like this:

providers: [
    CacheService,
    {provide: CacheStorageAbstract, useClass: CacheLocalStorage},
    ...
]

Another option is to use isPlatformBrowser() as shown here.

The ugliest option is to just put your code dealing with cookies in try-catch statements. That way it will work on the browser and be ignored on the server side.

Meek answered 22/8, 2017 at 20:44 Comment(4)
Personally, I haven't tried ng2-cache yet. But I don't think ignoring cookies server-side should be an option. The thing is most times we need to get at least a token from the cookie in order to have authorization and/or show data based on the user, right in the first render coming from the server.Emblazonry
so what is the option for auth token currently?Countercurrent
Since this question was previously asked, server side rendering has greatly been expanded. There are many documented solutions on how to make it work, but it really depends on your stack.Meek
can you please modify github.com/angular/universal-starter to show how to use cookies?Countercurrent

© 2022 - 2024 — McMap. All rights reserved.