Angular-How to use spread operator in angular html template - javascript

I have a simple angular app with a module and a component. For the sake of simplicity let us assume that the component ts and the template file is like the following snippet
import {
Component,
Input,
OnInit
} from '#angular/core';
import {
ChildComponent
} from './child/child.component';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
title = 'test-app';
val;
clickEvt() {
alert("clicked")
}
isTruthy() {
if (( < HTMLInputElement > document.getElementById("inp1")).value == "admin" && ( < HTMLInputElement > document.getElementById("inp2")).value == "admin") {
return true;
}
return false;
}
ngOnInit() {
this.val = {};
this.val.id = "id1";
this.val.class = "abc";
}
}
<label>User</label>
<input {{...val}} (input)="isTruthy()" />
<br>
<label>Password</label>
<input id="inp2" (input)="isTruthy()" />
<button (click)="clickEvt()">Login</button>
<div *ngIf="isTruthy(); then truthy else falsey"></div>
<ng-template #truthy>
<h1>Success</h1>
<child-component [value]="isTruthy()"></child-component>
</ng-template>
<ng-template #falsey>
<h1>Failure</h1>
<child-component [value]="isTruthy()"></child-component>
</ng-template>
In the HTML template, you can notice me trying to use {{ ...val }}. This is my attempt to use spread operator in the template but unfortunately I get the exception
ERROR DOMException: Failed to execute 'setAttribute' on 'Element': '{{...val.id}}' is not a valid attribute name.
I just want to know, is there a way to use spread operator or is there an equivalent way in angular to give multiple attributes in one go which is obtained from a variable?

No, you cannot use the spread in a template (even less like you're trying).
To achieve that, you will need a reference to your template.
<input #myInput (input)="isTruthy()" />
#ViewChild('myInput', { static: true }) myInput: ElementRef<HTMLInputElement>;
ngOnInit() {
Object.assign(this.myInput.nativeElement, this.val);
}

One way to do this could be create a directive like
import { Directive, ElementRef, HostListener, Input } from '#angular/core';
#Directive({
selector: '[setAttr]'
})
export class AttrDirective {
constructor(private el: ElementRef) { }
#Input() attr: any;
ngOnInit(){
if(this.attr){
console.log(this.el.nativeElement)
Object.keys(this.attr).forEach(k=>{
this.el.nativeElement.setAttribute(k,this.attr[k])
console.log(this.el.nativeElement)
})
}
}
}
then apply on input like
<input type="text" setAttr [attr]="val">
demo

Related

ngOnChange is not called when the parent updates the child

i want to know how ngOnChanges callback works. so i have added it to observe changes in a prpoperty annotated with Input decorator as follows:
#Input() postsToAddToList: Post[] = [];
now, when I compile the code i add some values that causes change in the property annotated with #Input, but that does not cause the the ngOnChanges callback to be called and executed. please see logs shown in the screen-shot posted below.
i want to see the logs in the ngOnChanges displayed in the browser.
please let me know what prevents the ngOnChanges to be invoked and called correctly
app.component.ts:
import { Component } from '#angular/core';
import { Post } from './post-create/post-create.component';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'binding2';
postsArray: Post[] = [];
onReceiveSubmittedEvtEmitter(post: Post) {
this.postsArray.push(post);
console.log("onReceiveSubmittedEvtEmitter->: post.title: " + post.title);
console.log("onReceiveSubmittedEvtEmitter->: post.content:" + post.content);
}
}
app.component.html:
<app-post-create (onPostSubmittedEvtEmitter)="onReceiveSubmittedEvtEmitter($event)"></app-post-create>
<app-post-list [postsToAddToList]="postsArray"></app-post-list>
post-list.component.ts:
import { Component, Input,OnInit, OnChanges, SimpleChanges,Output, EventEmitter } from '#angular/core';
import { Post } from '../post-create/post-create.component';
#Component({
selector: 'app-post-list',
templateUrl: './post-list.component.html',
styleUrls: ['./post-list.component.css']
})
export class PostListComponent implements OnInit {
constructor() {}
#Input() postsToAddToList: Post[] = [];
ngOnInit(): void {}
ngOnChanges(changes: SimpleChanges) {
for (let changedProperty in changes) {
if (changes.hasOwnProperty(changedProperty)) {
console.log("ngOnChanges->: changedProperty: " + changedProperty);
console.log("ngOnChanges->: changedProperty:" + changedProperty);
switch(changedProperty) {
case 'postsToAddToList':
console.log("ngOnChanges->: changes[changedProperty].previousValue: " + changes[changedProperty].previousValue);
console.log("ngOnChanges->: changes[changedProperty].currentValue):" + changes[changedProperty].currentValue);
break;
}
}
}
}
}
post-list.component.html:
<!-- post-list.component.html -->
<h3>List</h3>
<ng-container *ngIf="postsToAddToList.length; else elseTemplate">
<ul class="list-group">
<li class="list-group-item" *ngFor="let post of postsToAddToList; let i = index">
<h5>{{i+1}}) {{post.title}}</h5>
<p>
{{post.content}}
</p>
</li>
</ul>
</ng-container>
<ng-template #elseTemplate>
<div class="alert alert-danger" role="alert">
No Post Found!
</div>
</ng-template>
error screen-shot:
As u are using push() in your parent component, ngOnChanges will not be invoked in the child component. Instead of using push() you can reassign value to postsToAddToList every time there is a change in it.

Angular app is compiling successfully but giving errors property does not exist in 'ng build --prod'

Angular app is compiling successfully but giving the following errors in 'ng build --prod'
ERROR in src\app\header\header.component.html(31,124): : Property 'searchText' does not exist on type 'HeaderComponent'.
src\app\dashboard\dashboard.component.html(3,72): : Property 'newsService' is private and only accessible within class 'DashboardComponent'.
src\app\dashboard\dashboard.component.html(3,72): : Property 'p' does not exist on type 'DashboardComponent'.
src\app\dashboard\dashboard.component.html(29,46): : Property 'p' does not exist on type 'DashboardComponent'.
I have used these properties in my html file as below:
header.component.htmlfile
<input type="text" class="form-control mr-2 align-self-center" required placeholder="Search" name="searchText" [ngModel]="searchText" value="">
dashboard.component.htmlfile
<pagination-controls class="text-center" (pageChange)="p = $event"></pagination-controls>
my header.component.html file
import { Component, OnInit, Output, EventEmitter, ViewEncapsulation } from '#angular/core';
import { NgForm } from '#angular/forms';
#Component({
selector: 'app-header',
templateUrl: './header.component.html',
styleUrls: ['./header.component.css']
})
export class HeaderComponent implements OnInit {
filterText : string;
#Output() search = new EventEmitter();
#Output() filterButton = new EventEmitter();
constructor() { }
ngOnInit() {
}
onSubmit(form : NgForm)
{
console.log(form);
this.search.emit(form);
}
filterClicked($event)
{
this.filterText = $event.target.text;
this.filterButton.emit(this.filterText);
}
}
my dashboard.component.html file
import { Component, OnInit, ViewEncapsulation } from '#angular/core';
import { NewsService } from '../shared/news.service';
import { NewsModel } from '../shared/news.model';
import { Form } from '#angular/forms';
import { Pipe, PipeTransform } from '#angular/core';
import { element } from 'protractor';
#Component({
selector: 'app-dashboard',
templateUrl: './dashboard.component.html',
styleUrls: ['./dashboard.component.css']
})
export class DashboardComponent implements OnInit {
articles : any;
temp : NewsModel = new NewsModel;
constructor(private newsService : NewsService) { }
ngOnInit() {
this.FetchHeadlines();
}
FetchHeadlines()
{
this.newsService.GetAllGaurdian()
.subscribe((result) =>
{
this.articles = result;
this.articles.response.results.forEach(element => {
this.newsService.newsArticles.push(this.newsService.CusotomMapper(element));
});
})
}
}
can't able to figure out where is the error exactly!
I think the error descriptions are as accurate as it can be. each of them tells you that something wrong with your component, lets examine each of them
ERROR:
ERROR in src\app\header\header.component.html(31,124): : Property 'searchText' does not exist on type 'HeaderComponent'.
you have searchText in HeaderComponent HTML, but not in the Component itself
SOLUTION: add searchText variable to the Component
#Component({
selector: 'app-header',
templateUrl: './header.component.html',
styleUrls: ['./header.component.css']
})
export class HeaderComponent implements OnInit {
searchText:string
...
}
ERROR :
src\app\dashboard\dashboard.component.html(3,72): : Property 'newsService' is private and only accessible within class 'DashboardComponent'.
all the fields you are using inside the template, must be the public field inside component itself, otherwise it will not compile
SOLUTION: change private modifier to public at newService
#Component({
selector: 'app-dashboard',
templateUrl: './dashboard.component.html',
styleUrls: ['./dashboard.component.css']
})
export class DashboardComponent implements OnInit {
constructor(public newsService : NewsService) { }
...
}
ERRORS :
src\app\dashboard\dashboard.component.html(3,72): : Property 'p' does not exist on type 'DashboardComponent'.
src\app\dashboard\dashboard.component.html(29,46): : Property 'p' does not exist on type 'DashboardComponent'.
same as HeaderComponent. you are using p field but it's not defined in DashboardComponent
SOLUTION : add p field to the dashboard component
#Component({
selector: 'app-dashboard',
templateUrl: './dashboard.component.html',
styleUrls: ['./dashboard.component.css']
})
export class DashboardComponent implements OnInit {
p:any
...
}
You are trying to access from the template, variables that aren't defined in the corresponding components.
In header.component.html you are setting [ngModel]="searchText" and variable searchText isn't defined on header.component.ts. Could it be filterText variable instead?
In dashboard.component.html you are setting p = $event and variable p isn't defined on dashboard.component.ts. You also have an error about newsService being private. If you are gonna use it in the template it must be declared public when you inyect it on the constructor. I hope this helps. If you need more help is better if you provide a Stackblitz with minimum code.

Scope class name and script per angular component

I have a component which is repeated various times on a page. I am implementing the AutoNumeric library and need to pass a selector to the function. However, the selector needs to be unique per instance of the component, so the first instance would be cssClass-1 and the second instance cssClass-2, and the js inside the component would know which selector to look for.
import { Component, ViewChild, OnInit } from '#angular/core';
import { FieldType } from '#ngx-formly/material';
import AutoNumeric from 'AutoNumeric';
#Component({
selector: 'app-form-currency-type',
template: `
<div class="cssClass-1">
<input
matInput
[formControl]="formControl"
[formlyAttributes]="field"
autocomplete="false"
type="text"
/>
</div>
`,
})
export class CurrencyTypeComponent extends FieldType implements OnInit {
ngOnInit() {
const anElement = new AutoNumeric('.cssClass-1 > input', {
allowDecimalPadding: false,
caretPositionOnFocus: "start",
currencySymbol: "£",
decimalPlaces: 0
});
}
}
I can't find a way to scope the script to just this instance of the component, is there a way to do this?
You need to make the class name a JS variable so it can be changed for each instance.
If you are happy with random strings maybe use a uuid library.
import { Component, ViewChild, OnInit } from '#angular/core';
import { FieldType } from '#ngx-formly/material';
import AutoNumeric from 'AutoNumeric';
const uuidv1 = require('uuid/v1');
#Component({
selector: 'app-form-currency-type',
template: `
<div [ngClass]="cssClass">
<input
matInput
[formControl]="formControl"
[formlyAttributes]="field"
autocomplete="false"
type="text"
/>
</div>
`,
})
export class CurrencyTypeComponent extends FieldType implements OnInit {
// initialize empty by default
public cssClass = '';
ngOnInit() {
// generate new random string
const cssID = uuidv1();
// prefix required as class names can't start with numbers
const cssPrefix = "cur--";
this.cssClass = cssPrefix + cssID.toString();
// use string interpolation
const anElement = new AutoNumeric(`${this.cssClass} > input`, {
allowDecimalPadding: false,
caretPositionOnFocus: "start",
currencySymbol: "£",
decimalPlaces: 0
});
}
}

Angular - How do I detect form events from inside a directive when they are initiated in component.ts file?

If I have a component with a form input and I want to detect the two statements in the OnInit block as events inside my directive, what is the correct way to do this? I have had luck with 'input' and 'ngModelChange', but none of the events I try to listen to catch the patchValue() method for model driven forms or the straight assignment for template driven forms (even though it reflects in the DOM).
Here is my Component:
import { Component, OnInit } from '#angular/core';
import { FormGroup, FormControl } from '#angular/forms'
#Component({
selector: 'my-app',
template:
`
<h5>Model form input</h5>
<form [formGroup]="inputForm">
<input patchable-input formControlName="input" />
</form>
<h5>Template form input</h5>
<input patchable-input [(ngModel)]="input" />
`
})
export class AppComponent implements OnInit {
inputForm = new FormGroup({
input: new FormControl('')
})
input = '';
ngOnInit() {
this.inputForm.patchValue({
input: 'testing patch'
})
this.input = 'testing override'
}
}
Here is my Directive:
import { Directive, HostListener } from '#angular/core';
#Directive({
selector: '[patchable-input]'
})
export class PatchableInputDirective {
#HostListener('ngModelChange', ['$event']) ngOnChanges($event) {
console.log($event);
}
}
and a minimal reproduction in StackBlitz (watch the console)
You have to implement AfterViewInit instead of OnInit. The reason for that is at this point in the life cycle your directive has been initialized and has subscribed to the ngModelChange event via the #HostListener decorator.
See also the Angular Life Cycle Hooks documentation.
stackblitz
import { Component, AfterViewInit } from '#angular/core';
import { FormGroup, FormControl } from '#angular/forms'
#Component({
selector: 'my-app',
template:
`
<h5>Model form input</h5>
<form [formGroup]="inputForm">
<input patchable-input formControlName="input" />
</form>
<h5>Template form input</h5>
<input patchable-input [(ngModel)]="input" />
`
})
export class AppComponent implements AfterViewInit {
inputForm = new FormGroup({
input: new FormControl('')
})
input = '';
ngAfterViewInit() {
this.inputForm.patchValue({
input: 'testing patch'
})
}
}

How can I access an already transcluded ContentChild?

I have a angular component app-b that is used within a component app-a that is used in the app-component. The app-component has some content in app-a, app-a transcludes this with ng-content into app-b, app-b shows it with another ng-content - but how can I access this content within the component (and not it's template)?
I would think that ContentChild is the correct approach but appears to be wrong.
Example:
https://stackblitz.com/edit/angular-ddldwi
EDIT: Updated example
You cannot query by tag name with #ContentChild decorator. You can query either by template variable, component or directive selector.
app-a.component.html
<app-b>
<ng-content></ng-content>
<p #myContent>This is a content child for app-b.</p>
</app-b>
app-b.component.ts
import { Component, AfterContentInit, ContentChild } from '#angular/core';
#Component({
selector: 'app-b',
templateUrl: './b.component.html',
styleUrls: ['./b.component.css']
})
export class BComponent implements AfterContentInit {
#ContentChild('myContent') contentchild;
ngAfterContentInit() {
console.log(this.contentchild);
}
}
Live demo
I recommend sharing the data between components. For example, move your data (E.G. dummyName) into a service. Then add the service to each component (where you need the shared data).
Service:
import { Injectable } from '#angular/core';
#Injectable()
export class DataShareService {
public dummyName: String = 'one';
constructor() { }
}
Add the new service to app.module.ts:
providers: [DataShareService],
Child Component:
import { DataShareService } from './data-share.service';
import { Component } from '#angular/core';
#Component({
selector: 'app-child',
templateUrl: './child.component.html'
})
export class ChildComponent {
constructor(public ds: DataShareService) { }
toggle() {
this.ds.dummyName = this.ds.dummyName === 'one' ? 'two' : 'one';
}
}
Child Component template (html):
<p> {{ds.dummyName}}</p>
<button (click)="toggle()">Click Me to Toggle Value</button>
Parent Component:
import { Component, OnInit } from '#angular/core';
import { DataShareService } from './data-share.service';
#Component({
selector: 'app-root',
templateUrl: './app.component.html'
})
export class AppComponent {
constructor(public ds: DataShareService) {}
displayNumber() {
return this.ds.dummyName === 'one' ? 1 : 2;
}
}
Parent Component template (html):
<p> this is the parent component:</p>
<div> value as string: {{ ds.dummyName }} </div>
<div> value as number: <span [textContent]="displayNumber()"></span></div>
<hr>
<p> this is the child component:</p>
<app-child></app-child>
Note! The child component toggle() function demonstrates how you can change the data. The parent component displayNumber() function demonstrates how to use the data, independent of it's display (I.E. as just pure data).
This appears to be impossible due to a bug in Angular:
https://github.com/angular/angular/issues/20810
Further reference:
https://www.reddit.com/r/Angular2/comments/8fb3ku/need_help_how_can_i_access_an_already_transcluded/

Categories

Resources