How to operate multiple doms at the same time with angular directive? - javascript

I solved this problem, just < p [appHighlight]="markArray" #mark>111< /p >, and set '#Input('appHighlight') mark: Array' in highlight.directive.ts.
refer to: https://stackblitz.com/edit/angular-7ewavt
Thanks for all answer, and welcome other solutions.
Question Desc:
This is HTML:
<p appHighlight>111</p>
<p appHighlight>222</p>
<p appHighlight>333</p>
This is directive.ts:
#Directive({
selector: '[appHighlight]'
})
export class HighlightDirective {
constructor(private el: ElementRef) {
}
#HostListener('mouseenter') onMouseEnter() {
console.log(this.el);
// only output current mouse-hover DOM ElementRef
// but I want to get all DOM's ElementRef whose bound appHighlight
// in highlight.directive.ts, NOT xxx.component.ts
// in pursuit of better decouple and reuse.
}
}
I want :
When the mouse is hover one of the DOM elements, all DOMs bound with the appHighlight instruction are triggered.
The question is how to get the DOM ElementRef of all bound elements in directive.ts, NOT xxx.component.ts ? (because in pursuit of better decouple and reuse.)
Thanks.

the ViewChildren decorator gives you exactly that:
export class AppComponent implements AfterViewInit {
#ViewChildren(HighlightDirective, {read: ElementRef) children: QueryList<ElementRef>;
// ...
ngAfterViewInit() {
this.children.forEach(child => console.log(child.nativeElement));
}
}

If you are looking for all the elements inside the directive then it is not the right place to look into. If you need all the elements which are bound to appHighlight directive then you should look that in the parent component.
export class AppComponent {
#HostListener('mouseenter') onMouseEnter() {
this.highlights.forEach(highlight => console.log(highlight));
}
#ViewChildren(HighlightDirective, { read: ElementRef }) highlights: QueryList<ElementRef>
name = 'Angular';
}
Here we are now listening to mouseenter in AppComponent rather than inside
HighlightDirective .
Working Stackblitz

Related

Angular 2+ : Get Reference of appcomponent div in another components

I have components called app.component which is the main component in the angular project.
Navigation to customer component is done by routing.
And
Folder structer
src\app
- app.component.html
- app.component.ts
and
src\app\components\customer
- customer.component.html
- customer.component.ts
In my app.component.html
<div class="top-container" #topContainerRef>
<router-outlet></router-outlet>
</div>
In my customer.component.ts
I want to get reference of the top most container div which is contained in app.components
I want to replace
document.getElementsByClassName('top-container')[0].scrollTop = some values
with something similar to
#ViewChild('topContainerRef', { read: ElementRef, static: false }) topContainerRef: ElementRef;
this.topContainerRef.nativeElement.scrollTop= "some value" //here the topContainerRef is undefined
Is there any way i can use elementRef instead of classname or Id's.
You cannot use ViewChild for the #topContainerRef to get a reference of this element, because it is not rendered by your CustomerComponent.
You either need to get the reference of this element inside the app component itself and find a way to pass it to all the other children that might need it (not recommended).
Or you can just build a service and use that to "request" the scrollTop change by whichever component has access to this element (in your case the app component).
I would do it something like this:
export class AppContainerService {
private scrollTopSource = new ReplaySubject<number>(1);
public scrollTop$ = this.scrollTopSource.asObservable();
public updateScrollTop(value: number) {
this.scrollTopSource.next(value);
}
}
Inside your CustomerComponent:
public class CustomerComponent implements OnInit {
// ...
constructor(private containerService: AppContainerService) {
}
ngOnInit() {
this.containerService.updateScrollTop(/* whatever value you need */);
}
// ...
}
And finally, the AppComponent that will react to the scrollTop changes:
export class AppComponent implements AfterViewInit {
#ViewChild('topContainerRef', { read: ElementRef, static: false }) topContainerRef: ElementRef;
private subscriptions = new Subscription();
constructor(private containerService: AppContainerService) {
}
ngAfterViewInit() {
this.subscriptions.add(this.containerService.scrollTop$.subscribe((value: number) => {
this.topContainerRef.nativeElement.scrollTop = value;
}));
}
ngOnDestroy() {
this.subscriptions.unsubscribe();
}
}
Don't forget about unsubscribing inside ngOnDestroy. This is important so that you don't have memory leaks

Angular: ngOnInit hook does not work in dynamically created component

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

Angular 2 Input ID not getting bound to element

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

Access DOM element with template reference variables on component

I'm trying to get a reference to the DOM element for a component in an Angular 2 template using a template reference variable. This works on normal html tags but has a different behaviour on components. e.g.
<!--var1 refers to the DOM node -->
<div #var1></div>
<!--var2 refers to the component but I want to get the DOM node -->
<my-comp #var2></my-comp>
Is there any way force the template reference variable to refer to the DOM node even if it is on a component? And if so is it covered in the docs anywhere? The only docs I can find on this are here https://angular.io/docs/ts/latest/guide/template-syntax.html#!#ref-vars and they don't go into much detail on how the variables are resolved.
It depends on how you are going to use this reference.
1) There is no straight way to get component DOM reference within template:
import {Directive, Input, ElementRef, EventEmitter, Output, OnInit} from '#angular/core';
#Directive({selector: '[element]', exportAs: 'element'})
export class NgElementRef implements OnInit
{
#Output()
public elementChange:EventEmitter<any> = new EventEmitter<any>();
public elementRef:ElementRef;
constructor(elementRef:ElementRef)
{
this.elementRef = elementRef;
this.elementChange.next(undefined);
}
#Input()
public get element():any
{
return this.elementRef.nativeElement;
}
public set element(value:any)
{
}
ngOnInit():void
{
this.elementChange.next(this.elementRef.nativeElement);
}
}
Usage:
<my-comp [(element)]="var2"></my-comp>
<p>{{var2}}</p>
<!--or-->
<my-comp element #var2="element"></my-comp>
<p>{{var2.element}}</p>
2) You can get this reference in component that owns template with #ViewChild('var2', {read: ElementRef}).
As of Angular 8, the following provides access to the ElementRef and native element.
/**
* Export the ElementRef of the selected element for use with template references.
*
* #example
* <button mat-button #button="appElementRef" appElementRef></button>
*/
#Directive({
selector: '[appElementRef]',
exportAs: 'appElementRef'
})
export class ElementRefDirective<T> extends ElementRef<T> {
constructor(elementRef: ElementRef<T>) {
super(elementRef.nativeElement);
}
}

Bindings not working in dynamically loaded component

I'm encountering a problem where if I dynamically load a component, none of the bindings in the template are working for me. As well as this the ngOnInit method is never triggered.
loadView() {
this._dcl.loadAsRoot(Injected, null, this._injector).then(component => {
console.info('Component loaded');
})
}
Dynamically loaded component
import {Component, ElementRef, OnInit} from 'angular2/core'
declare var $:any
#Component({
selector: 'tester',
template: `
<h1>Dynamically loaded component</h1>
<span>{{title}}</span>
`
})
export class Injected implements OnInit {
public title:string = "Some text"
constructor(){}
ngOnInit() {
console.info('Injected onInit');
}
}
This is my first time using dynamically loaded components so I think may be attempting to implement it incorrectly.
Here's a plunkr demonstrating the issue. Any help would be appreciated.
As Eric Martinez pointed out this is a known bug related to the use of loadAsRoot. The suggested workaround is to use loadNextToLocation or loadIntoLocation.
For me this was problematic as the component I was trying to dynamically load was a modal dialog from inside a component with fixed css positioning. I also wanted the ability to load the modal from any component and have it injected into the same position in the DOM regardless of what component it was dynamically loaded from.
My solution was to use forwardRef to inject my root AppComponent into the component which wants to dynamically load my modal.
constructor (
.........
.........
private _dcl: DynamicComponentLoader,
private _injector: Injector,
#Inject(forwardRef(() => AppComponent)) appComponent) {
this.appComponent = appComponent;
}
In my AppComponent I have a method which returns the app's ElementRef
#Component({
selector: 'app',
template: `
<router-outlet></router-outlet>
<div #modalContainer></div>
`,
directives: [RouterOutlet]
})
export class AppComponent {
constructor(public el:ElementRef) {}
getElementRef():ElementRef {
return this.el;
}
}
Back in my other component (the one that I want to dynamically load the modal from) I can now call:
this._dcl.loadIntoLocation(ModalComponent, this.appComponent.getElementRef(), 'modalContainer').then(component => {
console.log('Component loaded')
})
Perhaps this will help others with similar problems.
No need to clean component instance from DOM.
use 'componentRef' from angular2/core package to create and dispose component instance.
use show() to load the modal component at desired location and hide() to dispose the component instance before calling loadIntoLocation secondtime.
for eg:
#Component({
selector: 'app',
template: `
<router-outlet></router-outlet>
<div #modalContainer></div>
`,
directives: [RouterOutlet]
})
export class AppComponent {
private component:Promise<ComponentRef>;
constructor(public el:ElementRef) {}
getElementRef():ElementRef {
return this.el;
}
show(){
this.component = this._dcl.loadIntoLocation(ModalComponent,this.appComponent.getElementRef(), 'modalContainer').then(component => {console.log('Component loaded')})
}
hide(){
this.component.then((componentRef:ComponentRef) => {
componentRef.dispose();
return componentRef;
});
}
}

Categories

Resources