The future of standalone components in the post-Ivy release days

Angular Ivy is almost here, and this a great opportunity for us to start exploring what’s new in Angular, learn about the new APIs and start thinking about the concept behind standalone components.

Eliran Eliassy
Angular In Depth
9 min readSep 24, 2019

--

AngularInDepth is moving away from Medium. More recent articles are hosted on the new platform inDepth.dev. Thanks for being part of indepth movement!

This post will not explain what Angular Ivy is. If you’re looking for a more detailed post of what Angular Ivy is, you can check my previous blog post: All you need to know about Ivy, The new Angular engine!

Most of the ideas in this blog post are still considered as experimental and are not yet official. Most of them still can be changed in the future.

A few weeks ago, version 9.0.0-next3 has been released with the following exciting statement: “Angular now compiles with Ivy by default”! So, Ivy is just around the corner, and for me, this looks like the best time to have a closer look at the future of components implementation with the concept behind stand-alone components.

Let’s take the simple idea of having 1 component. In our case, a counter component. It has one input to set the initial value of the counter, and one output to notify that the counter has reset.

We’re creating Angular components so we can use them as custom HTML tags:

In order to do it, we will probably need to:

  1. Create a NgModule - CounterModule
  2. declare our counter component in it and then export it.
  3. We need to bootstrap this module,
  4. Import this CounterModule in the main AppModule.

And only then we can use our custom selector.

We have here a lot more here than just a component, right?

So, I've checked the Angular official docs and found this:

“A component must belong to an NgModule”.

And I really have a problem with that. NgModules for me is much more than just a component, right? Modules have Injectors, providers, DI and many more than just a simple component. NgModules are kind of representing applications.

And my point is, applications are not components! Sometimes we just want to render a single standalone component without being tied to any application.

What’s really shiny about Ivy is that it finally gives us this ability to render a component…in a matter of 1 simple function:

But, there is a price!
As we said, NgModules defines Injectors, zone, directives, and providers. If we will create our own stand-alone component without declaring it in our module, we will have have to handle those things by ourselves.

Let’s see it in action:

So, instead of just using the app-counter selector, we can call on our OnInit lifecycle to the renderComponent function, and also declare the host:

Now, on the screen, we finally can see our Counter:

It’s working! Sort of….
If we click the + or the reset buttons, nothing really reflect on the screen…
I’ve added a console.log print on the plus() and reset() buttons:

As we know already, NgModule defines zones, which cause automatic change detection. And because our standalone component is not part of any Module, we don’t have an automatic change detection.

How we can fix it? Ivy providing us a function named detectChanges() which can remind us a little of the detectChanges() method we had on our ChangeDetectorRef. Here, this is just a function, without the need to inject any service, not DI needed, just an independent function:

And now:

It’s working!

Now let’s try something even more interesting…

I have here a service, which can perform saving of the counter value through an HTTP request:

Now, we want to use this service, as part of our component, so we injecting it through the constructor, and also implementing the save method which will be triggered by clicking on the save button:

Now, when running the application, we’re getting an error straight away:

What’s wrong?

It’s simple. Our CounterComponent doesn't have an Injector and can’t really resolve the HttpClient we’re trying to inject in the StorageService. Who has an Injector? The application who uses the CounterComponent.

We can inject the Injector (P.S — It always make me laugh to say that), in our main app like this:

Or, if we don’t want to use the DI, Ivy now provides a function to inject tokens, directiveInject:

Components dependencies & Automatic Change Detection

Components dependencies

Most of the time our component needs more than injectors or change detection mechanism. Sometimes we want to use directives, pipes or import a completely different module.

So, it’s still not something you can do right now, but according to Rob Wormald talk from AngularUP, we will have the ability to import providers and dependencies to a specific component in the following way:

Automatic Change Detection

Automatic change detection is a really important part of our day to day with an Angular application. And most of the time, when we are using streams that will be resolved asynchronously, by using the async pipe.

Not many know, but if you check the source code of the async pipe, you’ll find out that the async pipe is relying on Zone, it’s calling the markForCheck() function to affect the changes on the template.

Let’s see it in action:

I’m adding on observable which will be resolved in 1 second, and want to show the value of it on the template. Usually, the async pipe should be good enough for that use case, but without zones, this won’t work for us.

How we can solve it? So the solution will probably be something like the push pipe. Again, it still something we don’t have officially yet, but there is already a possible implementation for it by Manfred Steyer.

OnPush components

If we already mentioned the markForCheck() function, Let’s go for another example and demonstrate how it will look like in Ivy.

So, we have 2 components, markForCheck() component which is the container component and it holds a behavior subject with the initial stream of numbers between 1 and 3:

We also have a child component that subscribes to the data we getting as an input:

Now, if we will click on the Add Number button, the number 4 will be added to the view:

Now, if we change the child component to on push strategy, clicking the Add Number button wouldn’t update the list of numbers. How we can fix it? So, Until now we had the markForCheck() function from the ChangeDetectorRef.

In Ivy, we can use the markDirty() function to tell Angular to render this component and all of its anchors in the next cycle.

Dynamic Import

Another great way of thinking of those standalone components is the ability to load them dynamically only on demand.

Think about the use case where you have something like a small chat box, which has to be part of all pages in our website, but less than 5 percent of the users are actually using it.
When something like that is part of your layout, it will end up probably in your main bundle. And most of the time you don’t want to pay in bytes for features most of your users do not use.

Angular 8 introduces lazy loading of modules using the ES6 import function:

Why don’t we try to do the same with components?

And, as we can see in the network tab on our chrome dev tools, the CounterComponent chunk being downloaded only when clicking on the Load Counter button!

Loading standalone component lazy is a great way to keep our main bundle lean and to make our application much faster. In my opinion, maybe the best thing we got out of Ivy!

Higher-Order Components(HOC)

We talked a lot here about the future of Angular components. But, if you ask me, one of the most exciting things we can do in our Angular applications in the post Ivy days is creating Higher-order components(HOC).

An HOC is a function that gets a component and returns a component but is also affecting the component in between.

I’ve already shown and explained what is HOC, in my previous blog post about Ivy. Now, it’s time to show another practical example of it!

RXJS auto unsubscribe

Let’s start with an example:

We have here a timer that “ticks” every 1 second. We’re adding a tap operator to console log the result, this tap will be our indicator that this subscription is still alive even if we destroyed the component:

Let’s create a container component, where we can destroy and revive the component:

Let’s try to destroy our component and check the console:

As we can see, even that we destroyed the component, the subscription is still there. If we will revive the timer, we will have another subscription, and we’re actually risking in a potential memory leak.

The solution — Auto Unsubscribe HOC

So, let’s leverage the power of HOC to create an Automatically unsubscribe HOC for our components:

Let’s first check the result:

Sweet!

Now, let’s explain what is going on here:

  1. We’re creating the basic HOC using the cmpType.ngComponentDef.factory
  2. We have 2 things on our hand: the cmpTyp which represent the component definition whos generating by the ngcand the cmp which represent the component instance.
  3. We’re overriding the ngComponentDef.onDestory function and setting it to a function which:

a. Checking if the component instance has a property named ngOnDestroy, if so, we don’t want to override the user OnDestory hook, so we’re just invoking the cmp.ngOnDestroy()

b. We’re iterating over the component instance and checking for each key if the type of the property is Subscription. If so, we can just call the unsubscribe() method to achieve automatically unsubscribe.

This RXJS auto unsubscribe will be released with other RXJS tools my company e-square.io working on! Stay tuned!

Summary

Ivy the 3rd generation of Angular compiler is almost here! And it brings a lot of cool and important functionality for us as developers.

I know most of the things you just read are still experimental, but they can give a sense of how the implementation of standalone components will look like in the future.

Bye Bye NgModules talk (NG-DE conf 2019)

A few weeks ago, I gave a talk on the exact same topic on NG-DE conf, you more than welcome to check out my talk on youtube:

And of course the slides, and repo, I used for the talk and this blog are here: Slides | Github Repo

Thanks for reading!

Visit e-square & my website for more content.
Follow me on Twitter.
Special thanks to Itay Oded for working with me on the RXJS auto unsubscribe HOC.
Thanks to: Tim Deschryver and Denis Levkov for reviewing it.

--

--

Eliran Eliassy
Angular In Depth

Google Developer Expert in Angular & Web Technologies. Founder and CEO @ e-square.io. Angular enthusiast, Public speaker, Community leader.