Table of contents
Before Angular@14 we could only bind public properties in views. Even though, the properties were meant to be private to the component, we had to make them public. This led to exposing component internals to the consumer. Now, we can make use of typescript's protected
access modifier to prevent users from accessing internals and still be able to bind those properties in the view.
Public properties
Before Angular@14, if you tried binding protected
property in the view, the compiler would complain that the property is not accessible outside class.
In this example, I'm using color
setter to prepend a value with the @
symbol. It's assigned to the colorWithHash
property and displayed in the view.
import { Component, Input } from '@angular/core';
@Component({
selector: 'app-public-test',
standalone: true,
template: `<span>{{ colorWithHash }}</span>`,
})
export class PublicTestComponent {
colorWithHash: string | undefined;
@Input() set color(color: string) {
this.colorWithHash = `@${color}`;
}
}
Take a look at the IDE auto-completion - both properties are available.
Since both setter
and colorWithHash
are public we can access them directly, i.e. by using @ViewChild
decorator. Now, I can override internal value not meant to be accessed from the outside.
import { AfterViewInit, Component, ViewChild } from '@angular/core';
import { PublicTestComponent } from './public-test.component';
@Component({
selector: 'app-root',
standalone: true,
imports: [PublicTestComponent],
template: `<app-public-test [color]="'red'" #publicTestComponent></app-public-test>`,
})
export class AppComponent implements AfterViewInit {
@ViewChild('publicTestComponent') publicTestComponent!: PublicTestComponent;
ngAfterViewInit(): void {
this.publicTestComponent.color = 'green';
this.publicTestComponent.colorWithHash = '-1-2-';
}
}
Protected properties
The component body is almost identical to the previous one. The only difference is the colorWithHash
property is now protected.
import { Component, Input } from '@angular/core';
@Component({
selector: 'app-protected-test',
standalone: true,
template: `<span>{{ colorWithHash }}</span>`,
})
export class ProtectedTestComponent {
protected colorWithHash: string | undefined;
@Input() set color(color: string) {
this.colorWithHash = `@${color}`;
}
}
Take a look at the IDE auto-completion - only the color
setter is available.
Now, I can't access protected properties and have to use only public ones, which is an expected behavior. The protected value is properly interpolated in the view.
import { AfterViewInit, Component, ViewChild } from '@angular/core';
import { ProtectedTestComponent } from './protected-test.component';
@Component({
selector: 'app-root',
standalone: true,
imports: [ProtectedTestComponent],
template: `<app-protected-test [color]="'red'" #protectedTestComponent></app-protected-test>`,
})
export class AppComponent implements AfterViewInit {
@ViewChild('protectedTestComponent') protectedTestComponent!: ProtectedTestComponent;
ngAfterViewInit(): void {
this.protectedTestComponent.color = 'white';
}
}
Footnote
Please do not confuse Typescript's private / protected
keywords with the Javascript's native private fields prefixed with the #
symbol. These are completely different topics. Nevertheless, there are still ways to access protected or private members. You can always cheat the compiler or iterate through object properties. Protected fields are meant to hide the class internals and remove users' uncertainty about whether to use a certain property or not.