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);
}
}
Related
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
I want to show taxDetailsId in my child component Html page.
But when click submit button.
After click submit button then shows taxDetailsId in my child component Html page.
Parent Component
export class OnlinePaymentComponent implements OnInit {
HttpClient: any;
paymentForm: FormGroup = this.formBuilder.group({
taxDetailsId: ['', [Validators.required]]
});
constructor(
private formBuilder: FormBuilder,
private router: Router,
) {}
ngOnInit() {}
submitForm(): void {
if (!this.paymentForm.valid) {
this.router.navigate(['/home/online-payment/error']);
return;
}
}
}
Parent.Component.html
<form [formGroup]="paymentForm" (ngSubmit)="submitForm()">
<label>Tax Details Id</label>
<input type="text" formControlName="taxDetailsId" placeholder="Tax Details Id" />
<button>Pay Bill</button>
<form>
Child Component
export class OnlinePaymentErrorComponent implements OnInit {
constructor() { }
ngOnInit(): void {
}
}
Child.Component.html
<div>
<button [routerLink]="['/home/online-payment']" >Back Home</button>
</div>
you can try this pattern this.router.navigate(['/heroes', { id: heroId }]);
https://angular.io/guide/router
you can use angular #Input() decorator for it.
Child Component
import { Component, Input } from '#angular/core';
export class ChileComponent {
#Input() public taxDetailsId: number;
}
Child Component HTML
enter code here
<div>
{{ taxDetailsId }}
<button [routerLink]="['/home/online-payment']" >Back Home</button>
</div>
Parent Component HTML
<app-child-component [taxDetailsId]="taxDetailsId"> </app-child-component>
https://angular.io/guide/inputs-outputs
You can pass components around using Angular's InjectionToken.
First you start off by creating the token:
export const ONLINE_PAYMENT_REF = new InjectionToken<OnlinePaymentComponent>('OnlinePaymentComponent');
Next you add the token to one of the root components as a provider, in this case it is the OnlinePaymentComponent. This way everything that is a child of this component, and everything that is a child of those components, and so on; will have a reference to the main parent that we create here:
#Component({
selector: 'online-payment',
template: `
<online-payment-error></online-payment-error>
`,
providers: [
{
provide: ONLINE_PAYMENT_REF,
// Forwards the instance of OnlinePaymentComponent when injected into
// the child components constructor.
useExisting: forwardRef(() => OnlinePaymentComponent)
}
]
})
export class OnlinePaymentComponent {
message = 'I am the Online Payment Component';
}
Now that we have the main component setup, we can access it through the constructor of anything that is a child of OnlinePaymentComponent (no matter how deep it is).
#Component({
selector: 'online-payment-error',
template: `
<h2>Child</h2>
<strong>Parent Message:</strong> {{parentMessage}}
`
})
export class OnlinePaymentErrorComponent implements OnInit {
parentMessage = '';
constructor(
#Inject(ONLINE_PAYMENT_REF) private parent: OnlinePaymentComponent
) {}
ngOnInit() {
this.parentMessage = this.parent.message;
}
}
When all is said and done, you will see the following:
The pros of this method are that you don't have to bind values to the elements in the template, and if those components have components that need to reference the parent you wouldn't have to bind to those either. Since components look up the hierarchy till they find the first instance of the provider that we are looking for they will find the one in OnlinePaymentComponent.
This becomes very helpful when components get deeper and deeper into the parent component (say 5 levels deep), that means every time you would have to pass a reference to the template element 5 times, and if it changes or gets deeper you would have to update all the templates.
With this method we no longer need to update templates to pass data from one component to another component, we just request it in our constructor as seen in OnlinePaymentErrorComponent.
There are two simple ways:
Using query params (without routerLink).
Using Observables.
Using query params, you can use the router.navigate and pass the params you need (Id) along with the route.
eg: this.route.navigate(['yourroute/route', { tId: variableWithId }])
Using Observable, when you click on the button, use the same router navigate without params and pass the required data to an observable. On successful routing to the next page, get the resolved data from the observable.
I'm trying to create an angular component maker and i would like to be able to dynamically generate the HTML code for this component. I already tried this but I can't find a way to dynamically set the element type in the HTML code.
import {Component, Input} from '#angular/core';
export class BaseComponent<K extends HTMLElement> {
public type: K;
public classes: string[] = [];
public style = {};
public childNodes: BaseComponent<HTMLElement>[];
}
#Component({
selector: 'base-component',
template: `
<<my component.type here> [ngClass]="this.component.classes" [ngStyle]="this.component.style" ]>
<base-component *ngFor="let child of component.childNodes" [component]="child"></base-component>
</<my component.type here>>
`
})
export class GraphicalComponent {
#Input()
public component: BaseComponent<HTMLElement>;
}
but yet I did not find any way to properly inject my k type element in the dom unless I use document.createElement function which doesn't really make sense in an angular reference based program
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
I have been looking for a solution for this for a while. I have tried a bunch of different things from #Input, #Query, dynamicContentLoader, #Inject, #Inject(forwardRef(() but haven't been able to figure this out yet.
My Example Structure:
parent.ts
import {Component} from 'angular2/core';
#Component({
selector : 'my-app',
directives : [Child],
template : `<parent-component></parent-component>`
})
export class Parent
{
private options:any;
constructor()
{
this.options = {parentThing:true};
}
}
child.ts
import {Component} from 'angular2/core';
#Component({
selector : 'parent-component',
template : `<child-component></child-component>`
})
export class Child
{
constructor(private options:any) <- maybe I can pass them in here somehow?
{
// what I am trying to do is get options from
// the parent component at this point
// but I am not sure how to do this with Angular 2
console.log(options) <- this should come from the parent
// how can I get parent options here?
// {parentThing:true}
}
}
This is my current HTML output in the DOM, so this part is working as expected
<parent-component>
<child-component></child-component>
</parent-component>
Question Summarized:
How can I pass options from a parent component to a child component and have those options available in the child constructor?
Parent to child is the simplest form of all but it's not available in the constructor, only in ngOnInit() (or later).
This only requires an #Input() someField; in the child component and using binding this can be passed from parent to children. Updates in the parent are updated in the child (not the other direction)
#Component({
selector: 'child-component',
template'<div>someValueFromParent: {{someValueFromParent}}</div>'
})
class ChildComponent {
#Input() someValueFromParent:string;
ngOnInit() {
console.log(this.someValue);
}
}
#Component({
selector: 'parent-component',
template: '<child-component [someValueFromParent]="someValue"></child-component>'
})
class ParentComponent {
someValue:string = 'abc';
}
to have it available in the constructor use a shared service. A shared service is injected into the constructor of both components. For injection to work the service needs to be registered in the parent component or above but not in the child. This way both get the same instance.
Set a value in the parent and read it in the client.
#Injectable()
class SharedService {
someValue:string;
}
#Component({
selector: 'child-component',
template: '<div>someValueFromParent: {{someValueFromParent}}</div>'
})
class ChildComponent {
constructor(private sharedService:SharedService) {
this.someValue = sharedService.someValue;
}
someValue:string = 'abc';
}
#Component({
selector: 'parent-component',
providers: [SharedService],
template: '<child-component></child-component>'
})
class ParentComponent {
constructor(private sharedService:SharedService) {
sharedService.someValue = this.someValue;
}
someValue:string = 'abc';
}
update
There is not much difference. For DI only the constructor can be used. If you want something injected it has to be through the constructor. ngOnInit() is called by Angular when additional initialization has taken place (like bindings being processed). For example if you make a network call it doesn't matter if you do it in the constructor on in ngOnInit because the call to the server is scheduled for later anyway (async). When the current sync task is completed, JavaScript looks for the next scheduled task and processes it (and so on). Therefore it's probably so that the server call initiated in the constructor is actually sent after ngOnInit() anyway no matter where you place it.
You could use an #Input parameter:
import {Component,Input} from 'angular2/core';
#Component({
selector : 'parent-component',
template : `<child-component></child-component>`
})
export class Child {
#Input()
options:any;
ngOnInit() {
console.log(this.options);
}
}
Notice that the value of options is available in the ngOnInit and not in the constructor. Have a look at the component lifecycle hooks for more details:
https://angular.io/docs/ts/latest/guide/lifecycle-hooks.html
And provides the options as decribed below:
import {Component} from 'angular2/core';
#Component({
selector : 'my-app',
directives : [Child],
template : `<parent-component [options]="options"></parent-component>`
})
export class Parent {
private options:any;
constructor() {
this.options = {parentThing:true};
}
}
If you want to implement custom events: child triggers an event and the parent register to be notified. Use #Output.