Unable to get hold of child DOM element - javascript

Note: since the problem is a little complex, the code is abstracted for readability
We've a <parent-component> like this:
<child-component></child-component>
<button (click)="doSomeClick()"> Do Some Click </button>
The template of the <child-component> is:
<textarea #childComponentElement #someField="ngModel" name="someName" [(ngModel)]="someModel"></textarea>
We're trying to access the value of this element inside the parent-component.component.ts like this:
export class ParentComponent implements AfterViewInit {
#ViewChild('childComponentElement') el:ElementRef;
ngAfterViewInit() {
console.log(this.el.nativeElement.value);
}
doSomeClick(){
}
}
However it throws this error:
Cannot read property 'nativeElement' of undefined
What have we tried so far:
This gives access to <parent-component>, we need <textarea> of <child-component>
It's not about angular-tree-component
The directive name is camelCased
ElementRef seems to be an old thing
This throws Cannot read property 'nativeElement' of undefined
How is the reference between this.element.nativeElement & <input> element is getting established?
There is no *ngIf or *ngSwitchCase
There is no *ngIf used with #childComponentElement
The call is inside ngAfterViewInit only
Time out is a very dirty approach

There's no easy way to this with a nested component, you'll have to create an EventEmitter that emits the ElementRef of the element you are trying to get access to:
child.component.ts
class ChildComponent implements AfterViewInit {
#Output()
templateLoaded: EventEmitter<ElementRef> = new EventEmitter()
#ViewChild('childComponentElement') el: ElementRef
ngAfterViewInit(): void {
this.templateLoaded.emit(this.el)
}
}
parent.component.html
<child-component (templateLoaded)="templateLoaded($event)"
parent.component.ts
class ParentComponent {
templateLoaded(template: ElementRef): void {
// do stuff with the `template`
}
}
Original Answer
Try using the read property in the 2nd parameter of ViewChild
#ViewChild('childComponentElement', {read: ElementRef}) el: ElementRef
If you are wondering about the second parameter, this answer gives a very good explanation: What is the read parameter in #ViewChild for

Use the #Output decorator or a service instead of trying hopelessly to access the textarea directly from the parent component
child template
<textarea #childComponentElement #someField="ngModel" name="someName" [(ngModel)]="someModel"></textarea>
child component
#ViewChild('childComponentElement') el:ElementRef;
#Output() textarea = new EventEmitter();
constructor(){
this.textarea.emit(this.el.nativeElement.value);
}
parent template
<child-component (change)="getdata($event)"></child-component>
parent component
export class ParentComponent {
getdata(e) {
console.log(e);
}
}

Related

Angular - Cannot get parent component data

I'm passing a function as parameter from parent to child component. When click event is occurred, function of parent component trigger, but all the property of parent component is undefined. For example,
Parent Component
export class AppComponent implements OnInit {
constructor( private notificationService: NotificationService ) {}
unreadNotification(): Observable<any> {
// here this.notificationService is undefined
console.log( this.notificationService );
}
}
Parent html
<notification-menu [unread]= "unreadNotification"></notification-menu>
child Component
export class NotificationMenuComponent implements OnInit {
#Input() updateUnread: Function;
}
child html
<button type="button" class="icon-button" (click)="updateUnread()">
</button>
Now when I click on notification button, unreadNotification is triggered, but value of this.notificationService in console.log is undefined.
How can I solve this?
You should use #Input() to pass values from parent to child and #Output() to pass values from child to parent.
Child HTML:
<button type="button" class="icon-button" (click)="update()">
</button>
Child Component:
export class NotificationMenuComponent implements OnInit {
#Output() updateUnread = new EventEmitter<string>();
update() {
this.updateUnread.emit("I am working man!");
}
}
Parent HTML:
<notification-menu (updateUnread)= "unreadNotification($event)"></notification-menu>
Parent Component:
export class AppComponent implements OnInit {
constructor( private notificationService: NotificationService ) {}
unreadNotification(dataFromChild: string) {
console.log(dataFromChild);
}
}
The answer from #nimeresam is good advice - using an #Output is an idomatic way to achieve this.
It's worth noting though, that the reason that your original solution doesn't work is due to the way that javascript handles the this context.
Writing (click)="updateUnread()" is equivalent to saying this.updateUnread() with this being NotificationMenuComponent - as notificationService does not exist on NotificationMenuComponent you get the undefined error.
To have the context of the parent component used, you would need to bind the context to the updateUnread function before passing it into the child component.
This can be achieved either by converting the function to be an arrow functionn, or using Function.bind
See:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_objects/Function/bind
It's normally a good idea to enable the typescript option for --noImplicitThis to help catch these errors (though unsure if it will detect it in this case)
You can use arrow function so that you can use parent component's information. You can try as like as given below.
updateUnreadNotification = () => {
// by using arrow function you can get notificationService information
console.log( this.notificationService );
}
Hope your problem will be solve by this.

How can I access DOM elements in angular

I am tetsing a template driven form in angular, just testing not validating it.
I have read that it can be done using a viewChild property but it seems to not work for me.
I create a reference like this in my one of my forms label:
<label #ref id=.. class=...>
And now in my component I do this:
#ViewChild('ref') ref:ElementRef;
So, I suppose I created a valiable of type ElementRef that is viewChild of my input. So now I can use ref in my tests.
Inside my tests I do this:
let ref: HTMLElement:
it(....=>
{
ref = fixture.debugElement.query(By.css('ref')).nativeElement;
fixture.detectChanges();
expect(ref.innerHTML)toContain('Name');// or whatever
}
)
Now consider that the test, html and component files are separated from one another.
I still get errors of nativeElemnt property cannot be read. eventhough I have imported ElemntRef.
Is this the right way to access the DOM elemnts?? Or this viewChild doesnt make a referece to my label??
And again can I use the ID to access the form elements? What I have read they use a reference with #.
Thanks!!
For direct access to DOM in Angular you can make judicious use of ElementRef
However Direct access to DOM elements is not a good practice because
it leaves you vulnerable to XSS attacks.
Your AppComponent
import {Component, ElementRef} from '#angular/core';
#Component({
selector: 'my-app'
})
export class AppComponent implements ngOnInit {
constructor(private _elementRef : ElementRef) { }
ngOnInit(): void
{
this.ModifyDOMElement();
}
ModifyDOMElement() : void
{
//Do whatever you wish with the DOM element.
let domElement = this._elementRef.nativeElement.querySelector(`#someID`);
}
}
Your HTML
<p id="someID"></p>

How to pass template variable to component without ViewChild

I need to pass a component A to another component B.
Component B needs access to the nativeElement of A.
I managed to get it to work like this:
Container
Template
<component-a #componentA></component-a>
<component-b [origin]="reference"></component-b>
Controller
#ViewChild('componentA', {read: ElementRef}) reference: ElementRef;
Component B
#Input() origin: ElementRef;
Is there a way to get it to work without ViewChild, just with passing the template reference?
It should look like this:
<component-a #componentA></component-a>
<component-b [origin]="componentA"></component-b>
Right now if I do it like this I cannot access the nativeElement.
You can write the service class which can refer to the component and inject the service wherever you require to use the referred component.
#Component
class Component1 implements OnInit{
constructor(private ShareService : ShareService, private ref : ElementRef){
}
public ngOnInit(){
this.shareService.sharedComponent = this.ref;
}
}
ShareService {
public sharedComponent;
}
Better design would be to have sharedComponent as Observable.

Angular.js 2: Access component of a directive

Consider the following snippet of Parent's template:
<div *ngFor= "let event of events" >
<event-thumbnail [theEvent] = 'event'></event-thumbnail>
</div>
Also event-thumbnail component definition is:
export class EventThumbnailComponent{
intoroduceYourself(){
console.log('I am X');
}
}
In Parent component class, I want to iterate over all generated event-thumbnail elements, access the component beneath each, and call introduceYourself function on single one of them.
You want to use the #ViewChildren() decorator to get a list of all instances of a specific component type within the view:
class ParentComponent implements AfterViewInit {
#ViewChildren(EventThumbnailComponent)
eventThumbnails: QueryList<EventThumbnailComponent>;
ngAfterViewInit(): void {
// Loop over your components and call the method on each one
this.eventThumbnails.forEach(component => component.introduceYourself());
// You can also subscribe to changes...
this.eventThumbnails.changes.subscribe(r => {
// Do something when the QueryList changes
});
}
}
The eventThumbnails property will be updated whenever an instance of this component is added to or removed from the view. Notice the eventThumbnails is not set until ngAfterViewInit.
See the docs here for more information:
https://angular.io/docs/ts/latest/api/core/index/ViewChildren-decorator.html
Your child component should have #Input() theEvent to get access to the event you are passing. Then you can use the following lifecycle hook:
ngOnInit(){
introduceYourself(){
console.log('I am X');
}
}

Angular2: Creating child components programmatically

Question
How to create child components inside a parent component and display them in the view afterwards using Angular2? How to make sure the injectables are injected correctly into the child components?
Example
import {Component, View, bootstrap} from 'angular2/angular2';
import {ChildComponent} from './ChildComponent';
#Component({
selector: 'parent'
})
#View({
template: `
<div>
<h1>the children:</h1>
<!-- ??? three child views shall be inserted here ??? -->
</div>`,
directives: [ChildComponent]
})
class ParentComponent {
children: ChildComponent[];
constructor() {
// when creating the children, their constructors
// shall still be called with the injectables.
// E.g. constructor(childName:string, additionalInjectable:SomeInjectable)
children.push(new ChildComponent("Child A"));
children.push(new ChildComponent("Child B"));
children.push(new ChildComponent("Child C"));
// How to create the components correctly?
}
}
bootstrap(ParentComponent);
Edit
I found the DynamicComponentLoader in the API docs preview. But I get the following error when following the example: There is no dynamic component directive at element 0
This is generally not the approach I would take. Instead I would rely on databinding against an array that will render out more child components as objects are added to the backing array. Essentially child components wrapped in an ng-for
I have an example here that is similar in that it renders a dynamic list of children. Not 100% the same, but seems like the concept is still the same:
http://www.syntaxsuccess.com/viewarticle/recursive-treeview-in-angular-2.0
Warning: DynamicComponentLoader has been deprecated in RC.4
In Angular 2.0, loadIntoLocation method of DynamicComponentLoader serve this purpose of creating parent-child relationship. By using this approach you can dynamically create relationship between two components.
Here is the sample code in which paper is my parent and bulletin is my child component.
paper.component.ts
import {Component,DynamicComponentLoader,ElementRef,Inject,OnInit} from 'angular2/core';
import { BulletinComponent } from './bulletin.component';
#Component({
selector: 'paper',
templateUrl: 'app/views/paper.html'
}
})
export class PaperComponent {
constructor(private dynamicComponentLoader:DynamicComponentLoader, private elementRef: ElementRef) {
}
ngOnInit(){
this.dynamicComponentLoader.loadIntoLocation(BulletinComponent, this.elementRef,'child');
}
}
bulletin.component.ts
import {Component} from 'angular2/core';
#Component({
selector: 'bulletin',
template: '<div>Hi!</div>'
}
})
export class BulletinComponent {}
paper.html
<div>
<div #child></div>
</div>
Few things you needs to be take care of are mentioned in this answer
You should use ComponentFactoryResolver and ViewElementRef to add component at runtime.Let's have a look at below code.
let factory = this.componentFactoryResolver.resolveComponentFactory(SpreadSheetComponent);
let res = this.viewContainerRef.createComponent(factory);
Put the above code inside your ngOnInit function and replace "SpreadSheetComponent" by your component name.
Hope this will work.
Programmatically add components to DOM in Angular 2/4 app
We need to use ngAfterContentInit() lifecycle method from AfterContentInit. It is called after the directive content has been fully initialized.
In the parent-component.html, add the a div like this:
<div #container> </div>
The parent-component.ts file looks like this:
class ParentComponent implements AfterContentInit {
#ViewChild("container", { read: ViewContainerRef }) divContainer
constructor(private componentFactoryResolver: ComponentFactoryResolver) { }
ngAfterContentInit() {
let childComponentFactory = this.componentFactoryResolver.resolveComponentFactory(childComponent);
this.divContainer.createComponent(childComponentFactory);
let childComponentRef = this.divContainer.createComponent(childComponentFactory);
childComponentRef.instance.someInputValue = "Assigned value";
}
}
Inside src\app\app.module.ts, add the following entry to the #NgModule() method parameters:
entryComponents:[
childComponent
],
Notice that we're not accessing the div#container using the #ViewChild("container") divContainer approach. We need it's reference instead of the nativeElement. We will access it as ViewContainerRef:
#ViewChild("container", {read: ViewContainerRef}) divContainer
The ViewContainerRef has a method called createComponent() which requires a component factory to be passed as a parameter. For the same, we need to inject a ComponentFactoryResolver. It has a method which basically loads a component.
The right approach depends on the situation you're trying to solve.
If the number of children is unknown then NgFor is the right approach.
If it is fixed, as you mentioned, 3 children, you can use the DynamicComponentLoader to load them manually.
The benefits of manual loading is better control over the elements and a reference to them within the Parent (which can also be gained using templating...)
If you need to populate the children with data, this can also be done via injection, the Parent is injected with a data object populating the children in place...
Again, a lot of options.
I have used 'DynamicComponentLoader' in my modal example, https://github.com/shlomiassaf/angular2-modal

Categories

Resources