I have a component that has a dynamic view, which I am implementing using an ng-template.
The following is that component, lets say parent.component.ts.
#Component({
...
template: '<div><ng-template child-area></ng-template></div>',
...
})
export class ParentComponent implements OnInit {
#ViewChild(ChildAreaDirective) childArea: ChildAreaDirective;
loadComponent (details) {
let _vcf = this.childArea.viewContainerRef;
let _cf= resolveComponentFactory(childrenList[details.name]);
// How do I pass the options object from here into the child component??
this.childOptions = details.options;
let componentRef = _vcf.createComponent(_cf);
}
}
The child.component.ts is written like this:
#Component ({
template: '<div>{{ title }}</div>'
})
I want to call the loadComponent method like this:
parent.loadComponent({ name: 'choose-doc', options: {title: "Main File"} });
How can I pass the options object into the child component, so that the title gets the value 'Main File'?
componentRef has the attribute instance which is the actual instance of the created component. From this instance, you can access to all public attributes and functions of the component.
You can map your options inside a function on the component or just set them from loadComponent.
Check the class doc: https://angular.io/api/core/ComponentRef#instance
Related
I'm trying to make a schema render component with angular, it depends on dynamic component, just like:
class DynamicComponent {
// ...
private loadComponent(host: ViewContainerRef, component: Type<any>) {
const type = this.componentFactoryResolver.resolveComponentFactory(component);
host.clear();
return host.createComponent(type);
}
}
in some case, a Component's Input can be TemplateRef element:
class SomeComponent {
#Input() public template: TemplateRef<void>;
}
so i want to create TemplateRef element for this case:
class DynamicComponent {
// ...
private loadComponent(host: ViewContainerRef, component: Type<any>) {
const type = this.componentFactoryResolver.resolveComponentFactory(component);
host.clear();
const componentRef = host.createComponent(type);
componentRef.instance.template = ?
}
}
is there any way can do that?
update
let me introduce the SchemaRenderComponent, it accept json schema and render the ui.
the json schema describe the structure of component:
{
type: SomeComponent,
style: {},
props: {
template: OtherComponent
},
events: {},
}
in my design, SomeComponent's Input template is a Component in the json schema, and i will dynamic create TemplateRef for it. In html template looks like:
<SomeComponent [template]="template"></SomeComponent>
<ng-template #template>
<OtherComponent></OtherComponent>
</ng-template>
so i have to find a way to create TemplateRef.
(The TemplateRef is rendered by ngTemplateOutlet directive)
I'm trying to figure out how to bind a view child to a child component of a class inside of my view.
I have a models that emulates binary expression:
export interface IODataExpression{
}
export class ODataExpressionDescriptor implements IODataExpression{
property: ODataProperty;
selectedFunction: ODataFunctionDescriptor;
value: any;
isNegated: boolean = false;
}
export class ODataBinaryExpressionDescriptor implements IODataExpression{
left: IODataExpression;
right: IODataExpression;
operator: ODataBinaryOperators;
}
I have a component class which looks like so:
binaryExpression: ODataBinaryExpressionDescriptor = new ODataBinaryExpressionDescriptor();
binaryOperatorKeys: any;
binaryOperators = ODataBinaryOperators;
#ViewChild('left') leftExpression: OdataSimpleFilterComponent;
the left property points to a component which internally has a property:
odataExpression: ODataExpressionDescriptor = new ODataExpressionDescriptor();
How can I make it so that the binaryExpression.left always equals the view childs leftExpression.odataExpression?
Use an EventEmitter.
In OdataSimpleFilterComponent
#Output() odataExpressionChange = new EventEmitter<ODataExpressionDescriptor>();
Then, whenever tha value changes internally in the component, you do:
this.odataExpressionChange.emit(this.odataExpression);
In the main component, you'll have to do in ngAfterViewInit (or ngAfterViewChecked), to make sure that leftExpression is initialised:
ngAfterViewInit() {
leftExpression.odataExpressionChange.subscribe(data => {
this.binaryExpression.left = data;
}
}
This way, whenever the value changes in the child component, you'll receive a notification (via the subsrcription to the EventEmitter) and can react accordingly.
Of course some details might change, as I can't know all of the details of your implementation.
I have a component MyComponent and it is declared like this:
export class MyComponent implements IComponent {
...
#Input() Departments: any;
#Input() DropDownOptions: any;
#Input() Data: any[];
...
}
However, there is no property Data, when I try to access from PersonComponent component.
HTML of PersonComponent component:
<fieldset>
<my-comp #myGrid [Options]="ps.Options['myGrid']"></my-comp>
</fieldset>
TypeScript of PersonComponent component:
export class PersonComponent implements OnInit {
#ViewChild('myGrid') myGridComponent: MyComponent;
ngAfterViewInit() {
debugger;
let localData2 = this.myGridComponent.Data; // NO DATA PROPERTY. Undefined
}
ngAfterContentInit() {
debugger;
let localData1 = this.myGridComponent.Data; // NO DATA PROPERTY. Undefined
}
}
Variables that can be seen at debugger of Chrome:
How can I read values of Data property of MyComponent? What am I doing wrong?
#Input Data ... decorator "receives" data from the parent component. You set it via the attribute [Data] inside the parent template. If you don't set it it will be indefined. On the other hand you have [Options] attribute that doesn't have the corresponding #Input in the child.
You can fix it like so:
<fieldset>
<my-comp #myGrid [Data]="person.data"></my-comp>
</fieldset>
where person is an array with data field in parent component.
Please read thoughtfully the documention https://angular.io/guide/template-syntax#inputs-outputs and https://angular.io/guide/component-interaction#pass-data-from-parent-to-child-with-input-binding
And it would be better to not use reserved/too generic name like Data, Options to avoid name collisions and also camel case them.
I have two components, a Parent and a Child:
// Parent Directive
#Component({
selector: 'parent-directive',
template: `
<button (click)="nextVal($event)"></button>
<button (click)="prevVal($event)"></button>
<child-directive [content]="myValue"></child-directive>`,
directives: [ChildDirective]
})
export class ParentDirective {
public myValue : string;
constructor() {this.myValue = "Hello";}
nextVal() {this.myValue = "Next";}
prevVal() {this.myValue = "Prev";}
}
This is the child directive:
// Child directive
type ObservableContent = Observable<string>;
#Component({
selector: 'child-directive',
template: `<div></div>`
})
export class ChildDirective {
#Input() content : ObservableContent;
subscription : Subscription;
constructor() {
// I instantiate the content property as an observer. I want to see if it logs anything.
this.content = new Observable<string>(ob => {console.log('constructor', ob)});
// I'm trying to get the propagated values here.
this.subscription = this.content.subscribe(value => { console.log('value', value);});
}
}
Let me break down what I'm trying to do here. I have a child component nested in parent component. The parent has two buttons, next and prev, which when clicked change a property bound to the scope of the parent.
The child has another property, content that is bound to the myValue scope property of the parent. When I update myValue in the parent, I want the content property of the the child to change. However when I try to subscribe to that value the subscription listener is never called. What am I doing wrong?
As I can see content is a string and not an Observable. So you don't need to use .subscribe here as it will throw an error.
In your child component this.content will always give you the latest value. Just use changeDetection: ChangeDetectionStrategy.OnPush. This makes sure that angular updates the component only if one of its input attributes is changed.
To get the latest value of content in component use the ngOnChanges lifecycle method provided by angular.
// Child directive
type ObservableContent = Observable<string>;
#Component({
selector: 'child-directive',
template: `<div>{{content}}</div>`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ChildDirective {
#Input() content : ObservableContent;
ngOnChanges(changes) {
console.log('new content' + changes.content.currentValue);
console.log('old content' + changes.content.previousValue);
}
}
The content in template will always reflect the updated value due to Angular's change detection.
I have two components let's call them CompA and CompB. I would like for the clicked item object in CompA to appear in CompB. Here is what I have done so far.
CompA:
import {Component} from 'angular2/core';
import {CompB} from './compB';
#Component({
selector: 'comp-a',
template: '<ul>
<li *ngFor="#item of items" (click)="show(item)">
{{item.name}}
</li>
</ul>',
providers: [CompB]
})
export class CompA {
constructor(public _compB: CompB){}
show(item){
this._compB.displayItem(item);
}
}
CompB:
import {Component} from 'angular2/core';
#Component({
selector: 'comp-b',
template: '<div>
{{item.name}}
</div>'
})
export class CompB {
public newItem: Object;
constructor(){
this.newItem = {name: "TEST"};
}
displayItem(item){
this.newItem = item;
}
}
The problem is that when I click an item it doesn't change anything in CompB. I did a console log on CompB and I am getting the item object just fine but I view doesn't update with the clicked item's name. It just stays as 'TEST'.
Even if I set this.newItem in the displayItem function to a hardcoded string, it still doesn't change.
Update:
Both components are siblings in a main.html like this...
main.html
<div class="row">
<div class="col-sm-3">
<comp-a></comp-a>
</div>
<div class="col-sm-9">
<comp-b></comp-b>
</div>
</div>
Thats because the Component B you got injected in the constructor is not the component B used in the application. Its another component B that the hierarchical injector created, when Component B was added to the list of providers.
One way to do it is to create a separate injectable service, and inject it in both components. One component subscribes to the service and the other triggers a modification. For example:
#Injectable()
export class ItemsService {
private items = new Subject();
newItem(item) {
this.subject.next(item);
}
}
This needs to be configured in the bootstrap of the Angular 2 app:
boostrap(YourRootComponent, [ItemsService, ... other injectables]);
And then inject it on both components. Component A sends new items:
export class CompA {
constructor(private itemsService: ItemsService){}
show(item){
this.itemsService.newItem(item);
}
}
And component B subscribes to new items:
export class CompB {
constructor(itemsService: ItemsService){
itemsService.items.subscribe((newItem) => {
//receive new item here
});
}
Have a look at the async pipe, as its useful to consume observables in the template directly.
If you get a CompB instance passed to
constructor(public _compB: CompB){}
it's not the instance you expect but a different (new) one.
There are different strategies to communicate between components. This depends on how they are related in the view. Are they siblings, parent and child or something else. Your question doesn't provide this information.
For parent and child you can use data binding with inputs and outputs.
For siblings you can use data binding if you include the common parent (use it as mediator)
You always can use a shared service.
For data-binding details see https://angular.io/docs/ts/latest/guide/template-syntax.html