SplitView migration guide

Since the inception of the new angular backend, the splitView has been the go-to solution when we needed a way to facilitate navigation between a number of related views. Over time, this way of navigation handling has become bloated and increasingly difficult to implement and maintain. With the new angular based routing we created a lightweight and easy to implement solution to replace it. This guide will provide you with step-by-step instructions on replacing old splitView routing with our new angular routing solution.

Further reading

Step by step to angular routing

1. As a first step, we take a look at the routing config. Since there are no routes available to lead us past the overview (filter + table) yet, we need to define additional routes for those views previously reachable through the splitView. Create a new file *.routing.ts (next to your module and/or entry component) which exports a constant defining all the routes and register those routes by assigning it to the children property of the base route of your UI. In the permissions object, leave the policy property as empty string to make the route only visible to admin users. If isStatic:true is set, the route is visible to every user. Set a policy string to make the route only visible for a specific policy.

{
        path:        'settings/properties/configuration',
        component:   PropertiesComponent,
        data:        {
           permissions: {
            policy: ''
        },
           label:       'properties.configuration',
           isHidden:    true
        },
        children: propertyConfigurationRoutes,
        canActivate: GUARDS
},
export const propertyConfigurationRoutes:TerraRoutes = [
   {
       path:       '',
       pathMatch:  'full',
       redirectTo: 'overview',
       data:       {
           label:       'properties.modules.filters',
           permissions: {
               isStatic:  true
           },
           isHidden:    true
       }
   },
   {
       path:      'overview',
       component: PropertiesOverviewComponent,
       data:      {
           label:       'properties.modules.table',
           permissions: {
                policy: ''
           },
           isHidden:    true
       }
   },
   {
       path:      'overview/new',
       component: PropertiesAddViewComponent,
       data:      {
           label:       'properties.modules.createProperty',
           permissions: {
                policy: ''
           },
           isHidden:    true
       },
       resolve: {
           destinations: PropertyDestinationsResolver
       }
   },
   {
       path:       'overview/:propertyId',
       pathMatch:  'full',
       redirectTo: 'overview/:propertyId/general',
       data:       {
           label:       (translation:TranslationService, params:Params, data:Data):string =>
                        {
                            let lang:string = localStorage.getItem('plentymarkets_lang_');
                            return SystemPropertyHelper.getPropertyNameWithId(data.property as PropertyInterface, lang);
                        },
           permissions: {
                policy: ''
           },
           isHidden:    true
       },
       resolve:    {
           property: PropertyResolver
       }
   }

2. For our ‘entry-point’ or base route, we adjust the entry component so that it holds our breadcrumbs and router-outlet. E.g. properties.component.html

    <terra-breadcrumbs></terra-breadcrumbs>
    <div class="outlet-container">
       <router-outlet></router-outlet>
    </div>
  

For more information about the router-outlet see this page.

3. Instead of directly routing to the old components, we create some new view-components which take care of things like asynchronous data loading, breadcrumbs handling and routing.

What is async?

For more information see this page.

Why View-Components?

View-Components are used to allow usual components to work in a specific context. Components are supposed to only display data and contain a small amount of logic that is directly related to the data displayed, such as saving the shown data. View-Components on the contrary can handle specific tasks that depend on the context in which the component is used. For example, you should be able to use an address-component in various places in plentymarkets to display or edit address data. But when it comes to routing, you may want to navigate to different URLs when the address is saved or deleted. This is what the View-Component can take care of, what the component itself is not supposed to do.

4. Now we need to remove all the references to the old splitView handling from our components and replace them by code that uses our new routing approach.

a. SplitView-Params become Inputs.

b. Instead of adding views to our splitConfig, we navigate to the Views routes directly.

c. removeView becomes closeBreadcrumb/reset component data.

    @Component({
      selector: 'terra-properties-add-view',
      template: `
                    <ng-container *ngIf="data$ | async as data">
                        <terra-settings-properties-add
                            [destinations]="data.destinations"
                            (propertyCreated)="onPropertyCreation($event)">
                        </terra-settings-properties-add>
                    </ng-container>`
   })
   export class PropertiesAddViewComponent
   {
      protected data$:Observable<Data>;

      constructor(private route:ActivatedRoute,
                  private router:Router,
                  private propertiesComponent:PropertiesComponent)
      {
          this.data$ = this.route.data;
      }

      protected onPropertyCreation(property:PropertyInterface):void
      {
   this.propertiesComponent.breadcrumbsService.closeBreadcrumbByUrl('/' + this.route.snapshot.url.join('/'));
          this.router.navigate(['../', property.id], {relativeTo: this.route});
      }
   }

d. Make sure to implement ngOnChanges where Inputs can change (especially for components that are loaded on parameterised routes) and remember that the view needs to be updated.


public ngOnChanges(changes:SimpleChanges):void
{
   if(changes.hasOwnProperty('property'))
   {
       this.updateFormFields();
   }

   if(changes.hasOwnProperty('destination'))
   {
       this.propertyOptionsConfig = this.propertyDynamicViewService.processOptionsConfig(this.destination);
   }

   if(changes.hasOwnProperty('options'))
   {
       this.propertyOptionsData = this.propertyDynamicViewService.updateSelectedOptions(this.propertyOptionsConfig, this.options);
       this.propertyOptionsConfig = this.propertyDynamicViewService.setupOptionsId(this.propertyOptionsConfig, this.options);
   }
}

5. Since we are directly routing to views now, we don’t need any submodules. All the components, services (including resolvers) and modules we declared/imported into our submodules can be moved into our main ‘feature’ module and the submodules can be removed.

6. The filter and table component which we previously displayed by adding them to our splitView-Config when loading the properties-configuration route now need their own view component to be displayed. Inside this component, we use the TerraTwoColumn component and the attributes ‘left’ and ‘right’ to designate the two components that we want to be shown next to each other and to position them.

    @Component({
       selector: 'terra-properties-overview',
       template: `
                     <terra-2-col>
                         <terra-settings-properties-filter left></terra-settings-properties-filter>
                         <terra-settings-properties-table right></terra-settings-properties-table>
                     </terra-2-col>`
    })
    export class PropertiesOverviewComponent
    {
    }

7. Now we can set up our resolvers to load preloadable data like countries, user roles and similar static data that we will require in our views and to load the view specific data when we route to one of our views.

    @Injectable()
    export class PropertyResolver implements Resolve<PropertyInterface>
    {
       constructor(private propertiesService:PropertiesService)
       {
       }

       public resolve(route:ActivatedRouteSnapshot):Observable<PropertyInterface>
       {
           let propertyId:number = +route.params['propertyId'];

           if(isNullOrUndefined(propertyId) || isNaN(propertyId))
           {
               return;
           }

           return this.propertiesService.getProperty(propertyId);
       }
    }

8. Although our views are now working as they should, there are still some artifacts of the old SplitView-routing that we need to get rid of. Components that still extend the MultiSplitViewBaseComponent need to be changed and logic that is based on methods inherited from it (translation, error messages) has to be refactored to work without it. You can either change the extension to TerraAlertBase or remove the extension if you are not using any of the alert handling methods like handleMessage. Moreover, configs need to be replaced (see below).

    export class SomeComponent extends TerraAlertBase
    {
       constructor(translation:TranslationService)
       {
           super(translation);
       }

       private someFunction():void
       {
           this.handleMessage(this.translation.translate('test'));
       }
    }

Improve code quality

After migrating from splitView to angular routing, it is possible to further optimize the code:

  1. Replace for and for-of loops with for-each loops to improve readability and reduce the risk of one-off errors.
  2. Remove unused properties, methods and imports.
  3. Replace unneeded *.config.ts files. If you used config files to synchronize data between two components in splitView, consider replacing them with direct component communication using Inputs and Outputs.

Is this article helpful?

 

Thank you for your Feedback

you can close this field now!