Angular ngx-mat-select-search Custom Component - javascript

I am trying to use the ngx-mat-select-search component to put a mat-select style dropdown menu with a search bar in my application.
https://www.npmjs.com/package/ngx-mat-select-search
I have the dropdown working fine, but I am trying to turn it into a custom directive that I can then call and reuse on multiple pages through out the app.
So far I have this: site-dropdown-component.ts
import {AfterViewInit, Component, OnDestroy, OnInit, ViewChild} from '#angular/core';
import {FormControl} from '#angular/forms';
import {ReplaySubject, Subject} from 'rxjs';
import {MatSelect} from '#angular/material';
import {take, takeUntil} from 'rxjs/operators';
#Component({
selector: 'app-site-dropdown',
template: `
<mat-form-field class="w-100">
<mat-select [formControl]="siteCtrl" placeholder="Site" #singleSelect>
<mat-option>
<ngx-mat-select-search [formControl]="siteFilterCtrl" [placeholderLabel]="'Search Sites...'"></ngx-mat-select-search>
</mat-option>
<mat-option *ngFor="let site of filteredSites | async" [value]="site">{{site.name}}</mat-option>
</mat-select>
</mat-form-field>
`
})
export class SiteDropdownComponent implements OnInit, OnDestroy, AfterViewInit {
/** list of sites */
protected sites: Site[] = SITES;
/** control for the selected site */
public siteCtrl: FormControl = new FormControl();
/** control for the MatSelect filter keyword */
public siteFilterCtrl: FormControl = new FormControl();
/** list of sites filtered by search keyword */
public filteredSites: ReplaySubject<Site[]> = new ReplaySubject<Site[]>(1);
#ViewChild('singleSelect') singleSelect: MatSelect;
/** Subject that emits when the component has been destroyed. */
protected onDestroy = new Subject<void>();
constructor() { }
ngOnInit(): void {
// set initial selection
this.siteCtrl.setValue(this.sites);
// load the initial site list
this.filteredSites.next(this.sites.slice());
// listen for search field value changes
this.siteFilterCtrl.valueChanges
.pipe(takeUntil(this.onDestroy))
.subscribe(() => {
this.filterSites();
});
}
ngAfterViewInit(): void {
this.setInitialValue();
}
ngOnDestroy(): void {
this.onDestroy.next();
this.onDestroy.complete();
}
/**
* Sets the initial value after the filteredBanks are loaded initially
*/
protected setInitialValue() {
this.filteredSites
.pipe(take(1), takeUntil(this.onDestroy))
.subscribe(() => {
// setting the compareWith property to a comparison function
// triggers initializing the selection according to the initial value of
// the form control (i.e. _initializeSelection())
// this needs to be done after the filteredBanks are loaded initially
// and after the mat-option elements are available
this.singleSelect.compareWith = (a: Site, b: Site) => a && b && a.id === b.id;
});
}
protected filterSites() {
if (!this.sites) {
return;
}
// get the search keyword
let search = this.siteFilterCtrl.value;
if (!search) {
this.filteredSites.next(this.sites.slice());
return;
} else {
search = search.toLowerCase();
}
// filter the sites
this.filteredSites.next(
this.sites.filter(site => site.name.toLowerCase().indexOf(search) > -1)
);
}
}
export interface Site {
id: string;
name: string;
}
export const SITES: Site[] = [
{id: 'site1', name: 'Site 1'},
{id: 'site2', name: 'Site 2'},
{id: 'site3', name: 'Site 3'},
];
For the component im trying to use it in, i have:
<app-site-dropdown formControlName="site"></app-site-dropdown>
And inside the component class I have a form:
this.mySearchForm = this.formBuilder.group( {
site: []
});
I can see and interact with the dropdown just fine, but when i submit my form, I cannot get the value of the selected option. It just always returns null when i try mySearchForm.controls['site'].value
What am I missing to be able to inject my custom dropdown component, and retrieve its value upon form submission?
UPDATE:
I was able to make it work by doing the following:
Inside site-dropdown.component.ts, I changed
protected siteCtrl: FormControl;
to
#Input() siteCtrl: FormControl;
And inside my html using the custom dropdown, i added:
<app-site-dropdown [siteCtrl]="myForm.get('site')"></app-site-dropdown>
This allowed me to save the selected value into my form on submission.

you can get the value of the selected option by having your SiteDropdownComponent implement the ControlValueAccessor interface as follows, resulting in your SiteDropdownComponent behaving as a form control and allowing to access the value with e.g. <app-site-dropdown formControlName="site"></app-site-dropdown>:
...
import { forwardRef } from '#angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '#angular/forms';
#Component({
selector: 'app-site-dropdown',
template: ...
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => SiteDropdownComponent),
multi: true
}
],
})
export class SiteDropdownComponent implements OnInit, OnDestroy, AfterViewInit, ControlValueAccessor {
...
onChange: Function = (_: any) => {};
onTouched: Function = (_: any) => {};
constructor() { }
ngOnInit() {
...
// call this.onChange to notify the parent component that the value has changed
this.siteCtrl.valueChanges
.pipe(takeUntil(this.onDestroy))
.subscribe(value => this.onChange(value))
}
writeValue(value: string) {
// set the value of siteCtrl when the value is set from outside the component
this.siteCtrl.setValue(value);
}
registerOnChange(fn: Function) {
this.onChange = fn;
}
registerOnTouched(fn: Function) {
this.onTouched = fn;
}
}
See e.g. https://github.com/bithost-gmbh/ngx-mat-select-search/blob/d7ea78d511bbec45143c58c855f013a44d0d5055/src/app/mat-select-search/mat-select-search.component.ts#L134

Related

Update the thousand separator commas when the input values are changed in Angular reactive form

The link to the stackblitz https://stackblitz.com/edit/angular-ivy-csqein?file=src/app/hello.component.html
When I copy-paste a value into the INPUT box it returns the correct data
INPUT - 12345678 OUTPUT - 12,345,678
but when I input values one by one it is not able to format it
the output looks like this 1,234567
Expected OUTPUT
When the input is first loaded it comes with the comma-separated digits.
I want to make it so that when the users add or delete values in the input box, the commas are updated in the very box.
Things that I have tried
Creating custom Pipe and updating the value in the component using valueChanges
You can't transform input value when you using formControlName, because when you want to transform that value, angular give you a formControl that you can use to change that value.
For example in your code, you can do this:
constructor(private fb: FormBuilder, private currencyPipe: CurrencyPipe) {}
profileForm;
ngOnInit() {
this.profileForm = this.fb.group({
name: this.currencyPipe.transform(12345678, '', '','0.0'),
});
// this.changes();
}
Note: Don't forget to provide CurrencyPipe in your module.
The code above it's the only way or solution you can do to change the input value when you use the formControlName.
Or another way you can use is remove your formControlName from your input and it will working fine.
<input
id="number"
[value]="profileForm.get('name').value | number"
maxlength="8"
/>
But the problem is you should have to do it manually to patch value from input to your formControl. You can use (input) like what I do in custom input component below.
Custom Input Component
If you like to using custom input component, then code below maybe can help you to resolve your question:
You can create app-input.ts and put this code below:
import { CurrencyPipe } from '#angular/common';
import {
Component,
forwardRef,
HostListener,
Input,
OnInit,
} from '#angular/core';
import {
ControlContainer,
ControlValueAccessor,
FormControl,
FormGroup,
NG_VALUE_ACCESSOR,
} from '#angular/forms';
#Component({
selector: 'app-input[type=currency]',
templateUrl: './currency.component.html',
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => CurrencyComponent),
multi: true,
},
],
})
export class CurrencyComponent implements ControlValueAccessor, OnInit {
#Input() formControlName: string;
value: any;
onChange: () => any;
onTouche: () => any;
public formGroup: FormGroup;
public formControl: FormControl;
constructor(
private controlContainer: ControlContainer,
private currencyPipe: CurrencyPipe
) {}
ngOnInit() {
console.log('Currency Component');
console.log(this.controlContainer);
this.setStateInitialization();
}
private setStateInitialization(): void {
this.formGroup = this.controlContainer.control as FormGroup;
this.formControl = this.formGroup.get(this.formControlName) as FormControl;
}
writeValue(value: any): void {
this.value = value;
}
registerOnChange(fn: any): void {
this.onChange = fn;
}
registerOnTouched(fn: any): void {
this.onTouche = fn;
}
#HostListener('input', ['$event'])
private _onHostListenerInput(event: InputEvent): void {
const inputElement = event.target as HTMLInputElement;
let value: string | number = inputElement.value;
if (value) value = +inputElement.value.replace(/\D/g, '');
this.formControl.patchValue(value);
inputElement.value = value
? this.currencyPipe.transform(value, '', '', '0.0')
: '';
}
}
Now you can add app-input.html and use code below:
<input
type="text"
[value]="formControl.value ? (formControl.value | currency: '':'':'0.0') : ''"
/>
After that if you want to use this component, you can call:
<app-input type="currency" formControlName="currency"></app-input>
Or whatever name you want, you can change it.
Update:
Live Preview: https://angular-ivy-aqppd6.stackblitz.io
Live Code: https://stackblitz.com/edit/angular-ivy-aqppd6?file=src/app/app.component.ts
I hope it can help you to imagine what you can do to resolve your question.

Change the css class of custom component from another component

I mocked up a very small example of my problem here: https://github.com/lovefamilychildrenhappiness/AngularCustomComponentValidation
I have a custom component, which encapsulates an input field. The formControl associated with this input field has Validators.required (it is a required field). Inside the custom component, I have an onChange event which is fired when text is entered. I check if field is empty; if so, I add css class using ngClass. I also have set the registerOnChange of NG_VALUE_ACCESSOR, so I notify the form when the input changes. Finally, I implement NG_VALIDATORS interface to make the formControl invalid or valid.
My problem is I have a button that is clicked (it's not the submit button). When this button is clicked, I need to check if the custom component is blank or not, and if it is, change the css class and make the form invalid. I think the validate method of NG_VALIDATORS is doing that. But I need to change the css class of customComponent so background turns red. I spend severals hours on this and cannot figure it out:
// my-input.component.html
<textarea
[value]="value"
(input)="onChange($event.target.value)"
[ngClass]="{'failed-validation' : this.validationError }">
</textarea>
// my-input.component.ts
validate(control: FormControl): ValidationErrors | null {
if(!this.validationError){
return null
} else {
return { required: true };
}
}
private onChange(val) {
if(val.length > 0) {
this.value = val
this.validationError = false;
} else {
this.validationError = true;
}
// update the form
this.propagateChange(val);
}
// app.component.html
<form [formGroup]="reactiveForm">
<app-my-input formControlName="result"></app-my-input>
<input
value="Submit"
(click)="nextStep($event)"
type="button">
</form>
// app.component.ts
private nextStep(event){
// How do I dynamically change the class of the form control so I can change the style if formControl invalid when clicking the nextStep button
// pseudocode:
// if( !this.reactiveForm.controls['result'].valid ){
// this.reactiveForm.controls['result'].addClass('failed-validation');
// }
}
How can I get the css of the formControl to change in another component?
Since you using reactive form I have modified your custom form control. Here I have Use Injected NgControl Which is base class for all FormControl-based directives extend.
Try this:
import { Component, Input, forwardRef, OnInit } from "#angular/core";
import {
ControlValueAccessor,
NG_VALUE_ACCESSOR,
NgControl,
NG_VALIDATORS,
FormControl,
ValidationErrors,
Validator
} from "#angular/forms";
#Component({
selector: "app-my-input",
templateUrl: "./my-input.component.html",
styleUrls: ["./my-input.component.scss"]
})
export class MyInputComponent implements ControlValueAccessor, OnInit {
private propagateChange = (_: any) => {};
value = "";
onTouch: () => void;
constructor(public controlDir: NgControl) {
controlDir.valueAccessor = this;
}
writeValue(value) {
this.value = value;
}
registerOnChange(fn) {
this.propagateChange = fn;
}
registerOnTouched(fn) {
this.onTouch = fn;
}
onChange(value) {
this.propagateChange(value);
}
ngOnInit() {
const control = this.controlDir.control;
control.setValidators([control.validator ? control.validator : null]);
control.updateValueAndValidity();
}
}
Example
For More Information Forms Check this

Bind different values in multiple textbox with same ngModel property in Angular 8

I am trying to create a time-picker. The picker will be opened when the user focuses on a text-box. Now, a single page may contain multiple text-boxes, for each of which the picker should be opened. The issue I am facing is, I get the values from time-picker for different text-boxes, but when binding to ngModel, any selected value gets bound to all the text-boxes.
Let me show you my approach:
component.html
<input type="text" [(ngModel)]="pickerData" (focus)="initPicker($event)" id="f-1" />
<input type="text" [(ngModel)]="pickerData" (focus)="initPicker($event)" id="f-2" />
<div #timepicks></div> <!-- Here where the picker will be dynamically Injected -->
component.ts
import { Component, OnInit, ViewChild, Output, EventEmitter, HostListener, ViewContainerRef,
ComponentFactoryResolver } from '#angular/core';
import { TimepickComponent } from './timepick/timepick.component';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
pickerData: any;
#ViewChild('timepicks', {read: ViewContainerRef, static: false}) timepicks: ViewContainerRef;
constructor(
private _componentFactoryResolver: ComponentFactoryResolver
) {}
ngOnInit() {
}
initPicker = (event) => {
this.timepicks.clear();
let pickerComponentFactory =
this._componentFactoryResolver.resolveComponentFactory(TimepickComponent);
//Dynamically creating the ' TimepickComponent ' component
let pickerComponentRef = this.timepicks.createComponent(pickerComponentFactory);
(<TimepickComponent>(pickerComponentRef.instance)).pickerId = event.target.id; // Passing id
pickerComponentRef.instance.pickData.subscribe(res => {
this.pickerData = res;
pickerComponentRef.destroy();
});
}
}
Timepickcomponent.ts
.....
.....
#Input() pickerId: any;
#Output() pickData = new EventEmitter();
.....
.....
setClose = () => {
this.pickData.emit(this.valueHolder); // Emitting as output
}
Current Output
Screenshot 1
screenshot 2
As it can be seen, screen1 is opening based on text-box id, but in screen2, when I select and set, it gets populated in another text-box. Ideally, the selected picker from a text-box should bind with that particular text-box.
Any help would be appreciated.
I will use Document Service to access the element and set the value for clicked input control:
HTML:
<input type="text" (focus)="initPicker($event)" id="f-1" />
<input type="text" (focus)="initPicker($event)" id="f-2" />
<div #timepicks></div>
Removed Two way data binding
TS:
import { Component, OnInit, ViewChild, Output, EventEmitter, HostListener, ViewContainerRef, ComponentFactoryResolver, Inject } from '#angular/core';
import { TimepickComponent } from './timepick/timepick.component';
// Added Document reference
import { DOCUMENT } from '#angular/common';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
title = 'timepicker';
isVisible: boolean = false;
pickerData: any;
#ViewChild('timepicks', { read: ViewContainerRef, static: false }) timepicks: ViewContainerRef;
constructor(
#Inject(DOCUMENT) private document: HTMLDocument,
private _componentFactoryResolver: ComponentFactoryResolver
) { }
ngOnInit() {
}
initPicker = (event) => {
this.timepicks.clear();
let pickerComponentFactory = this._componentFactoryResolver.resolveComponentFactory(TimepickComponent);
let pickerComponentRef = this.timepicks.createComponent(pickerComponentFactory);
(<TimepickComponent>(pickerComponentRef.instance)).pickerId = event.target.id;
pickerComponentRef.instance.pickData.subscribe(res => {
console.log(event.target.id);
if (res) {
(<HTMLInputElement>this.document.getElementById(event.target.id)).value = res;
}
pickerComponentRef.destroy();
});
}
}
Import Inject from #angular/core and DOCUMENT from #angular/common
Injected in constructor: #Inject(DOCUMENT) private document: HTMLDocument
Used (<HTMLInputElement>this.document.getElementById(event.target.id)).value = res; to set the value attribute of clicked input element
Working_Demo
The problem here is you were binding both the input boxes with same variable pickerData. So, even if you change any one of them, the change will be reflected for both the values.
Since you have two textboxes you need two variables to store their values, like pickerData1 and pickerData2.
While calling the initPicker() you need to pass one more parameter i,e in which textbox the datepicker is currently being opened and store the date in that respective variable.
Html code
<input type="text" [(ngModel)]="pickerData1" (focus)="initPicker($event, 1)" id="f-1" />
<input type="text" [(ngModel)]="pickerData2" (focus)="initPicker($event, 2)" id="f-2" />
TS code
initPicker = (event, index) => {
this.timepicks.clear();
let pickerComponentFactory =
this._componentFactoryResolver.resolveComponentFactory(TimepickComponent);
//Dynamically creating the ' TimepickComponent ' component
let pickerComponentRef = this.timepicks.createComponent(pickerComponentFactory);
(<TimepickComponent>(pickerComponentRef.instance)).pickerId = event.target.id; // Passing id
pickerComponentRef.instance.pickData.subscribe(res => {
if (index === 1)
this.pickerData1 = res;
elseif (index === 2)
this.pickerData2 = res;
pickerComponentRef.destroy();
});

Angular 5 - Validating own input components derived from a base componet

Currently we are going to port our old application to new technologies and get rid of old design failures and improve the overall UX.
At the moment we are wrapping all standard inputs in own components, so we can change them very easily if needed. All our input components could have a validation but the input components can be nested in an input group of bootstrap, like this:
<form #myForm="ngForm">
<div>
<label>Birth Year</label>
<vng-controls-textbox #user="ngModel" name="user" minlength="5" ngModel ></vng-controls-textbox>
<label>Text</label>
<vng-controls-textbox #text="ngModel" name="text" maxlength="5" ngModel ></vng-controls-textbox>
</div>
<div vng-control-group [icon]="['fa','user']">
<vng-controls-textbox required type="text" #username="ngModel" ngModel name="Username">
</vng-controls-textbox>
<vng-controls-textbox required type="password" #password="ngModel" ngModel name="Passwort">
</vng-controls-textbox>
</div>
</form>
The validation works fine for standalone input components, but we want to deactivate the validation for each nested component in our and get all errors of all possible controls with validations. The desired solution should be a generic approach and no specifics, if possible.
Our base component:
import { Component, OnInit, EventEmitter, Input, Output, forwardRef, AfterViewInit, Self } from '#angular/core';
import { Validator, ControlValueAccessor, FormControl, NgControl } from '#angular/forms';
#Component({
selector: 'app-base'
})
export class BaseComponent implements OnInit, ControlValueAccessor, Validator, AfterViewInit//, IControlBase<any>
{
public ngControl: any;
private parseError: boolean;
private _value: any = '';
/** Input value */
#Input() set value(newValue: any) {
if (newValue != this._value && newValue !== undefined) {
console.log("[BaseComponent]: Set value " + newValue);
this.valueChange.emit(newValue);
this._value = newValue;
}
};
get value(): any {
if (this._value) {
return this._value;
}
}
/** Platzhalter */
#Input() placeholder: any;
/** Disabled */
#Input() disabled: boolean;
/** Name des Controls */
#Input() name: string;
/** Optional: Typ des Controls (Text, Password) */
#Input() type?: string;
#Input() hideErrors: boolean = false;
#Output() valueChange: EventEmitter<any> = new EventEmitter<any>();
// this is the initial value set to the component
public writeValue(obj: any) {
if (obj !== undefined && obj) {
console.log("[BaseComponent] writeValue ", obj)
this._value = obj;
}
}
// registers 'fn' that will be fired wheb changes are made
// this is how we emit the changes back to the form
public registerOnChange(fn: any) {
this.propagateChange = fn;
}
// validates the form, returns null when valid else the validation object
// in this case we're checking if the json parsing has passed or failed from the onChange method
public validate(c: FormControl) {
return null;
}
// not used, used for touch input
public registerOnTouched() { }
// change events from the textarea
private onChange(event) {
if (event.target.value !== undefined) {
console.log("[BaseComponent] "+this.name+" OnChange " + event.target.value)
// get value from text area
this._value = event.target.value;
this.propagateChange(this._value);
}
}
// the method set in registerOnChange to emit changes back to the form
private propagateChange = (_: any) => { };
registerOnValidatorChange?(fn: () => void): void {
console.log("[BaseComponent]: registerOnValidatorChange");
}
setDisabledState?(isDisabled: boolean): void {
console.log("[BaseComponent]: setDisabledState");
}
constructor(#Self() ngControl: NgControl) {
ngControl.valueAccessor = this;
if (ngControl) {
this.ngControl = ngControl;
}
}
ngOnInit() {
}
ngAfterViewInit(): void {
//debugger;
//this.placeholder = this.translateService.instant((this.placeholder ? this.placeholder : this.name))
}
}
Our control group component typescript:
import { Component, OnInit, Input, Host, Self } from '#angular/core';
import { ControlContainer, FormControl, AbstractControl, NgControl } from '#angular/forms';
import { BaseComponent } from '../../_bases/base.component'
import { QueryList, ViewChildren, ContentChildren, TemplateRef, ContentChild } from '#angular/core';
import { TextboxComponent } from '../textbox/textbox.component';
/**
* Komponente für Input-Groups
* #example
* <div vng-control-group [config]="textboxUsernameConfig">
* // Kann ein diverses Control sein, was als CSS-Klasse "form-control besitzt"
* <input class="input form-control" />
* </div>
*
* // oder
*
* <div vng-control-group [icon]="['fa','user']" [label]="Test Label" [position]="'append'">
*/
#Component({
selector: 'div[vng-control-group]',
templateUrl: './control-group.component.html'
})
export class ControlGroupComponent {
private controlContainer: ControlContainer;
public formControl: AbstractControl;
#ContentChild(TemplateRef) template: TemplateRef<any>;
ngAfterContentInit() {
}
/** Konfiguration der Inputgroup als Objekt */
#Input() config: InputGroupConfig;
/** Icon als Attribut (z.B. im HTML) */
#Input() icon?: object;
/** Label als Attribut (z.B. im HTML) */
#Input() label?: string;
/** Position als Attribut (z.B. im HTML) */
#Input() position?: GroupPosition;
constructor(#Host() parent: ControlContainer) {
//this.controlContainer = parent;
//this.formControl = this.controlContainer.control;
}
ngOnInit() {
// Wenn kein Konfig-Objekt übergeben worden ist, setze die Attribute bzw. setze Default-Werte
if (!this.config) {
console.log("[ControlGroupComponent]: Keine Config übergeben")
this.config = {
icon: this.icon || ['fa', 'question'],
label: this.label || '',
position: this.position || GroupPosition.prepend
}
}
}
}
export interface InputGroupConfig {
icon?: object,
label?: string,
position?: GroupPosition
}
export enum GroupPosition {
append = 'append',
prepend = 'prepend'
}
Our control-group html:
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text" id="basic-addon1">#</span>
</div>
<ng-content></ng-content>
</div>
Show error component (ts/html):
// show-errors.component.ts
import { Component, Input } from '#angular/core';
import { AbstractControlDirective, AbstractControl } from '#angular/forms';
#Component({
selector: 'show-errors',
template: `
<ul *ngIf="shouldShowErrors()">
<li style="color: red" *ngFor="let error of listOfErrors()">{{error}}</li>
</ul>
`,
})
export class ShowErrorsComponent {
private static readonly errorMessages = {
'required': () => 'This field is required',
'minlength': (params) => 'The min number of characters is ' + params.requiredLength,
'maxlength': (params) => 'The max allowed number of characters is ' + params.requiredLength,
'pattern': (params) => 'The required pattern is: ' + params.requiredPattern,
'years': (params) => params.message,
'countryCity': (params) => params.message,
'uniqueName': (params) => params.message,
'telephoneNumbers': (params) => params.message,
'telephoneNumber': (params) => params.message
};
#Input()
private control: AbstractControlDirective | AbstractControl;
shouldShowErrors(): boolean {
return this.control &&
this.control.errors &&
(this.control.dirty || this.control.touched);
}
listOfErrors(): string[] {
return Object.keys(this.control.errors)
.map(field => this.getMessage(field, this.control.errors[field]));
}
private getMessage(type: string, params: any) {
return ShowErrorsComponent.errorMessages[type](params);
}
}
How can we achieve that? I'm pretty stuck since 3 days and can't get it working.I really don't know how to disable the error messages in the content of and print all errors messages of all nested inputs under the input group.
Besides that, our nested inputs within the control group are looking pretty bad:
Bad bootstrap format on nested inputs
I've created a stackblitz example which demonstrates the behavior of both problems:
https://angular-ng-bootstrap-khjkkhhjkhk.stackblitz.io
Editor:
https://stackblitz.com/edit/angular-ng-bootstrap-khjkkhhjkhk
Do you have any idea how can we fix that too? I guess it's because of the which wraps the controls in an additional div which causes the strange looking inputs. Preferably we want to style them in the manner of bootstrap.
Maybe it's not the case but I see you're not declaring the providers in #Component decorator using ControlValueAccessor.
Like this:
#Component({
selector: 'app-input',
templateUrl: './input.component.html',
styleUrls: ['./input.component.scss'],
providers: [
{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => InputComponent), multi: true },
{ provide: NG_VALIDATORS, useExisting: forwardRef(() => InputComponent), multi: true }
]
})

Angular 2 shared service to pass data to component-to-component

I am trying to pass the string value of this.title from my LandingPage.component to my ResultPage.component.
I retrieve the list.show value, and send it to my TitleService in like so in my:
landingpage.component.html
<ol>
<li (click)="selectShow(list.show)" [routerLink]="['/details', list.id]" *ngFor="let list of shows">{{list.show}}
</li>
</ol>
landingpage.component.ts
import { TitleService } from '../../services/title.service';
constructor(private TitleService: TitleService) {}
selectShow(show) {
this.TitleService.fetchTitle(show)
}
The above sends the list.show value to my:
title.service.ts
// this gives us the name of the clicked show, which we send to TitleResolver
#Injectable()
export class TitleService {
fetchTitle(title) {
console.log("title is " + title); // this outputs correctly
return title;
}
}
And here is how I manage the routing in my:
app-routing.module.ts
import { TitleService } from './services/title.service';
const routes: Routes = [
{ path: '', component: LandingPage },
{
path: 'details/:id', component: ResultPage
}
];
#NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule],
providers: [TitleService]
})
My question
Once I receive the title.show value in my service component, I'm unsure how to then send it to my receiving component (resultpage.component)
How can I send my title value from my service to my ResultPage.component?
Make the title a public property of the service like this:
// this gives us the name of the clicked show, which we send to TitleResolver
#Injectable()
export class TitleService {
selectedTitle: string;
fetchTitle(title) {
console.log("title is " + title); // this outputs correctly
this.selectedTitle = title;
return title; // No need to return it.
}
}
Then any other component can inject this service and access this.titleService.selectedTitle
In title.service.ts you can declare a variable called title and have setter and getter:
title: string ="";
// replace fetchTitle with setTitle
// remember to change it in the component too
setTitle(title) {
this.title = title;
}
getTitle() {
return this.title;
}
Then, when ResultPage.component is initialized, call getTitle() from TitleService and set the result to a variable declared in the component.
Here's an example of sharing data via shared services.
Separation of concerns... Your landing page is used to select the list item and navigate to the result page. Let it do just that and only that. Let the ResultPage.component do the rest. Note: Other answers recommend storing the value of the last title in the TitleService. It's not a good idea to store state in a service. Then TitleService cannot be used as a generic way to get any title separate from your current navigation, without side effects.
Remove (click) event. Add 'show' as a QueryParam.
landingpage.component.html
<li [routerLink]="['/details', list.id]"
[queryParams]="{show: list.show}"
*ngFor="let list of shows">
{{list.show}}
</li>
Subscribe to router params and queryparams to get the id and show.
resultpage.component.ts
import { Component, OnInit, OnDestroy } from '#angular/core';
import { ActivatedRoute, Router } from '#angular/router';
import { TitleService } from '../../services/title.service';
#Component({
...
})
export class ResultPageComponent implements OnInit, OnDestroy {
itemId: string;
show: string;
subParams: any; // infinite Observable to be unsubscribed
subQueryParams: any; // infinite Observable to be unsubscribed
constructor(
...
private TitleService: TitleService,
protected route: ActivatedRoute,
protected router: Router,
...
) {}
ngOnInit() {
this.subParams = this.route.params.subscribe(this.onParams);
this.subQueryParams = this.route.queryParams(this.onQueryParams);
}
ngOnDestroy() {
// Delete active subscribes on destroy
this.subParams.unsubscribe();
this.subQueryParams.unsubscribe();
}
onParams = (params: any) => {
this.itemId = params['id'];
}
onQueryParams = (data: any) => {
this.show = data.show;
if(this.show) {
this.TitleService.fetchTitle(this.show)
}
}

Categories