Unable to pass props using #Input() in angular - javascript

I'm working with very basic angular project. I want to pass props between two angular components. The properties of a component are working fine when directly rendering them in the relevant HTML file. When passing it to another component it shows error.
HTML file of the parent component
<app-todo-item *ngFor="let todo of todos" [todo]="todo">
</app-todo-item>
The child component:
import { Component, OnInit,Input } from '#angular/core';
import { Todo } from 'src/app/models/Todo';
#Component({
selector: 'app-todo-item',
templateUrl: './todo-item.component.html',
styleUrls: ['./todo-item.component.css']
})
export class TodoItemComponent implements OnInit {
#Input() todo:Todo;
constructor() {
}
ngOnInit(): void {
}
}
The error I got:
Error: src/app/components/todo-item/todo-item.component.ts:11:12 - error TS2564: Property 'todo' has no initializer and is not definitely assigned in the constructor.

That comes from the new Ts compiler. Can fix with:
#Input() todo?: Todo;
What tells compiler this property is optional. Or:
#Input() todo!: Todo;
What tells compiler this property will not be unassigned

Related

How to pass a class component value to custom decorator in Angular?

When I tired to access an angular component value inside a custom decorator, I am getting an error stating this is not defined. From online documentation, I have found out that decorator function will get called first even before the class initialization. Because of this decorators will not have access to instance variables.
Note: I have tried creating a static variable and tried to access the static variable inside the custom decorator. Only problem with this approach is customDecorator runs first before assigning the value to static variable. Hence the AccountId is undefined when i tried to access inside customDecorator.
How I can access this AccountId inside the custom decorator? Any help would be greatly appreciated.
import { Component,Input, OnInit } from '#angular/core';
import {CustomDecorator } from '#sharedDecorators';
#Component({
selector: 'app-account-display',
templateUrl: './accounts.component.html',
styleUrls: ['./account s.component.scss']
})
export class AccountsComponent implements OnInit {
#Input() AccountId: string
constructor() {
}
#CustomDecorator(this.AccountId)
ngOnInit() {
}
...........................
}
hu Ane!
if AccountId is passed statically from parent you can simply:
ngOnInit() {
console.log(this.AccountId)
}
if AccountId is asynchronous you can do something like this
#Input() get AccountId(value: string) {
if(value) {
this.accountID$.next(value)
}
}
accountId$ = new ReplaySubject<any>()
ngOnInit() {
this.accountId$.subscribe((x) => console.log(x) ) <-- remember to unsubscribe
}
here you can find more info

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

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 to make child component detects object (#Input()) from parent component has changed in Angular

I have an object from parent component also received in a child component similar to this:
{
attribute: 'aaaa',
attribute2: [
{
value
},
{
value
},
{
value
},
]
}
This object is an #Input from a parent component. When I make changes to the objects inside the attribute2 array, I would like the child component detect that changes were made and then gets updated. As this is an object, I could'nt make it work, so I clone the entire object (this.objet = _.cloneDeep(this.object) in the parent component so then the child component detects that changes happened.
Is there any other way of doing this that does not clone the entire object? Thanks in advance
EDIT:
Child Component
export class ChildComponent implements OnInit, OnChanges {
#Input() public object: any;
}
html
<div>
<span>{{object.attribute}}</span>
<div *ngFor="let items of object.attribute2">{{item.value}}</div>
</div>
Parent Component
export class ParentComponent implements OnInit {
public object: any;
updateObject() {
this.object.attribute2[1] = 'Changed value';
this.object = _.cloneDeep(this.object);
}
}
html
<div>
<child-component [object]="object"></child-component>
</div>
An efficient way is to use EventEmitter and service communication to
trigger changes in the child component.
On way as mentioned by #Tony is to use ngOnChanges(). It is a good shortcut for detecting bounded properties change but as you add more and more bindings, using this hook will affect you application in the long run because it will run every time any of the bound property changes whether or not you desire it all the calls.
So for Service based communication, I've created an example on
Stackblitz:
https://stackblitz.com/edit/angular-fgut7t
Gist: https://gist.github.com/stupidly-logical/a34e272156b498513505127967aec851
In this example, I am binding an Array to the child component using #Input() an on addition of new data, the array is updated by the parent and the latest value is passed on the service which then emits this value. The child component subscribes to this value and the relevant code is executed.
The Service:
import { Injectable, EventEmitter } from '#angular/core';
#Injectable({
providedIn: "root"
})
export class DataService {
dataUpdated:EventEmitter<any> = new EventEmitter();
constructor() { }
setLatestData(data) {
this.dataUpdated.emit(data);
}
}
Child Component TS
import { Component, OnInit, Input } from '#angular/core';
import { DataService } from '../data-service.service';
#Component({
selector: 'app-child',
templateUrl: './child.component.html',
styleUrls: ['./child.component.css']
})
export class ChildComponent implements OnInit {
#Input() allData: [];
latestData: any;
constructor(private dataService: DataService) { }
ngOnInit() {
this.dataService.dataUpdated.subscribe((data) => {
this.latestData = data;
});
}
}
Child Component HTML
<p>
Latest Data: {{ latestData }}
</p>
<h3>List:</h3>
<li *ngFor="let data of allData">
{{ data }}
</li>
Parent Component TS
import { Component } from '#angular/core';
import { DataService } from './data-service.service'
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
name = 'Angular';
dataArr = [];
constructor(private dataService: DataService){}
onAddTimestamp() {
let timestamp = new Date();
this.dataArr.push(timestamp);
this.dataService.setLatestData(timestamp);
}
}
Parent Component HTML
<hello name="{{ name }}"></hello>
<p>
Start editing to see some magic happen :)
</p>
<button
(click)="onAddTimestamp()"
>
Add Timestamp
</button>
<app-child
[allData] = "dataArr"
></app-child>
Use the ngOnChanges() lifecycle method in your component.
ngOnChanges is called right after the data-bound properties have been
checked and before view and content children are checked if at least
one of them has changed.
Some like this
#Input() object: string;
ngOnChanges(changes: SimpleChanges) {
console.log(changes.object.currentValue);
// You can also use object.previousValue and
// object.firstChange for comparing old and new values
}

Angular 2, How to pass options from parent component to child component?

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.

Categories

Resources