Categories
JavaScript Vue

Watch Vuex Store State Change in a Vue.js App

We can easily watch for store Vuex store changes in a Vue.js app by adding computed properties or using getters.

In this article, we’ll look at how to do watch the Vuex store state with both ways.

Computed Properties

We can use computed properties to watch for the latest value from the store and return it.

For instance, we can write the following code to create a store and then access the value in a component:

main.js:

import Vue from "vue";
import App from "./App.vue";
import Vuex from "vuex";
Vue.use(Vuex);

Vue.config.productionTip = false;

const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment(state) {
      state.count++;
    }
  }
});

new Vue({
  store,
  render: h => h(App)
}).$mount("#app");

App.vue:

<template>
  <div id="app">
    <button @click="increment">Increment</button>
    <p>{{count}}</p>
  </div>
</template>

<script>
export default {
  name: "App",
  computed: {
    count() {
      return this.$store.state.count;
    }
  },
  methods: {
    increment() {
      this.$store.commit("increment");
    }
  }
};
</script>

In the code above, we have the the store in main.js which holds the count state.

We put the store in our Vue instance. Then in App.vue, we reference the store by using this.$store.

Then we can access the count state we did in the count method in the computed property.

In the end, we see the latest count displayed on the screen as we click the Increment button to call increment, which commits a new value to the store.

Getters

We can create a getter in the store and then use the mapGetters method to add the getter as a computed property in our component. For instance, we an write the following to do that:

main.js:

import Vue from "vue";
import App from "./App.vue";
import Vuex from "vuex";
Vue.use(Vuex);

Vue.config.productionTip = false;

const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment(state) {
      state.count++;
    }
  },
  getters: {
    count: state => {
      return state.count;
    }
  }
});

new Vue({
  store,
  render: h => h(App)
}).$mount("#app");

App.vue:

<template>
  <div id="app">
    <button @click="increment">Increment</button>
    <p>{{count}}</p>
  </div>
</template>

<script>
import { mapGetters } from "vuex";
export default {
  name: "App",
  computed: {
    ...mapGetters(["count"])
  },
  methods: {
    increment() {
      this.$store.commit("increment");
    }
  }
};
</script>

In the code above, we added a getter in our store with:

getters: {
  count: state => {
    return state.count;
  }
}

in main.js. Then in the script section of App.vue, we have:

import { mapGetters } from "vuex";
export default {
  name: "App",
  computed: {
    ...mapGetters(["count"])
  },
  methods: {
    increment() {
      this.$store.commit("increment");
    }
  }
};

We imported the mapGetters method from vuex to add the getter directly as a computed property.

In the array we passed into mapGetters we pass in the getter name from the store to access the returned value from the getter.

Therefore, we’ll see the same result as before.

Conclusion

Computed properties are good for getting some simple states. If we need to derive states from a given state, then we should use getters with the mapGetters method to map them to computed properties.

Categories
Angular JavaScript TypeScript

How to do Localization and Translation in Angular

With the modular structure of Angular apps, adding localization and translation features to it is easy. The most popular Angular add-on is ngx-translate . Angular 2+ is supported by ngx-translate .

Given an existing Angular app, we can install ngx-translate by running npm i ngx-translate .

After installing the package, make sure you have the following in your Angular module file. For example, in app.module.ts , we have:

export function HttpLoaderFactory(http: HttpClient) {  
  return new TranslateHttpLoader(http, './assets/i18n/', '.json');  
}

This allow us to load our JSON translation files from the ./assets/i18n/ . The JSON file should be named according to standard language codes listed in the ‘639–1’ column in the table of https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes. ngx-translate will load it into the Angular app, with its HTTP loader via a GET request.

app.module.ts should look something this, given all of these components and libraries are included in the app:

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';
import { RouterModule, Routes } from '@angular/router';
import { PaginationModule } from 'ngx-bootstrap/pagination';
import { AppComponent } from './app.component';
import { AccountPageComponent } from './account-page/account-page.component';
import { LoginPageComponent } from './login-page/login-page.component';
import { RegisterPageComponent } from './register-page/register-page.component';
import { MenuPageComponent } from './menu-page/menu-page.component';
import { OrdersPageComponent } from './orders-page/orders-page.component';
import { CategoryPageComponent } from './category-page/category-page.component';
import { AboutPageComponent } from './about-page/about-page.component';
import { HomePageComponent } from './home-page/home-page.component';
import { OrderService } from './order.service';
import { MenuService } from './menu.service';
import { UserService } from './user.service';
import { LocationService } from './location.service';
import { CategoryService } from './category.service';
import { RestaurantService } from './restaurant.service';
import { RestaurantsService } from './restaurants.service';
import { IsLoggedInGuard } from './is-logged-in.guard';
import { BottomBarComponent } from './bottom-bar/bottom-bar.component';
import { CartPageComponent } from './cart-page/cart-page.component';
import { VerifyAccountPageComponent } from './verify-account-page/verify-account-page.component';
import { SearchPageComponent } from './search-page/search-page.component';
import { RestaurantPageComponent } from './restaurant-page/restaurant-page.component';
import { ItemPageComponent } from './item-page/item-page.component';
import { ForgotPasswordPageComponent } from './forgot-password-page/forgot-password-page.component';
import { ResetPasswordPageComponent } from './reset-password-page/reset-password-page.component';
import { HttpClientModule, HttpClient } from '@angular/common/http';
import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
import { TranslateHttpLoader } from '@ngx-translate/http-loader';
export function HttpLoaderFactory(http: HttpClient) {
  return new TranslateHttpLoader(http, './assets/i18n/', '.json');
}
const appRoutes: Routes = [
  { path: 'category/:category', component: CategoryPageComponent },
  { path: 'login', component: LoginPageComponent },
  { path: 'verifyaccount/:userId/:verifyToken', component: VerifyAccountPageComponent },
  { path: 'register', component: RegisterPageComponent, },
  { path: 'resetpassword/:userId/:resetToken', component: ResetPasswordPageComponent, },
  { path: 'cart', component: CartPageComponent, },
  { path: 'forgotpassword', component: ForgotPasswordPageComponent, },
  { path: 'restaurant/:id', component: RestaurantPageComponent },
  { path: 'item/:restaurantId/:menuId/:itemId', component: ItemPageComponent },
  { path: 'menu', component: MenuPageComponent, canActivate: [IsLoggedInGuard] },
  { path: 'orders', component: OrdersPageComponent, canActivate: [IsLoggedInGuard] },
  { path: 'about', component: AboutPageComponent, },
  { path: 'search/:address', component: SearchPageComponent, },
  { path: 'account', component: AccountPageComponent, canActivate: [IsLoggedInGuard] },
  { path: '**', component: HomePageComponent }
];
@NgModule({
  declarations: [
    AppComponent,
    AccountPageComponent,
    LoginPageComponent,
    RegisterPageComponent,
    MenuPageComponent,
    OrdersPageComponent,
    CategoryPageComponent,
    AboutPageComponent,
    HomePageComponent,
    BottomBarComponent,
    CartPageComponent,
    VerifyAccountPageComponent,
    SearchPageComponent,
    RestaurantPageComponent,
    ItemPageComponent,
    ForgotPasswordPageComponent,
    ResetPasswordPageComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule,
    RouterModule.forRoot(
      appRoutes, { useHash: true }
    ),
    PaginationModule.forRoot(),
    HttpClientModule,
    TranslateModule.forRoot({
      loader: {
        provide: TranslateLoader,
        useFactory: HttpLoaderFactory,
        deps: [HttpClient]
      }
    })
  ],
  providers: [
    RestaurantService,
    RestaurantsService,
    OrderService,
    MenuService,
    UserService,
    LocationService,
    CategoryService,
    IsLoggedInGuard
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

The JSON should be something like this (en.json):

{  
    "Chiyoda": "Chiyoda",  
    "Chūō": "Chūō",  
    "Minato": "Minato",  
    "Shinjuku": "Shinjuku",  
    "Bunkyō": "Bunkyō",  
    "Taitō": "Taitō",  
    "Sumida": "Sumida",  
    "Kōtō": "Kōtō",  
    "Shinagawa": "Shinagawa",  
    "Meguro": "Meguro",  
    "Ōta": "Ōta",  
    "Setagaya": "Setagaya",  
    "Shibuya": "Shibuya",  
    "Nakano": "Nakano",  
    "Suginami": "Suginami",  
    "Toshima": "Toshima",  
    "Kita": "Kita",  
    "Arakawa": "Arakawa",  
    "Itabashi": "Itabashi",  
    "Nerima": "Nerima",  
    "Adachi": "Adachi",  
    "Katsushika": "Katsushika",  
    "Edogawa": "Edogawa",  
    "Select a Location": "Select a Location",  
    "City": "City",  
    "Ward": "Ward",  
    "Submit": "Submit",  
    "Buffet": "Buffet",  
    "Office Packages": "Office Packages",  
    "Family Event": "Family Event",  
    "Seminar": "Seminar",  
    "High tea and dessert table": "High tea and dessert table",  
    "Christmas": "Christmas",  
    "Kids Party": "Kids Party",  
    "Wedding": "Wedding",  
    "Become a partner": "Become a partner",  
    "About": "About"  
}

and foreign language file (jp.json ):

{  
    "Chiyoda": "千代田区",  
    "Chūō": "中央区",  
    "Minato": "港区",  
    "Shinjuku": "新宿区",  
    "Bunkyō": "文京区",  
    "Taitō": "台東区",  
    "Sumida": "墨田区",  
    "Kōtō": "江東区",  
    "Shinagawa": "品川区",  
    "Meguro": "目黒区",  
    "Ōta": "大田区",  
    "Setagaya": "世田谷区",  
    "Shibuya": "渋谷区",  
    "Nakano": "中野区",  
    "Suginami": "杉並区",  
    "Toshima": "豊島区",  
    "Kita": "北区",  
    "Arakawa": "荒川区",  
    "Itabashi": "板橋区",  
    "Nerima": "練馬区",  
    "Adachi": "足立区",  
    "Katsushika": "葛飾区",  
    "Edogawa": "江戸川区",  
    "Select a Location": "地域を選択",  
    "City": "都市",  
    "Ward": "市区町村",  
    "Submit": "検索",  
    "Buffet": "ビュッフェ",  
    "Office Packages": "お弁当",  
    "Family Event": "家族行事",  
    "Seminar": "セミナー",  
    "High tea and dessert table": "デザート",  
    "Christmas": "クリスマス",  
    "Kids Party": "子供向けパーティー",  
    "Wedding": "ウェディング",  
    "Become a partner": "パートナー提携",  
    "About": "私たちについて"  
}

You can also nest your translations in a lower level in your JSON file, like so:

{  
  "text":{      
    "Chiyoda": "千代田区",  
    "Chūō": "中央区",  
    "Minato": "港区",  
    "Shinjuku": "新宿区",  
    "Bunkyō": "文京区",  
    "Taitō": "台東区",  
    "Sumida": "墨田区",  
    "Kōtō": "江東区",  
    "Shinagawa": "品川区",  
    "Meguro": "目黒区",  
    "Ōta": "大田区",  
    "Setagaya": "世田谷区",  
    "Shibuya": "渋谷区",  
    "Nakano": "中野区",  
    "Suginami": "杉並区",  
    "Toshima": "豊島区",  
    "Kita": "北区",  
    "Arakawa": "荒川区",  
    "Itabashi": "板橋区",  
    "Nerima": "練馬区",  
    "Adachi": "足立区",  
    "Katsushika": "葛飾区",  
    "Edogawa": "江戸川区",  
    "Select a Location": "地域を選択",  
    "City": "都市",  
    "Ward": "市区町村",  
    "Submit": "検索",  
    "Buffet": "ビュッフェ",  
    "Office Packages": "お弁当",  
    "Family Event": "家族行事",  
    "Seminar": "セミナー",  
    "High tea and dessert table": "デザート",  
    "Christmas": "クリスマス",  
    "Kids Party": "子供向けパーティー",  
    "Wedding": "ウェディング",  
    "Become a partner": "パートナー提携",  
    "About": "私たちについて"  
  }  
}

Then in your template, you use the keys with the translate filter in to get translated text:

{{'Select a Location' | translate}}

If the translations in JSON is nested in another property, you can display them like so:

{{'text.About' | translate}}

Note that you cannot have spaces in your keys if you use the dot notation.

With this, the Select a Location key is looked up in the JSON, and depending on the language you set, it’ll get the right text from the value gotten from the key. If the value does not existing for the given key, then it will display the translate key.

Language can be changed in the code by injecting the TranslateService into your code. For example, in your component code, you do the following:

constructor(private translate: TranslateService)

Then you can do:

this.translate.use('jp');

to set the app to display Japanese.

You can also display the default language like so:

this.translate.setDefaultLang('jp');

Translations in Angular is straightforward. It is just a matter of installing a new package, adding the translation, setting the language, and make sure that it displays in the template.

Categories
BootstrapVue

Introduction to Bootstrap Vue

BootstrapVue is the Vue version of Bootstrap. Like the plain JavaScript version of Bootstrap, it has many kinds of elements to let us style our page and make them look good.

BoostrapVue has the same elements, but they’re all available as Vue components. Therefore, we can add them straight into our Vue app without much hassle.

In this article, we’ll look at how use BootstrapVue in our Vue app to make it look good.

Getting Started

Getting started with BootstrapVue is easy. We need Vue 2.6 in our app but Vue 2.6.11 is recommended. At least Bootstrap 4.3.1 is required. Also, PortalVue 2.1 is required by Toasts.

To install it, we run:

npm install vue bootstrap-vue bootstrap

Then we register the BootstrapVue library in our app so we can use the components and directives in our app’s components.

Assuming that we used the Vue CLI to generate our Vue project, we add the following code register the BootstrapVue libraries in our app:

main.js:

import Vue from "vue";
import App from "./App.vue";
import { BootstrapVue, IconsPlugin } from "bootstrap-vue";
import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'

Vue.use(BootstrapVue);
Vue.use(IconsPlugin);

Vue.config.productionTip = false;

new Vue({
  render: h => h(App)
}).$mount("#app");

In the code above, we imported the CSS and the called Vue.use to register BootstrapVue for the core library and IconsPlugin for the icons.

To register individual components and directives manually, we can write the following in the entry point file of our Vue app:

Vue.component('b-modal', BModal)
Vue.directive('b-modal', VBModal)

The individual components of the BootstrapVue library can also be imported individually into our app. For instance, if we only want to include the Bootstrap modal in our app, we write:

import { ModalPlugin } from 'bootstrap-vue'
Vue.use(ModalPlugin)

in the same file.

We can also import the component straight into our component if we want to use them in our component. For instance, we can write:

import { BModal, VBModal } from 'bootstrap-vue'

Vue.component('modal-component', {
  components: {
    'b-modal': BModal
  },
  directives: {
    'b-modal': VBModal
  }
})

to import the modal from BootstrapVue and then register them in our component so that we can use them.

Basic Form Controls

One of the common components in a web app are probably forms and the corresponding inputs. BootstrapVue has form controls that are commonly used like text inputs, checkboxes, files inputs, radio buttons, dropdowns, etc.

To make a simple form with BootstrapVue, we can write the following:

App.vue:

<template>
  <div id="app">
    <b-form @submit.prevent="onSubmit" @reset="onReset">
      <b-form-group label="Name:" description="Enter your name" id="name">
        <b-form-input
          id="name-group"
          v-model="form.name"
          type="text"
          required
          placeholder="Enter name"
        ></b-form-input>
      </b-form-group>

      <b-form-group id="email-group" label="Email:" label-for="email">
        <b-form-input id="email" v-model="form.email" required placeholder="Enter email"></b-form-input>
      </b-form-group>

      <b-form-group id="fruit-group" label="Fruit:" label-for="favorite-fruit">
        <b-form-select id="favorite-fruit" v-model="form.fruit" :options="fruits" required></b-form-select>
      </b-form-group>

      <b-form-group id="sign-up">
        <b-form-checkbox-group v-model="form.choices" id="sign-up">
          <b-form-checkbox value="foo">I want foo</b-form-checkbox>
          <b-form-checkbox value="bar">I want bar</b-form-checkbox>
        </b-form-checkbox-group>
      </b-form-group>

      <b-button type="submit" variant="primary">Submit</b-button>
      <b-button type="reset" variant="danger">Reset</b-button>
    </b-form>
  </div>
</template>

<script>
export default {
  name: "App",
  data() {
    return {
      fruits: ["apple", "orange", "banana"],
      form: {}
    };
  },
  methods: {
    onSubmit(){
      console.log(this.form)
    },
    onReset(){
      alert('Form reset')
    }
  }
};
</script>

In the code above, we wrap our controls in the b-form-group component, which corresponds to the form group in the plain JavaScript version of Bootstrap. Inside each b-form-group, we have our form control. If it’s a text input, then we use the b-form-input component. It takes the v-model directive to bind the input value to the model.

The b-form-select component is a dropdown. It takes the options prop, which binds to an array of values and display them in the dropdown as the choices. v-model binds to the selected choice.

The b-form-checkbox component renders the checkbox control. They reside inside the b-form-checkbox-group component, which takes the v-model directive that binds to an array of the checked choices.

b-buttons are render buttons. We can set the style with the variant prop. It also takes the type prop, which sets the type attribute of the button.

All of them items above are wrapped inside the b-form component. It handles the submit event and reset event just like a regular HTML form. In the code above, we have the @submit.prevent directive to prevent the default submit action, so it’ll run onSubmit and log the value.

The onReset handler runs when we click Reset to reset the form.

Inline form

We can add the inline prop on b-form to display form controls and buttons on a single horizontal row.

For instance, we can make a form with an inline input, checkbox and button as follows:

<template>
  <div id="app">
    <b-form inline>
      <label class="sr-only" for="name">Name</label>
      <b-input-group class="mb-2 mr-sm-2 mb-sm-0">
        <b-input id="name" placeholder="Name" v-model='form.name'></b-input>
      </b-input-group>

      <b-form-checkbox class="mb-2 mr-sm-2 mb-sm-0">Remember me</b-form-checkbox>

      <b-button variant="primary">Save</b-button>
    </b-form>
  </div>
</template>

<script>
export default {
  name: "App",
  data() {
    return {
      form: {}
    };
  }
};
</script>

All we did was add inline to the b-form as an attribute. Then everything inside is inline.

Form Validation Feedback

The b-form-invalid-feedback component lets us add feedback when an invalid inputs are entered into an input. There’s also a b-form-valid-feedback to indicate that a valid value is inputted into the input box.

For instance, we can use it as follows:

<template>
  <div>
    <b-form @submit.stop.prevent>
      <label for="email">Email</label>
      <b-input v-model="form.email" :state="validation" id="email-user"></b-input>
      <b-form-invalid-feedback :state="validation">Email not valid.</b-form-invalid-feedback>
      <b-form-valid-feedback :state="validation">Email is valid.</b-form-valid-feedback>
    </b-form>
  </div>
</template>

<script>
export default {
  name: "App",
  computed: {
    validation(){
      return /^\S+@\S+$/.test(this.form.email)
    }
  },
  data() {
    return {
      form: {}
    };
  }
};
</script>

In the code above, we have an input which takes an email. We use a computed property to compute the validity of the inputted text to see if it’s actually an email address. This computation is done in the validation function.

The resulting validation computed property is set as the value of the state prop. Then if validation returns true we display the b-form-valid-feedback component. Otherwise, we display the b-form-invalid-feedback component.

The b-input component also takes the state prop with the value set to validation. The border and icon on the right changes depending if the input is valid. If validation returns true, which means the inputted value is valid, then the border turns green and a check mark icon is displayed. Otherwise, the border turns red and an exclamation mark icon is displayed.

If we don’t want to display any validation feedback and we have the state prop added onto the components, we can set the value to null.

Radio Button

We can use the b-form-group with the b-form-radio-group nested inside to create a group of radio buttons. b-form-radio-group takes the options prop which is the options for the radio button group. It also takes the v-model directive to bind the radio value that’s selected to the model.

For example, we can add the radio button as follows:

<template>
  <div>
    <b-form-group label="Favorite Fruit">
      <b-form-radio-group
        id="radio-group"
        v-model="selected"
        :options="options"
        name="radio"
      ></b-form-radio-group>
    </b-form-group>
    <p>{{selected}}</p>
  </div>
</template>

<script>
export default {
  name: "App",
  data() {
    return {
      selected: "apple",
      options: [
        { text: "apple", value: "apple" },
        { text: "orange", value: "orange" },
        { text: "banana", value: "third", disabled: true },
        { text: "grape", value: { name: 'grape' } }
      ]
    };
  }
};
</script>

In the code above, we have an options array with the data for the choices. The text property will be displayed to the user as the text for each choice. The value will be the selected value. It doesn’t matter what kind of object or value it is. As we can see, anything works.

When we click the value, we get the value selected set as the value of selected.

Images

Bootstrap has the b-img component for displaying images. It’s useful because it makes images responsive so they won’t be larger than their parent elements.

We can use it as follows:

<template>
  <div>
    <b-img
      src="https://images.unsplash.com/reserve/bOvf94dPRxWu0u3QsPjF_tree.jpg?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=755&q=80"
      fluid
      alt="tree"
    ></b-img>
  </div>
</template>

<script>
export default {
  name: "App"
};
</script>

The code above creates a responsive image automatically. It also takes an alt attribute like a regular img element for accessibility.

We can also change fluid to fluid-grow so that the image fills the screen’s width if it’s smaller than width of the screen.

It also has a feature to round the corner of images. To make an image with rounded corners, we can add the rounded prop to the b-img component. If we don’t pass in any value, then all corners are rounded.

We can also set the values as follows:

  • true or no value: round all corners
  • false or prop not present: no rounding or corners (default)
  • 'top': round the top corners
  • 'right': round the right corners
  • 'bottom': round the bottom corners
  • 'left': round the left corners
  • 'circle': make a circle (if square image) or oval (if not square) border
  • '0': turn off rounding of corners

We can make an oval image as follows:

<template>
  <div>
    <b-img
      src="https://images.unsplash.com/reserve/bOvf94dPRxWu0u3QsPjF_tree.jpg?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=755&q=80"
      fluid
      rounded='circle'
      alt="tree"
    ></b-img>
  </div>
</template>

<script>
export default {
  name: "App"
};
</script>

Tables

Tables are an important part of many apps. Therefore, like the regular version of Bootstrap, BootstrapVue also has a table component built in.

We can create a table by using the b-table component. Then we don’t have to create a table from scratch. For instance, we can create a table with some data and an explicit mapping of table fields to the headings by writing:

<template>
  <div>
    <b-table striped hover :items="items" :fields='fields'></b-table>
  </div>
</template>

<script>
export default {
  name: "App",
  data() {
    return {
      fields: [
        { key: "firstName", label: "First" },
        { key: "lastName", label: "Last" },
        "age",
      ],
      items: [
        { age: 38, firstName: "Jane", lastName: "Smith" },
        { age: 21, firstName: "Alex", lastName: "May" },
        { age: 16, firstName: "Mary", lastName: "Jones" },
      ]
    };
  }
};
</script>

In the code above, we have the fields object, which has the key, which is the property names of the items entries. The label is the heading text of the column of the corresponding field name. So firstName in the item from the items entry corresponds has the First heading. The lastName in the item from the items entry has the Last heading.

Modals

Modals are also an important part of many apps. We need them to overlay the page for dialog boxes and other similar things.

BootstrapVue has the b-modal component to make creating modal dialogs easy. We just need to set the id prop of the b-modal and then we use the id name as the v-b-modal‘s directive modifier on the element that’ll open when the element is clicked so that the element will open the modal with the id that we set.

The title is set so that we can set the title of the modal.

For instance, we can write the following to show the modal when a button is clicked:

<template>
  <div>
    <b-button v-b-modal.modal-simple>Show modal</b-button>

    <b-modal id="modal-simple" title="Simple Modal">
      <p>Hello!</p>
    </b-modal>
  </div>
</template>

<script>
export default {
  name: "App"
};
</script>

In the code above, we have the v-b-modal.modal-simple directive on the button to show the modal with the id. Then the modal will show ‘Simple Modal’ as the title once it’s opened.

The modal also has the OK and Cancel button by default.

Conclusion

With BootstrapVue, we can create a Vue app that has styled components fast. It has components for form controls, tables, images, and many more that aren’t shown in this article. It’s easy to use and so we can get results fast.

To components also lets us write cleaner code. As we can see from the b-table example, we didn’t have to reference the table elements at all to create a table. We just need the data and the b-table component will map the fields to table cells.

Categories
JavaScript

How to Check if a Property Exists in a JavaScript Object

Since JavaScript allows you to create dynamic objects, you have to be careful and check if an object’s property actually exists. There are at least 2 ways to do this.

hasOwnProperty

hasOwnProperty is a function built into all objects.

if (obj.hasOwnProperty('prop')) {  
    // do something  
}

Note: Internet Explorer host objects (objects supplied by the environment and are different between browsers) do not have the hasOwnProperty function.

in Operator

in is a built in operator you can use to check for an existence of an object property.

if ('prop' in obj) {  
    // do something  
}

Note: obj ‘s prorotypes will also be checked for the prop property.

Undefined Check

You can check if an object has a property by checking if it’s undefined :

if (typeof obj.prop !== 'undefined') {  
  // do something  
}

or:

if (typeof obj['prop'] !== 'undefined') {  
  // do something  
}

You have to use triple equals so that it won’t check for other falsy values.

Categories
JavaScript JavaScript Basics

JavaScript Best Practice — Replacing Old with New

Like any other programming language, JavaScript has its own list of best practices to make programs easier to read and maintain. There are a lot of tricky parts to JavaScript, and we can follow some best practices to improve our code.

Since ES6 was introduced, new constructs are replacing older ones for good reasons. It’s much shorter, cleaner, and easier to understand.

In this article, we’ll look at which older constructs that can be replaced with new ones, including replacing then with async and await, replacing dictionary objects with Maps, replacing apply with the spread operator, and replacing function constructors with the class syntax.

Replace Then with Async / Await

When we chain promises, we used to do it by using the then method and then returning another promise in the callback function that we pass into then.

This means that we have code that looks something like this:

Promise.resolve(1)  
  .then((val) => {  
    console.log(val);  
    return Promise.resolve(2);  
  })  
  .then((val) => {  
    console.log(val);  
    return Promise.resolve(3);  
  })  
  .then((val) => {  
    console.log(val);  
  })

With the introduction of the async and await syntax, which is just a shorthand for calling the then method repeatedly. We can write the code above like this:

(async () => {  
  const val1 = await Promise.resolve(1);  
  console.log(val1);  
  const val2 = await Promise.resolve(2);  
  console.log(val2);  
  const val3 = await Promise.resolve(3);  
  console.log(val3);  
})();

They both output 1, 2 and 3, but the second code snippet is so much shorter. It’s so much clearer that there’s no reason to go back to the old syntax.

Also, we can loop through promises and run them one after the other by using the for-await-of loop. We can loop through the promises we have above by rewriting the code like the following:

(async () => {  
  const promises = [  
    Promise.resolve(1),  
    Promise.resolve(2),  
    Promise.resolve(3),  
  ] 

  for await (let p of promises) {  
    const val = await p;  
    console.log(val);  
  }  
})();

Code like the one we have above is very handy for looping through many promises or promises that are created on the fly, which we can’t do in the earlier examples.

Replacing Dictionary Objects with Maps

ES6 also introduced the Map constructor, which lets us create hash maps or dictionaries without using JavaScript objects with string keys to do so.

Maps are also better because they have their own methods to get and manipulate the entries.

For example, instead of writing:

const dict = {  
  'foo': 1,  
  'bar': 2  
}

We write:

const dict = new Map([  
  ['foo', 1],  
  ['bar', 2]  
])

Then we can get an entry by using the get method as follows:

console.log(dict.get('foo'));

We can set the value of an existing entry by the key of the entry with the set method:

dict.set('foo', 2);

Also, we can check if an entry exists with the given key with the has method:

dict.has('baz');

There are also the keys and entries methods to get all the keys of the map and all the entries respectively.

For example, we can write:

console.log(dict.keys());

To get an iterator object with the keys of the Map . This means that we can loop through them with the for...of loop or convert it to an array with the spread operator.

Similarly, the entries method returns an iterator object with all the entries in the Map with each entry being an array of [key, value] .

There’s also the value method to get an iterator object with all the values in the Map .

We can also use other primitive values as keys. If we use objects, we can’t get the value back when we look them up since the lookup is done with the === operator which returns false is 2 objects that don’t have the same reference even if they have the same content.

These methods aren’t available in a regular object. Also, we might accidentally get or modify the object’s prototype’s properties instead of its own if we use the for...in loop.

Therefore, there aren’t many reasons to use a regular object as a dictionary anymore.

Replace Apply with Spread

If we don’t want to change the value of this inside the function, there isn’t much reason to use the apply method in functions.

If we only want to call a function with many arguments, we can use the spread operator when passing in an array for our arguments as follows:

const arr = [1, 2, 3, 4, 5];  
const add = (...args) => args.reduce((a, b) => a + b, 0);  
console.log(add(...arr));

All we have to do is to use the spread operator on our array, which is the 3 dots operator in the last line, and then the array will be separated into a comma-separated list of arguments.

Replace Constructor Functions with Classes

Another great ES6 feature is the class syntax for constructor functions. It’s simply syntactic sugar that makes the constructor function looks like a class.

The advantage of it is that it makes inheritance easy.

For example, if we want to inherit from a constructor function, we have to write something like this:

function Person(name) {  
  this.name = name;  
}

function Employee(name, employeeCode) {  
  Person.call(this, name);  
  Employee.prototype.constructor = Person;  
  this.employeeCode = employeeCode;  
}

const employee = new Employee('Joe', 1);  
console.log(employee)

This syntax looks strange coming from class-based object-oriented languages like Java.

However, the class syntax makes things look a lot familiar to developers that used other languages more than JavaScript. We can rewrite the code above to the following:

class Person {  
  constructor(name) {  
    this.name = name;  
  }  
}

class Employee extends Person {  
  constructor(name, employeecode) {  
    super(name);  
    this.employeecode = employeecode;  
  }  
}

const employee = new Employee('Joe', 1);  
console.log(employee)

The code does the same thing as what we have above. However, it’s clearer what we’re inheriting from since we have the extends keyword to indicate what we’re inheriting from.

With the constructor function, we have to worry about the value of this in the first argument of the call method, and what we pass into the subsequent arguments of call.

With the class syntax, we don’t have to worry about this. If we forgot to make the super call like the following code:

class Person {  
  constructor(name) {  
    this.name = name;  
  }  
}

class Employee extends Person {  
  constructor(name, employeecode) {     
    this.employeecode = employeecode;  
  }  
}

const employee = new Employee('Joe', 1);  
console.log(employee)

We’ll get the error ‘Uncaught ReferenceError: Must call super constructor in derived class before accessing ‘this’ or returning from derived constructor.’

It’s one less chance of making a mistake.

We don’t get any error if we omit the Person.call like in the Employee constructor function since the browser doesn’t know we want Employee to inherit from Person .

In addition, when we log the prototype of employee , we get that the prototype of employee is Person as expected with the class syntax.

However, we don’t get that unless we put Employee.prototype.constructor = Person; which is easily forgotten.

Conclusion

async and await and for-await-of are new constructs that make chaining promises much cleaner. It’s much better to use them instead of using then because of it.

for-await-of also lets us loop through promises that are generated dynamically which we can’t do with then or async and await alone.

Map s are much better than plain objects for hashes and dictionaries because it has its own methods to manipulate and get the entries. Also, we may accidentally be accessing the properties of prototype of plain objects.

If we don’t want to change the value of this inside a function, we can replace the apply method for calling functions with an array of arguments with the spread operator, which does the same thing.

Finally, the class syntax for constructors is much better than the original function syntax since we can inherit from parent classes easier than setting the prototype constructor of a constructor function.