Video details

NG-BE 2021 - Maria Korneeva - Flexible data flow architecture in Angular

Angular
12.09.2021
English

https://ng-be.org/talks/2021/12/3/maria-korneeva
Maria is a Frontend Technology Lead at Aleri with current focus on Angular. She writes for ng-conf and speaks now and there at tech meetups and conferences. In her tweets and articles Maria likes to share her learnings from everyday coding. Fun fact: she illustrates her stories herself because she likes drawing.
Learn more about Maria at https://browserperson.medium.com/, https://twitter.com/BrowserPerson or https://www.linkedin.com/in/maria-korneeva/

Transcript

Now we have someone very special on stage. This is Maria. She has already done multiple talks, but. This is her first real life talk. This is exciting, right? Yeah. More scary. I'm not alone here during the first time. So let's welcome her with our applause and let her do the thing. So hello everyone, and thank you for having me here. Today. We're going to talk about flexible data flow architecture and angular. And before we dive into the topic, I want to steal a couple of seconds of your attention to introduce myself. As Xenia stated, my name is Maria Kaneva. I mostly identify as human beings. I also have some other properties, but they're not relevant for this lovely conference. And to justify me standing on the stage, I work as a front end technology lead at a middle sized company called Alari and I have a PhD in linguistics. So I did study linguistics, but I didn't like it because there is no true or false. There is no valid or Invalid. There is no compiled or not compiled. So it depends. It's always someone's opinion, someone's authority. So I fleet to web development and what I have here, I have here some architectural questions. And again, it depends. And again, there is no true or false. There is no right or wrong. So I have to argument. I have to deep dive into the code. I have to talk to stakeholders and there is no right solution. There is just the most suitable solution for this particular use case. This is challenging. However, this is also really interesting because it makes you to deal with the code concepts. It makes you to talk to people. That's why today I brought this topic to you. So we're going to talk about the software architecture. And one cannot talk about software architecture without showing the obligatory slide with some random pictures of buildings on it. So here's the obligatory slide with some random pictures of buildings on it and the definition of the software architecture. So this is a set of software elements among them and the properties of the elements and relations. The topic itself is huge. So there is no way I can cover the software architecture in itself in 20 minutes. So we have to focus on some parts of it. We could talk about how many floors the building should have. That would be something like layered architecture. We could talk about how many flats should be on one floor or what rooms should one flat have. This would be something like component design or structure. But today I'm going to focus on stairs, windows, corridors or elevators. So how do you come from the balcony to the living room? How many windows do you have in your bathroom? So in other words, I'm going to talk about how you pass around the data in an angular application. In particular, I'm going to talk about these three aspects. So what are the options Angular gives you to share data among the items, like among the components, services, whatever. Where do you want to keep your data or handle it and where do you need it? So what are the options when you can require some data? And at the beginning of my talk I have to disappoint you. I won't talk about state management in terms of NJX or NGX or any other state management frameworks. I will merely concentrate on the native Angular capabilities because this is enough to fill 20 minutes. That's how I found out this when practicing. And all the state management best practices are worth a separate talk. Maybe at the next Ngbe conference. So let's start. Since we are talking about data, we can remove all the styling and we just end up with two parts. We have the TypeScript code and we have the HTML template and moving data in the code is no Biggie. So we won't talk about this. But you might want to move your data from the TypeScript code to the template or get some data from the template to the TypeScript. So at the component level you have the following options. You have the text interpolation including pipes. You have property binding, you have directives, something like structural directives, Ng, four or maybe even custom directives that can move some data to your template. If you use some forms you have the option of ng model or form group, then you might get the two way binding and the opposite way around. You can get some data from your template through Dom events. When you get the event details, some clicking, some focusing, something like this. If you want to just share the data within your template, you can go with the template variable. So you use the hashtag, you put the variable on some tag and then you can request this reference this component beneath or above. You have also the walk around option via the code. So these are the options at the component level, but front end is all about nesting. So when you have the nested component you still have all the previous options. How to share data but you also want to share data between the TypeScript parts, between the parent and child and also the HTML parts. So if you have parentchild relationship there's this best practice that you all well know of inputs and outputs. You can also use children, but bear in mind what Philip just told you, there are some security issues if you have the component rep and you also might go with content child or content children depending on the nesting type. So if you go with the content projection you probably use content child. Otherwise you would use few child and you have the option of creating components dynamically. It's not really sharing data, but I just find it crazy that you have this option with create methods, creating some runtime components with a create component method that Angular gives you. And if you want to share data between the HTML templates, you can again use this local template variable. So again you put this hashtag variable on your child and then in your parent you can even call some methods of your child. However, there is a caveat because when you reference like this you reference just the instance but not the class of your child component. So if you need something static, if you need something from the class you'd better go with viewchild. You can again go via the TypeScript code. So this is a workaround with inputs, outputs, whatever. And you have the option of content protection which is kind of data sharing because you put some data into your parent. Moving further, this is again a quite typical situation. You have siblings, they should communicate via the parent and if the components are not related you have services. So this is probably something that you already know at the app level. You have a couple more options. So if you want to share data between some remote components you have the router data so you can access it via snapshot. For example, in my current project we place the title of a component in the snapshot and using the app title service we just update the page title which is bound to this component to this route. You can also share the data using injection token, so you might share some configuration data. With this option you still have services, you might use the perimeterized route, so something like productdetal product ID. So you might get the product ID in your components. You might share this information by the route and you have the good old URL parameters. So moving further, the next step is what if you want to communicate or share data between two angle applications, or maybe between the same application. If the user just hits refresh and the app has to bootstrap again, you might store the state or something what you need to store in the back end. That's a valid solution, but you can also leverage the capabilities of your browser. So you might store something and retrieve it afterwards in the session. Storage in the local storage or use some cookies. We use local storage for example to save the settings configuration of the user, so if the user comes back it's stored in the browser. However, please do not store there are any sensitive data and again the good old URL parameters. Here we actually use this option. We have an Angular app which holds the overview over all settings of the user and we have a different app which helps to edit those settings and we pass the particular mode or the particular settings item by the parameters so that the editing app knows what to do. You can't react upon this. So here's the overview of all the options that I've mentioned. So how do you get the date in your component? You have the options that come from the TypeScript. You have the options that come from your template, you have the options that come from outside with the Ajax request or some file reading. You have the data that you can retrieve from the browser, and you have some other techniques like directives that might just apply some data on your component without you being controlling this from your component. So this is a lot and that makes you actually flexible in your choices. But as you know, with the great flexibility, the great responsibility comes, so you have to choose wisely. And for this you should better follow some best practices. And this leads me to the next slide. Design Patterns It's a very interesting topic and actually it's worth another topic at the next NGP conference. But today I just brought four of them and I want to look at them from the data perspective. The first is called Mediator, and this is pretty straightforward. You don't want your sibling components communicate directly to each other because then they get tightly coupled. You want them to communicate via the common parent or maybe via the service if they're not related. So you need a communicator which decouples it. The next design pattern called bridge goes in the same direction, so it also helps to share your data in a way that your components stay decoupled. In the context of Angular, if you have content projection, do you not reference in your parent the hard coded content component? Instead, you should reference an abstraction, maybe some injection token in your content component. You should just implement this interface. So the data sharing occurs on the two components. The parent one and the content component connect each other on the abstraction level so that the data sharing again enables the loosely coupled way of sharing data. The next one is Singleton. This one is simple. Usually most of the service in Angular are singletons, so this enables you to update the data and keep it in a consistent way. And the last one on the slide is the chain of responsibility. And in the Angular world, this would be the chain of Http interceptors. So the request gets in and it gets processed till all the Http interceptors are done. So you don't have to care about how to move this request from one interceptor to the other. You just get it in and then get it out and it all is handled in an automated way. So these four examples show you that the way you share data between your components or items may affect the design of your application. And you should aim for loose coupling, consistent data, and automated data sharing, something like Http interceptors or maybe a sync pipe in your template. So the next aspect is where to keep or handle your data. And here I have the very well known example of the Darmin smart components. It depends where you want to retrieve and handle, process the data and where you just want to represent it. There is a common belief that the upper level components should be the smart components, and the leaves should be represented just merely representative ones. However, in this design you might propagate all the data all the way down to your lead, and all the intermediate components will have a lot of overhead without benefits. So maybe if the chain of nesting is so long, you might reconsider your choice and design your leaf component also as a smart component which will retrieve and handle the data in encapsulated ways so you don't have to propagate it any longer. With this, there is another tradeoff or another set of options regarding the responsibility for the input. Here we have a following scenario. Imagine your child component has to implement some dark mode or light mode, or any mode. In the first option, the child component can handle it by itself, so that the parent has no responsibility for whatsoever happens there. The next one would be that the parent component sets some input true or false, and the child component decides upon this input what mode to implement. The next stage would be that the parent component inserts the mode completely, and then the parent component has the full responsibility. The moral responsibility for this and the last step would be that the parent component completely inserts the Insights, which is basically content projection. So this trade off is interested, particularly if you have some Stylist team which delivers web components, so they have the choice of baking this button in or letting you to insert your custom button. The next aspect that I'm going to touch upon is the time limit. So let's have a look at the options that Angular gives us. The first one is not really about data sharing, it's more about synchronizing or updating. So depending on which strategy you decide to follow, your data will be updated synchronized more often, or maybe on some special occasions. But if you need some data in your component, you should go with the lifecycle hooks. You probably will retrieve something in the ongoing. Hook. But sometimes rendering this component doesn't make sense if the data is not there, like the server's down happens, and then you should better show something else. In this case, you'd better go with the Resolver which is there to fetch the data before the component is being instantiated. On the module level, you have the well known concept of laser Loading. Depending on the strategy, you can decide which ones and how and when the components should be loaded. The module should be loaded. But if you need the data even before the app bootstraps, then you probably better go with the injection token app initialiser and there you have the opportunity to fetch the data even before the app put strips. This would be a use case if you have some authentication. For example, if you're working on the user profile and all your app the premise of all your app is about that the user has to be authenticated then you need to check it before the output straps and then you can react upon it and maybe direct the user somewhere else. So let's wrap up when you have your architectural decisions it might be helpful to take the data perspective on it. And when you have this perspective you might think about what are your options, what angle gives you how can you get the data or how can you pass the data around? The second question would be where you want to handle this data and the third when do you need this data? So if you combine all these three aspects I hope that you will come to the best suitable solution for your use case that's it for today. Thank you for your attention and if you have some questions just look around for the person with Maria on the back for the discussion afterwards. Thank you.