Angular - Cannot get parent component data - javascript

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.

Related

How to handle undefined when passing data with Input in Angular?

A component colled with passing value with Input():
<app-available-modal [good]="'test'"></app-available-modal>
The component looks like:
#Component({
selector: 'app-available-modal',
templateUrl: './available-modal.component.html',
styleUrls: ['./available-modal.component.scss']
})
export class AvailableModalComponent implements OnInit {
#Input() good: TyreGoodModelTemp;
constructor() {
console.log(this.good);
}
ngOnInit(): void {
console.log(this.good);
}
}
I expect "test" and "test" output in the console.
And the console.log from ngOnInit() prints "test". But console.log from constructor() prints "undefined".
Why does it happen and how do I handle it?
In Angular the constructor function of a component class is being used for service injection only. Everything that is related to rendering and #Input resolving has to be handled in Angular's lifecycle hooks like ngOnInit and ngOnChanges
Don't try to access #Input()s in the constructor, do it in the ngOnInit life-cycle hook. The constructor has almost nothing to do with the Angular application life-cycle.
For more information : #8015757

Manipulating a reusable component based on input

I have created a reusable component and using it twice inside a component. But I need two buttons, that can manipulate the component individually.
In my case the button for component1 should not update both the instance of the component.
I think I'm doing something wrong by design, but any suggestion will help me.
Stackblitz
Reusable Component:-
import { Component, OnInit,Input } from '#angular/core';
import { AppService } from '../app.service';
#Component({
selector: 'app-reusable',
templateUrl: './reusable.component.html',
styleUrls: ['./reusable.component.css']
})
export class ReusableComponent implements OnInit {
#Input() items:any;
constructor(
private service:AppService
) { }
ngOnInit() {
this.service.addFruit.subscribe(()=>{
this.items.unshift({fruit:'Blackberry'});
});
}
}
Usage:-
<button type="button" (click)="Add()">Add Component1</button>
<app-reusable [items]="fruitList1"></app-reusable>
<hr/>
<button type="button" (click)="Add()">Add Component2</button>
<app-reusable [items]="fruitList2"></app-reusable>
I want to update only one instance of reusable component at once.
Either instance 1 or 2.
You have to let the service know which component you are calling from.
Try the changes I made in the demo.
app.component.html
<button type="button" (click)="Add(1)">Add Component1</button>
<app-reusable [items]="fruitList1" [componentNumber]="1"></app-reusable>
app.component.ts:
Add(componentNumber:number){
this.service.addFruit.next(componentNumber);
}
reusable.component.ts:
#Input() componentNumber: number;
ngOnInit() {
this.service.addFruit.subscribe((x) => {
if (x == this.componentNumber)
this.items.unshift({ fruit: 'Blackberry' });
});
}
Working Stackbiltz
More cleaner approch would be simply pass the component instance and call related method so create a method in your reusable component something like
addFruit(){
this.items.unshift({fruit:'Blackberry'});
}
Modify your add method to get component as instance and call this method
Add(instance:ReusableComponent){
instance.addFruit();
}
Then add hash to seprate each instance and pass instance in method
<button type="button" (click)="Add(instance1)">Add Component1</button>
<app-reusable [items]="fruitList1" #instance1></app-reusable>
Working demo
Each instance of ReusableComponent is subscribed to the Subject addFruit. Clicking on the button will update Subject value which will trigger all subscriptions.
In order to avoid this, you will need to add a filter in a subscription which ignores values from other components by adding some when doing this.service.addFruit.next();. You can do filtering with RXJS filter operator. https://rxjs-dev.firebaseapp.com/api/operators/filter
Another idea is to create a subscription for each component in service and save them in some map/object in service. When a component requests a subscription from service it would add an entry to map which would be subjectId: new Subject(). You would return that new subject for the component to subscribe. Instead of doing next() directly you would call service method addNewFruit(subjectId: string, newFruit: string): void.
Map would be:
{
'firstId': Subject,
'secondId': Subject,
}
The most simple idea for this case is to use ViewChild and call method addFruit from the parent component.
Instead of having subscription of APP service in the reusable component , on click of button you should modified the input provided to your components. If you update the fruitList1 or fruitList2 at a time then it would not update the another instance of the component.

Unable to get hold of child DOM element

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

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');
}
}

Observable isnt triggering

I have two components that should be connected via Observable().
In the first i am declaring
#Injectable()
export class One{
private changeConfirmed = new Subject<boolean>();
changeConfirmed$ = this.changeConfirmed.asObservable();
public init$: EventEmitter<boolean>;
registerChangeConfirmed(category:boolean){
alert('sending')
this.changeConfirmed.next(category);
}
onMsg(){
this.registerChangeConfirmed(true);
}
}
onMsg is event bound to template
and in the second one
Import { One } from './pat/to/it'
#Component({
providers:[One]
})
export class two{
constructor( private childClass : One ){
this.childClass.changeConfirmed$.subscribe( x => {alert(x)})
}
}
However the event does not get emitted.
But when i emit event in class two instead of class one = i include
this.childClass.registerChangeConfirmed(true)
in class two the event gets triggered. Why isn't it working when i invoke it from class one?
Don't provide the service One on the component. This way each component instance will get its own One instance. If you provide it at a common parent (AppComponent) then they will get a shared instance.
Angular2 DI maintains an instance (singleton) per provider.

Categories

Resources