Reputation: 62674
I have a button component fancy-textbox
. I want to make it such that users can dynamically add new fancy-textbox
, but they have different labels above the textbox that is based on a scope variable that is unique to the fancy-textbox
itself (or maybe from a parent scope variable that is not shared among all fancy-textboxes
). How do I do this? Currently, I use this directly in my template showing and hiding, but I want to be able to "dynamically add more instances of this" programatically:
<div *ngIf="showTextBox">
<fancy-textbox labelForBox="TextBox 1"></fancy-textbox>
</div>
That is good, if fancy-textboxes are only created in the DOM in one specific area. However, what I want to do, is to be able to dynamically create components in different sections of the DOM.
<div class="container">
<div class="navsection"><input id="classname" type="text"><button (click)="createFancyButton()">Create Fancy Button</button></div>
<div class="topsection">
</div>
<div class="midsection">
</div>
<div class="botsection">
</div>
<div class="footsection"></div></div>
Given the template above... assuming that users input the classname (e.g. botsection) into the textbox and hit the "createfancybutton button), I want ""
"<fancy-button></fancy-button>
" to be put into the appropriate section of the page, I want to be able to dynamically "create" instances of independent "fancy-button" within different sections of the page template. I could stick 3 ng-if statements with ng-for, though it seems impractical. Looking for a better alternative...
UPDATE: So the steps would be:
1) User enters "midsection" into textbox.
2) User clicks on the button "Create Fancy Button"
3) - The <fancybutton></fancybutton>
component would be added under the div with the classname "midsection"-
The user can repeat clicking on the same "Create Fancy Button" button to create more under that. If user changes the input box to "topsection", then when user clicks on "Create Fancy Button", the fancybutton component would be added under div with "topsection".
If the user enters "newsection", then a new div with classname "newsection" would be created under the div with the classname "container", and fancybutton component would be added to the div with classname "newsection".
Upvotes: 8
Views: 3664
Reputation: 2562
I don't believe you need any other scopes/components. Something like this should work.
component (TypeScript):
sections: {[key] : string[]} = {};
createFancyButton: (section: string) => {
if (!this.sections[section]) {
this.sections[section] = [];
}
this.sections[section].push('');
}
getSectionKeys: () => {
return Object.keys(this.sections);
}
The "sections" property is an indexable object, which means it behaves like a hash table. Each property ("key") of the sections object is an array of strings, which represent the fancy button values (ngModel).
template (HTML)
<div class="container">
<div class="navsection">
<input #section type="text" placeholder="section" />
<button (click)="createFancyButton(#section.value)">Create Fancy Button</button>
</div>
<div *ngFor="let key of getSectionKeys()" [class]="key">
<fancy-textbox *ngFor="let textBox of sections[key]" [(ngModel)]="textBox"></fancy-textbox>
</div>
</div>
There is a template reference variable (#section), which provide for easy access to a DOM element in the template. Then we *ngFor over the keys from the sections hashtable to create each section div. Finally we *ngFor over the array of strings for each section.
More information on template reference variables can be found here. https://angular.io/docs/ts/latest/guide/template-syntax.html#!#ref-vars
Note: There may be typos as I did not test this.
Upvotes: 1
Reputation: 3118
This is how you construct and add components programmatically:
import {ComponentRef, ViewContainerRef, ComponentFactoryResolver } from '@angular/core';
@Component({
selector: 'fancy-box',
template: `<div>{{fancyContent}}</div> `,
})
export class FancyBox {
fancyContent;
doStuff() {
console.log('done');
}
}
@Component({
selector: 'fancy-parent',
template: `<div (click)="addNewFancyBox()">Add Box</div> `,
})
export class FancyParent {
private counter = 0;
constructor(
private viewContainerRef: ViewContainerRef,
private resolver: ComponentFactoryResolver) {
}
addNewFancyBox() {
const factory = this.resolver.resolveComponentFactory(FancyBox);
fancybox = this.viewContainerRef.createComponent(factory);
const fancyboxElement = fancybox.instance as FancyBox;
fancyboxElement.content = 'box number: ' + counter;
fancyboxElement.doStuff();
counter++;
}
}
Upvotes: 0
Reputation: 8315
You need load component dynamically
Here is my solution
Parent.component.ts
import { Component, OnInit, ViewChild, ViewContainerRef, Input, ComponentFactoryResolver, ReflectiveInjector } from
"@angular/core";
import { FancyButtonCompoent } from "../FancyButton.component";
@Component({
moduleId: module.id,
selector: "app-parent",
templateUrl: "parent.component.html",
styleUrls: ["parent.component.css"],
entryComponents: [FancyButtonCompoent]
})
export class ParentCompoent {
@ViewChild("midsection", { read: ViewContainerRef })
midsectionContainer: ViewContainerRef;
constructor(private resolver: ComponentFactoryResolver) {
}
createFancyButton() {
//Name Is Fancybutton data binding property
var yourdatavalues= {Name:'myname'}
this.createDynamicbutton({
input: yourdatavalues,
});
}
//you can add your own model to get what you want like remove,move
// var dynamiccompoent={Data:yourmodel,compoentcontainer:any}
//fancybuttonarray:dynamiccompoent[];
fancybuttonarray:any[];
createDynamicbutton(elementData) {
if (!elementData) {
return;
}
// Inputs need to be in the following format to be resolved properly
let inputProviders = Object.keys(elementData.inputs)
.map((inputName) => { return { provide: inputName, useValue: elementData.inputs[inputName] }; });
let resolvedInputs = ReflectiveInjector.resolve(inputProviders);
// We create an injector out of the data we want to pass down and this components injector
let injector = ReflectiveInjector
.fromResolvedProviders(resolvedInputs, this.midsectionContainer.parentInjector);
// We create a factory out of the component we want to create
let factory = this.resolver.resolveComponentFactory(DefaultButtonComponent);
// We create the component using the factory and the injector
let component = factory.create(injector);
this.midsectionContainer.insert(component.hostView)
//your getting every instance of fancy button instance into array
this.fancybuttonarray.push.(component )
//if you want to clear elment if you wish
//this.fancybuttonarray[0].destroy()
//this.fancybuttonarray[1].destroy()
}
}
parent.component.html
<div class="row col-lg-12" >
<div #midsection >
</div>
</div>
Fancybutton.compoent.ts
import { Component, OnInit, Injector } from '@angular/core';
@Component({
moduleId: module.id,
selector: 'Fancy-button',
templateUrl: 'Fancybutton.component.html'
})
export class FancybuttonComponent {
inputelement:yourDatamodel
constructor(private injector: Injector) {
this.inputElement = this.injector.get('inputElement');
}
}
Fancybutton.compoent.html
<div>
<button title="inputelement.Name"(click)r ="alert(inputelement.Name)"></button>
</div>
Update
here is the nice post about load dynamically child component using angular2
Also Avilable Plunker example
Upvotes: 1
Reputation: 1876
Looking at this 'architecturally', and leveraging JS/TS OO class inheritance, I would personally consider adding a 'compound component' ('cc': follows an OO 'composite' pattern) to each section (eg: <top-section-cc-fancytextboxes...></>
or <bot-section-cc-fancytextboxes...></>
). These serve as 'place holders' for the various <fancy-textbox>
types which could be zero or more instances inside each cc (compound component).
Now each compound component derives/implements from a base class/interface component(eg: <base-section-cc-fancytextboxes>
) which contains the base methods to manage adding multiple <fancy-textbox>
types. The derived class methods, however, would 'know' where to add the proper <fancy-textbox>
s (probably a *ngFor
'd array as mentioned above). As for instantiating a specific type of <fancy-textbox>
, perhaps leveraging a class factory pattern will be useful as well--probably an AJ2 service provider that returns instances and driven by the compound components.
Regardless, with AJ2's built in TS OO, and details notwithstanding, this is the vector I personally will use to solve this particular problem. This is just a thought.
Upvotes: 1
Reputation: 692023
Have an array of labels in your component.
Use *ngFor to iterate over this array and generate a fancy textbox for each of those labels.
To add a new fancy-textbox, add a new label to the array.
<fancy-textbox *ngFor="let label of labels" [labelForBox]="label"></fancy-textbox>
And in the component:
labels: string[] = [];
// to add a fancy textbox:
this.labels.push('new label');
Upvotes: 1