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
});
}
}
Related
My Problem: I created a CardComponent acts like a card view with css and html and has a constructor.
I want to use it in a cards array (of its type). I use service to store the cards' data.
home comp. is using the service and loop over with ngFor, This is the code.. and below is the error I get...
Is there another way of using this so it will work?
card.component.ts:
import { Component } from '#angular/core';
#Component({
selector: 'app-card',
templateUrl: './card.component.html',
styleUrls: ['./card.component.css']
})
export class CardComponent{
imageSrc : string;
title : string;
constructor(imgSrc : string, title : string) {
this.imageSrc = imgSrc;
this.title = title;
}
ngOnInit(): void {
}
cards.service.ts:
import { CardComponent } from './card/card.component';
export class CardsService{
ctgryCards : CardComponent[] = [
new CardComponent("https://cdn3.iconfinder.com/data/icons/outline-amenities-icon-set/64/Beauty_Saloon-512.png", "Beauty"),
new CardComponent("https://www.pinclipart.com/picdir/middle/391-3917890_get-business-value-from-sustainable-data-electronics-icon.png", "Electronics")
];
getAllCtgryCards(){
return this.ctgryCards.slice();
}
}
home.component.ts:
import { Component, OnInit } from '#angular/core';
import { CardComponent } from '../card/card.component';
import { CardsService } from '../cards.service';
#Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.css']
})
export class HomeComponent implements OnInit {
categoryCards : CardComponent[] = [];
constructor(private cardsServ : CardsService) { }
ngOnInit(): void {
this.categoryCards = this.cardsServ.getAllCtgryCards();
}
}
home.component.html:
<app-card *ngFor = "let card of categoryCards"></app-card>
Error NG2003:
ERROR in src/app/card/card.component.ts:12:15 - error NG2003: No
suitable injection token for parameter 'imgSrc' of class
'CardComponent'. Found string
constructor(imgSrc : string, title : string) {
In card.component.ts:
Update the constructor to be
//import { Inject } from '#angular/core';
constructor(#Inject(String) private imgSrc : string, title : string)
OR
//import { Inject } from '#angular/core';
constructor(#Inject('imgSrc') private imgSrc : string, title : string)
According to https://angular-2-training-book.rangle.io/di/angular2/inject_and_injectable ; #Inject() is a manual mechanism for letting Angular know that a parameter must be injected. #Inject decorator is only needed for injecting primitives.
The primitive types are number, string, boolean, bigint, symbol, null, undefined.
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
I have the following problem in Angular: as I understand, *ngIf directive doesn't allow me to add event on nesting DOM's component.
This is the code:
Template
<div class="bf-search-field">
<input #productSearch
[(ngModel)]="term"
(ngModelChange)="search()"
(blur)="closeAutocomplete()"
(focus)="activeAutocomplete()"
autocomplete="off" type="search" id="search"
placeholder="Search black friday deals"
/>
<button class="bf-search-field__search-btn"></button>
</div>
<div class="bf-search-hints">
<!--use for not duplicate full term in autocomplete-->
<ng-container *ngFor="let product of products | async">
<div *ngIf="(term !== product.title) && autocomplete" (click)="update(product.title)"
class="bf-search-hint" >
{{ product.title }}
</div>
</ng-container>
</div>
Component
/**
* Created by lizarusi on 02.07.17.
*/
import {Component, OnInit, ViewChild, ElementRef} from '#angular/core';
import { ProductService } from '../shared/product.service'
import {Product} from '../shared/product.model';
import {Subject} from 'rxjs/Subject';
import {Observable} from 'rxjs/Observable';
import 'rxjs/add/observable/of';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/distinctUntilChanged';
import 'rxjs/add/operator/switchMap';
#Component({
selector: 'product-search',
templateUrl: './product-search.component.html',
styleUrls: ['./product-search.component.scss'],
providers: [ProductService]
})
export class ProductSearchComponent implements OnInit {
#ViewChild('productSearch') searchField: ElementRef;
products: Observable<Product[]>;
autocomplete = true;
term = '';
private searchTerms = new Subject<string>();
constructor(private productService: ProductService) {};
search(): void {
console.log('term ' + this.term);
this.searchTerms.next(this.term);
}
ngOnInit(): void {
this.products = this.searchTerms
.debounceTime(300)
.distinctUntilChanged()
.switchMap( term => this.productService.searchProducts(term))
.catch((err) => {
console.log(err);
return Observable.of<Product[]>([]);
});
}
closeAutocomplete(): void {
this.autocomplete = false;
}
activeAutocomplete(): void {
this.autocomplete = true;
this.search();
}
update(value: string): void {
console.log('asaa');
this.term = value;
this.searchField.nativeElement.focus();
}
}
In this case my (click) doesn't work. I suppose that it happens
because *ngIf removing my element from DOM and than creating it again, but event listeners wasn't assigned.
The question is: How can I use (click) inside/together with *ngIf? Or any other suggestions to figured this out.
Update:
I looked through your code beyond the ngIf and ngFor and realized you were attempting to directly use your Observable in your ngFor instead of a Subscription to the Observable. The general idea is that after you observe your data you'll want to then push the data into a variable of the proper format. Check out this guide for more information: https://angular-2-training-book.rangle.io/handout/observables/using_observables.html
And specifically this part of the code:
let subscription = this.data.subscribe(
value => this.values.push(value),
error => this.anyErrors = true,
() => this.finished = true
);
In your case this.data will be this.products, and this.values will be a new variable. If you use this.finished, you can replace that as your conditional in the ngIf
I'm using mdl-select component. It's a drop-down list. When you press it there are focusin event fired. But it doesn't when you press an arrow-dropdown icon, so I needed to change a template a bit to have a desired behavior. But it's a library component. Is there a way to override it's template?
The thing I need to change just to add tabindex=\"-1\" to element. I can do it with js, but I use component a lot in app, and I don't want to use document.getElement... every time I use MdlSelectComponent in the views of my own components.
I tried to use #Component decorator function on MdlSelectComponent type, however it requires to declare this class once again and anyway have done nothing.
Update
main.browser.ts
/*
* Angular bootstraping
*/
import { Component } from '#angular/core';
import { platformBrowserDynamic } from '#angular/platform-browser-dynamic';
import { decorateModuleRef } from './app/environment';
import { bootloader } from '#angularclass/hmr';
import {MdlSelectComponent} from '#angular2-mdl-ext/select';
/*
* App Module
* our top level module that holds all of our components
*/
import { AppModule } from './app';
/*
* Bootstrap our Angular app with a top level NgModule
*/
export function main(): Promise<any> {
console.log(MdlSelectComponent)
MdlSelectComponent.decorator.template = "<div class=\"mdl-textfield is-upgraded\" [class.is-focused]=\"this.popoverComponent.isVisible || this.focused\" [class.is-disabled]=\"this.disabled\" [class.is-dirty]=\"isDirty()\"> <span [attr.tabindex]=\"!this.disabled ? 0 : null\" (focus)=\"open($event);addFocus();\" (blur)=\"removeFocus()\"> <!-- don't want click to also trigger focus --> </span> <input #selectInput tabindex=\"-1\" [readonly]=\"!autocomplete\" class=\"mdl-textfield__input\" (click)=\"toggle($event)\" (keyup)=\"onInputChange($event)\" (blur)=\"onInputBlur()\" [placeholder]=\"placeholder ? placeholder : ''\" [attr.id]=\"textfieldId\" [value]=\"text\"> <span class=\"mdl-select__toggle material-icons\" (click)=\"toggle($event)\"> keyboard_arrow_down </span> <label class=\"mdl-textfield__label\" [attr.for]=\"textfieldId\">{{ label }}</label> <span class=\"mdl-textfield__error\"></span> <mdl-popover [class.mdl-popover--above]=\"autocomplete\" hide-on-click=\"!multiple\" [style.width.%]=\"100\"> <div class=\"mdl-list mdl-shadow--6dp\"> <ng-content></ng-content> </div> </mdl-popover> </div> ";
return platformBrowserDynamic()
.bootstrapModule(AppModule)
.then(decorateModuleRef)
.catch((err) => console.error(err));
}
// needed for hmr
// in prod this is replace for document ready
bootloader(main);
APP.COMPONENT.TS
import { Component, OnInit, ViewEncapsulation } from '#angular/core';
import { Router } from '#angular/router';
require('../../../styles/styles.scss');
import {MdlSelectComponent} from '#angular2-mdl-ext/select';
//
declare let Reflect: any;
Reflect.getOwnMetadata('annotations', MdlSelectComponent)[0].template = 'Hooray';
#Component({
selector: 'app',
encapsulation: ViewEncapsulation.None,
styleUrls: [],
template: `
<div>
<mdl-select [(ngModel)]="personId">
<mdl-option *ngFor="let p of people" [value]="p.id">{{p.name}}</mdl-option>
</mdl-select>
<router-outlet></router-outlet>
</div>`
})
export class AppComponent implements OnInit {
constructor(
router: Router,
) {
router.events.subscribe(data => {
scrollTo(0, 0);
});
}
public ngOnInit() {
}
}
As #angular2-mdl-ext/select uses Reflect to define decorators then you do the following
declare let Reflect: any;
Reflect.getOwnMetadata('annotations', MdlSelectComponent)[0].template = 'Hooray';
Plunker Example
Consider the following component
import {Component} from 'angular2/core'
#Component({
selector: 'my-app',
providers: [],
template: `
<div>
<h3>Input with two decimals</h3>
<input type="text"
[value]="formattedN"
(change)="nChanged($event.target.value)"/>
</div>
`,
directives: []
})
export class App {
private n: number = 0;
private formattedN: string = "0.00"
public nChanged(value) {
this.n = parseFloat(value);
this.formattedN = this.n.toFixed(2);
}
}
The input should always be a number with two decimals. That is however not always the case. Try removing the last zero, the field is not changed to 0.00 which is what I want. I understand it doesn't work because the formattedN value is not updated, which means that the property binding is not updated, hence the value of the input is not updated.
Run the example in plunker: http://plnkr.co/edit/Vyi4RKladslrdZslZQhm
Does anyone have a nice solution for this problem?
Is this Answer what you're trying to accomplish????
//our root app component
import {Component} from 'angular2/core'
#Component({
selector: 'my-app',
providers: [],
template: `
<div>
<input type="text" [(ngModel)]="formattedN" (change)="nChanged($event.target.value)"/>
</div>
`,
directives: []
})
export class App {
private n: number = 0;
private formattedN: string = "0.00"
constructor() {
this.name = 'Angular2'
}
public nChanged(value) {
console.log(value);
this.n = parseFloat(value);
console.log(this.n);
this.formattedN = this.n.toFixed(2)
console.log(this.formattedN);
}
}