Using routerLink to make result tabs as links

In Susper, it was required that different result sections i.e All, Images,Videos and News should behave as links. Links provide us an advantage of opening the different result tabs in a New Tab by right clicking on it. Also at the same time we wanted that Active tabs must not behave as links. We wanted that tabs should behave in a similar way as Google, Bing and other search engine.In this blog, I will describe how we have achieved this by using routerLink and [hidden] attribute in Angular.

Steps:

1.Adding routerLink attribute in anchor tag.

Now, To make any element as link we must enclose it in anchor tag, therefore we will use <a> tag enclosing the <li> tag to make it a link.

<a [routerLink]="['/search']" [queryParams]="{query:this.searchdata.query,start:this.searchdata.start,rows:this.searchdata.rows,resultDisplay:'all',mode:'text',nopagechange:'false',append:'false'}"><li [class.active_view]="Display('all')" (click)="docClick()">All</li></a>
<a [routerLink]="['/search']" [queryParams]="{query:this.searchdata.query,start:this.searchdata.start,rows:this.searchdata.rows,mode:'text',nopagechange:'false',append:'false',fq:'url_file_ext_s%3A(png%2BOR%2Bjpeg%2BOR%2Bjpg%2BOR%2Bgif)',resultDisplay:'images'}"><li [class.active_view]="Display('images')" (click)="imageClick()">Images</li></a>
<a [routerLink]="['/search']" [queryParams]="{query:this.searchdata.query,start:this.searchdata.start,rows:this.searchdata.rows,resultDisplay:'videos',mode:'text',nopagechange:'false',append:'false',fq:'url_file_ext_s%3A(avi%2BOR%2Bmov%2BOR%2Bflv%2BOR%2Bmp4)'}"><li [class.active_view]="Display('videos')" (click)="videoClick()">Videos</li></a>
<a [routerLink]="['/search']" [queryParams]="{query:this.searchdata.query,start:this.searchdata.start,rows:this.searchdata.rows,resultDisplay:'news',mode:'text',nopagechange:'false',append:'false'}"><li [class.active_view]="Display('news')" (click)="newsClick()">News</li></a>

Now we must navigate to a link on the click of this tabs, for this we can simply use href attribute which we earlier tried to use but there is a disadvantage of using href tag. Using href tab reloads the whole page when we try to navigate to another tab.

Therefore we will use routerLink to achieve the same functionality as Susper is a single page application, where the page should not be reloaded. routerLink navigates to the new url and the component is rendered in place of <router-outlet> without reloading the whole page.

After using routerLink, we will use queryParams attribute to pass routing parameters to a route. Since these parameters will be variable we must use string interpolation to set query parameters. We will use searchdata object and set query parameters according to its value.

Now we are done with making tabs as links. But as of now all tabs will behave a links even if it is an active tab. Therefore we need to hide the link property of any tab when it is active.

2.Hiding the link property of the active tab:

Now we will use to different elements for each tab, one as link and other as normal li element.For hiding the link elements when the tab is active we will use [hidden] attribute in angular. We will just hide the link element when the value of resultDisplay variable is similar to current tab.

<a [routerLink]="['/search']" [queryParams]="{query:this.searchdata.query,start:this.searchdata.start,rows:this.searchdata.rows,resultDisplay:'all',mode:'text',nopagechange:'false',append:'false'}" [hidden]="this.resultDisplay==='all'"><li [class.active_view]="Display('all')" (click)="docClick()">All</li></a>
        <li [class.active_view]="Display('all')" (click)="docClick()" *ngIf="Display('all')">All</li>

For hiding the simple li element we will use *ngIf attribute which will remove the simple li element from the DOM for un active tabs. We might have used *ngIf attribute to hide link elements but rendering a lot of elements on DOM slows down the page and affects performance.

We will follow this for rest of the tabs also and we are done with our changes.

Now we can the inactive tabs will behave as link and the active tabs will not behave as link similar to Google.

Resources

  1. Angular Routing: https://angular.io/guide/router
  2. ngIf and [hidden]: https://stackoverflow.com/questions/39777990/angular2-conditional-display-bind-to-hidden-property-vs-ngif-directive
  3. Angular queryParams: https://stackoverflow.com/a/46008187

Create new route ‘/settings’ to display project’s configuration

Having a separate route to display all the hardcoded project’s configuration makes it very easy to compare web apps with each other. A similar architecture has been followed in loklak to create a ‘/settings’ route to display configs used inside it. Respective implementation would be discussed in this blog.

Creating Settings Component

First step would be to create a Settings component to render under /settings route.

ng g component settings

 

This command will create new settings component. We have to create settings-routing and settings module inside settings component separately. This route module will be called inside the root app module. The settings-routing module would import Router module as follows:

import { NgModule } from ‘@angular/core’;
import { Routes, RouterModule } from ‘@angular/router’;
import { SettingsComponent } from ‘./settings.component’;
const routes: Routes = [
    {
        path: '',
        component: SettingsComponent
    }
];
@NgModule({
    imports: [RouterModule.forChild(routes)],
    exports: [RouterModule],
    providers: []
})
export class LoklakSettingsRoutingModule { }

 

The settings module would follow the basic module configuration of Angular, and import the settings-routing module as follows:

import { NgModule } from ‘@angular/core’;
import { CommonModule } from ‘@angular/common’;
import { LoklakSettingsRoutingModule }
    from ‘./settings-routing.module’;
import { SettingsComponent } from ‘./settings.component’;
@NgModule({
    imports: [
        CommonModule,
        LoklakSettingsRoutingModule
    ],
    declarations: [
        SettingsComponent
    ]
})
export class SettingsModule { }

 

At this point of time, we are having a basic settings component to render under /settings route.

Loading /settings as child route under root app module

Now, we need to add path of ‘/settings’ route inside root app module as follows:

path: ‘settings’,
loadChildren: ‘./settings/settings.module#SettingsModule’

 

Which means when user is on path /settings, load settings component.

Fetching project configs inside settings component

At this point, we have successfully established /settings route and settings component. Now we need to fetch the project configs inside the settings component to display. We would actually import the config and store it in an array to loop through it in template file using *ngFor to display the items under config array.

Note: We would import newsOrgs containing twitter username of news organizations, and the defaultUrlConfig containing the list of all hardcoded URLs used inside the project. It can be taken as an example to add and display more configurations.

import { Component, OnInit } from ‘@angular/core’;
import { defaultUrlConfig } from ‘../shared/url-config’;
import { newsOrgs } from ‘../shared/news-org’;
@Component({
    selector: ‘app-settings’,
    templateUrl: ‘./settings.component.html’,
    styleUrls: [‘./settings.component.scss’]
})
export class SettingsComponent implements OnInit {
    public urlObject = [];
    public newsConfigOrgs = newsOrgs;
    ngOnInit() {
    let i = 0;
    for (const key in defaultUrlConfig) {
        if (key) {
            const data = [];
            const value = defaultUrlConfig[key];
            const data2 = [];
            for (const key2 in value) {
                if (key2) {
                    const value2 = value[key2];
                    data2.push(key2, value2);
                }
            }
            data.push(key, data2);
            this.urlObject.push(data);
            i++;
            }
        }
    }
}

 

urlObject is an array used to store the list of hardcoded URLs, and newsConfigOrgs is an array containing all the list of news organizations.

Displaying array items in template file

Up to this point, we have successfully stored the data inside the component file of settings component and now we need to display this data in template file. For a sake of an example, we would display news orgs as follows:

<!– News Config –>
<h1><u>News Configuration</u></h1>
<div class=“url-container” *ngFor=“let key of newsConfigOrgs”>
   <h4 class=“news”>{{key}}</h4>
</div>

The above code could be used as an example to display more config stored inside component file. Only the basic implementation has been followed and mentioned inside this blog, complete code can be found here: code.

Creating settings tab in Advanced-Feed

Now we need to provide a link to user to check /settings route. We would actually add another tab as settings inside Advanced-Feed as follows:

<a routerLink=“/settings” target=“_blank” class=“tab”>
   Settings
</a>

Testing /settings route

Search a query on loklak.org, and then click on settings tab which would be similar to:

On clicking settings tab, user should be directed to the /settings route containing non-editable (read-only) configuration.

Resources

Implementation of Child Routes in SUSI Skill CMS

In a previous blog post I discussed about how we implemented routing in SUSI Web Chat Application. In this post I’m planning to discuss about how we developed child routes in SUSI Skill CMS .

When we start developing our application, it was working correctly but  all skills loaded in the same URL. ( skill.susi.ai/SkillPage ). When user clicks the edit button every skill loaded in the same URL ( skill.susi.ai/EditSkill ). We got a requirement to load each of our skills in separate routes. This is how we implemented the child routes of the application.

We wanted to show each individual skill under this type of URL,

skill.susi.ai/ [SKILL GROUP] / [SKILL NAME] / [LANGUAGE]

When user clicks on the edit button, we needed to show that particular skill under this URL.

skill.susi.ai/ [SKILL GROUP] / [SKILL NAME] / edit / [LANGUAGE]

First we set our routings in index.js file.

<Switch>
    <Route exact path="/:category/:skill/edit/:lang" component={Home} />
    <Route exact path="/:category/:skill/:lang" component={SkillListing}/>
    <Route exact path="/" component={BrowseSkill} />
    <Route exact path="*" component={NotFound} />
</Switch>

We have to add the “exact” attribute, if we don’t add that it will not redirect users to “404” page when user trying to access wrong routes.

Next step is sending data from one component to another component.
In SUSI Skill CMS, user can choose any skill from the home page. Then after it goes to the skill page and shows details about the selected skill. We have to modify the button as,

<Link to={{ pathname: '/'+self.state.groupValue+'/'+el+'/'+self.state.languageValue }} >
<Card>
</Card>
</Link>

Now the user clicks on the card. It changes the URL and loads the corresponding component according to the routes that we defined in “index.js” file previously.
Second thing that we need to do is to catch URL routs and render relevant data according to the URL routes.
Let’s say I clicked on “distance” skill. Then user will go to this URL “http://skills.susi.ai/Knowledge/distance/en ”
Now It loads the “SkillListing” component according to the route we defined in “ index.js ” here ””.
To derive data from URL we simply used these codes in “SkillListing.js”.

let baseUrl = 'http://api.susi.ai/cms/getSkillMetadata.json';           
let modelValue = "general";
           this.name = this.props.location.pathname.split('/')[2];
           this.groupValue = this.props.location.pathname.split('/')[1];
           this.languageValue = this.props.location.pathname.split('/')[3];
           url = baseUrl + '?model=' + modelValue + '&group=' + this.groupValue + '&language=' + this.languageValue + '&skill=' + this.name;

We collected data from the URL and made another URL, we used this URL to get details of the skill from the server. We used this urls as below.

           $.ajax({
               url: url,
               jsonpCallback: 'pc',
               dataType: 'jsonp',
               jsonp: 'callback',
               crossDomain: true,
               success: function (data) {
                   self.updateData(data.skill_metadata)
               }
           });

If the Ajax request is success, those data are passed to “updateData()” and it updates the component and shows to users like this.

We applied same mechanism to the edit button and edit page. This is how we modified skill.susi.ai ‘s Routings. If you like to contribute SUSI Skill CMS please fork our repository on github. here

Resources:

  • Previous Blogpost about routing: https://blog.fossasia.org/implementation-of-react-routers-in-susi-web-chat/
  • React Router v4 tutorial https://medium.com/@pshrmn/a-simple-react-router-v4-tutorial-7f23ff27adf

Route Based Chunking in Loklak Search

The loklak search application running at loklak.org is growing in size as the features are being added into the application, this growth is a linear one, and traditional SPA, tend to ship all the code is required to run the application in one pass, as a single monolithic JavaScript file, along with the index.html. This approach is suitable for the application with few pages which are frequently used, and have context switching between those logical pages at a high rate and almost simultaneously as the application loads.

But generally, only a fraction of code is what is accessed most frequently by most users, so as the application size grows it does not make sense to include all the code for the entire application at the first request, as there is always some code in the application, some views, are rarely accessed. The loading of such part of the application can be delayed until they are accessed in the application. The angular router provides an easy way to set up such system and is used in the latest version of loklak search.

The technique which is used here is to load the content according to the route. This makes sure only the route which is viewed is loaded on the initial load, and subsequent loading is done at the runtime as and when required.

Old setup for baseline

Here are the compiled file sizes, of the project without the chunking the application. Now as we can see that the file sizes are huge, especially the vendor bundle, which is of 5.4M and main bundle which is about 0.5M now, these are the files which are loaded on the first load and due to their huge sizes, the first paint of the application suffers, to a great extent. These numbers will act as a baseline upon which we will measure the impact of route based chunking.

Setup for route based chunking

The setup for route based chunking is fairly simple, this is our routing configuration, the part of the modules which we want to lazy load are to be passed as loadChildren attribute of the route, this attribute is a string which is a path of the feature module which, and part after the hash symbol is the actual class name of the module, in that file. This setup enables the router to load that module lazily when accessed by the user.

const routes: Routes = [
{
path: '',
pathMatch: 'full',
loadChildren: './home/home.module#HomeModule',
data: { preload: true }

},
{
path: 'about',
loadChildren: './about/about.module#AboutModule'
},

{
path: 'contact',
loadChildren: './contact/contact.module#ContactModule'
},

{
path: 'search',
loadChildren: './feed/feed.module#FeedModule',
data: { preload: true }
},
{
path: 'terms',

loadChildren: './terms/terms.module#TermsModule'
},
{
path: 'wall',
loadChildren: './media-wall/media-wall.module#MediaWallModule'
}
];

Preloading of some routes

As we can see that in two of the configurations above, there is a data attribute, on which preload: true attribute is specified. Sometimes we need to preload some part of theapplication, which we know we will access, soon enough. So angular also enables us to set up our own preloading strategy to preload some critical parts of the application, which we know are going to be accessed. In our case, Home and Feed module are the core parts of the application, and we can be sure that, if someone has to use our application, these two modules need to be loaded. Defining the preloading strategy is also really simple, it is a class which implements PreloadingStrategy interface, and have a preload method, this method receives the route and load function as an argument, and this preload method either returns the load() observable or null if preload is set to true.

export class CustomPreloadStrategy implements PreloadingStrategy {
preload(route: Route, load: Function): Observable<any> {
return route.data && route.data.preload ? load() : of(null);
}
}

Results of route based chunking

The results of route based chunking are the 50% reduction in the file size of vendor bundle and 70% reduction in the file size of the main bundle this provides the edge which every application needs to perform well at the load time, as unnecessary bytes are not at all loaded until required.

Resources

Stubbed Routing Inbuilt Service used in Open Event Frontend

In Open Event Frontend, we have used services like ‘auth-manager’, ‘l10n’, ‘loader’, ‘sanitizer’, etc to ease our work with the help of predefined-functions in those services. However, while dealing with an issue in the project, there was a need to use ‘Routing’ as a service.

In the issue, we wanted to generate an access link dynamically from the access code entered by the user. The format of the access link was as follows:

“base_url + event_id + access_code”

So, for the above URL, we needed to have ‘event_id’ and ‘access_code’.

The ‘access_code’ can be readily accessed from the user’s input itself, whereas to get the event_id, we used the ‘Routing’ service in Ember.

Generally to use a service in Ember, it has to be written first,then registered, injected and then used.

‘Routing’ service in Ember is an inbuilt service unlike the ones listed at the beginning.

There is no need to write it. It can be simply registered, injected and used.

this.register('service:routing', routingStub);
this.inject.service('routing', { as: 'routing' });

where ‘register’ and ‘inject’ are the methods on Ember objects.

The integration tests in Open Event Frontend are written such that the services can be used without injecting, but the tests will fail. To pass those tests, we had to register and inject the service in the required component.

The Routing service could thus be registered and injected into the specific component( injection in the component’s integration test ) only but for future needs, this service might be needed in any other component too. For this purpose, this service was registered and injected in ‘component-helper.js’.

const routingStub = Service.extend({
  router: {
    router: {
      state: {
        params: {
          'events.view': {
            event_id: 1
          }
        }
      }
    },
    generate() {
      return 'http://dummy-url.com';
    }
  }
});


export default function(path, name, testCase = null) {
  moduleForComponent(path, name, {
    integration: true,

    beforeEach() {
      this.register('service:routing', routingStub);
      this.inject.service('routing', { as: 'routing' });
      this.register('service:l10n', L10n);
      this.inject.service('l10n', { as: 'l10n' });
      this.application = startApp();
      l10nTestHelper(this);
      run(() => fragmentTransformInitializer.initialize(getOwner(this)));
    }
  }
}

Stubbing a Service: This is a process of faking an app of importing a service when no path is available to import. Stubbing of a service is mainly done when one needs to deal with the testing of the app. In our case, the same is done. We have stubbed the ‘Routing’ service in order to deal with the testing part. It can be seen from the above code that we have generated a ‘routingStub’ which fakes the app while registering the service in the ‘beforeModel’. The next line of code shows the ‘injection’ of service into the app.

Now we are just left with one task i.e to pass ‘routing’ from our integration tests to the component.

test('it renders', function(assert) {
  this.render(hbs`{{forms/events/view/create-access-code routing=routing}}`);
  assert.ok(this.$().html().trim().includes('Save'));
});

Above code shows the same.

Thus we can stub the services in Ember when any component depends on them.

Resources:

Official Ember guide: https://guides.emberjs.com/v2.1.0/testing/testing-components

Blog by Todd Jordan: http://presentationtier.com/stubbing-services-in-emberjs-integration-tests/

Source codehttps://github.com/sumedh123/open-event-frontend/blob/0b193ca679ce3b51f65e19ee0d03ac6a679258de/tests/helpers/component-helper.js