Angular2: Creating child components programmatically - javascript

Question
How to create child components inside a parent component and display them in the view afterwards using Angular2? How to make sure the injectables are injected correctly into the child components?
Example
import {Component, View, bootstrap} from 'angular2/angular2';
import {ChildComponent} from './ChildComponent';
#Component({
selector: 'parent'
})
#View({
template: `
<div>
<h1>the children:</h1>
<!-- ??? three child views shall be inserted here ??? -->
</div>`,
directives: [ChildComponent]
})
class ParentComponent {
children: ChildComponent[];
constructor() {
// when creating the children, their constructors
// shall still be called with the injectables.
// E.g. constructor(childName:string, additionalInjectable:SomeInjectable)
children.push(new ChildComponent("Child A"));
children.push(new ChildComponent("Child B"));
children.push(new ChildComponent("Child C"));
// How to create the components correctly?
}
}
bootstrap(ParentComponent);
Edit
I found the DynamicComponentLoader in the API docs preview. But I get the following error when following the example: There is no dynamic component directive at element 0

This is generally not the approach I would take. Instead I would rely on databinding against an array that will render out more child components as objects are added to the backing array. Essentially child components wrapped in an ng-for
I have an example here that is similar in that it renders a dynamic list of children. Not 100% the same, but seems like the concept is still the same:
http://www.syntaxsuccess.com/viewarticle/recursive-treeview-in-angular-2.0

Warning: DynamicComponentLoader has been deprecated in RC.4
In Angular 2.0, loadIntoLocation method of DynamicComponentLoader serve this purpose of creating parent-child relationship. By using this approach you can dynamically create relationship between two components.
Here is the sample code in which paper is my parent and bulletin is my child component.
paper.component.ts
import {Component,DynamicComponentLoader,ElementRef,Inject,OnInit} from 'angular2/core';
import { BulletinComponent } from './bulletin.component';
#Component({
selector: 'paper',
templateUrl: 'app/views/paper.html'
}
})
export class PaperComponent {
constructor(private dynamicComponentLoader:DynamicComponentLoader, private elementRef: ElementRef) {
}
ngOnInit(){
this.dynamicComponentLoader.loadIntoLocation(BulletinComponent, this.elementRef,'child');
}
}
bulletin.component.ts
import {Component} from 'angular2/core';
#Component({
selector: 'bulletin',
template: '<div>Hi!</div>'
}
})
export class BulletinComponent {}
paper.html
<div>
<div #child></div>
</div>
Few things you needs to be take care of are mentioned in this answer

You should use ComponentFactoryResolver and ViewElementRef to add component at runtime.Let's have a look at below code.
let factory = this.componentFactoryResolver.resolveComponentFactory(SpreadSheetComponent);
let res = this.viewContainerRef.createComponent(factory);
Put the above code inside your ngOnInit function and replace "SpreadSheetComponent" by your component name.
Hope this will work.

Programmatically add components to DOM in Angular 2/4 app
We need to use ngAfterContentInit() lifecycle method from AfterContentInit. It is called after the directive content has been fully initialized.
In the parent-component.html, add the a div like this:
<div #container> </div>
The parent-component.ts file looks like this:
class ParentComponent implements AfterContentInit {
#ViewChild("container", { read: ViewContainerRef }) divContainer
constructor(private componentFactoryResolver: ComponentFactoryResolver) { }
ngAfterContentInit() {
let childComponentFactory = this.componentFactoryResolver.resolveComponentFactory(childComponent);
this.divContainer.createComponent(childComponentFactory);
let childComponentRef = this.divContainer.createComponent(childComponentFactory);
childComponentRef.instance.someInputValue = "Assigned value";
}
}
Inside src\app\app.module.ts, add the following entry to the #NgModule() method parameters:
entryComponents:[
childComponent
],
Notice that we're not accessing the div#container using the #ViewChild("container") divContainer approach. We need it's reference instead of the nativeElement. We will access it as ViewContainerRef:
#ViewChild("container", {read: ViewContainerRef}) divContainer
The ViewContainerRef has a method called createComponent() which requires a component factory to be passed as a parameter. For the same, we need to inject a ComponentFactoryResolver. It has a method which basically loads a component.

The right approach depends on the situation you're trying to solve.
If the number of children is unknown then NgFor is the right approach.
If it is fixed, as you mentioned, 3 children, you can use the DynamicComponentLoader to load them manually.
The benefits of manual loading is better control over the elements and a reference to them within the Parent (which can also be gained using templating...)
If you need to populate the children with data, this can also be done via injection, the Parent is injected with a data object populating the children in place...
Again, a lot of options.
I have used 'DynamicComponentLoader' in my modal example, https://github.com/shlomiassaf/angular2-modal

Related

print html tag by typescript in html page

It is an angular app.
I want to call one component inside another component.
So, if I write in 1st component like this to call 2nd component using 2nd component's selector:
html:
<div>
<app-nice-component></app-nice-component>
</div>
All works fine.
But I have a lot of components, If I try to do the same from an array, it is no longer an HTML tag, it becomes an string (and my 2nd component is no longer loading)
ts:
let x: string[] = [{<app-nice-component></app-nice-component>},{<app-nice2-component></app-nice2-component>}]
html:
<div>
{{x[0]}}
</div>
So how can I make it an HTML tag, not an string, in 2nd case?
I suggest you read the next doc about dynamic component loader in Angular: https://angular.io/guide/dynamic-component-loader
You need to load dynamically the components. Steps:
Create a directive for telling your HTML code where to load the components:
#Directive({
selector: '[myDirective]'
})
export class MyDirective {
constructor(public viewContainerRef: ViewContainerRef) { }
}
Setup your HTML with the new directive into a ng-template, don't forget to add the directive in the module that is using it.
<div>
Component loading:
<ng-template myDirective></ng-template>
</div>
Create a function for dynamic loading, it is necessary to import all the components that you want to use, in this example I prepare 3 components and dynamically load all of them, you can play with the componentToLoad array to load more or less components dynamically.
ngOnInit() {
let componentToLoad : any[];
componentToLoad = [ this.componentFactoryResolver.resolveComponentFactory(Component1Component),
this.componentFactoryResolver.resolveComponentFactory(Component2Component),
this.componentFactoryResolver.resolveComponentFactory(Component3Component) ]
const viewContainerRef = this.myDirective.viewContainerRef;
componentToLoad.forEach( c => {
viewContainerRef.createComponent(c);
});
}

How can I access DOM elements in angular

I am tetsing a template driven form in angular, just testing not validating it.
I have read that it can be done using a viewChild property but it seems to not work for me.
I create a reference like this in my one of my forms label:
<label #ref id=.. class=...>
And now in my component I do this:
#ViewChild('ref') ref:ElementRef;
So, I suppose I created a valiable of type ElementRef that is viewChild of my input. So now I can use ref in my tests.
Inside my tests I do this:
let ref: HTMLElement:
it(....=>
{
ref = fixture.debugElement.query(By.css('ref')).nativeElement;
fixture.detectChanges();
expect(ref.innerHTML)toContain('Name');// or whatever
}
)
Now consider that the test, html and component files are separated from one another.
I still get errors of nativeElemnt property cannot be read. eventhough I have imported ElemntRef.
Is this the right way to access the DOM elemnts?? Or this viewChild doesnt make a referece to my label??
And again can I use the ID to access the form elements? What I have read they use a reference with #.
Thanks!!
For direct access to DOM in Angular you can make judicious use of ElementRef
However Direct access to DOM elements is not a good practice because
it leaves you vulnerable to XSS attacks.
Your AppComponent
import {Component, ElementRef} from '#angular/core';
#Component({
selector: 'my-app'
})
export class AppComponent implements ngOnInit {
constructor(private _elementRef : ElementRef) { }
ngOnInit(): void
{
this.ModifyDOMElement();
}
ModifyDOMElement() : void
{
//Do whatever you wish with the DOM element.
let domElement = this._elementRef.nativeElement.querySelector(`#someID`);
}
}
Your HTML
<p id="someID"></p>

How to define a "global" variable in a component? (Angular4)

I have an app-editor-component which contains a lot of nested components inside it's structure. When the component is loaded, it creates an array: obj_list : MyObject[]
Many of the nested components will contain a <select> element, where one of the elements in obj_list has to be selected in each.
How is it possible to share this list with all the elements in the structure?
One way to share data between nested components is Input/Output+EventEmitter system.
Or, you can use shared service to transfer data between components.
Here are some links to official docs and good post from firebase about component interaction:
Docs
Post
In your case, for example, you can pass obj_list from app-editor-component down to child components through Inputs, then, in child components, observe <select>’s change event and emit changes back to app-editor-component.
But, if you have deep nesting, using service is better approach
The entity that is shared by several nesting components should be a service. This is naturally provided by Angular hierarchical injectors.
More importantly, if data is supposed to be changed asynchronously, components should be notified of this somehow. This is conveniently done with RxJS observables:
import { Subject } from 'rxjs/Subject';
#Injectable()
class Foo {
objListSubject = new Subject();
objList$ = this.objListSubject.asObservable();
}
#Component({
providers: [Foo] // belongs to NgModule providers if the service is global
...
})
class ParentComponent {
constructor(private foo: Foo) {
...
this.foo.next(['bar']);
}
}
#Component({
template: `
<select ...>
<option *ngFor="foo.objList$ | async">
</select>
`,
...
});
class ChildComponent {
constructor(public foo: Foo) {}
}

When does angular 2 call onDestroy on a component?

I am not able to figure out when does the angular decide to call onDestory event?
When I toggle the component with *ngIF directive, onDestory is not called and the state of the component is maintained as if it is using the same instance of the component?
Can anybody elaborate as to when angular(2) destroys component? And how to achieve newer instance of component when toggling with *ngIf?
Most DOM manipulations in Angular are performed using ViewContainerRef. In particular, this mechanism is used internally by ngIf:
private _updateView() {
...
this._viewContainer.clear();
...
this._thenViewRef =
this._viewContainer.createEmbeddedView(this._thenTemplateRef, this._context);
}
}
and router-outlet:
#Directive({selector: 'router-outlet', exportAs: 'outlet'})
export class RouterOutlet implements OnDestroy, OnInit {
constructor(..., private location: ViewContainerRef, ...)
detach(): ComponentRef<any> {
...
this.location.detach();
...
}
attach(ref: ComponentRef<any>, activatedRoute: ActivatedRoute) {
...
this.location.insert(ref.hostView);
}
Whenever either viewContainer.clear() or viewContainer.remove(index) method is called the relevant components or embedded views (created with ng-template) are removed and ngOnDestroy lifecycle hook is called on them.
Can anybody elaborate as to when angular(2) destroys component?
This will happen when using structural directives like ngIf and ngFor, when router navigates away from current router-outlet directive or when you manually remove dynamic components or embedded views using viewContainerRef.
You can read more about DOM manipulation using ViewContainerRef in:
Exploring Angular DOM manipulation techniques using ViewContainerRef

How can I get child component names as they or after they initialize in Angular?

I've been studying Angular's lifecycle hooks while looking for a way to know when and which child components are loaded.
I see that ngAfterViewInit()
"Responds after Angular initializes the component's views and child
views."
Since ngAfterViewInit knows about the children, how could I get their identifying information as they (or after) they initialize?
Something like this pseudo code:
// ngAfterViewInit() - Respond after Angular initializes the component's views and child views.
export class AppComponent implements AfterViewInit {
ngAfterViewInit() {
console.log(‘Children should be loaded');
// loop through ngAfterViewInit
querySelector('body').classList.add(ngAfterViewInit[i].componentName);
}
}
sidenote: the goal is to add component names or selector names to <body>'s class list.

Categories

Resources