Mat-select is not showing the options: Angular 10 - angular10

I wonder what is wrong with my use of mat-select. Because it is not showing the list of options:
<form>
<mat-form-field appearance="standard">
<mat-label>New Error Type</mat-label>
<mat-select [(ngModel)]="selectedNewErrorCode" name="faultType">
<mat-option *ngFor="let faultType of faultTypes" [value]="faultType.code">
{{faultType.label}}
</mat-option>
</mat-select>
</mat-form-field>
<p>Selected error: {{selectedNewErrorCode}}</p>
</form>
The component which is displayed in a modal is the following.
/** Imports animations */
import {slideInAnimation} from '../../animations';
/** Imports models */
import {StackTraceView} from '../../objects/stack-trace-view.model';
import {FaultType} from '../../objects/fault-type.model';
#Component({
selector: 'app-consult-details',
templateUrl: './consult-details.component.html',
styleUrls: ['./consult-details.component.sass'],
animations: [slideInAnimation]
})
export class ConsultDetailsComponent implements OnInit {
constructor() {
}
public flux: StackTraceView;
public modifState = false;
/** Used in resetting the form */
originalFlux: string;
faultTypes: FaultType[];
/** determines if the original error flux should be visible or not */
public originalErrorVisible = false;
/** Sets the new fault type for reanalysing the stack trace */
selectedNewErrorCode: string;
ngOnInit(): void {
}
modifyFlux() {
this.modifState = !this.modifState;
}
sendFlux() {
console.log(`The flux changed to ${this.flux.originalFlux}`);
}
/** Reste the form to its original state */
resetForm() {
document.getElementById('toBeReset').innerHTML = this.originalFlux;
this.flux.originalFlux = this.originalFlux;
this.modifState = false;
}
/** Sets the visibility of the original error flux to flse if it is true and vice versa */
isOriginalErrorVisible() {
this.originalErrorVisible = !this.originalErrorVisible;
}
}
The entire component is displayed in a modal. The variable faultTypes is fed in when the modal is called in the parent component. The corresponding code in the parent component is the following:
const detailsContent = this.specificReportList.filter(
entry => entry.messageId === originalMessageId
)[0];
const modalRef = this.modalService.open(ConsultDetailsComponent, {
size: 'xl',
ariaDescribedBy: 'Details'
});
/** The input sata for the pop-up component */
modalRef.componentInstance.flux = detailsContent;
modalRef.componentInstance.originalFlux = detailsContent.originalFlux;
modalRef.componentInstance.faultTypes = this.faultTypeList;
modalRef.result.then((result) => {
this.closeResult = `Close with ${result}`;
}, (reason) => {
this.closeResult = `Dismissed ${this.getDismissReason(reason)}`;
});
}```
As a final comment the variable faulttypes is well fed in via the
parent component and when I use nromal select and option I do not have
any problem; The code works very well, the only problem is when I use
mat-select which is important for me beacuse It gives a unifrom look
and feel to my application.

Related

Stencil usage - how to load external dependent stencil component in stencil module - but not to add in final build

I am planning to create filter box web component using #stencil/core library.
In this filter component, i need to load external atomic web components like (dropdown, input, radio) implemented using #stencil/core itself. But i dont want these external web component to be part of final bundle of filter web component
Usage:
I want to use this filter component in host application using Angular.
And its responsibility of HOST application to import all atomic web component/external dependencies like dropdown/radio web component. Filter web component should not load these dependencies or take them in its build
Example:
**Dropdown **component's tsx file - bundled as dropdown npm package
import { Component, Prop, State, Event, EventEmitter, h } from '#stencil/core';
#Component({
tag: 'my-dropdown',
styleUrl: 'dropdown.scss'
})
export class myDropdown {
/**
* #public
* #property items
*
* Defines the data that we want to load into our dropdown area
*
*/
public items: Array<any> = [
{
heading: 'Virtual DOM',
description: 'A tree of custom objects representing a part of the DOM which can be acted upon quicker than manipulating the DOM itself'
},
{
heading: 'Async rendering',
description: 'Allows parts of a component state to be rendered asynchronously (I.e. via XHR)'
},
{
heading: 'Reactive data-binding',
description: 'Allows data binding to be implemented through binding a state variable to an onChange event which allows the state to be changed as the input value changes'
}
];
/**
* #public
* #property name
* #type String
*
* This will accept values supplied by a name attribute
* on the component HTML
*/
#Prop() name: string;
/**
* #type boolean
*
* This will track state changes (I.e. whether the
* dropdown component is open or closed)
*/
#State() toggle: boolean = false;
#Event({
eventName: 'itemClicked',
composed: true,
bubbles: true,
cancelable: true
}) itemClicked: EventEmitter;
/**
* #type EventEmitter
*
* Track component events (I.e. activation of dropdown component)
*/
#Event({
composed: true,
bubbles: true,
eventName: 'toggleDropdown',
cancelable: true
}) onToggle: EventEmitter;
/**
* #public
* #method toggleComponent
*
* This will manage the dropdown component event and state changes
*/
toggleComponent(): void {
this.toggle = !this.toggle;
// When the user click emit the toggle state value
// A event can emit any type of value
this.onToggle.emit({ visible: this.toggle });
}
onListItemClicked(ind) {
console.log(ind);
this.itemClicked.emit({ index: ind });
}
/**
* Create HTML representation of component DOM and return this
for output to the browser DOM
*/
render() {
return (
<div class="container">
<h2 onClick={() => this.toggleComponent()}>{this.name} {this.toggle ? <span>▲</span> : <span>▼</span>}</h2>
<ul class={this.toggle ? 'active' : 'inactive'}>
{this.items.map((item, index) => <li onClick={this.onListItemClicked.bind(this, index)}><h3>{item.heading}</h3><p>{item.description}</p></li>)}
</ul>
</div>
)
}
}
In my wrapper web component - MyComponent.tsx
import { Component, Prop, h, Listen } from '#stencil/core';
import { IButtonClickEvent } from '../simple-button/simple-button.model';
import 'dropdown';
#Component({
tag: 'my-component',
styleUrl: 'my-component.css',
shadow: true
})
export class MyComponent {
/**
* The first name
*/
#Prop() first: string;
/**
* The middle name
*/
#Prop() middle: string;
/**
* The last name
*/
#Prop() last: string;
private getText(): string {
return this.first + this.middle + this.last;
}
#Listen('buttonClicked')
onButtonClickHandler(evt:CustomEvent<IButtonClickEvent>){
console.log(evt)
}
#Listen('itemClicked')
itemClickedHandler(event: CustomEvent) {
console.log('Received the custom itemClicked event: ', event.detail);
}
render() {
return (
<div class="container" style={{
'font-size': '12px',
'padding':'10px',
'border':'2px solid red'
}}>
<div class="sc-abc abc">Hello, World! I'm {this.getText()}</div>
<simple-button type="raised" color="primary">Child</simple-button>
<my-dropdown name="Stencil key features"></my-dropdown>
</div>
);
}
}
I m loading this dropdown using 'import dropdown' - this works fine.
But issue with this is - stencil config considers this dropdown as well in final build of MyComponent. I want to ignore dropdown bundle to be included in final build.
Host application (Angular app) to install and load dropdown as per requirement
After some debugging found issue
Since i need to load dropdown component as external dependency, it cannot be imported in MyComponent tsx.
loaded external dropdown component in index.html directly via below script inclusion
<script type="module" src="/build/dropdown/dist/dropdown/dropdown.esm.js"></script>
<script nomodule src="/build/dropdown/dist/esm/dropdown.js"></script>
Also made changes in stencil config to copy dropdown component while working on local
import { Config } from '#stencil/core';
import { sass } from '#stencil/sass';
export const config: Config = {
namespace: 'demo',
outputTargets: [
{
type: 'dist',
esmLoaderPath: '../loader',
},
{
type: 'dist-custom-elements',
},
{
type: 'docs-readme',
},
{
type: 'www',
serviceWorker: null, // disable service workers
copy: [
{
src: '../node_modules/dropdown/',
dest: 'build/dropdown'
}
]
},
],
plugins: [sass()]
};

Angular 5 - Event emitter (Property 'update' does not exist on type ....)

I've got a component that I want to update when a person's name changes by emitting an event. My problem is the code doesn't compile because of an error. This is my code
ApplicationFormComponent
#Output() nameChange = new EventEmitter();
closeAccordion(isComplete: string, accordionToClose: string, accordion: NgbAccordion) {
if (accordionToClose === 'personal-details-panel') {
this.applicationStatusFlags.personalDetailsStatus = (isComplete === 'true');
this.nameChange.emit({ personId: this.personId });
}
}
ApplicationFormComponent.html
<name-display
[personId]="personId"
[placeHolderText]="'Hello'"
(nameChange)="update($event)">
</name-display>
NameDisplayComponent
import { Component, Input, OnChanges, SimpleChanges } from '#angular/core';
import { PersonService } from "../../../service/person.service";
#Component({
selector: 'name-display',
templateUrl: './NameDisplay.component.html',
providers: [PersonService]
})
export class NameDisplayComponent implements OnChanges {
constructor(private readonly personService: PersonService) { }
#Input() personId;
#Input() placeHolderText: string = "";
forename: string = "";
ngOnChanges(changes: SimpleChanges): void {
if (changes["personId"]) {
this.personService.getPersonDetails(this.personId).subscribe((res: IPersonDetails) => {
this.forename = res.forenames;
});
}
};
update(personId: number) {
alert("update name");
this.personService.getPersonDetails(personId).subscribe((res: IPersonDetails) => {
this.forename = res.forenames;
});
}
}
My problem is basically when I use angular cli with the command ng server --aot, it doesn't compile because of this error:
ERROR in src\app\component\ApplicationForm\ApplicationForm.component.html(42,9): : Property 'update' does not exist on type 'ApplicationFormComponent'.
I've written a similar component that uses an event emitter which doesn't have this problem, so I'm stuck with how to fix the error.
Any ideas?
It is because you are passing $event to method.
(nameChange)="update($event)"
But it accepts number.
update(personId: number) {
alert("update name");
}
Please change the method as below.
update(event:any) {
const personId = event as number
alert("update name");
}

ngx-chart error "TypeError: Object(...) is not a function"

I am trying to implements some statistics in my develepping platform and I try to use ngx-charts to display them. However I get an error and I can't figure out why.
I am using storedProcedures for MySQL statistics which I call from Java Restful Backend and return them in Angular 5 front-end. The returned table has the following two fields: Date and number of incidents per day. So the table returned by the backend has those two columns.
My code for the component rendering the chart is the following:
import {Component, OnInit} from '#angular/core';
import {StatisticsService} from '../../statistics.service';
class Data {
private _name: string;
private _value: number;
get name(): string {
return this._name;
}
set name(value: string) {
this._name = value;
}
get value(): number {
return this._value;
}
set value(value: number) {
this._value = value;
}
}
#Component({
selector: 'app-daily-incidents-statistics',
templateUrl: './daily-incidents-statistics.component.html',
styleUrls: ['./daily-incidents-statistics.component.css']
})
export class DailyIncidentsStatisticsComponent implements OnInit {
view: any[] = [700, 400];
data: any[] = [];
// options
showXAxis = true;
showYAxis = true;
gradient = false;
showLegend = false;
showXAxisLabel = true;
xAxisLabel = 'Ημέρα';
showYAxisLabel = true;
yAxisLabel = 'Αρ. Περιστατικών';
constructor(private statisticsService: StatisticsService) {
// Object.assign(this, { single })
// Object.assign(this, { data } );
}
colorScheme = {
domain: ['#5AA454', '#A10A28', '#C7B42C', '#AAAAAA']
};
onSelect(event) {
console.log(event);
}
async ngOnInit() {
console.log('NG ON INIT EXECUTION');
await this.getIncidentsByDay();
}
getIncidentsByDay() {
this.statisticsService.getIncidentsByDay()
.subscribe(
(results) => {
let temp = new Data();
for (let i in results) {
console.log(results[i][0] + '>>=====>> ' + results[i][1]);
temp.name = results[i][0];
temp.value = results[i][1];
this.data.push(temp);
}
const test = this.data;
// for (let i = 0; i < this.data.length; i++) {
// console.log('wtf: ' + this.data[i][0] + '::::' + this.data[i][1]);
// }
// console.log(results);
// console.log(JSON.stringify(results));
// Object.assign(this, {test});
}
);
}
}
However when I run the above code I get in JavaScript console the error:
ERROR TypeError: Object(...) is not a function
at BarVerticalComponent../src/common/base-chart.component.ts.BaseChartComponent.bindWindowResizeEvent (index.js:7818)
at BarVerticalComponent../src/common/base-chart.component.ts.BaseChartComponent.ngAfterViewInit (index.js:7730)
at callProviderLifecycles (core.js:12689)
at callElementProvidersLifecycles (core.js:12656)
at callLifecycleHooksChildrenFirst (core.js:12639)
at checkAndUpdateView (core.js:13794)
at callViewAction (core.js:14136)
at execComponentViewsAction (core.js:14068)
at checkAndUpdateView (core.js:13791)
at callViewAction (core.js:14136)
My Html Template File:
<div>
lalalal <br/>
ante pali... <br/>
kala ti na pw... <br/>
Gamiete pali... <br/>
<ngx-charts-bar-vertical
[view]="view"
[scheme]="colorScheme"
[results]="data"
[gradient]="gradient"
[xAxis]="showXAxis"
[yAxis]="showYAxis"
[legend]="showLegend"
[showXAxisLabel]="showXAxisLabel"
[showYAxisLabel]="showYAxisLabel"
[xAxisLabel]="xAxisLabel"
[yAxisLabel]="yAxisLabel"
(select)="onSelect($event)">
</ngx-charts-bar-vertical>
</div>
While the service for retreiving the values is:
import {Injectable} from '#angular/core';
import {HttpClient} from '#angular/common/http';
import {catchError} from 'rxjs/operators';
import {ErrorHandler} from '../shared/lib/error-handler';
import {Observable} from 'rxjs/Observable';
#Injectable()
export class StatisticsService {
constructor(private http: HttpClient) {
}
public getIncidentsByDay(): Observable<any> {
console.log("FEtching Incidents All By Day");
const url = 'statistics/incidents/day';
return this.http.get(url)
.pipe(catchError(ErrorHandler.handleError));
}
}
What am I doing wrong?
I am using Angular version 5.3 and ngx-charts 8.0 which is compatible with Angular 6 and not Angular 5. I installed ngx-charts version 7.4 and everything works fine.
I fixed the problem for me by downgrading to version 7.3.0
yarn add #swimlane/ngx-charts#7.3.0
I think I see the same with ngx-charts-bar-horizontal, whereas before this was not the case. The documentation page seems to be broken at the moment as well, so I assume the software has recently been updated in a broken way.
If you really need to use the 8.0 version, you can upgrade to angular 6 to solve the problem. Here is how you can do the upgrade from v5 to v6 https://stackoverflow.com/a/49474334
You can also think that the documention page is broken by now but ou can find it here https://swimlane.gitbook.io/ngx-charts/v/docs-test/installing

Technique for jquery change events and aurelia

I need to find a reliable solution to making the two frameworks play nicely.
Using materialize-css, their select element uses jquery to apply the value change. However that then does not trigger aurelia in seeing the change. Using the technique of...
$("select")
.change((eventObject: JQueryEventObject) => {
fireEvent(eventObject.target, "change");
});
I can fire an event aurelia sees, however, aurelia then cause the event to be triggered again while it's updating it's bindings and I end up in an infinite loop.... Stack Overflow :D
Whats the most reliable way of getting the two to play together in this respect?
I have worked with materialize-css + aurelia for a while and I can confirm that the select element from materialize is quite problematic.
I just wanted to share one of my solutions here in case anyone wants some additional examples. Ashley's is probably cleaner in this case. Mine uses a bindable for the options instead of a slot.
Other than that the basic idea is the same (using a guard variable and a micro task).
One lesson I learned in dealing with 3rd party plugins and two-way data binding is that it helps to make a more clear, distinct separation between handling changes that originate from the binding target (the select element on the DOM) and changes that originate from the binding source (e.g. the ViewModel of the page containing the element).
I tend to use change handlers with names like onValueChangedByBindingSource and onValueChangedByBindingTarget to deal with the different ways of syncing the ViewModel with the DOM in a way that results in less confusing code.
Example: https://gist.run?id=6ee17e333cd89dc17ac62355a4b31ea9
src/material-select.html
<template>
<div class="input-field">
<select value.two-way="value" id="material-select">
<option repeat.for="option of options" model.bind="option">
${option.displayName}
</option>
</select>
</div>
</template>
src/material-select.ts
import {
customElement,
bindable,
bindingMode,
TaskQueue,
Disposable,
BindingEngine,
inject,
DOM
} from "aurelia-framework";
#customElement("material-select")
#inject(DOM.Element, TaskQueue, BindingEngine)
export class MaterialSelect {
public element: HTMLElement;
public selectElement: HTMLSelectElement;
#bindable({ defaultBindingMode: bindingMode.twoWay })
public value: { name: string, value: number };
#bindable({ defaultBindingMode: bindingMode.oneWay })
public options: { displayName: string }[];
constructor(
element: Element,
private tq: TaskQueue,
private bindingEngine: BindingEngine
) {
this.element = element;
}
private subscription: Disposable;
public isAttached: boolean = false;
public attached(): void {
this.selectElement = <HTMLSelectElement>this.element.querySelector("select");
this.isAttached = true;
$(this.selectElement).material_select();
$(this.selectElement).on("change", this.handleChangeFromNativeSelect);
this.subscription = this.bindingEngine.collectionObserver(this.options).subscribe(() => {
$(this.selectElement).material_select();
});
}
public detached(): void {
this.isAttached = false;
$(this.selectElement).off("change", this.handleChangeFromNativeSelect);
$(this.selectElement).material_select("destroy");
this.subscription.dispose();
}
private valueChanged(newValue, oldValue): void {
this.tq.queueMicroTask(() => {
this.handleChangeFromViewModel(newValue);
});
}
private _suspendUpdate = false;
private handleChangeFromNativeSelect = () => {
if (!this._suspendUpdate) {
this._suspendUpdate = true;
let event = new CustomEvent("change", {
bubbles: true
});
this.selectElement.dispatchEvent(event)
this._suspendUpdate = false;
}
}
private handleChangeFromViewModel = (newValue) => {
if (!this._suspendUpdate) {
$(this.selectElement).material_select();
}
}
}
EDIT
How about a custom attribute?
Gist: https://gist.run?id=b895966489502cc4927570c0beed3123
src/app.html
<template>
<div class="container">
<div class="row"></div>
<div class="row">
<div class="col s12">
<div class="input-element" style="position: relative;">
<select md-select value.two-way="currentOption">
<option repeat.for="option of options" model.bind="option">${option.displayName}</option>
</select>
<label>Selected: ${currentOption.displayName}</label>
</div>
</div>
</div>
</div>
</template>
src/app.ts
export class App {
public value: string;
public options: {displayName: string}[];
constructor() {
this.options = new Array<any>();
this.options.push({ displayName: "Option 1" });
this.options.push({ displayName: "Option 2" });
this.options.push({ displayName: "Option 3" });
this.options.push({ displayName: "Option 4" });
}
public attached(): void {
this.value = this.options[1];
}
}
src/md-select.ts
import {
customAttribute,
bindable,
bindingMode,
TaskQueue,
Disposable,
BindingEngine,
DOM,
inject
} from "aurelia-framework";
#inject(DOM.Element, TaskQueue, BindingEngine)
#customAttribute("md-select")
export class MdSelect {
public selectElement: HTMLSelectElement;
#bindable({ defaultBindingMode: bindingMode.twoWay })
public value;
constructor(element: Element, private tq: TaskQueue) {
this.selectElement = element;
}
public attached(): void {
$(this.selectElement).material_select();
$(this.selectElement).on("change", this.handleChangeFromNativeSelect);
}
public detached(): void {
$(this.selectElement).off("change", this.handleChangeFromNativeSelect);
$(this.selectElement).material_select("destroy");
}
private valueChanged(newValue, oldValue): void {
this.tq.queueMicroTask(() => {
this.handleChangeFromViewModel(newValue);
});
}
private _suspendUpdate = false;
private handleChangeFromNativeSelect = () => {
if (!this._suspendUpdate) {
this._suspendUpdate = true;
const event = new CustomEvent("change", { bubbles: true });
this.selectElement.dispatchEvent(event)
this.tq.queueMicroTask(() => this._suspendUpdate = false);
}
}
private handleChangeFromViewModel = (newValue) => {
if (!this._suspendUpdate) {
$(this.selectElement).material_select();
}
}
}
Ok, I spent entirely too long getting this one answered the way I wanted, but more on that later. The actual answer to stop the infinite loop is fairly simple, so let's look at it first. You need to have a guard property, and you'll need to use Aurelia's TaskQueue to help unset the guard property.
Your code will look a little something like this:
$(this.selectElement).change(evt => {
if(!this.guard) {
this.guard = true;
const changeEvent = new Event('change');
this.selectElement.dispatchEvent(changeEvent);
this.taskQueue.queueMicroTask(() => this.guard = false);
}
});
Notice that I'm using queueing up a microtask to unset the guard. This makes sure that everything will work the way you want.
Now that we've got that out of the way, let's look at a gist I created here. In this gist I created a custom element to wrap the Materialize select functionality. While creating this, I learned that select elements and content projection via slot elements don't go together. So you'll see in the code that I have to do some coding gymnastics to move the option elements over from a dummy div element into the select element. I'm going to file an issue so we can look in to this and see if this is a bug in the framework or simply a limitation of the browser.
Normally, I would highly recommend creating a custom element to wrap this functionality. Given the code I had to write to shuffle nodes around, I can't say that I highly recommend creating a custom element. I just really recommend it in this case.
But anyways, there you go!

Aurelia, check when DOM is compiled?

How to check when DOM is compiled and inserted from Aurelia repeat cycle when the model is updated?
I have the following html:
<div clas="parent">
<div class="list-group">
<a repeat.for="$item of treeData">${$item.label}</a>
</div>
</div>
Here I need to know when all <a> tags are listed in the DOM, in order to run jquery scroll plugin on the parent <div> container.
At first load, I do that from the attached() method and all is fine.
When I update the treeData model from a listener, and try to update the jquery scroll plugin, it looks that the DOM is not compiled, so my scroll plugin can not update properly.
If I put timeout with some minimum value like 200ms it works, but I don't think it is a reliable workaround.
So is there a way to solve that?
Thanks!
My View Model:
#customElement('tree-view')
#inject(Element, ViewResources, BindingEngine)
export class TreeView {
#bindable data = [];
#bindable filterFunc = null;
#bindable filter = false;
#bindable selectedItem;
constructor(element, viewResources, bindingEngine) {
this.element = element;
this.viewResources = viewResources;
this.bindingEngine = bindingEngine;
}
bind(bindingContext, overrideContext) {
this.dataPropertySubscription = this.bindingEngine
.propertyObserver(this, 'data')
.subscribe((newItems, oldItems) => {
this.dataCollectionSubscription.dispose();
this._subscribeToDataCollectionChanges();
this.refresh();
});
this.refresh();
if (this.filter === true) {
this.filterChanged(this.filter);
}
if (this.selectedItem) {
this.selectedItemChanged(this.selectedItem);
}
}
attached() {
$(this.element).perfectScrollbar();
}
refresh() {
this.treeData = processData(this.data, this.filterFunc);
this.listItemMap = new WeakMap();
this.treeData.forEach(li => this.listItemMap.set(li.item, li));
this.filterChanged(this.filter);
$(this.element).perfectScrollbar('update');
}
This is only part of the code, but most valuable I think.
I attach the jq plugin in attached function and try to update it in refresh function. In general I have listener that track model in other view, which then update that one without triggering bind method.
An approach would be to use something called window.requestAnimationFrame (https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame).
In your view-model, when you modify your treeData array, try calling
window.requestAnimationFrame(()=>{
$.fn.somePlugin();
});
Haven't tested this out, but based off what you're telling me, this might do what you need.
You could push your code onto the microTaskQueue, which will schedule your function to be executed on the next event loop. For instance:
import { TaskQueue } from 'aurelia-task-queue';
//...
#inject(Element, ViewResources, BindingEngine, TaskQueue)
export class TreeView {
constructor(element, viewResources, bindingEngine, taskQueue) {
this.element = element;
this.viewResources = viewResources;
this.bindingEngine = bindingEngine;
this.taskQueue = taskQueue;
}
refresh() {
this.treeData = processData(this.data, this.filterFunc);
this.listItemMap = new WeakMap();
this.treeData.forEach(li => this.listItemMap.set(li.item, li));
this.filterChanged(this.filter);
// queue another task, which will execute after the tasks queued above ^^^
this.taskQueue.queueMicroTask(() => {
$(this.element).perfectScrollbar('update');
});
}
}