Smarter Dumb Components

I was recently refactoring some Angular components at work to follow the smart/dumb component pattern. (I don’t wanna waste time on explaining what exactly that entails - There are many good explanations out there, see this for example.) In the process, I found a pattern for processing inputs in dumb components that I wanted to share.

The problem

Imagine you have a components for displaying a user profile. It’s dumb, so it will have an @Input() where the user is passed in, and its job is to pull it apart and process the different properties for viewing, such as his name or the date he joined. Some of those can be displayed as they are (such as the username), but others might require processing, such as the join date: This probably comes in formatted as something like 2018-08-18T23:10:26+02:00, but you might want to format it with something like momentjs to show it in a more readable way.
Now, the naive approach would be to just have methods on your component that fetch and process the fields:

interface User {
    name: string
    joinDate: string

    // ...
}

@Component({
    selector: "user-profile",
    template: `
        <span>Name: {{ user.name }}</span>
        <span>User joined on: {{ getReadableJoinDate() }}</span>
    `,
})
export class UserProfileComponent {
    @Input()
    user: User

    getReadableJoinDate(): string {
        const date = moment(this.user.joinDate)
        return date.format("MMM Do YY") // outputs "Aug 18th 18"
    }
}

Now, the problem with this is that getReadableJoinDate() is called every time the view is checked. This is because functions are opaque to the change detection mechanism - It can’t know if the function will produce the same value until it actually runs the functions, so it has to be run on every cycle.
If the function just returns a string that might not be a problem, and if you’re just using it once or twice and only to do light work, you’re probably fine. However, if this pattern is used throughout an app, it can pile up and actually have a performance impact. And anyways, it’s always a good idea to reduce the work your app has to do during change detection.

Process once, display as needed

So, how do we fix this? Optimally, we want to process our input only whenever it changes, calculate everything we want to display and store it in properties in the class. The most obvious mechanic for reacting when something changes is (duh) OnChanges. With that, our example would look like this:

@Component({
    selector: "user-profile",
    template: `
        <span>Name: {{ user.name }}</span>
        <span>User joined on: {{ readableJoinDate }}</span>
    `,
})
export class UserProfileComponent implements OnChanges {
    @Input()
    user: User

    readableJoinDate: string

    ngOnChanges(changes: SimpleChanges): void {
        if (!changes.user || !changes.user.currentValue)
            return

        const date = moment(this.user.joinDate)
        this.readableJoinDate = date.format("MMM Do YY")
    }
}

Now, this would actually be a fine solution, but I don’t particularly like this pattern - If you have multiple @Inputs(), the ngOnChanges-method could become complicated and long, and you don’t get type checking for the SimpleChanges object. You could cast it of course, and split the processing of different properties and inputs into different functions, but it doesn’t feel nice to me.

The pattern

While trying out random things I had the idea to use setters to achieve this. Any property of a class can have the @Input() annotation, and setters are no exception. Then we can just store the property we receive in a non-Input-property, and also calculate everything we need right in the setter. This is what it would look like:

@Input()
set user(value: User) {
    this._user = value

    const date = moment(value.joinDate)
    this.readableJoinDate = date.format("MMM Do YY")
}

_user: User
readableJoinDate: string

Now this looks great and very intuitive! There’s just one more thing I dislike about it, but that’s a matter of personal preference. I’m not a fan of prefixing variables with _, because it can have many different meanings; usually it suggests that the variable is private, but that changes from language to language and even between projects.
Also, it feels a little awkward to use _someObject in the template everytime you want to access the original input object. My solution for this is to use named inputs:

@Input("user")
set userInput(value: SomeObject) {
    this.user = value

    const date = moment(value.joinDate)
    this.readableJoinDate = date.format("MMM Do YY")
}

user: SomeObject
readableJoinDate: string

Like this, you can naturally use the input from inside and outside the component as if it was a normal property, but execute whatever logic you need to whenever it gets set.

Notes

Now, of course there’s some things to consider when using this pattern. First of all, this isn’t any better or worse than using the OnChanges-hook, at least performance wise. Both of these techniques can be used interchangeably and accomplish the same thing.

Also, be careful when using this with inputs that can change a lot. When you do heavy work in the setter, you will still incur a performance penalty. You can alleviate this by checking whether the actual value your calculation depends on changed; which results in a primitive form of memoization.

This isn’t the only thing that came out of my refactoring binge, so I’ll probably write a few more things on custom reactive form components and ngrx related things.

Hope this helps someone!

Comments

comments powered by Disqus