Angular 7, elementRef not correct - javascript

i have created component autocomplete and i was using it just fin till i had to user 2 of those component in 1 view, i saw that dropdown in template is referenced to previous instance of my autocomplete component
#Component({
selector: 'app-autocomplete',
templateUrl: './autocomplete.component.html',
styles: []
})
export class AutocompleteComponent implements OnInit, OnChanges {
delayId;
#ViewChild('dropdown')
dropdown;
#ViewChild('element')
input;
....
}
and template
<div class="form-group" style="position: relative;">
<label for="{{buttonId}}">{{buttonCaption}}</label>
<input #element (keyup)="sendKeyStrokes(element.value)"
type="text" class="form-control"
id="{{buttonId}}"
aria-describedby=""
placeholder="{{buttonCaption}}">
<ng-content></ng-content>
<div #dropdown <-- **this is wrong** class="dropdown-menu" aria-labelledby="btnGroupDrop1">
<span *ngFor="let option of options"
class="dropdown-item"
(click)="optionClick(option)">
{{option.caption}}
</span>
</div>
</div>
can i create reference # dynamically ? or someone can point me to right direction

Related

How do I add a conditional form field through content projection in Angular?

I am trying to add a conditional field through content projection.
The projected input field is toggling correctly in accordance to its sibling checkbox field. When the checkbox is "checked" the conditional field displays. When the checkbox is "unchecked", the input field disappears.
However, when I move the checkbox into its "checked" state, I'm receiving an error:
ERROR Error: Cannot find control with name: 'detail'
I want the projected detail field to be apart of the _formGroup in the child component, not the parent component. How can I achieve this?
The error seems to suggest that the formControlName="detail" is not visible to the _formGroup in the child component. How can I rectify this?
Here is the parent component with the outer parent Form Group.
Parent Component
import { Component, OnInit } from '#angular/core';
import { FormControl, FormGroup } from '#angular/forms';
#Component({
selector: 'ch-parent-comp',
templateUrl: './parent-comp.component.html',
styleUrls: ['./parent-comp.component.scss']
})
export class ParentComponent implements OnInit {
formGroup: FormGroup;
constructor() {
this.formGroup = new FormGroup({
//detail: new FormControl('') --> Adding this will solve the issue, but I don't want this field here. I want it in the child
});
}
ngOnInit(): void {
}
}
Parent Component HTML Template
<form>
<ch-check-box-group [parentFormGroup]="formGroup" controlName="new_property" controlLabel="New Property" [hasDetail]="true" [detailRequired]="true">
<!-- Projected Input Field -->
<ng-template contentHandle>
<mat-form-field>
<mat-label>Purchase Date</mat-label>
<input type="text" matInput name="detail" formControlName="detail" id="">
</mat-form-field>
</ng-template>
</ch-check-box-group>
</form>
Child Component(ch-check-box-group)
export interface CheckboxGroupForm {
value: FormControl<boolean>,
detail?: FormControl<any>
}
#Component({
selector: 'ch-check-box-group',
templateUrl: './check-box-group.component.html',
styleUrls: ['./check-box-group.component.scss']
})
export class CheckBoxGroupComponent implements OnInit, OnDestroy {
private _ks: Subject<void> = new Subject<void>();
_formGroup: FormGroup
_showDetailInput: boolean = false;
#Input() controlLabel!: string;
#Input() controlName!: string;
#Input() parentFormGroup!: FormGroup;
#Input() hasDetail: boolean = false;
#Input() detailRequired: boolean = false;
#ContentChild(ContentHandleDirective) content!: ContentHandleDirective;
constructor() {
this._formGroup = new FormGroup<CheckboxGroupForm>({
value : new FormControl(false, {nonNullable: true}),
});
}
ngOnInit(): void {
if(this.hasDetail){
this._formGroup.addControl('detail', new FormControl(''));
}
this.parentFormGroup.addControl(this.controlName, this._formGroup);
this._formGroup.setParent(this.parentFormGroup);
this._formGroup.valueChanges
.pipe(takeUntil(this._ks))
.subscribe((change) => {
// Toggle visibility of detail input
if(this.hasDetail && this._showDetailInput != change.value){
this._showDetailInput = change.value;
}
// Toggle Validation(if necessary) of detail input
if(this.hasDetail && this.detailRequired && change.value){
setTimeout(() => {
this._formGroup.get('detail')?.addValidators(Validators.required);
});
} else {
this._formGroup.get('detail')?.clearValidators();
if(this._showDetailInput != change.value){
this._formGroup.get('detail')?.updateValueAndValidity();
}
}
console.log(this._formGroup.controls)
});
}
ngOnDestroy(){
this._ks.next();
this._ks.complete();
}
}
Child Component Template
<div class="row">
<form class="col-12" [formGroup]="_formGroup">
<mat-checkbox formControlName="value" i18n>{{ controlLabel }}</mat-checkbox>
<button mat-icon-button color="primary">
<mat-icon>help</mat-icon>
</button>
<div class="conditional-input-box-container" *ngIf="_showDetailInput">
<ng-container [ngTemplateOutlet]="content.templateRef"></ng-container>
</div>
</form>
</div>
To solve this, pass the child components _formGroup to the [ngTemplateOutletContext] like so:
<div class="row">
<form class="col-12" [formGroup]="_formGroup">
<mat-checkbox formControlName="value" i18n>{{ controlLabel }}</mat-checkbox>
<button mat-icon-button color="primary">
<mat-icon>help</mat-icon>
</button>
<div class="conditional-input-box-container" *ngIf="_showDetailInput">
<ng-container [ngTemplateOutlet]="content.templateRef" [ngTemplateOutletContext]="{ $implicit: _formGroup}"></ng-container>
</div>
</form>
</div>
Then use that new variable in the parent component like this:
<form>
<ch-check-box-group [parentFormGroup]="formGroup" controlName="new_property" controlLabel="New Property" [hasDetail]="true" [detailRequired]="true">
<!-- Projected Input Field -->
<ng-template contentHandle let-_formGroup>
<div [formGroup]="_formGroup">
<mat-form-field>
<mat-label>Purchase Date</mat-label>
<input type="text" matInput name="detail" formControlName="detail" id="">
</mat-form-field>
</div>
</ng-template>
</ch-check-box-group>
</form>

Why Parent does not listen to child on Angular?

I cannot make this eventemitter work. Can you please help? I am a beginner and it should be quite simple code for you.
I have a parent component, reading two different emitters from two different children:
<app-van [vans]="vans"></app-van>
<app-modal *ngIf="modalOpen" (closed)="onClick()" (openModal)="onClickTwo($event)"></app-modal>
import { Component, OnInit } from '#angular/core';
import { Van } from '../../interface';
#Component({
selector: 'app-fleet-home',
templateUrl: './fleet-home.component.html',
styleUrls: ['./fleet-home.component.css']
})
export class FleetHomeComponent implements OnInit {
modalOpen = true;
vans: Van [] = [
{ name: 'Ubeddu', description: 'Mercedes Sprinter', plate: 'NH55GKA' },
{ name: 'Abbestia', description: 'Ford Transit', plate: 'DK66HHR' },
{ name: 'Eumulu', description: 'Citroen Berlingo', plate: 'DR55MKL' }
];
constructor( ) { }
ngOnInit() {
}
onClick() {
this.modalOpen = !this.modalOpen;
console.log('modalOpen changed');
}
onClickTwo(event) {
this.modalOpen = event;
console.log('modalOpen changed');
}
}
the parent listened to this child:
<div (click)="onCloseClick()" class="ui dimmer visible active">
<div (click)="$event.stopPropagation()" class="ui modal visible active">
<div class="asuca">
<form class="ui form" >
<h4 class="ui dividing huge header">Van</h4>
<div class="required field">
<label class="ui header">Van Name</label>
<input type="text" placeholder="Van NickName">
</div>
<div class="field">
<label class="ui header">Description</label>
<input type="text"placeholder="Description">
</div>
<div class="field">
<label class="ui header">Plate</label>
<input type="text"placeholder="License Plate">
</div>
<button (click)="onCloseClick()" class="ui button" type="submit">Submit</button>
</form>
</div>
</div>
</div>
import { Component, OnInit, ElementRef, Output, EventEmitter } from '#angular/core';
#Component({
selector: 'app-modal',
templateUrl: './modal.component.html',
styleUrls: ['./modal.component.css']
})
export class ModalComponent implements OnInit {
#Output() closed = new EventEmitter();
constructor(private el: ElementRef) { }
ngOnInit() {
document.body.appendChild(this.el.nativeElement);
}
// tslint:disable-next-line: use-lifecycle-interface
ngOnDestroy() {
this.el.nativeElement.remove();
}
onCloseClick() {
this.closed.emit();
}
}
and doesnt listen to the second child:
<div class="ui fluid four black cards">
<div *ngFor="let van of vans" class="card">
<div class="content">
<div class="header">
{{ van.name }}
</div>
<div class="meta">
{{ van.description }}
</div>
<div class="description">
{{ van.plate }}
</div>
</div>
<div class="extra content">
<div class="ui two buttons">
<div (click)="onEditClick(true)" class="ui basic black button">Edit</div>
<div class="ui basic red button">Delete</div>
</div>
</div>
</div>
</div>
import { Component, OnInit, Input, Output, EventEmitter } from '#angular/core';
#Component({
selector: 'app-van',
templateUrl: './van.component.html',
styleUrls: ['./van.component.css']
})
export class VanComponent implements OnInit {
#Input() vans = [];
#Output() openModal = new EventEmitter<boolean>();
constructor() { }
ngOnInit() {
}
onEditClick(event: boolean) {
this.openModal.emit(event);
}
}
the whole thing is to hide the modal clicking around the screen and show it again clicking a button.
On the console.log, the object emitter by the first child has got an observer, where the object emitter by the second child as none; no idea what means though.
thanks in advance for the help. I can provide the whole folder if needed. I am just trying to learn :)
Seems like you have missed binding the output event in parent template. Please correct like below:
<app-van [vans]="vans" (openModal)="onEditClick($event)"></app-van>

Why [(ngModel)] breaks all template in angular 4?

This is my first component in angular. Here it is:
import { Component } from '#angular/core';
export class Hero{
id:number;
name:string;
}
const HEROES: Hero[]=[
{id:11,name:'Mr. Nice'},
{id:12,name:'Arco'},
{id:13,name:'Gillette'},
{id:14,name:'Celeritas'},
{id:15,name:'Magneta'},
{id:16,name:'RubberMan'},
{id:17,name:'Dynama'},
{id:18,name:'Dr. Iq'},
{id:19,name:'Magma'},
{id:20,name:'TOrnado'}
];
#Component({
selector: 'my-app',
template: `<h1>{{title}}</h1>
<h2>My heroes</h2>
<ul class="heroes">
<li *ngFor="let hero of heroes" (click)="onSelect(hero)">
<span class="badge">{{hero.id}}</span> {{hero.name}}
</li>
</ul>
<h2>Details of {{selectedHero.name}}</h2>
<div><label>Id: </label>{{selectedHero.id}}</div>
<div>
<label>Name: </label>
<input [(ngModel)]="selectedHero.name"/>
</div>
`,
styles: [`
`]
})
export class AppComponent {
title = 'Tour of Heroes';
heroes = HEROES;
selectedHero: Hero;
onSelect(hero: Hero): void {
this.selectedHero = hero;
}
}
I deleted styles element because its big.
My template doesn't bind heroes array, When I use this in template element:
<input [(ngModel)]="selectedHero.name"/>
But when I delete above input, all heroes from array are correctly displayed in a <ul> list.
Why is it?
You do not set selectedHero property, but still you try to display and modify it. Move hero details to section and add ngIf to it to display it only when it's set:
<div *ngIf="selectedHero">
<h2>Details of {{selectedHero.name}}</h2>
<div><label>Id: </label>{{selectedHero.id}}</div>
<div>
<label>Name: </label>
<input [(ngModel)]="selectedHero.name"/>
</div>
</div>
It's because Angular can't read selectedHero.name while there is no selected hero. You can (and need to) show this input only when you have selectedHero using *ngIf directive, like this:
<input *ngIf="selectedHero" [(ngModel)]="selectedHero.name"/>
And not only for input - for every element where you're using selectedHero:
<h2 *ngIf="selectedHero">Details of {{selectedHero.name}}</h2>
<div *ngIf="selectedHero"><label>Id: </label>{{selectedHero.id}}</div>
<div *ngIf="selectedHero">
<label>Name: </label>
<input [(ngModel)]="selectedHero.name"/>
</div>
Or better add a wrapper with one *ngIf directive:
<div *ngIf="selectedHero">
<h2>Details of {{selectedHero.name}}</h2>
<div><label>Id: </label>{{selectedHero.id}}</div>
<div>
<label>Name: </label>
<input [(ngModel)]="selectedHero.name"/>
</div>
</div>
I remember, it was written somewhere in "Tour of Heroes" for Angular, it's not just my thoughts ;)

angular4 viewchild not working on dom element

i want to know how to fetch the dom element from a components template :
Component
export class JokeListComponent implements OnInit, AfterViewInit {
jokes: Joke[];
constructor() { }
#ViewChild('.myclass') el: ElementRef;
ngOnInit() {
this.jokes = [
new Joke('joke1', 'content1'),
new Joke('Joke2', 'content2'),
new Joke(),
];
}
ngAfterViewInit(): void {
console.log(this.el);
}
}
View
<div class="card">
<div class="card-block">
<h4 class="card-title"> New Joke Form </h4>
<div class="myclass">
</div>
<div class="form-group">
<label for="jokeHeader">Joke Header</label>
<input type="text" id="jokeHeader" class="form-control" placeholder="Joke Head" #jokeHead>
</div>
<div class="form-group">
<label for="jokeContent">Joke Header</label>
<input type="text" id="jokeContent" class="form-control" placeholder="Joke Content" #jokeContent>
</div>
<button class="btn btn-primary" (click)="addJoke(jokeHead.value, jokeContent.value)"> Validate </button>
</div>
</div>
<hr>
<joke *ngFor="let joke of jokes" [joke]="joke" (deleteEvt)="deleteJoke($event)"></joke>
the problem is that this.el is always undefined, i dont know why.
PS: i'm using the last version of angular 4
You cannot use the class name for the #ViewChild, you will need a local variable:
#Component({
template: `
<div><span #myVar>xxx</span><div>`
})
class MyComponent {
#ViewChild('myVar') myVar:ElementRef;
ngAfterViewInit() {
console.log(this.myVar.nativeElement);
}
}

Angular2 add HTML to dynamic elements

I have this code:
import { Component, ElementRef, Renderer2 } from '#angular/core';
#Component({
selector: 'my-app',
template: '<button (click)="runR()">Run</button>
<div class="testme">
<div class="somediv">
<div class="dynamically_created_div unique_identifier"></div>
<div class="dynamically_created_div unique_identifier"></div>
<div class="dynamically_created_div unique_identifier"></div>
</div>
</div>',
})
export class AppComponent{
hostEl: any;
constructor(
private el:ElementRef,
private renderer:Renderer2,
) {
this.hostEl = el.nativeElement;
}
runR(){
let change_this;
change_this= this.renderer.createElement('span');
this.renderer.addClass(change_this, 'change_this');
this.renderer.appendChild(this.hostEl, change_this);
}
}
Is there any way in Angular2 to add HTML to the .dynamically_created_div?
Because the above only adds to the end of the HTML of the component.
I also tried with:
import { Component, ElementRef, ViewChild, Renderer, AfterViewInit } from '#angular/core';
#Component({
selector: 'my-app',
template: `<button (click)="runR()">Run</button>
<div class="testme">
<div class="somediv">
<div class="dynamically_created_div">
</div>
</div>
</div>
`,
})
export class AppComponent {
constructor(private renderer:Renderer) {}
runR() {
#ViewChild('dynamically_created_div') d1:ElementRef;
this.renderer.invokeElementMethod(this.d1.nativeElement, 'insertAdjacentHTML', ['beforeend', '<div class="new_div">new_div</div>'] );
}
}
But it's not working because the #ViewChild directive must be outside the function and I can't have control over it anymore
I also tried like this:
<div class="dynamically_created_div" [innerHtml]="newHTML"></div>
this.newHTML = '<div class="new_div">new_div</div>';
Thing I cannot do because my content is dynamic and uses unique IDs and I cannot use [innerHtml] dynamically ( it only works for what I put in themplate for the first time, then anything else that changes can't use innerHtml anymore.
I checked Angular2: Insert a dynamic component as child of a container in the DOM but there is the same problem, the placeholders aren't dynamic
UPDATE:
My code is a little bit more complex:
TS:
import { AfterContentInit, Component, OnInit, OnDestroy, ViewEncapsulation } from '#angular/core';
import { NgForm, FormsModule, ReactiveFormsModule, FormGroup, FormControl, FormBuilder, Validators } from '#angular/forms';
import { SFService } from '../services/sf.service';
import { Injectable, Pipe, PipeTransform } from '#angular/core';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
providers: [ SFService ],
})
export class AppComponent implements OnInit {
constructor(
private sfservice: SFService,
) {}
ngOnInit(){
this.sfservice.getMembers().subscribe(members => {
this.members = members.members;
});
}
members: Member[];
member_selector: Member[];
member_each: Member;
member_selector_each: Member[];
cases: Case;
runR(){
this.members.forEach(member_each => {
this.member_selector.forEach(member_selector_each => {
if(member_each.Id === member_selector_each.Id){
console.log(member_selector_each.Id);
this.sfservice.getCaseHistory(member_selector_each.Id, "2017-04-25T00:00:00", "2017-04-28T23:59:59").subscribe(cases => {
this.member_each['cases'] = cases;
console.log(this.member_each);
});
}
})
})
}
}
HTML:
<form #myForm="ngForm" novalidate>
<select name="member_selector_name" [(ngModel)]="member_selector" multiple ng-model="selectedValues" style="height:200px;">
<option *ngFor="let member of members" [ngValue]="member">{{member.Name}}</option>
</select>
<button (click)="runR()">Run</button>
</form>
<div id="results">
<div *ngFor="let mem of members" class="member-card-{{mem.Id}}">
<div class="card-container">
<div *ngFor="let case of mem.Cases" class="case-card" id="{{case.Id}}">{{case.Number}}
</div>
</div>
</div>
</div>
I was trying to use only ngFor but now I get
Cannot set property 'cases' of undefined
What's the problem with this approach?
export class AppComponent{
#ViewChild('d1') d1:ElementRef;
#ViewChild('d2') d2:ElementRef;
#ViewChild('d3') d3:ElementRef;
constructor(private renderer:Renderer2) { }
runR(){
let change_this;
change_this= this.renderer.createElement('span');
this.renderer.addClass(change_this, 'change_this');
this.renderer.appendChild(this.d1, change_this);
}
}
Template:
<div class="dynamically_created_div unique_identifier" #d1></div>
<div class="dynamically_created_div unique_identifier" #d2></div>
<div class="dynamically_created_div unique_identifier" #d3></div>
you can use ngfor and create you elements inside it and using index you can create different ids and names.
I do something like this i dont know if you want to do the same but here's my code to create some input's dynamically and add or access their values
<div *ngFor="let comp of templateVals | async;let i=index">
<md-input-container class="example-90" *ngIf="comp.type=='code'">
<textarea rows="4" mdInput name="desc{{i}}" [(ngModel)]="comp.data" placeholder="Description"></textarea>
</md-input-container>
<md-input-container class="example-90" *ngIf="comp.type=='text'">
<textarea rows="4" mdInput name="text{{i}}" [(ngModel)]="comp.data" placeholder="Text"></textarea>
</md-input-container>
<md-input-container class="example-90" *ngIf="comp.type=='title'">
<input mdInput name="title{{i}}" [(ngModel)]="comp.data" placeholder="Title">
</md-input-container>
<span class="example-90" *ngIf="comp.type=='upload'">
<input-file *ngIf="!comp.data" [acceptId]="comp.id" (onFileSelect)="addedFileInfo($event)"></input-file>
<span *ngIf="comp.data">{{comp.data}}</span>
</span>
<span class="example-10">
<button md-mini-fab (click)="removeThis(comp)"><md-icon>remove circle</md-icon></button>
</span>
</div>

Categories

Resources