Search code examples
typescriptvue-class-components

How to properly initialise property of the class considering that it's value will be assigned later dynamically?


Below is the Vue component class with an event listeners that can receive an instance of service worker at some point of time and save it under registration property.

The problem I experience is that even doing:

if (this.registration && this.registration.waiting) {
  this.registration.waiting.postMessage(SERVICE_WORKER_MESSAGES.skipWaiting)
}

TypeScript communicates that this.registration is TS2571: Object is of type 'unknown'. which makes sense because of: private registration: unknown

I would really appreciate if somebody could suggest what is a proper way of initialising and access properties that can be defined later?

P.S: The actual type of registration is ServiceWorkerRegistration

Class implementation:

export default class Core extends Vue {
  private refreshing = false
  private updateExists = false
  private registration: unknown

  updateApp (): void {
    this.updateExists = false

    // this.registration --> TS2571: Object is of type 'unknown'.
    if (this.registration && this.registration.waiting) {
      //                     ~~~~~~~~~~~~~~~~~  

      // this.registration --> TS2571: Object is of type 'unknown'.
      this.registration.waiting.postMessage(SERVICE_WORKER_MESSAGES.skipWaiting)
      // ~~~~~~~~~~~~~~
    }
  }

  addEventListeners () {
    document.addEventListener(SERVICE_WORKER_EVENTS.updated, ((event: CustomEvent) => {
      this.registration = event.detail /* store the ServiceWorkerRegistration instance for later use. */
      this.updateExists = true
    }) as EventListener,
    { once: true })
  }

  created () {
    this.addEventListeners()

    /*
    Refresh all open app tabs when a new service worker is installed */
    navigator.serviceWorker.addEventListener(`controllerchange`, () => {
      if (!this.refreshing) {
        this.refreshing = true
        window.location.reload()
      }
    })
  }
}

tsconfig.json

{
  "compilerOptions": {
    "target": "esnext",
    "module": "esnext",
    "strict": true,
    "jsx": "preserve",
    "importHelpers": true,
    "moduleResolution": "node",
    "experimentalDecorators": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "allowJs": true,
    "sourceMap": true,
    "noImplicitThis": true,
    "baseUrl": ".",
    "types": [
      "webpack-env",
      "jest"
    ],
    "paths": {
      "@/*": [
        "src/*"
      ]
    },
    "lib": [
      "esnext",
      "dom",
      "dom.iterable",
      "scripthost"
    ]
  },
  "include": [
    "src/**/*.ts",
    "src/**/*.tsx",
    "src/**/*.vue",
    "tests/**/*.ts",
    "tests/**/*.tsx"
  ],
  "exclude": [
    "node_modules"
  ]
}

Solution

  • Since registration is not initialized when the instance is created it can be undefined.

    You can mark it as optional:

    private registration?: ServiceWorkerRegistration
    

    Or explicitly specify that it can be undefined:

    private registration: ServiceWorkerRegistration | undefined
    

    Using non-null assertion operator is actually a bad idea because it will allow accessing this.registration properties without checking if it is defined/initialized or not.