{"id":36718,"date":"2022-06-09T18:03:52","date_gmt":"2022-06-09T16:03:52","guid":{"rendered":"https:\/\/www.inovex.de\/?p=36718"},"modified":"2022-11-17T12:59:22","modified_gmt":"2022-11-17T11:59:22","slug":"angular-master-detail-views-reduced-boilerplate","status":"publish","type":"post","link":"https:\/\/www.inovex.de\/de\/blog\/angular-master-detail-views-reduced-boilerplate\/","title":{"rendered":"How to Reduce Boilerplate Code for Angular Master-Detail Views"},"content":{"rendered":"<p>In this blog post, we will implement an architecture for Angular applications that removes two sources of boilerplate from Master-Detail views using Auxiliary Routes and Resolvers.<!--more--><\/p>\n<div id=\"ez-toc-container\" class=\"ez-toc-v2_0_82_2 counter-hierarchy ez-toc-counter ez-toc-custom ez-toc-container-direction\">\n<div class=\"ez-toc-title-container\"><p class=\"ez-toc-title\" style=\"cursor:inherit\"><\/p>\n<\/div><nav><ul class='ez-toc-list ez-toc-list-level-1 ' ><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-1\" href=\"https:\/\/www.inovex.de\/de\/blog\/angular-master-detail-views-reduced-boilerplate\/#Intro-to-Master-Detail-views\" >Intro to Master-Detail views<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-2\" href=\"https:\/\/www.inovex.de\/de\/blog\/angular-master-detail-views-reduced-boilerplate\/#Implementation\" >Implementation<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-3\" href=\"https:\/\/www.inovex.de\/de\/blog\/angular-master-detail-views-reduced-boilerplate\/#Basic-Structure\" >Basic Structure<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-4\" href=\"https:\/\/www.inovex.de\/de\/blog\/angular-master-detail-views-reduced-boilerplate\/#Loading-Data-for-the-Detail-Views\" >Loading Data for the Detail Views<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-5\" href=\"https:\/\/www.inovex.de\/de\/blog\/angular-master-detail-views-reduced-boilerplate\/#Triggering-a-Reload\" >Triggering a Reload<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-6\" href=\"https:\/\/www.inovex.de\/de\/blog\/angular-master-detail-views-reduced-boilerplate\/#Access-to-Current-State\" >Access to Current State<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-7\" href=\"https:\/\/www.inovex.de\/de\/blog\/angular-master-detail-views-reduced-boilerplate\/#Discussion\" >Discussion<\/a><\/li><\/ul><\/nav><\/div>\n<h2><span class=\"ez-toc-section\" id=\"Intro-to-Master-Detail-views\"><\/span>Intro to Master-Detail views<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>In admin dashboard-type applications, we often display lists of data and need to provide a detailed view of an individual entry. An often-used pattern for implementing this requirement is the Master-Detail UI. This pattern provides a nice UX but the implementation often requires some boilerplate.<\/p>\n<p>One source of boilerplate is needing to implement the same Master-Detail pattern in multiple pages of your application. Another is needing to handle loading and error states in the detail views.<\/p>\n<p>In this blog post, we will discuss an Angular architecture for implementing Master-Detail UIs that reduces the required boilerplate using mechanisms provided by the router. As an additional benefit, we will see how we can access the state of the detail view from anywhere in our application without the need for a store.<\/p>\n<p>The code as well as an interactive demo can be found on <a href=\"https:\/\/github.com\/inovex\/blog-angular-resolver-arch\" target=\"_blank\" rel=\"noopener\">GitHub<\/a>.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"Implementation\"><\/span>Implementation<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<h3><span class=\"ez-toc-section\" id=\"Basic-Structure\"><\/span>Basic Structure<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>In the first step, we build a basic layout that includes router outlets for both, the master and the detail layout. We use auxiliary routes, meaning the detail views are not children of their respective master views. The \u201coutlet\u201c properties of the detail routes correspond to the \u201cname\u201c property of the router outlet.<\/p>\n<pre class=\"lang:xhtml decode:true\">&lt;div class=\"content\"&gt;\r\n &lt;div class=\"left-content\"&gt;\r\n   &lt;router-outlet&gt;&lt;\/router-outlet&gt;\r\n &lt;\/div&gt;\r\n\r\n &lt;div class=\"right-content\"&gt;\r\n   &lt;router-outlet name=\"details\"&gt;&lt;\/router-outlet&gt;\r\n &lt;\/div&gt;\r\n&lt;\/div&gt;<\/pre>\n<pre class=\"lang:js decode:true\">const routes: Routes = [\r\n ...,\r\n {\r\n   path: 'characters',\r\n   component: CharactersComponent,\r\n },\r\n {\r\n   path: 'characters\/:id',\r\n   component: CharacterComponent,\r\n   outlet: 'details',\r\n },\r\n {\r\n   path: 'movies',\r\n   component: MoviesComponent,\r\n },\r\n {\r\n   path: 'movies\/:id',\r\n   component: MovieComponent,\r\n   outlet: 'details',\r\n },\r\n];\r\n<\/pre>\n<p>The main benefit of this approach is that we only have to implement the Master-Detail styles once in our parent component. The components used by the individual routes only need to provide the content that goes inside the left and right pane. As a side effect, the detail view will stay open when you navigate to a different master view. Depending on the use case, this can be a great improvement in the useability of your application.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-36724 size-full\" src=\"https:\/\/www.inovex.de\/wp-content\/uploads\/routes.gif\" alt=\"\" width=\"1272\" height=\"682\" \/><\/p>\n<p>To open a detail route, we use router links with a special format. Below is the route we want to navigate to and the respective router link.<\/p>\n<pre class=\"lang:js decode:true \">{\r\n  path: 'characters\/:id',\r\n  component: CharacterComponent,\r\n  outlet: 'details',\r\n}<\/pre>\n<pre class=\"lang:xhtml decode:true \">&lt;a [routerLink]=\"['\/', { outlets: { details: ['characters', character.id] } }]\"&gt;\r\n  {{character.name}}\r\n&lt;\/a&gt;<\/pre>\n<p>Let\u2019s break down the format. The router link is an array of path segments. The first segment \u201c\/\u201c means that we start at the root. The second segment is an object with the single property \u201coutlets\u201c, meaning that we provide destinations for specific outlets separately. The keys of this object are the outlet names we defined in our app.component.html, in this case, the outlet name is \u201cdetails\u201c. The value is again an array of path segments. The path of our route for the CharacterComponent is defined as \u201ccharacters\/:id\u201c and the entries of the array &#8222;&#8218;characters'&#8220; and &#8222;character.id\u201c correspond to these segments.<\/p>\n<p>We can style the currently selected item, in our example, it is bold, by applying the \u201crouterLinkActive\u201c directive.<\/p>\n<p>Closing an auxiliary route is possible by providing \u201cnull\u201c as the value for the respective outlet\u2019s path.<\/p>\n<pre class=\"lang:xhtml decode:true \">&lt;a [routerLink]=\"['\/', { outlets: { details: null }}]\"&gt;Close&lt;\/a&gt;<\/pre>\n<h3><span class=\"ez-toc-section\" id=\"Loading-Data-for-the-Detail-Views\"><\/span>Loading Data for the Detail Views<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>In our architecture, loading the data is not the responsibility of the component but rather a class that implements the Resolve&lt;T&gt; interface. The interface has only one method named \u201cresolve\u201c that needs to return an Observable, a Promise or an instance of T. In this method, we have access to the route that is being navigated which can be used to access path or query parameters.<\/p>\n<pre class=\"lang:js decode:true \">@Injectable({ providedIn: 'root' })\r\nexport class CharacterResolver implements Resolve&lt;Character&gt; {\r\n constructor(private http: HttpClient) {}\r\n\r\n resolve(\r\n   route: ActivatedRouteSnapshot, \r\n   state: RouterStateSnapshot\r\n ): Observable&lt;T&gt; | Promise&lt;T&gt; | T {\r\n   return this.http.get&lt;Character&gt;('https:\/\/swapi.dev\/api\/people\/' + route.paramMap.get('id'));\r\n }\r\n}<\/pre>\n<p>In our case, the resolver is very simple but it does not always have to be. If the component requires data to be aggregated or transformed, it is a good idea to put the logic inside the resolver and keep the component simple. If the logic is sufficiently complex, it can be a good candidate for a unit test.<\/p>\n<p>The route configuration needs to be extended to reference the resolver.<\/p>\n<pre class=\"lang:js decode:true \">{\r\n path: 'characters\/:id',\r\n component: CharacterComponent,\r\n outlet: 'details',\r\n resolve: {\r\n   character: CharacterResolver,\r\n },\r\n}<\/pre>\n<p>In our component, we access the loaded data using the route\u2019s \u201cdata\u201c property which is an Observable. Note that it is <a href=\"https:\/\/angular.io\/guide\/router-tutorial-toh#observable-parammap-and-component-reuse\" target=\"_blank\" rel=\"noopener\">not required<\/a> to unsubscribe from this Observable, as the component class and the ActivatedRoute object have the same lifecycle.<\/p>\n<pre class=\"lang:js decode:true \">@Component({ ... })\r\nexport class CharacterComponent implements OnInit {\r\n\r\n character!: Character;\r\n\r\n constructor(private route: ActivatedRoute) {}\r\n\r\n ngOnInit() {\r\n   this.route.data.subscribe(data =&gt; {\r\n     this.character = data['character'];\r\n   })\r\n }\r\n}<\/pre>\n<p>For a good UX, we want to handle loading and error states. In the traditional approach, where the component is responsible for fetching its data, this would be a source of boilerplate. However, in our architecture, we can put this logic in one central place, removing the need to handle it in the components.<\/p>\n<p>For our example, we will use the <a href=\"https:\/\/www.npmjs.com\/package\/@ngx-loading-bar\/core\" target=\"_blank\" rel=\"noopener\">@ngx-loading-bar\/core<\/a> library. We include the loading bar in our AppComponent\u2019s template.<\/p>\n<pre class=\"lang:xhtml decode:true \">&lt;ngx-loading-bar [includeSpinner]=\"false\"&gt;&lt;\/ngx-loading-bar&gt;<\/pre>\n<p>Next, we introduce a base class for our resolvers that handles loading and errors.<\/p>\n<pre class=\"lang:js decode:true \">@Injectable({ providedIn: 'root' })\r\nexport abstract class BaseResolver&lt;T&gt; implements Resolve&lt;T&gt; {\r\n private loadingBar: LoadingBarState;\r\n private snackbar: MatSnackBar;\r\n\r\n protected constructor(injector: Injector) {\r\n   this.loadingBar = injector.get(LoadingBarService).useRef();\r\n   this.snackbar = injector.get(MatSnackBar);\r\n }\r\n\r\n resolve(route: ActivatedRouteSnapshot) {\r\n   const result = this.resolveInternal(route);\r\n\r\n   const observable = isObservable(result) ? result : from(Promise.resolve(result));\r\n\r\n   this.loadingBar.start();\r\n   return observable.pipe(\r\n     catchError(e =&gt; this.handleResolverError(e)),\r\n     finalize(() =&gt; this.loadingBar.complete()),\r\n   );\r\n }\r\n\r\n abstract resolveInternal(route: ActivatedRouteSnapshot): Observable&lt;T&gt; | Promise&lt;T&gt; | T\r\n\r\n handleResolverError(e: unknown) {\r\n   console.error(e);\r\n   this.snackbar.open('An error occurred while loading.', undefined, { duration: 2_000 });\r\n   return EMPTY;\r\n }\r\n}<\/pre>\n<p>The resolve method now delegates the actual fetching of data to an abstract method implemented by the subclasses. Before returning the result, the loading animation is started. The result is converted into an observable and two operators are applied, \u201ccatchError\u201c for displaying a <a href=\"https:\/\/material.angular.io\/components\/snack-bar\/overview\" target=\"_blank\" rel=\"noopener\">material snackbar<\/a> when an error happens and \u201cfinalize\u201c which is used to stop the loading animation when the result is ready.<\/p>\n<p>Our actual resolvers need to extend this class.<\/p>\n<pre class=\"lang:js decode:true \">@Injectable({ providedIn: 'root' })\r\nexport class CharacterResolver extends BaseResolver&lt;Character&gt; {\r\n\r\n constructor(private http: HttpClient, injector: Injector) {\r\n   super(injector);\r\n }\r\n\r\n resolveInternal(route: ActivatedRouteSnapshot) {\r\n   return this.http.get&lt;Character&gt;('https:\/\/swapi.dev\/api\/people\/' + route.paramMap.get('id'));\r\n }\r\n}<\/pre>\n<p>Below we can see the result in action.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-36727 size-full\" src=\"https:\/\/www.inovex.de\/wp-content\/uploads\/loading-and-error.gif\" alt=\"\" width=\"1270\" height=\"682\" \/><\/p>\n<h3><span class=\"ez-toc-section\" id=\"Triggering-a-Reload\"><\/span>Triggering a Reload<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>In some cases, we might want to trigger a reload of the data. This can be achieved by first setting the \u201conSameUrlNavigation\u201c property to \u201creload\u201c in the RouterModule.<\/p>\n<pre class=\"lang:js decode:true \">@NgModule({\r\n imports: [RouterModule.forRoot(routes, { onSameUrlNavigation: 'reload' })],\r\n exports: [RouterModule],\r\n})\r\nexport class AppRoutingModule {\r\n}\r\n<\/pre>\n<p>This change does not do anything on its own. However, in combination with the route config\u2019s \u201c<a href=\"https:\/\/angular.io\/api\/router\/Route#runGuardsAndResolvers\" target=\"_blank\" rel=\"noopener\">runGuardsAndResolvers<\/a>\u201c property, we can use it to make resolvers run again when we navigate to the currently active route. However, we cannot set the property statically as it, unfortunately, would also make the resolvers run when we navigate to a different master view while the detail view is active. To work around this problem, we want to set this property at runtime only when we want to trigger a reload.<\/p>\n<p>A service class \u201cRoutingService\u201c implementing this logic is available <a href=\"https:\/\/github.com\/inovex\/blog-angular-resolver-arch\/blob\/master\/src\/app\/routing.service.ts\" target=\"_blank\" rel=\"noopener\">on GitHub<\/a>. Feel free to copy the class to your project. In our component, all we have to do to trigger a reload is to call the \u201creloadDetails\u201c method on the RoutingService instance injected in the constructor.<\/p>\n<pre class=\"lang:js decode:true \">@Component({ ... })\r\nexport class CharacterComponent {\r\n\r\n constructor(\r\n   ...,\r\n   private routingService: RoutingService,\r\n ) {\r\n }\r\n\r\n async reload() {\r\n   await this.routingService.reloadDetails();\r\n }\r\n}<\/pre>\n<h3><span class=\"ez-toc-section\" id=\"Access-to-Current-State\"><\/span>Access to Current State<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>In some situations, we might want to access the current state, i.e., the result of the resolver of the currently active detail view. Traditionally, this would be achieved using a store. However, in our architecture, the state can be accessed using nothing but the router. Again, the code is available <a href=\"https:\/\/github.com\/inovex\/blog-angular-resolver-arch\/blob\/master\/src\/app\/routing.service.ts\" target=\"_blank\" rel=\"noopener\">on GitHub<\/a> in the form of the RoutingService class. It provides a getter property \u201cdetailsData\u201c to access the currently active route\u2019s data as well as an observable version \u201cdetailsData$\u201c that emits whenever navigation is completed.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"Discussion\"><\/span>Discussion<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>In this blog post, we have implemented an architecture for Angular applications that removes two sources of boilerplate from Master-Detail views: having to reimplement the layout on different pages and having to handle loading and error states in the detail views. The architecture offers the possibility to access the detail state anywhere in the application removing the need for a store.<\/p>\n<p>Of course, this architecture comes with some compromises. Because the loading and error states are pulled out of the detail views\u2019 components, the previously active detail view will stay visible while the next one is loaded or when it fails to load. This might or might not be a problem depending on your requirements. Having to implement a resolver for every detail component can be seen as boilerplate, though I would argue that it is outweighed by the benefits of separating the concerns of fetching and displaying the data as well as the testability of the resolver services.<\/p>\n<p>Like with everything in programming, the proposed architecture will fit some use cases better than others and should be seen as an additional tool in your toolbelt.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In this blog post, we will implement an architecture for Angular applications that removes two sources of boilerplate from Master-Detail views using Auxiliary Routes and Resolvers.<\/p>\n","protected":false},"author":285,"featured_media":36747,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"inline_featured_image":false,"ep_exclude_from_search":false,"footnotes":""},"tags":[128,70],"service":[420],"coauthors":[{"id":285,"display_name":"Kirill Rakhman","user_nicename":"krakhman"}],"class_list":["post-36718","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","tag-angular","tag-web","service-apps"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.5 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>How to Reduce Boilerplate Code for Angular Master-Detail Views - inovex GmbH<\/title>\n<meta name=\"description\" content=\"A how-to guide for an architecture reducing the boilerplate in Angular master-detail views using Auxiliary Routes and Resolvers.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.inovex.de\/de\/blog\/angular-master-detail-views-reduced-boilerplate\/\" \/>\n<meta property=\"og:locale\" content=\"de_DE\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"How to Reduce Boilerplate Code for Angular Master-Detail Views - inovex GmbH\" \/>\n<meta property=\"og:description\" content=\"A how-to guide for an architecture reducing the boilerplate in Angular master-detail views using Auxiliary Routes and Resolvers.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.inovex.de\/de\/blog\/angular-master-detail-views-reduced-boilerplate\/\" \/>\n<meta property=\"og:site_name\" content=\"inovex GmbH\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/inovexde\" \/>\n<meta property=\"article:published_time\" content=\"2022-06-09T16:03:52+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2022-11-17T11:59:22+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.inovex.de\/wp-content\/uploads\/amgular-master-detail-view-boilerplate.png\" \/>\n\t<meta property=\"og:image:width\" content=\"1920\" \/>\n\t<meta property=\"og:image:height\" content=\"1080\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"author\" content=\"Kirill Rakhman\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:image\" content=\"https:\/\/www.inovex.de\/wp-content\/uploads\/amgular-master-detail-view-boilerplate-1024x576.png\" \/>\n<meta name=\"twitter:creator\" content=\"@inovexgmbh\" \/>\n<meta name=\"twitter:site\" content=\"@inovexgmbh\" \/>\n<meta name=\"twitter:label1\" content=\"Verfasst von\" \/>\n\t<meta name=\"twitter:data1\" content=\"Kirill Rakhman\" \/>\n\t<meta name=\"twitter:label2\" content=\"Gesch\u00e4tzte Lesezeit\" \/>\n\t<meta name=\"twitter:data2\" content=\"9\u00a0Minuten\" \/>\n\t<meta name=\"twitter:label3\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data3\" content=\"Kirill Rakhman\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/angular-master-detail-views-reduced-boilerplate\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/angular-master-detail-views-reduced-boilerplate\\\/\"},\"author\":{\"name\":\"Kirill Rakhman\",\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/#\\\/schema\\\/person\\\/e67ddc50eb00d796467e5f8c83f1baca\"},\"headline\":\"How to Reduce Boilerplate Code for Angular Master-Detail Views\",\"datePublished\":\"2022-06-09T16:03:52+00:00\",\"dateModified\":\"2022-11-17T11:59:22+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/angular-master-detail-views-reduced-boilerplate\\\/\"},\"wordCount\":1278,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/#organization\"},\"image\":{\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/angular-master-detail-views-reduced-boilerplate\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/www.inovex.de\\\/wp-content\\\/uploads\\\/amgular-master-detail-view-boilerplate.png\",\"keywords\":[\"Angular\",\"Web\"],\"articleSection\":[\"Applications\",\"English Content\",\"General\"],\"inLanguage\":\"de\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/angular-master-detail-views-reduced-boilerplate\\\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/angular-master-detail-views-reduced-boilerplate\\\/\",\"url\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/angular-master-detail-views-reduced-boilerplate\\\/\",\"name\":\"How to Reduce Boilerplate Code for Angular Master-Detail Views - inovex GmbH\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/angular-master-detail-views-reduced-boilerplate\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/angular-master-detail-views-reduced-boilerplate\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/www.inovex.de\\\/wp-content\\\/uploads\\\/amgular-master-detail-view-boilerplate.png\",\"datePublished\":\"2022-06-09T16:03:52+00:00\",\"dateModified\":\"2022-11-17T11:59:22+00:00\",\"description\":\"A how-to guide for an architecture reducing the boilerplate in Angular master-detail views using Auxiliary Routes and Resolvers.\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/angular-master-detail-views-reduced-boilerplate\\\/#breadcrumb\"},\"inLanguage\":\"de\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/angular-master-detail-views-reduced-boilerplate\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"de\",\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/angular-master-detail-views-reduced-boilerplate\\\/#primaryimage\",\"url\":\"https:\\\/\\\/www.inovex.de\\\/wp-content\\\/uploads\\\/amgular-master-detail-view-boilerplate.png\",\"contentUrl\":\"https:\\\/\\\/www.inovex.de\\\/wp-content\\\/uploads\\\/amgular-master-detail-view-boilerplate.png\",\"width\":1920,\"height\":1080,\"caption\":\"A website with a shiny angular logo\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/angular-master-detail-views-reduced-boilerplate\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"How to Reduce Boilerplate Code for Angular Master-Detail Views\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/#website\",\"url\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/\",\"name\":\"inovex GmbH\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"de\"},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/#organization\",\"name\":\"inovex GmbH\",\"url\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"de\",\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/#\\\/schema\\\/logo\\\/image\\\/\",\"url\":\"https:\\\/\\\/www.inovex.de\\\/wp-content\\\/uploads\\\/2021\\\/03\\\/inovex-logo-16-9-1.png\",\"contentUrl\":\"https:\\\/\\\/www.inovex.de\\\/wp-content\\\/uploads\\\/2021\\\/03\\\/inovex-logo-16-9-1.png\",\"width\":1921,\"height\":1081,\"caption\":\"inovex GmbH\"},\"image\":{\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/#\\\/schema\\\/logo\\\/image\\\/\"},\"sameAs\":[\"https:\\\/\\\/www.facebook.com\\\/inovexde\",\"https:\\\/\\\/x.com\\\/inovexgmbh\",\"https:\\\/\\\/www.instagram.com\\\/inovexlife\\\/\",\"https:\\\/\\\/www.linkedin.com\\\/company\\\/inovex\",\"https:\\\/\\\/www.youtube.com\\\/channel\\\/UC7r66GT14hROB_RQsQBAQUQ\"]},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/#\\\/schema\\\/person\\\/e67ddc50eb00d796467e5f8c83f1baca\",\"name\":\"Kirill Rakhman\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"de\",\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/895733f4f7b5d5b699d2c032dfb946901a8bff705827b2be7ffa18e2436a9706?s=96&d=retro&r=g78e7a66496748487e02a3578787ef7bc\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/895733f4f7b5d5b699d2c032dfb946901a8bff705827b2be7ffa18e2436a9706?s=96&d=retro&r=g\",\"contentUrl\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/895733f4f7b5d5b699d2c032dfb946901a8bff705827b2be7ffa18e2436a9706?s=96&d=retro&r=g\",\"caption\":\"Kirill Rakhman\"},\"url\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/author\\\/krakhman\\\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"How to Reduce Boilerplate Code for Angular Master-Detail Views - inovex GmbH","description":"A how-to guide for an architecture reducing the boilerplate in Angular master-detail views using Auxiliary Routes and Resolvers.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.inovex.de\/de\/blog\/angular-master-detail-views-reduced-boilerplate\/","og_locale":"de_DE","og_type":"article","og_title":"How to Reduce Boilerplate Code for Angular Master-Detail Views - inovex GmbH","og_description":"A how-to guide for an architecture reducing the boilerplate in Angular master-detail views using Auxiliary Routes and Resolvers.","og_url":"https:\/\/www.inovex.de\/de\/blog\/angular-master-detail-views-reduced-boilerplate\/","og_site_name":"inovex GmbH","article_publisher":"https:\/\/www.facebook.com\/inovexde","article_published_time":"2022-06-09T16:03:52+00:00","article_modified_time":"2022-11-17T11:59:22+00:00","og_image":[{"width":1920,"height":1080,"url":"https:\/\/www.inovex.de\/wp-content\/uploads\/amgular-master-detail-view-boilerplate.png","type":"image\/png"}],"author":"Kirill Rakhman","twitter_card":"summary_large_image","twitter_image":"https:\/\/www.inovex.de\/wp-content\/uploads\/amgular-master-detail-view-boilerplate-1024x576.png","twitter_creator":"@inovexgmbh","twitter_site":"@inovexgmbh","twitter_misc":{"Verfasst von":"Kirill Rakhman","Gesch\u00e4tzte Lesezeit":"9\u00a0Minuten","Written by":"Kirill Rakhman"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/www.inovex.de\/de\/blog\/angular-master-detail-views-reduced-boilerplate\/#article","isPartOf":{"@id":"https:\/\/www.inovex.de\/de\/blog\/angular-master-detail-views-reduced-boilerplate\/"},"author":{"name":"Kirill Rakhman","@id":"https:\/\/www.inovex.de\/de\/#\/schema\/person\/e67ddc50eb00d796467e5f8c83f1baca"},"headline":"How to Reduce Boilerplate Code for Angular Master-Detail Views","datePublished":"2022-06-09T16:03:52+00:00","dateModified":"2022-11-17T11:59:22+00:00","mainEntityOfPage":{"@id":"https:\/\/www.inovex.de\/de\/blog\/angular-master-detail-views-reduced-boilerplate\/"},"wordCount":1278,"commentCount":0,"publisher":{"@id":"https:\/\/www.inovex.de\/de\/#organization"},"image":{"@id":"https:\/\/www.inovex.de\/de\/blog\/angular-master-detail-views-reduced-boilerplate\/#primaryimage"},"thumbnailUrl":"https:\/\/www.inovex.de\/wp-content\/uploads\/amgular-master-detail-view-boilerplate.png","keywords":["Angular","Web"],"articleSection":["Applications","English Content","General"],"inLanguage":"de","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/www.inovex.de\/de\/blog\/angular-master-detail-views-reduced-boilerplate\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/www.inovex.de\/de\/blog\/angular-master-detail-views-reduced-boilerplate\/","url":"https:\/\/www.inovex.de\/de\/blog\/angular-master-detail-views-reduced-boilerplate\/","name":"How to Reduce Boilerplate Code for Angular Master-Detail Views - inovex GmbH","isPartOf":{"@id":"https:\/\/www.inovex.de\/de\/#website"},"primaryImageOfPage":{"@id":"https:\/\/www.inovex.de\/de\/blog\/angular-master-detail-views-reduced-boilerplate\/#primaryimage"},"image":{"@id":"https:\/\/www.inovex.de\/de\/blog\/angular-master-detail-views-reduced-boilerplate\/#primaryimage"},"thumbnailUrl":"https:\/\/www.inovex.de\/wp-content\/uploads\/amgular-master-detail-view-boilerplate.png","datePublished":"2022-06-09T16:03:52+00:00","dateModified":"2022-11-17T11:59:22+00:00","description":"A how-to guide for an architecture reducing the boilerplate in Angular master-detail views using Auxiliary Routes and Resolvers.","breadcrumb":{"@id":"https:\/\/www.inovex.de\/de\/blog\/angular-master-detail-views-reduced-boilerplate\/#breadcrumb"},"inLanguage":"de","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.inovex.de\/de\/blog\/angular-master-detail-views-reduced-boilerplate\/"]}]},{"@type":"ImageObject","inLanguage":"de","@id":"https:\/\/www.inovex.de\/de\/blog\/angular-master-detail-views-reduced-boilerplate\/#primaryimage","url":"https:\/\/www.inovex.de\/wp-content\/uploads\/amgular-master-detail-view-boilerplate.png","contentUrl":"https:\/\/www.inovex.de\/wp-content\/uploads\/amgular-master-detail-view-boilerplate.png","width":1920,"height":1080,"caption":"A website with a shiny angular logo"},{"@type":"BreadcrumbList","@id":"https:\/\/www.inovex.de\/de\/blog\/angular-master-detail-views-reduced-boilerplate\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.inovex.de\/de\/"},{"@type":"ListItem","position":2,"name":"How to Reduce Boilerplate Code for Angular Master-Detail Views"}]},{"@type":"WebSite","@id":"https:\/\/www.inovex.de\/de\/#website","url":"https:\/\/www.inovex.de\/de\/","name":"inovex GmbH","description":"","publisher":{"@id":"https:\/\/www.inovex.de\/de\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.inovex.de\/de\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"de"},{"@type":"Organization","@id":"https:\/\/www.inovex.de\/de\/#organization","name":"inovex GmbH","url":"https:\/\/www.inovex.de\/de\/","logo":{"@type":"ImageObject","inLanguage":"de","@id":"https:\/\/www.inovex.de\/de\/#\/schema\/logo\/image\/","url":"https:\/\/www.inovex.de\/wp-content\/uploads\/2021\/03\/inovex-logo-16-9-1.png","contentUrl":"https:\/\/www.inovex.de\/wp-content\/uploads\/2021\/03\/inovex-logo-16-9-1.png","width":1921,"height":1081,"caption":"inovex GmbH"},"image":{"@id":"https:\/\/www.inovex.de\/de\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/www.facebook.com\/inovexde","https:\/\/x.com\/inovexgmbh","https:\/\/www.instagram.com\/inovexlife\/","https:\/\/www.linkedin.com\/company\/inovex","https:\/\/www.youtube.com\/channel\/UC7r66GT14hROB_RQsQBAQUQ"]},{"@type":"Person","@id":"https:\/\/www.inovex.de\/de\/#\/schema\/person\/e67ddc50eb00d796467e5f8c83f1baca","name":"Kirill Rakhman","image":{"@type":"ImageObject","inLanguage":"de","@id":"https:\/\/secure.gravatar.com\/avatar\/895733f4f7b5d5b699d2c032dfb946901a8bff705827b2be7ffa18e2436a9706?s=96&d=retro&r=g78e7a66496748487e02a3578787ef7bc","url":"https:\/\/secure.gravatar.com\/avatar\/895733f4f7b5d5b699d2c032dfb946901a8bff705827b2be7ffa18e2436a9706?s=96&d=retro&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/895733f4f7b5d5b699d2c032dfb946901a8bff705827b2be7ffa18e2436a9706?s=96&d=retro&r=g","caption":"Kirill Rakhman"},"url":"https:\/\/www.inovex.de\/de\/blog\/author\/krakhman\/"}]}},"_links":{"self":[{"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/posts\/36718","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/users\/285"}],"replies":[{"embeddable":true,"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/comments?post=36718"}],"version-history":[{"count":6,"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/posts\/36718\/revisions"}],"predecessor-version":[{"id":36818,"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/posts\/36718\/revisions\/36818"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/media\/36747"}],"wp:attachment":[{"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/media?parent=36718"}],"wp:term":[{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/tags?post=36718"},{"taxonomy":"service","embeddable":true,"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/service?post=36718"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/coauthors?post=36718"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}