Ionic: change ion-list items on click - javascript

Lets say I have 3 lists
list: 1 ) bus , plane
list: 2 ) [related to bus] slow , can't fly
list: 3) [related to plane] fast, can fly
In my Ionic Angular project I have successfully made the 1st ion-list. But how can I change the whole ion-list by clicking on the item inside it?
[I get it, its something to do with (click) function, but how I can affect the whole list using typescript]

Edit: I get what you want to achieve. You can do this by creating an intermediary list and using that list in your ngFor. That way you can just simply change the reference of the intermediary list to whatever list you like onClick
export class ListPage {
transportationTypes: string[] = ['bus', 'plane'];
busSpecs: string[] = ['slow', "can't fly"];
planeSpecs: string[] = ['fast', 'can fly'];
currentList: string[] = this.transportationTypes;
itemClicked(type): void {
if (type === 'bus') {
this.currentList = this.busSpecs;
} else if(type === 'plane') {
this.currentList = this.planeSpecs;
} else {
this.currentList = this.transportationTypes;
}
}
}
And in your HTML just call the itemClicked function
<ion-list *ngIf="currentList">
<ion-item *ngFor="let item of currentList" (click)="itemClicked(item)">
{{item}}
</ion-item>
</ion-list>

Related

ng2-dragula after adding new item it's getting displayed at the top

I am using ng2-dragula for drag and drop feature. I am seeing issue when I drag and drop first element(or any element) at the end and then try to add new item to the array using addNewItem button, new item is not getting added to the end. If i don't drop element to the end, new item is getting added at the end in UI.
I want new items to be displayed at the bottom in any scenario. Any help is appreciated.
This issue is not reproducible with Angular 7. I see this happening with Angular 9
JS
export class SampleComponent {
items = ['Candlestick','Dagger','Revolver','Rope','Pipe','Wrench'];
constructor(private dragulaService: DragulaService) {
dragulaService.createGroup("bag-items", {
removeOnSpill: false
});
}
public addNewItem() {
this.items.push('New Item');
}
}
HTML
<div class="container" [dragula]='"bag-items"' [(dragulaModel)]='items'>
<div *ngFor="let item of items">{{ item }}</div>
</div>
<button id="addNewItem" (click)="addNewItem()">Add New Item
I edited the stackblitz from the comment to help visualize the issue. This seems to be triggered when a unit is dragged to the bottom of the list. Updated stackblitz : https://stackblitz.com/edit/ng2-dragula-base-ykm8fz?file=src/app/app.component.html
ItemsAddedOutOfOrder
You can try to restore old item position on drop.
constructor(private dragulaService: DragulaService) {
this.subscription = this.dragulaService.drop().subscribe(({ name }) => {
this.dragulaService.find(name).drake.cancel(true);
});
}
Forked Stackblitz
Explanation
There is some difference between how Ivy and ViewEngine insert ViewRef at specific index. They relay on different beforeNode
Ivy always returns ViewContainer host(Comment node)ref if we add item to the end:
export function getBeforeNodeForView(viewIndexInContainer: number, lContainer: LContainer): RNode|
null {
const nextViewIndex = CONTAINER_HEADER_OFFSET + viewIndexInContainer + 1;
if (nextViewIndex < lContainer.length) {
const lView = lContainer[nextViewIndex] as LView;
const firstTNodeOfView = lView[TVIEW].firstChild;
if (firstTNodeOfView !== null) {
return getFirstNativeNode(lView, firstTNodeOfView);
}
}
return lContainer[NATIVE]; <============================= this one
}
ViewEngine returns last rendered node(last <li/> element)ref
function renderAttachEmbeddedView(
elementData: ElementData, prevView: ViewData|null, view: ViewData) {
const prevRenderNode =
prevView ? renderNode(prevView, prevView.def.lastRenderRootNode!) : elementData.renderElement;
...
}
The solution might be reverting the dragged element back to original container so that we can let built-in ngForOf Angular directive to do its smart diffing.
Btw, the same technique is used in Angular material DragDropModule. It remembers position of dragging element and after we drop item it inserts it at its old position in the DOM which is IMPORTANT.

Make all filter options 'active' on initialisation - Angular

https://stackblitz.com/edit/timeline-angular-7-wbff3f
The above stackblitz will demo a few pipe filters. If you click on locations of "North, South or East", it will populate the *ngFor loop - these filters can be selected on/off and use a "multifilter pipe". By default I want all these filter buttons "active" or "on" to show the complete populated list to begin with, allowing you to then click the filters off. Otherwise, the list if blank on first load!
.html file
<button [class.active]="entry.isLocationActive" (click)="toggle(entry.location); entry.isLocationActive = !entry.isLocationActive" class="btn btn-primary" type="button" *ngFor="let entry of timeLine | filterUnique">{{entry.location}}</button>
<my-timeline-entry *ngFor="let entry of timeLine | filter:filteredYear:'year'| multifilter:filteredLocations:'location' " timeEntryHeader={{entry.year}} timeEntryContent={{entry.detail}} timeEntryPlace={{entry.place}} timeEntryLocation={{entry.location}}></my-timeline-entry>
.ts file
filteredLocations: string[] = [];
toggle(location) {
let indexLocation = this.filteredLocations.indexOf(location);
if (indexLocation >= 0) {
this.filteredLocations = this.filteredLocations.filter((i) => i !== location);
} else {
this.filteredLocations.push(location);
}
}
I've played around with pre-populating the filteredLocations object with all the values, so it starts off with them all therefore active.
I managed to get the 'active' class to be active on first load but that did not carry through to the pipe filter. I'm sure its not a big change just can't see it, any help would be appreciated greatly!
filteredLocations = this.timeLine.map(a => a.location);
Adding this to the .ts file and replacing the empty filteredLocations: string[] = [];
I wasn't far off initially in the end. Hope it helps others.

Angular ngfor select all and deselect

I have items :
items: [
{name: 'Name'},
{name: 'Name'},
{name: 'Name'}
]
I am showing them in html :
<ion-checkbox (click)="click(); selectAllItems()" class="checkboxas" [(ngModel)]="allTobuli"></ion-checkbox>
<!-- this isnt in ngFor and it Selects all items -->
<ion-card *ngFor="let item of jsonObj" class="relative" (click)="compareTobuli(item,i);checkboxTobuli(item)">
<ion-checkbox (click)="compareTobuli(item,i)" [(ngModel)]="item.allTobuliItem" class="checkboxas absolut-check"></ion-checkbox>
</ion-card>
Each of them as you can see got checkbox - if you click on item it is added to another array and checkbox value becomes true. Another thing is that there is one checkbox out of ngFor which make another array same as items.
The problem is that I don't know how to change ngFor all checkboxes values when clicking on checkBox which isnt in ngFor.
I am using sets so it's my TS :
compareTobuli(item,i){
if (this.selected.has(item)) {
this.selected.delete(item);
console.log('Trinam', this.selected)
} else {
this.selected.add(item);
console.log('Pridedan', this.selected)
}
}
click() {
this.clicked = !this.clicked;
console.log(this.clicked);
return this.clicked;
}
selectAllItems() {
if(this.clicked == true) {
this.selected = new Set(this.jsonObj);
console.log(this.selected);
}
else {
this.selected = new Set;
console.log('deleted all', this.selected);
}
}
checkboxTobuli(item){
item.allTobuliItem = !item.allTobuliItem;
}
First of all:
Here is a working stackblitz that also select and deselect all values on click.
You already use (click)="click(); selectAllItems()" on your ion-checkbox to call click() and selectAllItems().
All you need to do is modifing the specific values (of the iterated items) inside these method.
selectAllItems() {
for(let i=0; i<this.jsonObj.length; i++) {
let item = this.jsonObj[i]; // this is your item from *ngFor="let item of jsonObj"
item.allTobuliItem = true; // select every single item
}
}
You need to update the value of allTobuliItem for each item in the jsonObj. Angular's data binding will take care of the rest. Example:
Template
<ion-checkbox
(click)="click(); selectAllItems()"
class="checkboxas"
[(ngModel)]="allTobuli"></ion-checkbox>
Component
public selectAllItems() {
this.jsonObj = this.jsonObj.map(item => {
item.allTobuliItem = this.allTobuli;
return item;
});
}

Angular ngFor not displaying changed array

in my application I have a Dashboard where the user can add and delete widgets. To do so I am using angular-gridster2. This works well, but when I am adding or deleting a widget from the dashboard first no widgets are displayed anymore and then only after a refresh the correct changes occur. My Widget list the ngFor directive is iterating through is build from an observable. These values change correctly.
This is my html:
<gridster #dashboardgrid [options]="options" class="widget-container">
<gridster-item *ngFor="let widget of widgetList()" (mouseup)="setCurrentWidgetId(widget.id)" [item]="widget.position" class="gridster-design drag-handler">
<!-- some stuff-->
</gridster-item>
</gridster>
In my ngOnInit I am subscribing from the observable:
this.dataService.currentDashboardId.subscribe(dashboardId => this.currentDashboardId = dashboardId);
this.dataService.currentSheetId.subscribe(sheetId => this.currentSheetId = sheetId);
this.dataService.projectData
.subscribe((project: Project) => {
this.project = project;
});
And this is the method returning the list of widgets which should be displayed:
widgetList(): Array<Widget> {
return this.project.dashboards
.find(x => x.id === this.currentDashboardId).sheets
.find(x => x.id === this.currentSheetId).widgets;
}
I really can't find the reason for this behaviour so if anybody does I appreciate that. Thanks in advance.
Did you try to debug it?
Try to change the foreach loop to a list, instead of a function
*ngFor="let widget of widgetList()"
*ngFor="let widget of list"
and in your ngOnInit:
this.list = this.widgetList()
this way you can debug it and see if your list contains any items. If it does, then I would look deeper into your gridster-item component and make sure that the
[item]="widget.position" has value according to the component logic
store the array in some variable let say widgetList
widgetList : any;
widgetList(): Array<Widget> {
return this.widgetList = this.project.dashboards
.find(x => x.id === this.currentDashboardId).sheets
.find(x => x.id === this.currentSheetId).widgets;
}
and use ngfor in html like this
*ngFor="let widget of widgetList"

Typescript Parent/Child

Angular2/Typescript - Parent/Child Directive(?)
I'm new to, and very much still learning, Angular2/Typescript/javascript. As a result, I'm not entirely sure how to title my question. The basis of my app is a card game. The premise (relative to my struggle) is that the game has 2 players and each player has a hand of 5 cards. I have API calls to build/return the hand of cards.
In my app.component template, I have 2 div blocks; one for each players' hand of cards. Currently, I have it working by building two distinct arrays of cards (named p1cards and p2cards). Here is the relative code for that:
<div class="player1Cards" id="player1Cards">
<ul class="list-group">
<div draggable *ngFor="let card of p1cards" [dragData]="card"
class="list-group-item">
<img src="{{cardBluePath + card.fileName}}">
</div>
</ul>
</div>
<div class="player2Cards" id="player2Cards">
<ul class="list-group">
<div draggable *ngFor="let card of p2cards" [dragData]="card"
class="list-group-item">
<img src="{{cardBluePath + card.fileName}}">
</div>
</ul>
</div>
And here is the actual export class of the entire AppComponent:
#Injectable()
export class AppComponent implements OnInit
{
#ViewChild(ModalComponent) errorMsg: ModalComponent;
errorMessage: string;
gameBoard: GameBoard[];
name: {};
mode = 'Observable';
//we need a gameboard (maybe not)
//we need an array of players
player:Player;
players:Player[] = [];
p1cards:Card[] = [];
p2cards:Card[] = [];
droppedItems = [];
//This tells us where the card images can be found
cardBluePath = "/assets/deck/Blue/";
cardRedPath = "/assets/deck/Red/";
//The boardService will handle our API calls
boardService;
//Initialize the API service
constructor(boardService:BoardService) {
this.boardService = boardService;
}
//On load...
ngOnInit()
{
//Create the game
this.boardService.createGame()
.subscribe(
error => this.errorMessage = <any>error);
//Create the players
this.createPlayer(0);
this.createPlayer(1);
}
createPlayer(player: number)
{
var playerName;
if (player == 0) {playerName = "Player1"} else {playerName = "Player2"};
//We'll make a call to the API to build the hand of cards
this.boardService.buildHand(player)
.subscribe(
cardList =>
{
var cardData = [];
cardData = JSON.parse(cardList.toString());
var i, itemLength, card
itemLength = cardData.length;
for(i=0;i<itemLength;i++)
{
let card = new Card();
Object.assign(card,
{
"cardNum":i,
"id": cardData[i].id,
"displayName": cardData[i].displayName,
"fileName": cardData[i].fileName,
"left": cardData[i].left,
"top": cardData[i].top,
"right": cardData[i].right,
"bottom": cardData[i].bottom,
"level": cardData[i].level,
"native": cardData[i].native
});
if (player == 0) {this.p1cards.push(card)} else {this.p2cards.push(card)};
////this.cards.push(card);
}
//Now we will create the player and feed it the hand
this.player = new Player(playerName);
if (player ==0) {this.player.cardHand = this.p1cards} else {this.player.cardHand = this.p2cards};
this.players.push(this.player);
}
);
}
//When a card is dropped...
onItemDrop(e: any, slot: any)
{
e.dragData.slot = slot;
//Update the object
this.boardService.playCard(slot, e.dragData.card)
.subscribe(result => {
//If the slot is open and the card is played, physically move the item
if (result == "true" )
{
this.droppedItems.push(e.dragData);
this.removeItem(e.dragData, this.p1cards);
}
else{
window.alert("Slot already occupied.");
//this.modalWindow.show()
//this.errorMsg.showErrorMessage("Slot already occupied.");
//this.errorMsg.show();
}
});
}
//Remove the card from the hand
removeItem(item: any, list: Array<any>)
{
let index = list.map((e) => {
return e.cardNum
}).indexOf(item.cardNum);
list.splice(index, 1);
}
}
The createPlayer function is really where the question begins. Currently, it will make the API call and parse the JSON back into an array of cards. Right now, the array of cards lives locally in the AppComponent (as p1cards or p2cards).
What I want to do instead is create a player objects (component) for each player, assign their respective hand of cards, and then put those players in an array. I had that part working (pieces of the code still exist above, but not all of it), but I hit a wall in my *ngFor to display the cards. In pseudocode, I understood what I needed to do, but in practice I couldn't figure it out.
I knew that div class player1Cards needed to be something like "let player of Players where name = player1", and then I needed to iterate over the player.cardHand[] array to display each of the cards. I tried quite a few things, but nothing worked.
So then, after a few hours of Google searching, I came to the conclusion that I needed a child view for the player to handle it. I currently have the following for that:
My player.html is:
<div draggable *ngFor="let card of cardHand" [dragData]="card" class="list-group-item">
<img src="{{cardBluePath + card.fileName}}">
</div>
And my player.ts is:
import { Component, Input, OnInit } from '#angular/core';
import { Card } from './card';
#Component({
selector: 'player',
templateUrl: './player.html',
})
export class Player implements OnInit
{
public cardHand: Card[];
cardBluePath = "/assets/deck/Blue/";
constructor
(
public name: string
)
{}
ngOnInit()
{
}
}
Then in my AppComponent template, I added the block (and Imported the player.ts)
I get an error message on App.Component "inline template:69:16 caused by: No provider for String!". Of all the Google research I performed and all of the changes I tried (ViewChild, Input/Output, Reference), I could not get it to work. I don't recall exactly what I did, but at one point I was able to eliminate the error, but the card array was not getting passed to the player (I wish I had committed or stashed that code).
In my mind, I understand the task at hand, I just can't make it happen. I know I need to create the Player object and feed it the respective cardHand in order for the player html to be able to parse it. I can do that fine in AppComponent, but once I try to do it as a parent/child, I get stuck.
Can someone help get me going in the right direction?
I know I need to create the Player object and feed it the respective
cardHand in order for the player html to be able to parse it. I can do
that fine in AppComponent, but once I try to do it as a parent/child,
I get stuck.
I agree that it makes sense to create an array of player objects, each with an array of cards in their hand. Something like this:
let player1 = {name:'Player 1',hand:[]}
let player2 = {name:'Player 2',hand:[]}
this.players = [player1, player2]
player1.hand.push(this.dealCard())
...
player2.hand.push(this.dealCard())
...
You can then create a player component to show the players (and even a card component to show their hand). In your root template you'll loop through the players, creating your player component and passing in the player data, including their hands.
<player-component *ngFor="let player of players" [player]="player"></player-component>
Make sure your player component has an input to receive the player data:
export class PlayerComponent implements OnInit {
#Input() player: Player;
constructor() { }
ngOnInit() { }
}
Then in the <player-component> template loop through the player's hand and render the cards:
<p>I am {{player.name}}. My hand is:</p>
<ul>
<li *ngFor="let card of player.hand">{{card}}</li>
</ul>
Here is a plunker showing a working demo that's a simplified version of this setup:
https://plnkr.co/edit/5Hz8P7poCb9Ju5IR6MWs?p=preview
You should be able to configure it to the specific setup of your game. Good luck!
in your player component, if you want to access another component:
1. that component needs a import statement on top
2. within the #component section, you need to include it in Providers
3. also include it in the constructor
For more information, visit here: https://angular.io/guide/dependency-injection

Categories