Video details

Blagoj Jovanov - Angular Elements: Write Once, Use Everywhere | AngularUP 2021

Angular
01.19.2022
English

Angular Elements: Write Once, Use Everywhere "Angular Elements is the result of the efforts to provide means of using Angular components outside of Angular. The ultimate goal is to have lightweight components with minimal Angular
framework support which can be used in applications developed in other frameworks. Web Components is umbrella term for 4 specifications on their own: HTML Templates, HTML Imports, Shadow DOM
and Custom Elements. This talk will focus on last two, explaining them in the most comprehensive way by defining and giving suitable examples about them. There will be two demos, one explaining how
to define custom elements without Angular support and the other one showing what can be done using latest Angular. I will also talk about packaging of custom elements, explain differential loading
and also the slots API and give example in the demo. Last but not least, I will mention possible means of optimizing the Angular-powered element, mainly bundle size wise, so it can be used wherever
we want it, how many times we want it." Blagoj Jovanov -
Senior Software Engineer, Netcetera "Blagoj is working as a Senior Software Engineer at Swiss software company Netcetera.
His first work experience included Spring based applications, and after that
he got involved in media asset management, specifically video transcoding.
His current work includes developing applications in the 3-D Secure Payment area as a fullstack developer (Spring Boot + Angular)."

Transcript

Thank you. Okay, so hello everyone and welcome to my presentation about Angular Elements, or better set framework powered framework agnostic components. The main motivation for Angular Elements is potential usage, so we're talking about content management systems, dynamic components, Angular Universal Micro Frontends, and custom widgets. So what we were going to discuss today, first, of course the theory. So the web components and it's four parts. Then we will see how Angular uses this APS in order to create such elements. We can also discuss about bundle sizes, briefly about Ivy. And lastly, there will be two demos to show that this actually works with the usual Disclaimer about web demo. So before we start, a couple of words about Me My name is Blago Irvanov. I work at Necro and this rather long job title wants to say that I have a few years of experience in building applications which revolve around the protocol, which enables increased security during online payments. The easiest way to describe me is there are three things that I love doing, and that is traveling, running, and last but not least, coding. Also worth mentioning that this is my first time in person presentation on a tech conference. So please be gentle and I will start a presentation with a definition about Angular Elements from Rob Warmold himself, saying that an Angular element is an Angular component on the inside because you actually use Angular to create it. But standards on the outside, meaning that once this is inserted into the Custom Elements registry, the browser seamlessly recognized it, so you couldn't tell whether this was done with or without framework support. And now, as promised, starting with the theory. So we have components is an umbrella term for four things. The first one HTML templates. So this is portion of the HTML which doesn't get rendered immediately, but rather you need to fetch it via JavaScript and then append it to the Dom. Angular already has the template pack and also some structural directive, for example the Ng if under the hood, use Ng template for conditional rendering. Second thing HTML imports. So similar as we include JavaScript or CSS on HTML pages, we should be able to import one HTML page inside the other. Something like this. However, having in mind that there are already packs like iframe and especially the Object pack, which more or less do the same job, and therefore this HTML imports usage has been obsolete and discouraged. Now for the two more important things. The first one is the Shadow Dome. There are several definitions about it. I like to describe it via its counterpart, and that is the Light Dome. And in that manner, if we say that the Light Dome is something that a component user might write, then the Shadow Dome is something that a component author might write. So if we see here we have like the header and then on the Shadow Dome, this is fetched with JavaScript, a Shadow route is created and then custom HTML is inserted there. If we see the developer console, we can just confirm that the shadow route has been created. There are two modes, open and closed. Closed was intended for some security, but it's easy to circumvent this, so it's not of practical use and this is something that we already have seen. Like if we take a look at the component decorator, we can identify that the selector is actually the light Dome, while the template is the shadow Dome, simply speaking. But now what happens if the user wants to insert arbitrary content inside this header text? If the situation is unchanged on the Shadow tomb site, then we can see here that the H one is present in the developer console, but it isn't part of the shadow route and therefore it doesn't get rendered. There is a possibility however of the light to enter the shadows, but this is done only by invitation and this invitation comes in the form of slots. So if we now have the same example again arbitrary content into the header Tags, but on the left hand side in the shadows we have also slot tax. Now if you see the developer console again, the H one is outside of the shadow route, but now we see that due to the slot mechanism, a content projection happens and it gets inserted into the shadow root Evange there are several flavors of slots. The most basic one is the default slot. We also have a slot with a foldback content, meaning if there are no matches, basically this fallback content will be shown and then as opposed to the default which is also said unnamed, we have named slots in case we want to have a bigger control which content gets displayed where. And in order to do this on some content we need to define the slot attribute and then place the name of the name slot and it will end up there. The last thing is that also on slots we have like one slot change event where we can listen to changes into the content, and after that in JavaScript we use the assigned notes property on the event target and there we can manipulate the content as ever we want. Now for some equivalences with Angular. So first, as I mentioned previously, the slot mechanism is something similar to the content projection in Angular. There is no alternative for slot with the fallback content and it's not like very well documented, but legend hasn't packed has a select attribute which is something like CSS flexible selector. And if you write something like this this would be the equivalent of name slots. And the last thing is about fetching the content inside ng content tax. That's what the content children directive is for. Now what do we need to do in order to actually equivalent content projection in Angular? So if we have ng content, if we want to use like native slot support first, of course we will insert the slot tax. But most important we need to set bank obsculation to Shadow Dome explicitly because basically Shadow Dome version one, the slots are defined. And the other reason is because this is not the default mode, the default mode is emulated. And it is like that because unfortunately not all browsers still support Shadowdown. And now the most important part this is like custom Elements What do we need to do in order for the browser to recognize an element as custom? So first we need to define like one class in our example here it's a traffic light element with three lights. So it's important that this class needs to extend HTML element and then first we define the observed attributes. In our case these are attributes for which we want to listen to changes and in most cases this is like all that are available. Then we would need to define accessors in order to modify the properties dynamically via JavaScript. And then there are also reactions, reactions are like specific points inside the lifecycle of the component. And here we have the constructor which get called when the element is created. Then we have the connected callback when the element is attached to the Dome. Then we have the disconnected callback when an element is removed from the Dom and this last attribute change callback is rather self explanatory. And there it's important to notice that we can also dispatch our custom events so as opposed to the already built in ones, for example on click mouse, move on Blur, we can also define our own custom event. So we say the name of the event and arbitrary content. After we define this, it's also important to actually insert it into the Custom Elements registry. And the Custom Elements API already offers this defined method. So we need to define the name under which the definition class will be registered. And after this is done then the element is ready to be used so we can use it directly into the Dom or created via JavaScript. And there is also one other thing. So Custom Elements also offers this get method. So by the name of the tag we fetch the definition and then we can use a new constructor in order to have a new instance. This is important in case we need like some custom injectors, so it offers a more flexible way. And now a small demo. But firstly because I didn't have like additional monitor and in interest of time because I need to squeeze them, I will just paste code to see that this works. So believe me this is like the exact content what you saw on the slides and I'm pasting up until the point that we define the element into the Custom Elements registry. So now we can actually try first to create the document and also append it to the Dom. Yes, we see that the element created and the element connected callbacks are fired and now into the elements console we can see that our traffic light instance has been created. Since this is an ordinary element, we can do the same thing that we do to other components. For example we can query this tag and then attach event listener. In our case it will listen to the color state which is basically dispatched above and then we will just lock the state of the traffic rate. So if I paste this here it's fine. And then what happens if for example I programmatically want to change the green property of the traffic light? So as we see here now we have printed the state. What basically happens is that once we set the property, this in turn calls the property setter which in turn calls the attribute setter which triggers the attribute change callback, and this dispatches this custom event which we actually listen to and therefore the state is printed. So basically this works. Also, I do not need to do everything like into the consulting. I can for example duplicate this element. Notice that the green flag went back to false because we are setting the initial state into the connected callback. And since this is an independent instance, I can also use for example select or all and attach additional event listener here and then we can change the state to see that these are independent. Again, I do not need to do it via the console. So for example I can go here and I will just change this one to true. Going back into the console. We see now that again everything works because we have printed on the console the state of the traffic light. Also we can try out the last thing and that is to use the Get method on the Custom Elements API in order to create a new instance. I can actually copy the quoting also to append it to the Dom and here we see basically no difference. So again the two callbacks code and into the elements tab we have like our third traffic light instance. The last thing to check here is to delete something. I can also do it from here. For example I will delete this element and if we see into the console this element disconnected has been called. So basically everything works. Okay, I will for the next demo. So back here now the question is that you already saw that basically where is Angular in this picture? Because everything we did with Vanilla JS and everything seems to work. Do we need the framework support at all? And the key point here is that when I was displaying this, we're talking about a basic component. But as soon as we are approaching like more real world like examples, things start to get complicated. And what do we do when things are too complicated and repetitive to do them by ourselves? We delegate them. So in our case the Angular itself is the perfect candidate to do actually the heavy lifting for us. But we hope that as opposed to this example here, in our case there will be a heavier ending. So it's about to see now just briefly the equivalence. So what Angular does. On the left hand side we have the custom elements API, on the right hand side we have the equivalents of the entities into the Angular world and other things. So again as we saw, on the left hand side we have the reactions and on the right hand side you will recognize the life cycle hooks in Angular. So again the mapping is rather straightforward. So now basically two things remain. The one is to see which is the exactly API that Angular offers for creating custom elements and where to write it. And as we see there is exactly one method and that is Create Custom element which accepts two things. The first one is definition of Angular component and the second one is the injector. The injector is needed in case the component comes with its own dependencies and therefore they need to be properly wired. The output is this HTML class definition that we saw previously and the next line is already known defined method and the last most important thing where to write this code. So this is like the first alternative to write it on a component level, but this is also actually the least preferred one because then we would need to bootstrap this component and most probably we wouldn't like to do this. So a better alternative would be to do it on a module level into the constructor. Also you can do it on the module level inside the ng do bootstrap method or you can even wait for the module to be bootstrapped and then get a hold of the injector and then create the custom elements. So all of this work also a small detail which is actually not easy to forget because the compiler will complain. We would still need to add the custom element schema in order for the browser not to complain about unknown HTML element. And some of you have worked previously with Angular elements you will see that now the entry components is missing. This is so because with the introduction of Ivy this local detection has improved and therefore all of the dynamic rated components are already part of it, meaning that we do not need to explicitly define it. The last thing is about packaging. So after we built the component we see that we will get like three files and we want to create one. So if we take the right hand side we will see that there are these two bundles because we use some differential Loading and we are calling the concat method in order to create like two separate files. After this we just copy the contents from the disk folder into our own. And this last thing about Web components bundles, we'll get to that in a minute. The last thing here is of course to create like a custom script inside package. Json build elements what this does is basically a productive build without hashes and then calls the elements build. Js which is the content that you have above. I mentioned on the previous slide about differential Loading, so maybe a refresher. For those of you who are not familiarized, starting from version eight, I think differential Loading is in place. So previously there was one bundle which needed to serve both evergreen and none evergreen browsers and now we have two different bundles. And if you see in the numbers above, the main difference is in the polyfills because obviously Internet Explorer needs more polyfills than other browsers do. How do we actually browsers distinguish which of these bundles to take it's based on the type attribute in the script? So if it's type module then it's evergreen browser and if not then it's Internet Explorer. Also here is like the latest can I use support about the custom elements and the Shadow Dome so we can see that it's mostly like green areas with the usual suspects in the red area. So now maybe I can just switch again now to the console. Is this one big enough? Not sure how to Zoom in here. How about now? Okay, so here we have like a small example. We have this toast component which basically has two slots. The first one is unnamed, the second one is a default one and there we listen to the slot changed event. So here we have a default also content and if we looked at the TypeScript, what we basically do is just take all the content, get all the input notes and attach a click listener. This listener doesn't do much, it's just displays like an alert box and after that it increments a counter which we basically emit to show that also the input output mechanism is working and then we're still in the angular world. So here into our app component we have defined some content for the header slot and also some content for the unnamed slot. And here we just again lock the details. In our case the details is basically the counter level. So I have already started this demo and basically here we see the radio button group and also the heading up. So what do we expect to do here is basically if I click on some of this we have the alert box shown and also here in the console we see that the counter is displayed. So for example again if I click here we have the counter. And now to see whether this works on all browsers. So next we have Firefox. So again clicking here this is working. Now if we go to edge we also expect that this is working. And lastly Internet Explorer and I do not need to Zoom here because as expected nothing is working. So the question here is that it's also problematic that for example into the console we don't see anything. So what the hell is there? But if we go into the Dom Explorer maybe here it is worth zooming. There we see that Internet Explorer tries to load module definitions, although it doesn't understand them and this is the case because we are using differential Loading here. So now the thing is that the angular CLA the reason is pretty obvious if you know it simply doesn't support this for the development goals and that is the surf and the watch and that is exactly what we are using. So in order to get past that we have like two options. For the first one I will give up the differential Loading and restart this one. And the second one is actually you need to use like a separate build configuration. So if you start this for development mode for I only then use that configuration, otherwise use the default one. So this takes a bit of time and once this is over now actually we expect that the error is resolved. And to verify this now we see that everything works pretty intuitive right? Internet Explorer. Okay so now I will just revert this back and also one other thing that I keep forgetting and then I get like false alarms. I would need to comment out this bootstrap of the component. So then I will go into the terminal and run this script that we already saw. So this is like the familiar command, we are just generating a productive build. So to check whether now this is working if we do not have angular support, this is basically our goal. This yellow warning is something that will be announced near the end of the presentation. So as we see here this one is finished. And right now I'm using like an Http server in order to serve this. So if I open this and then I click now we have like the static content meaning that this is actually working. And in order to confirm this completely I will need just to add this attaching of the event listener. Okay so now if I click here I also see that the counter is being displayed three because the first time I clicked we didn't listen to the event and this is test. Now in the interest of time also I will not show for the Firefox and H because there we are not interested. We expect this to work. And now again the main question will this work on Internet Explorer? But before that there is a story to show. So you saw already on the previous slide that there were some browsers that simply didn't want to cooperate with novel things. And in order to cope with this if you needed to support like Internet Explorer at some time you say okay I will use just polyfills. It's a common thing but there were some other things. For example this set Es Five adapter which is also poorly named. So think before differential Loading there was this one bundle and then when you need to transpile to Es Five, there are some classes which needed Es Six support, meaning that you are breaking something that evergreen browsers already understand to and this adapter is needed in order to revert that back. But however you need to include it conditionally because otherwise the Internet Explorer will break twice if you didn't get this. Don't worry because JavaScript to its eternity is a mystery to every developer regarding of years of experience. But as we said, this is like gone because we use differential Loading. And now the next question is to choose the right polyfill and after some short search we'll find like Web components. Js. And with this it seems like it's the Holy grail of browser compatibility. It's advertised. You have this one Web Components bundle, you load it via script tag and it works. And that's what I did. I loaded the Web Components bundle via the script tag and it didn't work. It didn't work from the first time, or for the second time, or for the third time, not even for the fourth time. However, there is this out of stake space error which is a bit specific. And I said okay, let's look at some forms to see if we can find a solution. And the reason why this doesn't work is actually a broken polyfill, which is a bit sad because you know, polyfills are supposed to be part of the solution and not part of the problem. But in the source code we can see that this broken polyfill gets loaded only if it's not previously defined. So if we can find the correct polyfill and load it before that to prevent the Loading of the correct one, then we should get past this error. Since I mentioned polyfill too many times, maybe here it is a bit better concept to explain what happens here. So we need to load a polyfill. This is for this symbol. Js. We use the reliable library core. Js in order to replace a broken polyfill, which is the corrupt version of symbol. Js inside another polyfill. And that's the polyfill for the Shadow Dome and the custom elements that comes from the Web Components bundle. So if you let this sink in, maybe eventually it will make some sense. Otherwise this is like the more common reaction to all of this. And now the question again, will it work for Internet Explorer? Yeah, I would need just to copy this. Fingers crossed it works. Finally it works. Okay, so maybe just other things. I will try this one on Chrome, not to experiment. So it's important to notice that when we're talking about changes, we need to do this on the top level change. Otherwise this will not work. I mean the content change event will not be triggered. So for example, if I have here into the shadow root anti edit HTML and here if I define this so we have display random message, but if I click on it nothing happens because it is not a top level change. However if I choose for example also other input, let's say input type button and now if I try to do the same thing but after the diff. So now we can see that the button is here but now the input change event is triggered. It's important to know, otherwise you could be surprised. Okay, so now back to the presentation. And of course it's time to do the happy dance because we didn't have any issues here. You already saw this yellow warning and this is actually a very good and long awaited news. So starting from Angular version 13, Internet Explorer support will finally be dropped. And I suppose this will be like a great relief to every year and developer that actually suffered during supporting it. The last thing I would like to mention is about the bundle sizes. So as you know the Angular team had an internal goal of making a Hello World program under 10 KB in order to receive a cake from their manager. And that's what they did with aggressive tree shaking. On our situation we see that Ivy for example has decreased a bundle, but we are talking about still like around 200 kw for a component which might be too much. There are some alternatives, for example Mantra's contribution of NGX build plus. So what this does is that it creates an external score and basically all the common runtime from Angular is extracted and put directly into the scripts array in AngularJS. Here you have the three most important things, how to add the extension, how to create the external school, and how to run it. The result from this, as we see now, the main bundle is tiny, but on the other hand the scripts bundle is a bit bigger because there is no possibility to insert tree shaking in between. However, this isn't meant to do if you have like only one component. So you should do basically the math how much is one component and then to see how big is the script. And then if you divide you select from what point on it is worth using this because then for every other component we'll just have this incremental change of a couple of kilobytes per component. The other thing here is also dropping off zone. Js as it was defined as a culprit with over 100 KB. And you could do this easily if you say ng zone, no op. But the drawback is that you will use change detection. And for example this could be you that just drops his own JS, or even better, see that this is the Angular framework working happily. And then this brick was you dropping zone. Js and of course everything was great. So the recommendation for now is just don't do it however, we can see that on the angular roadmap there is a possibility to offer full framework capabilities with Zone JSS opt out. So we're looking forward to that if something of this sparked your interest. So here are like five things. The first two are regarding theory so the web components and the polymer library and the second three are selected list of talks so old but gold first talk from Rob wmalt regarding angular elements, then Pascal practice presentation and last one mantras workshop a deep look at angular elements and now since we started it, I would also like to say a couple of words in Hebrew. I hope I get it right. So, ToddA Alte sumatale thank you. You.