Solvedangular AOT Compiler requires public properties, while non-AOT allows private properties
✔️Accepted Answer
@aluanhaddad you have a big misunderstanding in here. There is no subset of Typescript in here. No one said that.
By using JiT (AKA the common way of doing Angular 2 since day 1), you transpile your code to ES5 to run it in the browser. Say you have:
@Component({
selector: 'foo',
template: '<div>{{name}}</div>'
})
export class FooComponent {
private name = 'Hello';
}
That will generate, more or less something like:
define(["require", "exports"], function (require, exports) {
"use strict";
var FooComponent = (function () {
function FooComponent() {
this.name = 'Hello';
}
FooComponent = __decorate([
Component({
selector: 'foo',
template: '<div>{{name}}</div>'
})
], FooComponent);
return FooComponent;
}());
exports.FooComponent = FooComponent;
});
Notice how FooComponent()
contains a name
property. It doesn't matter whether you put public
or private
at name
because in ES5 that concept does not exist so you end with the same code.
When Angular runs that code, it will say. Ok, I have this html code, this template with a {{name}}
and I can see a this.name
in the ES5 code with a value, let's grab that and display it.
So far, so good.
So why do we have private
and public
if at the end the ES5 will not care? Well, the typescript compiler (and your editor) will complain if you try to access a private variable from where you can't do that:
class Foo {
private bar = 10;
}
let f = new Foo();
console.log(f.bar);
Your editor will tell you that you cannot access bar
and tsc
(typescript compiler) will give you an error because you are trying to access a private variable.
Back in the component example, tsc
doesn't complain because at compile time, no one tries to access that name
field and the template will do the databinding at runtime where your code is not typescript anymore but ES5 and again, no visibility modifier in there.
That being said, with AoT, we sort of "convert" the template code to Typescript itself (again, Typescript, not any subset). Imagine the previous component, it will generate something like this:
createInternal(rootSelector:string):import3.AppElement {
const parentRenderNode:any = this.renderer.createViewRoot(this.declarationAppElement.nativeElement);
this._el_0 = this.renderer.createElement(parentRenderNode,'div',(null as any));
this._text_1 = this.renderer.createText(this._el_0,'',(null as any));
this._expr_0 = import7.UNINITIALIZED;
this.init(([] as any[]),[
this._el_0,
this._text_1
]
,([] as any[]),([] as any[]));
return (null as any);
}
detectChangesInternal(throwOnChange:boolean):void {
this.detectContentChildrenChanges(throwOnChange);
const currVal_0:any = import4.interpolate(1,'',this.context.name,'');
if (import4.checkBinding(throwOnChange,this._expr_0,currVal_0)) {
this.renderer.setText(this._text_1,currVal_0);
this._expr_0 = currVal_0;
}
this.detectViewChildrenChanges(throwOnChange);
}
This is typescript code and it is our template converted to typescript code.
this._el_0 = this.renderer.createElement(parentRenderNode,'div',(null as any));
this._text_1 = this.renderer.createText(this._el_0,'',(null as any));
Here we create our div
and a "placeholder" for a binding (AKA {{name}}
).
A bit later we do:
const currVal_0:any = import4.interpolate(1,'',this.context.name,'');
We are accessing the name
property from context
where context is our FooComponent
.
Then we do:
this.renderer.setText(this._text_1,currVal_0);
We get that placeholder from before and we assign to it the value of name
AKA Hello
.
Having this in mind, name
has to be public, because if you set it private, this.context.name
will raise an error since name
is private and you cannot access it from outside the class.
So as TL;DR; there is no typescript subset. With JiT we convert all the code to ES5 and then in runtime we do the bindings. All the visibility modifiers are lost in that process, so it doesn't matter if you say public or private for that.
On the other hand, with AoT, we generate some typescript code for our templates, that will try to access those fields. If they are private, they simply cannot access those properties, hence, we have to put them as public.
@achimha All of this only applies to template <-> component interaction. A template is not part of the component class, so a template can only access to public properties from the component.
On the other hand, when we inject stuff using DI, we can use private because we only use that injected object within the component class and never in the template. That being said, if there is any use case where you need to inject something that needs to be used in the template, yes, you need public as well, but so far, that is not the case in any doc example.
Other Answers:
@robwormald Since a lot of official angular documentation uses private
, isn't this a massive pit of failure for developers new to TypeScript? Considering that a component class is conceptually a cohesive unit, anyone not understanding that private
properties are not private at runtime will have no intuition as to why their code no longer works under AOT.
While I am not of fan of the TypeScript's private
keyword, because it is somewhat misleading, it sounds like JIT compiled components are working precisely because privacy is not enforced at runtime.
I would suggest removing the visibility modifiers on properties during AOT transformations and changing private
to public
for constructor parameter properties.
Edit: This issue should be re-opened
I agree this inconsistency is a nasty pitfall for developers. As @aluanhaddad says, a lot (most?) of the samples use private
for DI in the constructor so developers get the habit especially since one is taught to never expose something that doesn't need exposing.
I believe this issue should be re-opened and addressed.
@robwormald Another, deeper problem that is emerging here, and in many other issues, is that AoT Angular code can not be written in TypeScript. It is written in a subset of TypeScript which is not, as far as I have seen, formally specified, documented, or acknowledged in any capacity.
This brings to mind a fair number of questions, including but not limited to the following:
- What is this language?
- When was this language introduced?
- Why was this language introduced?
- Who designed it?
a. What are their credentials?
b. What other languages have they worked on?
c. Is it intended to be a general purpose language or a domain specific language? - How does it improve on TypeScript as a language?
a. What new abstraction mechanisms does it offer?
b. What tasks does it simplify?
c. What are its motivating usecases? - Where is it formally specified?
- When did I adopt it?
This is the way it is because in JiT mode, we're not using Typescript - we're generating ES5 code, so there's no such concept as a "private" field, and we have no way of enforcing this.
Things that accessed from a template must be public, since they're being accessed outside of the class instance.
I'm submitting a ... (check one with "x")
Current behavior
When writing a component, fields referred inside a template is allowed to be declared
private
and it will still work.However, such
private
fields generate errors with the AOT compiler. They must be turnedpublic
.Expected behavior
Be consistent. Either allow or not allow.
Reproduction of the problem
Please tell us about your environment:
Windows 10.
node --version
= v4.3.1