feat(compiler): allow template to access private props - #70188
Conversation
5b918f8 to
416c285
Compare
6733a6f to
0509c48
Compare
1ab10b8 to
b468975
Compare
To allow this we'll catch the errors during typechecking and discard it. As context, when setting `isolatedDeclarations: true` this requires developers to explicitly type every property but the `private` ones. By allowing private properties to be used in templates we discard the actually for explicit typing for template only properties.
b468975 to
f7a1fab
Compare
|
This PR was merged into the repository. The changes were merged into the following branches:
|
This is basically a follow up to angular#70188 to address the same inconvenience introduced by `isolatedDeclarations: true`
|
Hi there. From my humble corner, if I may suggest something here, after the PR has been merged. It seems like the access modifiers question has not been yet properly explained to the framework users, both experts and newcomers. When Angular started this was the common understanding:
This was a clear contract back in the days. Let us also not forget that nowadays AI does benefit from clear contracts. It makes it more deterministic. In the last couple of years, the use The explanation was refined in the style guide: "A component class's public members intrinsically define a public API that's accessible via dependency injection and queries. Prefer protected access for any members that are meant to be read from the component's template". As an example: @Component({
...,
template: `<p>{{ fullName() }}</p>`,
})
export class UserProfile {
firstName = input();
lastName = input();
// `fullName` is not part of the component's public API, but is used in the template.
protected fullName = computed(() => `${this.firstName()} ${this.lastName()}`);
}And now there is this PR. This merge states that TS component classes and templates are 2 sides of the same coin and they should have access to the same things. With this new ability, private member would be accessible from the templates but still private to outer classes. IMHO what this suggests is that we can stop using Public members are the ones that would describe the API and private ones simply the rest of them. But as much as a technical reason for this implementation is to some extend understandable, the semantic concerns to the community are much broader than the small changes introduced here. Many users (including myself) prefer using JS private elements since they offer class members that are genuinely private at runtime. These members would not be accessible in the template and that has its advantages. There are several problems when using
private readonly router = inject(Router);
private readonly http = inject(HttpClient);
private readonly store = inject(MyStore);
private readonly analytics = inject(AnalyticsService);<button (click)="router.navigate(...)">
<button (click)="store.doSomething()">
{{ http... }}
{{ analytics... }}Now the template isn't expressing the component's UI behavior through a component API. It is reaching directly into the component's implementation dependencies. It would be much better to do this: #router = inject(Router);
protected goToDetails() {
this.#router.navigate(...);
}<button (click)="goToDetails()">
private readonly copilotKit = inject(CopilotKit);@let service = copilotKit;
{{ service.someInternalThing() }}
IMHO, we should promote this in the documentation as good practice: @Component({
selector: 'app-user-profile',
template: `
<h1>{{ fullName() }}</h1>
<button (click)="editProfile()">Edit profile</button>
`,
})
export class UserProfile {
// Private properties come first since we might need them next for public ones.
#router = inject(Router);
#analytics = inject(AnalyticsService);
// Public properties - Component API
readonly firstName = input.required<string>();
readonly lastName = input.required<string>();
// Protected Template API
protected fullName = computed(
() => `${this.firstName()} ${this.lastName()}`
);
// Not part of the public API, but intentionally exposed to the template.
protected editProfile() {
this.#router.navigate(['/profile', 'edit']);
}
}The use of |
|
We have a large Angular codebase and I don't feel comfortable upgrading the Angular now without rewriting all the private/protected members in our codebase. Any chance providing an opt-out from this feature while we transition to the new style?
|
|
@ippeiukai, you might be interested in following #70514 that talks about the consequences of this change. |
To allow this we'll catch the errors during typechecking and discard it.
As context, when setting
isolatedDeclarations: truethis requires developers to explicitly type every property but theprivateones. By allowing private properties to be used in templates we discard the actually for explicit typing for template only properties.