Using Typescript Getters in Your Angular App
tldr;
I’ve been using Angular since the early betas, but only recently did I start using a Typescript feature called getters. The simplest explanation of a getter is that it allows you to access properties of the Typescript class. But the difference over using a getter and just accessing a normal property is that the getter can run some amount of code before returning the value. In an Angular context, this can be great if you need to use a property that is calculated based on a different property which could change throughout the lifetime of the component. OnInit is helpful, but only runs once in the lifetime of the component, and other lifecycle hooks like OnChanges can have performance implications. In this article we’ll look at an example of how we can use getters effectively.
Background
I was recently working on a form to gather information about a family. The form was used to enter personal information, and the user may potentially enter personal information for multiple people in a family. The form was reused for whichever person was selected for edit. The form component was created and added to the screen once, so the OnInit lifecycle method ran just once. One of the fields was a birth date field, which used a date picker component from PrimeNG.
For the main account holder, there was minimum required age of 18. For all other family members, there was no minimum age. The form component had a property, maximumDate, that was initialized when the component was created based on an Input, isPrimaryMember. Here’s an example:
export class FormComponent {
@Input() personForm: FormGroup;
@Input() isPrimaryMember: boolean;
maximumDate = this.isPrimaryMember ? sub(new Date(), { years: 18 }) : new Date();
}
This technically worked, but it only worked properly if you were adding the primary family member or a family member whose birthday was over 18 years ago. It would allow you to add someone younger if the component was initialized with isPrimaryMember being false. The problem then became that the primary member could have any birth date.
The reason this happens is because, as mentioned, the maximumDate was initialized when the component was first created, and it isn’t being destroyed and recreated when the family member was changed. The same issue would happen if the maximumDate was initialized in the class’s constructor or the ngOnInit lifecycle hook. Those methods are also only run once when the component is created.
Solution
There are a couple solutions to get around this issue. The first solution is using a different lifecycle hook. The OnChanges lifecycle hook is made for situations just like this. Here’s a sample of what the hook would look like:
ngOnChanges(changes: SimpleChanges) {
if (!!changes.isPrimaryMember) {
this.maximumDate = this.isPrimaryMember ?
sub(new Date(), { years: 18 }) :
new Date();
}
}
This would work, but many people will tell you to be careful with ngOnChanges. The reason you need to be careful is that your code inside the lifecycle hook runs on each change detection cycle and you can negatively impact performance. Now, in a situation like this, you’ll probably be okay. And, if you make sure that you limit when to make the assignment to maximumDate with the if statement like I have shown, you should be totally fine.
Another solution is to use a Typescript getter. The getter allows you to run some code each time the value is accessed. In our situation, the value of the getter will also update when the component Inputs update as well. In practice, there’s no difference between using the getter and using ngOnChanges. The difference is primarily when the value of the attribute is calculated.
Here’s an example of using the getter:
export class FormComponent {
@Input() personForm: FormGroup;
@Input() isPrimaryMember: boolean;
get maximumDate(): Date {
return this.isPrimaryMember ? sub(new Date(), { years: 18 }) : new Date();
}
}
The getter has a few parts to it that we’ll look at.
First, it uses the get keyword. That signifies what type of variable this is for us. We will know by seeing get that we are working with a getter. In part, this means that we can’t assign a value to maximumDate, as getters alone are read-only.Next is the name of the attribute, how we’ll refer to it in our component. In this case, that is maximumDate.Next up is that empty parentheses need to follow the name we gave the attribute. getters are technically methods, but they can’t accept any input parameters.Next is the return type, in this case: : Date. This is simply to be explicit about what type this getter will be. Of course, this type can be whatever you need it to be but it is a Date in our use case.The last part is the body of the getter. Here you can calculate any value you want to use as the value of the attribute. In this case, we return a Date. The Date is either today if the value of isPrimaryMember is true, or 18 years ago if the value is false.
Once we’ve created our getter, we can reference it like any other variable. Even though it looks just like a method, we don’t need to call it to get the value. If we’re going to use it in our template, it would look like this:
<p-calendar formControlName=”birthdate” [maxDate]=”maximumDate”>
</p-calendar>
In both of these examples, the value of maximumDate will be calculated the value of isPrimaryMember changes. This is perfect, because it allows us to enforce the rule when we want to, and at other times we will allow the user to input any date. If we just initialize maximumDate to a value in ngOnInit, or when we declare the variable, we are stuck with the initial calculation for the rest of the lifetime of the component.
Again, there is no difference between using the getter and ngOnChanges. It really comes down to personal preference. I like the clean look of the getter, in part because I don’t have to look anywhere else (like in the ngOnChanges lifecycle hook) to see how the value of maximumDate is calculated.
Conclusion
Typescript getters are really convenient ways to calculate values that you need to display or use elsewhere in the component. You don’t need to call a method to get the value, but the value of the attribute can change as needed based on other properties of the class.
If you want to learn more about Typescript getters, you can check out these links:
Typescript Getters and SettersMozilla Object.definePropertyMozilla getter
Using Typescript Getters in Your Angular App was originally published in ngconf on Medium, where people are continuing the conversation by highlighting and responding to this story.