Related
I have scenario as below:
I want to achieve is:
When user click on All then all options shall be selected and when user click All again then all options shall be deselcted.
If All option is checked and user click any other checkbox than All then All and clicked checkbox shall be deselected.
When user selects 4 options one by one then All shall be selected.
HTML file
<mat-select placeholder="User Type" formControlName="UserType" multiple>
<mat-option *ngFor="let filters of userTypeFilters" [value]="filters.key">
{{filters.value}}
</mat-option>
<mat-option #allSelected (click)="toggleAllSelection()" [value]="0">All</mat-option>
</mat-select>
TS file
this.searchUserForm = this.fb.group({
userType: new FormControl('')
});
userTypeFilters = [
{
key: 1, value: 'Value 1',
},
{
key: 2, value: 'Value 2',
},
{
key: 3, value: 'Value 3',
},
{
key: 4, value: 'Value 4',
}
]
toggleAllSelection() {
if (this.allSelected.selected) {
this.searchUserForm.controls.userType
.patchValue([...this.userTypeFilters.map(item => item.key), 0]);
} else {
this.searchUserForm.controls.userType.patchValue([]);
}
}
Now, how to achieve 2nd and 3rd point
Stackblitz is: https://stackblitz.com/edit/angular-material-with-angular-v5-znfehg?file=app/app.component.html
Use code as below create function on click each mat-option and select()/deselect() all option:
See stackblitz:https://stackblitz.com/edit/angular-material-with-angular-v5-jsgvx6?file=app/app.component.html
TS:
togglePerOne(all){
if (this.allSelected.selected) {
this.allSelected.deselect();
return false;
}
if(this.searchUserForm.controls.userType.value.length==this.userTypeFilters.length)
this.allSelected.select();
}
toggleAllSelection() {
if (this.allSelected.selected) {
this.searchUserForm.controls.userType
.patchValue([...this.userTypeFilters.map(item => item.key), 0]);
} else {
this.searchUserForm.controls.userType.patchValue([]);
}
}
HTML:
<form [formGroup]="searchUserForm" fxFlex fxLayout="column" autocomplete="off" style="margin: 30px">
<mat-select placeholder="User Type" formControlName="userType" multiple>
<mat-option *ngFor="let filters of userTypeFilters" [value]="filters.key" (click)="togglePerOne(allSelected.viewValue)">
{{filters.value}}
</mat-option>
<mat-option #allSelected (click)="toggleAllSelection()" [value]="0">All</mat-option>
</mat-select>
</form>
Simply you can do it without adding a new option to your data source by adding a checkbox.
See the: Demo
import { Component, VERSION, ViewChild } from '#angular/core';
import { FormControl } from '#angular/forms';
import { MatSelect } from '#angular/material/select';
import { MatOption } from '#angular/material/core';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
#ViewChild('select') select: MatSelect;
allSelected=false;
foods: any[] = [
{value: 'steak-0', viewValue: 'Steak'},
{value: 'pizza-1', viewValue: 'Pizza'},
{value: 'tacos-2', viewValue: 'Tacos'}
];
toggleAllSelection() {
if (this.allSelected) {
this.select.options.forEach((item: MatOption) => item.select());
} else {
this.select.options.forEach((item: MatOption) => item.deselect());
}
}
optionClick() {
let newStatus = true;
this.select.options.forEach((item: MatOption) => {
if (!item.selected) {
newStatus = false;
}
});
this.allSelected = newStatus;
}
}
.select-all{
margin: 5px 17px;
}
<mat-form-field>
<mat-label>Favorite food</mat-label>
<mat-select #select multiple>
<div class="select-all">
<mat-checkbox [(ngModel)]="allSelected"
[ngModelOptions]="{standalone: true}"
(change)="toggleAllSelection()">Select All</mat-checkbox>
</div>
<mat-option (click)="optionClick()" *ngFor="let food of foods" [value]="food.value">
{{food.viewValue}}
</mat-option>
</mat-select>
</mat-form-field>
Another way to do this is with the #ViewChild selector to get the mat-select component and troggle the mat-options items selected or unselected. We need also a variable to save the selected actual status to select or unselect all the elements on every click. Hope will help.
import {MatOption, MatSelect} from "#angular/material";
export class ExampleAllSelector {
myFormControl = new FormControl();
elements: any[] = [];
allSelected = false;
#ViewChild('mySel') skillSel: MatSelect;
constructor() {}
toggleAllSelection() {
this.allSelected = !this.allSelected; // to control select-unselect
if (this.allSelected) {
this.skillSel.options.forEach( (item : MatOption) => item.select());
} else {
this.skillSel.options.forEach( (item : MatOption) => {item.deselect()});
}
this.skillSel.close();
}
}
<mat-select #mySel placeholder="Example" [formControl]="myFormControl" multiple>
<mat-option [value]="0" (click)="toggleAllSelection()">All items</mat-option>
<mat-option *ngFor="let element of elements" [value]="element">{{skill.name}}</mat-option>
</mat-select>
Here is an example of how to extend a material option component.
See stackblitz Demo
Component:
import { ChangeDetectorRef, Component, ElementRef, HostListener, HostBinding, Inject, Input, OnDestroy, OnInit, Optional } from '#angular/core';
import { MAT_OPTION_PARENT_COMPONENT, MatOptgroup, MatOption, MatOptionParentComponent } from '#angular/material/core';
import { AbstractControl } from '#angular/forms';
import { MatPseudoCheckboxState } from '#angular/material/core/selection/pseudo-checkbox/pseudo-checkbox';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
#Component({
selector: 'app-select-all-option',
templateUrl: './select-all-option.component.html',
styleUrls: ['./select-all-option.component.css']
})
export class SelectAllOptionComponent extends MatOption implements OnInit, OnDestroy {
protected unsubscribe: Subject<any>;
#Input() control: AbstractControl;
#Input() title: string;
#Input() values: any[] = [];
#HostBinding('class') cssClass = 'mat-option';
#HostListener('click') toggleSelection(): void {
this. _selectViaInteraction();
this.control.setValue(this.selected ? this.values : []);
}
constructor(elementRef: ElementRef<HTMLElement>,
changeDetectorRef: ChangeDetectorRef,
#Optional() #Inject(MAT_OPTION_PARENT_COMPONENT) parent: MatOptionParentComponent,
#Optional() group: MatOptgroup) {
super(elementRef, changeDetectorRef, parent, group);
this.title = 'Select All';
}
ngOnInit(): void {
this.unsubscribe = new Subject<any>();
this.refresh();
this.control.valueChanges
.pipe(takeUntil(this.unsubscribe))
.subscribe(() => {
this.refresh();
});
}
ngOnDestroy(): void {
super.ngOnDestroy();
this.unsubscribe.next();
this.unsubscribe.complete();
}
get selectedItemsCount(): number {
return this.control && Array.isArray(this.control.value) ? this.control.value.filter(el => el !== null).length : 0;
}
get selectedAll(): boolean {
return this.selectedItemsCount === this.values.length;
}
get selectedPartially(): boolean {
const selectedItemsCount = this.selectedItemsCount;
return selectedItemsCount > 0 && selectedItemsCount < this.values.length;
}
get checkboxState(): MatPseudoCheckboxState {
let state: MatPseudoCheckboxState = 'unchecked';
if (this.selectedAll) {
state = 'checked';
} else if (this.selectedPartially) {
state = 'indeterminate';
}
return state;
}
refresh(): void {
if (this.selectedItemsCount > 0) {
this.select();
} else {
this.deselect();
}
}
}
HTML:
<mat-pseudo-checkbox class="mat-option-pseudo-checkbox"
[state]="checkboxState"
[disabled]="disabled"
[ngClass]="selected ? 'bg-accent': ''">
</mat-pseudo-checkbox>
<span class="mat-option-text">
{{title}}
</span>
<div class="mat-option-ripple" mat-ripple
[matRippleTrigger]="_getHostElement()"
[matRippleDisabled]="disabled || disableRipple">
</div>
CSS:
.bg-accent {
background-color: #2196f3 !important;
}
Another possible solution:
using <mat-select [(value)]="selectedValues" in the template and set the selectedValues via toggle function in the component.
Working Stackblitz Demo.
Component
export class AppComponent {
selectedValues: any;
allSelected = false;
public displayDashboardValues = [
{key:'0', valuePositionType: 'undefined', viewValue:'Select all'},
{key:'1', valuePositionType: 'profit-loss-area', viewValue:'result'},
{key:'2', valuePositionType: 'cash-area', viewValue:'cash'},
{key:'3', valuePositionType: 'balance-area', viewValue:'balance'},
{key:'4', valuePositionType: 'staff-area' ,viewValue:'staff'},
{key:'5', valuePositionType: 'divisions-area', viewValue:'divisions'},
{key:'6', valuePositionType: 'commisions-area', viewValue:'commisions'},
];
toggleAllSelection() {
this.allSelected = !this.allSelected;
this.selectedValues = this.allSelected ? this.displayDashboardValues : [];
}
}
Template
<mat-select [(value)]="selectedValues" (selectionChange)="selectionChange($event)" formControlName="dashboardValue" multiple>
<mat-option [value]="displayDashboardValues[0]" (click)="toggleAllSelection()">{{ displayDashboardValues[0].viewValue }}</mat-option>
<mat-divider></mat-divider>
<div *ngFor="let dashboardPosition of displayDashboardValues">
<mat-option class="dashboard-select-option" *ngIf="dashboardPosition.key>0" [value]="dashboardPosition">
{{ dashboardPosition.viewValue }}
</mat-option>
</div>
</mat-select>
There are some problems with other answers. The most important one is that they're listening to the click event which is not complete (user can select an option via space key on the keyboard).
I've created a component that solves all the problems:
#Component({
selector: 'app-multi-select',
templateUrl: './multi-select.component.html',
styleUrls: ['./multi-select.component.scss'],
})
export class MultiSelectComponent<V> implements OnInit {
readonly _ALL_SELECTED = '__ALL_SELECTED__' as const;
#Input() options: ReadonlyArray<{ value: V; name: string }> = [];
#Input('selectControl') _selectControl!: FormControl | AbstractControl | null | undefined;
get selectControl(): FormControl {
return this._selectControl as FormControl;
}
#Input() label: string = '';
#Input() hasSelectAllOption = false;
selectedValues: (V | '__ALL_SELECTED__')[] = [];
constructor() {}
ngOnInit(): void {}
onSelectAllOptions({ isUserInput, source: { selected } }: MatOptionSelectionChange) {
if (!isUserInput) return;
this.setValues(selected ? this.options.map(o => o.value) : []);
}
private setValues(values: (V | '__ALL_SELECTED__')[]) {
const hasAllOptions = ArrayUtils.arraysAreSame(
values,
this.options.map(o => o.value),
);
if (!values.includes(this._ALL_SELECTED)) {
if (hasAllOptions) {
values = [...values, this._ALL_SELECTED];
}
} else if (!hasAllOptions) {
values = values.filter(o => o !== this._ALL_SELECTED);
}
setTimeout(() => {
this.selectedValues = values;
});
this.selectControl.setValue(values.filter(o => (o as any) !== this._ALL_SELECTED));
}
onSelectOtherOptions({ isUserInput, source: { selected, value } }: MatOptionSelectionChange) {
if (!isUserInput) return;
this.setValues(
selected ? [...this.selectedValues, value] : this.selectedValues.filter(o => o !== value),
);
}
}
<mat-form-field>
<mat-label>Choose some options</mat-label>
<mat-select multiple [value]="selectedValues">
<mat-option
*ngFor="let d of options"
[value]="d.value"
(onSelectionChange)="onSelectOtherOptions($event)"
>
{{ d.name }}
</mat-option>
<mat-option
*ngIf="hasSelectAllOption"
[value]="_ALL_SELECTED"
(onSelectionChange)="onSelectAllOptions($event)"
>
Select all
</mat-option>
</mat-select>
</mat-form-field>
I created a dropdown in an angular library to be used in our applications. I used angular-material2 for the dropdown (mat-select and mat-autocomplete).
I must be doing something wrong since I don't get the value when I use the dropdown in an app. I already tried pretty much everything I found on the net, with no result.
I commented most of it and I'm trying to solve the simplest version, but even in this case I'm not getting the value. Here is what I have now:
DropdownComponent.html library:
<mat-form-field appearance="outline">
<mat-select disableOptionCentering (selectionChange)="writeValue($event)" [multiple]="multi">
<mat-option *ngFor="let item of list" [value]="item">
{{ item }}
</mat-option>
</mat-select>
</mat-form-field>
DropdownComponent.ts library:
import {Component, OnInit, ViewEncapsulation, Input, forwardRef} from '#angular/core';
import {ControlValueAccessor, NG_VALUE_ACCESSOR, NG_VALIDATORS, FormControl} from '#angular/forms';
import {Observable} from 'rxjs';
#Component({
selector: 'pux-dropdown',
templateUrl: './dropdown.component.html',
styleUrls: ['./dropdown.component.scss'],
encapsulation: ViewEncapsulation.None,
providers: [
{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => DropdownComponent), multi: true },
{ provide: NG_VALIDATORS, useExisting: forwardRef(() => DropdownComponent), multi: true }
]
})
export class DropdownComponent implements OnInit, ControlValueAccessor {
#Input() list: any[] = [];
#Input() selected: any;
#Input() multi = false;
#Input() search = false;
items: any[] = [];
propagateChange = (_: any) => {};
validateFn: any = () => {};
constructor() { }
ngOnInit() {
this.items = this.list;
}
// Form
get value(): any { return this.selected; }
set value(newValue: any) {
if (newValue !== this.selected) {
this.writeValue(newValue);
this.registerOnChange(newValue);
this.selected = newValue;
}
}
registerOnChange(fn: any): void { this.propagateChange = fn; }
registerOnTouched(fn: any): void {}
setDisabledState(isDisabled: boolean): void {}
writeValue(obj: any): void {
if (obj !== null) {
this.selected = obj.value;
this.registerOnChange(this.selected);
console.log(this.selected);
}
}
validate(c: FormControl) { return this.validateFn(c); }
}
DropDownComponent.html application:
<div>
<form [formGroup]="selectForm" (ngSubmit)="saveSelect(selectForm)" #form1="ngForm">
<div>
<pux-dropdown formControlName="selectValue" [list]="list1"> </pux-dropdown>
</div> <br>
<button mat-flat-button="primary" type="submit" class="btn btn-primary">Save</button>
</form> <br>
<div>
Saved Value: {{selectValue | json}}
</div>
</div>
DropdownComponent.ts application:
import {Component, OnInit} from '#angular/core';
import {FormGroup, FormBuilder} from '#angular/forms';
const states = [
'Alabama', 'Alaska', 'Arizona', 'Arkansas', 'California', 'Colorado', 'Connecticut', 'Delaware',
'Florida', 'Georgia', 'Hawaii', 'Idaho', 'Illinois', 'Indiana', 'Iowa', 'Kansas', 'Kentucky',
'Louisiana', 'Maine', 'Maryland', 'Massachusetts', 'Michigan', 'Minnesota', 'Mississippi',
'Missouri', 'Montana', 'Nebraska', 'Nevada', 'New Hampshire', 'New Jersey', 'New Mexico',
'New York', 'North Carolina', 'North Dakota', 'Ohio', 'Oklahoma', 'Oregon', 'Pennsylvania',
'Rhode Island', 'South Carolina', 'South Dakota', 'Tennessee', 'Texas', 'Utah', 'Vermont',
'Virginia', 'Washington', 'West Virginia', 'Wisconsin', 'Wyoming'
];
#Component({
selector: 'app-dropdown',
templateUrl: './dropdown.component.html',
styleUrls: ['./dropdown.component.scss']
})
export class DropdownComponent implements OnInit {
list1;
multi: boolean;
selected: any;
search: boolean;
// Form
selectForm: FormGroup;
selectValue: string;
constructor(private fb: FormBuilder) { }
ngOnInit() {
this.list1 = states;
// Form
this.selectForm = this.fb.group({
selectValue: this.selected
});
}
saveSelect(formValues) {
console.log(formValues.value.selectValue);
this.selectValue = formValues.value.selectValue;
}
}
The console.log in writeValue in the library gives me the value I select in the dropdown, but the console.log in saveSelect shows me null. So the value isn't sent to the parent. Any idea what I'm doing wrong? Thank you in advance.
Your writeValue implementation needs to call the change function, but instead it is calling the registerOnChange function which is there for the form control to register its change function. Try something like this:
propagateChange: (value: any) => void = () => {};
registerOnChange(fn: (value: any) => void) { this.propagateChange = fn; }
writeValue(obj: any): void {
if (obj !== null && obj !== this.selected) {
this.selected = obj.value;
this.propagateChange(this.selected);
}
}
I using ui-routing for NG4 (each section is different ui-view).
In some section I use (waypoint.js - imakewebthings.com/waypoints/) with ngZone and I need wait for load all images and videos (in all ui-view in page) to get true page height. Is it posible and if is how can I do this?
Something like addEventListener('load', ...) not working because I have got some pages (each have multiple sections (ui-view)) and it's work only for first open page.
My code:
My page container (similar for evry page)
<ui-view name="header"></ui-view>
<ui-view name="moving-car"></ui-view>
<ui-view name="aaa"></ui-view>
<ui-view name="bbb"></ui-view>
for example moving-car component:
<section class="moving-car" id="moving-car" [ngClass]="{'is-active': isActive}">
<!-- content -->
</section>
TS:
import { Component, OnInit, AfterViewInit, OnDestroy, NgZone,
ChangeDetectorRef } from '#angular/core';
declare var Waypoint: any;
import 'waypoints/lib/noframework.waypoints.js';
#Component({
selector: 'app-avensis-moving-car',
templateUrl: './avensis-moving-car.component.html',
styleUrls: ['./avensis-moving-car.component.scss']
})
export class AvensisMovingCarComponent implements OnInit, OnDestroy, AterViewInit {
constructor(
private $zone: NgZone,
private ref: ChangeDetectorRef
) {}
private waypoint: any;
public isActive: boolean = false;
ngOnInit() {}
ngAfterViewInit(): void {
const activate = () => {
this.isActive = true;
this.ref.detectChanges();
};
this.$zone.runOutsideAngular(
() => {
/* this code below I want run after loaded all image in my
subpage (not only in this component) */
this.waypoint = new Waypoint({
element: document.getElementById('moving-car'),
handler: function (direction) {
activate();
this.destroy();
},
offset: '70%'
});
}
);
}
ngOnDestroy() {
this.waypoint.destroy();
}
}
I modify my ngAfterViewInit function and now it's look like working but I not sure if this is good way to resolve my problem
ngAfterViewInit(): void {
const activate = () => {
this.isActive = true;
this.ref.detectChanges();
};
const images = document.querySelectorAll('img');
let counter = 0;
for (let i = 0; i < images.length; i++) {
images[i].addEventListener('load', () => {
console.log('image loaded');
if (++counter === images.length) {
this.$zone.runOutsideAngular(
() => {
this.waypoint = new Waypoint({
element: document.getElementById('moving-car'),
handler: function (direction) {
activate();
this.destroy();
},
offset: '70%'
});
}
);
}
}, false);
}
}
We are going to implement a columnpicker and currently the only idea I have is to implement a ColumnPickableList that wraps a List. This would also hold a list of checkboxes that will enable the user to hide a column.
But before I go ahead do that I just wondered if I'm reinveting the wheel and if there is a simpler approach to solving this?
No simpler way. You'll have to implement your own List component for that
I'm following up on this since I'm struggling to make this work. Maybe it is because I have chosen to create a wrapper that filters the children to be displayed. So techically this approach doesn't implement its own List.
I have made a naive draft which I was hoping would work, but it fails to re-render the children even though they are changed/filtered in the parent component.
The console.log(..) in ColumnPickableList render()-function does print the correct children/props, but still the children won't update/re-render. Any clues as to why? Is this approach too naive?
So here is the current draft:
ColumnPicker.js
import React, { PropTypes } from 'react';
import Checkbox from 'material-ui/Checkbox';
export default class ColumnPicker extends React.Component {
constructor(props) {
super(props);
this.onCheck = this.onCheck.bind(this);
}
onCheck(column, isChecked) {
return this.props.onCheckboxChanged(column, isChecked);
}
renderCheckbox(column, onCheck) {
const disabled = (column.source === 'id');
return (<Checkbox key={column.source} label={column.source.toUpperCase()} onCheck={(event, checked) => onCheck(column, checked)} defaultChecked disabled={disabled} />);
}
render() {
const columns = this.props.columns || [];
return (
<div className="column-picker">
{columns.map((column) => {
return this.renderCheckbox(column, this.onCheck);
})}
</div>
);
}
}
ColumnPicker.propTypes = {
columns: PropTypes.array,
onCheckboxChanged: PropTypes.func,
};
ColumnPicker.defaultProps = {
columns: [], // [{source: myField, checked: true} ...]
};
ColumnPickableList.js:
import React, { PropTypes } from 'react';
import { connect } from 'react-redux';
import { List, Datagrid } from 'admin-on-rest';
import ColumnPicker from './ColumnPicker';
import { toggleColumnPickerStatusAction, initializeColumnPickerAction } from './actions';
export class ColumnPickableList extends React.Component {
componentWillMount() {
let columnSourceNames = [];
if (this.props.children) {
columnSourceNames = React.Children.map(this.props.children, (child) => {
return ({ source: child.props.source, checked: true });
});
}
const columnsDisplayed = columnSourceNames.filter((column) => column.source);
this.props.initializeColumnPicker(this.props.resource, columnsDisplayed);
}
shouldComponentUpdate(nextProps) {
const diff = nextProps.columnsDisplayed.filter((currentColumn) => {
return !this.props.columnsDisplayed.some((prevColumn) => {
return currentColumn.source === prevColumn.source && currentColumn.checked === prevColumn.checked;
});
});
return diff.length > 0;
}
removeHiddenColumns(children) {
return React.Children.map(children, (child) => {
if (!child.props.source) {
return child;
}
const column = this.props.columnsDisplayed.find((columnDisplayed) => {
return columnDisplayed.source === child.props.source;
});
if (this.props.columnsDisplayed.length === 0 || (column && column.checked)) {
return React.cloneElement(child);
}
return null;
});
}
render() {
const { children, ...rest } = this.props;
const displayedChildren = this.removeHiddenColumns(children);
console.log('Does it render? Rendering children', displayedChildren.map((child) => child.props.source));
return (
<div className="columnpickable-list">
<ColumnPicker columns={this.props.columnsDisplayed} onCheckboxChanged={this.props.handleCheckboxChanged} />
<List {...rest}>
<Datagrid>
{displayedChildren}
</Datagrid>
</List>
</div>
);
}
}
ColumnPickableList.propTypes = {
resource: PropTypes.string,
columnsDisplayed: PropTypes.array,
children: PropTypes.node,
initializeColumnPicker: PropTypes.func,
handleCheckboxChanged: PropTypes.func,
};
ColumnPickableList.defaultProps = {
columnsDisplayed: [],
};
function mapStateToProps(state) {
return {
columnsDisplayed: state.columnsDisplayed || [],
};
}
actions.js:
export const actions = {
INIT_COLUMNPICKER: 'INIT_COLUMNPICKER',
TOGGLE_COLUMNPICKER_STATUS: 'UPDATE_COLUMNPICKER_STATUS',
UPDATE_COLUMNPICKER_STATUSES: 'UPDATE_COLUMNPICKER_STATUSES',
}
export function initializeColumnPickerAction(resource, columns) {
return {
type: actions.INIT_COLUMNPICKER,
columns,
meta: { resource },
};
}
export function toggleColumnPickerStatusAction(column) {
return {
type: actions.TOGGLE_COLUMNPICKER_STATUS,
column,
};
}
reducers.js:
import { actions } from './actions';
function columnPickerReducer(state = [], action) {
switch (action.type) {
case actions.INIT_COLUMNPICKER: {
console.log('Init columnopicker reducer');
return action.columns;
}
case actions.TOGGLE_COLUMNPICKER_STATUS: {
const columns = state.map((column) => {
if (column.source === action.column.source) {
return { ...column, checked: !column.checked };
}
return column;
});
return columns;
}
default:
return state;
}
}
export default columnPickerReducer;
Example snippet of parent component:
...
<ColumnPickableList title="SillyStuff" {...props}>
<TextField source="id" />
<TextField source="NAME" />
<TextField source="SILLY_NAME" />
<TextField source="CHANGED_BY" />
<DateField source="CHANGED_TS" showTime />
<EditButton />
<DeleteButton />
</ColumnPickableList>
...
I've implemented a solution similar to this Answer however I'm using Angular2 conventions to define my page and not traditional Nativescript as in the referenced discussion.
The issue is that my ListView does not react to the notifyPropertyChange event to update the images when the upload into the image-cache is complete. The displayed images do not change from the placeholder image until I force it to update such as scrolling to hide the row and it updates when it reappears or leave the page and return.
I'm thinking the problem is that I'm mixing data/observable and the Angular2 Observable and/or the changeDetection: ChangeDetectionStrategy.OnPush. Suggestions?
Definition of ImageItem
import observable = require("data/observable");
import imageCache = require("ui/image-cache");
import imageSource = require("image-source");
import {Config, everliveConfig } from './config';
var cache = new imageCache.Cache();
cache.maxRequests = 10;
cache.placeholder = imageSource.fromFile("~/resource-seed/butterfly.jpg");
export class ImageItem extends observable.Observable
{
private _imageSrc: string
get imageSrc(): imageSource.ImageSource
{
var image = cache.get(this._imageSrc);
if (image)
{
return image; /* 3 */
}
cache.push(
{
key: this._imageSrc,
url: this._imageSrc,
completed: (image) =>
{
this.notifyPropertyChange('imageSrc', image);
console.log("this.notifyPropertyChange('imageSrc', image)");
}
});
return cache.placeholder;
}
constructor(imageId : string)
{
super();
everliveConfig.files.getDownloadUrlById(imageId)
.then( url => {
this._imageSrc = url;
});
}
}
Layout of my component
<GridLayout rows="32, 32, *" columns="*">
<Label row="0" text='Points of Interest' class='page-title' horizontalAlignment="center" > </Label>
<Label row="1" text='Tap to learn more' class='page-subtitle' horizontalAlignment="center" > </Label>
<ListView row="2" [items]="areas" class="area-group" (itemTap)="onItemTap($event)">
<template let-area="item">
<GridLayout rows="100" columns="38, 100, *" orientation="horizontal">
<Button col="0" [backgroundImage]='checkButtonImagePath(area)'
class='area-checkmark-button'
(tap)='onToggleCheck(area)'>
</Button>
<Image col="1" [src]='area.imageItem.imageSrc' class='area-thumbnail' stretch="aspectFill"></Image>
<Label col="2" [text]='area.Name' class='area-title'> </Label>
</GridLayout>
</template>
</ListView>
</GridLayout>
Component Definition
import { Component, ChangeDetectionStrategy, Inject } from "#angular/core";
import { Router } from '#angular/router';
import { Area, AreaList } from '../../shared/model/area.list';
#Component({
moduleId: module.id,
selector: "area",
templateUrl: "area.html",
styleUrls:["area.common.css"],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class AreaComponent {
public areas ;
constructor(
private _router: Router,
#Inject('AreaList') public areaList: AreaList) {
this.areas = areaList.list;
}
public onItemTap(args) {
console.log("Item Tapped at cell index: " + args.index);
var area: Area = this.areaList.area(args.index);
this._router.navigate(["/detail", area.Key]);
}
public onToggleCheck(area:Area) {
area.isChecked = !area.isChecked;
}
public checkButtonImagePath(area):string {
return area.isChecked ? 'res://dawes_11' : 'res://dawes_21';
}
}
Definition of AreaList
import {Config, everliveConfig } from './config';
import { Injectable } from "#angular/core";
import {ImageItem} from '../../shared/model/image.item';
import { ObservableArray } from "data/observable-array";
export class Area {
public isChecked = false;
public imageItem : ImageItem;
constructor(
public Id: string,
public Name: string,
public Description: string,
public Image: string,
public Key: string
) {
this.imageItem = new ImageItem(this.Image);
}
}
#Injectable()
export class AreaList {
public list : Array<Area>;
constructor () {
var areaData = everliveConfig.data('Area');
areaData.get(null, data => {
var l = new Array<Area>();
data.result.forEach(area => {
l.push(new Area(area.Id, area.Name, area.Description,
area.Image, area.Key))
})
this.list = l;
},
err => {
console.log(err.message);
});
}
public area(index: number) : Area {
return this.list[index];
}
public areaByKey(key: string) : Area {
for (let area of this.list) {
if (key == area.Key) {
return area;
}
}
return null;
}
}