Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Bug] Cannot read properties of undefined setItem when set theme from NgDocThemeService in SSR #126

Open
runyasak opened this issue Jan 10, 2024 · 4 comments
Assignees
Labels
Type: Bug Something isn't working

Comments

@runyasak
Copy link

Is this a regression?

Yes

Description

Overview

Hi, I'm using ng-doc to create my docs. I saw an error about setItem when set the theme with NgDocThemeService in SSR pre-render.

For now, my docs is working properly. But I'm curious about the error that appeared, I'm not sure whether I missed some configs or not.

Actually, I have been looking at the source code. setItem is from localStorage, used by NgDocThemeService. It would appear an error on the server side from my usage.

Expected Result

The error should not show when set the theme in SSR.
Or, my code should be updated to suitable config.

Screenshots

Screen Shot 2567-01-10 at 22 16 10


If you have any feedback or suggestions, please let me know.

Please provide the exception or error you saw

ERROR TypeError: Cannot read properties of undefined (reading 'setItem')
    at _NgDocStoreService.set (/my-projects/ng-storefront-ui/node_modules/.pnpm/@ng-doc+app@17.2.0_@angular+cdk@17.0.4_@angular+common@17.0.8_@angular+core@17.0.8_@angular+r_jewa6hzv5ukvdufdlw67bo7hna/node_modules/@ng-doc/app/fesm2022/ng-doc-app-services-store.mjs:10:30)
    at _a7.eval (/my-projects/ng-storefront-ui/node_modules/.pnpm/@ng-doc+app@17.2.0_@angular+cdk@17.0.4_@angular+common@17.0.8_@angular+core@17.0.8_@angular+r_jewa6hzv5ukvdufdlw67bo7hna/node_modules/@ng-doc/app/fesm2022/ng-doc-app-services-theme.mjs:83:28)
    at Generator.next (<anonymous>)
    at eval (/my-projects/ng-storefront-ui/.angular/vite-root/docs/chunk-P3VG54SN.mjs:91:61)
    at ZoneAwarePromise (/my-projects/ng-storefront-ui/node_modules/.pnpm/zone.js@0.14.2/node_modules/zone.js/fesm2015/zone-node.js:1342:21)
    at Module.__async (/my-projects/ng-storefront-ui/.angular/vite-root/docs/chunk-P3VG54SN.mjs:75:10)
    at _a7.set (/my-projects/ng-storefront-ui/node_modules/.pnpm/@ng-doc+app@17.2.0_@angular+cdk@17.0.4_@angular+common@17.0.8_@angular+core@17.0.8_@angular+r_jewa6hzv5ukvdufdlw67bo7hna/node_modules/@ng-doc/app/fesm2022/ng-doc-app-services-theme.mjs:69:30)
    at _AppComponent (/my-projects/ng-storefront-ui/projects/docs/src/app/app.component.ts:27:23)
    at NodeInjectorFactory.AppComponent_Factory (/my-projects/ng-storefront-ui/projects/docs/src/app/app.component.ts:28:3)
    at getNodeInjectable (/my-projects/ng-storefront-ui/node_modules/.pnpm/@angular+core@17.0.8_rxjs@7.8.0_zone.js@0.14.2/node_modules/@angular/core/fesm2022/core.mjs:4391:44)
ERROR TypeError: Cannot read properties of undefined (reading 'setItem')
    at _NgDocStoreService.set (/my-projects/ng-storefront-ui/node_modules/.pnpm/@ng-doc+app@17.2.0_@angular+cdk@17.0.4_@angular+common@17.0.8_@angular+core@17.0.8_@angular+r_jewa6hzv5ukvdufdlw67bo7hna/node_modules/@ng-doc/app/fesm2022/ng-doc-app-services-store.mjs:10:30)
    at _a7.eval (/my-projects/ng-storefront-ui/node_modules/.pnpm/@ng-doc+app@17.2.0_@angular+cdk@17.0.4_@angular+common@17.0.8_@angular+core@17.0.8_@angular+r_jewa6hzv5ukvdufdlw67bo7hna/node_modules/@ng-doc/app/fesm2022/ng-doc-app-services-theme.mjs:83:28)
    at Generator.next (<anonymous>)
    at eval (/my-projects/ng-storefront-ui/.angular/vite-root/docs/chunk-P3VG54SN.mjs:91:61)
    at ZoneAwarePromise (/my-projects/ng-storefront-ui/node_modules/.pnpm/zone.js@0.14.2/node_modules/zone.js/fesm2015/zone-node.js:1342:21)
    at Module.__async (/my-projects/ng-storefront-ui/.angular/vite-root/docs/chunk-P3VG54SN.mjs:75:10)
    at _a7.set (/my-projects/ng-storefront-ui/node_modules/.pnpm/@ng-doc+app@17.2.0_@angular+cdk@17.0.4_@angular+common@17.0.8_@angular+core@17.0.8_@angular+r_jewa6hzv5ukvdufdlw67bo7hna/node_modules/@ng-doc/app/fesm2022/ng-doc-app-services-theme.mjs:69:30)
    at _AppComponent (/my-projects/ng-storefront-ui/projects/docs/src/app/app.component.ts:27:23)
    at NodeInjectorFactory.AppComponent_Factory (/my-projects/ng-storefront-ui/projects/docs/src/app/app.component.ts:28:3)
    at getNodeInjectable (/my-projects/ng-storefront-ui/node_modules/.pnpm/@angular+core@17.0.8_rxjs@7.8.0_zone.js@0.14.2/node_modules/@angular/core/fesm2022/core.mjs:4391:44)

OS

Unix (Linux, macOS, etc.)

Browser

Chrome

Node version

v18.19.0

Anything else?

// app.component.ts
import { NgDocThemeService } from '@ng-doc/app';
import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  standalone: true,
  templateUrl: './app.component.html',
})
export class AppComponent {
  constructor(protected readonly themeService: NgDocThemeService) {
    this.themeService.set('custom-theme');
  }
}
// app.config.ts
import {
  provideNgDocApp,
  provideSearchEngine,
  NgDocDefaultSearchEngine,
  providePageSkeleton,
  NG_DOC_DEFAULT_PAGE_SKELETON,
  provideMainPageProcessor,
  NG_DOC_DEFAULT_PAGE_PROCESSORS,
} from '@ng-doc/app';
import { NG_DOC_ROUTING, provideNgDocContext } from '@ng-doc/generated';
import { provideHttpClient, withFetch, withInterceptorsFromDi } from '@angular/common/http';
import { provideAnimations } from '@angular/platform-browser/animations';
import { ApplicationConfig } from '@angular/core';
import { provideRouter, withInMemoryScrolling } from '@angular/router';

import { routes } from './app.routes';
import { provideClientHydration } from '@angular/platform-browser';

export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(routes),
    provideClientHydration(),
    provideAnimations(),
    provideHttpClient(withInterceptorsFromDi(), withFetch()),
    provideRouter(
      NG_DOC_ROUTING,
      withInMemoryScrolling({ scrollPositionRestoration: 'enabled', anchorScrolling: 'enabled' })
    ),
    provideNgDocContext(),
    provideNgDocApp({
      themes: [
        {
          // Your theme ID that you can use to enable theme
          id: 'custom-theme',
          // Path to the theme, that will be used by NgDoc to load theme
          path: 'assets/themes/custom-theme.css',
        },
      ],
      defaultThemeId: 'custom-theme',
    }),
    provideSearchEngine(NgDocDefaultSearchEngine),
    providePageSkeleton(NG_DOC_DEFAULT_PAGE_SKELETON),
    provideMainPageProcessor(NG_DOC_DEFAULT_PAGE_PROCESSORS),
  ],
};
// angular.json
{
  "projects": {
    "docs": {
      "projectType": "application",
      "schematics": {
        "@schematics/angular:component": {
          "prefix": "docs",
          "standalone": true,
          "changeDetection": "OnPush",
          "style": "none",
          "inlineTemplate": true,
          "skipTests": true,
          "flat": true
        }
      },
      "root": "projects/docs",
      "sourceRoot": "projects/docs/src",
      "prefix": "app",
      "architect": {
        "build": {
          "builder": "@ng-doc/builder:application",
          "options": {
            "outputPath": "dist/docs",
            "index": "projects/docs/src/index.html",
            "browser": "projects/docs/src/main.ts",
            "polyfills": [
              "zone.js"
            ],
            "tsConfig": "projects/docs/tsconfig.app.json",
            "assets": [
              {
                "glob": "**/*",
                "input": "node_modules/@ng-doc/app/assets",
                "output": "assets/ng-doc/app"
              },
              {
                "glob": "**/*",
                "input": "node_modules/@ng-doc/ui-kit/assets",
                "output": "assets/ng-doc/ui-kit"
              },
              {
                "glob": "**/*",
                "input": "ng-doc/docs/assets",
                "output": "assets/ng-doc"
              },
              "projects/docs/src/favicon.ico",
              "projects/docs/src/assets"
            ],
            "styles": [
              "node_modules/@ng-doc/app/styles/global.css",
              "node_modules/highlight.js/styles/github-dark.min.css",
              "projects/docs/src/styles.css"
            ],
            "scripts": [],
            "server": "projects/docs/src/main.server.ts",
            "prerender": true,
            "ssr": {
              "entry": "projects/docs/server.ts"
            },
            "allowedCommonJsDependencies": [
              "@ng-doc/core"
            ]
          },
          "configurations": {
            "production": {
              "budgets": [
                {
                  "type": "initial",
                  "maximumWarning": "500kb",
                  "maximumError": "1mb"
                },
                {
                  "type": "anyComponentStyle",
                  "maximumWarning": "2kb",
                  "maximumError": "4kb"
                }
              ],
              "outputHashing": "all"
            },
            "development": {
              "optimization": false,
              "extractLicenses": false,
              "sourceMap": true
            }
          },
          "defaultConfiguration": "production"
        },
        "serve": {
          "builder": "@ng-doc/builder:dev-server",
          "configurations": {
            "production": {
              "buildTarget": "docs:build:production"
            },
            "development": {
              "buildTarget": "docs:build:development"
            }
          },
          "defaultConfiguration": "development"
        },
        "extract-i18n": {
          "builder": "@angular-devkit/build-angular:extract-i18n",
          "options": {
            "buildTarget": "docs:build"
          }
        },
        "test": {
          "builder": "@angular-devkit/build-angular:karma",
          "options": {
            "polyfills": [
              "zone.js",
              "zone.js/testing"
            ],
            "tsConfig": "projects/docs/tsconfig.spec.json",
            "assets": [
              "projects/docs/src/favicon.ico",
              "projects/docs/src/assets"
            ],
            "styles": [
              "projects/docs/src/styles.css"
            ],
            "scripts": []
          }
        }
      }
    }
  }
}

You can see my code from this repo:
https://github.com/runyasak/ng-storefront-ui/tree/docs/projects/docs

@runyasak runyasak added the Type: Bug Something isn't working label Jan 10, 2024
@skoropadas
Copy link
Member

Hey thanks for the ticket and link to the repo I'll try to check it today

@skoropadas
Copy link
Member

skoropadas commented Jan 11, 2024

Okay, I looked at the repository. The first thing I didn't quite understand is why you manually call this.themeService.set('custom-theme'); while passing defaultThemeId. In any case, on the server side, this won't work. You need to call this function inside the afterRender function so that it is only called on the client side:

constructor(protected readonly themeService: NgDocThemeService) {
    afterRender(() => this.themeService.set('custom-theme'));
}

I encountered this issue in the application initialization function when NgDoc tries to determine which theme to load for the application based on the value in localStorage. Since afterRender cannot be called inside a provider, I resorted to a small hack.

However, I want to explore this issue in the future and fix it because during a quick page refresh, you may notice that the application briefly had the default theme and then switched to the one set in localStorage or using defaultThemeId.

You can also completely abandon NgDocThemeService and simply import the theme into your styles.css without additional registrations or sharing using the assets folder, and change the theme using CSS classes.

the theme looks sick btw. :)

@runyasak
Copy link
Author

Hi @skoropadas, thank you for your help.

However, I want to explore this issue in the future and fix it because during a quick page refresh, you may notice that the application briefly had the default theme and then switched to the one set in localStorage or using defaultThemeId.

The reason that I set the theme manually is to fix the glitch while the theme is shifting on page refresh.
I've passed with defaultThemeId at first but it didn't work.
Then, I tried to set the theme manually, it worked like magic but an error appeared.

For now, I will override the theme by CSS or simply styles.css as your suggestion.

However, I'm looking forward to set the theme with config in SSR or pre-render.

Thank you so much, for your help.
Please let me know if you have more suggestions for me.


FYI. As far, as I've tried many libraries in Angular. I think ng-doc is the best Angular library for creating docs at this moment. 😂

@skoropadas
Copy link
Member

Thank you! :) Yes, the glitch occurs because Angular pre-renders the page with the default theme. After that, on the client side, scripts are loaded that perform hydration, fetch the theme set in localStorage, and change it.

At the moment, it seems to me that it's not entirely possible to fix this in the current implementation because the delay will still occur due to loading the theme via HTTP. Since the themes are lightweight due to the ability to configure everything with CSS variables, I think it makes sense to switch to CSS classes. Therefore, importing the theme in styles.css is a good solution.

I will address this later since I wanted to slightly expand the theme functionality, adding the ability to set settings for the application, such as settings for the header or sidebar, to implement fullscreen themes, for example.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Type: Bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants