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

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 ;)

Related

I want to create a mat-checkbox change event, which can show or hide the cards

Here I am sharing my Angular Code:
1. app-component.ts
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-all-trades',
templateUrl: './all-trades.component.html',
styleUrls: ['./all-trades.component.css']
})
export class AllTradesComponent implements OnInit {
// Arrays of Object
crops = [
{
name:'Rice',
checked: false
},
{
name:'Wheat',
checked : false
},
{
name:'Barley',
checked : false
},
{
name:'Soyabean',
checked : false
},
];
constructor() { }
ngOnInit(): void {
}
// Here I tried the logic but its not working
onChange(event, index, item) {
item.checked = ! item.checked
console.log(index, event, item);
}
}
2. app-component.html
<app-header></app-header>
<div
fxLayout="row"
fxLayout.lt-md="column"
fxLayoutAlign="space-between start"
fxLayoutAlign.lt-md="start stretch"
>
<div class="container-outer" fxFlex="20">
<div class="filters">
<section class="example-section">
<span class="example-list-section">
<h1>Select Crop</h1>
</span>
<span class="example-list-section">
<ul>
<li *ngFor="let crop of crops; let i = index">
<mat-checkbox
[checked]="item.checked"
(change)="onChange($event, i, item)"
>
{{ crop.name }}
</mat-checkbox>
</li>
</ul>
</span>
</section>
<section class="example-section">
<span class="example-list-section">
<h1>Select District</h1>
</span>
<span class="example-list-section">
<ul>
<li *ngFor="let district of districts">
<mat-checkbox>
{{ district.name }}
</mat-checkbox>
</li>
</ul>
</span>
</section>
</div>
</div>
<div class="content container-outer" fxFlex="80">
<mat-card
class="crop-card"
style="min-width: 17%"
*ngFor="let crop of crops"
>
<mat-card-header>
<img
mat-card-avatar
class="example-header-image"
src="/assets/icons/crops/{{ crop.name }}.PNG"
alt="crop-image"
/>
<mat-card-title>{{ crop.name }}</mat-card-title>
<mat-card-subtitle>100 Kgs</mat-card-subtitle>
</mat-card-header>
<mat-card-content>
<p>PRICE</p>
</mat-card-content>
</mat-card>
</div>
</div>
<app-footer></app-footer>
Here I shared my output window, as you guys can see on left hand side I have created mat-checkbox, all I need to do is that when I check the "RICE" box its should only show the RICE CARD and when I check Wheat then only Wheat Card.
the first thing to know is that you
Can't have multiple template bindings on one element
as you saw when trying to do so and here is the official document for that if you are interested
so here are two possible solutions for you
hide unchecked cards by [hidden] directive as below:
<mat-card [hidden]="!crop.checked">
wrap your card in another element like a div and do as below:
<div *ngFor="let crop of crops">
<mat-card
class="crop-card"
style="min-width: 17%"
*ngIf="crop.checked">
.
.
</mat-card>
</div>

Angular 7, elementRef not correct

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

classList.toggle() not working IE11 Angular 7 (Invalid Calling Object)

I've just been testing my app on IE11 and I cant figure out why this isn't working,
I have this code it has three elements .hamburger-small, .hamburger-big and .menu
<div [class.shown]="!chatbarFullscreen">
<div [class.disabled]="router.url.includes('home')">
<img (click)="closeChatbar(true, router.url.includes('home') ? true : false)" *ngIf="chatbarFullscreen" src="../assets/images/whole-app/arrow-right.svg" alt="Arrow Right">
<img (click)="closeChatbar(false, router.url.includes('home') ? true : false)" *ngIf="!chatbarFullscreen" src="../assets/images/whole-app/arrow-left.svg" alt="Arrow Left">
</div>
<img (click)="goHome()" src="../assets/images/chatbar/header-logo.svg" alt="header logo">
<div id="small" (click)="hamburgerClick()" class="hamburger hamburger--slider hamburger-small">
<div class="hamburger-box">
<div class="hamburger-inner"></div>
</div>
</div>
</div>
<div id="big" (click)="hamburgerClick()" class="hamburger hamburger--slider hamburger-big">
<div class="hamburger-box">
<div class="hamburger-inner"></div>
</div>
</div>
<div class="menu">
<p (click)="closeChatbar(false); hamburgerClick();" [routerLink]="['/app/main/home']">Home</p>
</div>
</div>
and when you click it, it calls this function
hamburgerClick() {
const small = <HTMLElement>document.querySelector('.hamburger-small');
const big = <HTMLElement>document.querySelector('.hamburger-big');
const menu = <HTMLElement>document.querySelector('.menu');
small.classList.toggle('is-active');
big.classList.toggle('is-active');
menu.classList.toggle('show');
}
now It works on every other browser, Chrome, Firefox, Safari and Edge but not in IE I've seen similar questions but it seems as if it should work? I'm also getting this error in the console when I click the button for the first time, but it does not happen any other time
any help would be great..
EDIT
I have tried using #ViewChild() but it still isn't working, however the Invalid Calling Object error is no longer happening
#ViewChild('hamburgerBig') hamburgerBig: ElementRef;
#ViewChild('hamburgerSmall') hamburgerSmall: ElementRef;
#ViewChild('menu') menu: ElementRef;
hamburgerClick() {
this.hamburgerBig.nativeElement.classList.toggle('is-active');
this.hamburgerSmall.nativeElement.classList.toggle('is-active');
this.menu.nativeElement.classList.toggle('show');
}
Thanks!!
try using Renderer2 to manipulate dom elements along with ElementRef and ViewChild as other previously mentioned.
first import ViewChild, ElementRef and Renderer2
import { Renderer2, ElementRef, ViewChild } from '#angular/core';
get the Element using ViewChild of type ElementRef after you've made template references in your DOM, like
<div #hamburgerBig></div>
<div #hamburgerSmall></div>
<div #menu></div>
#ViewChild('hamburgerBig') hamburgerBig: ElementRef;
#ViewChild('hamburgerSmall') hamburgerSmall: ElementRef;
#ViewChild('menu') menu: ElementRef;
and do your stuff with your hamburgerClick function
hamburgerClick() {
const hamBigIsActive = this.hamburgerBig.nativeElement.classList.contains('is-active');
const hamSmallIsActive = this.hamburgerSmall.nativeElement.classList.contains('is-active');
const menuShow = this.menu.nativeElement.classList.contains('show');
if(hamBigIsActive) {
this.renderer.removeClass(this.hamburgerBig.nativeElement, 'is-active');
} else {
this.renderer.addClass(this.hamburgerBig.nativeElement, 'is-active');
}
if(hamSmallIsActive) {
this.renderer.removeClass(this.hamburgerSmall.nativeElement, 'is-active');
} else {
this.renderer.addClass(this.hamburgerSmall.nativeElement, 'is-active');
}
if(hamSmallIsActive) {
this.renderer.removeClass(this.menu.nativeElement, 'show');
} else {
this.renderer.addClass(this.menu.nativeElement, 'show');
}
}
or you could just simply use [ngClass](not sure why you aren't using this instead)
hope this helps
also dont forget to add render to your contructor
contructor(private renderer: Renderer2){}
Edit: here's the [ngClass] implementation
<div id="small"
(click)="hamburgerClick()"
[ngClass] = "{'is-active' : hamClick}"
class="hamburger hamburger--
slider hamburger-small">
<div class="hamburger-box">
<div class="hamburger-inner"></div>
</div>
</div>
<div id="big"
(click)="hamburgerClick()"
[ngClass] = "{'is-active' : hamClick}"
class="hamburger hamburger--slider
hamburger-big">
<div class="hamburger-box">
<div class="hamburger-inner"></div>
</div>
</div>
<div
[ngClass] = "{'show' : hamClick}"
class="menu">
<p (click)="closeChatbar(false); hamburgerClick();" [routerLink]="
['/app/main/home']">Home</p>
</div>
and then just use a function to switch
hamClick: boolean
hamburgerClick(){
this.hamClick = !this.hamClick;
}
there you go
Try to make a test with code below may help you to solve your issue.
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
public show:boolean = false;
public buttonName:any = 'Show';
ngOnInit () { }
toggle() {
this.show = !this.show;
// CHANGE THE NAME OF THE BUTTON.
if(this.show)
this.buttonName = "Hide";
else
this.buttonName = "Show";
}
}
.is-active{color:green;
}
<button (click)="toggle()" id="bt">
Hide
</button>
<ng-container *ngIf="show">
<div style="margin: 0 auto;text-align: left;">
<div>
<label>Name:</label>
<div><input id="tbname" name="yourname" /></div>
</div>
<div>
<label>Email Address:</label>
<div><input name="email" id="email" /></div></div>
<div>
<label>Additional Information (optional):</label>
<div><textarea rows="5" cols="46"></textarea></div>
</div>
</div>
</ng-container>
Further, You can try to modify the code based on your requirement.

How to verify if my list with checkbox are all checked using Angular 5

I created a list with one checkbox for each item, and I need to verify if all of them are ckecked.
This is my HTML:
<ul class="ingredient-list" >
<li class="btn-list" *ngFor="let ingredient of recipe.ingredients">
<label class="checky round">
<input type="checkbox" checked>
<span></span>
<span>{{ ingredient }}</span>
</label>
</li>
</ul>
in your component ts file
import { FormArray, FormBuilder, FormGroup, Validators, FormControl } from '#angular/forms';
ingredientsForm;
constructor( private fb: FormBuilder) { }
ngOnInit() {
//do what you need to get your recipe details first
const ingredientFGs = recipe.ingredients.map(ingredient => this.fb.group(ingredient));
ingredientFGs.forEach(item => {
item.addControl('isChecked', new FormControl());
item.get('isChecked').setValue(false);
item.get('isChecked').setValidators(Validators.requiredTrue);
})
this.ingredientsForm = this.fb.group({
ingredients: this.fb.array(ingredientFGs);
});
}
and in your html
<form [formGroup]="ingredientsForm">
<ul class="ingredient-list" formArrayName="ingredients">
<li class="btn-list" *ngFor="let ingredient of ingredients.controls">
<label class="checky round">
<input type="checkbox" formControlName="isChecked">
<span></span>
<span>{{ ingredient.value() }}</span>
</label>
</li>
</ul>
</form>
then doing ingredientsForm.valid will return true if everything is checked
As I dind'nt wanted to import others modules just for solve this problem I came up with another idea.
I created a method at my component.ts to verify the checkbox:
import { Component, ViewChild, ElementRef } from '#angular/core';
#Component({
selector: 'app-recipes',
templateUrl: './recipes.component.html',
styleUrls: ['./recipes.component.css']
})
export class RecipesComponent {
#ViewChild('ingredientList') ingredientList: ElementRef;
#ViewChild('preparationList') preparationList: ElementRef;
isAllChecked(list: ElementRef): boolean{
// take the inputs by tag and convert to array
var inputs = [].slice.call(list.nativeElement.getElementsByTagName('input'));
return !inputs.some(function(input){
// verify if any item is not checked
return input.ckecked == false;
});
}
}
My HTML looks like this now
<ul #ingredientList >
<li #item class="btn-list" *ngFor="let ingredient of recipe.ingredients">
<label class="checky round">
<input type="checkbox" >
<span></span>
<span>{{ ingredient }}</span>
</label>
</li>
</ul>
Thanks everyone for the help

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);
}
}

Categories

Resources