One place for hosting & domains

      Custom

      How To Use Built-In and Custom Directives in Vue.js


      The author selected Open Sourcing Mental Illness to receive a donation as part of the Write for DOnations program.

      Introduction

      As a front-end framework, Vue.js can be thought of as a mixture of React and Angular. Vue borrows the prop-driven approach of React, but also uses directives, which were made popular by Angular. In this context, directives are reusable chunks of code or logic that developers can use within an HTML template. These can allow you to manipulate your HTML in many different ways, such as conditionally rendering an element, connecting events to an element, or creating dynamic attributes that depend on your Vue code.

      In this tutorial, you will try out some of the most common built-in Vue directives, such as v-if, v-show, v-on, v-bind, v-model, and v-html. For these first sections, the tutorial will lead you through examples that you can follow in an online Vue playground like the Vue Single File Component Playground. In addition to this, you will create a new Vue project to try out custom directives that you can add to HTML elements for additional functionality.

      Prerequisites

      Using the v-if, v-else, and v-else-if Directives

      Vue.js comes pre-packaged with a number of directives that developers commonly use when working within the framework. Directives such as v-if, v-show, v-on, v-model, v-bind, v-html, and more are all provided for you out-of-the-box. All of these provided directives start with v-.

      The v-if, v-else, and v-else-if directives allow you to create conditionally rendered HTML elements, and work similarly to JavaScript’s if, else if, else conditions. Instead of returning HTML or code like you would in JavaScript, Vue will conditionally render that block of code.

      To apply a Vue directive, you will add it to an HTML element in the template section of your Vue single-file component. For example, if you wanted to add a v-if directive to a paragraph element, you would use the following syntax:

      <p v-if="https://www.digitalocean.com/community/tutorials/condition"></p>
      

      In this case, the highlighted condition is a placeholder for the boolean expression that determines if the paragraph element is rendered or not.

      To illustrate how the v-if directives work in a real situation, create the following code for a user welcome page:

      <template>
        <p v-if="user.firstName && user.lastName">
          Welcome, {{ user.firstName }} {{ user.lastName }}!
        </p>
        <p v-else-if="user.username">
          Welcome, {{ user.username }}!
        </p>
        <div v-else>
          <button>Login</button>
          <button>Create Account</button>
        </div>
      </template>
      
      <script setup>
          const user = {
          firstName: 'Sammy',
          lastName: 'Shark',
          username: 'sammyDO'
        }
      </script>
      

      In this code, there are a number of HTML elements, including two <p> tags and a <div> tag with two buttons. However, when this code runs in the browser, only one of these chunks of code will render, depending on the condition that evaluates to true. HTML with false conditions will not render in your DOM (Document Object Model).

      As it is now, since both firstName and lastName have values in the <script> tag, the expression user.firstName && user.lastName will evaluate to true, and the browser will render the paragraph. This will look like the following image in your browser:

      Web page with the words

      If either firstName or lastName is not defined, the condition will evaluate to false, and Vue will move to the v-else-if directive. This directive is attached to the user.username expression, so if this evaluates to true it will render the attached paragraph element. To try this, delete the firstName in your <script> tag:

      ...
      <script setup>
          const user = {
          lastName: 'Shark',
          username: 'sammyDO'
        }
      </script>
      

      Since the condition user.firstName && user.lastName now evaluates to false but user.username evaluates to true, the username will render instead. This is shown in the following image:

      Web page with the words

      Lastly, if all the conidtions evaluate to false, control will pass to the v-else directive, which here renders two buttons to log in or create an account. To try this out, delete the username data in the script element:

      ...
      <script setup>
          const user = {
          lastName: 'Shark',
        }
      </script>
      

      Now you will find default buttons rendered in the browser:

      Web page with

      It is important to note that you must have an element with a v-if directive directly before a v-else-if or v-else directive. Otherwise it will not work.

      As an example, the following code snippet is considered invalid and you will get an error in your browser’s console:

      <template>
        <p v-if="user.firstName && user.lastName">
          Welcome, {{ user.firstName }} {{ user.lastName }}!
        </p>
        <p v-else-if="user.username">
          Welcome, {{ user.username }}!
        </p>
        <h1>Some Title</h1>
        <div v-else>
          <button>Login</button>
          <button>Create Account</button>
        </div>
      </template>
      
      <script setup>
          const user = {
          firstName: 'Sammy',
          lastName: 'Shark',
          username: 'sammyDO'
        }
      </script>
      

      Since the <h1> element after the paragraph with the v-else-if directive has no directive, this code will fail to render and will yield the following syntax error:

      Output

      SyntaxError: v-else/v-else-if has no adjacent v-if.

      With this summary of v-if covered, you can now try out another way to conditionally display HTML elements: the v-show directive.

      Using the v-show Directive

      Beyond the v-if-related directives, there is another directive that you can use to display HTML elements based on a condition. That directive is v-show. It’s similar to v-if, with the exceptions that there are no else-if and else counterparts and the elements are conditionally displayed rather than conditionally rendered.

      Take a look at the following code that addresses a similar situation as in the last section:

      <template>
        <div v-show="!userIsLoggedIn">
          <button>Login</button>
          <button>Create Account</button>
        </div>
        <div v-show="userIsLoggedIn">
          <p>Welcome!</p>
        </div>
      </template>
      
      <script setup>
          const userIsLoggedIn = true
      </script>
      

      In this code, you’ve attached v-show to two <div>s, along with conditional expressions. v-show will display the attached HTML if the condition in the quotation marks is met. In this example, userIsLoggedIn evaluates to true, so the browser will display the <div> with the Welcome! paragraph:

      Web page with the words

      Now change the userIsLoggedIn value to false:

      ...
      <script setup>
          const userIsLoggedIn = false
      </script>
      

      You will again find the login buttons:

      Web page with

      It is worth emphasizing the difference between v-if and v-show. v-if conditionally renders the HTML; if the conditional expression evaluates to false, the DOM will not include it at all. v-show on the other hand will always render the HTML in the DOM. But the element will not appear in the browser because it will be hidden with the display: none CSS style.

      With the conditional v-if and v-show directives covered, you can now move on to the v-on directive to tie events to HTML elements.

      Using the v-on Directive

      The v-on directive executes functions on a specific event. This can be a custom event or a standard JavaScript event, such as click, hover, or mouseenter. When using the v-on directive, you must provide the event type after a colon (:) and the function to be executed. The component’s function name will reside in-between the quotation marks.

      As an example of this, examine the following highlighted code:

      <template>
        <button v-on:click="handleClick">Click Me</button>
      </template>
      
      <script setup>
          function handleClick() {
          alert('You clicked the button!')
        }
      </script>
      

      In this example, the component function handleClick will execute when the user clicks on the <button> element that the directive is attached to. If you run this code and click on the button, you will receive an alert that reads You clicked the button!:

      Browser popup box that reads

      Events with the v-on directive can also have event modifiers chained to the event itself. These event modifiers can change how the event is executed, and can save time when writing functionality that would normally take multiple lines of JavaScript. Some modifiers provided by Vue include:

      • once: Limits the event to fire only once.
      • self: Event will only trigger if the event target is the same element that holds the directive.
      • prevent: Stops the event from happening.
      • stop: Stops the event from propagating.

      Next, you will run through an example of the once event modifier to try out the syntax. Add the following highlighted code to the previous snippet:

      <template>
        <button v-on:click.once="handleClick">Click Me</button>
      </template>
      ...
      

      With the .once modifier, the function handleClick will only execute one time. Try clicking the Click Me button, and the alert will pop up. Clear the alert, then click it again. The event will not fire because it has already fired once.

      The v-on directive also has a shorthand syntax. To use the shorthand syntax, replace v-on: with @.

      <template>
        <button @click.once="handleClick">Click Me</button>
      </template>
      ...
      

      This will lead to the same behavior as v-on:click.once.

      Now that events have been addressed with v-on, you can move on to binding data to template elements with v-bind and v-model.

      Using the v-bind and v-model Directives

      Vue.js is a Model-View-ViewModel (MVVM) framework, which means separate data (the model) updates the HTML (view), and the view in turn updates the model. This separation means that view, or what renders in the browser, can operate independently of the data processing.

      To enact the MVVM approach, Vue provides ways for data, props, and computed properties to pass into a Vue component. This is done with two-way data binding, which allows for the view and model to interact with each other.

      In the following code, you have a <p> element with dynamic content that changes depending on the data values:

      <template>
        <p>I am from {{ city }}.</p>
      </template>
      
      <script setup>
          const city = 'Cincinnati'
      </script>
      

      In this snippet, {{ city }} is a data reference to the city constant in the <script> element after it. As it is now, {{ city }} will evaluate to the string "Cincinnati", and a paragraph element with the words I am from Cincinnati will render in your browser. If you were to change Cincinnati to Seattle, the {{ city }} in the template would update accordingly.

      For this dynamic content, you don’t need to use a directive: The template has access to the city constant automatically. However, if you wanted to make an HTML attribute dynamic by referencing the <script> data, you would have to explicitly bind the data with v-bind.

      To illustrate this, you will enclose the {{ city }} placeholder in a link (<a>) tag that will dynamically lead to the Wikipedia page for the city assigned to the city constant. First, wrap the {{ city }} in an <a> tag so the user can click on it and get more information on the city itself. After that, create a computed property called wikipediaLink and have it return the appropriate URL. Add the highlighted code:

      <template>
        <p>I am from <a href="">{{ city }}</a>.</p>
      </template>
      
      <script setup>
        import { computed } from 'vue'
      
          const city = 'Cincinnati"https://www.digitalocean.com/community/tutorials/const wikipediaLink = computed(() =>`https://en.wikipedia.org/wiki/${city}`)
      </script>
      

      In the <script> element, you imported the computed function from the Vue library, then used it to assign a URL to wikipediaLink. The URL string uses a template literal to change depending on the value of city.

      Now that you have your Wikipedia link, go ahead and add the computed property name to the href attribute of the anchor tag you added earlier:

      <template>
        <p>I am from <a href="https://www.digitalocean.com/community/tutorials/wikipediaLink">{{ city }}</a>.</p>
      </template>
      ...
      

      If you were to view this in a browser at this point, you would recieve a console error. This is because right now Vue.js thinks wikipediaLink is a string literal, which is not a valid URL.

      To tell Vue.js to use the returned value of the wikipediaLink computed property, you need to use the v-bind directive. This will bind the reference in the template to the computed property, as shown in the following:

      <template>
        <p>I am from <a v-bind:href="https://www.digitalocean.com/community/tutorials/wikipediaLink">{{ city }}</a>.</p>
      </template>
      ...
      

      Now, the href value will be the URL for the Cincinnati Wikipedia page. This URL will update if the city property changes to another city, such as Seattle.

      Similar to v-on, v-bind also has a shorthand that you can opt to use. To use the shorthand, replace v-bind: with :, as shown in the following:

      <template>
        <p>I am from <a :href="https://www.digitalocean.com/community/tutorials/wikipediaLink">{{ city }}</a>.</p>
      </template>
      

      There is another directive in Vue.js that is used to bind a value to a data property. However, this directive only works for input tags:

      <template>
        <input v-model="city" type="text" />
      </template>
      
      <script setup>
        const city = 'Cincinnati'
      </script>
      

      When using the v-model directive, you are binding the input fields value attribute to the data property in the quotation marks. It is recommended to use the v-model directive over v-bind for form <input /> tags.

      This Vue component will render a text input box with the default text of Cincinnati, as shown in the following image:

      Text input box with

      This section covered different ways to bind HTML attributes to variable or computed data. Next, you will use the v-html directive to inject HTML into your template.

      Using the v-html Directive

      The last pre-packaged directive that this tutorial will go over is v-html. This directive converts HTML inside of a string literal into raw HTML for the browser to read, which will allow you more flexibility in how you create and apply your HTML.

      In this hypothetical component, you have a <div> that has a v-html directive with a data property as its value:

      <template>
        <div v-html="someHtmlCode" />
      </template>
      
      <script setup>
        const someHtmlCode = `<p>Some <span>HTML</span> in this string</p>`
      </script>
      

      The v-html directive is telling Vue.js to inject the string of the someHtmlCode data reference into the <div /> in the template. When compiled by Vue, you will find the following in your DOM:

      <div>
        <p>Some <span>HTML</span> in this string</p>
      </div>
      

      The HTML will also render in your browser:

      A browser window with

      This directive is useful if you need to render HTML that comes from a REST API service or large HTML from an external JavaScript file.

      At this point in the tutorial, you’ve tried out the directives that are provided to you by Vue.js. However, there may be times when you will need a directive that Vue doesn’t provide. In cases such as these, you can create a custom directive to handle specific logic in your HTML template.

      Creating a Custom Directive

      In the Vue.js framework, you have the ability to create custom directives to suit the individual needs for your project. In this section, for example, you will create a v-theme directive that applies a specific style to an element of your template.

      Up to this point, you did not have to follow along with a stand-alone Vue project. However, in this section, you will create a fresh project generated from the Vue CLI.

      Open your terminal and generate a new project with the following command. The name of this project will be custom-directive, which will also be the name of the root directory:

      • vue create custom-directive

      For this tutorial, select Vue 3x for Choose Vue Version. Once the app has been created, open your generated project in your text editor of choice. Then open the main.js file in the src directory.

      First, you will get this file set up. Store createApp(App) into a const so you can reference it later:

      custom-directive/src/main.js

      import { createApp } from 'vue'
      import App from './App.vue"https://www.digitalocean.com/community/tutorials/const app = createApp(App)
      
      app.mount('#app')
      

      Now with that createApp function in its own const, you can extend this app by adding a directive:

      custom-directive/src/main.js

      import { createApp } from 'vue'
      import App from './App.vue'
      
      const app = createApp(App)
      
      app.directive("theme", {})
      
      app.mount('#app')
      

      In this code, you are leveraging the directive() function on the Vue instance. This function accepts two arguments: the directive name (theme in this case) and an object with lifecycle hooks.

      You will set this code up so that it executes when the component is mounted. This mounted hook accepts two arguments: el, the HTML element, and binding, the value passed into the directive. With this information, you can construct the following:

      custom-directive/src/main.js

      const app = createApp(App)
      
      app.directive("theme", {
        mounted(el, binding) {
      
        }
      })
      
      app.mount('#app')
      

      For this theme directive, you will pass in a string to determine the element’s styling. This string will either be primary, secondary, or tertiary, each of which corresponds to a color in a hypothetical colorscheme.

      Now that the directive code is in place, you can add the logic. To access the value inside of the quotaion marks of the directive, you can use binding.value. To change the color of the element’s text, you will use JavaScript to access the el properties:

      custom-directive/src/main.js

      const app = createApp(App)
      
      app.directive("theme", {
        mounted(el, binding) {
          if (binding.value === 'primary') {
            el.style.color="red"
          } else if (binding.value === 'secondary') {
            el.style.color="green"
          } else if (binding.value === 'tertiary') {
            el.style.color="blue"
          } else {
            el.style.color="black"
          }
        }
      })
      
      app.mount('#app')
      

      The highlighted section of this snippet is a series of if/else statements. If the value passed into the directive is primary, the text color will be red, secondary will be green, tertiary will be blue, and no value will revert to the default black.

      Close and save your main.js file.

      At this point, you have created a custom directive that you can use in your Vue.js application. Vue will automatically prefix the directive name with v-, so the template can access your directive as v-theme.

      Open your App.vue file in your text editor of choice. In the template, add a <p> tag with some text in it:

      custom-directive/src/App.vue

      <template>
        <p>This text will change color based on the directive value!</p>
      </template>
      

      Save this file, then start the application with npm run serve. This will run your project on the :8080 port on your localhost:

      To view your generated project, open your browser of choice and visit localhost:8080 in the URL bar. You will find your paragraph element rendered in black:

      The words

      Next, change the color by adding your directive to the HTML:

      custom-directive/src/App.vue

      <template>
        <p v-theme="`primary`">This text will change color based on the directive value!</p>
      </template>
      

      Save this file and open it in your web browser. The value inside the quotation marks is a raw value and automatically bound to Vue, so inside the quotation marks you will need to wrap primary using back ticks to convert it to a string.

      Save your file and the text on your rendered site will turn red:

      The words

      The directive reads the value in the binding object and executes the code accordingly. Since the value is primary, JavaScript will change the color of the text to red.

      In this section, you created a custom directive, registered it, and executed its logic. You also added your new custom directive and assigned it to an HTML element in the template.

      Conclusion

      In this tutorial, you ran through exercises to test out what directives are and how to use them. Specifically, you used the most common built-in directives, including v-if, v-on, v-show, and v-html. In addition to this, you registered and created your own custom directive. This directive, v-theme, can now be used on any HTML element to execute a JavaScript function.

      While you went over a number of directives, there are many more made available to you by Vue.js. For more information regarding directives, it is recommended to review the official Vue.js Documentation. For more tutorials on Vue, check out the How To Develop Websites with Vue.js series page.



      Source link

      How To Create Custom Types in TypeScript


      The author selected the COVID-19 Relief Fund to receive a donation as part of the Write for DOnations program.

      Introduction

      TypeScript is an extension of the JavaScript language that uses JavaScript’s runtime with a compile-time type checker. This combination allows developers to use the full JavaScript ecosystem and language features, while also adding optional static type-checking, enums, classes, and interfaces on top of it.

      Though the pre-made, basic types in TypeScript will cover many use cases, creating your own custom types based on these basic types will allow you to ensure the type checker validates the data structures specific to your project. This will reduce the chance of bugs in your project, while also allowing for better documentation of the data structures used throughout the code.

      This tutorial will show you how to use custom types with TypeScript, how to compose those types together with unions and intersections, and how to use utility types to add flexibility to your custom types. It will lead you through different code samples, which you can follow in your own TypeScript environment or the TypeScript Playground, an online environment that allows you to write TypeScript directly in the browser.

      Prerequisites

      To follow this tutorial, you will need:

      • An environment in which you can execute TypeScript programs to follow along with the examples. To set this up on your local machine, you will need the following:
      • If you do not wish to create a TypeScript environment on your local machine, you can use the official TypeScript Playground to follow along.
      • You will need sufficient knowledge of JavaScript, especially ES6+ syntax, such as destructuring, rest operators, and imports/exports. If you need more information on these topics, reading our How To Code in JavaScript series is recommended.
      • This tutorial will reference aspects of text editors that support TypeScript and show in-line errors. This is not necessary to use TypeScript, but does take more advantage of TypeScript features. To gain the benefit of these, you can use a text editor like Visual Studio Code, which has full support for TypeScript out of the box. You can also try out these benefits in the TypeScript Playground.

      All examples shown in this tutorial were created using TypeScript version 4.2.2.

      Creating Custom Types

      In cases where programs have complex data structures, using TypeScript’s basic types may not completely describe the data structures you are using. In these cases, declaring your own type will help you address the complexity. In this section, you are going create types that can be used to describe any object shape you need to use in your code.

      Custom Type Syntax

      In TypeScript, the syntax for creating custom types is to use the type keyword followed by the type name and then an assignment to a {} block with the type properties. Take the following:

      type Programmer = {
        name: string;
        knownFor: string[];
      };
      

      The syntax resembles an object literal, where the key is the name of the property and the value is the type this property should have. This defines a type Programmer that must be an object with the name key that holds a string value and a knownFor key that holds an array of strings.

      As shown in the earlier example, you can use ; as the separator between each property. It is also possible to use a comma, ,, or to completely omit the separator, as shown here:

      type Programmer = {
        name: string
        knownFor: string[]
      };
      

      Using your custom type is the same as using any of the basic types. Add a double colon and then add your type name:

      type Programmer = {
        name: string;
        knownFor: string[];
      };
      
      const ada: Programmer = {
        name: 'Ada Lovelace',
        knownFor: ['Mathematics', 'Computing', 'First Programmer']
      };
      

      The ada constant will now pass the type checker without throwing an error.

      If you write this example in any editor with full support of TypeScript, like in the TypeScript Playground, the editor will suggest the fields expected by that object and their types, as shown in the following animation:

      An animation showing suggestions to add the

      If you add comments to the fields using the TSDoc format, a popular style of TypeScript comment documentation, they are also suggested in code completion. Take the following code with explanations in comments:

      type Programmer = {
        /**
         * The full name of the Programmer
         */
        name: string;
        /**
         * This Programmer is known for what?
         */
        knownFor: string[];
      };
      
      const ada: Programmer = {
        name: 'Ada Lovelace',
        knownFor: ['Mathematics', 'Computing', 'First Programmer']
      };
      

      The commented descriptions will now appear with the field suggestions:

      Code completion with TSDoc comments

      When creating an object with the custom type Programmer, if you assign a value with an unexpected type to any of the properties, TypeScript will throw an error. Take the following code block, with a highlighted line that does not adhere to the type declaration:

      type Programmer = {
        name: string;
        knownFor: string[];
      };
      
      const ada: Programmer = {
        name: true,
        knownFor: ['Mathematics', 'Computing', 'First Programmer']
      };
      

      The TypeScript Compiler (tsc) will show the error 2322:

      Output

      Type 'boolean' is not assignable to type 'string'. (2322)

      If you omitted any of the properties required by your type, like in the following:

      type Programmer = {
        name: string;
        knownFor: string[];
      };
      
      const ada: Programmer = {
        name: 'Ada Lovelace'
      };
      

      The TypeScript Compiler will give the error 2741:

      Output

      Property 'knownFor' is missing in type '{ name: string; }' but required in type 'Programmer'. (2741)

      Adding a new property not specified in the original type will also result in an error:

      type Programmer = {
        name: string;
        knownFor: string[];
      };
      
      const ada: Programmer = {
        name: "Ada Lovelace",
        knownFor: ['Mathematics', 'Computing', 'First Programmer'],
        age: 36
      };
      

      In this case, the error shown is the 2322:

      Output

      Type '{ name: string; knownFor: string[]; age: number; }' is not assignable to type 'Programmer'. Object literal may only specify known properties, and 'age' does not exist in type 'Programmer'.(2322)

      Nested Custom Types

      You can also nest custom types together. Imagine you have a Company type that has a manager field that adheres to a Person type. You could create those types like this:

      type Person = {
        name: string;
      };
      
      type Company = {
        name: string;
        manager: Person;
      };
      

      Then you could create a value of type Company like this:

      const manager: Person = {
        name: 'John Doe',
      }
      
      const company: Company = {
        name: 'ACME',
        manager,
      }
      

      This code would pass the type checker, since the manager constant fits the type designated for the manager field. Note that this uses the object property shorthand to declare manager.

      You can omit the type in the manager constant because it has the same shape as the Person type. TypeScript is not going to raise an error when you use an object with the same shape as the one expected by the type of the manager property, even if it is not set explicitly to have the Person type

      The following will not throw an error:

      const manager = {
        name: 'John Doe'
      }
      
      const company: Company = {
        name: 'ACME',
        manager
      }
      

      You can even go one step further and set the manager directly inside this company object literal:

      const company: Company = {
        name: 'ACME',
        manager: {
          name: 'John Doe'
        }
      };
      

      All these scenarios are valid.

      If writing these examples in an editor that supports TypeScript, you will find that the editor will use the available type information to document itself. For the previous example, as soon as you open the {} object literal for manager, the editor will expect a name property of type string:

      TypeScript Code Self-Documenting

      Now that you have gone through some examples of creating your own custom type with a fixed number of properties, next you’ll try adding optional properties to your types.

      Optional Properties

      With the custom type declaration in the previous sections, you cannot omit any of the properties when creating a value with that type. There are, however, some cases that require optional properties that can pass the type checker with or without the value. In this section, you will declare these optional properties.

      To add optional properties to a type, add the ? modifier to the property. Using the Programmer type from the previous sections, turn the knownFor property into an optional property by adding the following highlighted character:

      type Programmer = {
        name: string;
        knownFor?: string[];
      };
      

      Here you are adding the ? modifier after the property name. This makes TypeScript consider this property as optional and not raise an error when you omit that property:

      type Programmer = {
        name: string;
        knownFor?: string[];
      };
      
      const ada: Programmer = {
        name: 'Ada Lovelace'
      };
      

      This will pass without an error.

      Now that you know how to add optional properties to a type, it is time to learn how to create a type that can hold an unlimited number of fields.

      Indexable Types

      The previous examples showed that you cannot add properties to a value of a given type if that type does not specify those properties when it was declared. In this section, you will create indexable types, which are types that allow for any number of fields if they follow the index signature of the type.

      Imagine you had a Data type to hold an unlimited number of properties of the any type. You could declare this type like this:

      type Data = {
        [key: string]: any;
      };
      

      Here you create a normal type with the type definition block in curly brackets ({}), and then add a special property in the format of [key: typeOfKeys]: typeOfValues, where typeOfKeys is the type the keys of that object should have, and typeOfValues is the type the values of those keys should have.

      You can then use it normally like any other type:

      type Data = {
        [key: string]: any;
      };
      
      const someData: Data = {
        someBooleanKey: true,
        someStringKey: 'text goes here'
        // ...
      }
      

      Using indexable types, you can assign an unlimited number of properties, as long as they match the index signature, which is the name used to describe the types of the keys and values of an indexable type. In this case, the keys have a string type, and the values have any type.

      It is also possible to add specific properties that are always required to your indexable type, just like you could with a normal type. In the following highlighted code, you are adding the status property to your Data type:

      type Data = {
        status: boolean;
        [key: string]: any;
      };
      
      const someData: Data = {
        status: true,
        someBooleanKey: true,
        someStringKey: 'text goes here'
        // ...
      }
      

      This would mean that a Data type object must have a status key with a boolean value to pass the type checker.

      Now that you can create an object with different numbers of elements, you can move on to learning about arrays in TypeScript, which can have a custom number of elements or more.

      Creating Arrays with Number of Elements or More

      Using both the array and tuple basic types available in TypeScript, you can create custom types for arrays that should have a minimum amount of elements. In this section, you will use the TypeScript rest operator ... to do this.

      Imagine you have a function responsible for merging multiple strings. This function is going to take a single array parameter. This array must have at least two elements, each of which should be strings. You can create a type like this with the folowing:

      type MergeStringsArray = [string, string, ...string[]];
      

      The MergeStringsArray type is taking advantage of the fact that you can use the rest operator with an array type and uses the result of that as the third element of a tuple. This means that the first two strings are required, but additional string elements after that are not required.

      If an array has less than two string elements, it will be invalid, like the following:

      const invalidArray: MergeStringsArray = ['some-string']
      

      The TypeScript Compiler is going to give error 2322 when checking this array:

      Output

      Type '[string]' is not assignable to type 'MergeStringsArray'. Source has 1 element(s) but target requires 2. (2322)

      Up to this point, you have created your own custom types from a combination of basic types. In the next section, you will make a new type by composing two or more custom types together.

      Composing Types

      This section will go through two ways that you can compose types together. These will use the union operator to pass any data that adheres to one type or the other and the intersection operator to pass data that satisfies all the conditions in both types.

      Unions

      Unions are created using the | (pipe) operator, which represents a value that can have any of the types in the union. Take the following example:

      type ProductCode = number | string
      

      In this code, ProductCode can be either a string or a number. The following code will pass the type checker:

      type ProductCode = number | string;
      
      const productCodeA: ProductCode="this-works";
      
      const productCodeB: ProductCode = 1024;
      

      A union type can be created from a union of any valid TypeScript types.

      Intersections

      You can use intersection types to create a completely new type that has all the properties of all the types being intersected together.

      For example, imagine you have some common fields that always appear in the response of your API calls, then specific fields for some endpoints:

      type StatusResponse = {
        status: number;
        isValid: boolean;
      };
      
      type User = {
        name: string;
      };
      
      type GetUserResponse = {
        user: User;
      };
      

      In this case, all responses will have status and isValid properties, but only user resonses will have the additional user field. To create the resulting response of a specific API User call using an intersection type, combine both StatusResponse and GetUserResponse types:

      type ApiGetUserResponse = StatusResponse & GetUserResponse;
      

      The type ApiGetUserResponse is going to have all the properties available in StatusResponse and those available in GetUserResponse. This means that data will only pass the type checker if it satisfies all the conditions of both types. The following example will work:

      let response: ApiGetUserResponse = {
          status: 200,
          isValid: true,
          user: {
              name: 'Sammy'
          }
      }
      

      Another example would be the type of the rows returned by a database client for a query that contains joins. You would be able to use an intersection type to specify the result of such a query:

      type UserRoleRow = {
        role: string;
      }
      
      type UserRow = {
        name: string;
      };
      
      type UserWithRoleRow = UserRow & UserRoleRow;
      

      Later, if you used a fetchRowsFromDatabase() function like the following:

      const joinedRows: UserWithRoleRow = fetchRowsFromDatabase()
      

      The resulting constant joinedRows would have to have a role property and a name property that both held string values in order to pass the type checker.

      Using Template Strings Types

      Starting with TypeScript 4.1, it is possible to create types using template string types. This will allow you to create types that check specific string formats and add more customization to your TypeScript project.

      To create template string types, you use a syntax that is almost the same as what you would use when creating template string literals. But instead of values, you will use other types inside the string template.

      Imagine you wanted to create a type that passes all strings that begin with get. You would be able to do that using template string types:

      type StringThatStartsWithGet = `get${string}`;
      
      const myString: StringThatStartsWithGet="getAbc";
      

      myString will pass the type checker here because the string starts with get then is followed by an additional string.

      If you passed an invalid value to your type, like the following invalidStringValue:

      type StringThatStartsWithGet = `get${string}`;
      
      const invalidStringValue: StringThatStartsWithGet="something";
      

      The TypeScript Compiler would give you the error 2322:

      Output

      Type '"something"' is not assignable to type '`get${string}`'. (2322)

      Making types with template strings helps you to customize your type to the specific needs of your project. In the next section, you will try out type assertions, which add a type to otherwise untyped data.

      Using Type Assertions

      The any type can be used as the type of any value, which often does not provide the strong typing needed to get the full benefit out of TypeScript. But sometimes you may end up with some variables bound to any that are outside of your control. This will happen if you are using external dependencies that were not written in TypeScript or that do not have type declaration available.

      In case you want to make your code type-safe in those scenarios, you can use type assertions, which is a way to change the type of a variable to another type. Type assertions are made possible by adding as NewType after your variable. This will change the type of the variable to that specified after the as keyword.

      Take the following example:

      const valueA: any = 'something';
      
      const valueB = valueA as string;
      

      valueA has the type any, but, using the as keyword, this code coerces the valueB to have the type string.

      Note: To assert a variable of TypeA to have the type TypeB, TypeB must be a subtype of TypeA. Almost all TypeScript types, besides never, are a subtype of any, including unknown.

      Utility Types

      In the previous sections, you reviewed multiple ways to create custom types out of basic types. But sometimes you do not want to create a completely new type from scratch. There are times when it might be best to use a few properties of an existing type, or even create a new type that has the same shape as another type, but with all the properties set to be optional.

      All of this is possible using existing utility types available with TypeScript. This section will cover a few of those utility types; for a full list of all available ones, take a look at the Utility Types part of the TypeScript handbook.

      All utility types are Generic Types, which you can think of as a type that accepts other types as parameters. A Generic type can be identified by being able to pass type parameters to it using the <TypeA, TypeB, ...> syntax.

      Record<Key, Value>

      The Record utility type can be used to create an indexable type in a cleaner way than using the index signature covered previously.

      In your indexable types example, you had the following type:

      type Data = {
        [key: string]: any;
      };
      

      You can use the Record utility type instead of an indexable type like this:

      type Data = Record<string, any>;
      

      The first type parameter of the Record generic is the type of each key. In the following example, all the keys must be strings:

      type Data = Record<string, any>
      

      The second type parameter is the type of each value of those keys. The following would allow the values to be any:

      type Data = Record<string, any>
      

      Omit<Type, Fields>

      The Omit utility type is useful to create a new type based on another one, while excluding some properties you do not want in the resulting type.

      Imagine you have the following type to represent the type of a user row in a database:

      type UserRow = {
        id: number;
        name: string;
        email: string;
        addressId: string;
      };
      

      If in your code you are retrieving all the fields but the addressId one, you can use Omit to create a new type without that field:

      type UserRow = {
        id: number;
        name: string;
        email: string;
        addressId: string;
      };
      
      type UserRowWithoutAddressId = Omit<UserRow, 'addressId'>;
      

      The first argument to Omit is the type that you are basing the new type on. The second is the field that you’d like to omit.

      If you hover over UserRowWithoutAddressId in your code editor, you will find that it has all the properties of the UserRow type but the ones you omitted.

      You can pass multiple fields to the second type parameter using a union of strings. Say you also wanted to omit the id field, you could do this:

      type UserRow = {
        id: number;
        name: string;
        email: string;
        addressId: string;
      };
      
      type UserRowWithoutIds = Omit<UserRow, 'id' | 'addressId'>;
      

      Pick<Type, Fields>

      The Pick utility type is the exact opposite of the Omit type. Instead of saying the fields you want to omit, you specify the fields you want to use from another type.

      Using the same UserRow you used before:

      type UserRow = {
        id: number;
        name: string;
        email: string;
        addressId: string;
      };
      

      Imagine you need to select only the email key from the database row. You could create such a type using Pick like this:

      type UserRow = {
        id: number;
        name: string;
        email: string;
        addressId: string;
      };
      
      type UserRowWithEmailOnly = Pick<UserRow, 'email'>;
      

      The first argument to Pick here specifies the type you are basing the new type on. The second is the key that you would like to include.

      This would be equivalent to the following:

      type UserRowWithEmailOnly = {
          email: string;
      }
      

      You are also able to pick multiple fields using an union of strings:

      type UserRow = {
        id: number;
        name: string;
        email: string;
        addressId: string;
      };
      
      type UserRowWithEmailOnly = Pick<UserRow, 'name' | 'email'>;
      

      Partial<Type>

      Using the same UserRow example, imagine you want to create a new type that matches the object your database client can use to insert new data into your user table, but with one small detail: Your database has default values for all fields, so you are not required to pass any of them. To do this, you can use a Partial utility type to optionally include all fields of the base type.

      Your existing type, UserRow, has all the properties as required:

      type UserRow = {
        id: number;
        name: string;
        email: string;
        addressId: string;
      };
      

      To create a new type where all properties are optional, you can use the Partial<Type> utility type like the following:

      type UserRow = {
        id: number;
        name: string;
        email: string;
        addressId: string;
      };
      
      type UserRowInsert = Partial<UserRow>;
      

      This is exactly the same as having your UserRowInsert like this:

      type UserRow = {
        id: number;
        name: string;
        email: string;
        addressId: string;
      };
      
      type UserRowInsert = {
        id?: number | undefined;
        name?: string | undefined;
        email?: string | undefined;
        addressId?: string | undefined;
      };
      

      Utility types are a great resource to have, because they provide a faster way to build up types than creating them from the basic types in TypeScript.

      Conclusion

      Creating your own custom types to represent the data structures used in your own code can provide a flexible and useful TypeScript solution for your project. In addition to increasing the type-safety of your own code as a whole, having your own business objects typed as data structures in the code will increase the overall documentation of the code-base and improve your own developer experience when working with teammates on the same code-base.

      For more tutorials on TypeScript, check out our TypeScript Topic page.



      Source link

      How To Create a Custom Template For Your Laravel Application Using Bulma CSS



      Part of the Series:
      How To Build a Links Landing Page in PHP with Laravel and Docker Compose

      Laravel is an open-source PHP framework that provides a set of tools and resources to build modern PHP applications. In this project-based tutorial series, you’ll build a Links Landing Page application with the Laravel framework, using a containerized PHP development environment managed by Docker Compose.

      At the end, you’ll have a one-page website built with Laravel and managed via Artisan commands where you can share relevant links to an audience on social channels and presentations.

      So far, you’ve seen how to set up the application’s MySQL database tables using migrations, how to create an Eloquent model to interact with the links table, and how to create Artisan commands to manage links in the database. You’ll now see how to create a custom Blade template to show your links in the application’s front-end. To facilitate styling this page while keeping it minimal, for this series we are going to use Bulma, a single-file CSS framework.

      The default route set up within the Laravel web routes file points to an example template that you can find at resources/views/welcome.blade.php. You’ll create a new index.blade.php file within that same directory, and edit the main routes file so that the / route points to this template instead. In the route definition, you’ll also need to obtain a list of all links that you want to show in the new index template.

      Start by updating the routes file of your Laravel application. Open the routes/web.php file using your text or code editor of choice:

      Your current / route points to the example page that comes with Laravel by default:

      Route::get('/', function () {
          return view('welcome');
      });
      

      To make the proposed changes, first you’ll use the Link Eloquent model to fetch all links from the database, and sort them in decreasing order to make sure any new links you create are listed first, and thus will be shown at the top of the page.

      $links = Link::all()->sortDesc();
      

      The view helper function will look for a template file named welcome.blade.php, in the root of the resources/views directory, and return the rendered result to the browser. You’ll change this to point to a new index.blade.php template. Additionally, you’ll pass the $links variable along as template data.

          return view('index', [
              'links' => $links
          ]);
      

      The following code implements the discussed changes for the / route. Replace the contents in your routes/web.php file with:

      routes/web.php

      <?php
      
      use IlluminateSupportFacadesRoute;
      use AppModelsLink;
      
      /*
      |--------------------------------------------------------------------------
      | Web Routes
      |--------------------------------------------------------------------------
      |
      | Here is where you can register web routes for your application. These
      | routes are loaded by the RouteServiceProvider within a group which
      | contains the "web" middleware group. Now create something great!
      |
      */
      
      Route::get('/', function () {
          return view('index', [
              'links' => Link::all()->sortDesc()
          ]);
      });
      

      Save and close the file when you’re done.

      The routes file is all set, but if you try to access your main application’s page right now you will get an error message because the index.blade.php template doesn’t exist yet. You’ll create it now.

      You can base your template on the Bulma starter template, which provides a minimal HTML page structure with a title, a subtitle, and a main content area. Later on, you’ll include some CSS styling to customize the appearance of this page.

      To get started, create a new index.blade.php template using your text or code editor of choice:

      • nano resources/views/index.blade.php

      Apart from the HTML boilerplate code, which creates the page structure and the static elements that you may want to use (such as headers and other information), you’ll need to show the list of links that was passed along as template data —a collection of Link objects.

      You can use Blade’s foreach loop to loop through the links in the collection, and output them to the page:

                  @foreach ($links as $link)
                      <li>
                          <a href="https://www.digitalocean.com/community/tutorials/{{ $link->url }}" target="_blank" title="Visit Link: {{ $link->url }}">{{ $link->description }}</a>
                      </li>
                  @endforeach
      

      Include the following content in your index.blade.php file. Feel free to customize the title and other information in the page as you wish:

      resources/views/index.blade.php

      <!DOCTYPE html>
      <html>
      <head>
          <meta charset="utf-8">
          <meta name="viewport" content="width=device-width, initial-scale=1">
          <title>Sammy's Awesome Links</title>
          <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.1/css/bulma.min.css">
      </head>
      <body>
      <section class="section">
          <div class="container">
              <h1 class="title">
                  Check out my awesome links
              </h1>
              <p class="subtitle">
                  You can include a little description here.
              </p>
      
              <ul>
                  @foreach ($links as $link)
                      <li>
                          <a href="https://www.digitalocean.com/community/tutorials/{{ $link->url }}" target="_blank" title="Visit Link: {{ $link->url }}">{{ $link->description }}</a>
                      </li>
                  @endforeach
              </ul>
          </div>
      </section>
      </body>
      </html>
      

      Save the file when you’re done.

      Now go to your browser to check the results. You should be able to access your application at port 8000 of either localhost or your remote server’s IP address, in case you are using a remote server as a development platform.

      http://localhost:8000
      

      You’ll see a page like this, showing all links present in your database from latest to first:

      Landing Laravel Demo Application - Initial Version

      Your application is now fully-functional, but you can still improve the appearance of this starter page to make it more appealing to your audience.

      Styling and Customizing the Template (Optional)

      Now that the base template is ready, you can include a few optional CSS customizations to style the page using some of the features available in Bulma, in addition to custom styles.

      To give this page a new look, you can start by setting up a full page background. In this guide, we’ll use a DigitalOcean Wallpaper, but as an alternative you can also use a personal image or an image from a free stock photo website such as unsplash. You’ll need to obtain the image URL and use it to set up the background CSS property for the html element. A few other properties can be adjusted to make sure the image is centralized.

      html {
                  background: url("https://i.imgur.com/BWIdYTM.jpeg") no-repeat center center fixed;
                  -webkit-background-size: cover;
                  -moz-background-size: cover;
                  -o-background-size: cover;
                  background-size: cover;
              }
      

      To style the list of links, you might want to replace the <li> elements for each link with box components, and include the link URL as a paragraph under the link description.

                  @foreach ($links as $link)
                      <div class="box link">
                          <h3><a href="https://www.digitalocean.com/community/tutorials/{{ $link->url }}" target="_blank" title="Visit Link: {{ $link->url }}">{{ $link->description }}</a></h3>
                          <p>{{$link->url}}</p>
                      </div>
                  @endforeach
      

      Finally, you can create a couple additional CSS styles to customize the appearance of the link text.

              div.link h3 {
                  font-size: large;
              }
      
              div.link p {
                  font-size: small;
                  color: #718096;
              }
      

      The following Blade template contains all suggested implementations. Replace your current index.blade.php file contents with:

      resources/views/index.blade.php

      <!DOCTYPE html>
      <html>
      <head>
          <meta charset="utf-8">
          <meta name="viewport" content="width=device-width, initial-scale=1">
          <title>Sammy's Awesome Links</title>
          <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.1/css/bulma.min.css">
      
          <style>
              html {
                  background: url("https://i.imgur.com/BWIdYTM.jpeg") no-repeat center center fixed;
                  -webkit-background-size: cover;
                  -moz-background-size: cover;
                  -o-background-size: cover;
                  background-size: cover;
              }
      
              div.link h3 {
                  font-size: large;
              }
      
              div.link p {
                  font-size: small;
                  color: #718096;
              }
          </style>
      </head>
      <body>
      <section class="section">
          <div class="container">
              <h1 class="title">
                  Check out my awesome links
              </h1>
              <p class="subtitle">
                  You can include a little description here.
              </p>
      
              <section class="links">
                  @foreach ($links as $link)
                      <div class="box link">
                          <h3><a href="https://www.digitalocean.com/community/tutorials/{{ $link->url }}" target="_blank" title="Visit Link: {{ $link->url }}">{{ $link->description }}</a></h3>
                          <p>{{$link->url}}</p>
                      </div>
                  @endforeach
              </section>
          </div>
      </section>
      </body>
      </html>
      

      Save the file when you’re done.

      Now reload your browser and you’ll see the updated page:

      Landing Laravel Demo Application - Final Version



      Source link