When does angular 2 call onDestroy on a component? - javascript

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

Related

Reloading DOMContent

I understand angular is one page application with multiple components and use route to interact between pages.
We have an angular app like that. One of the requirements is that on a specific component we need to add an eventListener to DomContentLoaded event.
Since the index.html page has been loaded (hence DomContentLoaded event has been fired) way before, we have a problem.
I cannot find a way to re-trigger the DomContentLoaded event.
The DomContentLoaded event fired before the first component was created. You can however use the ngAfterViewInit() method.
ngAfterViewInit() is a callback method that is invoked immediately after Angular has completed initialization of a component's view. It is invoked only once when the view is instantiated.
class YourComponent {
ngAfterViewInit() {
// Your code here
}
}
If you really need to catch the DomContentLoaded event anyway, do it in the main.ts file.
I don't know exactly, why you need to listen to DomContentLoaded in angular, because there are plenty of functions there, depends on which Version of angular do you use.
export class SomeComponent implements AfterViewInit {
ngAfterViewInit(): void {
}
}
But, you can retrigger an rendering of an component with *ngIf (Angular2+) or ng-if (AngularJS). Just put a boolean there and switch the boolean on an event.
Please try not use such listeners, they are (often) not needed in Angular. Try to use Component lifecycle hooks.
In your case, I recommend using ngAfterViewChecked(), which fires when the component finished rendering.
import { Component, AfterViewChecked } from '#angular/core';
#Component({
...
})
export class WidgetLgFunnelsComponent implements AfterViewChecked {
ngAfterViewChecked() {
console.log('Component finished rendering!')
}
}
If you want to do something with the DOM elements inside the template of the component, and you want to make sure they are there, you should use the ngAfterViewInit hook from angular:
#Component({
// ...
})
export class SomeComponent implements AfterViewInit {
ngAfterViewInit(): void {
// here you can be sure that the template is loaded, and the DOM elements are available
}
}

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.

How to pass data from parrent constructor to child component in angular 2

I have a angular 2 page where i need to show 2 different components using same array of data from external API. Parent is regular component, and child is shared among several other components using same functionality.
In parent component class i have output property declared:
public weatherList: WeatherForecast[];
#Output() public weatherListData: any;
Inside constructor of parent component, i populate weatherListData property with data from an external API
http.get(url)
.subscribe(result => {
this.weatherList= result.json() as WeatherForecast[];
this.weatherListData = this.weatherList;
});
and i'm using it inside parent template with success, something like: {{ weatherList.someValue }}
Also, inside parent component template, i have a call to a child component
<daily-temperature-chart [weatherListData]='weatherListData'></daily-temperature-chart>
In child component class i have declared property
#Input() weatherListData: any;
but, when i try to access weatherListData property in constructor, or init of child component, i get undefined result.
EDIT: I have played with console.log() and noticed that child component Constructor and OnInit() methods return before http.get() from parent component. Maybe this is problem, but i'm still new to angular and can't tell.
Can someone point me how to solve this?
You've a service call so you can't go for constructor or OnInit because component initialization is not dependent on your service call for this situation angular provides OnChanges whenever your input value is updated OnChanges fired.
ngOnChanges(changes: any){
console.log(changes);
console.log(this.weatherListData);
}
OnChanges passes as well an argument which informs about the current state and pervious state now you are able to use input values. If your components are bases on input and input is based on any other operation you can handle it in this block.

Angular2 directive wait for component to be initialised

I've got an wrapper component loading multiple components by using createComponent and in every component i want to append an canvas element. Now i figured there are a few ways of doing so, and with every way of doing i get kind of stuck.
Number 1
In the wrapper component access the child component (which i've not been able to do because nativeElement in this.cmpRef = this.target.createComponent(factory); is a private property), and i need to know the child component is initialised (so i'll have to use a service for this).
Number 2
Create a directive and place the canvas element with in the loaded component: <canvas projectCanvas class="overlay"></canvas> Now i'm already able to access the template through the directive itself. (Which is great but kind of repetitive).
Only thing is the directive is constructed before the component's view is initialised, so i have to create an service that will keep track of the components status.
Actual question
How would you approach this? In my point of view there's no escaping a service, or is there a way to check if the view is initialised from the wrapper component?
---------------Update------------------
Tried to make a version with a service but ngAfterViewInit / ngOnInit seems not to work when the component is loaded by createComponent see Pliunkr
How did you try ngAfterViewInit ?
Did you try injecting the children components in the wrapper like so?
#Component({...})
export class WrapperComponent implements AfterViewInit{
#ViewChildren(InnerComponent) innerComponents : QueryList<InnerComponent>;
ngAfterViewInit(){
this.innerComponents.forEach(c => c.doSomething());
}
}

Angular2: Creating child components programmatically

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

Categories

Resources