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
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 | β | β | β |
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
.
ngrx π
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.