I have a list of items along with their associated check-boxes.
And I'd like to achieve the following:
select/un-select all items in list using "Select All" checkbox.
select/un-select individual items in list.
when all items are selected and any of the selected items is clicked, u-nselect "Select All" checkbox.
Most of these steps are working as expected, except, when I :
select all list-items by checking the "Select All" checkbox
un-checking any of the selected items
then again checking "Select All" checkbox.
This causes that any list-item I un-selected before clicking on "Select All" checkbox, remain un-selected.
It looks as if (for some reason) the internal state of the checkbox is not being changed in this case.
Although, when :
all list-items are un-selected and I select any of the list-items
then check the "Select All" checkbox
it correctly selects all list-items. So I'm a little confused as why it's not working in the other case mentioned above.
Note: The main reason , I don't want to store state for every item in the list, is that I will be using this in a table with virtual-scroll. Which fetches data page by page.. So I don't have access to all items data, hence, I only store either items I manually selected or un-selected.
app.component.ts
import { Component, ChangeDetectionStrategy, ChangeDetectorRef } from '#angular/core';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class AppComponent {
constructor( private cdr: ChangeDetectorRef ) {
this.cdr.markForCheck();
}
public list = [
"item1", "item2", "item3", "item4", "item5"
];
public selected = {
ids: [],
all: false,
all_except_unselected: false
};
public toggleSelectItem( id: number, event: MouseEvent ): void {
if ( this.selected.all === true ) {
this.selected.all = false;
this.selected.all_except_unselected = true;
this.selected.ids = [];
}
if ( this.selected.all_except_unselected === true ){
this.selected.ids = [ ...this.selected.ids, id ];
} else if ( this.selected.all == false && this.selected.all_except_unselected == false ) {
if ( this.selected.ids.indexOf( id ) === -1 ) {
this.selected.ids = [ ...this.selected.ids, id ];
} else {
this.selected.ids = [ ...this.selected.ids].filter( itemId => itemId !== id );
}
}
console.log(this.selected.ids);
}
public isSelected( id: number ): boolean {
if ( this.selected.all === true ) {
console.log(id, 'selected all')
return true;
} else if ( this.selected.all_except_unselected === true ) {
console.log(id, 'selected all except unselected');
return true;
}
console.log(id, this.selected.ids.indexOf( id ) >= 0 ? 'selected' : 'unselected');
return this.selected.ids.indexOf( id ) >= 0;
}
public toggleSelectAll(): void {
if ( this.selected.all == false ) {
this.selected.ids = [];
}
this.selected.all = !this.selected.all;
this.selected.all_except_unselected = false;
console.log('selected all ', this.selected );
}
}
app.component.html
<input type="checkbox" [checked]="selected.all" (change)="toggleSelectAll()"> Select All
<br>
<br>
<div *ngFor="let item of list; let i = index" >
<input type="checkbox" [checked]="isSelected(i)" (change)="toggleSelectItem(i, $event)"> {{ item }}<br>
</div>
Old code link
Solution
The code is really messy. I would not bother with the booleans, you can make the checks on the array on the fly and the checkboxes will update the state correctly.
Here is the updated code, You can make further modifications but you can get the idea of how this works.
https://angular-hvbfai.stackblitz.io
import { Component, ChangeDetectionStrategy, ChangeDetectorRef } from '#angular/core';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class AppComponent {
constructor(private cdr: ChangeDetectorRef) {
this.cdr.markForCheck();
}
public list = [
"item1", "item2", "item3", "item4", "item5"
];
public selected = {
ids: []
};
public allSelected() {
return this.selected.ids.length === this.list.length;
}
public toggleSelectItem(id: number, event: MouseEvent): void {
if (this.selected.ids.indexOf(id) === -1) {
this.selected.ids = [...this.selected.ids, id];
} else {
this.selected.ids = [...this.selected.ids].filter(itemId => itemId !== id);
}
console.log(this.selected.ids);
}
public isSelected(id: number): boolean {
return this.selected.ids.indexOf(id) >= 0;
}
public toggleSelectAll(): void {
if (this.allSelected()) {
this.selected.ids = [];
} else {
let i = 0;
this.list.forEach(element => {
if (this.selected.ids.indexOf(i) === -1) {
this.selected.ids = [...this.selected.ids, i];
}
i++;
});
}
console.log('selected all ', this.selected);
}
}
HTML :
<input type="checkbox" [checked]="allSelected()" (change)="toggleSelectAll()"> Select All
<br>
<br>
<div *ngFor="let item of list; let i = index" >
<input type="checkbox" [checked]="isSelected(i)" (change)="toggleSelectItem(i, $event)"> {{ item }}<br>
</div>
Related
I want to render a component into a Angular dialog. This component is used on on a full page of my application and it has children.
When I add the selector into my modal, I've got an error : ExpressionChangedAfterItHasBeenCheckedError
This error happens on the page which loads the modal.
Is it possible to reuse a component into an Angular modal with his inputs and outputs ?
<!-- modal -->
<ng-container>
<div class="close-button-wrapper">
<button (click)="onCloseClick()" mat-icon-button>
<mat-icon>close</mat-icon>
</button>
</div>
<div class="dialog-content">
<picture-container></picture-container> <!-- not working ExpressionChangedAfterItHasBeenCheckedError -->
</div>
<div class="dialog-footer">
<raised-button-round class="action-button">Cancel</raised-button-round>
<raised-button-round icon="save" color="primary" class="action-button">OK</raised-button-round>
</div>
</ng-container>
<!-- component I want to reuse -->
#Component({
selector: 'picture-container',
template: `
<ng-container *ngIf="data$ | async as data">
<picture-presenter
[images]="data.pictures"
[tagImages]="data.tagImages"
[totalCountItems]="data.totalCountItems"
[tags]="data.tags"
[pageSize]="20"
[isSelectablePicture]="data.isSelectablePicture"
(displayableContent)="onChangeDisplay($event)"
(searchTagSelect)="onSearchTagSelect($event)"
(importPicture)="onImportPicture()"
(pictureAction)="onPictureAction($event)"
(changePage)="pageChange$.next($event)"
></picture-presenter>
</ng-container>
<upload-widget #uploadWidget (upload)="onUpload($event)"></upload-widget>
`,
styles: [``],
})
export class PictureContainerComponent implements OnDestroy, OnInit {
#ViewChild('uploadWidget') uploadWidget?: UploadWidgetComponent
data$: Observable<
Maybe<{
pictures: Maybe<PictureFragment>[]
tagImages: TagImage[]
tags: Maybe<{ name: string; count: number }>[]
totalCountItems: number,
isSelectablePicture: boolean
}>
> = of(null)
destroyed$ = new Subject<boolean>()
pageChange$ = new BehaviorSubject<PaginatorEvent>({})
personalImages$ = new BehaviorSubject<boolean>(true)
searchTags$ = new BehaviorSubject<string[]>([])
constructor(public store: Store) { }
ngOnInit(): void {
combineLatest([
this.store.select(selectStore),
this.store.select(selectGalleries),
this.searchTags$,
this.pageChange$,
this.personalImages$,
this.store.select(selectPictureUpdated),
])
.pipe(takeUntil(this.destroyed$))
.subscribe(
([store, galleries, tags, direction, isPersonal, picUpdated]) => {
if (!galleries) return
// TODO - High - Get right gallery
const galleriesId = galleries.map((gal) => gal?.id || '')
const filters = {
galleries: !isPersonal ? galleriesId : [],
tags: !isPersonal ? tags : [],
owner: store?.id,
}
this.store.dispatch(
loadPictures({
filters,
paginate: {
count: 15,
},
paginatorEvent: direction,
noCache: picUpdated != null ? true : false,
})
)
this.store.dispatch(
loadTags({
search: {
galleries: galleriesId,
owner: store?.id,
},
})
)
}
)
this.data$ = combineLatest([
this.store.select(selectPicturesState),
of(TAG_IMAGES),
this.store.select(selectPagination),
this.store.select(selectTags),
this.store.select(selectIsSelectablePicture),
]).pipe(
takeUntil(this.destroyed$),
map(([picturesState, tagImages, pagination, tags, isSelectablePicture]) => {
return {
pictures: picturesState?.pictures || [],
tagImages,
totalCountItems: pagination?.totalCount || 0,
tags,
isSelectablePicture
}
})
)
}
ngOnDestroy(): void {
this.destroyed$.next(true)
this.destroyed$.complete()
}
onSearchTagSelect(tags: string[]) {
this.pageChange$.next({})
this.searchTags$.next(tags)
}
onImportPicture() {
if (this.uploadWidget) {
this.uploadWidget?.openDialog({ pictureType: 'food' })
}
}
onPictureAction($event: {
picture: Maybe<PictureFragment>
action: 'edit' | 'delete'
}) {
if (!$event.picture) return
const picture = parsePictureFragmentToPictureInput($event.picture)
if (!picture) return
if ($event.action === 'edit') {
this.store.dispatch(
setUpdatePicture({
picture,
})
)
} else {
this.store.dispatch(setDeletePicture({ picture }))
}
}
onUpload($event: any) {
this.store.select(selectStore).subscribe((store) => {
this.store.dispatch(
setUploadPicture({
picture: { ...$event, owner: store?.id },
})
)
})
}
onChangeDisplay($event: 'import' | 'suggestions' | 'search') {
this.personalImages$.next($event !== 'search')
}
Thank you for your help
How do we keep the current tab when a page is refreshed? I have a component which is TransactionsDetailsComponent which renders the tabs I want and when the page is refreshed it should keep the current selected tab.
I have put my HTML and TS code below if someone is interested.
Any idea how to made this possible in angular?
#html code
<div headerTabs>
<div style="margin-top: -49px;">
<mat-tab-group animationDuration="0ms" [(selectedIndex)]="transactionTabIndex"
(selectedTabChange)="setTransactionTabIndex($event)">
<mat-tab label="{{tab}}" *ngFor="let tab of tabs">
<ng-template matTabContent>
<div class="mat-tab-shadow-container">
<div class="mat-tab-shadow"></div>
</div>
<div class="tab-content">
<app-transaction-overview *ngIf="tab === 'Overview' && transaction != null" [transaction]="transaction"
(teamAndUsers)="teamAndUsersEvent($event)" #transactionOverView
(saveTransactionEvent)="saveTransactionEvent($event)"></app-transaction-overview>
<app-deals-transaction *ngIf="tab === 'Deals' && transaction != null" [transaction]="transaction">
</app-deals-transaction>
<app-transaction-activity *ngIf="tab === 'Activity' && transaction != null" [transactionId]="transactionId" [isLocked]="transaction.isLocked">
</app-transaction-activity>
<app-document-management-list #DocumentManagementList *ngIf="tab === 'Documents' && transaction != null"
[dto]="transaction" [documentType]="documentType" [isLocked]="transaction.isLocked"></app-document-management-list>
</div>
</ng-template>
</mat-tab>
</mat-tab-group>
</div>
</div>
</app-page-header>
</div>
#tscode
export class TransactionsDetailsComponent implements OnInit {
documentType = EnumDocumentManagementType.TRANSACTION;
selectedIndex = 0;
transactionId: number;
propertyId: string;
#ViewChild('transactionOverView') transactionOverView: any;
isInProgress = false;
isEdit = false;
accountId: number;
accountRole: string;
tabs = ['Overview', 'Documents', 'Deals', 'Activity'];
transaction: any = null;
partialTransaction: any = null;
transactionTabIndex: number = 0;
/*page header component Inputs*/
// pageHeaderTitleData = {
// title: {
// primary: null
// }
// }
pageHeaderTitleData = {
title: {
main: "",
sub: ""
},
status: {
text: 'in progress',
},
otherInfo: []
}
breadCrumbsData = {
paths: [
{
text: "My Transactions",
link: "/transactions"
}
],
currentPage: ''
}
constructor(
private _notificationService: NotificationService,
private formBuilder: FormBuilder,
private _activatedRoute: ActivatedRoute,
private _transactionService: TransactionsService,
ngOnInit(): void {
this._activatedRoute.queryParams
.subscribe(
params => {
if (params.tab) {
this.transactionTabIndex = params.tab;
}
}
)
}
setTransactionTabIndex(e: any) {
this.setIsEdit(false);
this.transactionTabIndex = e.index;
}
}
You can use localStorage for this purpose, it will persist across multiple sessions until the localStorage is cleared.
Change setTransactionTabIndex to store the selection to localStorage
setTransactionTabIndex(e: any) {
this.setIsEdit(false);
this.transactionTabIndex = e.index;
localStorage.setItem('transactionTabIndex', e.index);
}
and then in the ngOnInit, check if you have transactionTabIndex set in localStorage and set it:
ngOnInit(): void {
this._activatedRoute.queryParams
.subscribe(
params => {
if (params.tab) {
this.transactionTabIndex = params.tab;
}
}
);
this.transactionTabIndex = +localStorage.getItem('transactionTabIndex') || 0
}
I have some checkboxes with parent-child structure whose values are coming from loop.Here when I click submit button I need to capture the selected/unselected value into below format(mentioned as commented output). When I click submit for preselected value is working fine, but if I remove checked from html(unselected on page load) and click submit that time it shows empty array.As per my requirement in project sometimes all checkboxes will be preselected,some times few selected/few unselected and some times all will be unselected based on condition and I need to capture selected/unselected value(same as output) on submit. Here is the code below
Demo - https://stackblitz.com/edit/angular-ar5apb?file=src%2Fapp%2Fapp.component.html
app.component.html
Checkbox -
<div class="col-md-3" id="leftNavBar">
<ul *ngFor="let item of nestedjson">
<li class="parentNav">{{item.name}}</li>
<li class="childData">
<ul>
<li *ngFor="let child of item.value; let i = index">{{child}}<span class="pull-right"><input checked type="checkbox" (change)="item.checked[i] = !item.checked[i]" ></span></li>
</ul>
</li>
</ul>
<div><button type="submit" (click)="getit()">submit</button></div>
</div>
app.component.ts
import { Component, OnInit } from "#angular/core";
#Component({
selector: "my-app",
templateUrl: "./app.component.html",
styleUrls: ["./app.component.css"]
})
export class AppComponent implements OnInit {
data: any;
nestedjson: any;
message = "";
test: any;
constructor() {}
ngOnInit() {
this.nestedjson = [
{ name: "parent1", value: ["child11", "child12"] },
{ name: "parent2", value: ["child2"] },
{ name: "parent3", value: ["child3"] }
];
this.nestedjson.forEach(
v => (v.checked = Array(v.value.length).fill(true))
);
}
getit() {
var duplicatePushArray = [];
this.nestedjson.forEach(item => {
let checked = [];
item.checked.forEach((isCkecked, i) => {
if (isCkecked) {
checked.push(item.value[i]);
}
});
if (checked.length > 0) {
duplicatePushArray.push({
name: item.name,
value: checked
});
}
});
console.log("Final Array: ", duplicatePushArray);
/* output: [{"name":"parent1","value":["child11","child12"]},{"name":"parent2","value":["child2"]},{"name":"parent3","value":["child3"]}]*/
}
}
<input type="checkbox" [checked]="item.checked[i]" (change)="item.checked[i] = $event.target.checked">
or try template forms
<input type="checkbox" [name]="'checked_' + i" [(ngModel)]="item.checked[i]">
Make sure to import the forms module
Here is a reduce function that starts with an empty array, filters the values based on the checked array and the pushes a new object into the results if there are any checked ones with the checked results.
getit() {
var duplicatePushArray = this.nestedjson.reduce((results, item) => {
const checked = item.value.filter((value, index) => item.checked[index]);
if (checked.length) {
results.push({ name: item.name, value: checked });
}
return results;
}, []);
console.log("Final Array: ", duplicatePushArray);
}
Here is an update to your StackBlitz https://stackblitz.com/edit/angular-aasetj?file=src/app/app.component.ts
I am using angular 6 application and i am trying to make a multiple select using input box without any third party plugin, jquery, datalist, select box and it is pure input box, typescript based.
HTML:
<div class="autocomplete">
<input name="suggestion" type="text" placeholder="User" (click)="suggest()" [formControl]="typeahead">
<div class="autocomplete-items" *ngIf="show">
<div *ngFor="let s of suggestions" (click)="selectSuggestion(s)">{{ s }}</div>
</div>
</div>
TS:
import { Component } from '#angular/core';
import { FormControl } from '#angular/forms';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
name = 'Angular';
suggestions: string [] = [];
suggestion: string;
show: boolean;
typeahead: FormControl = new FormControl();
fieldHistory: string [] = [];
suggest() {
this.suggestions = this.users;
this.show = true;
}
selectSuggestion(s) {
this.suggestion = "";
this.fieldHistory.push(s)
for (let i = 0; i < this.fieldHistory.length; i++)
this.suggestion = this.suggestion + " " + this.fieldHistory[i];
this.typeahead.patchValue(this.suggestion);
this.show = false;
}
users = ['First User', 'Second User', 'Third User', 'Fourth User'];
}
Here i need to delete the selected values like the angular material chips, User is able to select multiple values but he also can delete the wrongly selected values.
How can i make a delete option for each individual items to delete the wrongly selected values inside the input box?
Stackblitz link with multi select option https://stackblitz.com/edit/angular-dndhgv
Any edit in the above link to make the multi select with delete option would also be much more appreciable..
Please try this.
component.ts
import { Component } from '#angular/core';
import { FormControl } from '#angular/forms';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
name = 'Angular';
suggestions: string [] = [];
suggestion: string = '';
show: boolean;
typeahead: FormControl = new FormControl();
fieldHistory: string [] = [];
suggest() {
this.suggestions = this.users;
this.show = true;
}
selectSuggestion(s,status) {
this.suggestion = '';
if(status){
this.fieldHistory.push(s);
this.typeahead.patchValue(this.fieldHistory);
}else{
this.fieldHistory.forEach((element,index) => {
if(element == s){
this.fieldHistory.splice(index,1);
}
});
this.typeahead.patchValue(this.fieldHistory);
}
}
users = ['First User', 'Second User', 'Third User', 'Fourth User'];
}
Html
<div class="autocomplete">
<input name="suggestion" type="text" placeholder="User" (click)="suggest()" [formControl]="typeahead">
<div class="autocomplete-items" *ngFor="let s of suggestions">
<input type="checkbox" name='{{s}}' (click)="selectSuggestion(s,$event.target.checked)" />{{s}}
</div>
</div>
I'm not an Angular developer, but i tried to do solution.
Chosen phrases from suggested are storing in "chosen" variable. You can type something and divide it by "," to store it in "chosen" like in angular material chips.
Stackblitz
Maybe you should use a selected field for your users object, as following :
users = [
{
name: 'First User',
selected: false
},
{
name: 'Second User',
selected: false
},
{
name: 'Third User',
selected: false
},
{
name: 'Fourth User',
selected: false
}
]
The new html would be:
<div class="autocomplete">
<div (click)="showChoices()" style="border: solid 1px; display: flex">
<span *ngIf="!selectedUsers.length">Users</span>
<div *ngFor="let user of selectedUsers">
{{user.name}} <a style="cursor: pointer" (click)="unselectUser(user)">x</a>
</div>
</div>
<div class="autocomplete-items" *ngIf="show">
<div *ngFor="let user of users" [ngClass]="user.selected ? 'selected-suggestion' : ''" (click)="selectUser(user)">{{user.name}}</div>
</div>
</div>
And the .ts :
selectedUsers: { name: string, selected: boolean }[] = [];
show: boolean = false;
selectUser(user: { name: string, selected: boolean }) {
if (!user.selected) {
user.selected = true;
}
this.selectedUsers = this.users.filter((u) => u.selected);
console.log(this.selectedUsers)
}
unselectUser(user: { name: string, selected: boolean }) {
if (user.selected) {
user.selected = false;
}
this.selectedUsers = this.users.filter((u) => u.selected);
console.log(this.selectedUsers)
}
showChoices() {
if (this.selectedUsers.length) {
return;
}
this.show = !this.show;
}
Here is the working stackblitz.
I am creating a shopping cart in Angular 4 and want to check if a new product prod yet exists in the cartProducts array.
Here's my Component:
Component
import { Component, OnInit } from '#angular/core';
import { Router } from "#angular/router";
import { ProductsService } from '../service/products.service';
#Component({
selector: 'app-store',
templateUrl: './store.component.html',
styleUrls: ['./store.component.css']
})
export class StoreComponent implements OnInit {
itemCount: number;
cartProducts: any = [];
productsList = [];
constructor( private _products: ProductsService ) { }
ngOnInit() {
this.itemCount = this.cartProducts.length;
this._products.product.subscribe(res => this.cartProducts = res);
this._products.updateProducts(this.cartProducts);
this._products.getProducts().subscribe(data => this.productsList = data);
}
addToCart(prod){
this.cartProducts.hasOwnProperty(prod.id) ? console.log("Added yet!") : this.cartProducts.push(prod);
console.log(this.cartProducts)
}
}
My addToCart function which is fired by click works fine, but only from second time.
1 click - we add a product in the empty cartProducts array, the product is added
2 click - although the product is added, it is added again and there are two same products in the array now. I've got the array with the two same products.
3 click - console shows "Added yet!", now it recognizes that the product is in the array yet.
UPD
The product is an object of type:
{
"id" : "1",
"title" : "Title 1",
"color" : "white"
}
How to fix the issue?
hasOwnProperty is for checking if a key exists in an object, you're using it for an array. Use this instead:
addToCart(prod){
this.cartProducts.indexOf(prod) > -1 ? console.log("Added yet!") : this.cartProducts.push(prod);
console.log(this.cartProducts)
}
try this :
let idx = this.cartProducts.findIndex(elem => {
return prod === elem
})
if (idx !== -1) {
console.log("Added yet!")
} else {
this.cartProducts.push(prod);
}