Angular 6 - MatDialog - EventEmitter - share object to parent component from MatDialog - javascript

Right now able to communicate between two components. but don't know how to pass user (selected) entered value as Object via event emitter from MatDialog component to the parent component. Here I want to pass selected option value and text area value as object after clicking the submit button.
dialog.html
<mat-select [(ngModel)]="selectedIssue" [ngModelOptions]="{standalone: true}">
<mat-option [value]="option1">Option AA</mat-option>
<mat-option [value]="option2">Option BB</mat-option>
<mat-option [value]="option3">Option CC</mat-option>
<mat-option [value]="option4">Option DD</mat-option>
<mat-option [value]="option5">Option EE</mat-option>
</mat-select>
<div>
<textarea class="mention-reason" [(ngModel)]="reason" [ngModelOptions]="{standalone: true}"></textarea>
</div>
<button class="cancel" matDialogClose>Cancel</button>
<button class="submit" [mat-dialog-close] (click)="submitUserReason()">Submit</button></span>
dialog.ts
onSubmitReason = new EventEmitter();
submitUserReason(): void {
this.onSubmitReason.emit('hello');
}
onConfirmClick(): void {
this.dialogRef.close(true);
}
parent.ts
callSupport() {
const dialogRef = this.dialog.open(customerSupportComponent);
const subscribeDialog = dialogRef.componentInstance.onSubmitReason.subscribe((data) => {
console.log('dialog data', data);
//i can see 'hello' from MatDialog
});
dialogRef.afterClosed().subscribe(result => {
subscribeDialog.unsubscribe();
});
Thanks so much whoever helping.

I assume there are two buttons. 1) submit 2) close
So, If you want selected data in parent component on submit button click then,
submitUserReason(): void {
this.dialogRef.close({ option: userSelectedOption, reason:userReason });
}
And in parent component,
dialogRef.afterClosed().subscribe(result => {
console.log(result);
});

In your dialog.ts you want to pass your selected option instead of just a string. Something like:
submitUserReason(): void {
this.onSubmitReason.emit(selectedIssue);
}
You can emit whatever you want (depending on how you've typed things) so if you'd like to pass more data you could also pass an object:
submitUserReason(): void {
let data = { issue : selectedIssue, reason: userReason};
this.onSubmitReason.emit(data);
}

Related

Trigger an event on scrolling to the end of Mat-0ptions in Mat-Select : Angular 6

I recently started modifying a project based on Angular 6 and ran into a bit of an issue.
So, I have (lets assume) 4 Mat-Select fields, for Language, Currency, Country and Organization.
Initially all the dropdowns have 10 default values, I fetch by making an API call. My requirement is to make another API call once the user scrolls to the end of the Mat-Options box that opens up on selecting dropdown.
I had referred to This Question and it works fine but with a few issue, I have noticed. That answer covers the solution for one field. Do we have to repeat the code if we are using multiple fields?
Here is the Html
<mat-select [disabled]="isDisabled" [(ngModel)]="model.currency" name="currency" (selectionChange)="OnChange('currency',$event.value)" #currency>
<mat-option [value]="getCurrency.currencyCode" *ngFor="let getCurrency of fetchCurrency |
filternew:searchCurrencyvaluedrop">{{ getCurrency.currencyDescription }}
</mat-option>
</mat-select>
<mat-select [disabled]="isDisabled" [(ngModel)]="model.language" name="language"
(selectionChange)="OnChange('language', $event.value)" #language>
<mat-option [value]="getLanguage.languageDescription" *ngFor="let getLanguage of fetchLanguage |
filternew:searchLanguagevaluedrop">{{ getLanguage.languageDescription }}</mat-option>
</mat-select>
I am adding only two fields for the sake of simplicity. Below is the code from .ts:
viewIndex = 0;
windowSize = 10;
private readonly PIXEL_TOLERANCE = 3.0;
ngAfterViewInit() {
this.selectElemCu.openedChange.subscribe((event) =>
this.registerPanelScrollEventCurrency(event)
);
this.selectElem.openedChange.subscribe((event) =>
this.registerPanelScrollEventLang(event)
);
}
//for language field
registerPanelScrollEventLang(e) {
if (e) {
const panel = this.selectElem.panel.nativeElement;
panel.addEventListener('scroll', event => this.loadNextOnScrollLang(event, this.selectElem.ngControl.name));
}
}
loadNextOnScrollLang(event, select_name) {
if (this.hasScrolledToBottomLang(event.target)) {
this.viewIndex += this.windowSize;
this.modifyPageDetails(select_name)
this.appService.getAllLanguages(this.standardPageSizeObj, resp => {
console.log('list of languages', resp)
this.fetchLanguage.push(...resp['data'])
}, (error) => {
});
}
}
private hasScrolledToBottomLang(target): boolean {
return Math.abs(target.scrollHeight - target.scrollTop - target.clientHeight) < this.PIXEL_TOLERANCE;
}
The code for currency drop down stays the same. SO, duplication is my first problem. The second this is on scrolling there are two API calls made instead of one. I can live with that but since there are 6 fields, the repition is way too much.
Due to certain security restrictions I cant even use an external library. Is there a better way to implement this. Please let me know if any more clarification is required. Thanks
Here is how you can do it:
STEP 1
For each mat-select you should create a local variable with ViewChild.
#ViewChild('select_1', { static: false }) select_1: MatSelect;
#ViewChild('select_2', { static: false }) select_2: MatSelect;
#ViewChild('select_3', { static: false }) select_3: MatSelect;
#ViewChild('select_4', { static: false }) select_4: MatSelect;
STEP 2
Create a function that will be triggered when any of the dropdowns is opened. You can use the (openedChange) event emitter and check it the value of the event is true. Second parameter to the function can be the dropdown that is opened, so we don't have repetitive code for each dropdown.
<mat-form-field>
<mat-select placeholder="Select 1" #select_1 (openedChange)="onOpenedChange($event, 'select_1')">
<mat-option *ngFor="let item of select_1_options;">
{{item}}
</mat-option>
</mat-select>
</mat-form-field>
STEP 3
Define the onOpenedChange() function that will be triggered on openedChange event. That function should check if the scrollTop of the dropdown's panel is equal to (scrollHeight-offsetHeight) because that means that the user scrolled to bottom of the scroll. When that happens you can call the API.
onOpenedChange(event: any, select: string) {
if (event) {
this[select].panel.nativeElement.addEventListener('scroll', (event: any) => {
if (this[select].panel.nativeElement.scrollTop === this[select].panel.nativeElement.scrollHeight - this[select].panel.nativeElement.offsetHeight) {
console.log(`Call API for ${select}`);
}
});
}
}
Here is the working code: https://stackblitz.com/edit/angular-ivy-xritb1?file=src%2Fapp%2Fapp.component.ts
We can create a directive that handles this scrolling logic for us and we can prevent code duplication.
here is a demo: https://stackblitz.com/edit/angular-ivy-m2gees?file=src/app/mat-select-bottom-scroll.directive.ts
And Here is a brief explanation:
Create a custom directive and get the reference of MatSelect inside it.
import {Directive,ElementRef,EventEmitter,Input,OnDestroy,Output} from '#angular/core';
import { MatSelect } from '#angular/material/select/';
import { fromEvent, Subject } from 'rxjs';
import { filter, switchMap, takeUntil, throttleTime } from 'rxjs/operators';
#Directive({
selector: '[appMatSelectScrollBottom]'
})
export class MatSelectScrollBottomDirective implements OnDestroy {
private readonly BOTTOM_SCROLL_OFFSET = 25;
#Output('appMatSelectScrollBottom') reachedBottom = new EventEmitter<void>();
onPanelScrollEvent = event => {};
unsubscribeAll = new Subject<boolean>();
constructor(private matSelect: MatSelect) {
this.matSelect.openedChange
.pipe(filter(isOpened => !!isOpened),
switchMap(isOpened =>fromEvent(this.matSelect.panel.nativeElement, 'scroll').pipe(throttleTime(50))), //controles the thrasold of scroll event
takeUntil(this.unsubscribeAll)
)
.subscribe((event: any) => {
console.log('scroll');
// console.log(event, event.target.scrollTop, event.target.scrollHeight);
if (
event.target.scrollTop >= (event.target.scrollHeight - event.target.offsetHeight - this.BOTTOM_SCROLL_OFFSET)) {
this.reachedBottom.emit();
}
});
}
ngOnDestroy(): void {
this.unsubscribeAll.next(true);
this.unsubscribeAll.complete();
}
}
This directive will emit an event as soon as the scroll reaches to the bottom.
We are starting with the openedChange event and then we switchMaped it to the scroll event of the selection panel. SwitchMap will automatically unsubscribe from the older scroll event as soon the new open event fires.
In your component use this directive to listen to the event as follows.
<mat-form-field>
<mat-select placeholder="Choose a Doctor" (openedChange)="!$event && reset()"
(appMatSelectScrollBottom)="loadAllOnScroll()"><!-- liste to the event and call your load data function -->
<mat-option *ngFor="let dr of viewDoctors;let i = index">{{dr}}</mat-option>
</mat-select>
</mat-form-field>

Show hide text box when specific option is selected from dropdown inside dynamic form Angular

I have a form where a user could add one/more div of Address on click of add button.
I want if user select options=5 from the dropdown, want to show and hide textbox in that particular address Div.
Component Code
get contactFormGroup() {
return this.form.get('Array') as FormArray;
}
ngOnInit() {
this.form= this.fb.group({
Array: this.fb.array([])
});
}
createContact(): FormGroup {
return this.fb.group({
ABC: [null, Validators.compose([Validators.required])],
Test: [null, Validators.compose([Validators.required])]
});
}
addContact() {
this.Group.push(this.createContact());
}
showValue(event) {
const selectedValue = event;
if (selectedValue === '5') {
this.showValuetxtbox = true;
} else {
this.showValuetxtbox = false;
}
}
As you are looping to add the divs, you could use a template reference variable on the drop down. e.g #select then refer to that in the *ngIf:
<form [formGroup]="addExpressionform">
<div formArrayName="expressionArray">
<div *ngFor="let item of contactFormGroup.controls; let i = index;" [formGroupName]="i">
<carbon-dropdown #select
(optionSelected)="showValue($event)"
[formControlN]="'UOM'"
[options]="listOptions" [formGroup]="item"
name="UOM"
>
</carbon-dropdown>
<carbon-text-input *ngIf="select.value == 5"
[formControlN]="'Value'"
[formGroup]="item"
name="Value"
>
</carbon-text-input>
<carbon-button type="primary" (click)="submit()" id="save-parameter">Save</carbon-button>
</div>
</div>
</form>
Simplified StackBlitz demo.
Take a look at this Stackblitz, it's referred to in the Angular docs and could serve as boilerplate to what you are trying to achieve.
You should isolate every possible type of question by creating a different class for each one, so you can shape the data and then use ngSwitch to dynamically create the HTML accordingly.
Question base class:
export class QuestionBase<T> {
controlType: string;
value: T;
key: string;
label: string;
// etc
constructor(options) {
// constructor logic
}
}
Some special class that inherents from base class
import { QuestionBase } from './question-base';
export class SpecialQuestion extends QuestionBase<string> {
controlType = 'specialQuestion';
type: string;
// special Question
specialValue: string;
constructor(options) {
super(options);
this.type = options['type'] || '';
}
}
Then, a question component:
<div [formGroup]="form">
<label>{{question.label}}</label>
<div [ngSwitch]="question.controlType">
// controls logic
<input *ngSwitchCase="'textbox'" >
<select *ngSwitchCase="'specialQuestion'"></select>
</div>
</div>
Then you throw this into a container component where you loop through the entire questions array.
This way your code will be future proof and reusable as you add/change functionality to your forms down the road. You won't have to create spaghetti to meet edge case requirements like an extra input field.

EmberJS Octane set focus on element

I have component that contains a number of text areas and a button to add another text area. When the user clicks the button, a new text area is added. I want the focus to move to this new text area.
I saw this answer but it's for an older version and we are not using jQuery with Ember.
What I have so far:
five-whys.ts
type LocalWhy = {
content: string;
};
export default class FiveWhys extends Component<FiveWhysArgs> {
#tracked
whys: LocalWhy[] = ...
#action
addWhy() {
this.whys.pushObject({ content: "" });
}
}
five-whys.hbs
{{#each this.whys as |why i|}}
<TextQuestion #value={{why.content}} />
{{/each}}
<button {{on "click" (action this.addWhy)}}>Add Why</button>
text-question.hbs
...
<textarea value="{{ #value }}" />
Summary of question
How do I set the focus to the new textarea after the user clicks "Add Why"?
I've made something similar these days:
component.hbs:
{{#each this.choices as |item|}}
{{input
type="text"
id=item.id
keyPress=(action this.newElement item)
value=(mut item.value)
}}
{{/each}}
component.js
#action
newElement({ id }) {
let someEmpty = this.choices.some(({ value }) => isBlank(value));
if (!someEmpty)
this.choices = [...this.choices, this.generateOption()];
document.getElementById(id).focus();
}
generateOption(option) {
this.inc ++;
if (!option)
option = this.store.createRecord('option');
return {
option,
id: `${this.elementId}-${this.inc}`,
value: option.description
};
}
In my case I have no buttons, and I've created ember data records. With some modifications I bet you can do that!
Found out I can use Ember.run.schedule to run code after the component re-renders.
#action
addWhy() {
... // Adding why field
Ember.run.schedule('afterRender', () => {
// When this function has called, the component has already been re-rendered
let fiveWhyInput = document.querySelector(`#five-why-${index}`) as HTMLTextAreaElement
if (fiveWhyInput)
fiveWhyInput.focus();
})
}

I need to send values from my select options

I am using angular I want to send information about what element I selected in the select option. Specifically I want to send the data-value to a variable in my product-form.component.ts.
I tried using ngModel but I keep getting errors saying that it doesn't recognize (click)=selectCategory1(category) use a function I am using template forms for my forms that could be the reason. You can see my code live at :
https://stackblitz.com/github/RashellSmith/Dashboard-FrontEnd
Product Form component html
<div class="form-group">
<label for="productCategory">Product Category</label>
<select [(ngModel)]="model.category" (click)=selectCategory1(category) name="category" class="form-control" id="productCategory" (click)="value()">
<option *ngFor="let category of categories" (click)=selectCategory1(category) data-value="{{category.category}}" id={{category.categoryName}} >{{category.categoryName}}</option>
</select>
</div>
Product Form component ts
export class ProductFormComponent implements OnInit {
suppliers: Supplier[];
categories: Category[];
value: number;
model = new Newproduct("name",new Category( this.value,"name"),66,33,true,new Supplier(null,null),76);
selectCategory1(Category){
console.log(Category);
}
submitted = false;
get diagnostic() { return JSON.stringify(this.model); }
onSubmit() { this.submitted = true; }
constructor(private supplierService: SupplierService,private categoryService: CategoryService) { }
ngOnInit() {
this.supplierService.getAll().subscribe(data => {
this.suppliers = data;
});
this.categoryService.getAll().subscribe(data => {
this.categories = data;
});
}
}
You are definitely trying to over-complicate things. You need to bind a simple (change) method to your select list which would be be triggered on value change. You can either pass the value to this function using template reference variable as
<Select #select (change)="SelectChanged(select.value)">
Or you can bind [(ngModel)] directive and access that directly in your component class on (change)
A sample template code:
<select [(ngModel)]="selectedOption" (change)="GetSelectedValue(selectedOption)">
<option *ngFor="let option of options" [ngValue]="option">{{option}}</option>
</select>
And component class would look like
GetSelectedValue(val) {
//do something with val
}
Stackblitz at: https://stackblitz.com/edit/angular-r4d7ul

Angular Material Autocomplete - How to allow user to add item not in suggested list?

I'm trying to implement the autocomplete component from Angular Material:
https://material.angular.io/components/autocomplete/overview
It works well for letting the user select a particular item from the suggested list but I also want to allow the user to add items not in the list.
So lets say the suggested list has the following items:
Cats
Birds
Dogs
And the user starts typing "Do" and the autocomplete shows "Dogs" as the suggested option (because I'm also filtering the list based on what they type). But then the user continues typing "Dolls" and now nothing is displayed in the autocomplete suggestions. Then the user hits enter and it gets added to the list.
Current behavior is that if what the user typed doesn't exist in the list then they are unable to add the item.
If you add an enter key listener to the input field, you can process the entered value and add it to the options if it doesn't exist. You can also dynamically add whatever the user enters to the list of filtered options as an "add new item" option, or add an "add" icon to the field (e.g. as a matSuffix). Or you can do all three:
Stackblitz
HTML
<form class="example-form">
<mat-form-field class="example-full-width">
<input matInput placeholder="Item" aria-label="Item" [matAutocomplete]="auto" [formControl]="itemCtrl" (keyup.enter)="addOption()">
<mat-autocomplete #auto="matAutocomplete" (optionSelected)="optionSelected($event.option)">
<mat-option *ngFor="let item of filteredItems | async" [value]="item">
<span>{{ item }}</span>
</mat-option>
</mat-autocomplete>
<button *ngIf="showAddButton && itemCtrl.value" matSuffix mat-button mat-icon-button (click)="addOption()"><mat-icon matTooltip='Add "{{itemCtrl.value}}"'>add</mat-icon></button>
</mat-form-field>
</form>
TS
import { Component } from '#angular/core';
import { FormControl } from '#angular/forms';
import { Observable } from 'rxjs/Observable';
import { startWith } from 'rxjs/operators/startWith';
import { map } from 'rxjs/operators/map';
/**
* #title Autocomplete with add new item option
*/
#Component({
selector: 'autocomplete-overview-example',
templateUrl: 'autocomplete-overview-example.html',
styleUrls: ['autocomplete-overview-example.css']
})
export class AutocompleteOverviewExample {
itemCtrl: FormControl;
filteredItems: Observable<any[]>;
showAddButton: boolean = false;
prompt = 'Press <enter> to add "';
items: string[] = [
'Cats',
'Birds',
'Dogs'
];
constructor() {
this.itemCtrl = new FormControl();
this.filteredItems = this.itemCtrl.valueChanges
.pipe(
startWith(''),
map(item => item ? this.filterItems(item) : this.items.slice())
);
}
filterItems(name: string) {
let results = this.items.filter(item =>
item.toLowerCase().indexOf(name.toLowerCase()) === 0);
this.showAddButton = results.length === 0;
if (this.showAddButton) {
results = [this.prompt + name + '"'];
}
return results;
}
optionSelected(option) {
if (option.value.indexOf(this.prompt) === 0) {
this.addOption();
}
}
addOption() {
let option = this.removePromptFromOption(this.itemCtrl.value);
if (!this.items.some(entry => entry === option)) {
const index = this.items.push(option) - 1;
this.itemCtrl.setValue(this.items[index]);
}
}
removePromptFromOption(option) {
if (option.startsWith(this.prompt)) {
option = option.substring(this.prompt.length, option.length -1);
}
return option;
}
}
It's weird that the user can add an item in the suggested list. The list is suggested to the user by someone who knows what to suggest. But anyway...
The user can type anything in the field and ignore the suggestions. By ignoring the suggested Dogs and typing Dolls, user can press an "Add" button which will add whatever is typed in (Dolls) to the options array.
For example, you can do it by listening to the submit event on the form:
(ngSubmit)="options.push(myControl.value); myControl.reset()"
Here's the complete demo as well.

Categories

Resources