sascha245 / vue-typedi

Use typedi injections in Vue components
MIT License
17 stars 2 forks source link

Nuxt support #2

Open sobolevn opened 5 years ago

sobolevn commented 5 years ago

I have this page component in nuxt:

@Component({})
export default class Index extends mixins(TypedStoreMixin) {
  fetch ({ store }: { store }): Promise<RawCommentType[]> {
    // Here we don't have a DI setup yet, so we use the explicit approach:
    const typedStore = useStore<TypedStore>(store)
    return typedStore.comments.fetchComments()
  }

  fetchComments () {
    this.typedStore.comments.fetchComments()
  }
}

When I use fetch method from Nuxt I see this error:

Cannot determine a class of the requesting service "function TestService() {}"
Снимок экрана 2019-06-23 в 15 29 50

But, if fetch is not used and I instead use fetchComments method on button click - it works fine.

I guess the reason is that nuxt handles fetch differently and something is not setup properly.

sobolevn commented 5 years ago

Here's my module definition:

import 'reflect-metadata'
import { Action, Mutation, State, Getter } from 'vuex-simple'
import { Container, Service } from 'vue-typedi'

import { CommentType, CommentPayloadType } from '~/logic/comments/types'
import { RawCommentType } from '~/logic/comments/models'
import CommentService from '~/logic/comments/services/api'

@Service()
class TestService {
  public vas (): number {
    return 1
  }
}

export default class CommentsModule {
  // State

  @State()
  public comments: CommentType[] = []

  // Getters

  @Getter()
  public get hasComments (): boolean {
    return Boolean(this.comments && this.comments.length > 0)
  }

  // Mutations

  @Mutation()
  public setComments (payload: RawCommentType[]): void {
    const updatedComments: CommentType[] = []

    for (const comment of payload.slice(0, 10)) {
      // We transform RawCommentType in CommentType here:
      updatedComments.push({ ...comment, 'rating': 0 })
    }

    this.comments = updatedComments
  }

  @Mutation()
  public updateRating ({ commentId, delta }: CommentPayloadType): void {
    if (!this.comments) return

    const commentIndex = this.comments.findIndex((comment): boolean => {
      return comment.id === commentId
    })

    if (!this.comments || !this.comments[commentIndex]) return

    this.comments[commentIndex].rating += delta
  }

  // Actions

  @Action()
  public async fetchComments (): Promise<RawCommentType[]> {
    console.log(Container.get(TestService), Container.get(TestService).vas())
    const service = Container.get(CommentService)
    const commentsList = await service.fetchComments()
    this.setComments(commentsList)
    return commentsList
  }
}

My typed store:

import { Module } from 'vuex-simple'

// TODO: document

import CommentsModule from '~/logic/comments/module'

export default class TypedStore {
  @Module()
  public comments = new CommentsModule()
}

And store from nuxt:

// We use default Nuxt Module-based store,
// read more about it here:
// https://nuxtjs.org/guide/vuex-store

// TODO: document

import Vue from 'vue'
import Vuex from 'vuex'
import { createVuexStore } from 'vuex-simple'
import { Container } from 'vue-typedi'

import TypedStore from '~/logic/store'
import tokens from '~/logic/tokens'

Vue.use(Vuex)

// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
export default function store () {
  const typedStore = new TypedStore()

  // Registering DI container items:
  Container.set(tokens.STORE, typedStore)
  Container.set(tokens.COMMENTS, typedStore.comments)

  return createVuexStore(typedStore, {
    'strict': false,
    'modules': {},
    'plugins': [],
  })
}
sobolevn commented 5 years ago

Here's the fix for this case: https://github.com/typestack/typedi/issues/99

sobolevn commented 5 years ago

The next problem is that Reflect.getMetadata returns undefined here: https://github.com/typestack/typedi/blob/ba2d73f245ec92f8d67538e2409ec55d2c5cdb7d/src/decorators/Inject.ts#L42

For some reason Reflect.getMetadata("design:type", target, propertyName) does not work. Maybe metadata is not transferred?

sobolevn commented 5 years ago

This is what I get:

Снимок экрана 2019-06-23 в 19 35 22
sobolevn commented 5 years ago

Code:

import CommentService from '~/logic/comments/services/api'

@Injectable()
export default class CommentsModule {
  // Dependencies

  @Inject()
  public service!: CommentService
  // ...
}
sobolevn commented 5 years ago

The thing is that plugins are called after new store is created in nuxt:

import 'reflect-metadata'
import { AxiosInstance } from 'axios'
import Vue, { VueConstructor } from 'vue'
import VueTypeDI, { Container } from 'vue-typedi'

import tokens from '~/logic/tokens'

export function install (
  vueConstructor: VueConstructor,
  $axios: AxiosInstance,
): void {
  vueConstructor.use(VueTypeDI)
  Container.remove(tokens.AXIOS)
  Container.set(tokens.AXIOS, $axios)
}

/* istanbul ignore next */
export default ({ $axios }): void => {
  install(Vue, $axios)
}
sobolevn commented 5 years ago

And store.ts:

// We use default Nuxt Module-based store,
// read more about it here:
// https://nuxtjs.org/guide/vuex-store

// TODO: document

import Vue from 'vue'
import Vuex from 'vuex'
import { createVuexStore } from 'vuex-simple'
import { Container } from 'vue-typedi'

import tokens from '~/logic/tokens'
import TypedStore from '~/logic/store'

Vue.use(Vuex)

// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
export default function store () {
  if (!Container.has(tokens.AXIOS)) {
    Container.set(tokens.AXIOS, (noop) => {})
  }

  const typedStore = new TypedStore()

  // Registering DI container items:
  Container.set(tokens.STORE, typedStore)

  return createVuexStore(typedStore, {
    'strict': false,
    'modules': {},
    'plugins': [],
  })
}

Service:

import { AxiosInstance } from 'axios'
import { Service, Container } from 'vue-typedi'
import * as ts from 'io-ts'
import * as tPromise from 'io-ts-promise'

import tokens from '~/logic/tokens'
import { RawComment, RawCommentType } from '~/logic/comments/models'

@Service(tokens.COMMENT_SERVICE)
export default class CommentService {
  protected get $axios (): AxiosInstance {
    return Container.get(tokens.AXIOS) as AxiosInstance
  }

  /**
   * Fetches comments from the remote API.
   *
   * @returns Parsed response data.
   */
  public async fetchComments (): Promise<RawCommentType[]> {
    // Note, that $axios has some custom methods, that are not used on purpose
    // https://github.com/nuxt-community/axios-module#-features
    const response = await this.$axios.get('comments')
    return tPromise.decode(ts.array(RawComment), response.data)
  }
}