I'm learning Angular and wondering how I can't hide some item and show certain items only when the user select specific item in the drop down list.
For example, In my page, I have Chart TextBox, Text TextBox, Grid TextBox and a drop down list that contain Chart, Text, and Grid. when ever user select Text, I want to show only Text TextBox and hide the rest.
right now, the three chart type options are showing on drop drop list when ever I run the project but it's not doing anything when I select Text and also I got an error on my ngIf saying that
"Property 'text' does not exist on type 'ChartType'."
I would be really appreciate if can somebody teach me or help me.
The problem I have is in the project I found from from github, the data for drop down list is in the another file called chart.model.ts and it written like this
export class ChartUtil {
public static getChartTypeDisplay(type: ChartType): string {
switch (type) {
case ChartType.chart:
return "Chart"
case ChartType.text:
return "Text";
case ChartType.grid:
return "Grid";
default:
return "unknown";
}
}
}
and display it like this
design.component.ts
chartTypes: TypeListItem[] = [];
setupTypes() {
let keys = Object.keys(ChartType);
for (let i = 0; i < (keys.length / 2); i++) {
this.chartTypes.push({ value: parseInt(keys[i]), display: ChartUtil.getChartTypeDisplay(parseInt(keys[i])) })
}
html
<ng-container *ngIf="chart.chartTypes == chartTypes.text">
<mat-form-field appearance="fill">
<mat-label>Text</mat-label>
<input matInput />
</mat-form-field>
I can't uploaded the full project on stackblitz but I uploaded all the code from those file over https://stackblitz.com/edit/angular-ivy-dmf3vn
This is normally how you would tackle a ng-switch
Component Code (badly done)
import { Component, VERSION, OnInit } from '#angular/core';
export class ChartType {
chart = 1;
text = 2;
grid = 3;
}
export class TypeListItem {
public value = 0;
public display = '';
public chartType = -1;
}
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent implements OnInit {
public chartTypesEnum = new ChartType();
public chartTypes: TypeListItem[] = [];
ngOnInit() {
let keys = Object.keys(this.chartTypesEnum);
for (let i = 0; i < (keys.length ); i++) {
this.chartTypes.push(
{
value: parseInt(ChartType[keys[i]]),
chartType: this.chartTypesEnum[keys[i]],
display: `This is a ${keys[i]}`
})
}
}
}
HTML (again badly done but simple)
<ng-container *ngFor="let chart of chartTypes">
<ng-container [ngSwitch]="chart.chartType" >
<div *ngSwitchCase="chartTypesEnum.chart">Chart</div>
<div *ngSwitchCase="chartTypesEnum.grid">Grid</div>
<div *ngSwitchCase="chartTypesEnum.text">Text</div>
</ng-container>
</ng-container>
Example
https://stackblitz.com/edit/angular-ivy-bievwr
Example 2
https://stackblitz.com/edit/angular-ivy-jhv4bq
This solution will render an Angular Material dropdown List using Enum with the ChartTypes insted of the your switch.
The Component:
import { Component, OnInit } from '#angular/core';
export enum ChartTypes {
Chart = 'Chart',
Text = 'Text',
Grid = 'Grid',
}
#Component({
selector: 'app-select-by-type',
templateUrl: './select-by-type.component.html',
styleUrls: ['./select-by-type.component.css']
})
export class SelectByTypeComponent implements OnInit {
// create an enum variable to work with on the view
chartTypes = ChartTypes;
// initialize the dropdown to a default value (in this case it's Chart)
chartsValue: string = ChartTypes.Chart;
constructor() { }
ngOnInit() {
}
}
The View:
<mat-form-field appearance="fill">
<mat-select required [(value)]="chartsValue">
<mat-option *ngFor="let chartType of chartTypes | keyvalue" [value]="chartType.value">{{chartType.value}}
</mat-option>
</mat-select>
<mat-label>Chart Type</mat-label>
</mat-form-field>
<ng-container *ngIf="chartsValue === chartTypes.Text">
<mat-form-field appearance="fill">
<mat-label>Text</mat-label>
<input matInput />
</mat-form-field>
</ng-container>
<ng-container *ngIf="chartsValue === chartTypes.Chart">
Chart In Template
</ng-container>
<ng-container *ngIf="chartsValue === chartTypes.Grid">
Grid In Template
</ng-container>
Related
My goal is to create Angular Material Chips that are unique to each expansion panel. For example, panel 1 should have chips called chip 1 and chip 2, while panel 2 should have chip 3 and chip 4. However, the panels should all show up on a singular page (such as localhost:4200).
When I run my current code, it produces chips that appear on every panel, and when a chip is deleted, it is deleted from every panel as well. I believe that the solution lies in creating an *ngFor loop with tags being replaced with p.tags, where adding a tag will be unique to that object’s tags array. However, I can’t figure out how push that data back into a singular object and then display that data on my html page.
Here's what the .ts code looks like.
import { Component, OnInit } from "#angular/core";
import { MatChipInputEvent } from "#angular/material/chips";
import { COMMA, ENTER } from "#angular/cdk/keycodes";
export interface People {
people: Person[];
}
export interface Person {
name: string;
id: string;
tags: string[];
}
#Component({
selector: "app-root",
templateUrl: "./app.component.html",
styleUrls: ["./app.component.css"],
})
export class PersonComponent implements OnInit {
persons;
visible = true;
selectable = true;
removable = true;
addOnBlur = true;
readonly separatorKeysCodes: number[] = [ENTER, COMMA];
tags: string[] = [];
constructor(private personService: PersonService) {}
ngOnInit() {
this.personService
.getStudents()
.subscribe((data) => (this.persons = data.persons));
}
add(event: MatChipInputEvent): void {
const input = event.input;
const value = event.value;
if ((value || "").trim()) {
this.tags.push(value.trim());
}
if (input) {
input.value = "";
}
}
remove(tag: string): void {
const index = this.tags.indexOf(tag);
if (index >= 0) {
this.tags.splice(index, 1);
}
}
}
Here's what the .html code looks like.
<mat-expansion-panel *ngFor="let p of persons>
<mat-expansion-panel-header [collapsedHeight]="'125px'" [expandedHeight]="'125px'">
{{ p.name }}
</mat-expansion-panel-header>
<mat-form-field>
<mat-chip-list #chipList>
<!-- tags should be switched to p.tags here I think -->
<mat-chip *ngFor="let tag of tags" [selectable]="selectable"
[removable]="removable" (removed)="remove(tag)">
{{tag}}
<mat-icon matChipRemove *ngIf="removable">cancel</mat-icon>
</mat-chip>
<input placeholder="Add a tag"
[matChipInputFor]="chipList"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
[matChipInputAddOnBlur]="addOnBlur"
(matChipInputTokenEnd)="add($event)">
</mat-chip-list>
</mat-form-field>
</mat-expansion-panel>
this is my angular materiel auto complete code
<input type="search" id="setId" name="setId" [attr.list]='collectionType' [(ngModel)]="selValue" class="text-box"
placeholder="--Select--" (focus)="ValidateParent()" (keyup.tab)="test()" (keyup)="EmitValues($event)" [id]="setId"
[matAutocomplete]="auto" [title]="selValue" [placeholder]='WaterMarkText'>
<div [hidden]="IsCascading">
<mat-autocomplete [id]="collectionType" #auto="matAutocomplete" (optionSelected)='onChange($event)'>
<mat-option *ngFor="let items of codeList" [value]="items.text" [attr.data-text]='items.text' [id]="items.value">
{{items.text}}
</mat-option>
</mat-autocomplete>
</div>
Angular material had a problem with tab selection.like the materiel auto complete not able to select the value while click the tab button. but it's working while click the enter button. So manually I need to overwrite the enter key event on tab key event. How could possible?
Improve my comment, and based on the response we can create a directive
import {
Directive,
AfterViewInit,
OnDestroy,
Optional
} from '#angular/core';
import {
MatAutocompleteTrigger
} from '#angular/material';
#Directive({
selector: '[tab-directive]'
})
export class TabDirective implements AfterViewInit, OnDestroy {
observable: any;
constructor(#Optional() private autoTrigger: MatAutocompleteTrigger) {}
ngAfterViewInit() {
this.observable = this.autoTrigger.panelClosingActions.subscribe(x => {
if (this.autoTrigger.activeOption) {
this.autoTrigger.writeValue(this.autoTrigger.activeOption.value)
}
})
}
ngOnDestroy() {
this.observable.unsubscribe();
}
}
You use:
<input tab-directive type="text" matInput [formControl]="myControl"
[matAutocomplete]="auto" >
(see stackblitz)
Update We can control only tab.key, else always you close, you get the selected value, so
#Directive({
selector: '[tab-directive]'
})
export class TabDirective {
observable: any;
constructor(#Optional() private autoTrigger: MatAutocompleteTrigger) {}
#HostListener('keydown.tab', ['$event.target']) onBlur() {
if (this.autoTrigger.activeOption) {
this.autoTrigger.writeValue(this.autoTrigger.activeOption.value)
}
}
}
(see a new stackblitz)
Update 2 I don't believe this answer has so many upvotes because it's wrong. As #Andrew allen comments, the directive not update the control. Well, It's late, but I try to solve. One Option is use
this.autoTrigger._onChange(this.autoTrigger.activeOption.value)
Anohter idea is inject the ngControl, so
constructor(#Optional() private autoTrigger: MatAutocompleteTrigger,
#Optional() private control: NgControl) {}
ngAfterViewInit() {
this.observable = this.autoTrigger.panelClosingActions.subscribe(x => {
if (this.autoTrigger.activeOption) {
const value = this.autoTrigger.activeOption.value;
if (this.control)
this.control.control.setValue(value, {
emit: false
});
this.autoTrigger.writeValue(value);
}
})
}
I'm a little late to the party; so there's one annoying issue with Eliseo's answer: It autocompletes even when the user has already selected a different option from the panel with a mouse click. The workaround I finally came up with was not straight forward at all.
/**
* Selects currently active item on TAB
* #example
* <input vxTabActiveOption [matAutocomplete]="matAutocomplate">
* <mat-autocomplete #matAutocomplate="matAutocomplete" autoActiveFirstOption>
*/
#Directive({ selector: '[vxTabActiveOption]' })
export class TabActiveOptionDirective implements AfterViewInit, OnDestroy {
private readonly destroy$ = new Subject<void>();
/**
* Whether or not the autocomplete panel was open before the event
*/
private panelOpen = false;
constructor(
private autoTrigger: MatAutocompleteTrigger,
private control: NgControl
) { }
ngAfterViewInit() {
const autocomplete = this.autoTrigger.autocomplete;
merge(
autocomplete.opened.pipe(map(() => true)),
autocomplete.closed.pipe(map(() => false))
).pipe(
takeUntil(this.destroy$),
delay(0)
).subscribe(value => this.panelOpen = value);
}
#HostListener('keydown.tab')
onBlur() {
if (this.panelOpen && this.autoTrigger.activeOption) {
const value = this.autoTrigger.activeOption.value;
this.control.control.setValue(value, { emit: false });
this.autoTrigger.writeValue(value);
}
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
}
Yes, I know it's an old question, but there're a better aproach that is use a directive like
#Directive({ selector: '[tab-selected]' })
export class TabSelected implements AfterViewInit {
constructor(private auto: MatAutocomplete) {}
ngAfterViewInit() {
this.auto._keyManager.onKeydown = (event: KeyboardEvent) => {
switch (event.keyCode) {
case TAB:
if (this.auto.isOpen) {
const option = this.auto.options.find(x => x.active);
if (option) {
option.select();
event.preventDefault();
return;
}
}
this.auto._keyManager.tabOut.next();
break;
case DOWN_ARROW:
this.auto._keyManager.setNextItemActive();
break;
case UP_ARROW:
this.auto._keyManager.setPreviousItemActive();
break;
}
};
}
}
You use like
<mat-form-field class="example-full-width" appearance="fill">
<mat-label>Number</mat-label>
<input type="text"
placeholder="Pick one"
aria-label="Number"
matInput
[formControl]="myControl"
[matAutocomplete]="auto">
<mat-autocomplete tab-selected #auto="matAutocomplete"
autoActiveFirstOption >
<mat-option *ngFor="let option of filteredOptions | async" [value]="option">
{{option}}
</mat-option>
</mat-autocomplete>
</mat-form-field>
You can be on work in this stackblitz
I have a stackblitz as a guide.
I am wanting to display a list of material cards that I click an 'edit' button, to which I can edit text fields, and when I click on the 'save' icon, it of course saves by triggering an function etc.
I am struggling however to get to grips with how this all works within Angular and the Material nature of my app.
html
<form id="myForm" [formGroup]="thisIsMyForm">
<mat-card [formGroup]="x" *ngFor="let x of data; let i = index">
<mat-form-field>
<label for="{{x.name}}">Name</label>
<input formControlName="name" id="{{x.name}}" matInput value="{{x.name}}">
</mat-form-field>
<mat-form-field>
<label for="{{x.type}}">Type</label>
<input formControlName="type" id="{{x.type}}" matInput value="{{x.type}}"/>
</mat-form-field>
</mat-card>
</form>
ts
import { Component, ViewChild } from '#angular/core';
import {MatSnackBar} from '#angular/material';
import {FormArray, FormBuilder, FormGroup, Validators} from '#angular/forms';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
thisIsMyForm: FormGroup
data = [
{name:"one", type:"one"},
{name:"two", type:"two"},
{name:"three", type:"three"},
];
constructor(private formBuilder: FormBuilder) {}
onSubmit() {
// Here I would like to be able to access the values of the 'forms'
}
}
You are diving into the deep end for sure, trying to build a dynamic reactive form within the scope of an *ngFor is a challenge. I will walk you through it the best I can.
You will need to create an array for controls, in your constructor create your form setting formArrayName as an empty array using this.formBuild.array([])... call this whatever you want, I just used formArrayName for demonstration purposes.
After the form is instantiated with an empty array call this.buildForm()
constructor(private formBuilder: FormBuilder) {
this.thisIsMyForm = new FormGroup({
formArrayName: this.formBuilder.array([])
})
this.buildForm();
}
In your buildForm() iterate over your data[] and push controls for each index while assigning the default value and a default state of disabled.
buildForm() {
const controlArray = this.thisIsMyForm.get('formArrayName') as FormArray;
Object.keys(this.data).forEach((i) => {
controlArray.push(
this.formBuilder.group({
name: new FormControl({ value: this.data[i].name, disabled: true }),
type: new FormControl({ value: this.data[i].type, disabled: true })
})
)
})
console.log(controlArray)
}
Please Note: console.log(controlArray.controls) results in the following output... each index is a FormGroup with two controls name and type
0: FormGroup
1: FormGroup
2: FormGroup
In your html you will need to establish a container hierarchy that mimics the thisIsMyForm you just created.
parent:thisIsMyForm
child:formArrayName
grandchild:i as formGroupName
grandchild is important because it matches the console log of controlArray.controls in previous step
<form id="myForm" [formGroup]="thisIsMyForm">
<div [formArrayName]="'formArrayName'">
<mat-card *ngFor="let x of data; let i = index">
<div [formGroupName]="i">
Create edit and save buttons based on control disabled state
<button *ngIf="formControlState(i)" (click)="toggleEdit(i)">Enable Edit</button>
<button *ngIf="!formControlState(i)" (click)="toggleEdit(i)">Save</button>
Create methods in component to receive the index as an argument and handle logic to hide buttons and toggle input fields enable and disable state.
toggleEdit(i) {
const controlArray = this.thisIsMyForm.get('formArrayName') as FormArray;
if(controlArray.controls[i].status === 'DISABLED'){
controlArray.controls[i].enable()
}else{
controlArray.controls[i].disable()
}
}
formControlState(i){
const controlArray = this.thisIsMyForm.get('formArrayName') as FormArray;
return controlArray.controls[i].disabled
}
Submit button that console.log's the form value when clicked... also disable button while any of the input formControls are in enabled state.
<button [disabled]="thisIsMyForm.get('formArrayName').enabled" (click)="onSubmit()">Submit Form</button>
onSubmit() {
// Here I would like to be able to access the values of the 'forms'
console.log(this.thisIsMyForm.value)
}
Stackblitz
https://stackblitz.com/edit/dynamic-form-ngfor-otbuzn?embed=1&file=src/app/app.component.ts
Doing it with QueryList:
your html (this is an example):
<ng-container *ngFor="let x of data; let i = index">
<div class="ctr">
<span #names class="item">{{x.name}}</span>
<span #types class="item">{{x.type}}</span>
<span class="button" (click)="showData(i)">Show data</span>
</div>
</ng-container>
<h2>Selected values: </h2>
Selected name: {{selectedName}} <br>
Selected type: {{selectedType}}
some css just for the style
.ctr{
display: flex;
flex-direction: row;
margin-bottom: 20px;
}
.item{
margin-right:40px;
}
.button{
border: 1px solid black;
padding: 2px 5px 2px 5px;
cursor: pointer;
}
the component:
import { Component, QueryList, ViewChildren } from '#angular/core';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
#ViewChildren('names') names:QueryList<any>;
#ViewChildren('types') types:QueryList<any>;
selectedName: string;
selectedType: string;
data = [
{name:"one", type:1},
{name:"two", type:2},
{name:"three", type:3},
];
showData(index){
let namesArray = this.names.toArray();
let typesArray = this.types.toArray();
this.selectedName = namesArray[index].nativeElement.innerHTML;
this.selectedType = typesArray[index].nativeElement.innerHTML;
}
}
Working stackblitz: https://stackblitz.com/edit/angular-j2n398?file=src%2Fapp%2Fapp.component.ts
I am new comer in Angular 5. I would like to get the html elements from .ts file and dynamically add them in html file through ngFor. I tried to do it using [innerHtml] but it is not working and return me [object HTMLSelectElement]. How can I solve this problem?
transferDataSuccess(event) {
var dropHere = document.getElementById('dropHere')
var dropInSettings = document.getElementById('dropInSettings')
for (var key in this.transferData) {
if (this.transferData[key].name == event.dragData.name) {
this.receivedData.push({
elem:this.transferData[key].element});
}
}
}
<div class="panel-heading" id="dropHere" *ngFor="let x of receivedData" [innerHTML] = "x.elem">
A better way to do this can be as below:
let's say your options are coming dynamically from the service and you want to create <option> element dynamically.
A sample snippet to describe the functionality :
<select>
<ng-container *ngFor="let option of options;">
<option [value]="option['value']">{{option['label']}}</option>
</ng-container>
</select>
If you already have the element in the component, you can access it using the ElementRef as below:
import { Component, ElementRef, AfterViewInit } from '#angular/core';
#Component({
selector: 'app-my-component',
templateUrl: './my.component.html',
styleUrls: ['./my.component.scss']
})
export class MyComponent implements AfterViewInit {
constructor(private el: ElementRef) {
this.el = el;
}
ngAfterViewInit() {
console.log('element', this.el.nativeElement);
this.el.nativeElement.getElementById('dropHere');
}
}
If it not good practice to manipulate DOM from the component class.
I hope this help.
I am attempting to pull a single select from a Kendo grid to a variable so I can pass the id of the object to a route of the selected item. I can see the variable of the object under selectedRows > dataItem but I can't seem to call any of the objects below it.
Plunker https://plnkr.co/edit/1yLrC3EXkHREkgnMijgt?p=preview
import { Component } from '#angular/core';
import { products } from './products';
import { SelectableSettings } from '#progress/kendo-angular-grid';
#Component({
selector: 'my-app',
template: `
<kendo-grid [data]="gridData" [selectable]="{ mode: 'single'}" [height]="500" (selectionChange)="selected($event)">
<kendo-grid-column field="ProductName" title="Product Name" [width]="300"></kendo-grid-column>
<kendo-grid-column field="UnitsInStock" title="Units In Stock"></kendo-grid-column>
<kendo-grid-column field="UnitsOnOrder" title="Units On Order"></kendo-grid-column>
<kendo-grid-column field="ReorderLevel" title="Reorder Level"></kendo-grid-column>
</kendo-grid>
`
})
export class AppComponent {
public gridData: any[] = products;
public selectedId: number;
constructor() { }
public selected(e){
//Problem here
this.selectedId = e.selectedRows.dataItem(n => n.ProductId);
console.log(selectedId);
}
}
In order to access the ProductID on the selected item, you'll need to use
this.selectedId = e.selectedRows[0].dataItem.ProductID;
Here is a working plunkr: https://plnkr.co/edit/Kd9jxi5WMuXLIYIxhXQx?p=preview