Creating an Angular Component Library - Alert Component
Published on
The first component we are going to create for our library is an alert component. Alerts are a common component used to notify the user that something has change or maybe an error occured or even a valididation constraint failed on a form.
Prerequisites
- Install Node
- Install Angular CLI
- Creating an Angular Component Library - Workspace Setup
- OR Clone this github repository
Generating Our Alert Component
We will need to generate a new module along with a component in our library project. Then we will need to make them both accessible to applications using our library. Let generate a new module and a new component.
ng generate module components/alert --project=foo
ng generate component components/alert --project=foo
We will update our newly generated module to export our alert component
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { AlertComponent } from './alert.component';
@NgModule({
declarations: [
AlertComponent
],
imports: [
CommonModule
],
exports: [
AlertComponent
]
})
export class AlertModule { }
And last, we will update components
directory’s public_api.ts
file to export both the module and component…
export * from './alert/alert.module';
export * from './alert/alert.component';
Creating Our Alert Component
The structure of an alert is fairly straight forward. An alert has a container, a message, a possible icon, and a way to close the alert. We can make use of Angular’s @Input
and @Output
decorators to make our alert customizable.
Our alert component will have the following bindings for customization…
Binding | Type | Default | Description |
---|---|---|---|
[type] |
AlertType | PRIMARY | The alert type to display (primary, info, etc) |
[message] |
string | EMPTY | The message to display in the alert |
[(visible)] |
boolean | true | The default visibility of the alert |
[dismissible] |
boolean | false | Whether or not the alert is dismissible by the user |
[iconClass] |
string | EMPTY | The icon classes to add if using icon lib |
We will start first with our simple HTML tempalte…
<div class="alert" *ngIf="visible" [ngClass]="type" [@showHide]>
<span class="alert-icon" *ngIf="iconClass" [ngClass]="iconClass"></span>
<p class="alert-message">{{ message }}</p>
<span class="alert-dismiss" *ngIf="dismissible" (click)="dismiss()">×</span>
</div>
Then our SCSS for our alert…
@import "../_shared/scss/variables";
.alert {
padding: 1.5rem 1rem;
margin: 0 0 1rem 0;
border-radius: $border-radius;
display: flex;
flex-direction: row;
font-size: 1rem;
align-items: flex-start;
.alert-icon,
.alert-message {
line-height: 22px;
vertical-align: middle;
}
.alert-icon {
flex: 0 0 18px;
}
.alert-message {
flex-grow: 1;
margin: 0;
padding: 0 0.25rem;
word-break: break-all;
}
.alert-dismiss {
flex: 0 0 22px;
font-weight: bold;
font-size: 24px;
line-height: 24px;
cursor: pointer;
}
&.alert-primary {
background: lighten($primary, 20%);
color: darken($primary, 10%);
.alert-dismiss:hover {
color: darken($primary, 30%);
}
}
&.alert-secondary {
background: lighten($secondary, 20%);
color: darken($secondary, 20%);
.alert-dismiss:hover {
color: darken($secondary, 30%);
}
}
&.alert-success {
background: lighten($success, 20%);
color: darken($success, 20%);
.alert-dismiss:hover {
color: darken($success, 30%);
}
}
&.alert-info {
background: lighten($info, 20%);
color: darken($info, 20%);
.alert-dismiss:hover {
color: darken($info, 30%);
}
}
&.alert-warning {
background: lighten($warning, 20%);
color: darken($warning, 20%);
.alert-dismiss:hover {
color: darken($warning, 30%);
}
}
&.alert-danger {
background: lighten($danger, 20%);
color: darken($danger, 20%);
.alert-dismiss:hover {
color: darken($danger, 30%);
}
}
}
And finally our component class…
import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy } from '@angular/core';
import { SHOW_HIDE } from '../_shared/animations/show-hide.animation';
import { AlertType } from './alert-type.enum';
@Component({
selector: 'foo-alert',
templateUrl: './alert.component.html',
styleUrls: ['./alert.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
animations: [SHOW_HIDE]
})
export class AlertComponent {
@Input()
public type: AlertType;
@Input()
public message: string;
@Input()
public iconClass: string | string[];
@Input()
public dismissible: boolean;
@Output()
public visibleChange: EventEmitter<boolean>;
private isVisible: boolean;
constructor() {
this.type = AlertType.PRIMARY;
this.message = "",
this.isVisible = true;
this.dismissible = false;
this.visibleChange = new EventEmitter<boolean>();
}
@Input()
public set visible(value: boolean) {
this.isVisible = value;
}
public get visible(): boolean {
return this.isVisible;
}
public dismiss(): void {
this.isVisible = !this.isVisible;
}
}
One thing you will notice is the use of the @Output
decorator. In Angular, you can achieve two way data binding by creating an EventEmitter<T>
with the @Output
decorator. The catch is that the emitter must be named the same as the @Input
with Change
appended to it. Since our input is visible
, our output must be visibleChange
to achieve the two way binding ([(visible)]
).
Another option would be to use an rxjs Subject
that the parent can subscribe to and can be notified of the alert being closed. For now we will keep the two way binding but I wanted to make you aware of another approach if you’re looking to execute a callback on close.
Now we need to create two additional files. One is an emum holding the different alert types. This will allow us to style out alert with different styles based on the severity of the alert. The other file will hold the animation to execute when the alert is added and removed from the DOM.
Let create our alert type enum…
touch projects/foo/src/lib/components/alert/alert-type.enum.ts
And our enum…
export enum AlertType {
PRIMARY = "alert-primary",
SECONDARY = "alert-secondary",
SUCCESS = "alert-success",
INFO = "alert-info",
WARNING = "alert-warning",
DANGER = "alert-danger"
}
Now we can create our animation. The animation file will be placed in our _shared
directory since we probably will end up reusing this animation with multiple components in our library. We will create a directory to hold all our shared animations…
mkdir -p projects/foo/src/lib/components/_shared/animations
touch projects/foo/src/lib/components/_shared/animations/show-hide.animation.ts
And our animation…
import { trigger, state, style, animate, transition } from '@angular/animations';
export const SHOW_HIDE = trigger('showHide', [
transition(':enter', [
style({
opacity: 0,
transform: 'scaleX(0.98) scaleY(0)',
position: 'relative'
}),
animate('150ms', style({
opacity: 1,
transform: 'scale(1)'
}))
]),
transition(':leave', [
style({
opacity: 1,
transform: 'scale(1)'
}),
animate('250ms', style({
opacity: 0,
transform: 'scaleX(0.98) scaleY(0)'
}))
])
])
Our animation has two transitions, one for when the component is being added to the DOM and another for when it is removed. Each transition has a starting style followed by the animation with a duration and a style to animate to. This will give us a very simple effect, similar to an old tube tv turning off/on. Feel free to change the animation to whatever you like.
Using Our Alert
Now that we have our alert created, we can build our library and use it in our demo application.
First we will run our build script.
npm run build
And now we can import our alert module from our library in our demo application’s app module…
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { NgModule } from '@angular/core';
import { AlertModule } from 'foo';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
BrowserAnimationsModule,
AppRoutingModule,
AlertModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
Now we will add a couple alerts our demo’s app component.
Our app component template will look something like this…
<div class="container">
<div>
<foo-alert
[type]="AT.DANGER"
[visible]="true"
[message]="'This is a danger alert'"
[dismissible]="true">
</foo-alert>
</div>
<div>
<foo-alert
[type]="alertType"
[visible]="alertIsVisible"
[message]="alertMessage"
[iconClass]="alertIconClass"
[dismissible]="alertIsDismissible">
</foo-alert>
</div>
</div>
Our app component styles
.container {
padding: 1.5rem;
div {
display: block;
margin-bottom: 1rem;
}
}
And our app component class
import { Component, OnInit } from '@angular/core';
import { AlertType } from 'foo';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {
public AT = AlertType;
public alertType: AlertType;
public alertIsVisible: boolean;
public alertMessage: string;
public alertIconClass: string;
public alertIsDismissible: boolean
constructor() {
this.alertType = AlertType.INFO;
this.alertIsDismissible = false;
this.alertMessage = 'This is an example alert with our new reusable component';
this.alertIconClass = 'fas fa-info-circle';
this.alertIsDismissible = true;
}
ngOnInit() {
setTimeout(() => this.alertIsVisible = true, 2000);
setTimeout(() => this.alertIsVisible = false, 4000);
}
}
If you look in our ngOnInit
method you will see we added a couple setTimeout(...)
’s to simulate dynamically adding and removing an alert from the DOM.
We will also add is some styling in our demo app’s root style.scss file. We are adding an import for a font, font-awesome icons, and a light css reset.
@import url('https://fonts.googleapis.com/css2?family=Source+Sans+Pro:wght@200;400;600;700&display=swap');
@import url("https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.13.0/css/all.min.css");
$background: #F6F6F8;
$font-color: #FFFF;
html, body, ul, p, h1, h2, h3, h4, h5, h6, blockquote {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Source Sans Pro', Arial, Helvetica, sans-serif;
}
html {
font-size: 16px;
}
body {
background: $background;
}
Now we can run our demo application with npm start
.
Stackblitz Result
I’ve also embeded a Stackblitz showing our different alerts with our dashabord layout we create a few posts back.
The completed github repository can be found here
More Posts
- Creating an Angular Component Library - Workspace Setup
- Creating an Angular Component Library - Alert Component
- Creating an Angular Component Library - Progress Bar Component
- Creating an Angular Component Library - Toaster Component
- Creating an Angular Component Library - Card Component
- Creating an Angular Component Library - Flip Card Component
- Creating an Angular Component Library - Overlay Loader Component
- Creating an Angular Component Library - Overlay Side Panel Component
- Creating an Angular Component Library - Button Component
- Creating an Angular Component Library - Toggle Switch Component
- Creating an Angular Component Library - Checkbox Component