Create a highly customizable component
Published: 2022-11-10 19:22:34

## Welcome to Angular challenges.
The aim of this series of Angular challenges is to increase your skills by practicing on real life exemples. Moreover you will have to submit your work though a PR which I or other can review; as you will do on real work project or if you want to contribute to Open Source Software.

The first challenge is to create an highly customizable component that you can reuse with any crazy ideas that the product team can come up with.

If you haven’t done the challenge yet, I invite you to try it first by going to [Angular Challenges](https://github.com/tomalaforge/angular-challenges/blob/main/apps/projection/README.md) and coming back afterward to compare your solution with mine. _(You can also submit a PR that I’ll review)_

---

We are going to implement a dashboard of multiples entities. _(Teacher, Student and City)_. A naive working implementation of Teacher card and Student Card has already been coded and we need to refactor it to make it easier to customize.

Each Card must have a background-color, image, a list of removable items and an add button.


![image of student component](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wo7e7i7uvj04lq5wm1c1.png)_Card component of Student Entity_

You will find below the current implementation of CardComponent and ListItemComponent
{% embed https://gist.github.com/tomalaforge/0d31620072a91b7f341b4efcbb848ba1.js %}


### Issues:

1. **Lots of ngIf condition**: this will be become harder and harder to implement new cards in the future. Each card will need a new condition.

2. You can **only** pass a name as input in your ItemListComponent. What will happen if the product team decides to add an icon, or multiple properties to display, or if you have a new button? You will have to add **new specific inputs** and **new if conditions**…

3. The constructor of the component contains very specific imports. We want a generic component _(called a presentational component)_, which doesn’t have any logic inside.

4. Component should be set to **OnPush** strategy.

5. Component is not **strongly typed**!

---

Let’s tackle each issue one by one :

## Issue 1:
To delete all **ifs** inside my html, we will need to project the content of our image from the parent to the card component. To handle this, Angular has a tag called **ng-content**. _(You can add a “select” attribute to be more specific on what you want to project from your parent)_
{% embed https://gist.github.com/tomalaforge/9636a150d75b0b2eb17d45da84f7a51f.js %}
We can now see that l.2–11 from our previous component has been replaced by a simple line. And on your parent component, you can simply add the img tag you want to project between your Card Component tags.


## Issue 1bis and 3:
What about all the if conditions inside our component. To resolve this issue, we need to add a **@Ouput() decorator** to emit an event to the parent component which will take care of doing its specific action.
{% embed https://gist.github.com/tomalaforge/16313241ad5f857641cbdfd8a8c43648.js %}


## Issue 2:
This is the most complex part of the exercice. We could move ngFor to our student component and use ng-content to project the result. This will work but we want to keep the list pattern inside card component because we want to add _(outside the scope of this exercice)_ generic logic and we don’t want to copy that logic in all our parent components.

We could add a ng-content inside ngFor. But this won’t work since we are missing the reference of the current item being displayed.

But Angular has us cover. **NgTemplateOutlet** is a directive which take a **TemplateRef as input**. We can then retrieve a custom template from the parent using **@ContentChild()** and pass it to our **outlet** directive. At this stage we are still missing our current items. And Angular has us cover again. We can pass a **context** to our custom template.
{% embed https://gist.github.com/tomalaforge/9d383614f7437695ac53579f9a781790.js %}

### Remarks:

- The first argument of **ngTemplateOutletContext** is **$implicit**. If you want to pass more, you must named them.
```html
[ngTemplateOutletContext]={$implicit: item; arg2: index}
<ng-template #rowRef let-teacher let-myIndex="arg2">
```

- let-teacher in ng-template is not typed. You can add a **ngTemplateContextGuard** for strong typing, but this will be part of another challenge. _(stay tuned!!)_

- **app-list-item** is still poorly customizable. But you know how to do it now. Go back to **Issue 1** and apply the same strategy to your component.


## Issue 4:
Now that our component only have Inputs and Outputs, there is no danger to simply add **changeDetection: ChangeDetectionStrategy.OnPush **in the decorator of our Component.

## Issue 5:
To type our list of item in our component, we can use Generic.
{% embed https://gist.github.com/tomalaforge/3439285259886913eacf5c90d798fa8c.js %}

---

And now our final **CardComponent** and **ListItemComponent** look like this: _(This allows us to customize any dashboard card at will)_
{% embed https://gist.github.com/tomalaforge/bde4fffaac94082cf783ebaf0239ac92.js %}


### Remarks:
- I’ve added the **host** property inside my decorator. Thus I can get rid of one level of encapsulation.

- Don’t forget to import NgIf, NgFor and NgTemplateOutlet to your imports array. Or you can simply import CommonModule.

---

Finally, the code of **StudentCard** looks like below:
{% embed https://gist.github.com/tomalaforge/30e1020d85785e8e9dbca65723807e17.js %}


### Remarks:
- All the **logics** is now located only **inside the smart component**. Easy to maintain.

- **app-list-item is fully customizable**. We can easily add an icon, or change the property we want to display

- Component use the **OnPush** strategy since we are using the **AsyncPipe** to retrieve our data from the store.

---

I hope you enjoyed this first challenge and learned from it.

> Other challenges are waiting for you at Angular Challenges. Come and try them. I’ll be happy to review you!

Follow me on [Medium](https://medium.com/@thomas.laforge), [Twitter](https://twitter.com/laforge_toma) or [Github](https://github.com/tomalaforge) to read more about upcoming Challenges!