An in-depth look at Angular lifecycle hooks
Angular components have a lifecycle from their instantiation up to their destruction. During this lifecycle, there are different steps for example initialization, rendering, checking the dom, and so on. You can use these steps in the lifecycle of a component with Angular lifecycle hooks. Inside these hooks, you can perform logic when the hook is triggered. In this article, we will take an in-depth look at what Angular lifecycle hooks are and when and how to use them.
Angular lifecycle hooks explained
Angular lifecycles apply to components and directives as Angular creates, updates, and destroys them equally during the course of their execution. The lifecycle of a component (or a directive) starts when Angular initializes the component class and renders its views and the views of the child components. The lifecycle of the components continues with Angular change detection. During this process Angular checks if it needs to update and rerender the view because one of the data-bound properties changed. When Angular destroys the component instance because it is removed from the DOM, the lifecycle of the component ends.
During the lifecycle, Angular exposes lifecycle hooks to tab into events emitted during the lifecycle of the component or directive. In general, these lifecycle hooks are used to initialize, react on updates and handle the cleanup of the component when it gets destroyed. Angular exposes 8 lifecycle hook methods to us and they are executed in the following order:
ngOnChanges: Called when data-bound input properties change
ngOnInit: Called when the component or directive is initialized
ngDoCheck: Called when change detection is triggered for the component or directive
ngAfterContentInit: Called once after the first ngDoCheck
ngAfterContentChecked: Called once after the first ngAfterContentInit and every ngDoCheck
ngAfterViewInit: Called once after the first ngAfterContentChecked
ngAfterViewChecked: Called after ngAfterViewInit and after every ngAfterCntentChecked
ngOnDestroy: Called before Angular destroys the component or directive
Implementing lifecycle hooks
To use the Angular lifecycle hooks you need to implement their interface and the correlating method in your component (or directive) classes. There is no minimum amount of lifecycle hooks you need to implement. You can create a component or directive without any of the lifecycle hooks implements, with all of them, or anything in between. To use an Angular lifecycle hook you implement the interface and create a method equally named to the interface and prefix it with ng (this results in the 8 method names defined above). So implementing the OnInit lifecycle hook with the ngOnInit method looks like this:
In theory (depending on the Angular version you use) you actually do not have to implement the interface of the lifecycle. You can just declare the method and Angular will pick up on them and still run the lifecycle hook, but it's good practice to also implement the interface onto the component class.
The ngOnChanges method is only called when a component (or directive) declares @Input() properties. When a component has @Input() properties the ngOnChanges lifecycle hook is called when the component is created and each time one of the @Input() properties changes. If the ngOnChanges hook is called, it's the first lifecycle hook triggered by Angular and it runs even before the OnInit lifecycle hook.
The ngOnChanges method is the only lifecycle hook that receives an (optional) function parameter. When you declare the parameter you receive an object of type SimpleChanges containing your previous and current values and a boolean indicating if it's the first change. Together with the SimpleChanges object this lifecycle hook can be used in many scenarios and is a great tool to trigger additional logic when a component or directive receives new input values.
To demonstrate the ngOnChanges lifecycle let's create a child.component.ts with a count @Input() and implement the ngOnChanges method including the function parameter receiving the SimpleChanges object.
In our HTML we are just going to display our count inside a div:
In our app.component.ts we declare a property count and in the HTML a button to update the count and the child component where we pass this count property to the count input.
And for our HTML:
Now if we start our application and inspect our developer tools console we can see our log from the child component and the SimpleChanges object we log with it. In this SimpleChanges object, we can see this is the first change, the previous value is undefined and our current value is 0.
Now if we click our button and update the count with 1 we see that the ngOnChanges lifecycle hook is triggered again. The SimpleChanges object helps us to keep track of the changes and now we can see it's not the first change, our previous value was 0 and the current value is 1.
If you have multiple @Input() properties in your component the SimpleChanges object will only contain the @Inputs that actually received a new input value (declaring a default value inside the component itself will not trigger the ngOnChanges lifecycle, only values received through input by the parent component). So let's write some logic inside your ngOnChanges method to do something only when the SimpleObject changes contain a value for a specific @Input() property.
Now let us update our app component and add a new property 'count2', pass this to the child component and add a new button to update the 'count2' property:
When we check our console we can see that on initialization the ngOnChanges method logs the SimpleChanges object with both the 'count' and 'secondCount' @Input() property. When we click the first button we update the 'count' property and thus we see a simple changes object only containing this property logging 'Count changed'. When we click the second button the 'secondCount' is updated and inside the SimpleChanges object and we log 'Second count changed'.
The ngOnInit lifecycle hook is the most commonly used lifecycle in the Angular framework and is called when Angular initializes the component or directive after Angular first displays the data-bound properties and sets the @Input properties. Because Angular first displays the data-bound properties and sets the @Input properties the ngOnChanges lifecycle runs before the ngOnInit lifecycle.
NgOnInit is only called once and can be used to perform all your (complex) component initialization. NgOnInit is a common place to initialize component properties, request data from your stores, subscribe to subscriptions, create FormGroups, and do anything else you need for your component initialization. You might be asking yourself why to use the ngOnInit method and not the class constructor.
Basically, we want to keep our components cheap and safe to construct. The constructor should do nothing more than set the initial local variables with simple values. If we put a lot of logic and asynchronous logic inside our constructor it gets expensive and slow to construct the component and that might result in slow rendering or even components that do not construct because they are unable to fetch data. The ngOnInit method is only called when the component is successfully constructed and the data-bound properties are displayed, making it a safe and performant place to do all your initialization.
Now to demonstrate let's add the ngOnInit method to our child.component.ts and app.component.ts:
And for the app.component.ts:
Now if we inspect the console we can see the ngOnInit of the app component is logged first, next the ngOnChanges of the child component because of the initialization of the @Input() properties, and last we see the ngOnInit of the child component. If you remove the inputs from the HTML tag in the app.component.html you will only see the 2 ngOnInit logs in the console.
The ngDoCheck lifecycle hook can be seen as an extension of the ngDoChanges lifecycle. This Angular lifecycle hook can be used to detect changes that the Angular framework can't or won't detect on its own. The ngDoChanges is called after ngOnInit, after every ngOnChanges, and every time Angular change detection runs on the component or directive.
You should be careful with implementing costly or slow logic inside of the ngDoChanges lifecycle method because it's called very frequently. Every time something changes (no matter what) Angular will kick off change detection and the ngDoCheck lifecycle hook will run.
To demonstrate the ngDoCheck lifecycle let's implement it on our child component and add a console.log inside the method. We will also add a new local property 'innerCount' to the child component and update the HTML template to display the 'innerCount' and a button to update it.
After the updates our TS file will look like this:
The HTML of the child component now looks like this:
Now when we load our application and inspect the console we can see 1 log for our app component ngOnInit, 1 log for ngOnChanges in the child component, 1 log for ngOnInit of the child component, and 2 logs for ngDoCheck of the child component. The ngDoCheck occurs 2 times because it's called once after the onChanges and once after the onInit. Now when we click the button 'update inner count' you can see the ngDoCheck is trigged again, but the ngOnchanges is not. When we click the 'add to count' button and change one of the @Input values, both the ngOnChanges and the ngDoCheck are triggered.
The ngAfterContentInit method responds right after Angular projects external content into the component's view. Because of this ngAfterContentInit is the Angular lifecycle hook where you have access to your ElementRef of the ContentChild properties for the first time. The lifecycle method is called once during the lifecycle of a component or directive and runs right after the first ngDoCheck.
It's important to note that when ngAfterContentInit runs you can only access ElementRef's and ContentChilds of content projected into the child component and not yet the HTML elements of the component itself. The view of the component is not rendered yet and will be available in the ngAfterViewInit lifecycle hook.
To demonstrate this we will use Angular content projection to pass some HTML from our app component to the child component and display the HTML inside the child component. In our child component, we will try to log the projected content and the container we project the content in.
After updating our app component HTML looks like this:
The HTML of our child component looks like this:
And the TS file of our child component looks like this:
Now if we inspect the console we will see our log for ngAfterContentInit, the log for our wrapper is still undefined because the view of the child component is not rendered at this stage. The projected value we receive from the parent does have a value at this stage.
On initialization, the ngAfterContentChecked lifecycle hook is called once after the ngAfterContentInit lifecycle and then every time after the ngDoCheck lifecycle runs. The ngAfterContentChecked is not suited to perform costly or time-consuming logic because it runs after each ngDoCheck. This lifecycle indicates that Angular has checked the projected content after its initialization.
The ngAfterViewInit lifecycle responds when Angular initializes the component and the child component's views. Inside this Angular lifecycle hook, we can access the ElementRef of our ViewChild properties for the first time. The entire view has been constructed at this point so we can log and use ElementRefs of both ContentChild and ViewChild elements. The ngAfterViewInit lifecycle hook can be useful for all logic that depends on the view and HTML elements to work and only has to be performed once during the initialization of the component.
To demonstrate this let's add the lifecycle to our component and log the wrapper inside the lifecycle method so we can observe that it's accessible at this stage of the component lifecycle.
Now if we inspect the console we can see the wrapper is undefined at the ngAfterContentInit log but defined at the ngAfterViewInit log.
The ngAfterViewChecked responds when Angular has checked the view and is ready to render it onto the screen in its final form. This lifecycle hook is called on initialization after the ngAfterViewInit and then every time after the ngAfterContentChecked runs. This lifecycle method runs each time Angular change detection runs, so be careful what you implement in this lifecycle.
The last Angular lifecycle hook is ngOnDestroy. This lifecycle hook is called just before Angular destroys the component or directive and removed it from the view. The lifecycle hook is perfect for cleaning up your component. If you use subscriptions and event handlers you want to unsubscribe and detach from them to prevent memory leaks in your Angular application. The ngOnDestory lifecycle hook is the perfect place to unsubscribe, detach and perform any other logic you want to run when a component is destroyed.
It's important to note that the ngOnDestory is not called when a user refreshes the page or closes the browser. In this scenario, you do not have to clean up your subscriptions to prevent memory leaks, but you might want to preform some other clean up logic. If this is the case you can add a HostListner decorator to the ngOnDestroy lifecycle method like this:
Angular lifecycle hooks are a great way to op into the lifecycle of your components and directives and perform some logic based on your needs. They help with keeping your constructors clean and give a good place to access your view elements for the first time and let you clean up your components before they are destroyed. Be careful with some of the lifecycle hooks because of the frequency they are called, but besides that, I would make use of lifecycle hooks whenever you can.