So I know that I can pass data from a parent to a child using #Input like so..
<app-component [data]="data"></app-component>
#Input() data: any;
now I have a component that gets dynamically added like so..
HTML
<app-header></app-header>
<div #entry [vimeoId]="vimeoId"></div>
<router-outlet></router-outlet>
APP.COMPONENT.TS
ngOnInit() {
this._videoService.videoSource.subscribe((result) => {
if (result !== '') {
this.vimeoId = result;
this.createComponent();
}
});
}
createComponent() {
const factory = this._resolver.resolveComponentFactory(VideoComponent);
const component = this.entry.createComponent(factory);
}
now this isn't working I'm getting this error
[Angular] Can't bind to 'vimeoId' since it isn't a known property of 'div'.
now I know why I'm getting this error, its because div doesn't have the #Input so.. my question is how can I pass down data to a dynamically created child component??
Thanks
Related
I have two sibling components inside a parent component like below
<parentComponent>
<sibling1></sibling1>
<sibling2></sibling2>
</parentComponent>
Am emitting data from sibling 2 to parent. Then am passing it as Input from parent to sibling 1. But as the sibling 1 gets initialized before sibling 2 am unable to get the data on sibling 1. How to get the data on sibling 1 with the same setup.
When you receive data in parent component you need run a callback function to update sibling1 data. In order to run a callback in parent you can do something like this.
SIBLING2:
class Sibling2 {
#Output() private onChange: EventEmitter<string> = new EventEmitter<string>();
ngOnInit () {
this.onChange.emit("hello parent")
}
}
PARENT:
class Parent {
private parentData: string = null;
ngOnInit () {
this.onChange.emit("hello parent")
}
onSibling2Change(data) {
this.parentData = data; //this will update sibling1 data
}
}
HTML:
<parentComponent>
<sibling1 [data]="parentData"></sibling1>
<sibling2 (onChange)="onSibling2Change($event)"></sibling2>
</parentComponent>
I'm new to Angular and I have this problem here:
I want to pass the data that I have from this service+component to another component.
I have a service doing this:
getRecs() {
let recsSub = new Subject<any>();
let recsSubObservable = from(recsSub);
this.socket.on('recs', (recsStatus: any) => {
recsSub.next(recsStatus);
});
return recsSubObservable;
}
Then I have this parent component
private misRecs = null;
snackBarShown = false;
constructor (private appSocketIoService: AppSocketIoService, private snackbar: MatSnackBar) {
let recsObservable = this.appSocketIoService.getRecommendations();
recsObservable.subscribe((recsStatus: any) => {
console.log(recsStatus);
this.misRecs = {};
for(let property in recsStatus.output){
if (recsStatus.output[property]) {
this.misRecs[property] = recsStatus.output[property];
}
};
this.snackbar.openFromComponent (CustomSnackBar, { duration: 5000, });
});
}
What I need is to populate a list in another component with the values obtained from recsStatus but I don't know how to do it.
Thank you all for your help.
If the component is a child component of your component (parent) you describe in the listing you can use the #Input() annotation.
#Component({
selector: 'child-comp',
template: `
<div>
{{ localRecStatus | json }}
</div>
`
})
export class ChildComponent {
#Input()
localRecStatus: [];
}
Now you can use the component in HTML file of your parent component like this:
<child-comp [localRecStatus]="recStatus"></child-comp>
With this, you can use recStatus in your child component. However, recStatus must be a public variable of the parent component. With this technique, you can pass any data to child components. There is also an #Output() annotation you can use in combination with an EventEmitter to send data to the parent component. If the component is not a child, probably a better way is to communicate via a Service between both components.
Following what is documented here: Dynamic Component Loader.
I want to know how is it possible to handle the data inside this HeroJobAdComponent class:
import { Component, Input } from '#angular/core';
import { AdComponent } from './ad.component';
#Component({
template: `
<div class="job-ad">
<h4>{{data.headline}}</h4>
{{data.body}}
</div>
`
})
export class HeroJobAdComponent implements AdComponent {
#Input() data: any;
}
As you can see, data is the object holding the data received. I want to be able to define a constructor for my HeroJobAdComponent class but if I do, the object data is undefined inside my constructor. I tried using ngOnChange instead which supposedly executes once input is changed from undefined to defined but it also did not execute at all.
Can someone please explain first why is the object undefined even though the data is defined in my main component calling it, and what's the workaround for this issue?
This is the constructor I am using:
constructor()
{
this.values = this.data.values;
this.spec_name = this.data.spec_name;
}
if you want to use any operation when you receive data in your component , you can use setter
export class HeroJobAdComponent implements AdComponent {
_data;
#Input() set data (data: any){
//operation on data goes here
this._data=data
};
get data() {
return this._data;
}
}
How to handle/provide #Input and #Output properties for dynamically created Components in Angular 2?
The idea is to dynamically create (in this case) the SubComponent when the createSub method is called. Forks fine, but how do I provide data for the #Input properties in the SubComponent. Also, how to handle/subscribe to the #Output events the SubComponent provides?
Example:
(Both components are in the same NgModule)
AppComponent
#Component({
selector: 'app-root'
})
export class AppComponent {
someData: 'asdfasf'
constructor(private resolver: ComponentFactoryResolver, private location: ViewContainerRef) { }
createSub() {
const factory = this.resolver.resolveComponentFactory(SubComponent);
const ref = this.location.createComponent(factory, this.location.length, this.location.parentInjector, []);
ref.changeDetectorRef.detectChanges();
return ref;
}
onClick() {
// do something
}
}
SubComponent
#Component({
selector: 'app-sub'
})
export class SubComponent {
#Input('data') someData: string;
#Output('onClick') onClick = new EventEmitter();
}
You can easily bind it when you create the component:
createSub() {
const factory = this.resolver.resolveComponentFactory(SubComponent);
const ref = this.location.createComponent(factory, this.location.length, this.location.parentInjector, []);
ref.someData = { data: '123' }; // send data to input
ref.onClick.subscribe( // subscribe to event emitter
(event: any) => {
console.log('click');
}
)
ref.changeDetectorRef.detectChanges();
return ref;
}
Sending data is really straigthforward, just do ref.someData = data where data is the data you wish to send.
Getting data from output is also very easy, since it's an EventEmitter you can simply subscribe to it and the clojure you pass in will execute whenever you emit() a value from the component.
I found the following code to generate components on the fly from a string (angular2 generate component from just a string) and created a compileBoundHtml directive from it that passes along input data (doesn't handle outputs but I think the same strategy would apply so you could modify this):
#Directive({selector: '[compileBoundHtml]', exportAs: 'compileBoundHtmlDirective'})
export class CompileBoundHtmlDirective {
// input must be same as selector so it can be named as property on the DOM element it's on
#Input() compileBoundHtml: string;
#Input() inputs?: {[x: string]: any};
// keep reference to temp component (created below) so it can be garbage collected
protected cmpRef: ComponentRef<any>;
constructor( private vc: ViewContainerRef,
private compiler: Compiler,
private injector: Injector,
private m: NgModuleRef<any>) {
this.cmpRef = undefined;
}
/**
* Compile new temporary component using input string as template,
* and then insert adjacently into directive's viewContainerRef
*/
ngOnChanges() {
class TmpClass {
[x: string]: any;
}
// create component and module temps
const tmpCmp = Component({template: this.compileBoundHtml})(TmpClass);
// note: switch to using annotations here so coverage sees this function
#NgModule({imports: [/*your modules that have directives/components on them need to be passed here, potential for circular references unfortunately*/], declarations: [tmpCmp]})
class TmpModule {};
this.compiler.compileModuleAndAllComponentsAsync(TmpModule)
.then((factories) => {
// create and insert component (from the only compiled component factory) into the container view
const f = factories.componentFactories[0];
this.cmpRef = f.create(this.injector, [], null, this.m);
Object.assign(this.cmpRef.instance, this.inputs);
this.vc.insert(this.cmpRef.hostView);
});
}
/**
* Destroy temporary component when directive is destroyed
*/
ngOnDestroy() {
if (this.cmpRef) {
this.cmpRef.destroy();
}
}
}
The important modification is in the addition of:
Object.assign(this.cmpRef.instance, this.inputs);
Basically, it copies the values you want to be on the new component into the tmp component class so that they can be used in the generated components.
It would be used like:
<div [compileBoundHtml]="someContentThatHasComponentHtmlInIt" [inputs]="{anInput: anInputValue}"></div>
Hopefully this saves someone the massive amount of Googling I had to do.
createSub() {
const factory = this.resolver.resolveComponentFactory(SubComponent);
const ref = this.location.createComponent(factory, this.location.length,
ref.instance.model = {Which you like to send}
ref.instance.outPut = (data) =>{ //will get called from from SubComponent}
this.location.parentInjector, []);
ref.changeDetectorRef.detectChanges();
return ref;
}
SubComponent{
public model;
public outPut = <any>{};
constructor(){ console.log("Your input will be seen here",this.model) }
sendDataOnClick(){
this.outPut(inputData)
}
}
If you know the type of the component you want to add i think you can use another approach.
In your app root component html:
<div *ngIf="functionHasCalled">
<app-sub [data]="dataInput" (onClick)="onSubComponentClick()"></app-sub>
</div>
In your app root component typescript:
private functionHasCalled:boolean = false;
private dataInput:string;
onClick(){
//And you can initialize the input property also if you need
this.dataInput = 'asfsdfasdf';
this.functionHasCalled = true;
}
onSubComponentClick(){
}
Providing data for #Input is very easy. You have named your component app-sub and it has a #Input property named data. Providing this data can be done by doing this:
<app-sub [data]="whateverdatayouwant"></app-sub>
In my parent component, I want to create a child component with a unique ID associated with it, and I want to pass that unique ID into the child component, so the child component can put that ID on its template.
Parent template:
<ckeditor [ckEditorInstanceID]="someUniqueID"> </ckeditor>
Here is the child component:
import { Component, Input } from '#angular/core'
var loadScript = require('scriptjs');
declare var CKEDITOR;
#Component({
selector: 'ckeditor',
template: `<div [id]="ckEditorInstanceID">This will be my editor</div>`
})
export class CKEditor {
#Input() ckEditorInstanceID: string;
constructor() {
console.log(this.ckEditorInstanceID)
}
ngOnInit() {
}
ngAfterViewInit() {
loadScript('//cdn.ckeditor.com/4.5.11/standard/ckeditor.js', function() {
CKEDITOR.replace(this.ckEditorInstanceID);
console.info('CKEditor loaded async')
});
}
}
What am I missing? I can't seem to get the child component to receive the value of "someUniqueID". it is always undefined.
UPDATE: I was able to get the child component to receive the value "someUniqueID. Code udpated above. However, I cannot reference the #Input property by calling this.ckEditorInstanceID because this is undefined.
How do I reference the property I brought in via #Input?
Don't name inputs id. That's conflicting with the id attribute of the HTMLElement.
The trick was to use an arrow function like #David Bulte mentioned.
loadScript('//cdn.ckeditor.com/4.5.11/standard/ckeditor.js', () => {
CKEDITOR.replace(this.ckEditorInstanceID);
console.info('CKEditor loaded async')
});
For some reason the arrow function can access this.ckEditorInstanceID, but a regular function() {} cannot access this.ckEditorInstanceID. I don't know why, maybe someone can enlighten me to the reasoning for this.
In addition, I had to change my markup like this:
<ckeditor [ckEditorInstanceID]="'editor1'"> </ckeditor>
<ckeditor [ckEditorInstanceID]="'editor2'"> </ckeditor>
And set the #Input property to the name inside the [] which is ckEditorInstanceID , and also the template source should be the property name ckEditorInstanceID, like [id]="ckEditorInstanceID" .
Full working child component that receives the ID from the parent html selector:
import { Component, Input } from '#angular/core'
var loadScript = require('scriptjs');
declare var CKEDITOR;
#Component({
selector: 'ckeditor',
template: `<div [id]="ckEditorInstanceID">This will be my editor</div>`
})
export class CKEditor {
#Input() ckEditorInstanceID: string;
constructor() {}
ngAfterViewInit() {
loadScript('//cdn.ckeditor.com/4.5.11/standard/ckeditor.js', () => {
CKEDITOR.replace(this.ckEditorInstanceID);
console.info('CKEditor loaded async')
});
}
}