I have component where I implement ControlValueAccessor and I'm having problems understanding the correct way to use it:
import { Component, OnInit, forwardRef, Output, EventEmitter, OnChanges, Input, ViewChild } from '#angular/core';
import { NG_VALUE_ACCESSOR, ControlValueAccessor } from '#angular/forms';
import { UserOrEmail } from '../entities/UserOrEmail';
export const USER_INPUT_CONTROL_VALUE_ACCESSOR: any = {
provide: NG_VALUE_ACCESSOR,
// tslint:disable-next-line
useExisting: forwardRef(() => AddUserOrEmailComponent),
multi: true,
};
const noop = () => {
// Placeholder operation
};
#Component({
selector: 'app-add-user-or-email',
templateUrl: './add-user-or-email.component.html',
providers: [USER_INPUT_CONTROL_VALUE_ACCESSOR]
})
export class AddUserOrEmailComponent implements OnInit, ControlValueAccessor {
#Input()
user: any = UserOrEmail;
#Output()
change: EventEmitter<UserOrEmail> = new EventEmitter<UserOrEmail>();
users: any = [];
ngOnInit() {
this.user = {
userId: 'ull',
name: 'null',
email: 'null'
};
this.users = ConstantService.UserArray;
}
// #region [ Value Accessor Interface ]--------------------------------------------------------
// Placeholders for the callbacks which are later provided
// by the Control Value Accessor
private onTouchedCallback: () => void = noop;
private onChangeCallback: (_: any) => void = noop;
get value(): any {
return this.user;
}
// [ ControlValueAccessor interface implementation ]-------------------------------------------
set value(v: any) {
if (this.user !== v) {
this.user = <UserOrEmail>v;
this.onChangeCallback(v);
this.change.next(this.user);
}
}
writeValue(value: any) {
if (value !== this.user)
this.user = <UserOrEmail>value;
}
registerOnChange(fn: any) {
this.onChangeCallback = fn;
}
registerOnTouched(fn: any) {
this.onTouchedCallback = fn;
}
}
and html:
<div>
<div class="form-column">
<div class="form-row">
<label>
{{'GENERIC.USER'|translate}}
</label>
<select>
<option [ngValue]="'default'"></option>
<option *ngFor="let user of users" [ngValue]="user">{{user.login}}</option>
</select>
</div>
<div class="form-row">
<label for="addPersonEmail" >{{'GENERIC.EMAIL' | translate}}</label>
<input type="email" placeholder="{{'GENERIC.EMAIL'|translate}}" pattern="^\w+([\.-]?\w+)*#\w+([\.-]?\w+)*(\.\w{2,3})+$">
</div>
</div>
</div>
I try to use it in another component:
hmtl:
<app-modal class="form" #addMilestoneModal [width]="550">
<div header>{{'MODALS.ADD_MILESTONE'|translate}}</div>
<div body>
<div class="form-column">
<div class="form-value">
<app-add-user-or-email #addUser [(ngModel)]="milestone.assignee"></app-add-user-or-email>
</div>
<div class="form-row">
<label for="addMilestoneDescription">{{'GENERIC.DESCRIPTION' | translate}}</label>
<textarea style="height: 150px" [(ngModel)]="milestone.description"></textarea>
</div>
</div>
</div>
<div footer class="flex-container">
<button class="flex-item-row btn btn-a" [disabled]="!milestone.description" (click)="apply()">{{'GENERIC.APPLY'
| translate}}</button>
</div>
</app-modal>
Typescript:
import { Component, ViewChild, EventEmitter } from '#angular/core';
import { ModalComponent } from '../../widgets/modal/modal.component';
import { Milestone } from '../../entities/Milestone';
import { ConstantService } from '../../services/ConstantService';
import { AddUserOrEmailComponent } from '../../add-user-or-email/add-user-or-email.component';
#Component({
selector: 'app-add-milestone-modal',
templateUrl: './add-milestone-modal.component.html'
})
export class AddMilestoneModalComponent {
#ViewChild('addMilestoneModal')
modal: ModalComponent;
cs = ConstantService;
emitter: EventEmitter<Milestone> = new EventEmitter<Milestone>();
milestone: any = Milestone;
apply() {
console.log(this.milestone); // <---- HERE IT SHOULD BE ACCESSED
debugger;
this.emitter.next(this.milestone);
this.modal.close();
}
cancel() {
this.emitter.next(null);
this.modal.close();
}
}
I should get it in milestone object but it is empty. What am I missing?
I had problems with this a couple of weeks ago and made a StackBlitz with a good example.
Hopefully this can help you?
https://stackblitz.com/edit/mat-select-with-controlvalueaccessor
Related
I have a form with a text input and then I want to add a select inside that Form:
The select is an array:
typeAlert: TypeAlert[] = [
{ value: 'MEDICAL'},
{ value: 'POLICE'},
{ value: 'FIRE'}
];
I want to bind the value selected on the select. I have a var called select to store which the user has selected. I have to add the selected inside formGroup?
Also I am getting this error:
ngModel cannot be used to register form controls with a parent formGroup directive
component.html
<form class="container" [formGroup]="alertForm">
<div class="actions">
<div class="action" [routerLink]="['/alertes']">
<span>Torna a Alertes</span>
</div>
<div class="space"></div>
<button class="action" (click)="save()" [disabled]="!alertForm.valid">
Guardar
</button>
</div>
<div class="dades">
<div class="card">
<div class="card-title">
<b>Title</b>
</div>
<div class="card-content">
<mat-form-field>
<mat-label>Title</mat-label>
<input formControlName="title" matInput>
</mat-form-field>
</div>
</div>
</div>
<div class="dades">
<div class="card">
<div class="card-title">
<b>Type Alert</b>
</div>
<div class="card-content">
<mat-form-field appearance="fill">
<mat-label>Type Alert</mat-label>
<mat-select [(ngModel)]="selected">
<mat-option *ngFor="let alert of typeAlert" [value]="alert.value">
{{alert.value}}
</mat-option>
</mat-select>
</mat-form-field>
</div>
</div>
</div>
</form>
component.ts:
import { Component, OnInit, OnDestroy } from '#angular/core';
import { FormGroup, FormControl, FormArray, Validators } from '#angular/forms';
import { ActivatedRoute, Router } from '#angular/router';
import { AlertesService } from 'src/app/core/services/alertes.service';
import { MatDialog } from '#angular/material/dialog';
import { DialogNotificationsComponent } from 'src/app/shared/components/dialog-notifications/dialog-notifications.component';
interface TypeAlert {
value: string;
}
#Component({
selector: 'app-alert',
templateUrl: './alert.component.html',
styleUrls: ['./alert.component.scss']
})
export class AlertComponent implements OnInit, OnDestroy {
typeAlert: TypeAlert[] = [
{ value: 'MEDICAL'},
{ value: 'POLICE'},
{ value: 'FIRE'}
];
selected: string = this.typeAlert[0].value;
alertForm: FormGroup;
alert;
constructor(
private alertesService: AlertesService,
private route: ActivatedRoute,
private router: Router,
public dialog: MatDialog
) { }
ngOnInit() {
this.alert = this.route.snapshot.data.alert;
this.initForm();
}
ngOnDestroy() {
}
initForm() {
this.alertForm = new FormGroup({
title: new FormControl(this.alert ? this.alert.title : '', [Validators.required]),
});
}
save() {
console.log(this.selected);
if(this.alert) {
this.alertesService.update(this.alert._id, this.alertForm.value).subscribe(() => {
this.router.navigate(['/alertes'])
})
}
else {
const dialogRef = this.dialog.open(DialogNotificationsComponent, {
width: '600px',
data: {title: "Nova alerta", msg: this.alertForm.value.title}
});
dialogRef.afterClosed().subscribe(result => {
console.log(result);
if(result != undefined && result != null) {
this.alertesService.create({
notification: result ? result: null,
...this.alertForm.value
}).subscribe(() => {
this.router.navigate(['/alertes'])
})
}
});
}
}
}
You should [formControlName] with form groups, instead of ngmodel.
Please check https://angular.io/api/forms/FormControlName
i have a deckbuilder, a decklist component that shows the list of decks, and when i click on a deck the proper deck-details component is loaded using the deck's id with routing. This is the deck-details part where i use route.
ngOnInit(){
this.decks = this.deckService.getDecks(); // this just returns the array of decks
this.id = this.route.snapshot.params["id"];
this.paramsSubscription = this.route.params.subscribe((params: Params) => {
this.id = params["id"];
console.log(this.id);
this.deck = this.decks.find((deck) => deck.id === this.id);
console.log("deck-detail deck", this.deck);
}
and this is the app-routing.module
const appRoutes: Routes = [
{ path: '', component: CardListComponent },
{ path: 'decks', component: DeckListComponent, children: [
{ path: ':id/:deckName', component: DeckDetailsComponent }
] },
{ path: 'createDeck', component: CreateDeckComponent },
];
#NgModule({
imports: [RouterModule.forRoot(appRoutes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
so the url become something like this when i click on a deck: localhost:4200/decks/id/deckName.
If page is reloaded when the url is like this, deck-detail component is not reloaded with the page, and i get this error
TypeError: Cannot read property 'deckName' of undefined
at DeckDetailsComponent_Template
What i'm doing wrong? there is a component lifecycle problem? how can i avoid decks to be undefined when i reload the page?
this is the whole deck-details component
import { Card } from "./../../card/card.model";
import { Deck } from "./../../deck/deck.model";
import { Component, OnInit, Input } from "#angular/core";
import { DeckService } from "src/app/deck/deck.service";
import { ActivatedRoute, Params, Router } from "#angular/router";
import { Subscription } from "rxjs";
import { DataStorageService } from "src/app/shared/data-storage.service";
import { CardService } from "src/app/card/card.service";
import { FormControl, FormGroup } from "#angular/forms";
#Component({
selector: "app-deck-details",
templateUrl: "./deck-details.component.html",
styleUrls: ["./deck-details.component.scss"],
})
export class DeckDetailsComponent implements OnInit {
//#Input() deck: Deck;
paramsSubscription: Subscription;
id: number;
decks: Deck[];
deck: Deck;
cards: Card[];
selectedClassCards: Card[];
classes = [
"Priest",
"Mage",
"Shaman",
"Rogue",
"Warrior",
"Warlock",
"Druid",
"Paladin",
];
addCardForm: FormGroup;
constructor(
private deckService: DeckService,
private cardService: CardService,
private route: ActivatedRoute,
private dataStorageService: DataStorageService
) {}
ngOnInit() {
this.addCardForm = new FormGroup({
deckCards: new FormControl(),
});
this.cards = this.cardService.getCards();
this.decks = this.deckService.getDecks();
this.id = this.route.snapshot.params["id"];
this.paramsSubscription = this.route.params.subscribe((params: Params) => {
this.id = params["id"];
this.deck = this.decks.find((deck) => deck.id === this.id);
console.log("deck-detail deck", this.deck);
this.selectedClassCards = this.cards.filter(
(card) => card.hero === this.deck.deckClass || card.hero === "Neutral"
);
});
}
onDeleteCard(i) {
this.deckService.deleteCard(this.deck, this.deck.deckCards[i]);
//this.deckService.cardsChanged.next(this.deck.deckCards.slice());
console.log(this.deck);
}
onAddCard() {
this.deckService.addCard(this.deck, this.addCardForm.value.deckCards);
console.log(this.deck);
}
onCardsEdit() {
this.dataStorageService.storeCards(
this.decks.indexOf(this.deck),
this.deck.deckCards
);
}
ngOnDestroy() {
this.paramsSubscription.unsubscribe();
}
}
and this is the DeckService
import { DataStorageService } from './../shared/data-storage.service';
import { Deck } from './deck.model';
import { Card } from '../card/card.model';
import {EventEmitter, Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { Subject } from 'rxjs';
#Injectable()
export class DeckService {
deckSelected = new EventEmitter<Deck>();
decksChanged = new Subject<Deck[]>();
cardsChanged = new Subject<Card[]>();
decks: Deck[] = [];
remainingDecks: Deck[];
constructor(/* private http: HttpClient, */ /* private dataStorageService: DataStorageService */) {}
setDecks(decks: Deck[]) {
this.decks = decks;
this.decksChanged.next(this.decks.slice());
}
getDecks() {
if (this.decks) {
return this.decks.slice();
}
}
addCard(deck: Deck, card: Card){
deck.deckCards.push(card);
}
deleteCard(deck: Deck, card: Card){
deck.deckCards.splice( deck.deckCards.indexOf(card), 1);
}
addNewDeck(deck: Deck){
this.decks.push(deck);
this.decksChanged.next(this.decks.slice());
};
deleteDeck(id: number) {
this.remainingDecks = this.decks.filter(
deck => deck.id !== id
)
this.decks = this.remainingDecks;
this.decksChanged.next(this.decks.slice());
}
}
and this is where i store and fetch decks with fire base and httpClient
import { DeckService } from "./../deck/deck.service";
import { CardService } from "./../card/card.service";
import { Deck } from "./../deck/deck.model";
import { Card } from "./../card/card.model";
import { Injectable } from "#angular/core";
import { HttpClient } from "#angular/common/http";
import { map, tap } from "rxjs/operators";
#Injectable({ providedIn: "root" })
export class DataStorageService {
constructor(private http: HttpClient, private deckService: DeckService) {}
storeDecks() {
const decks = this.deckService.getDecks();
this.http
.put("https://ang-cards.firebaseio.com/decks.json", decks)
.subscribe((response) => {
console.log(response);
console.log("stored");
});
}
fetchDecks() {
return this.http
.get<Deck[]>("https://ang-cards.firebaseio.com/decks.json")
.subscribe((decks) => {
decks
? this.deckService.setDecks(decks)
: this.deckService.setDecks([]);
console.log("fetching", decks);
});
}
storeCards(i: number, cards: Card[]){
this.http
.put("https://ang-cards.firebaseio.com/decks/" + i + "/deckCards.json", cards)
.subscribe((response) => {
console.log(response);
console.log("cards stored");
});
}
}
for info this is the deck-detail component html template
<div class="container">
<div class="row">
<div class="deck-details " style="border:solid black">
<!-- <img
[src]="'../../assets/img/' + deck.deckClass + '.png'"
alt="{{ deck.deckName }}"
class="img-responsive"
style="height: 200px;"
/> -->
<h1 contenteditable="true">{{ deck.deckName }} : {{ deck.deckClass }}</h1>
<div *ngFor="let card of deck.deckCards; let i = index" >
<span
[ngClass]="{
common: card.rarity == 'common',
rare: card.rarity == 'rare',
epic: card.rarity == 'epic',
legendary: card.rarity == 'legendary'
}"
>
{{ card.name }}
</span>
<h4 class="inlineh4">: {{ card.manaCost }} mana</h4>
<button class="btn btn-danger" (click) = "onDeleteCard(i)" >delete</button>
<!-- <img [src]="card.imagePath" alt="" style="height: 20px;" /> -->
</div>
<hr>
<label for="deckCards">Add a Card</label>
<form [formGroup]="addCardForm">
<select formControlName="deckCards" id="deckCards" name="deckCards">
<option *ngFor="let card of selectedClassCards" [ngValue]="card">
{{ card.name }}
</option>
</select>
<button (click)="onAddCard()" type="button" class="btn btn-primary">
Add Card
</button>
</form>
<hr>
<button class="btn btn-primary"(click) = "onCardsEdit()" >Save changes</button>
</div>
</div>
</div>
Below is my Component :
import { Component, OnInit } from '#angular/core';
import { FormBuilder, FormGroup, Validators } from '#angular/forms';
import { HttpService } from './http.service';
import { ProjectidService } from './projectid.service';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
projectDetailForm: FormGroup;
public submitted = false;
constructor(private fb: FormBuilder, private projectidvalidator: ProjectidService) { }
ngOnInit() {
this.projectDetailForm = this.fb.group({
projectid: ['', [Validators.required], [this.projectidvalidator.validate.bind(this.projectidvalidator)]],
projectname: ['name', Validators.required]
})
}
get f() { return this.projectDetailForm.controls; }
get validprojectid() { return this.projectDetailForm.get('projectid'); }
onSubmit(form: FormGroup) {
this.submitted = true;
// stop here if form is invalid
if (this.projectDetailForm.invalid) {
return;
}
console.log('Valid?', this.projectDetailForm.valid); // true or false
console.log('ID', this.projectDetailForm.value.projectid);
console.log('Name', this.projectDetailForm.value.projectname);
}
}
My Service :
import { Injectable } from '#angular/core';
import { Observable, of } from 'rxjs';
import { delay, tap, debounceTime } from 'rxjs/operators';
#Injectable()
export class HttpService {
constructor() { }
checkProjectID(id): Observable<any> {
// Here I will have valid HTTP service call to check the data
return of(true)
}
}
My Async validator :
import { HttpService } from './http.service';
import { Injectable } from '#angular/core';
import { AsyncValidator, AbstractControl, ValidationErrors } from '#angular/forms';
import { Observable, of } from 'rxjs';
import { map, catchError, debounceTime, switchMap } from 'rxjs/operators';
#Injectable()
export class ProjectidService {
constructor(private _httpService:HttpService) { }
validate(control: AbstractControl): Promise<ValidationErrors | null> | Observable<ValidationErrors | null> {
console.log(control.value);
return control.valueChanges.pipe(
debounceTime(500),
switchMap(_ => this._httpService.checkProjectID(control.value).pipe(
map(isTaken => {
console.log(isTaken);
if (isTaken) {
return { noproject: true }
} else {
return null
}
})
)),
catchError(() => null)
);
}
}
and template :
<form [formGroup]="projectDetailForm" name="projectdetails" (ngSubmit)="onSubmit(projectDetailForm)">
<div class="form-group">
<label for="id">Project ID</label>
<input type="text" class="form-control" id="id" [ngClass]="{ 'is-invalid': f.projectid.invalid && (f.projectid.dirty || f.projectid.touched) }" placeholder="Project ID" name="projectid" formControlName='projectid'>
<button type="button">Validate</button>
<div *ngIf="f.projectid.invalid && (f.projectid.dirty || f.projectid.touched)" class="invalid-feedback">
<div *ngIf="f.projectid.errors.required">Project ID is required</div>
<div *ngIf="f.projectid.errors?.noproject">
Project id is not valid
</div>
</div>
<div *ngIf="f.projectid.errors?.noproject">
Project id is not valid
</div>
{{f.projectid.errors | json}}
</div>
<div class="form-group">
<label for="name">Project Name</label>
<input type="text" class="form-control" id="name" placeholder="Project Name" name="projectname" readonly formControlName='projectname'>
</div>
<div class="form-group d-flex justify-content-end">
<div class="">
<button type="button" class="btn btn-primary">Cancel</button>
<button type="submit" class="btn btn-primary ml-1">Next</button>
</div>
</div>
</form>
Problem is my custom async validation error message is not getting displayed.
Here is stackblitz example
You could do it as follows using rxjs/timer:
import { timer } from "rxjs";
....
return timer(500).pipe(
switchMap(() => {
if (!control.value) {
return of(null);
}
return this._httpService.checkProjectID(control.value).pipe(
map(isTaken => {
console.log(isTaken);
if (isTaken) {
return { noproject: true };
} else {
return null;
}
})
);
})
);
Sample
The real problem is and I have encountered this myself, you subscribe to the value change but you need to wait for the statuschange to return.
It is "PENDING" while it is doing the call.
The debounce/timer/... are just 'hacks' since you never know when the value is returned.
Declare a variable:
this.formValueAndStatusSubscription: Subscription;
In your
this.formValueAndStatusSubscription =
combineLatest([this.form.valueChanges, this.form.statusChanges]).subscribe(
() => this.formStatusBaseOnValueAndStatusChanges = this.form.status
);
Don't forget to desstroy the subscription
The most important point in the async validation is as descriped in Angular Doc
The observable returned must be finite, meaning it must complete at
some point. To convert an infinite observable into a finite one, pipe
the observable through a filtering operator such as first, last, take,
or takeUntil.
so basically you can use for example take(1) , it'll take the first emission then mark the Observable completed
return control.valueChanges.pipe(
debounceTime(500),
take(1),
switchMap(() =>
this._httpService.checkProjectID(control.value).pipe(
map(isTaken =>
isTaken ? { noproject: true } : null
)
))
)
demo
Below is a simple reactive form with the filter of an array of checkboxes.
As soon as page render getting error
Cannot find control with path: 'accountsArray -> 555'
However, the filter is working perfectly, but while removing any character from filter throws an error
Cannot find control with path: 'accountsArray -> 123'
Form control not found based on search.
Below is length code, but that will help you to understand clearly.
Component:
import { Component, OnInit } from '#angular/core';
import { FormBuilder, FormArray, FormGroup, FormControl } from '#angular/forms';
import { SubAccount } from './account-model';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
searchForm: FormGroup;
searchTerm = '';
formUpdated = false;
accounts = [
new SubAccount('123'),
new SubAccount('555'),
new SubAccount('123555')
];
subAccount = [];
constructor(private fb: FormBuilder) { }
get accountsArray(): FormArray {
return this.searchForm.get('accountsArray') as FormArray;
}
addAccount(theAccount: SubAccount) {
this.accountsArray.push(this.fb.group({
account: theAccount
}));
}
ngOnInit() {
this.formUpdated = false;
this.searchForm = this.fb.group({
accountSearch: '',
accountsArray: this.fb.array([new FormControl('')])
});
this.accounts.forEach((field: any) => {
this.subAccount.push({ key: field.key, value: field.key });
});
const fieldFGs = this.subAccount.map((field) => {
const obj = {};
if (field.value) {
obj[field.value] = true;
} else {
obj[field] = true;
}
return this.fb.group(obj);
});
const fa = this.fb.array(fieldFGs);
this.searchForm.setControl('accountsArray', fa);
this.formUpdated = true;
}
getAccountNumber(account: SubAccount) {
return Object.keys(account)[0];
}
}
View:
<div [formGroup]="searchForm" *ngIf="formUpdated">
<label for="search">Find an account...</label>
<input id="search" formControlName="accountSearch" [(ngModel)]="searchTerm" />
<div formArrayName="accountsArray" *ngIf="formUpdated">
<div *ngFor="let account of accountsArray.controls | filter: 'key' :searchTerm; let ind=index">
<input type="checkbox" id="checkbox_claim_debtor_{{ind}}" formControlName="{{getAccountNumber(account.controls)}}"/>
<span> {{getAccountNumber(account.controls)}} </span>
</div>
</div>
</div>
Pipe:
import { Pipe, PipeTransform } from '#angular/core';
#Pipe({
name: 'filter'
})
export class FilterPipe implements PipeTransform {
transform(items: any[], field: string, value: string): any[] {
if (!value && !items) {
return items;
}
return items.filter((item) => {
const val = Object.keys(item.controls)[0];
if (val && val.toLowerCase().indexOf(value.toLowerCase()) >= 0) {
return true
} else {
return false;
}
});
}
}
Appreciate your help.
Stackblitz link:
https://stackblitz.com/edit/angular-9ouyqr
please check the [formGroupName]="ind" , it is not written while iterating the form array ,formGroupname should be addeed with the form index
<div [formGroup]="searchForm" *ngIf="formUpdated">
<label for="search">Find an account...</label>
<input id="search" formControlName="accountSearch" [(ngModel)]="searchTerm" />
<div formArrayName="accountsArray" *ngIf="formUpdated">
<div [formGroupName]="ind" *ngFor="let account of accountsArray.controls | filter: 'key' :searchTerm; let ind=index"
>
<input type="checkbox" id="checkbox_claim_debtor_{{ind}}" formControlName="{{getAccountNumber(account.controls)}}"/>
<span> {{getAccountNumber(account.controls)}} </span>
</div>
</div>
</div>
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}`;
}
}