Skip to content

Sandbox for testing Angular 2's AoT specifically what expressions break it

Notifications You must be signed in to change notification settings

rangle/angular-2-aot-sandbox

Repository files navigation

Angular 2 AoT SandBox

This repo is used to formalize what we can and cannot do with AoT (through ngc or @ngtools/webpack).

To run specific test case like control:

node sandbox-loader.js control

The default is to use ngc for AoT, but you can also use @ngtools/webpack:

node sandbox-loader.js control ngtools

or JiT:

node sandbox-loader.js control jit

The bundle files are inside ./dist/, and to host:

npm run start

Current Status

Test AoT With ngc AoT With @ngtools/webpack JiT
control βœ… βœ… βœ…
form-control βœ… βœ… βœ…
func-in-string-config βœ… βœ… βœ…
jquery βœ… βœ… βœ…
template-variable βœ… βœ… βœ…
template-expression βœ… βœ… βœ…
mut-property-decorator βœ… ❌ βœ…
nomut-property-decorator βœ… ❌ βœ…
angular-redux-store βœ… βœ… βœ…
ngrx βœ… βœ… βœ…
ngrx-compose βœ… βœ… βœ…
arrow-function-exports ❌ ❌ βœ…
default-exports ❌ ❌ βœ…
form-control-error ❌ ❌ βœ…
func-as-variable-export ❌ ❌ βœ…
func-declaration-export βœ… βœ… βœ…
func-in-declarations ❌ ❌ βœ…
func-in-providers ❌ ❌ βœ…
func-in-providers-useFactory ❌ ❌ βœ…
func-in-providers-useValue ❌ ❌ βœ…
func-in-routes ❌ ❌ βœ…
interpolated-es6 ❌ ❌ βœ…
property-accessors ❌ ❌ βœ…
private-contentchild ❌ ❌ βœ…
private-hostbinding ❌ ❌ βœ…
private-input ❌ ❌ βœ…
private-output ❌ ❌ βœ…
private-property ❌ ❌ βœ…
private-viewchild ❌ ❌ βœ…
service-with-generic-type-param ❌ ❌ βœ…

AoT Do's and Don'ts

This section explains the cases listed above, and will show how each of them fails and works.

arrow-function-exports πŸ”

Arrow function does not work with AoT when it is passed to an NgModule.

Don't:

export const couterReducer = (state, action: Action) => {
  // ...
}

Do:

export function counterReducer(state, action: Action) {
  // ...
}

control πŸ”

This is used as a simplest working case.

default-exports πŸ”

Default exports are not supported with AoT.

Don't:

export default class AppComponent {};

Do:

export class AppComponent {};

form-control πŸ”

Use this.helloForm.controls["greetingMessage"] to retrieve form control is fine.

form-control-error πŸ”

The syntax errors? is not supported by AoT.

Don't:

{{helloForm.controls["greetingMessage"].errors?.minlength}}

Do:

{{helloForm.controls["greetingMessage"].hasError("minlength")}}

func-as-variable-export πŸ”

Export function as variable is fine with AoT except when it is passed to an NgModule.

Don't:

//foo.ts
export const foo = function foo(state: number) {
  return "foo" + state;
}

//app.module.ts
@NgModule({
  imports: [
    //...
    FooModule.provideFoo(foo),
  ],
  //...
})
export class AppModule {};

Do:

//foo.ts
export function foo(state: number) {
  return "foo" + state;
}

//app.module.ts
@NgModule({
  imports: [
    //...
    FooModule.provideFoo(foo),
  ],
  //...
})
export class AppModule {};

func-declaration-export πŸ”

This is a fixed version of func-as-variable-export.

func-in-declarations πŸ”

Don't:

function someLoader() {...}
@NgModule({
  //...
  declarations: someLoader(),
  //...
})
export class AppModule {};

Apply @angular/router or other Angular logic to re-implement the same thing.

func-in-providers πŸ”

Pass a result of function to providers is not supported by AoT.

Don't:

//services/service-providers.ts
import { AppService } from "./app.service";
const services = {
  AppService: AppService
};

export function getService(k) {
  return services[k];
}
export const serviceProviders = Object.keys(services).map(getService);

//app.module.ts
import { serviceProviders } from "./services/service-providers";
@NgModule({
  //...
  providers: serviceProviders
})
export class AppModule {};

Do:

//services/service-providers.ts
import { AppService } from "./app.service";
export const serviceProviders = [
  AppService
];

//app.module.ts
import { serviceProviders } from "./services/service-providers";
@NgModule({
  //...
  providers: serviceProviders
})
export class AppModule {};

func-in-providers-useFactory πŸ”

Instead of using function directly, export it first in another module and import it back.

Don't:

@NgModule({
  //...
  providers: [
    { provide: AppService, useFactory: () => { return { name: "world test" }; }},
  ],
  //...
})
export class AppModule {};

Do:

import { randomFactory } from "./random.factory.ts"
@NgModule({
  //...
  providers: [
    { provide: AppService, useFactory: randomFactory }},
  ],
  //...
})
export class AppModule {};

func-in-providers-useValue πŸ”

This case only fails when the passed value is generated by a nested function. If the foo inside foo.ts is not returning another function, then it will pass. Another solution is to replace useValue with useFactory and set it to use bar instead of barConst.

Don't:

//foo.ts
export function foo() { return function() { return {}; }; };
export const fooConst = foo();

//bar.ts
import { fooConst } from "./foo";
export function bar() { return fooConst(); }
export const barConst = bar();

//app.module.ts
//...
import { barConst } from "./bar";

@NgModule({
  //...
  providers: [ { provide: AppService, useValue: barConst }]
})
export class AppModule {};

Do:

//foo.ts
export function foo() { return {}; };
export const fooConst = foo();

//app.module.ts
//...
import { fooConst } from "./bar";

@NgModule({
  //...
  providers: [ { provide: AppService, useValue: fooConst }]
})
export class AppModule {};

or

//foo.ts
export function foo() { return function() { return {}; }; };
export function fooFactory() {
   return foo();
}
//app.module.ts
//...
import { fooFactory } from "./foo";

@NgModule({
  //...
  providers: [ { provide: AppService, useFactory: fooFactory }]
})
export class AppModule {};

func-in-routes πŸ”

Direct use of function in route is not supported by AoT. Either avoid using it or export it from other module.

Don't:

function random() {
  return [{
    path: "",
    component: AppViewComponent
  }];
}
const SAMPLE_APP_ROUTES: Routes = random();

Do:

const SAMPLE_APP_ROUTES: Routes = [{
  path: "",
  component: AppViewComponent
}];

or

import { random } from "./random.routes.ts";
const SAMPLE_APP_ROUTES: Routes = random();

func-in-string-config πŸ”

Function in string configuration is supported by AoT.

interpolated-es6 πŸ”

Don't:

@Component({
  selector: "app",
  template: `Hello ${1 + 1}`
})
export class AppComponent {};

Do:

@Component({
  selector: "app",
  template: `Hello {{value}}`
})
export class AppComponent {
  value:number = 1 + 1;
};

jquery πŸ”

To use jQuery with AoT, one way is to use the webpack.ProvidePlugin to provide jquery as global variable; another way is to inject jquery as a service like this:

//...
const $ = require("jquery");
export function jqueryFactory () {
  return $;
}
@NgModule({
  //...
  providers: [
    { provide: "JqueryService", useFactory: jqueryFactory},
  ],
  bootstrap: [AppComponent]
})
export class AppModule {};

Notice that useValue here does not work.

mut-property-decorator πŸ”

Mutating property decorator is supported by ngc, but does not work with @ngtools/webpack, because @ngtools/webpack explicitly remove all custom decorators. Details can be found here: angular-redux/store#236.

Desired effect of this case is that Hello World 42 instead of Hello 42 should be displayed.

angular-redux πŸ”

Setting up basic angular-redux/store is fine with AoT. This includes the @select decorator, both with @ngtools/webpack and raw ngc.

Setting up basic ngrx is fine with AoT. This includes their @Effect decorator as well.

ngrx-compose πŸ”

Direct use of compose does not work with AoT, it requires a wrapper.

Don't:

//reducers/index.ts
const reducers = {
  counter: fromCounter.counterReducer,
};
const reducer: ActionReducer<State> = compose(storeLogger(), combineReducers)(reducers);

//app.module.ts
//...
import { reducer } from "./reducers";
@NgModule({
  imports: [
    BrowserModule,
    StoreModule.provideStore(reducer)
  ],
  //...
})
export class AppModule {}

Do:

//reducers/index.ts
const reducers = {
  counter: fromCounter.counterReducer,
};
const developmentReducer: ActionReducer<State> = compose(storeLogger(), combineReducers)(reducers);
export function reducer(state: any, action: any) {
  return developmentReducer(state, action);
};

//app.module.ts
//...
import { reducer } from "./reducers";
@NgModule({
  imports: [
    BrowserModule,
    StoreModule.provideStore(reducer)
  ],
  //...
})
export class AppModule {}

nomut-property-decorator πŸ”

No-mutating property decorator is supported by ngc, but does not work with @ngtools/webpack, because @ngtools/webpack explicitly remove all custom decorators. Details can be found here: angular-redux/store#236.

Desired effect of this case is that console should raise errors because we are trying to change a @ReadOnly property.

private-contentchild πŸ”

Don't:

export class TabComponent {
  //...
  @ContentChildren(PaneDirective) private panes: QueryList<PaneDirective>;
  //...
}

Do:

export class TabComponent {
  //...
  /** @internal */
  @ContentChildren(PaneDirective) panes: QueryList<PaneDirective>;
  //...
}

private-hostbinding πŸ”

Don't:

export class NameDirective {
  @HostBinding("class.world") private isWorld: boolean = false;
}

Do:

export class NameDirective {
  /** @internal */
  @HostBinding("class.world") isWorld: boolean = false;
}

private-input πŸ”

Don't:

export class NameComponent {
 @Input() private name: String;
};

Do:

export class NameComponent {
  /** @internal */
 @Input() name: String;
};

private-output πŸ”

Don't:

export class NameComponent {
 @Output() private onClicked = new EventEmitter<boolean>();
 //...
};

Do:

export class NameComponent {
  /** @internal */
 @Output() onClicked = new EventEmitter<boolean>();
 //...
};

private-property πŸ”

Don't:

export class AppComponent {
  private name: string;
  constructor() {
    this.name = 'World';
  }
}

Do:

export class AppComponent {
  /** @internal */
  name: string;
  constructor() {
    this.name = 'World';
  }
}

private-viewchild πŸ”

Don't:

export class AppComponent implements AfterViewInit {
  @ViewChild(ChildDirective) private child: ChildDirective;

  ngAfterViewInit() {
    console.log(this.child.value);
  }
};

Do:

export class AppComponent implements AfterViewInit {
  /** @internal */
  @ViewChild(ChildDirective) child: ChildDirective;

  ngAfterViewInit() {
    console.log(this.child.value);
  }
};

property-accessors πŸ”

The es6 property accessors are not supported by AoT if it is passed to an NgModule.

Don't:

import { ERROR, WARNING } from "./definitions";

export function handler1() {};
export function handler2() {};
export const ErrorEventHandlers = {
  [ERROR]: {
    handler: handler1
  },
  [WARNING]: {
    handler: handler2
  }
};

Do:

export function handler1() {};
export function handler2() {};
export const ErrorEventHandlers = {
  'ERROR': {
    handler: handler1
  },
  'WARNING': {
    handler: handler2
  }
};

service-with-generic-type-param πŸ”

Don't:

export class AppComponent {
  greeting: string;
  constructor(helloService: HelloService<boolean>) {
    this.greeting = helloService.getHello(true);
  }
};

The generic type parameter is not supported in Angular, and there is more detail.

template-expression πŸ”

Assign expression like greeting + answer to template is supported by AoT.

template-variable πŸ”

Assign variable like greeting to template is supported by AoT.

About

Sandbox for testing Angular 2's AoT specifically what expressions break it

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published