Categories
Vue 3

Vue Router 4 — Navigation

Vue Router 4 is in beta and it’s subject to change.

To build a single page app easily, we got to add routing so that URLs will be mapped to components that are rendered.

In this article, we’ll look at how to use Vue Router 4 with Vue 3.

Navigation

We can navigate to a route with the this.$router ‘s methods.

For example, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="https://unpkg.com/vue@next"></script>
    <script src="https://unpkg.com/vue-router@4.0.0-beta.7/dist/vue-router.global.js"></script>
    <title>App</title>
  </head>
  <body>
    <div id="app">
      <p>
        <router-link to="/foo">Go to Foo</router-link>
        <router-link to="/bar">Go to Bar</router-link>
        <a @click="goBack" href="#">Go Back</a>
      </p>
      <router-view></router-view>
    </div>
    <script>
      const Foo = { template: "<div>foo</div>" };
      const Bar = { template: "<div>bar</div>" };

      const routes = [
        { path: "/foo", component: Foo },
        { path: "/bar", component: Bar }
      ];

      const router = VueRouter.createRouter({
        history: VueRouter.createWebHistory(),
        routes
      });

      const app = Vue.createApp({
        methods: {
          goBack() {
            window.history.length > 1
              ? this.$router.go(-1)
              : this.$router.push("/foo");
          }
        }
      });
      app.use(router);
      app.mount("#app");
    </script>
  </body>
</html>

to create a goBack method and call it when we click on the Go Back link.

In the method, we check if there are anything in the browser history.

If there is, then we go back to the previous page.

Otherwise, we go to the /foo page.

Reacting to Params Changes

We can react to route params changes in a few ways.

For instance, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="https://unpkg.com/vue@next"></script>
    <script src="https://unpkg.com/vue-router@4.0.0-beta.7/dist/vue-router.global.js"></script>
    <title>App</title>
  </head>
  <body>
    <div id="app">
      <p>
        <router-link to="/foo/1">Foo 1</router-link>
        <router-link to="/foo/2">Foo 2</router-link>
      </p>
      <router-view></router-view>
    </div>
    <script>
      const Foo = {
        template: "<div>foo</div>",
        watch: {
          $route(to, from) {
            console.log(to, from);
          }
        }
      };

      const routes = [{ path: "/foo/:id", component: Foo }];

      const router = VueRouter.createRouter({
        history: VueRouter.createWebHistory(),
        routes
      });

      const app = Vue.createApp({});
      app.use(router);
      app.mount("#app");
    </script>
  </body>
</html>

We have the /foo/:id route that maps to the Foo component.

And in the Foo component, we have the $route watcher to watch the route.

to has the route to go to. from has the route that we departed from.

They’re both objects with the path, route metadata, route parameters, query parameters, and more.

Also, we can use the beforeRouteUpdate method to watch for route changes:

<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="https://unpkg.com/vue@next"></script>
    <script src="https://unpkg.com/vue-router@4.0.0-beta.7/dist/vue-router.global.js"></script>
    <title>App</title>
  </head>
  <body>
    <div id="app">
      <p>
        <router-link to="/foo/1">Foo 1</router-link>
        <router-link to="/foo/2">Foo 2</router-link>
      </p>
      <router-view></router-view>
    </div>
    <script>
      const Foo = {
        template: "<div>foo</div>",
        beforeRouteUpdate(to, from, next) {
          console.log(to, from);
          next();
        }
      };

      const routes = [{ path: "/foo/:id", component: Foo }];

      const router = VueRouter.createRouter({
        history: VueRouter.createWebHistory(),
        routes
      });

      const app = Vue.createApp({});
      app.use(router);
      app.mount("#app");
    </script>
  </body>
</html>

Instead of watching the $route object, we have the beforeRouteUpdate hook, which is made available with the app.use(router) call.

Now when we click on the links, we’ll see the same data as we did with the watcher.

The only difference is that we call next to move to the next route.

Conclusion

We can watch for navigation with watchers or hooks with Vue Router 4.

Categories
Vue 3

Vue Router 4–404 and Nested Routes

Vue Router 4 is in beta and it’s subject to change.

To build a single page app easily, we got to add routing so that URLs will be mapped to components that are rendered.

In this article, we’ll look at how to use Vue Router 4 with Vue 3.

Catch-All / 404 Not Found Route

We can create a catch-all or 404 route by using the asterisk pattern as the path.

For example, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="https://unpkg.com/vue@next"></script>
    <script src="https://unpkg.com/vue-router@4.0.0-beta.7/dist/vue-router.global.js"></script>
    <title>App</title>
  </head>
  <body>
    <div id="app">
      <p>
        <router-link to="/foo">Foo</router-link>
      </p>
      <router-view></router-view>
    </div>
    <script>
      const Foo = {
        template: "<div>foo</div>"
      };

      const NotFound = {
        template: "<div>not found</div>"
      };

      const routes = [
        { path: "/foo", component: Foo },
        { path: "/:catchAll(.*)", component: NotFound }
      ];

      const router = VueRouter.createRouter({
        history: VueRouter.createWebHistory(),
        routes
      });

      const app = Vue.createApp({});
      app.use(router);
      app.mount("#app");
    </script>
  </body>
</html>

We define a catch-all route with the /:catchAll(.*) capture group instead of * as in Vue Router 3.

Now when we go to any path that isn’t foo , we’ll see the ‘not found’ message.

Nested Routes

We can create nested routes with the children property.

For instance, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="https://unpkg.com/vue@next"></script>
    <script src="https://unpkg.com/vue-router@4.0.0-beta.7/dist/vue-router.global.js"></script>
    <title>App</title>
  </head>
  <body>
    <div id="app">
      <p>
        <router-link to="/parent/home">parent</router-link>
      </p>
      <router-view></router-view>
    </div>
    <script>
      const Parent = {
        template: `<div>
          parent
          <router-view></router-view>
        </div>`
      };

      const Home = {
        template: `<div>home</div>`
      };

       const routes = [
        {
          path: "/parent",
          component: Parent,
          children: [{ path: "home", component: Home }]
        }
      ];

      const router = VueRouter.createRouter({
        history: VueRouter.createWebHistory(),
        routes
      });

      const app = Vue.createApp({
        watch: {
          $route() {
            console.log(this.$route.resolve);
          }
        }
      });
      app.use(router);
      app.mount("#app");
    </script>
  </body>
</html>

We have a routes array with an object that has the children property.

The property has an object with the path and component properties like a regular route.

In the router-link , the to prop has the path to the nested route.

The Parent component has the router-view component so that we can view the content of the child route.

Therefore, when we click on the parent link, we see the:

parent
home

text displayed.

We can have URL parameters in the parent route.

For example, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="https://unpkg.com/vue@next"></script>
    <script src="https://unpkg.com/vue-router@4.0.0-beta.7/dist/vue-router.global.js"></script>
    <title>App</title>
  </head>
  <body>
    <div id="app">
      <p>
        <router-link to="/parent/1/home">parent</router-link>
      </p>
      <router-view></router-view>
    </div>
    <script>
      const Parent = {
        template: `<div>
          <div>parent {{ $router.currentRoute.value.params.id }}</div>
          <div><router-view></router-view></div>
        </div>`
      };

      const Home = {
        template: `<div>home</div>`
      };

      const routes = [
        {
          path: "/parent/:id",
          component: Parent,
          children: [{ path: "home", component: Home }]
        }
      ];

      const router = VueRouter.createRouter({
        history: VueRouter.createWebHistory(),
        routes
      });

      const app = Vue.createApp({
        watch: {
          $route() {
            console.log(this.$route.resolve);
          }
        }
      });
      app.use(router);
      app.mount("#app");
    </script>
  </body>
</html>

To add the :id URL parameter placeholder.

Since the router-link ‘s to prop is now /parent/1/home and we have the $router.currentRoute.value.params.id in the Parent ‘s template, we’ll see the number 1 displayed.

The URL parameter placeholder of the parent stays with the parent.

Conclusion

The way we define 404 and nested routes are slightly different from Vue Router 3 in Vue Router 4.

Categories
Vue

Vuelidate — More Complex Custom Validators

Vue.js doesn’t come with any form validation capabilities by default.

Therefore, we need to add our own form validation library or with our own code.

In this article, we’ll look at how to create custom validators with Vuelidate.

Accessing Component

We can access the whole component’s model with Vuelidate.

For example, we can write:

<template>
  <div id="app">
    <div>
      <label>name</label>
      <input v-model.trim="$v.name.$model">
      <div v-if="!$v.name.mustBeCool">name is invalid.</div>
    </div>

<div>
      <label>field</label>
      <input v-model.trim="$v.field.$model">
    </div>
  </div>
</template>
<script>
import { helpers } from "vuelidate/lib/validators";

const contains = param =>
  helpers.withParams(
    { type: "contains", value: param },
    (value, vm) => !helpers.req(value) || vm[param.field].includes(param.text)
  );
export default {
  name: "App",
  data() {
    return {
      name: "",
      field: ""
    };
  },
  validations: {
    name: {
      mustBeCool: contains({ text: "cool", field: "field" })
    },
    field: {}
  }
};
</script>

to add a validator that checks if the field field has the word 'cool' in it.

Is it is, then the field that uses the contains validator is valid if the field field has the word 'cool' in it.

vm has the component’s view model, which has the states.

Regex Based Validator

We can add validation based on regex.

For example, we can write:

<template>
  <div id="app">
    <div>
      <label>name</label>
      <input v-model.trim="$v.name.$model">
      <div v-if="!$v.name.alpha">name is invalid.</div>
    </div>
  </div>
</template>
<script>
import { helpers } from "vuelidate/lib/validators";

const alpha = helpers.regex("alpha", /^[a-zA-Z]*$/);

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

We added the alpha validator to check if the inputted value is all alphabetical.

Locator Based Validator

Also, we can validate with the locator strategy.

For example, we can write:

<template>
  <div id="app">
    <div>
      <label>name</label>
      <input v-model.trim="$v.password.$model">
    </div>

    <div>
      <label>confirm password</label>
      <input v-model.trim="$v.confirmPassword.$model">
      <div v-if="!$v.confirmPassword.same">confirm pasword must be the same as password.</div>
    </div>
  </div>
</template>
<script>
import { helpers } from "vuelidate/lib/validators";

const sameAs = equalTo =>
  helpers.withParams({ type: "sameAs", eq: equalTo }, function(
    value,
    parentVm
  ) {
    return value === helpers.ref(equalTo, this, parentVm);
  });

export default {
  name: "App",
  data() {
    return {
      password: "",
      confirmPassword: ""
    };
  },
  validations: {
    password: {},
    confirmPassword: {
      same: sameAs("password")
    }
  }
};
</script>

The helper.ref method checks if 2 fields are equal to each other with the equalTo parameter.

The equalTo parameter has the field name to check for in order to determine if it’s the same as that field.

So equalTo would be 'password' since we want to check if it’s the same as 'password' .

We return the condition to compare if the other field’s value is the same as what we inputted into the given field.

Conclusion

We can create our own custom validators with various strategies with Vuelidate.

Categories
Vue

Vuelidate — Dynamic Validation

Vue.js doesn’t come with any form validation capabilities by default.

Therefore, we need to add our own form validation library or with our own code.

In this article, we’ll look at how to validate forms dynamically with Vuelidate.

Dynamic Validation Schema

We can validate forms dynamically with Vuelidate.

For example, we can write:

<template>
  <div id="app">
    <div>
      <label>Name</label>
      <input v-model.trim="$v.name.$model">
      <div v-if="!$v.name.required">Name is required.</div>
    </div>

<div v-if="hasDescription">
      <label>Description</label>
      <input v-model.trim="$v.description.$model">
      <div v-if="!$v.description.required">Description is required.</div>
    </div>

<div>
      <label>Has Description</label>
      <input v-model="hasDescription" type="checkbox">
    </div>
  </div>
</template>
<script>
import { required } from "vuelidate/lib/validators";

export default {
  name: "App",
  data() {
    return {
      hasDescription: false,
      name: "",
      description: ""
    };
  },
  validations() {
    if (!this.hasDescription) {
      return {
        name: {
          required
        }
      };
    } else {
      return {
        name: {
          required
        },
        description: {
          required
        }
      };
    }
  }
};
</script>

We have the validations method to return an object with the fields to validate according to the this.hasDescription property.

In the template, we need to check the value if the hasDescription and only render the description field if hasDescription is true .

hasDescription ‘s value is controlled by the checkbox.

Dynamic Parameters

We can also set the name of the field dynamically.

For example, we can write:

<template>
  <div id="app">
    <div>
      <label>Name</label>
      <input v-model.trim="$v.name.$model">
      <div v-if="!$v.name[valName]">Name is invalid.</div>
    </div>
  </div>
</template>
<script>
import { minLength } from "vuelidate/lib/validators";

export default {
  name: "App",
  data() {
    return {
      name: "",
      minLength: 3,
      valName: "validatorName"
    };
  },
  validations() {
    return {
      name: {
        [this.valName]: minLength(this.minLength)
      }
    };
  }
};
</script>

We have a dynamic name for the validator for the name field.

This can be used in the template to check for the name.

Builtin Validators

There are various kinds of validators that comes with Vuelidate.

They include:

  • required — checks if a field is required.
  • requiredIf — a field is required given that the predicate is true
  • requiredUnless — a field is required given that the predicate is false
  • minLength — minimum length of input is required
  • maxLength — max length of input required
  • minValue — min numeric value
  • maxValue — max numeric value
  • between — numeric range
  • alpha — alphabetical characters
  • alphaNum — alphanumeric characters
  • numeric — numbers
  • integer — integers
  • decimal — decimal numbers
  • email — email address
  • ipAddress — IP address
  • macAddress — MAC address
  • sameAs — checks if a field has the same value as another field
  • url — URL
  • or — passes when at least one of the validator passes
  • and — passes when all validators pass
  • not — passes when the provided validator doesn’t pass
  • withParams — validator modifier for creating custom validator

For instance, we can write:

<template>
  <div id="app">
    <div>
      <label>field</label>
      <input v-model.trim="$v.field.$model">
      <div v-if="!$v.field.required">field is invalid.</div>
    </div>

    <div>
      <label>is optional</label>
      <input v-model="isOptional" type="checkbox">
    </div>
  </div>
</template>
<script>
import { requiredUnless } from "vuelidate/lib/validators";

export default {
  name: "App",
  data() {
    return {
      field: "foo",
      isOptional: true
    };
  },
  validations: {
    field: {
      required: requiredUnless("isOptional")
    }
  }
};
</script>

We have the isOptional field with the required property set to the requiredUnless validator.

requiredUnless(“isOptional”) means that the field is required when isOptional is true.

Conclusion

We can validate fields dynamically with Vuelidate.

It also comes with many validators built-in.

Categories
Vue

Vuelidate — Custom Validators

Vue.js doesn’t come with any form validation capabilities by default.

Therefore, we need to add our own form validation library or with our own code.

In this article, we’ll look at how to create custom validators with Vuelidate.

Custom Validators

We can create our own custom validators with Vuelidate.

For instance, we can write:

<template>
  <div id="app">
    <div>
      <label>name</label>
      <input v-model.trim="$v.name.$model">
      <div v-if="!$v.name.required">name is required.</div>
      <div v-if="!$v.name.mustBeCool">name is invalid.</div>
    </div>
  </div>
</template>
<script>
import { required } from "vuelidate/lib/validators";
const mustBeCool = value => value.includes("cool");

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

We created the mustBeCool function to validate the name field with it.

value has the inputted value.

We just return what we expect to be valid in the function.

Optional Validator

We can also validate optional fields with our custom validator.

To do this, we use a helper function from Vuelidate.

For example, we can write:

<template>
  <div id="app">
    <div>
      <label>name</label>
      <input v-model.trim="$v.name.$model">
      <div v-if="!$v.name.mustBeCool">name is invalid.</div>
    </div>
  </div>
</template>
<script>
import { helpers } from "vuelidate/lib/validators";
const mustBeCool = value => !helpers.req(value) || value.includes("cool");

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

to make name an optional field.

We only validate if the value is a non-empty string.

The helpers.req(value) to check if the value is required or not.

Negating that means we make the field optional.

Extra Parameters

We can add extra parameters to the field.

For example, we can write:

<template>
  <div id="app">
    <div>
      <label>name</label>
      <input v-model.trim="$v.name.$model">
      <div v-if="!$v.name.mustBeCool">name is invalid.</div>
    </div>
  </div>
</template>
<script>
import { helpers } from "vuelidate/lib/validators";
const contains = param => value => !helpers.req(value) || value.includes(param);
export default {
  name: "App",
  data() {
    return {
      name: ""
    };
  },
  validations: {
    name: {
      mustBeCool: contains("cool")
    }
  }
};
</script>

to add a parameter to our code for validation.

We created the contains function which takes a param parameter.

Then we can check for param with the includes method to see if what we entered is included.

value has the inputted value.

$props Support

Also, we can use the withParams method to create our validator with parameters.

If we use withParams , we get the information about the validation rule with the $params property.

For example, we can write:

<template>
  <div id="app">
    <div>
      <label>name</label>
      <input v-model.trim="$v.name.$model">
      <div v-if="!$v.name.mustBeCool">name is invalid.</div>
    </div>
  </div>
</template>
<script>
import { helpers } from "vuelidate/lib/validators";
const contains = param =>
  helpers.withParams(
    { type: "contains", value: param },
    value => !helpers.req(value) || value.includes(param)
  );

export default {
  name: "App",
  data() {
    return {
      name: ""
    };
  },
  validations: {
    name: {
      mustBeCool: contains("cool")
    }
  }
};
</script>

We called the helpers.withParams method to get our parameter for our contains function.

The type property lets us set the type of property that we’re accepting.

Conclusion

We can create custom validators with Vuelidate.