class-transformer: fix: builds fail with Angular 5 and Angular CLI when @Type is used

After upgrading to Angular 5 and Angular CLI 1.5.0, my production builds stopped working. It took me quite some time to figure out, that using the @Type decorator from class-transformer lead to the following error message when running ng build -prod:

ERROR in Error: TypeError: Cannot read property 'kind' of undefined
    at nodeCanBeDecorated (/demo/angular5-ct/node_modules/typescript/lib/typescript.js:7805:35)
    at nodeIsDecorated (/demo/angular5-ct/node_modules/typescript/lib/typescript.js:7825:16)
    at nodeOrChildIsDecorated (/demo/angular5-ct/node_modules/typescript/lib/typescript.js:7829:16)
    at Object.forEach (/demo/angular5-ct/node_modules/typescript/lib/typescript.js:1506:30)
    at Object.childIsDecorated (/demo/angular5-ct/node_modules/typescript/lib/typescript.js:7835:27)
    at getClassFacts (/demo/angular5-ct/node_modules/typescript/lib/typescript.js:51088:20)
    at visitClassDeclaration (/demo/angular5-ct/node_modules/typescript/lib/typescript.js:51113:25)
    at visitTypeScript (/demo/angular5-ct/node_modules/typescript/lib/typescript.js:50972:28)
    at visitorWorker (/demo/angular5-ct/node_modules/typescript/lib/typescript.js:50785:24)
    at sourceElementVisitorWorker (/demo/angular5-ct/node_modules/typescript/lib/typescript.js:50817:28)
    at saveStateAndInvoke (/demo/angular5-ct/node_modules/typescript/lib/typescript.js:50738:27)
    at sourceElementVisitor (/demo/angular5-ct/node_modules/typescript/lib/typescript.js:50799:20)
    at visitNodes (/demo/angular5-ct/node_modules/typescript/lib/typescript.js:49280:48)
    at Object.visitLexicalEnvironment (/demo/angular5-ct/node_modules/typescript/lib/typescript.js:49313:22)
    at visitSourceFile (/demo/angular5-ct/node_modules/typescript/lib/typescript.js:51059:53)
    at saveStateAndInvoke (/demo/angular5-ct/node_modules/typescript/lib/typescript.js:50738:27)

As soon as I removed @Type, the build was working fine again. I have no idea where this has to be fixed, but hoped that maybe someone here knows what I can do to be able to use the latest Angular version with class-transformer.

I have created a simple demo project here: https://github.com/philippd/angular5-ct Just run ng build -prod to see the error.

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Reactions: 16
  • Comments: 30 (3 by maintainers)

Most upvoted comments

@sandcake Can be made more universal, I came to this solution (this works on angular 5):

export function serializeType<T>(object: T) {
  return function () { return object; }
}

export class CatalogItem {

  id: string;

  @Type(serializeType(CatalogCategory))
  category?: CatalogCategory = null;

  @Type(serializeType(PackingVariant))
  packing: PackingVariant;

  price: string = null;

}

From the workaround mentioned in angular/angular#20216 For now using:

class Type {
}

function getter() {
  return Type;
}

class Foo {
  @Type(getter)
  public ofType;
}

instead of :

class Type {
}

class Foo {
  @Type(() => Type)
  public ofType;
}

works

We need to wait until angular team fix this issue. For now solution to avoid this problem is following:

    @Type(forwardRef(() => Person) as any)
    author: Person;

I want to share my workaround that I put together after reading other advices here and there. It’s patching an ng script from @angular/cli@1.6.4 (current version at the moment of writing). Because of that it prevents updating @angular/cli, so be prepared.

  1. Put a patched copy of ng somewhere in your repo. You can grab patched version from here: https://github.com/kirillgroshkov/angular-cli/blob/18f14d71bc5d73dba488f8fc1d08fd46d1d885f1/packages/%40angular/cli/bin/ng

Here’s what changed, compared to the original file: https://github.com/kirillgroshkov/angular-cli/commit/18f14d71bc5d73dba488f8fc1d08fd46d1d885f1

  1. Add a postinstall script in your package.json that will run every time after you do npm install (or yarn install) and copy/overwrite ng from your location to node_modules/@angular/cli/bin/ng.
"postinstall": "echo 'patching @angular/cli' & cp ./other/ng ./node_modules/@angular/cli/bin"
  1. Run your build as normal, you should see console.log message “!!! using patched @angular/cli”.
  2. Profit

I resolved upgrading typescript to 2.7, throwing away forwardRef , and adding // @dynamic at the top of the class:

import { Expose, Type } from 'class-transformer';

import { LookupQueryDTO } from './lookup_dto';
import { LookupQueryInfo } from './lookup_query_info';

// @dynamic
export class LookupQueryComplete extends LookupQueryDTO {

	/** rootEntityName */
	@Expose()
	public rootEntityName: string;

	/** lookupQueryInfo */
	@Expose()
	@Type(() => LookupQueryInfo)
	public lookupQueryInfo: LookupQueryInfo;

}

https://github.com/angular/angular-cli/issues/8434

@gmavritsakis

I had the same issue with: Angular CLI 1.6.1 Typescript 2.4.2 Angular 5.0.2

Found a solution by changing typescript.js for now. Replace all the function function nodeCanBeDecorated(node) with the following code

function nodeCanBeDecorated(node) {
       switch (node.kind) {
           case 229 /* ClassDeclaration */:
               // classes are valid targets
               return true;
           case 149 /* PropertyDeclaration */:
               // property declarations are valid if their parent is a class declaration.
   			// return node.parent.kind === 229 /* ClassDeclaration */;
   			return (node.parent && node.parent.kind === 229) || (node.original && node.original.parent && node.original.parent.kind === 229);
           case 153 /* GetAccessor */:
           case 154 /* SetAccessor */:
           case 151 /* MethodDeclaration */:
               // if this method has a body and its parent is a class declaration, this is a valid target.
               return node.body !== undefined &&
   				// && node.parent.kind === 229 /* ClassDeclaration */;
   				(node.parent && node.parent.kind === 229) || (node.original && node.original.parent && node.original.parent.kind === 229);
           case 146 /* Parameter */:
               // if the parameter's parent has a body and its grandparent is a class declaration, this is a valid target;
               // return node.parent.body !== undefined
               //     && (node.parent.kind === 152 /* Constructor */
               //         || node.parent.kind === 151 /* MethodDeclaration */
               //         || node.parent.kind === 154 /* SetAccessor */)
   			//     && node.parent.parent.kind === 229 /* ClassDeclaration */;
   			
   			var parent = node.parent || (node.original && node.original.parent);
   			return parent && parent.body !== undefined &&
                     (parent.kind === 152
                        || parent.kind === 151
                        || parent.kind === 154) && parent.parent.kind === 229;
       }
       return false;
   }

Which comes from here, if you compile typescript: shlomiassaf/TypeScript@7017fa2

Hi, same problem here, and thanks for the workaround, it works! want to know if there’s gonna be a fix in next releases?

Also problem remain

class TransferType {
  static stringify(value: string) {
    return JSON.stringify(classToPlain(value));
  }

  static parse(value: string) {
    return plainToClass(ApplicationData, JSON.parse(value));
  }
}

export class Application {

  @Transform(TransferType.parse, { toClassOnly: true })
  @Transform(TransferType.stringify, { toPlainOnly: true })
  data: ApplicationData;

}

export class ApplicationData {

  @Type(() => User)
  person: User;

  @Type(() => Travel)
  travel: Travel;

  @Type(() => Consumption)
  consumptions: Consumption[];

  @Type(() => Additional)
  additional: Additional;

}

Oh, I same problem )) @Type(() => …) I forgot about them

Pass the function reference, don’t execute the function.