I have difficulties binding to host element 'id' attribute in my select-picker directive. I am using #HostBinding('attr.id'), but it returns undefined. I have checked the actual DOC and it looks like this is the way this simple task should be done.
Here is my component:
import {Component, OnInit, Input, Output, EventEmitter, AfterViewInit, HostBinding} from '#angular/core';
declare const $;
#Component({
selector: '[select-picker]',
templateUrl: 'select-picker.component.html'
})
export class SelectPickerComponent implements OnInit, AfterViewInit {
#Input() options: Array<Object>;
#Input() #HostBinding('class.cancelable') cancelable: boolean;
#Input() #HostBinding('class.expand-up') expandUp: boolean;
#Input() #HostBinding('style.width') elemWidth: string;
#HostBinding('attr.id') id: string;
#Output() value: EventEmitter<boolean> = new EventEmitter<boolean>();
select: any;
constructor() {
}
ngOnInit() {
console.log(this.id) // <-- this logs 'undefined';
}
ngAfterViewInit() {
const self = this;
this.select = $(`#${this.id} select`).selectize({ // this init works, but with `id="undefined"`
readOnly: true,
onChange: function (val) {
self.value.emit(val);
},
dropdownDirection: 'up'
});
}
discardValue() {
this.select[0].selectize.setValue(0);
}
}
And this is the view (from parent component where directive is used):
<div select-picker id="page-options" [options]="pageOptions" [elemWidth]="'200px'" (value)="setItemsPerPage($event)"></div>
Attribute binding for static values can also be done with simple:
#Input() id: string;
Both versions - <div id="some-static-id" ...> and <div [id]="someDynamicId" ...> - will set the value on your component when using #Input().
EDIT: However, it is strongly discouraged to use jQuery and lookup by IDs in Angular. I would question if your approach is the best option to accomplish what you want. You should probably create a separate question where you explain what you try to accomplish and what is the best way to do this with Angular.
If you want to grab a Id value just use #ViewChild not #HostBinding
<div #myDiv></div>
#ViewChild('myDiv') myDiv: ElementRef;
console.log(this.myDiv.nativeElement.id)
and then use methods of ElementRef do get id attribute
You could directly retrieve attribute value of element using getAttribute method on directive DOM. This will only work when you have static element id. If you want to pass id dynamically, use Input bindings.
constructor(private elementRef: ElementRef){}
ngOnInit() {
this.elementRef.nativeElement.getAttribute('id')
}
Plunker Demo
Related
Wanted to update Dom elements like Image and Text after loading component in Angular. Note: Both of them does not have ID but have Class Name.
I used following code:
#ViewChild('.appnametitle') el: ElementRef;
constructor(
private renderer: Renderer2,
#Inject(DOCUMENT) private document,
#Inject(ONEPLACE_JS_URI) private oneplaceJsUri,
public cookieConfig: CookieConfig
) { }
ngAfterViewInit(): void {
this.renderer.setAttribute(this.el.nativeElement, 'innerHTML', 'Test Title Change');
}
but it shows following error:
Cannot read properties of undefined (reading 'nativeElement')
Could you help me where I am doing it wrong?
ViewChild has not a classname as selector (a directive yes). So you can create a directive with the selector of the class and use ViewChild asking about the directive
#Directive({
selector: '.appnametitle'
})
export class SelectDirective {
constructor(public el:ElementRef) { }
}
And in Component:
#ViewChild(SelectDirective) selectDirective:SelectDirective
ngAfterViewInit(): void {
this.renderer.setAttribute(this.selectDirective.el.nativeElement,
'innerHTML', 'Test Title Change');
}
But this is used if you has not access to the component (because was on third persons, e.g.), else you should rethinking the problem in a more angular way
I'm having the following directive that adds dynamic component to ng-container
#Directive({
selector: '[appAddingDirective]'
})
export class AddingDirective {
constructor(protected vc: ViewContainerRef) { }
public addComponent(factory: ComponentFactory<any>, inputs: any): void {
this.vc.clear();
const ref: ComponentRef<any> = this.vc.createComponent(factory);
Object.assign(ref.instance, inputs); // can't find more elegant way to assign inputs((
ref.instance.ngOnInit(); // IMPORTANT: if I remove this call ngOnInit will not be called
}
}
The directive is used in an obvious way.
#Component({
selector: 'app-wrapper',
template: `<ng-container appAddingDirective></ng-container>`
})
export class WrapperComponent implements AfterViewInit{
#ViewChild(DynamicItemDirective)
private dynamicItem: DynamicItemDirective;
constructor() { }
ngAfterViewInit(): void {
// hope it doesn't matter how we get componentFactory
this.dynamicItem.addComponent(componentFactory, {a: '123'});
}
}
Finally in a component that is loaded dynamically I have
#Component({
selector: 'app-dynamic',
template: '<p>Dynamic load works {{ a }}</p>'
})
export class DynamicComponent implements OnInit {
#Input() a: string;
constructor() {}
ngOnInit(): void {
console.log(this.a);
debugger;
}
}
Here are my questions.
If I remove ref.instance.ngOnInit() call in AddingDirective, I do not get in ngOnInit of DynamicComponent (debugger and console.log do not fire up). Do component lifecycle hooks work in a component that is created and attached dynamically? What is the best way to make these hooks work?
I don't see rendered string Dynamic load works 123 still if I remove {{ a }} in template (template: '<p>Dynamic load works</p>'), Dynamic load works is rendered as it should. What is the reason and how can I fix that?
Is there a better way to assing inputs than doing Object.assign(ref.instance, inputs) as above?
PS. I'm using Angular 11
Whatever values are inside the individuals are printed without issues but whatever is obtained using #Input or #Output is not displayed.
child.component.ts
#Component({
selector: 'app-form-input',
templateUrl: './form-input.component.html',
styleUrls: ['./form-input.component.scss']
})
export class FormInputComponent implements OnInit {
#Input() fieldType: string;
//with another Input and 1 Output DOM
constructor(
) {
}
ngOnInit() {
console.log(this.fieldType);
}
}
parent.component.html
<app-form-input (value)="action($event)"
[fieldType]="date"
[maxAllowItem]="1">
</app-form-input>
Is there anything goes wrong in syntax?
The Log always show 'undefined' in all cases.
Thanks
I think this is trying to pull in a variable defined within your component.
Try the following syntax, wrap the string again, this should ensure you are passing a string and not a variable from the component, the input will then know to expect a string.
[fieldType]="'date'"
This is wrapping the string in " and '.
You may need to initialize the initial values of your #Input and #Output variables inside your component because #Input properties will be undefined unless they are provided from outside and #Output properties need to be initialized with EventEmitter
Also you need to check the values inside ngOnChanges which will be executed during Change Detection
Your code will be like this:
#Component({
selector: 'app-form-input',
templateUrl: './form-input.component.html',
styleUrls: ['./form-input.component.scss']
})
export class FormInputComponent implements OnInit {
#Input() fieldType: string;
#Output() event: EventEmitter<any>
//with another Input and 1 Output DOM
constructor() {
this.fieldType = ''
this.event = new EventEmitter()
}
ngOnInit() {
}
ngOnChanges() { // <- it will run every time and give you the latest value of fieldType
console.log(this.fieldType);
}
}
I have an Angular 2 component that gets elementId as an Input then sets that as an id attribute on a div. For some reason the div's id not getting set.
#Component({
selector: 'my-chart',
template: `
<div>
Hello {{elementId}}
<div [id]="elementId"></div>
</div>
`
})
export class MyChartComponent implements OnInit, OnChanges {
#Input() elementId: string;
ngOnInit() {
this.createChart();
}
createChart() {
console.log("ID: ", this.elementId);
...
}
}
This is what it looks like in the parent component:
<div *ngFor="let chart of charts">
<my-chart [elementId]="chart.id"></my-chart>
</div>
--
When I inspect element on the div, it shows that the id attribute was not set on the HTML element. Also the Hello {{elementId}} only shows the "Hello ". There is no elementId filled in. See photo below.
But the console.log statement correctly prints out the id, indicating that the input binding is correct.
Image: Inspect Element shows id is missing
there is a equivalent for document.getElementById(); in angular and you can use that.
you can use element reference (ElementRef) in angular and querySelector the way you used in jQuery.
constructor(private elementRef: ElementRef) {
}
// this is inside any of the method
// this is to select multiple
this.elementRef.nativeElement.querySelectorAll('.mandate');
// this is to select single
this.elementRef.nativeElement.querySelector('.mandate')
So rather using document, you should use ElementRef
In angular lifecycle AfterViewInit is where the place all the html and js binding happened.
You should write external dom monuplation in your AfterViewInit method.
export class MyChartComponent implements OnInit, OnChanges, AfterViewInit {
#Input() elementId: string;
ngOnInit() {
}
ngAfterViewInit(): void {
console.log("ID: ", this.elementId);
Plotly.newPlot(this.elementId, ...);
}
}
Dynamically Setting the #HostListener's Arguments
I have a directive which needs to listen for any event provided declaratively by the engineer. Here's an example:
import { Directive, Input, ElementRef, HostListener, OnInit } from '#angular/core';
//--
import { Sandbox } from '../../../sandbox';
#Directive({ selector: '[addItem]' })
class AddNewItemDirective implements OnInit {
#Input('addItem') data;
#Input() on: string = 'click';
constructor(private $: Sandbox, private element: ElementRef) { }
ngOnInit() { console.log('#INIT', this); }
#HostListener('click', ['$event']) handleEvent(e) {
console.log('add-item', e);
}
}
export { AddNewItemDirective };
Here's its usage:
<button class="btn btn-primary" [addItem]="{ name: 'Jeffrey' }" on="focus">Add New Item</button>
This works fine. However, my intuition told me I should be able to dynamically set the HostListener's arguments at render time based upon an input parameter:
#Directive({ selector: '[addItem]' })
class AddNewItemDirective implements OnInit {
#Input('addItem') data;
#Input() on: string = 'click';
constructor(private $: Sandbox, private element: ElementRef) { }
ngOnInit() { console.log('#INIT', this); }
#HostListener(this.on, ['$event']) handleEvent(e) {
console.log('add-item', e);
}
}
Of course, this.on would not be overwritten with 'focus' until the time ngOnInit is invoked. To my surprise, this.on throws an error because undefined has no property 'on'. So when my directive class is instantiated, for whatever reason, this === undefined.
I found one similar question here, though, its looking to dynamically modify HostListener at runtime while I just need it modified at compile/render/instantiation time.
Can someone please shed light on how I can accomplish this?
Thx
HostListener is not dynamic, it can not be changed at runtime. You should use Renderer class, which provides listen method:
#Input()
public on:string;
private dispose:Function;
constructor(private renderer:Renderer, private elementRef:ElementRef){}
ngOnInit(){
this.dispose = this.renderer.listen(this.elementRef.nativeElement, this.on, e => console.log(e));
}
ngOnDestroy(){
this.dispose();
}