Iterative Development && Abstraction - Part 2

snackboy - Jun 10 - - Dev Community

In part 1, I wrote about taking blocks of static note cards and abstracting them into a carousel using a @for loop. These cards were utilized on a learn more page. The content for the cards were moved from the html template to the an JSON array in the controller (.ts) file.

Now that this is developed and working, another iteration of abstraction can be performed to see how the carousel could be made more useful. The idea here will be to move the carousel code out of the learn more template and move it into its own component.

Angular makes creating and implementing components super easy, barely an inconvenience [#RyanGeorge].

(Note that code snippets are at a high to mid level and are only provided to convey the idea of the development pattern.)

PS D:\thecookery> ng generate component ui/carousel
CREATE src/app/ui/carousel/carousel.component.html (24 bytes)
CREATE src/app/ui/carousel/carousel.component.spec.ts (633 bytes)
CREATE src/app/ui/carousel/carousel.component.ts (255 bytes)
CREATE src/app/ui/carousel/carousel.component.scss (0 bytes)
Enter fullscreen mode Exit fullscreen mode

Now that the component is created, the code from the learn more template can be moved into carousel component. The first thing to do is replace autogenerated carousel template content

<p>carousel works!</p>
Enter fullscreen mode Exit fullscreen mode

with the learn more content

@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>
    }
}
Enter fullscreen mode Exit fullscreen mode

Next, the carousel controller need to know about the slide controls. This is accomplished by moving from the learn more controller the nextSlide() and prevSlide() functions to the carousel and defining the necessary variables.

(Note that I am doing this a bit old school Angular and not using the newer standalone component. Old school means that then the component must be registered in the app modules area.)

import { Component } from '@angular/core';

@Component({
  selector: 'app-carousel',
  templateUrl: './carousel.component.html',
  styleUrl: './carousel.component.scss'
})


export class CarouselComponent {

    public slide: number = 1;
    public slideCount = 5;

    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;
        }
    }

}
Enter fullscreen mode Exit fullscreen mode

Looking at the code above, there is still a bit more work to do. The first thing is that the new carousel has no idea what the cards are as they are contained in the learn more controller. Another issue to address is the slide count as we know not all carousels would have 5 slides.

Addressing the first issue, the cards need to be passed into the carousel component. To do this, a cards input and card object need to be created in the carousel. Address the second issue, the slideCount value can be removed and replaced with the length of the cards array.

import { Component, Input } from '@angular/core';

export class Card {
    title: string = '';
    text: string = '';
    index: number = 0;  
}

@Component({
  selector: 'app-carousel',
  templateUrl: './carousel.component.html',
  styleUrl: './carousel.component.scss'
})

export class CarouselComponent {

    @Input() cards: Array<Card> = [];

    public slide: number = 1;

    nextSlide() {
        this.slide = this.slide + 1;
        if (this.slide > this.cards.length) {
            this.slide = 1;
        }
    }

    prevSlide() {
        this.slide = this.slide - 1;
        if (this.slide < 1) {
            this.slide = this.cards.length;
        }
    }

}
Enter fullscreen mode Exit fullscreen mode

Next, any CSS specific to the carousel needs to be added its CSS file.

The last step is to replace the existing learn more carousel code with new carousel component. The learn more template goes from this:


<style>

.card {
    background-image:
        linear-gradient(180deg, white 3rem, #F0A4A4 calc(3rem), #F0A4A4 calc(3rem + 2px), transparent 1px),
        repeating-linear-gradient(0deg, transparent, transparent 1.5rem, #DDD 1px, #DDD calc(1.5rem + 1px));
    box-shadow: 1px 1px 3px rgba(0,0,0,.25);
    background-color: white;
}

.card-title {
    font-size: 1.5em;
}

/*https://codepen.io/teddyzetterlund/pen/YPjEzP*/

</style>

<div class="w3-center" style="padding-top: 50px;margin-bottom:20px;width: 100%">
    <img class="w3-round-large w3-card" width=100 src="/assets/logo.png">
    <div style="font-size: 3vw;">learn more</div>
    <button class="w3-button w3-round-xxlarge w3-blue w3-hover-black" [routerLink]="['/register']">Become a member...</button>          
</div>

@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>
    }
}
Enter fullscreen mode Exit fullscreen mode

...to this:

<div class="w3-center" style="padding-top: 50px;margin-bottom:20px;width: 100%">
    <img class="w3-round-large w3-card" width=100 src="/assets/logo.png">
    <div style="font-size: 3vw;">learn more</div>
    <button class="w3-button w3-round-xxlarge w3-blue w3-hover-black" [routerLink]="['/register']">Become a member...</button>          
</div>

<app-carousel [cards]="cards"></app-carousel>
Enter fullscreen mode Exit fullscreen mode

The learn more controller can be reduced to:

import { Component } from '@angular/core';

@Component({
  selector: 'app-learn-more',
  templateUrl: './learn-more.component.html',
  styleUrls: ['./learn-more.component.scss']
})

export class LearnMoreComponent {

    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
        }

    ]

}
Enter fullscreen mode Exit fullscreen mode

...which is effectively the content of the cards.

By doing iterative development with abstraction along the way, we ended up with a carousel component that can be used elsewhere in the project. And just by modifying the one component - for example adding images to the card with an image key/value pair - the capability will persist to where ever that component is implemented.

Going through process of iterative development and abstraction helps you get to lower code solutions thus making your code more manageable, efficient and pliable.

There is one more abstraction I will share regarding this particular component - stay tuned for part 3. If you can think of other abstractions, please feel free to share in the comments.

Cheers!

. . . .