Create component template to use it multiple times - Angular - html

This question might sound dumb, but I'll try it anyways:
Is it possible to use a component multiple times, but with different content? Something like a template.
To be exact, I want to write the component only once, but then use it in different places with different content - e.g. (I don't know whether that makes any sense and if so, how to realize it) by getting some text from an allocated model to fill a div, so that I can solely add a further model instead of editing the component itself?

Make use of the <ng-content>. Illustration:
app.component.html
<my-component>
<p>I'm getting projected into a component from outside because of ng-content</p>
</my-component>
my.component.html
<p>Data from my own component</p>
<ng-content></ng-content>
<p>Data from my own component</p>
By use of the <ng-content> you can project data from outside to within your component. You can make use of this in multiple ways, without changing the original component.

One way you can pass data to a component using input.
<my-component [text]="myText"></my-component>
And then in the component you can get the text using:
#Input() text: Person;
And display it in your template

You can use ng-content for this. Please find the below pseudo code
<!-- card.component.html -->
<div class="card">
<div class="card-header">
{{ header }}
</div>
<!-- add the select attribute to ng-content -->
<ng-content select="[card-body]"></ng-content>
<div class="card-footer">
{{ footer }}
</div>
</div>
<!-- app.component.html -->
<h1>APP COMPONENT</h1>
<card header="my header" footer="my footer">
<div class="card-block" card-body><!-- We add the card-body attribute here -->
<h4 class="card-title">You can put any content here</h4>
<p class="card-text">For example this line of text and</p>
This button
</div>
<card>

Related

Angular - DragDropModule not working inside templates

I would like to use DragDropModule for my angular application so I can move panels that are stored inside templates (for practical purposes as well as for recursive plotting of child elements).
The problem that I have is that cdkDrag can't find the correct cdkDropList to drop into if cdkDrag is hidden within a template and is not directly nested under the HTML element. Example:
<div
cdkDropList
[cdkDropListData]="expanded.activities"
(cdkDropListDropped)="dropActivity($event)">
<div *ngFor="let activity of expanded.activities">
<ng-container [ngTemplateOutlet]="orangeProgramActivity"></ng-container>
</div>
</div>
<ng-template #orangeProgramActivity>
<div cdkDrag>This is just a test</div>
</ng-template>
With this code example, the orangeProgramActivity can be dragged anywhere but doesn't drop into the correct dropList as cdkDrag keyword can't find any droplist within its own template.
In the second example, everything works correctly and the item gets dropped into correct dropList:
<div
cdkDropList
[cdkDropListData]="expanded.activities"
(cdkDropListDropped)="dropActivity($event)">
<div *ngFor="let activity of expanded.activities">
<div cdkDrag>This is just a test</div>
</div>
</div>
I would like to achieve the same functionality as in the second example, but with the use of templates because I really need them for recursion. Unfortunately, I can't reveal the whole code as my employer wouldn't be happy with that.
All I need is some static reference for my cdkDrag that is inside a template, to point onto a correct element with dropList that is outside of the template.
These are the only solutions that I found on the internet and they don't seem to work for me:
Material 7 Drag and Drop ng-template incompatibility
CdkDragDrop and ngTemplateOutlet
This is my first question on Stack Overflow and I'm new to angular, so I'm sorry for any confusion in my post, and thanks for any help in advance!
This might not work for all use cases, but you could just write it like this:
<div
cdkDropList
[cdkDropListData]="expanded.activities"
(cdkDropListDropped)="dropActivity($event)">
<div *ngFor="let activity of expanded.activities" cdkDrag>
<ng-container [ngTemplateOutlet]="orangeProgramActivity"></ng-container>
</div>
</div>
<ng-template #orangeProgramActivity>
<div>This is just a test</div>
</ng-template>
I was lucky that I only need to reorder elements inside an array in which they are and I don't need to transfer them into other containers. So for my "root" activities, I used your simple solution to wrap it all in a draggable element.
<div cdkDropList [cdkDropListData]="expanded.activities" (cdkDropListDropped)="dropActivity($event)">
<!-- For every object in array make a container with template and an icon that will serve as a handle -->
<div *ngFor="let activity of expanded.activities">
<div cdkDrag>
<div cdkDragHandle>
<!-- Handle icon from FontAwesome -->
<i class="fas fa-grip-vertical"></i>
</div>
<!-- Container with a template that we need -->
<ng-container [ngTemplateOutlet]="orangeProgramActivity" [ngTemplateOutletContext]="{act: activity}"></ng-container>
</div>
<!-- Recursive template for children -->
<ng-container [ngTemplateOutlet]="childActivitiesRecursion" [ngTemplateOutletContext]="{act: activity}"></ng-container>
</div>
And for my recursion, I created a droplist inside a template that serves only for reordering these children activities.
(I had problems with pasting template so the ng-template tag is not terminated at the end of the code)
<ng-template let-act="act" #childActivitiesRecursion>
<!-- Droplist inside a template for reordering child activites -->
<div cdkDropList [cdkDropListData]="act.childActivities" (cdkDropListDropped)="dropActivity($event)">
<!-- Iterates through all child activities -->
<div *ngFor="let childActivity of act.childActivities">
<div cdkDrag>
<div cdkDragHandle>
<i class="fas fa-grip-vertical"></i>
</div>
<!-- Different template for child activities -->
<ng-container [ngTemplateOutlet]="blueProgramActivity" [ngTemplateOutletContext]="{act: childActivity}"></ng-container>
</div>
<!-- Recursion for even deeper child activites -->
<ng-container [ngTemplateOutlet]="childActivitiesRecursion" [ngTemplateOutletContext]="{act: childActivity}"></ng-container>
</div>
</div>
I will accept Richard's answer as a solution because if you are able to solve it like this, it's definitely the most elegant solution you can do. But there would be still an issue if you needed to transfer items into different containers after using templates for recursion!

Using a conditional in HTML/Typescript?

In my project I'm using a mat-dialog to display a description of an object. The objects are generated through ngFor, like this:
<mat-card id="CARDBOX" *ngFor="let box of box">
<img class="logoy" src="{{box.image}}" height=35px>
{{box.button_name}}
<input type="image" id="info" title="Click for description" src="{{box.info}}" (click)="openDialog()" height=20px/>
</mat-card>
It's a basic card object that has an info icon that when clicked, opens the dialog, which looks like this:
<title mat-dialog-title></title>
<div mat-dialog-content *ngFor="let box of box">
{{box.description}}
</div>
<div mat-dialog-action>
<button mat-button (click)="onNoClick()">Close</button>
</div>
This works. However, it is displaying EVERY description in box, rather than just the corresponding one. I know this is because of the ngFor running through every item. Is there a way so that it will only display the one correct description, perhaps through use of some kind of conditional? I would ideally like to keep everything as abstracted as possible, I figured using some kind of conditional in the HTML would make the most sense but I'm not sure if that exists? Please let me know if you need more detail.
<div mat-dialog-content *ngFor="let box of box">
{{box.description}}
</div>
Your ngFor directive is looping through with an element whose name (and thus its reference if I'm not making a mistake here) is equal to its container.
Have you tried this?
<div mat-dialog-content *ngFor="let boxEl of box">
{{boxEl.description}}
</div>
Your code might not be able to differentiate a "box" (element) from a "box" iterable.

Angular6 create reusable html artefacts

I am pretty new to angular, so please be patient.
I want to create a set of ui elements. Lets say, I have a section which is like this:
<section>
<h2 class="section-title">Title</h2>
<... html to make section collapsible .../>
<div class="section-body">
content goes here
</div>
</section>
Now everytime I want to use this, I have to copy paste it...
Is it somehow possible that I create my own template for this?
<my-section title="Some Title" collapsible="true">
content goes here
</my-section>
Please provide your code logic.
But I guess you are looking for iteration through some data, while the data in the example here is the items
<section>
<h2 class="section-title">Title</h2>
<... html to make section collapsible .../>
<div class="section-body" *ngFor="item of items">
{{ item }}
</div>
</section>

Creating boxes in Angular via *ngFor

I'm new to Angular, Bootstrap and front-end programming.
I'd like to create several boxes.
After clicking on the box, detailed view of it should display above all boxes section.
This is what I'm trying to create:
and then after clicking on box number 1:
But now it looks like this:
I'm creating these boxes via Angular *ngFor loop like this:
<div class="container text-center" id="cryptocontainer">
<div class="row" *ngIf="coins">
<div *ngFor="let coin of objectKeys(coins); let i = index" id="currencybox" class="col-md-2" (click)="coinDetails(coin,i)">
<img id="image" [src]="getImage(coin)" class="img-responsive">
<div class="cryptoname">{{ coin }}</div>
<div class="cryptoprice">{{ coins[coin].USD | currency:'USD':true}}</div>
<div class="details" *ngIf="detailToggle[i]" no-lines>
<div class="row">
<div class="col">
<div class="label">CHANGE(30 dni)</div>
<canvas id="canvas{{i}}">{{ chart[i] }}</canvas>
</div>
</div>
<div class="row" id="details">
<div class="col-sm">
<div class="label">MARKET CAP</div>
<div class="answer">{{details.MKTCAP}}</div>
</div>
<div class="col-sm">
<div class="label">CHANGE(24h)</div>
<div class="answer">{{ details.CHANGE24HOUR }} ({{ details.CHANGEPCT24HOUR }}%)</div>
</div>
<div class="col-sm">
<div class="label">MAX (24h)</div>
<div class="answer">{{ details.HIGH24HOUR }}</div>
</div>
</div>
</div>
</div>
</div>
</div>
It's difficult to answer specifically without seeing your component code, but what you are probably missing, conceptually, is an understanding of how to break a UI like this into components and then pass data between those components in the "Angular" way. There are several ways to attack this. Ideally, I think you would move the details div to a separate component that has an input that receives the details, then create a separate component for the clickable divs that have an output that fires a custom event when clicked. These would both be instantiated in the template of a parent component, which would hold the current state of the clicked detail.
So, when you click a clickable component, it passes its details data back up to the parent, where it's set on the details property. Since the input on the display/canvas component is bound to that property, angular change detection will handle updating that component for you.
Here is a stackblitz with a simplified example of the communication pattern I am describing. You could also do something like create a shared service that you inject into both and then update that when a component is clicked.
https://angular-azmcxt.stackblitz.io
Stackblitz Editor Link

Angular 2 include component html in a component inherited template

I have a component A with its html/ts file. When I inherit a second component B from A, this will take all properties and method on the first. If I want to use the component A html, I can reference the comp A html in the templateUrl property.
I have a problem. I want use the component A html, but I want extend it. So my idea is "include" the first component html to the second. It's possible in Angular2? Is there another way?
I don't want to create an instance of component A in the component B. I want only the html markup.
EDIT:
In this example there is my problem:
https://stackblitz.com/edit/ng-content-projection-lzjwea
when I inherited the Hello2 ts, If I create an instance of hello component in hello2 html, it take its name property. I found three solutions:
Change all properties that need to be used in all inherit component to input and inject the
Duplicate html code
Find a way to reference the html of first component without creating an instance of it.
I think the best solution is the third. But I don't know a way to do it..
ng-content could be used to project dynamic content in a component.
For example, consider the following
hello.component.html
<div id='border'>
<h1>Base Component</h1>
<p>ng-content could be used for projecting dynamic content within a component</p>
<ng-content>
<!-- Dynamic content goes here -->
</ng-content>
</div>
So, now whatever that is in between
<hello>
<!-- dynamic html here -->
</hello>
app.component.html
<hello>
<div style="border: 2px solid red">
<h2>Child Component</h2>
<button id="sample1"> Sample 1 </button>
<button id="sample2"> Sample 2 </button>
</div>
</hello>
Example
Hope this helps