Create new Child Component within a Child Component - html

I have an issue about creating new Components with the resolveComponentFactory. First there is a startComponent (parent component) and from this component there are several buttons and every btn creates an new child Component. For example now I create a "childComponent" and this also works. But now I want to create an new childComponent within the childComponent and this new component shall have the startComponent as parent component, not the childComponent itself. So I Need a way to call the addComponent() method from the startComponent with my childComponent.
Here is the way I'm doing it at the moment:
startComponent.ts:
import {
Component, OnInit, ViewChild,
ComponentFactoryResolver,
ViewContainerRef
} from '#angular/core';
Import { childComponent } from '../childComponent/child.component';
import { DataService } from '../data.service';
#Component({
selector: 'app-start',
templateUrl: './start.component.html',
styleUrls: ['./start.component.css']
})
export class startComponent implements OnInit {
#ViewChild('parent', { read: ViewContainerRef }) container: ViewContainerRef;
constructor(private dataService: DataService, private componentFactoryResolver: ComponentFactoryResolver){}
ngOnInit(){}
addComponent(){
let componentFactory = this.componentFactoryResolver.resolveComponentFactory(childComponent);
let component = this.container.createComponent(componentFactory);
// next Line i save the reference of the "childComponent" to a Service
// so the "childComponent" can get it and destroy himself if wanted
this.createComponentService.setReference(component, type);
}
}
startComponent.html
<div>
<span matTooltip="Select"><button (click)="addComponent()" class="btn">
<img class="img" src="./assets/StartIcons/childCreate-icon.png" alt="not found"> </button></span>
</div>
<div #parent></div>
It does work if I want to create a childComponent with my childComponent, but the startComponent is not the parent component then.
I hope you understand my Problem, else I can try to explain it again.

But now I want to create an new childComponent within the
childComponent and this new component shall have the startComponent as
parent component, not the childComponent itself. So I Need a way to
call the addComponent() method from the startComponent with my
childComponent.
So, in childComponent you should have reference to parentComponent(StartComponent). You can get it by injecting to new added childComponent:
childComponent:
constructor(private parentComp: StartComponent){
}
As you have reference to it, you get access to properties, methods of parent and within childComponent can call addComponent() easily like:
parentComp.addComponent();
Update
Interesting, dynamically created component doesn't have parent component in injector. So, it can't inject parent StartComponent.
Another solution
Set child component's parent property with StartComponet:
ngOnInit() {
this.comps.clear();
let aComponentFactory =
this.componentFactoryResolver.resolveComponentFactory(this.compArr[0]);
let aComponentRef = this.comps.createComponent(aComponentFactory);
(<AComponent>aComponentRef.instance).name = 'A name';
(<AComponent>aComponentRef.instance).parent = this;
}
StackBlitz Demo. Look at the console
Manually inject parent component in child:
constructor(public injector: Injector ) {
console.log('child injector', injector);
this.parent = injector.get(AppComponent);
}
ngOnInit() {
console.log('parent is here', this.parent);
this.parent.test();
}
StackBlitz Demo. Look at the console

Related

Can't send ngfor index from HTML to child component

I tried to let my child component to read parents HTML's ngfor value to display the child page. But the child component seems didn't get the index number from the parent page.
This is my Parents: app.component.html:
<ul *ngFor="let item of formDoc.components | keyvalue: valueAscOrder; let i = index ">
<li *ngIf="item.value['type'] == 'columns'">
<mat-card [style.backgroundColor]="'white'" [style.border]="lightgrey" [style.width]="'50%'">
{{item.value["label"]}}
<app-columns [indexnumber]="i"></app-columns>
</mat-card>
</li>
</ul>
And this is the child: Columns.component.ts
import { FormBuilderComponent } from 'angular-formio';
import { Injectable } from '#angular/core';
import { Component, Input } from '#angular/core';
//import { ColumnComponent } from 'src/column/Column.component';
import { AppComponent } from '../app.component'
#Injectable()
#Component({
selector: 'app-columns',
templateUrl: './Columns.component.html',
styleUrls: ['./Columns.component.scss'],
})
export class ColumnsComponent {
#Input() columnsData: object[];
public saveData: object[];
#Input() public indexnumber: string;
constructor(public appComponent: AppComponent){
if (this.indexnumber) {
this.columnsData = appComponent.Temp2[this.indexnumber]["component"];
}
}
}
The value of indexnumber is always undefined. I'm wondering how to make index number in Columns.component.ts get the ngfor 'i' value?
Fist of all, the index of an array is a number, not a string. So
#Input() public indexnumber: string;
should be
#Input() public indexnumber: number;
Second: in order for angular to be able to set the indexnumber of your component, your component needs to exist. So it must have been constructed before. So, at the time the constructor is invoked, the input can't possibly have been set yet. So trying to access the value of an input in the constructor can't possibly give you the value passed by the parent.
Use ngOnInit or ngOnChanges. That's the methods that are called when the inputs are set for the first time/changed.

Reload some directives after generated html code

I'm trying to create some html dynamically with angular framework from ngOnInit function. I want to add events thanks to directives on this generated html. The fact is that all directives are loaded before html generation and I didn't succeed to reload one of these.
I'm generating html in my components with Renderer2:
import { Component, AfterViewInit, ElementRef, ViewChild, Renderer2 } from '#angular/core';
import { AngularDraggableDirective } from 'angular2-draggable';
#Component({
selector: 'app-test',
templateUrl: './test.component.html',
styleUrls: ['./test.component.css']
})
export class TestComponent {
#ViewChild("myDiv", {static: false}) divView: ElementRef;
private myDiv: ElementRef;
constructor(private el: ElementRef, private renderer: Renderer2) {
this.myDiv = el;
}
ngOnInit(){
let div = this.renderer.createElement('div');
let text = this.renderer.createText('Generated draggable');
this.renderer.setAttribute(div, 'ngdraggable', '');
this.renderer.appendChild(div, text);
this.renderer.appendChild(this.myDiv.nativeElement, div);
}
}
And associate html is really simple:
<div #myDiv>
</div>
<div ngDraggable>Not generated draggable</div>
The first div (generated one) isn't draggable.
This second (initial one) is draggable.
Is there any way to reload my AngularDraggableDirective to set events on my generated div?
Thanks
You should write the code in ngafterviewinit method.

Component Interaction #Input

I would like a component to send input to another component. Below is the code .ts and .html. of the two components.
Now the problem is that the html page of the parent component also shows the html part of the child component ... I want the component to pass only one string to the child component
Parent.ts
import ...
#Component({
selector: 'app-parent',
templateUrl: './parent.html',
styleUrls: ['./parent.css']
})
export class ParentComponent implements OnInit {
sostegno : string;
constructor() { }
ngOnInit() { }
avvia1() {
this.sostegno = "xxx";
this.router.navigate(['./xxx'], { relativeTo: this.route });
}
avvia2()
this.sostegno = "yyy";
this.router.navigate(['./yyy'], { relativeTo: this.route });
}
}
Parent.html
<div>
...
</div>
<app-child [sostegno]="sostegno"></app-child>
Child.ts
import ...
#Component({
selector: 'app-child',
templateUrl: './child.html',
styleUrls: ['./child.css']
})
export class ChildComponent implements OnInit {
#Input() sostegno : string;
constructor() { }
ngOnInit() {
console.log(this.sostegno);
}
}
There are some changes which you need to make because looking at the code which your currently have it seems incomplete.
You are using this.router without injecting the Router class in your constructor.
You are using this.route without injecting the ActivatedRoute class in your constructor.
To test that your parent > child interaction is working you can remove your param and instead place a test for the html
<app-child [sostegno]="'Test'"></app-child>
This should work for your ngOnInit function which is inside of your child component. If this works all you need to do now is either initialize sostegno in your parent component else your console log inside your child component will not reflect the changes when you call avvia1 or avvia2 inside of your parent class.
Hope this helps!

Angular html nesting

Let's say I have in some upper level class some angular template code that looks like this
<outer-component>
<a></a>
</outer-component>
Where <a> can be any module that extends a certain interface defined elsewhere, is there a way for <outer-component> be able to take <a> or whatever is placed inside the tags and communicate with it specifically be able to listen to functions or bind to variables in a way that is as succinct as the snippet above?
If you want to share data between a parent and a child (hierarchical relationship) you can use EventEmitter to allow the parent to get data from the child.
In the child component:
import { Component, Input, Output, EventEmitter } from 'angular/core';
#Component({
selector: 'app-child',
template: `
<h3>Child</h3>
Say {{message}}
<button (click)="sendMessage()"></button>
ยด,
styleUrls: ['pathToStyles.css']
})
export class ChildComponent {
message: string = "Hello world";
#Output() messageEvent = new EventEmitter<string>();
constructor() {}
sendMessage() {
this.messageEvent.emit(this.message);
}
}
In the parent component:
import { Component } from '#angular/core';
#Component({
selector: 'app-parent',
template: `
Message: {{message}}
<app-child (messageEvent)="receiveMessage($event)"></app-child>
`,
styleUrls: ['pathToStyles.css']
})
export class ParentComponent {
constructor() { }
message:string;
receiveMessage($event) {
this.message = $event
}
}

Passing Parameter to Angular2 Component

I'm Learning Angular2 so be gentle... I have a basic Component which has a string array. I want to pass an integer to this component and have it return the string found at the index of that parameter.
E.g. myComponent[number]=1 returns string "second element".
My code so far is this:
import { Component } from '#angular/core';
#Component({
selector: 'myComponent',
template:
`<h1>Returned Value {{returnedString}}</h1>,`,
inputs:['number']
})
export class MyComponent {
myStringArray: string[];
returnedString: string;
constructor(){
this.myStringArray = ['First','Second','Third','Forth','Fifth','Sixth'];
this.returnedString = 'number'+this.myStringArray['number'.valueOf()];
}
}
I am calling this component as follows
<myComponent [number]=1></myComponent>
I print the value returned to the screen and get 'undefined'.
Any ideas folks?
Since you want to bind to a custom property import Input and OnChanges from core and then implement as Input to create your custom property. The OnChanges just ensures your value gets updated when the bound value changes.
Remove the inputs key from your component decorator
import { Component, Input, OnChanges } from '#angular/core';
#Component({
selector: 'myComponent',
template:
`<h1>Returned Value {{returnedString}}</h1>,`
})
export class MyComponent implements OnChanges {
myStringArray: string[];
returnedString: string;
#Input() inputNumber: number;
constructor(){
this.myStringArray = ['First','Second','Third','Forth','Fifth','Sixth'];
this.returnedString = 'number'+this.myStringArray[Number(this.inputNumber)];
}
ngOnChanges() {
this.returnedString = 'number'+this.myStringArray[Number(this.inputNumber)];
}
}
Update your code usage to the following
<myComponent [inputNumber]="1"></myComponent>
Here is a sample plunker.
https://plnkr.co/edit/S074zoVJ3ktQDKkcQgxe?p=preview
I had tough time to send string inputs. here is the correct way,
<myComponent [stringvar]="'string value'"></myComponent>
"string value" will not work. angular expecting object or number inside double quotes. string should be inside single quotes within double quotes "'string'"
You need to create a number variable in your component too that will hold the value.
import {Component, OnInit} from '#angular/core';
#Component({
selector: 'myComponent',
template:
`<h1>Returned Value {{returnedString}}</h1>,`,
inputs:['myNum']
})
export class MyComponent implements OnInit {
myStringArray: string[] = ['First','Second','Third','Forth','Fifth','Sixth'];
returnedString: string;
public myNum: number; <= here is your variable
ngOnInit() {
//you can use this.myNum anywhere now like this
this.returnedString = 'number '+ this.myStringArray[this.myNum];
}
constructor(){
}
}
You may have to change the name of your input because number is a keyword.
Another Note: You have to use OnInit instead of constructor to start using your inputs. ngOnInit is an Angular2 lifecycle method that is called by Angular when it's done building the component and evaluated the bindings
Here is another alternative. It demonstrates how to use a getter for returnedString. Less code needed than with ngOnChanges.
import { Component, Input } from '#angular/core';
#Component({
selector: 'my-cmp',
template: `
<p>returnedString = {{ returnedString }}</p>
`
})
export class MyComponent {
myStringArray: string[] = ['First','Second','Third','Forth','Fifth','Sixth'];
#Input() stringIndex: number;
get returnedString(): string {
if (this.stringIndex !== undefined) {
return this.myStringArray[this.stringIndex];
}
}
}
It's quite simple. See this demo. Let's say you have two components parent and child. And you want to pass a variable to child and modify it there, say views.
On parent template:
<child [(views)]="views"></child>
On child component:
#Input() views: number;
#Output() viewsChange = new EventEmitter<number>();
// Bind this function to button click or some events:
updateViews() {
this.views++;
this.viewsChange.emit(this.views); // Emit the value to parent:
}
Detail explanation:
When you bind [(views)] in parent, it is acting as:
<child
[views]="views"
(viewsChange)="views = $event">
</child>
So, it is listening to viewsChange output function. Whenever, you do viewsChange.emit, the parent views get updated.
Gotcha: The output function should be precisely named $var + "Change". If you chose to name something else you will have to do:
<child
[views]="views"
(customEmitterFunction)="views = $event">
</child>
In order to pass data from the child component to the father component you shuold set an Output parameter, to trigger the signal your component should implements the OnChanges interface, your component should be like this
import { Component, Input,Output,EventEmitter,OnChanges,SimpleChanges } from '#angular/core';
#Component({
selector: 'my-cmp',
template: `
<p>returnedString = {{ returnedString }}</p>
`
})
export class MyComponent implements OnChanges {
myStringArray: string[] = ['First','Second','Third','Forth','Fifth','Sixth'];
#Input() stringIndex: number;
#Output() cardinalNumber:EventEmitter<string> = new EventEmitter<string>();// you define an Output, this will emit a signal that will received from the father Component
ngOnChanges(changes:SimpleChanges) {
// when the input changes we emit a signal
this.cardinalNumber.emit(this.myStringArray[this.stringIndex]);
}
get returnedString(): string {
if (this.stringIndex !== undefined) {
return this.myStringArray[this.stringIndex];
}
}
}
then in the template of the father component you should insert :
<my-cmp [stringIndex]=the parameter in father Component's controller
(cardinalNumber)="method2beinvoked($event)">
</my-cmp>
method2beInvoked is the method in the father component that handles the message;
or you could do like this:
<my-cmp [stringIndex]=the parameter in father Component's controller
(cardinalNumber)="parameter=$event")>
</my-cmp
where parameter is a parameter in the father's component controller