How to create material cards on button click? - html

I'm trying to create mat-cards when clicking on a button.
This is the information, which should be in the mat-card (this information comes from a service).
blockHash: "iejg5gpylg6l9gjxor3bnvigs0ipaonr"
blockNumber: 1
previousBlock: "00000000000000000000000000000000"
transactions: Array (1)
0 {sender: "10", recipient: null, amount: null, fee: null}
At the beginning the section, where the mat cards are at should be completely empty. When clicking on a button, that section should be filled with one mat-card; when clicking again, a second mat-card should appear and so on.
This is the block with the information (in another component), which gets send to the component, which should add up the material cards.
This is how it should look like (This is just hardcoded at the moment).
What's an elegant way to do that?

You should create a parent-component that displays multiple card-components.
https://stackblitz.com/edit/angular-zvjblo
parent-component
The parent-component holds your list of blocks and displays multiple card-components by supplying each card-component with the block data for that card. There is also a button to add a new block to the list.
template
<button (click)="addCard()">Add Card</button>
<app-block-card *ngFor="let block of blocks" [blockData]="block"></app-block-card>
code
import { Component, OnInit } from '#angular/core';
import { BlockData } from './block-data';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
blocks: BlockData[];
ngOnInit() {
this.blocks = [];
}
addCard() {
this.blocks.push({
blockHash: '9348534985720587',
blockNumber: 3,
previousBlock: "0000",
transactions: [
{
sender: 'sender',
recipient: 'recipient',
amount: 1,
fee: 200
}
]
});
}
}
card-component
The card-component receives the data of one block from the parent component and displays it.
template
<mat-card class="card">
<p>{{blockData.blockHash}}</p>
<p>{{blockData.blockNumber}}</p>
<p>{{blockData.previousBlock}}</p>
<p>{{blockData.transactions | json}}</p>
</mat-card>
code
import { Component, OnInit, Input } from '#angular/core';
import { BlockData } from '../block-data';
#Component({
selector: 'app-block-card',
templateUrl: './block-card.component.html',
styleUrls: ['./block-card.component.css']
})
export class BlockCardComponent implements OnInit {
#Input() blockData: BlockData;
constructor() { }
ngOnInit() {
}
}

You could be using a list, but I am not sure how to make it horizontal so that might require some tweaking.
But, I would say a drag and drop list would also be very fitting.
The documentation easily describes how to apply these with a horizontal view. But if the drag and drop feature is not for you, then the go with the list.
Using either you can run a *ngFor loop that can be performed upon arrays of data.
Example of such:
<div cdkDropList cdkDropListOrientation="horizontal" class="example-list"
(cdkDropListDropped)="drop($event)">
<div class="example-box" *ngFor="let card of cardArray" cdkDrag>
<mat-card class="example-card">
<mat-card-header>
<div mat-card-avatar class="example-header-image"></div>
<mat-card-title>Shiba Inu</mat-card-title>
<mat-card-subtitle>Dog Breed</mat-card-subtitle>
</mat-card-header>
<img mat-card-image src="https://material.angular.io/assets/img/examples/shiba2.jpg" alt="Photo of a Shiba Inu">
<mat-card-content>
<p>
text
</p>
</mat-card-content>
<mat-card-actions>
<button mat-button>LIKE</button>
<button mat-button>SHARE</button>
</mat-card-actions>
</mat-card>
</div>
</div>
In your case you will have to apply the card template inside the *ngFor div, and bind the data accordingly to your naming.
To add a new cards through a button you will simply have to add a new element to the array that you loop over, and it will appear when it has been added.

Related

Why ngfor directive is not working, though I have created proper object in Typescript class

I have created proper Ingredient object in Typesript, also proper "model" for the object. Though ngfor directive is not working. I am getting this error "NG0303: Can't bind to 'ngforOf' since it isn't a known property of 'a'" on inspecting in browser.
My model code
export class Ingredient {
constructor(public name: string, public amount: number){}
}
My TypeScript code
import { Component, OnInit } from '#angular/core';
import { Ingredient } from '../shared/ingredient.model';
#Component({
selector: 'app-shopping-list',
templateUrl: './shopping-list.component.html',
styleUrls: ['./shopping-list.component.css']
})
export class ShoppingListComponent implements OnInit {
ingredients: Ingredient[] = [
new Ingredient ("Apple", 5),
new Ingredient ("Tomato", 5)
];
constructor() { }
ngOnInit(): void {
}
}
My HTML code
<div class="row">
<div class="col-xs-10">
<app-shopping-edit></app-shopping-edit>
<hr>
<ul class = "list-group">
<a class = "list-group-item"
style = "cursor : pointer"
*ngfor = "let ingredient of ingredients">
{{ ingredient.name }}({{ ingredient.amount}})
</a>
</ul>
</div>
</div>
Page is loading properly, but ingredients are not loading. On inspecting in browser this error "NG0303: Can't bind to 'ngforOf' since it isn't a known property of 'a'" is coming.
Can someone please help.
Try moving the *ngFor inside the ul tag.
Edit: You have a typo.. It's *ngFor="", not *ngfor="".
If you are inside AppModule check if you have BroswerModule in the imports array there.
If you are in some different module then check if CommonModule is a part of that module's array. CommonModule provides us with core Angular features like *ngIf or *ngFor in your specific case.
Also watch out, you typed *ngfor instead *ngFor.

Best practice for abstracting Angular HTML code?

In my Angular project I have a basic component
TS
#Component({
selector: 'app-cardboxes',
templateUrl: './cardboxes.component.html',
styleUrls: ['./cardboxes.component.scss']
})
export class CardboxesComponent implements OnInit {
constructor(public dialog: MatDialog) { }
ngOnInit() {}
}
In the HTML file, I use this component about 16 times. I have it completely hardcoded for 16 different "cardboxes", like so:
HTML
<mat-card id="CARDBOX">
<img class="logoy" src="assets/image" height=35px>
Box1
<input type="image" id="info" title="Click for description" src="assets/image2" height=20px/>
</mat-card>
<mat-card id="CARDBOX">
<img class="logoy" src="assets/image3" height=35px/>
Box2
<input type="image" id="info" title="Click for description" src="assets/image2.png" height=20px/>
</mat-card>
Essentially this renders a little box with a couple images, and a button that takes the user to an external link. There are 16 of these boxes typed into the HTML file, and everytime I want to add a new one, I have to rewrite essentially the same code with only the inputs different.
As you can see this format is heavily reused, but it is arduously hardcoded into the HTML file. Is there a way that I could abstract the reused code so that it is not tediously hardcoded in? How can I make a template for this that can be used multiple times? Going further, how can I add to, remove from, and alter this list of items?
In your component.ts file, you can add a property called something like: cards.
#Component({
selector: 'app-cardboxes',
templateUrl: './cardboxes.component.html',
styleUrls: ['./cardboxes.component.scss']
})
export class CardboxesComponent implements OnInit {
cards = [
{
'imageUrl': '/assets/image.png',
'link': 'link1.com',
... More Stuff YOu like
},
{
'imageUrl': '/assets/image1.png',
'link': 'link1.com',
... More Stuff YOu like
},
.. More items ...
];
constructor(public dialog: MatDialog) { }
ngOnInit() {}
}
Then in your html, you can loop through that cards array:
<mat-card id="CARDBOX" *ngFor="let card of cards">
<img class="logoy" src="{{ card.imageUrl }}" height=35px/>
Box2
<input type="image" id="info" title="Click for description" src="assets/image2.png" height=20px/>
</mat-card>
Note: I would also avoid adding the CARDBOX id to each element, as that is bad practice in HTML5.
Then your problem should be solved ... Let me know if it helps you.
Did not test it, but a ngFor can work wonders in this cases.
Make an array of a new interface cardDetails
export interface CardDetail {
id: number;
image: string;
link: string;
button: string;
input: string;
}
<mat-card *ngFor="let item of cardDetailArray">
<img class="logoy" src="{{item.image}}" height=35px>
{{item.button}}
<input type="image" id="info" title="Click for description" src="{{image.input}}" height=20px/>
</mat-card>
If it's literally only a diff on numbers. just make a number array[1,2,3,...x] and use the ngfor let item in numberArray , concatenate all things with the number variable.

Multiple Select component for Angular with list style

I need a select component like
The problem is they don't have it in Material Angular, so I tried using default HTML select inside the component. It works fine until I tried to destroy the view of the HTML select(for example when you redirect to other page), it will freeze the whole page for a couple of seconds(the larger the list the longer it will freeze).
First, anyone know the reason why Angular takes a while to destroy non material angular component? Then does anyone have a solution whether to make the freeze gone or appoint me to select component library that could be use in Angular perfectly? I really need the support of being able to select multiple items with click + shift
Here's my component code:
HTML:
<div class="chart">
<div class="toolbar">
<div class="row">
<i *ngIf="multiple" (click)="resetFilter()" class="option material-icons left">refresh</i>
<h4>Sample Id</h4>
<span class="option right"></span>
</div>
</div>
<div class="content">
<select *ngIf="!showSampleCSV" [multiple]="multiple" [size]="100" class="samples-list" [(ngModel)]="selectedSamples" (ngModelChange)="onSelect($event)">
<option *ngFor="let sampleID of sampleIDs" [value]="sampleID">{{sampleID}}</option>
</select>
<app-samples-text *ngIf="showSampleCSV" [samples]="selectedSamples" [multiple]="multiple" (filterSamples)="filterCSV($event)"></app-samples-text>
</div>
</div>
TS:
import { Component, OnInit, Input, Output, EventEmitter, ChangeDetectionStrategy, OnDestroy } from '#angular/core';
#Component({
selector: 'app-samples-list',
templateUrl: './samples-list.component.html',
styleUrls: ['./samples-list.component.css'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class SamplesListComponent implements OnInit, OnDestroy {
#Input() sampleIDs : string[] = [];
#Input() showSampleCSV : boolean;
#Input() selectedSamples : string[];
#Output() onSelectSamples = new EventEmitter<string[]>();
#Output() onUpdateSamples = new EventEmitter<string[]>();
#Input() multiple: boolean = true;
size = this.sampleIDs.length;
constructor() { }
ngOnInit() {
}
resetFilter() {
this.onSelectSamples.emit(this.sampleIDs);
}
onSelect(samples){
this.onSelectSamples.emit(samples);
}
filterCSV(samples){
this.onUpdateSamples.emit(samples.map(sample => sample.trim()));
}
ngOnDestroy() {
}
}
Problem illustration on stackblitz https://stackblitz.com/edit/angular-qojyqc?embed=1&file=src/app/app.component.html
Material does provide an option for multi select values
<mat-form-field>
<mat-label>Toppings</mat-label>
<mat-select [formControl]="toppings" multiple>
<mat-option *ngFor="let topping of toppingList" [value]="topping">{{topping}}</mat-
option>
</mat-select>
</mat-form-field>
For more information go Here

How to Show Placeholder if *ngFor Let Object of Objects from HTML Binding returns an empty array

I display data from a template that I create in another component (team-announcements.component)... in the main team.component.ts I use the selector for team-announcements, and add the [announcement]="announcements" and ngFor="let announcement of announcements" to load the data. If the array returns no data IE there are no announcements, how can I display a placeholder like "No Announcements"?
This is where I load the announcements in team.component.html. The data is served through an API service and is retrieved in "team.component.ts", the HTML for the objects in question is below.
team.component.ts (get announcement functions):
getAnnouncements() {
this.teamsService.getTeamAnnouncements(this.team.slug)
.subscribe(announcements => this.announcements = announcements);
console.log("announcements", this.announcements);
}
team.component.html
<div class="team-announcement">
<div class="announcement-title">Message of the Day</div>
<app-team-announcements
[announcement]="announcement"
*ngFor="let announcement of announcements">
</app-team-announcements>
</div>
This is how "app-team-announcements" above is templated in a separate file, "team-announcement.component.html" and is exported, and then used in the above code...
team-announcements.component.ts
import { Component, EventEmitter, Input, Output, OnInit, OnDestroy } from '#angular/core';
import { Team, Announcement, User, UserService } from '../core';
import { Subscription } from 'rxjs';
#Component({
selector: 'app-team-announcements',
templateUrl: './team-announcement.component.html'
})
export class TeamAnnouncementComponent implements OnInit, OnDestroy {
constructor(
private userService: UserService
) {}
private subscription: Subscription;
#Input() announcement: Announcement;
#Output() deleteAnnouncement = new EventEmitter<boolean>();
canModify: boolean;
ngOnInit() {
// Load the current user's data
this.subscription = this.userService.currentUser.subscribe(
(userData: User) => {
this.canModify = (userData.username === this.announcement.author.username);
}
);
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
}
team-announcements.component.html
<div class="announcement-text">
{{announcement.body}}
</div>
I am unsure of how or where to "If" check the array length to display a placeholder. Can anyone help?
If you want to hide it and display something else instead you can use the else property from *ngIf:
<div class="team-announcement">
<div class="announcement-title">Message of the Day</div>
<ng-container *ngIf="announcements.length != 0; else emptyArray">
<app-team-announcements
[announcement]="announcement"
*ngFor="let announcement of announcements">
</app-team-announcements>
</ng-container>
</div>
<ng-template #emptyArray>No announcements...</ng-template>
When you want an element with *ngFor to depend on a condition (*ngIf), a good alternative is to nest the element with an *ngFor in a <ng-container> with an *ngIf. A good thing about <ng-container> is that it wont actually be part of the DOM but will obey the *ngIf.
You could insert a div wich is only displayed when your array is empty:
<div class="team-announcement">
<div class="announcement-title">Message of the Day</div>
<app-team-announcements
[announcement]="announcement"
*ngFor="let announcement of announcements">
</app-team-announcements>
<div *ngIf="announcements.length===0"> No announcements </div>
</div>
Edit: Corrected the errors

Multi Tabs ionic2

i have an ionic2 app and i want to display an ion-tabs with multiple ion-tab from an array. This array i want to populate with a generic tab and pass it an array.
I have this component <multi-tab></multi-tab>:
<ion-tabs tabsPlacement="top" class="multi-tabs">
<ion-tab *ngFor="let tab of tabs" [tabTitle]="tab.title" [root]="tab.component"></ion-tab>
</ion-tabs>
And this is the ts file:
[some imports here]
export class Tab {
title: string;
component: any;
};
const tabs = [{title: 'Snacks', component: Snacks},
{title: 'Drinks', component: Drinks},
{title: 'Frozen', component: Frozen},
{title: 'Custom', component: customTab}];
#Component({
selector: 'multi-tab',
templateUrl: 'multitab.html'
})
export class multiTab {
tabs : Array<Tab>;
constructor(public modalCtrl: ModalController) {
this.tabs = tabs;
}
}
This is my custom tab component:
<ion-content>
<ion-list>
<ion-item *ngFor="let item of items">
<h2>{{item.title}}</h2>
<p>Quantity: {{item.units}}</p>
<ion-icon name="trash" item-right color="indigo100" (click)="onDelete(item)"></ion-icon>
</ion-item>
</ion-list>
<ion-fab center bottom>
<button ion-fab mini color="pink200" (click)="onAdd()"><ion-icon name="add"></ion-icon></button>
</ion-fab>
</ion-content>
And this the ts file:
[some imports]
const CustomItems = [{
'title': 'custom',
'units': 1
}];
#Component({
templateUrl: 'customTab.html'
})
export class customTab {
items : Array<Item>;
constructor(public modalCtrl: ModalController) {
this.items = CustomItems;
}
[some methods]
I want to use in all tabs the customTab and give it an array to initialize (the items array property) But the closest I've ever been is having this error:
Can't resolve all parameters for customTab(?)
when i tried:
constructor(public aux: Array<Item>) {
this.items = aux;
}
There are 2 options, either use the property rootParams (explained in depth here for example) or create a service that is injected in both components.
Both solutions have their merits, the first one is using the mechanism introduced explicitly for that purpose because ion-tabs are custom components and you can't use inputs and outputs. The second solution is the main mechanism of sharing data in angular applications (like a user session) but can seem as overkill for this small task.
There is an example of using rootParams in this commit.