I've been writing a lot of exploring Angular providers and how to make them global vs how to make them apply to a specific route or screen. I'm taking a sidebar on that to talk about how to create a component that can easily be configurable.

You could easily add inputs which control how the component behaves, but I'm going to do something different by giving the component a custom service

What Component should we Build?

For the purposes of this sample let's build a component that formats a date.

Create the Infrastructure

There will be multiple files related to this component, so let's create a module. Fire off a few of these Angular CLI Commands

view plain print about
1ng generate module dateFormatter
2ng generate service date-formatter/dateFormatterConfig
3ng generate component dateFormatter

You should see something like this:

We generated the main component, a config service, and a module to wrap this all together.

Modify the Config Service

Open up the Config Service:

view plain print about
1import { Injectable } from '@angular/core';
2
3@Injectable({
4 providedIn: 'root'
5})
6export class DateFormatterConfigService {
7
8 constructor() { }
9}

When I create a service related to a component, I do not want to use the providedIn metadata config object to automatically inject it. I want to give my component consumers more granular control. As such, let's remove that from the service. Additionally, let's add one piece of data for dateFormatterMask:

view plain print about
1import { Injectable } from '@angular/core';
2
3@Injectable()
4export class DateFormatterConfigService {
5
6 dateFormatterMask = 'shortDate';
7
8 constructor() { }
9}

This is super simple.

Create the Component

Open up the date-formatter.component.ts file:

view plain print about
1import { Component, OnInit } from '@angular/core';
2@Component({
3 selector: 'app-date-formatter',
4 templateUrl: './date-formatter.component.html',
5 styleUrls: ['./date-formatter.component.css']
6})
7export class DateFormatterComponent implements OnInit {
8 constructor() { }
9 ngOnInit(): void {
10 }
11}

This is the default component. First let's add an input, for the date that needs to be displayed. I defaulted it to a new Date() object instance:

view plain print about
1@Input()
2 displayDate = new Date();

Next, let's inject the DateFormatterConfig service and also the DatePipe into the constructor:

view plain print about
1constructor(public dateFormatterConfigService: DateFormatterConfigService,
2 public datePipe: DatePipe) { }

Don't forget about the imports:

view plain print about
1import { Component, OnInit } from '@angular/core';
2import {DateFormatterConfigService} from './date-formatter-config.service';
3import {DatePipe} from '@angular/common';

Now open up the date-formatter-component.html file:

view plain print about
1<p>date-formatter works!</p>

Pretty boring. Let's fix that to display the date and use the pipe and config service to format it::

view plain print about
1<b>Current Date:</b>: {{displayDate | date : dateFormatterConfigService.dateFormatterMask}}

That's all we need here.

Create the Component Module

Now, let's turn our attention to the component module:

view plain print about
1import { NgModule } from '@angular/core';
2import { CommonModule } from '@angular/common';
3
4@NgModule({
5 declarations: [],
6 imports: [
7 CommonModule
8 ]
9})
10export class DateFormatterModule { }

We want to declare, and export the DateFormatterComponent:

view plain print about
1declarations: [
2 DateFormatterComponent
3 ],
4 exports: [
5 DateFormatterComponent
6 ]

The imports can remain untouched, but we'll want to set up the two providers:

view plain print about
1providers: [
2 CustomDateFormatterConfigService,
3 DatePipe
4 ],

Don't forget to update all your imports:

view plain print about
1import { NgModule } from '@angular/core';
2import {CommonModule, DatePipe} from '@angular/common';
3import {DateFormatterComponent} from '../../../projects/my-lib/src/lib/date-formatter/date-formatter.component';
4import {CustomDateFormatterConfigService} from '../../../projects/lazyloading/src/app/custom-date-formatter-config.service';

Use This Component

We should pretend that our users of this component are using it deep within some library and that we're importing from remote npm repository without close access to the code. In a module load the module from the lib:

view plain print about
1imports: [
2 // other stuff
3 DateFormatterModule
4 ],

Now add it to the view template of a component:

view plain print about
1<app-date-formatter></app-date-formatter>

The load it up and see something like this:

Cool, but how do we change the display type? Easy; we just replace the config. First, create your own config class by loading a replacement provider. This is one way:

view plain print about
1providers: [
2 {provide: DateFormatterConfigService, useValue: {dateFormatterMask: 'medium'}}]
3]

Whenever the DateFormatterConfigService is loaded, instead we'll load the replacement object.

Rerun the code and you'll see this in the browser:

This has been a great approach for my team to build reusable components, but still provider the consumers a way to configure different options.