Cannot read property 'nativeElement' of undefined ng-content in Angular 4 - javascript

I have a my-alert-component which looks like this:
import { Component, OnInit,Input } from '#angular/core';
#Component({
selector: 'app-my-alert',
template: `
<h1 (click)="alert()">{{type}}</h1>
<ng-content></ng-content>
`
})
export class MyAlertComponent implements OnInit {
#Input() type: string = "Success";
alert(){
console.log("alert");
}
constructor() { }
ngOnInit() {
}
}
And in my app component, I am doing the following:
import { Component,ComponentRef,ComponentFactory,ViewContainerRef, ComponentFactoryResolver,ChangeDetectorRef, ViewChild, TemplateRef, ViewChildren, QueryList, AfterViewInit,ElementRef, ContentChild, AfterContentInit } from '#angular/core';
import { MyAlertComponent } from './my-alert.component';
#Component({
selector: 'app-root',
template: `
<app-my-alert>
<p #insideNgContentVar>A paragraph inside ng-content</p>
</app-my-alert>
<app-my-alert type="danger"></app-my-alert>
<app-my-alert type="success"></app-my-alert>
`
})
export class AppComponent implements AfterViewInit{
#ViewChildren(MyAlertComponent) alertComponents : QueryList<AlertComponent>;
#ContentChild('insideNgContentVar') insideNgContent:ElementRef;
ngAfterContentInit(){
console.log(this.insideNgContent.nativeElement.textContent);
}
ngAfterViewInit(){
this.alertComponents.forEach((alertComponentInstance) => console.log(alertComponentInstance));
}
}
This is pretty simple, I thought.
But the error I am getting is:
EXCEPTION: Error in :0:0 caused by: Cannot read property 'nativeElement' of undefined
What am I doing wrong?
EDIT
If in my-alert-component I do:
#Component({
selector: 'app-my-alert',
template: `
<h1 (click)="alert()">{{type}}</h1>
<ng-content></ng-content>
`
})
export class MyAlertComponent implements AfterContentInit,AfterViewInit {
#Input() type: string = "Success";
#ContentChild('insideNgContent') insideNgContentRef:ElementRef;
alert(){
console.log("alert");
}
ngAfterContentInit(){
console.log(this.insideNgContentRef.nativeElement.textContent);
}
And in my app component:
template: `
<app-my-alert>
</app-my-alert>
<app-my-alert type="danger">
<p #insideNgContent>A paragraph inside ng-content</p>
</app-my-alert>
<app-my-alert type="success"></app-my-alert>
`
After these changes also, something seems to be missing.
What is that?

I think you should be using #ViewChild here, as per this answer:
https://stackoverflow.com/a/34327754/5018962
So basically, ViewChild is going to look for components in the DOM that are defined in your component's template, but ContentChild will look for components defined as ng-content of your app-root component itself, but not ng-content of some child components. But in this case no one is using your app-root component and no one is going to inject any DOM there, so that's why you get undefined
EDIT.
It would work, if you added the following line to the app-my-alert component:
#ContentChild('insideNgContentVar') insideNgContent:ElementRef;
because that means, look at what is defined in the ng-content of app-my-alert. Hope that sheds some light on this topic

Related

Angular - trying to use child component function in parent view but I'm gettting an error

When I use #ViewChild I get the error that the component is not defined.
When I use #ViewChildren I get the error that the function from that component is not a function.
I am new to using child components in Angular so I'm not sure why it's doing this when I do have the child component defined in the parent component and when it's clearly a function in the child component.
I don't want to have to define every function from the child in the parent or else what's even the point of using a separate component.
Child Component
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-mood',
templateUrl: './mood.component.html',
styleUrls: ['./mood.component.css']
})
export class MoodComponent implements OnInit {
moodColors = ['red', 'orange', 'grey', 'yellow', 'green'];
constructor() { }
ngOnInit(): void {
}
chooseMood() {
alert(this.moodColors);
}
}
Parent Component (Relavant Part of Version with "ERROR TypeError: ctx_r3.mood is undefined")
import { Component, OnInit, ViewChild, ViewChildren } from '#angular/core';
import { ViewEncapsulation } from '#angular/core';
import { MoodComponent } from '../mood/mood.component';
#Component({
selector: 'app-calendar',
templateUrl: './calendar.component.html',
styleUrls: ['./calendar.component.css'],
encapsulation: ViewEncapsulation.None
})
export class CalendarComponent implements OnInit {
#ViewChild('mood') mood: MoodComponent = new MoodComponent;
Parent Component (Relavant Part of Version with "ERROR TypeError: ctx_r3.mood.chooseMood is not a function")
import { Component, OnInit, ViewChild, ViewChildren } from '#angular/core';
import { ViewEncapsulation } from '#angular/core';
import { MoodComponent } from '../mood/mood.component';
#Component({
selector: 'app-calendar',
templateUrl: './calendar.component.html',
styleUrls: ['./calendar.component.css'],
encapsulation: ViewEncapsulation.None
})
export class CalendarComponent implements OnInit {
#ViewChildren('mood') mood: MoodComponent = new MoodComponent;
Parent View
<h2 (click)="mood.chooseMood()"></h2>
You don't explicitly initialize view children via new.
Just use:
#ViewChild('mood') mood : MoodComponent;
If that doesn't work post a Stackblitz example which I can edit to resolve the issue.
Also, using ViewChild is more of an exception in Angular, and your use of it points to a probable design issue. More likely you child component should emit via an Output to the parent.
Regarding outputs, you can do something like this - though it is hard to give a precise answer without deeper knowledge of what you are trying to achieve:
export class MoodComponent implements OnInit {
#Input() moodId: string;
#Output() chooseMood = new EventEmitter<string>();
moodClicked(){
this.chooseMood.emit(moodId);
}
}
export class CalendarComponent implements OnInit {
moodChosen(string: moodId){
console.log(moodId);
}
}
// Calendar template:
<app-mood
moodId="happy"
(chooseMood)="moodChosen($event)"
></app-mood>
1 - you have to use this code
#ViewChild('mood') mood : MoodComponent;
when you are using #ViewChildren it will return list of items with the 'mood' name then you have to use this code
mood.first.chooseMood() ;
its better use ViewChildren when there is ngIf in your element
2- no need new keyword for initialize mood variable
it would be fill after ngOnInit life cycle fires

Problem calling one Angular component from another component

At work, I have run into a problem using Angular. I have this kind of Angular component:
#Component({
selector: 'foo',
templateUrl: 'foo.html'
})
export class FooComponent {
#Input() data: string;
content: string;
ngOnInit() {
this.content = this.data;
}
setValue(data) {
this.content = data;
}
}
This is initialized from my main Angular component in a code block such as this:
this.components = [FooComponent, BarComponent, BazComponent, QuuxComponent];
Now this works so far. But if I try to call the setValue() function with this.components[0].setValue("Hello world!"); I get an error "this.components[0].setValue is not a function."
What is the reason for this and how can I fix it?
This seems like a very very weird way to work with components in angular.
You really don't want to break encapsulation by calling methods inside one component from another component.
I personally haven't seen this kind of component referencing anywhere (and have doubts it is a correct approach).
There is no reason to duplicate the data property in the content.
You can pass values in the template. Or use a service if you don't have direct access to the template.
Here is a very basic example on how to modify data from the parent using a template and #Input.
app.component.ts
import { Component } from "#angular/core";
#Component({
selector: "my-app",
templateUrl: "./app.component.html",
styleUrls: ["./app.component.css"]
})
export class AppComponent {
message = "I am a message from the parent";
}
app.component.html
<app-child [content]='message'></app-child>
child.component.ts
import { Component, OnInit, Input } from "#angular/core";
#Component({
selector: "app-child",
templateUrl: "./child.component.html",
styleUrls: ["./child.component.css"]
})
export class ChildComponent implements OnInit {
#Input("content") public content: string;
constructor() {}
ngOnInit() {}
}
child.component.html
<p>{{content}}</p>

How can I access an already transcluded ContentChild?

I have a angular component app-b that is used within a component app-a that is used in the app-component. The app-component has some content in app-a, app-a transcludes this with ng-content into app-b, app-b shows it with another ng-content - but how can I access this content within the component (and not it's template)?
I would think that ContentChild is the correct approach but appears to be wrong.
Example:
https://stackblitz.com/edit/angular-ddldwi
EDIT: Updated example
You cannot query by tag name with #ContentChild decorator. You can query either by template variable, component or directive selector.
app-a.component.html
<app-b>
<ng-content></ng-content>
<p #myContent>This is a content child for app-b.</p>
</app-b>
app-b.component.ts
import { Component, AfterContentInit, ContentChild } from '#angular/core';
#Component({
selector: 'app-b',
templateUrl: './b.component.html',
styleUrls: ['./b.component.css']
})
export class BComponent implements AfterContentInit {
#ContentChild('myContent') contentchild;
ngAfterContentInit() {
console.log(this.contentchild);
}
}
Live demo
I recommend sharing the data between components. For example, move your data (E.G. dummyName) into a service. Then add the service to each component (where you need the shared data).
Service:
import { Injectable } from '#angular/core';
#Injectable()
export class DataShareService {
public dummyName: String = 'one';
constructor() { }
}
Add the new service to app.module.ts:
providers: [DataShareService],
Child Component:
import { DataShareService } from './data-share.service';
import { Component } from '#angular/core';
#Component({
selector: 'app-child',
templateUrl: './child.component.html'
})
export class ChildComponent {
constructor(public ds: DataShareService) { }
toggle() {
this.ds.dummyName = this.ds.dummyName === 'one' ? 'two' : 'one';
}
}
Child Component template (html):
<p> {{ds.dummyName}}</p>
<button (click)="toggle()">Click Me to Toggle Value</button>
Parent Component:
import { Component, OnInit } from '#angular/core';
import { DataShareService } from './data-share.service';
#Component({
selector: 'app-root',
templateUrl: './app.component.html'
})
export class AppComponent {
constructor(public ds: DataShareService) {}
displayNumber() {
return this.ds.dummyName === 'one' ? 1 : 2;
}
}
Parent Component template (html):
<p> this is the parent component:</p>
<div> value as string: {{ ds.dummyName }} </div>
<div> value as number: <span [textContent]="displayNumber()"></span></div>
<hr>
<p> this is the child component:</p>
<app-child></app-child>
Note! The child component toggle() function demonstrates how you can change the data. The parent component displayNumber() function demonstrates how to use the data, independent of it's display (I.E. as just pure data).
This appears to be impossible due to a bug in Angular:
https://github.com/angular/angular/issues/20810
Further reference:
https://www.reddit.com/r/Angular2/comments/8fb3ku/need_help_how_can_i_access_an_already_transcluded/

Accessing DOM element in Angular 2 and change the element's class attribute

I'm new in angular2. my code is like this:
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'main',
template: `
<div class="current">
</div>
`
})
export class MainComponent implements OnInit {
ngOnInit(): void {
//change the div class from current to next...
}
}
i'd like to change the div class from 'current' to 'next'.
i appropriate if you let me know what is the best way do that?
One option is to use a template reference variable.
In the example below, the reference variable #target is added to the desired element and then the decorator #ViewChild (#ViewChild('target') target) allows you to access the variable in your component.
From there, you can get a reference to the DOM element by accessing the nativeElement property on the variable.
Here is an example where the class name is updated:
import { Component, AfterViewInit, ViewChild } from '#angular/core';
#Component({
selector: 'main',
template: `
<div #target class="current">
</div>
`
})
export class MainComponent implements AfterViewInit {
#ViewChild('target') target;
constructor() { }
ngAfterViewInit(): void {
let element = this.target.nativeElement;
element.className = 'next';
}
}
However, it's worth pointing out that you can handle most DOM manipulation with the build-in DOM directives. In this case you could just use the ngClass directive to bind a variable with the class attribute:
import { Component, AfterViewInit } from '#angular/core';
#Component({
selector: 'main',
template: `
<div [ngClass]="targetClass">
</div>
`
})
export class MainComponent implements AfterViewInit {
private targetClass: string = 'current';
constructor() { }
ngAfterViewInit(): void {
this.targetClass = 'next';
}
}

Angular2 - Trying to add a component into a child component from parent

I am trying to add component dynamically into a child component from the parent component. I have injected the child component so that I can call its method in order to add component into the child component. Here is how I am doing it:
test.ts
import {Component, DynamicComponentLoader,ElementRef,AfterViewInit} from 'angular2/core';
import {bootstrap} from 'angular2/platform/browser';
import {ChildComponent} from './child_test.ts';
#Component({
selector: 'app',
template: '<div #element></div>',
providers: [ChildComponent,ElementRef]
})
class AppComponent implements AfterViewInit{
constructor(private _loader: DynamicComponentLoader, private _elementRef: ElementRef, private _childComponent: ChildComponent) {
}
ngAfterViewInit(){
this._loader.loadIntoLocation(ChildComponent,this._elementRef,'element');
this._childComponent.addElement();
}
}
bootstrap(AppComponent);
child_test.ts
import {Component, ElementRef,DynamicComponentLoader} from 'angular2/core';
#Component({
selector: 'element',
template: '<div>Test Element</div>'
})
export class MyElement{}
#Component({
selector: "child-component",
template: "<div>Child Component</div>"
})
export class ChildComponent{
constructor(public _elementRef: ElementRef,private _loader: DynamicComponentLoader){}
public addElement(){
this._loader.loadNextToLocation(MyElement,this._elementRef);
}
};
When execute the test.ts I get the following error:
TypeError: Cannot read property 'getViewContainerRef' of undefined
Not sure if I fully understand your question but I think this is what you want
ngAfterViewInit(){
this._loader.loadIntoLocation(ChildComponent,this._elementRef,'element')
.then(childComponent => childComponent.instance.addElement());
}
this._loader.loadIntoLocation(...) returns a Promise that completes with a reference to the added element.

Categories

Resources