Development is generally an iterative process going from brute force to abstraction and then repeat with more abstraction until you find a balance of maintainability, efficiency, and functionality.
Sometimes, daresay most times, it's not all that obvious how a page might function or look. I wanted to build a "learn more" page of an Angular website I am currently reworking but didn't really know how to structure it. After a few hours of messing around, I settled on notecards. Since this is a recipe site, I figured notecards would be a good relatable paradigm to work with.
The next thing to do was to actually create some content to go on the cards. I wrote up some basic ideas of what I wanted to convey to potential members. Then after a few design iterations, created cards using the following code:
(Note that I am using W3CSS for overall styling with some custom styling)
<div class="center-element w3-margin w3-animate-opacity">
<div class="recipe-card" style="max-width: 800px; height: 1000px;">
<div class="recipe-card-title recipe-card-title-position-1">
create and edit recipes
</div>
<div class="recipe-card-body card" style="text-align: left;height: 250px;font-size: 2em">
At mealHash, you can create and edit recipes. This includes areas for ingredients,
instructions, and other key points you'd expect as part of recipe.
</div>
</div>
</div>
Since I had 5 cards of information, I repeated this code 5 times swapping out the appropriate text. While this gets the information conveyed, the experience is really just a "meh". And while users generally don't care about what is happening behind the scenes, developers do.
When you develop, you need to think about the long term consequences of the code. In this particular example, the question becomes: What happens if I want to add, edit, or remove a card?
If I left the code as is, then the answer would be open the project, CRUD the cards to what is needed, test, recompile (using Angular in this instance), and redeploy. That's fine, kind of, if you only need to make an update or two. But this isn't going to be a static site as over time we will be adding more features. So let's abstract a bit and while we're at it provide the potential member with a little better experience in learning more.
I decided the make the information cards function as a carousel. I added a slide variable to keep track of which slide the user was on along with some functions to move forward and backward through the slides.
nextSlide() {
this.slide = this.slide + 1;
if (this.slide > this.slideCount) {
this.slide = 1;
}
}
prevSlide() {
this.slide = this.slide - 1;
if (this.slide < 1) {
this.slide = this.slideCount;
}
}
And then I added to each block previous and next buttons and wrapped each card into an if statement.
@if( slide == 1) {
<div class="center-element w3-margin w3-animate-opacity">
<div class="recipe-card" style="max-width: 800px; height: 1000px;">
<div class="recipe-card-title recipe-card-title-position-1">
create and edit recipes
</div>
<div class="recipe-card-body card" style="text-align: left;height: 250px;font-size: 2em">
At mealHash, you can create and edit recipes. This includes areas for ingredients,
instructions, and other key points you'd expect as part of recipe.
</div>
<div style="display: flex;justify-content: center;">
<button (click)="prevSlide()" class="w3-circle w3-hover-black w3-padding w3-margin-top" style="height: 60px;align-self: center;"><span class="bx bx-left-arrow" style="font-size: 2em;" ></span></button>
<button (click)="nextSlide()" class="w3-circle w3-hover-black w3-padding w3-margin-top" style="height: 60px;align-self: center;"><span class="bx bx-right-arrow" style="font-size: 2em;" ></span></button>
</div>
</div>
</div>
}
@if( slide == 2) {
<div class="center-element w3-margin w3-animate-opacity">
<div class="card" style="max-width: 800px;">
<div class="w3-padding card-title">
stores
</div>
<div class="w3-padding" style="text-align: left;height: 250px;font-size: 1.8em">
You can also add stores into your mealHash, create grocery lists and easily add and remove items from the list. Ingredients can be added to directly to your grocery
lists from your recipes allowing you manage your shopping experience.
</div>
<div style="display: flex;justify-content: center;">
<button (click)="prevSlide()" class="w3-circle w3-hover-black w3-padding w3-margin-top" style="height: 60px;align-self: center;"><span class="bx bx-left-arrow" style="font-size: 2em;" ></span></button>
<button (click)="nextSlide()" class="w3-circle w3-hover-black w3-padding w3-margin-top" style="height: 60px;align-self: center;"><span class="bx bx-right-arrow" style="font-size: 2em;" ></span></button>
</div>
</div>
</div>
}
@if( slide == 3) {
<div class="center-element w3-margin w3-animate-opacity">
<div class="recipe-card" style="max-width: 800px;">
<div class="recipe-card-title recipe-card-title-position-3">
search
</div>
<div class="recipe-card-body" style="text-align: left;height: 250px;font-size: 1.8em">
Use the search page to find recipes by name or that have a particular ingredient. When you find a recipe you want to try, to can copy
it right to your recipe binder and then edit it to make it your own.
</div>
<div style="display: flex;justify-content: center;">
<button (click)="prevSlide()" class="w3-circle w3-hover-black w3-padding w3-margin-top" style="height: 60px;align-self: center;"><span class="bx bx-left-arrow" style="font-size: 2em;" ></span></button>
<button (click)="nextSlide()" class="w3-circle w3-hover-black w3-padding w3-margin-top" style="height: 60px;align-self: center;"><span class="bx bx-right-arrow" style="font-size: 2em;" ></span></button>
</div>
</div>
</div>
}
@if ( slide == 4) {
<div class="center-element w3-margin w3-animate-opacity">
<div class="recipe-card" style="max-width: 800px;">
<div class="recipe-card-title recipe-card-title-position-4">
recipe feed
</div>
<div class="recipe-card-body" style="text-align: left;height: 250px;font-size: 1.8em">
A feed is available of your and others recipes as they are added to mealhash. Just like the search, you can add a recipe to your mealhash and modify it to make it your own.
</div>
<div style="display: flex;justify-content: center;">
<button (click)="prevSlide()" class="w3-circle w3-hover-black w3-padding w3-margin-top" style="height: 60px;align-self: center;"><span class="bx bx-left-arrow" style="font-size: 2em;" ></span></button>
<button (click)="nextSlide()" class="w3-circle w3-hover-black w3-padding w3-margin-top" style="height: 60px;align-self: center;"><span class="bx bx-right-arrow" style="font-size: 2em;" ></span></button>
</div>
</div>
</div>
}
@if ( slide == 5 ) {
<div class="center-element w3-margin w3-animate-opacity w3-animation-left">
<div class="recipe-card" style="max-width: 800px;">
<div class="recipe-card-title recipe-card-title-position-5">
cost
</div>
<div class="recipe-card-body" style="text-align: left;height: 250px;font-size: 1.5em">
mealhash is free and we intend to keep it that way. But will gladly accept donations to keep the site running. In future,
we may build out premium features as we have more mealhasher feedback. But our goal is to make mealhash a must have site
for your recipes, grocery lists, and meal plans.
</div>
<div style="display: flex;justify-content: center;">
<button (click)="prevSlide()" class="w3-circle w3-hover-black w3-padding w3-margin-top" style="height: 60px;align-self: center;"><span class="bx bx-left-arrow" style="font-size: 2em;" ></span></button>
<button (click)="nextSlide()" class="w3-circle w3-hover-black w3-padding w3-margin-top" style="height: 60px;align-self: center;"><span class="bx bx-right-arrow" style="font-size: 2em;" ></span></button>
</div>
</div>
</div>
}
Now this gets me a carousel and a better user experience. But the developer experience has now become a nightmare. Imagine for a second if I wanted to change some styling on the cards. I would have to do that 5 times which means 5 opportunities to introducing a bug into the page.
The key here is that duplicate code is an opportunity for abstraction and ask: What parts of the code can I condense and / or reuse and how might I go about implementing the non-duplicated parts (generally data).
The solution in this instance was really simple. I moved the card titles and text into JSON.
public cards: Array<any> = [
{ title: 'create and edit recipes',
text: ` At mealHash, you can create and edit recipes. This includes areas for ingredients, instructions, and other key points
you would expect as part of recipe.
`,
index: 1
},
{ title: 'stores',
text: ` You can also add stores into your mealHash, create grocery lists and easily add and remove items from the list.
Ingredients can be added to directly to your grocery lists from your recipes allowing you manage your shopping experience.
`,
index: 2
},
{ title: 'search',
text: ` Use the search page to find recipes by name or that have a particular ingredient. When you find a recipe you want to try, to can copy
it right to your recipe binder and then edit it to make it your own.
`,
index: 3
},
{ title: 'recipe feed',
text: ` A feed is available of your and others recipes as they are added to mealhash. Just like the search, you can add a recipe to your
mealhash and modify it to make it your own.
`,
index: 4
},
{ title: 'cost',
text: ` mealhash is free and we intend to keep it that way. But will gladly accept donations to keep the site running. In future,
we may build out premium features as we have more mealhasher feedback. But our goal is to make mealhash a must have site
for your recipes, grocery lists, and meal plans.
`,
index: 5
}
]
I created three key value pairs: title, text, and index. Title and text are obvious as they refer the recipe-card-title area of the card, and the text is in the recipe-card-body. Index is used as a way to track the cards. Then I created a @for loop based on one of the card blocks:
@for( card of cards; track card.index) {
@if( slide == card.index) {
<div class="center-element w3-margin w3-animate-opacity">
<div class="card" style="max-width: 800px; height: 400px;">
<div class="w3-padding card-title">
{{card.title}}
</div>
<div class="w3-padding" style="text-align: left;height: 250px;font-size: 1.8em">{{card.text}}</div>
</div>
</div>
<div style="display: flex;justify-content: center;gap: 20px">
<button (click)="prevSlide()" class="w3-circle w3-hover-black w3-padding w3-margin-top w3-card" style="height: 60px;align-self: center;"><span class="bx bx-left-arrow" style="font-size: 2em;" ></span></button>
<button (click)="nextSlide()" class="w3-circle w3-hover-black w3-padding w3-margin-top w3-card" style="height: 60px;align-self: center;"><span class="bx bx-right-arrow" style="font-size: 2em;" ></span></button>
</div>
}
}
So now if I want to update the styling of the cards, I only have to it one time. All the data elements relevant to the card are pulled from the JSON array which makes updating the "learn more" section much easier. If I want to add more information, I just add it into the array as opposed to creating a new card in the template.
While I've been the development (and hopefully the user) aspect a bit more palatable, there is still more abstraction that can be done. Let me know if you're interested and I will take the code even further.
If you got this far, thanks for reading. I don't post that often so be nice - but I do welcome construction feedback.
Cheers!
SOCIAL SHARE CARD GENERATOR