Understanding Decorators in Angular
We’ve already been exposed to decorators a little bit. Specifically, we have
seen @Component
and @Directive
decorators. A decorator in Angular looks like
this:
@Component({
someThing: 'some-value',
someOtherThing: [Some, Other, Values]
})
export class SomeComponent {}
They definitely look a little weird — they are like little hats that sit on top of our classes — but they play an important role. Their role in an Angular application is to provide some metadata about the class you are defining, i.e. they talk about your class rather than define the class.
The concept of a decorator is not an Angular specific concept. This is a common design pattern in Object Oriented Programming. It isn’t necessary for us to discuss the general applicability of the decorator pattern, but let’s consider what this is achieving for Angular.
I think that a @Component
decorator shows this quite well:
@Component({
selector: 'my-component',
template: `<p>Hello</p>`,
styles: [
`
.some-class {
display: none;
}
`
]
})
export class MyComponent {}
If you prefer to define your templates and styles in separate files you can do this instead:
@Component({
selector: 'my-component',
templateUrl: 'my-component.component.html',
styleUrls: ['my-component.component.scss']
})
export class MyComponent {
}
We have the class itself which handles all of our logic for us, but that is not all Angular needs to know to create the component. Consider that we are able to add a component to the template of another component like this:
<my-component></my-component>
Angular needs to know what name to give that tag and that is what the
selector
property in the decorator allows us to do. We also need a template
associated with our class, and the decorator allows us to do that with the
template
property (or the templateUrl
property if you want to link out to
a separate template file rather than defining it inline in the decorator). The
same thing again with the styles
property. In this case, this decorator is
telling Angular that:
- This class is a component (by virtue of the fact that we are using the
@Component
decorator) - It should have a selector of
my-component
- This is the template for the component (or this is where to find it)
- This is the styles for this component (or this is where to find it)
With all of that extra information, Angular can do its job and create the component.
@Directive
We already know about the @Component
decorator, now let’s cover all of the
common decorators you will be using one at a time.
The @Directive decorator allows you to create your own custom directives. We’ve touched on the concept of a directive briefly, but basically, it allows you to attach some behaviour to a particular component/element. Typically, the decorator would look something like this:
@Directive({
selector: '[my-selector]'
})
Then in your template, you could use that selector to trigger the behaviour of the directive you have created by adding it to an element:
<some-element my-selector></some-element>
We talked before about a directive that would make the background colour of an
element red
. Let’s take that a step further and create a directive that will
change the background colour to a random colour.
I am going to create this directive for the home page component in the example app we have been working with. If your root component no longer displays your home component, I am going to leave it to you to figure out how to get that displaying correctly again.
Create a file at
app/home/ui/random-color.directive.ts
and add the following:
import { Directive, HostBinding } from '@angular/core';
@Directive({
selector: '[randomColor]',
})
export class RandomColor {
@HostBinding('style.backgroundColor') color = `#${Math.floor(
Math.random() * 16777215
).toString(16)}`;
}
NOTE: This is a sneaky little technique to create a random colour in JavaScript. I definitely did not come up with this, I stole it from here.
I am sneaking in an Angular feature here that we haven’t talked about yet. The
idea with a HostBinding
is that it binds to the host which means the
element/component that the directive is attached to (remember the comparison
I made about the directive being like a parasite, well that analogy holds here
because a parasite attaches to a host). We can use this host binding to change
some property of the host.
We have made this directive standalone so we do not need to worry
about an @NgModule
. However, just so that you are somewhat familiar with the
NgModule approach… if you were using modules and wanted to use this directive
in a component, it would need to be declared within the same module as that
component, for example:
IMPORTANT: Do not do this — we are using a standalone directive, this is just to show you what the module option might look like.
app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { HomeComponent } from './home/home.component';
import { RandomColor } from './home/ui/random-color.directive';
import { WelcomeComponent } from './home/ui/welcome.component';
import { SettingsComponent } from './settings/settings.component';
@NgModule({
declarations: [
AppComponent,
HomeComponent,
SettingsComponent,
WelcomeComponent,
RandomColor,
],
imports: [BrowserModule, AppRoutingModule],
providers: [],
bootstrap: [AppComponent],
})
export class AppModule {}
This would then allow us to use the directive within the HomeComponent
. But,
since we are using a standalone directive we can just add it to the imports
of
our standalone component:
import { Component } from '@angular/core';
import { RandomColor } from './ui/random-color.directive';
import { WelcomeComponent } from './ui/welcome.component';
@Component({
selector: 'app-home',
template: `
<app-welcome />
<p>I am the home component</p>
<p randomColor>I am stylish</p>
`,
imports: [WelcomeComponent, RandomColor],
})
export class HomeComponent {}