I want to init my mat chips input with input data but when I have two chips, it forms only one form :
*.component.ts
#Component({
selector: 'app-pps-dialog',
templateUrl: './pps-dialog.component.html',
})
export class PPSDialogComponent implements OnInit {
patientid: string;
ppsForm: FormGroup;
ppssToDisplay;
visible: boolean = true;
selectable: boolean = true;
removable: boolean = true;
addOnBlur: boolean = true;
// Enter, comma
separatorKeysCodes = [ENTER, COMMA];
constructor(
private route: ActivatedRoute,
private ppssService:PPSsService,
private _changeDetectorRef: ChangeDetectorRef,
private location: Location,
private router: Router,
public dialog: MatDialog,
private formBuilder: FormBuilder,
public dialogRef: MatDialogRef<PPSDialogComponent>,
#Inject(MAT_DIALOG_DATA) public data: any)
{ this.initForm(); }
ngOnInit(): void {
this.ppssToDisplay = this.ppssService.getSinglePPS(this.key)
.subscribe(res => {
this.ppssToDisplay = res;
this.ppsForm.controls['requirements'].setValue(Array(this.ppssToDisplay.effetsind));
console.log(this.ppssToDisplay.effetsind));
});
add(event: MatChipInputEvent): void {
let input = event.input;
let value = event.value;
// Add our requirement
if ((value || '').trim()) {
this.requirements = this.ppsForm.get('requirements') as FormArray;
this.requirements.push(this.formBuilder.control(value.trim()));
}
// Reset the input value
if (input) {
input.value = '';
}
}
remove(index: number): void {
const requirements = this.ppsForm.get('requirements') as FormArray;
if (index >= 0) {
requirements.removeAt(index);
}
}
initForm(): void {
this.ppsForm = this.formBuilder.group({
requirements: this.formBuilder.array(['']),
// I don't know why, but I must init my form with empty chips if I want to receive data from service.
});
}
get formData() {
return <FormArray>this.ppsForm.get('requirements');
}
*ppss.service
import { Injectable } from '#angular/core';
import { Patient } from '../models/patient.model';
import { PPS } from '../models/pps.model';
import { AngularFireDatabase, AngularFireList, AngularFireObject} from 'angularfire2/database';
import { Observable } from 'rxjs/Observable';
import {switchMap, map} from 'rxjs/operators';
import * as firebase from 'firebase';
import DataSnapshot = firebase.database.DataSnapshot;
#Injectable({
providedIn: 'root'
})
export class PPSsService {
ppss: AngularFireList<any>;
constructor(private database: AngularFireDatabase) {this.ppss = database.list('ppss');}
getSinglePPS(key: string){
return this.database.object('ppss/' + key).valueChanges();
}
}
console.log(this.ppssToDisplay.effetsind):
(2) ["Troubles de la vessie", "Troubles de l'érection"]
I've used the example of Angular Material:
*.component.html
<mat-form-field style="width:50%" appearance="outline">
<mat-label>Description du traitement</mat-label>
<textarea matInput formControlName="description"></textarea>
</mat-form-field>
<mat-form-field style="width:50%" appearance="outline">
<mat-label>Effets indésirables du traitement</mat-label>
<mat-chip-list #chipList>
<mat-chip *ngFor="let requirement of formData.controls; let i = index;" [selectable]="selectable"
[removable]="removable" (removed)="remove(i)">
{{requirement.value}}
<mat-icon matChipRemove *ngIf="removable">cancel</mat-icon>
</mat-chip>
<input placeholder="Nouvel effet indésirable..."
[matChipInputFor]="chipList"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
[matChipInputAddOnBlur]="addOnBlur"
(matChipInputTokenEnd)="add($event)"/>
</mat-chip-list>
</mat-form-field>
ERROR Error: Must supply a value for form control at index: 0:
> core.js:1598 ERROR Error: Must supply a value for form control at index: 0.
at forms.js:4304
at forms.js:4274
at Array.forEach (<anonymous>)
at FormArray.push../node_modules/#angular/forms/fesm5/forms.js.FormArray._forEachChild (forms.js:4274)
at FormArray.push../node_modules/#angular/forms/fesm5/forms.js.FormArray._checkAllValuesPresent (forms.js:4302)
at FormArray.push../node_modules/#angular/forms/fesm5/forms.js.FormArray.setValu
I have the same problem if I init my form with :["Troubles de la vessie", "Troubles de l'érection",...]
Reactive FormArray are pretty tricky to use. Check this minimal and valid example:
import {Component, OnInit} from '#angular/core';
import {COMMA, ENTER} from '#angular/cdk/keycodes';
import {FormArray, FormBuilder, FormGroup} from '#angular/forms';
import {MatChipInputEvent} from '#angular/material';
#Component({
selector: 'app',
template: `
<mat-form-field [formGroup]="formData">
<mat-label>Effets indésirables du traitement</mat-label>
<mat-chip-list #chipList>
<mat-chip *ngFor="let requirement of formData.get('requirements').value; let i = index;"
[selectable]="selectable"
[removable]="removable"
(removed)="remove(i)">
{{requirement}}
<mat-icon matChipRemove *ngIf="removable">cancel</mat-icon>
</mat-chip>
<input
placeholder="Nouvel effet indésirable..."
[matChipInputFor]="chipList"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
[matChipInputAddOnBlur]="addOnBlur"
(matChipInputTokenEnd)="add($event)"/>
</mat-chip-list>
</mat-form-field>
`
})
export class AppComponent implements OnInit {
formData: FormGroup;
selectable = true;
removable = true;
addOnBlur = true;
readonly separatorKeysCodes: number[] = [ENTER, COMMA];
constructor(private formBuilder: FormBuilder) {}
ngOnInit() {
this.initForm();
this.ppssToDisplay();
}
initForm(): void {
this.formData = this.formBuilder.group({
requirements: this.formBuilder.array([]),
})
}
ppssToDisplay() {
// Simulate your async call to ppssService
setTimeout(() => {
const control = <FormArray>this.formData.controls.requirements;
['Troubles de la vessie', 'Troubles de l\'érection'].map(x =>
control.push(this.formBuilder.control(x))
);
}, 300)
}
add(e: MatChipInputEvent) {
const input = e.input;
const value = e.value;
if ((value || '').trim()) {
const control = <FormArray>this.formData.controls.requirements;
control.push(this.formBuilder.control(value.trim()));
}
if (input) {
input.value = '';
}
}
remove(i: number) {
let control = <FormArray>this.formData.controls.requirements;
control.removeAt(i);
}
}
Related
mat-autocomplete works great with mock data. when i integrate it with the actual service , It won't populate the dropdown.
my html looks like this
<mat-form-field class="example-full-width">
<input
matInput
placeholder="LastName, FirstName"
aria-label="LastName, FirstName"
[matAutocomplete]="auto"
[formControl]="userCtrl"
/>
<mat-autocomplete #auto="matAutocomplete">
<mat-option
(click)="onSelect(user,i)"
*ngFor="let user of filteredUsers | async; let i = index"
[value]="user.name"
>
<span>{{ user.name }}</span>
<!-- <small> GUID: {{user.guid}}</small> -->
</mat-option>
</mat-autocomplete>
</mat-form-field>
component is like below
import {Component, Inject, ViewChild, OnInit} from '#angular/core';
import { MAT_DIALOG_DATA} from '#angular/material/dialog';
import { FormControl } from "#angular/forms";
import { Observable } from "rxjs";
import { map, startWith } from "rxjs/operators";
import { UserService } from '../../../shared/services/user/user.service';
#Component({
selector: "app-change-user-dialog",
templateUrl: "./change-user-dialog.component.html",
styleUrls: ["./change-user-dialog.component.scss"]
})
export class ChangeUserComponent implements OnInit {
userCtrl: FormControl;
filteredUsers: any;
selectedUser = "";
users = [
{
name: "Barrett, David",
guid: "1213123"
},
{
name: "Barrett, Elizabeth",
guid: "8437593"
},
{
name: "Barrett, Elizabeth",
guid: "2o934u124"
}
];
constructor( public dialogRef: MatDialogRef<ChangeUserDialogComponent>,
#Inject(MAT_DIALOG_DATA) public data: ChangeUserDialogComponent,
private userService: UserService ) {
this.userCtrl = new FormControl();
this.filteredUsers = this.userCtrl.valueChanges.pipe(
startWith(""),
map(user => (user.length >= 3 ? this.filterUsers(user) : []))
);
}
ngOnInit(): void {
}
filterUsers(name) {
this.users.filter((user) => {
return user.name.toLowerCase().indexOf(name.toLowerCase()) === 0
});
}
}
above component works fine and displays the dropdown with mock data ,
Now, Im trying to get the response back from service and assign it back to this.users and it won't work .
Below is what i tried
filterUsers(name: string) {
var split_str = name.split(",");
const lastname = split_str[0] ? split_str[0] : "";
const firstname = split_str[1] ? split_str[1] : "";
this.userService.getGuidByName(firstname, lastname).subscribe((data) => {
this.users = data;
return this.users.filter(
user => user.name.toLowerCase().indexOf(name.toLowerCase()) === 0
);
});
}
my service looks like below
public getGuidByName(firstname, lastname): Observable<any> {
return this.httpService.get('file', `/search?firstname=${firstname}&lastname=${lastname}`)
.pipe(
catchError(this.httpService.handleError('error'))
);
}
service response is exactly same as mock data that i am using.
what am i missing ? Any input is appreciated. Thanks.
this problem is driving me crazy.
I have an array defined within a service, which is used in 3 other components:
This is the service, file products.service.ts (notice the product array of Products)
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { Product } from './../models/Product';
import { ProductForm, productFormToProduct } from './../models/ProductForm';
// #Injectable({
// providedIn: 'root'
// })
const apiUrl = 'http://localhost:3000/products';
#Injectable()
export class ProductsService {
public products: Product[] = [];
constructor(private http: HttpClient) {}
getProducts() {
return this.http.get(apiUrl)
}
deleteProduct(p: Product) {
// this.products = this.products.filter(prod => prod.id !== p.id);
const i = this.products.indexOf(p);
this.products.splice(i,1);
return this.http.delete(apiUrl + "/" + p.id)
}
storeNewProduct(pf: ProductForm) {
const idList = this.products.map((x) => {return x.id});
const i = Math.max(...idList) + 1;
const p = productFormToProduct(pf);
p.id = i;
this.products.push(p);
return this.http.post(apiUrl, p)
}
}
This is the component where i subscribe to getProducts, and fill the array (file products.component.ts):
import { Component, OnInit } from '#angular/core';
import { ProductsService } from '../../shared/services/products.service';
import { Product } from '../../shared/models/Product';
#Component({
selector: 'app-products',
templateUrl: './products.component.html',
styleUrls: ['./products.component.scss']
})
export class ProductsComponent implements OnInit {
products: Product[] = [];
searchText: string = "";
constructor(private productsService: ProductsService) {}
ngOnInit(): void {
this.productsService.getProducts()
.subscribe((data: Product[]) => {
this.productsService.products = data;
this.products = this.productsService.products;
})
}
}
And this is the component where i subscribe to deleteProduct (file product-card.component.ts):
import { Component, Input, OnInit } from '#angular/core';
import { ProductsService } from '../../services/products.service';
import { Product } from './../../models/Product';
#Component({
selector: 'app-product-card',
templateUrl: './product-card.component.html',
styleUrls: ['./product-card.component.scss']
})
export class ProductCardComponent implements OnInit {
constructor(private productsService: ProductsService) {}
ngOnInit(): void {
}
#Input() product: Product
public buttonDeleteFunction() {
this.productsService.deleteProduct(this.product).subscribe();
}
}
The problem is, when i click on some delete product button, i have this weird behaviour:
Before click:
After click:
Here is the products.component.html file:
<div class="products__header">
<h3 class="products__heading">
Listado de productos ({{ products.length }})
</h3>
<input
class="products__search"
placeholder="Buscador"
type="search"
[(ngModel)]="searchText"
/>
</div>
<p *ngFor="let p of products">{{ p.name }}</p>
<p>{{ products }}</p>
<div class="products__list">
<app-product-card
*ngFor="let p of products | filterNames: searchText"
[product]="p"
></app-product-card>
</div>
Why do i get the expected behaviour in only two of the four places where i use the products list?
I know i can use an Output to manually remove the item from the list when i click the button, but i have been told that services are used instead of Inputs/Outputs when i want to share between multiple components, so i'd rather not use an Output for this
When you use your approach with common data on service layer then a common pitfall is that Angular does not detect the changes that affect your component. In that case you must inform your component for those changes using an emmiter.
Use an emmiter on service
productUpdated :EventEmitter = new EventEmitter();
deleteProduct(p: Product) {
// this.products = this.products.filter(prod => prod.id !== p.id);
const i = this.products.indexOf(p);
this.products.splice(i,1);
this.productUpdated.emit(this.products);
return this.http.delete(apiUrl + "/" + p.id)
}
And then listen for that change ProductsComponent
export class ProductsComponent implements OnInit {
products: Product[] = [];
searchText: string = "";
constructor(private productsService: ProductsService) {}
ngOnInit(): void {
this.productsService.getProducts()
.subscribe((data: Product[]) => {
this.productsService.products = data;
this.products = this.productsService.products;
})
this.productsService.productUpdated.subscribe( (data) => {
this.products = data;
});
}
I'm trying to do an action after creating an event.
Create a control form that has been selected in form1 several p, and update a PSelected table.
I have two files, A.html and A.ts
A.html:
<mat-select placeholder="form1" [formControl]="product" (ngModelChange)="getP($event)" multiple> <mat-option *ngFor="let p of allProduct" [value]="p.pro_nom">{{p.pro_nom}}</mat-option>
</mat-select>
<mat-form-field *ngFor="let valeurControls of valeursControls">
<input matInput maxLength="255" type="text [placeholder]="valeurControls.valeur.n [formControl]="valeurControls.formControl">
</mat-form-field>
A.ts:
import { ChangeDetectorRef, Component, OnInit, ViewEncapsulation, Output, EventEmitter } from '#angular/core';
import { FormBuilder, FormControl, FormGroup, Validators } from '#angular/forms';
import { filter, takeUntil } from 'rxjs/operators';
import { Product, Candy } from 'src/app/models';
import { Service } from 'src/app/services';
import { Listener } from 'selenium-webdriver';
#Component({
selector: 'ei',
templateUrl: './A.component.html',
styleUrls: ['./A.component.less'],
encapsulation: ViewEncapsulation.None
})
export class AComponent extends FormFieldsContainer implements OnInit {
#Output('change') inputChange = new EventEmitter();
//var
allProduct: Product[];
Candy: Candy;
// Form
actionFormGroup: FormGroup;
product: FormControl;
event: Listener[]
// P
valeursControls: { valeur: candy, formControl: FormControl }[] = [];
public pSelected: Array<any>;
// Constructor
constructor(private fb: FormBuilder) {
super();
this.Candy = this.Candy ? this.Candy : { name: null, type: null };
this.pSelected = [];
this.buildForm();
}
ngOnInit() {
this.Service.getProduct()
.pipe(takeUntil(this.unsubscribe))
.subscribe(p => {
this.allProduct = p;
});
}
getP(event?: Event[]) {
if (typeof event == 'undefined') {
this.pSelected = [];
} else {
this.pSelected = event;
}
console.log(this.pSelected)
return this.pSelected;
}
getFormGroup(): FormGroup {
return this.actionFormGroup;
}
onSubmitSuccess<Boolean>(result: Boolean) {
}
private buildForm() {
this.submitted = false;
this.p= this.fb.control('', Validators.required);
this.actionFormGroup = this.fb.group({
product: this.product
});
// my array does not update, it remains empty
this.pSelected .forEach(p => {
const VarFormControl = this.fb.control(null);
this.valeursControls.push({
valeur: { name: p, type: this.Candy.type },
formControl: VarFormControl
});
this.actionFormGroup.addControl(p, VarFormControl );
});
}
Actions() {
this.submitted = true;
}
}
in the function getP(), my table is updating, but when I use it in buildForm() it is empty yet it should have the same value
you are calling this.buildForm(); from the component constructor.
The value of pSelected will be the declared one: public pSelected: Array;
You should rebuild valeursControls any time you select/unselect a value inside the mat-select component.
try:
getP(event?: Event[]) {
this.valeursControls = [];
if (typeof event == 'undefined') {
this.pSelected = [];
} else {
this.pSelected = event;
this.buildValeursControls();
}
}
private buildValeursControls(){
this.pSelected.forEach(p => {
const VarFormControl = this.fb.control(null);
this.valeursControls.push({
valeur: { n: p, r: this.B.r },
formControl: VarFormControl
});
this.actionFormGroup.addControl(p, VarFormControl );
});
}
private buildForm() {
this.submitted = false;
this.p= this.fb.control('', Validators.required);
this.actionFormGroup = this.fb.group({
p: this.p
});
}
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 }
]
})
I'm trying to figure out how to switch a variable in a group of child components
I have this component for a editable form control which switches between view states
import {
Component,
Input,
ElementRef,
ViewChild,
Renderer,
forwardRef,
OnInit
} from '#angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '#angular/forms';
const INLINE_EDIT_CONTROL_VALUE_ACCESSOR = {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => InlineEditComponent),
multi: true
};
#Component({
selector: 'inline-edit',
templateUrl: 'inline-edit.html',
providers: [INLINE_EDIT_CONTROL_VALUE_ACCESSOR],
})
export class InlineEditComponent implements ControlValueAccessor, OnInit {
#ViewChild('inlineEditControl') inlineEditControl: ElementRef;
#Input() label: string = '';
#Input() type: string = 'text';
#Input() required: boolean = false;
#Input() disabled: boolean = false;
private _value: string = '';
private preValue: string = '';
public editing: boolean = false;
public onChange: any = Function.prototype;
public onTouched: any = Function.prototype;
get value(): any {
return this._value;
}
set value(v: any) {
if (v !== this._value) {
this._value = v;
this.onChange(v);
}
}
writeValue(value: any) {
this._value = value;
}
public registerOnChange(fn: (_: any) => {}): void {
this.onChange = fn;
}
public registerOnTouched(fn: () => {}): void {
this.onTouched = fn;
}
constructor(element: ElementRef, private _renderer: Renderer) {
}
ngOnInit() {
}
}
<div>
<div [hidden]="!editing">
<input #inlineEditControl [required]="required" [name]="value" [(ngModel)]="value" [type]="type" [placeholder]="label" />
</div>
<div [hidden]="editing">
<label class="block bold">{{label}}</label>
<div tabindex="0" class="inline-edit">{{value}} </div>
</div>
</div>
I'm trying to create a simple directive to consume these components and change the editing flag to true
export class EditForm {
//I want to do something like this:
public toggleEdit(fn: () => {}): void {
var editableFormControls = $('#selector: 'inline-edit');
editableFormControls.forEach(control => control.editing = true)
}
}
I want to grab all of the ediiitable form controls and set the editing flag in all of them to true, how can I do this?
You might need to implement a service that keeps the state and all child component subscribe to the state and parent push changes there.
import {Component, NgModule, VERSION, Input} from '#angular/core'
import {BrowserModule} from '#angular/platform-browser'
import {BehaviorSubject} from 'rxjs/BehaviorSubject';
export class EditableService {
subject = new BehaviorSubject(true);
getAsObservable() {
return this.subject.asObservable();
}
}
#Component({
selector:'editable',
template: '<div>i am editable {{ x | async}}</div>'
})
export class Editable {
constructor(private editableService: EditableService) {
this.x = editableService.getAsObservable();
}
}
#Component({
selector: 'my-app',
template: `
<editable></editable>
<editable></editable>
<hr/>
<button (click)="change()">change</button>
`,
providers: [EditableService]
})
export class App {
change() {
this.editableService.subject.next(false);
}
constructor(private editableService: EditableService) {
this.name = `Angular! v${VERSION.full}`;
}
}