Note: The concepts herein helped me better (and fully) understand Angular component re-usability through concepts like: data binding, component-in-component placement, @Input()
decorators, and @Output()
decorators. Hopefully it can help you in this same way too!
Problems
- Since angular animations are meant for transitions / interactions, it's hard to get actual stop-motion graphics into the web app
- Since the framework is css-driven (doesn't play nice with html5 in general, let alone html5's
<canvas>
element), you don't get to utilize cool new tools like hype4 (for the life of me the 'iframe' route never seems to work) - I'm not an After Effects professional, so the alternative of more abstracted tools is far more appealing.
Solution & Workflow Assumptions
Here's how I got animations from Lottie's free library into a re-usable angular component using the ngx-lottie
package and angular's data / event binding features. Note:
- This article assumes you have basic knowledge of JavaScript/TypeScript/Angular (with associated dependencies like Node installed)
- For speed, the templates were created utilizing my
@riapacheco/yutes
package on npm (feel free to use it -- instructions for install is in npm docs) - The code from this demo is on my github, so feel free to fork it: https://github.com/riapacheco/lottie-demo
To learn more about Lottie, read more from their official site!
Skip ahead
Step 1: Add the File from Lottie
Step 2: Create the View Structure and General Style
Step 3: Install Lottie Packages
Step 4: Add the Lottie Animation to Splash Screen Component
Make it Reusable
Reusing in Another Component
Step 1: Add the File from Lottie
Create a new angular app by running the following command (--skip-tests added so that we don't generate tests files with our components):
$ ng new lottie-demo --skip-tests
Head to LottieFiles.com and navigate to their Free Animations section.
Then, choose an animation and download it as the lottie JSON file ( saved this file as 'piggy-bank.json):
Create a folder called 'lottie' inside your 'src/assets' folder and add the file to it:
Step 2: Create the View Structure and General Style
Create a new component ($ ng g c shared/splash-screen
) and replace all the content that's in the app.component.html file with the new components selector:
<!-- app.component.html -->
<app-splash-screen></app-splash-screen>
Add the following structural scss to the splash-screen.component.SCSS
file:
:host {
height: 100%;
overflow: hidden;
display: flex;
flex-flow: column nowrap;
align-items: center;
justify-content: center;
position: relative;
}
In the component's template (HTML file), add the following code:
<div class="lottie-animation mt-10">
This is where the animation will go.
</div>
<div class="container-sm" style="text-align: center">
<div class="splash-text">
<h1>
Step 1
</h1>
Lorem ipsum dolor sit amet consectetur adipisicing elit.
Impedit eligendi obcaecati quisquam molestias animi dolores
vitae veniam ut provident repudiandae doloremque quae quo
vel, magnam labore eveniet natus fugiat. Aspernatur.
</div>
<div class="button-div mt-3 mb-10">
<a class="btn btn-md accent light" style="margin: 1rem;">
Previous
</a>
<a class="btn btn-md primary" style="margin: 1rem;">
Next
</a>
</div>
</div>
Note: all the styling classes here are simply classes used from my @riapacheco/yutes package so I wouldn't worry about them
This is how the view will render when you run ng serve
:
Step 3: Install Lottie Packages
Install lottie with the following command (more details about this package can be found in their npm docs:
$ npm install lottie-web ngx-lottie
Add the lottie module to your app.module.ts file with an exported function. (note: don't forget to add the CommonModule from @angular/common, so that you can use customized directives)
import { AppComponent } from './app.component';
import { AppRoutingModule } from './app-routing.module';
import { BrowserModule } from '@angular/platform-browser';
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { SplashScreenComponent } from './shared/splash-screen/splash-screen.component';
// Add these two
import { LottieModule } from 'ngx-lottie';
import player from 'lottie-web';
// Export this function
export function playerFactory(): any {
return import('lottie-web');
}
@NgModule({
declarations: [
AppComponent,
SplashScreenComponent,
StepTwoComponent
],
imports: [
BrowserModule,
AppRoutingModule,
CommonModule,
// Add the module like so:
LottieModule.forRoot({ player: playerFactory }),
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
Step 4: Add the Lottie Animation to Splash Screen Component
In the splash-screen.component.TS file, you have to add a variable that binds lottie's AnimationOptions (to reference the file path of your JSON file), and a new function that binds to the event (we'll see later in the template HTML file):
export class SplashScreenComponent implements OnInit {
// This is the option that uses the package's AnimationOption interface
options: AnimationOptions = {
path: '/assets/lottie/piggy-bank.json'
};
constructor() { }
ngOnInit(): void { }
// This is the component function that binds to the animationCreated event from the package
onAnimate(animationItem: AnimationItem): void {
console.log(animationItem);
}
}
Now in your template HTML file, you can replace the dummy text with the <ng-lottie>
element and bind the options variable and the onAnimate event:
<!--splash-screen.component.html-->
<div class="lottie-animation mt-10">
<ng-lottie
[options]="options"
(animationCreated)="onAnimate($event)">
</ng-lottie>
</div>
<!--...more code...-->
Now when we run ng serve in our terminal, we'll see the animation appear in the browser:
Make it Reusable
You can make this component re-usable with Angular's @Input() and @Outputs() so that parent components in the future can feed data or recognize events.
Prepare the component by adding @Input() to the options variable (making it available to other component) and by adding new variables to represent the text rendered in the component. If we want to communicate that a click event happened to the parent, we add @Output() to a variable that emits the event like so:
// splash-screen.component.ts
export class SplashScreenComponent implements OnInit {
@Input() options: AnimationOptions = {
path: '/assets/lottie/piggy-bank.json'
};
@Input() titleText = 'Step 1';
@Input() stepParagraph = 'Lorem ipsum blah blah blah';
@Input() secondaryButtonText = 'Previous';
@Input() primaryButtonText = 'Next';
@Output() animationCreated = new EventEmitter();
@Output() secondaryClick = new EventEmitter();
@Output() primaryClick = new EventEmitter();
constructor() { }
ngOnInit(): void { }
onAnimate(animationItem: AnimationItem): void {
console.log(animationItem);
this.animationCreated.emit(animationItem);
}
onSecondaryClick(clickedSecondaryEvent): void {
this.secondaryClick.emit(clickedSecondaryEvent);
}
onPrimaryClick(clickedPrimaryEvent): void {
this.primaryClick.emit(clickedPrimaryEvent);
}
}
Then we just bind the data from the component to the template with either square brackets (on an element) or curly braces for actual string interpolation; or we bind events using parenthesis on elements:
<!--splash-screen.component.html-->
<div class="lottie-animation mt-10">
<ng-lottie
[options]="options"
(animationCreated)="onAnimate($event)">
</ng-lottie>
</div>
<div
class="container-sm"
style="text-align: center">
<div class="splash-text">
<h1>
{{ titleText }}
</h1>
{{ stepParagraph }}
</div>
<div
class="button-div mt-3 mb-10">
<a
class="btn btn-md accent light"
style="margin: 1rem;"
(click)="onSecondaryClick($event)">
{{ secondaryButtonText }}
</a>
<a
class="btn btn-md primary"
style="margin: 1rem;"
(click)="onPrimaryClick($event)">
{{ primaryButtonText }}
</a>
</div>
</div>
This then renders like this (notice the new words from the component template):
Reusing in Another Component
To show how we re-use this component, add another free animation to the assets/lottie folder we created earlier. I've added one and named it chat.json
.
Create a new component called step-two
by running the command $ ng g c views/step-two in your terminal
Note: For the sake of this demo we're pretending that we want to re-use the component by adding it to a newly generated component for every step of an onboarding process. This isn't advised (adds more work and lots more files than needed). Usually, you'd want to programmatically make it so variables change when items are clicked but stay within the same component. If you need help on how to do that just email me.
So that we can see the component in the browser, remove the contents of the app.component.html file (which is the splash-screen selector element) and replace it with the newly created step-two component's element:
<!--app.component.html-->
<app-step-two></app-step-two>
Now you should see "step two works!" in your screen if you run ng serve which is the default text that comes with a component. Replace this text with the splash-screen selector element so that you instead see the original component we created:
<!--step-two.component.html-->
<app-splash-screen></app-splash-screen>
To get access to the splash-screen component's inputs (without coming up errors at first) we'll create the variables inside the parent step-two component. The new 'stepTwoOptions' variable is now referencing the new 'chat.json' animation instead of the original. Notice that there aren't any input decorators or anything like that, since parent components shouldn't really know that a child component exists:
import { Component, OnInit } from '@angular/core';
import { AnimationOptions } from 'ngx-lottie';
@Component({
selector: 'app-step-two',
templateUrl: './step-two.component.html',
styleUrls: ['./step-two.component.scss']
})
export class StepTwoComponent implements OnInit {
stepTwoOptions: AnimationOptions = {
path: '/assets/lottie/chat.json'
};
stepTwoTitle = 'Step 2';
stepTwoParagraph = 'This is the second step of your stuff!';
constructor() { }
ngOnInit(): void { }
clickNext(clickEvent): void {
console.log(clickEvent);
}
}
So now, to link the variables together, we just need to bind the variables (like a ratio) to the splash-screen selector. In Angular, all square brackets represent data binding, and any parenthesis represent event binding:
<!-- step-two.component.html -->
<app-splash-screen
[options]="stepTwoOptions"
[titleText]="stepTwoTitle"
[stepParagraph]="stepTwoParagraph"
[secondaryButtonText]="'Cancel'"
[primaryButtonText]="'Save'" >
</app-splash-screen>
Notice that the button texts don't actual bind to anything and I just added a string directly inside the template.
Together, this component renders like this:
Ria