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-exportsArrow 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) {
// ...
}
🔝
controlThis is used as a simplest working case.
🔝
default-exportsDefault exports are not supported with AoT.
Don't:
export default class AppComponent {};
Do:
export class AppComponent {};
🔝
form-controlUse this.helloForm.controls["greetingMessage"]
to retrieve form control is fine.
🔝
form-control-errorThe syntax errors?
is not supported by AoT.
Don't:
{{helloForm.controls["greetingMessage"].errors?.minlength}}
Do:
{{helloForm.controls["greetingMessage"].hasError("minlength")}}
🔝
func-as-variable-exportExport 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-exportThis is a fixed version of func-as-variable-export
.
🔝
func-in-declarationsDon't:
function someLoader() {...}
@NgModule({
//...
declarations: someLoader(),
//...
})
export class AppModule {};
Apply @angular/router
or other Angular logic to re-implement the same thing.
🔝
func-in-providersPass 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-useFactoryInstead 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-useValueThis 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-routesDirect 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-configFunction in string configuration is supported by AoT.
🔝
interpolated-es6Don'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;
};
🔝
jqueryTo 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-decoratorMutating 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-reduxSetting up basic angular-redux/store
is fine with AoT. This includes the @select
decorator, both with @ngtools/webpack
and raw ngc
.
🔝
ngrxSetting up basic ngrx
is fine with AoT. This includes their @Effect
decorator as well.
🔝
ngrx-composeDirect 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-decoratorNo-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-contentchildDon't:
export class TabComponent {
//...
@ContentChildren(PaneDirective) private panes: QueryList<PaneDirective>;
//...
}
Do:
export class TabComponent {
//...
/** @internal */
@ContentChildren(PaneDirective) panes: QueryList<PaneDirective>;
//...
}
🔝
private-hostbindingDon't:
export class NameDirective {
@HostBinding("class.world") private isWorld: boolean = false;
}
Do:
export class NameDirective {
/** @internal */
@HostBinding("class.world") isWorld: boolean = false;
}
🔝
private-inputDon't:
export class NameComponent {
@Input() private name: String;
};
Do:
export class NameComponent {
/** @internal */
@Input() name: String;
};
🔝
private-outputDon't:
export class NameComponent {
@Output() private onClicked = new EventEmitter<boolean>();
//...
};
Do:
export class NameComponent {
/** @internal */
@Output() onClicked = new EventEmitter<boolean>();
//...
};
🔝
private-propertyDon't:
export class AppComponent {
private name: string;
constructor() {
this.name = 'World';
}
}
Do:
export class AppComponent {
/** @internal */
name: string;
constructor() {
this.name = 'World';
}
}
🔝
private-viewchildDon'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-accessorsThe 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-paramDon'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-expressionAssign expression like greeting + answer
to template
is supported by AoT.
🔝
template-variableAssign variable like greeting
to template
is supported by AoT.