This project is an Angular Single-Page Application based on the concept of "lootboxes", random virtual rewards commonly found in video games. The goal of this project is to recreate the anticipation and uniqueness of opening such a reward with interesting random items.
A working demo of this project can be found here.
- Angular v9.x
- Material UI
- Libraries / npm Packages
In a typical use case, the user will navigate to the root of the application (ex: https://devonjsmith.github.io/lootbox-simulator/) which will then navigate to a randomly generated sub-route.
Clicking the "Reveal" button on the card will fade in the "back" of the card to reveal the randomly generated lootbox item, which consists of a random title, a random image, and a random phrase/description.
Clicking the "Generate New Lootbox" will redirect the browser to the application root and generate a new seed and lootbox.
The "Shareable Link" textbox gives a convenient text-box with the current absolute URL. Clicking the "Copy" button will automatically copy the contents to the clipboard.
The application uses a random seed system so that each unique URL will always give the same result (ex: This link will always generate "Handcrafted Granite Soap" with the image of a starry night sky). This allows a user to save or reproduce an interesting result, while also allowing the user to get random results by navigating to the application root.
The LandingSeedGeneratorComponent
serves as the application root. Its only function is to generate a random seed (using the Faker.js
"password" functionality) and navigate to the /lootbox/
route with the generated seed as a route parameter:
ngOnInit(): void {
// use faker library to generate a seed
const seed = faker.internet.password(8);
// route to the lootbox home using the seed
this.router.navigate([`/lootbox/${seed}`]);
}
The LootboxHomeComponent
will parse the seed from the URL, which is then passed to the LootBoxService
to generate the lootbox items, ensuring that results are consistent for corresponding seeds:
ngOnInit() {
// get the seed from the URL, if possible
const seedParameter = this.route.paramMap.subscribe(params => {
if (params.get('seed')) {
this.seed = params.get('seed');
}
this.GenerateLootBoxItems();
});
}
GenerateLootBoxItems() {
this.lootBoxItems = [];
for (let i = 0; i < this.itemNumber; i ++) {
this.lootBoxService.generateLootboxItem(this.seed).subscribe(x => {
this.lootBoxItems.push(x);
});
}
}
The LootboxItemService
generateLootboxItem
method will parse the passed in seed into a number (by converting each character to its ASCII value) and pass the resulting number to the RandomTextService
and the RandomImageService
in order to return the Observable<LootBoxItemModel>
public generateLootboxItem(seed: string | null): Observable<LootBoxItemModel> {
const numberSeed = this.generateSeed(seed);
return new Observable(subscriber => {
subscriber.next({
name: this.randomTextService.getRandomName(numberSeed),
description: this.randomTextService.getRandomDescription(numberSeed),
imgUrl: this.randomImageService.getRandomImgUrl(numberSeed),
flipped: false
});
});
}
private generateSeed(seed: string | null): number {
let seedString = seed;
if (!seedString) {
seedString = faker.internet.password(8);
}
// convert password to ascii code
let asciiString = '';
for (let i = 0; i < seedString.length; i ++) {
asciiString += seedString.charCodeAt(i).toString(10);
}
return Number.parseInt(asciiString, 10);
}
Random image generation in the current release of Faker.js
is broken. The library still relies on Lorem-Pixel
API, which has been deprecated and is no longer functional. There is a pull request open for the Faker.js library to use the Lorem Picsum
api, but the stable branch does not have this merged.
Lorem Picsum
does have an angular library, however this does not provide support for the seed
functionality, so I wasn't able to use it for this project (I may revist this issue later in another project).
Lastly, the seed
functionality of Faker.js
only accepts a number
value, which is why the generated string needed to be converted using the ASCII values.
Initially, my vision for opening the lootbox items would be akin to flipping over a card to "reveal" the contents underneath. To this end, I tried implementing the project using the ngx-flip library, however I noticed visual bugs when combing this with flex boxes
:
I ended up replacing the "flip animation" with a simple fade in/out animation, inspired by this StackOverflow comment.
After replacing the ngx-flip
package and deciding to use Angular animations instead, I looked into reusing Animations in a robust way. I found this article in the Angular documentation which covered this exact topic, and I ended up with a exported animation like this:
import { animation, trigger, transition, style, animate } from '@angular/animations';
export const FadeInAnimation = animation([
style({opacity: 0}),
animate('{{ duration }}', style({opacity: 1}))
]);
export const FadeOutAnimation = animation([
animate('{{ duration }}', style({opacity: 0}))
]);
And I was able to implement them into the LootBoxHome
and LootBoxItem
components like this:
@Component({
selector: 'app-lootbox-home',
templateUrl: './lootbox-home.component.html',
styleUrls: ['./lootbox-home.component.css'],
animations: [
trigger('fadeInOut', [
transition(':enter', [
useAnimation(FadeInAnimation, {
params: {
duration: '900ms'
}
})
]),
transition(':leave', [
useAnimation(FadeOutAnimation, {
params: {
duration: '900ms'
}
})
])
])
]
})
I added the duration
parameter so that the animations could be re-used across components, but the length of the animation could be adjusted independently for each component.
I knew before starting this project that I wanted it to eventually be hosted on Github Pages. I already this personal website (https://devonjsmith.github.io) hosted by Github so I figured it would be much the same process for hosting an Angular application.
However, I did encounter some issues with building the Angular application in production mode and getting the es5 bundles to generate properly. I eventually moved to the angular-cli-ghpages plugin, which conveniently builds the project for production onto a new gh-pages
branch. From there, it's just a matter of modifying the settings for the github project.
My initial concept for this project included multiple lootbox items that would each need to be revealed one after another. I was inspired by the "card pack" opening mechanics in several free-to-play card games:
Image source: reddit.com/r/gwent
Image source: reddit.com/r/HSPulls
In order to allow multiple lootbox items to be generated, I would need to develop a system to generate n
possible numerical seeds from a single string seed. This is because I would like to keep the functionality where each unique URL produces consistent results, while still keeping an interesting amount of randomness.
This project was an enjoyable Angular experiment. I was impressed by the functionality of the Faker.js
library, though I do feel that a more complicated random system for generating the lootbox content could improve the system.
This project was a good opportunity to learn about Angular animations and deploying strategies for Github pages.
The UI and layout of this project were made very convenient by the Material
and Angular-flex
libraries. I am eager to see how my UI/UX design could improve in the future with more experience.