Categories
Angular JavaScript

How to use Directives in Angular

Angular is a popular front-end framework made by Google. Like other popular front-end frameworks, it uses a component-based architecture to structure apps.

In this article, we’ll look at how to use some built-in Angular directives.

Built-in Directives

Angular has some built-in directives. Built-in directives include attribute directives and structural directives.

Built-in Attribute Directives

Attributes directives change the behavior of HTML elements, attributes, properties and components.

The most common attributes are:

  • NgClass — adds or removes a CSS class
  • NgStyle — adds or removes a set of HTML styles
  • NgModel — adds 2-way data binding to an HTML form element.

NgClass

We can add or remove some CSS classes simultaneously with the ngClass directive.

For example, we can write:

app.component.ts :

import { Component } from "@angular/core";

@Component({  
  selector: "app-root",  
  templateUrl: "./app.component.html",  
  styleUrls: ["./app.component.css"]  
})  
export class AppComponent {  
  canSave = true;  
  isUnchanged = false;  
  isSpecial = true;  
  classes = {  
    saveable: this.canSave,  
    modified: !this.isUnchanged,  
    special: this.isSpecial  
  };  
}

app.component.html :

<div [ngClass]="classes">foo</div>

In the code above, we created the classes field in AppComponent so that we can apply the saveable , modified and special classes to the div in app.component.html .

The 3 classes are applied when the value of the keys in classes are truthy.

Therefore, when we write:

[ngClass]="classes"

in app.component.html , we’ll see the classes applied to the div.

If we only want to toggle one class, we can also write:

app.component.ts :

import { Component } from "@angular/core";

@Component({  
  selector: "app-root",  
  templateUrl: "./app.component.html",  
  styleUrls: ["./app.component.css"]  
})  
export class AppComponent {  
  isGreen = true;  
}

app.component.html :

<div [ngClass]="isGreen ? 'green' : null">foo</div>

Then we get the green class applied to the div in app.component.html since isGreen is set to true in AppComponent .

NgStyle

We can use ngStyle to add multiple styles to an element. This is useful for setting dynamic styles on an element.

To use it, we can write:

app.component.ts :

import { Component } from "@angular/core";

@Component({  
  selector: "app-root",  
  templateUrl: "./app.component.html",  
  styleUrls: ["./app.component.css"]  
})  
export class AppComponent {  
  isBig = true;  
}

app.component.html :

<div [style.font-size]="isBig ? '25px' : '12px'">foo</div>

In the code above, we have the isBig field in AppComponent set to true .

Also, we have style.font-size set to an expression that checks if isBig is truthy or not and then set the size accordingly.

Therefore, the font size of the div would be 25px.

We can also use it to set multiple styles. To do this, we can write:

app.component.ts :

import { Component } from "@angular/core";

@Component({  
  selector: "app-root",  
  templateUrl: "./app.component.html",  
  styleUrls: ["./app.component.css"]  
})  
export class AppComponent {  
  canSave = true;  
  isUnchanged = false;  
  isSpecial = true;  
  styles = {  
    "font-style": this.canSave ? "italic" : "normal",  
    "font-weight": !this.isUnchanged ? "bold" : "normal",  
    "font-size": this.isSpecial ? "24px" : "12px"  
  };  
}

app.component.html :

<div [ngStyle]="styles">foo</div>

In the code above, we have the styles field in AppComponent , with various CSS attribute names as the key. They’ll be set according to what we have in the expression we have as the values.

Then in app.component.html , we set styles of the div to what we have in the styles object with the ngStyle directive.

Therefore, we should see italic, bold and 24px large text.

[(ngModel)]: Two-way binding

We can use ngModel to do 2-way binding between template and component.

It’s useful for getting input data and then displaying it.

For example, we can use it as follows:

app.module.ts :

import { BrowserModule } from "@angular/platform-browser";  
import { NgModule } from "@angular/core";  
import { FormsModule } from "@angular/forms";
import { AppComponent } from "./app.component";

@NgModule({  
  declarations: [AppComponent],  
  imports: [BrowserModule, FormsModule],  
  providers: [],  
  bootstrap: [AppComponent]  
})  
export class AppModule {}

app.component.ts :

import { Component } from "@angular/core";

@Component({  
  selector: "app-root",  
  templateUrl: "./app.component.html",  
  styleUrls: ["./app.component.css"]  
})  
export class AppComponent {  
  name: string;  
}

app.component.html :

<input [(ngModel)]="name" />  
<p>{{name}}</p>

In the code above, we added the FormsModule to the imports array in app.module.ts .

Then we added the name field in AppComponent .

Finally, we added an input element with the ngModel directive and a p element with the name variable interpolated.

Then when we type something into the input, it’ll be shown in the p element.

Conclusion

We can use the ngClass directive to set classes on an element.

To set dynamic styles on an element, we can use the ngStyle directive.

To add 2-way data binding, we use the ngModel directive, which is available in the FormsModule .

Categories
JavaScript React

Type Checking With React PropTypes

React is a library for creating front end views. It has a big ecosystem of libraries that work with it. Also, we can use it to enhance existing apps.

In this article, we’ll look at how to valid prop data types by using PropTypes .

Type Checking With PropType

As our code grows, we can catch a lot of bugs with type checking.

React has built-in type checking abilities for props.

We can add prop type checks as follows:

import React from "react";  
import ReactDOM from "react-dom";  
import PropTypes from "prop-types";

class Greeting extends React.Component {  
  render() {  
    return <p>Hello, {this.props.name}</p>;  
  }  
}

Greeting.propTypes = {  
  name: PropTypes.string  
};

class App extends React.Component {  
  render() {  
    return <Greeting name="Jane" />;  
  }  
}

In the code above, we have:

Greeting.propTypes = {  
  name: PropTypes.string  
};

to check that the name prop passed in is a string.

In addition to strings, it has many other kinds of validations that can be used to make sure that data received is valid.

When an invalid value is provided for a prop, a warning will be shown in the JavaScript console.

Prop type checks are only run in development mode for performance reasons.

PropTypes

In addition to strings, there’re many more prop types available. They’re the following:

  • PropTypes.array — checks for arrays
  • PropTypes.bool — checks for boolean
  • PropTypes.func — checks for functions
  • PropTypes.number — checks for number
  • PropTypes.object — checks for object
  • PropTypes.string — checks for string
  • PropTypes.symbol — checks for symbol
  • PropTypes.node — checks for anything that can be rendered
  • PropTypes.element — checks for a React element
  • PropTypes.elementType — checks for a React element type (e.g. Component )
  • PropTypes.instanceOf(Foo) — checks for instance of a class, in this case, Foo
  • PropTypes.oneOf([‘foo’, ‘bar’]) — checks for one of the values in the array
  • PropTypes.oneOfType([PropTypes.string, PropTypes.number]) — checks for one of the types in the array
  • PropTypes.arrayOf(PropTypes.number) — checks for an array of the given type
  • PropTypes.shape({color: PropTypes.string, fontSize: PropTypes.number } — checks for an object with these properties and types of values
  • PropTypes.exact({ name: PropTypes.string, quantity: PropTypes.number }) — checks for an object with these properties and types of values with warnings on extra properties
  • isRequired — set a prop as being required, e.g. we can use is by writing PropTypes.func.isRequired to make a prop that takes a function required.

We can also set our own prop validation function as follows:

class Greeting extends React.Component {  
  render() {  
    return <p>Hello, {this.props.name}</p>;  
  }  
}

Greeting.propTypes = {  
  name(props, propName, componentName) {  
    if (!["Jane", "Joe"].includes(props[propName])) {  
      throw new Error("Invalid name");  
    }  
  }  
};

class App extends React.Component {  
  render() {  
    return <Greeting name="Foo" />;  
  }  
}

In the code above, we have:

Greeting.propTypes = {  
  name(props, propName, componentName) {  
    if (!["Jane", "Joe"].includes(props[propName])) {  
      throw new Error("Invalid name");  
    }  
  }  
};

which has the name function where we can check the name prop to see if it’s set to 'Jane' or 'Joe' .

If it’s not, like the example above, we’ll get the warning:

Warning: Failed prop type: Invalid name

in the JavaScript console.

Requiring Single Child

We can specify that only a single child can be passed to a component as children.

To do this, we write:

class Greeting extends React.Component {  
  render() {  
    return <p>Hello, {this.props.children}</p>;  
  }  
}

Greeting.propTypes = {  
  children: PropTypes.element.isRequired  
};

class App extends React.Component {  
  render() {  
    return <Greeting>Jane</Greeting>;  
  }  
}

In the code above, we specified that children has the type PropTypes.element.isRequired , which means that we check if only one element is passed in.

Default Prop Values

We can also set default values for props.

For instance, we can write:

class Greeting extends React.Component {  
  render() {  
    return <p>Hello, {this.props.name}</p>;  
  }  
}

Greeting.defaultProps = {  
  name: "Jane"  
};

class App extends React.Component {  
  render() {  
    return <Greeting />;  
  }  
}

In the code above, we have the defaultProps property set for Greeting to set the default props.

Since we didn’t pass any value for the name prop to Greeting , React will set the name prop to 'Jane' by default, so we get Hello, Jane.

If we change 'Jane' to 'Joe' , then we get Hello, Joe instead.

Conclusion

We can use prop types to check the types for our props. This prevents us from passing in data that we don’t want, preventing potential bugs.

Prop types can check for many types, including any JavaScript type, like numbers, objects, and symbols.

It can also check the structure of objects and arrays, and also checks if specific values are passed in.

In addition, we can make our own validator function to check what they’re and throw an error to reject invalid values.

We can add prop type check for children to validate what’s passed in between tags.

Also, we can set a default value for props, by setting a value for it in the defaultProps object.

Finally, we can use isRequired to check for if a prop value is passed in.

Categories
JavaScript React

Sharing State with the React Context API

React is a library for creating front end views. It has a big ecosystem of libraries that work with it. Also, we can use it to enhance existing apps.

React components can only pass data from parent to child via props. The Context API adds to that by allowing components with other relationships to share data.

In this article, we’ll look at how to use it to share data between components.

When to Use Context

We should use Context to share data between React components. However, it should be used sparingly since it creates tight coupling between components.

To use it within a simple app, we can write the following:

const ColorContext = React.createContext("green");

class Button extends React.Component {  
  render() {  
    return (  
      <div>  
        <ColorContext.Consumer>  
          {value => (  
            <button style={{ color: value }}>{this.props.children}</button>  
          )}  
        </ColorContext.Consumer>  
      </div>  
    );  
  }  
}

class App extends React.Component {  
  render() {  
    return (  
      <div>  
        <ColorContext.Provider value="blue">  
          <Button>Click Me</Button>  
        </ColorContext.Provider>  
      </div>  
    );  
  }  
}

In the code above, we created a Context to share data by writing:

const ColorContext = React.createContext("green");

createContext takes a default value as an argument, where we passed in 'green' .

Then in the App component, we have the ColorContext.Provider component with the value prop set to the value that we want to share.

In the example above, it’ll be 'blue' . We wrapped it around the components that we want to share the data with so that we can access the value from that component.

In this case, we created a new Button component, which has the ColorContext.Consumer component. Inside it, we can get the value shared from the context provider from the value parameter in the function we inserted inside the ColorContext.Consumer component.

value should be set to 'blue' since that’s what we set as the value of the value prop.

Inside the function we passed in the consumer, we returned a buttom element with the style prop and we set the color style to value , which is 'blue' .

Alternatives to Context

If we want to pass data into a deeply nested component, we can instead pass in the whole component down to where we want it. This way, we don’t have to worry about passing props to multiple levels to pass something that’s only needed by deeply nested components.

For example, if we want to pass the color prop to Button components, which is contained in a ButtonBar . We can do that as follows:

class Button extends React.Component {  
  render() {  
    return (  
      <button style={{ color: this.props.color }}>{this.props.children}</button>  
    );  
  }  
}

class ButtonBar extends React.Component {  
  render() {  
    return this.props.buttons;  
  }  
}

class App extends React.Component {  
  render() {  
    const buttons = [  
      <Button color="blue">Click Me</Button>,  
      <Button color="green">Click Me 2</Button>  
    ];  
    return <ButtonBar buttons={buttons} />;  
  }  
}

In the App component, we have the Button components in the buttons array. Then we passed the whole array straight down to the ButtonBar component.

Then ButtonBar just returns what we passed in, which is this.props.buttons .

This also means more complexity in the higher-order components, so it may not be suitable in all cases.

Updating Context from a Nested Component

We can pass in functions to the object that we pass into createContext so that we can call them inside the component that has the context consumer component.

For example, we can write the following:

const colorObj = {  
  color: "green",  
  toggleColor: () => {}  
};

const ColorContext = React.createContext(colorObj);  
class Button extends React.Component {  
  render() {  
    return (  
      <div>  
        <ColorContext.Consumer>  
          {({ color, toggleColor }) => (  
            <button onClick={toggleColor} style={{ color }}>  
              {this.props.children}  
            </button>  
          )}  
        </ColorContext.Consumer>  
      </div>  
    );  
  }  
}

class App extends React.Component {  
  constructor(props) {  
    super(props);  
    this.state = {  
      color: "blue",  
      toggleColor: () => {  
        this.setState(state => ({  
          color: state.color === "green" ? "blue" : "green"  
        }));  
      }  
    };  
  } 

  render() {  
    return (  
      <div>  
        <ColorContext.Provider value={this.state}>  
          <Button>Click Me</Button>  
        </ColorContext.Provider>  
      </div>  
    );  
  }  
}

The code above starts with defining the colorObj object, which is passed into createContext as the default value of ColorContext .

Then in the App component, we initialize this.state by setting it to an object with the toggleColor function, and the color property set to 'blue' .

We pass this.state as the value of the value prop of ColorContext.Provider .

Then we access the whole object inside the ColorContext.Consumer component in the Button component.

Inside there, we get the color and toggleColor property from the this.state which we passed in from the ColorContext.Provider . Then we pass toggleColor into the onClick prop, and color into the object that we passed into the style prop.

Then when we click the Click Me button, the text color will toggle between blue and green.

Consuming Multiple Contexts

We can consume multiple contexts by nesting them. For example, we can do that as follows:

const ColorContext = React.createContext("green");  
const BorderContext = React.createContext("");
class Button extends React.Component {  
  render() {  
    return (  
      <div>  
        <ColorContext.Consumer>  
          {color => (  
            <BorderContext.Consumer>  
              {border => (  
                <button style={{ color, border }}>{this.props.children}</button>  
              )}  
            </BorderContext.Consumer>  
          )}  
        </ColorContext.Consumer>  
      </div>  
    );  
  }  
}

class App extends React.Component {  
  render() {  
    return (  
      <div>  
        <ColorContext.Provider value="blue">  
          <BorderContext.Provider value="3px solid green">  
            <Button>Click Me</Button>  
          </BorderContext.Provider>  
        </ColorContext.Provider>  
      </div>  
    );  
  }  
}

In the code above, we create 2 Contexts, ColorContext and BorderContext and passed in values to the value prop to both. We nested the providers in the App component, which means that both contexts can be consumed by the Button component inside.

Then in the Button component, we have consumers for both contexts nested in each other. And then we can get both values that were passed in from the providers.

We then use both values to set the styles of the button .

In the end, we a button with blue text and a thick green border.

Conclusion

We can use the React Context API to share data between components.

It works by creating a Context object with React.createContext . Then we wrap the context provider component outside the components that we want to consume the context from.

Then in the component that we put inside the provider, we have the context consumer component wrapped outside of whatever we want to apply the context value to.

Finally, we can get the value inside the function that we pass inside the Context consumer.

Categories
JavaScript Vue

List Rendering with Vue.js — Object Change Detection and Arrays

Vue.js is an easy to use web app framework that we can use to develop interactive front end apps.

In this article, we’ll look at Vue’s object change detection capabilities and displaying sorted and filtered results.

Object Change Detection

Vue can’t detect property additions or deletions because of the limitations of modern JavaScript.

For example, if we have the following code:

src/index.js :

new Vue({  
  el: "#app",  
  data: {  
    foo: {  
      a: 1  
    }  
  },  
  methods: {  
    addProperty() {  
      this.foo.b = 2;  
    }  
  }  
});

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
  </head> <body>  
    <div id="app">  
      <button @click="addProperty">Add Property</button>  
      <div v-for="value in foo">  
        {{value}}  
      </div>  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

Then nothing happens when we click Add Property.

We can fix this by calling Vue.set as follows:

src/index.js :

new Vue({  
  el: "#app",  
  data: {  
    foo: {  
      a: 1  
    }  
  },  
  methods: {  
    addProperty() {  
      Vue.set(this.foo, "b", 2);  
    }  
  }  
});

and keep index.html the same, we’ll get ‘2’ when we click Add Property.

In the code above, we passed in the object to update as the first argument, the property name as the second argument, and the value of the property as the third argument.

We can also use the this.$set / vm.$set method as follows, where this and vm are a Vue instance.

To use it, we can write the following:

src/index.js :

new Vue({  
  el: "#app",  
  data: {  
    foo: {  
      a: 1  
    }  
  },  
  methods: {  
    addProperty() {  
      this.$set(this.foo, "b", 2);  
    }  
  }  
});

Then we get the same result as before.

To assign multiple new properties to an existing object, we can use Object.assign or Lodash’s _.extend method by assigning the returned object to the existing variable as follows:

src/index.js :

new Vue({  
  el: "#app",  
  data: {  
    foo: {  
      a: 1  
    }  
  },  
  methods: {  
    addProperties() {  
      this.foo = Object.assign({}, this.foo, {  
        b: 2,  
        c: 3  
      });  
    }  
  }  
});

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
  </head> <body>  
    <div id="app">  
      <button @click="addProperties">Add Properties</button>  
      <div v-for="value in foo">  
        {{value}}  
      </div>  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

In the code above, we used the Object.assign method with the empty object as the first argument, the original object as the second argument, and an object with the new properties and values as the third argument.

Then when the page first loads, we have only:

1

listed.

Then when we click Add Properties, we get:

123

This is because clicking Add Properties triggered a call to addProperties , which then calls Object.assign to return a merged object with the new properties, which is then assigned back to this.foo .

That will trigger a view update with the new items.

Displaying Filtered/Sorted Results

We should use computed properties to render filtered or sorted results.

This is a much more efficient way to rendered filtered results than checking each array entry during v-for with v-if since the whole array doesn’t have to rendered and then checked with v-if .

For example, we can render a list of odd numbers from a list of all kinds of numbers as follows:

src/index.js :

new Vue({  
  el: "#app",  
  data: {  
    numbers: [1, 2, 3, 4, 5]  
  },  
  computed: {  
    oddNumbers() {  
      return this.numbers.filter(n => n % 2 === 1);  
    }  
  }  
});

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
  </head> <body>  
    <div id="app">  
      <div v-for="num in oddNumbers">  
        {{num}}  
      </div>  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

Then we get:

135

displayed.

We can use methods in situations where computed properties aren’t feasible like inside nestedv-for loops.

For example, we can write a method as follows to render a list of odd numbers:

src/index.js :

new Vue({  
  el: "#app",  
  data: {  
    numbers: [1, 2, 3, 4, 5]  
  },  
  methods: {  
    oddNumbers(numbers) {  
      return numbers.filter(n => n % 2 === 1);  
    }  
  }  
});

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
  </head> <body>  
    <div id="app">  
      <div v-for="num in oddNumbers(numbers)">  
        {{num}}  
      </div>  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

In the code above, we called the oddNumbers method with the numbers array passed in to return an array of odd numbers.

Then we can iterate through it by using the v-for loop as usual.

We can display sorted numbers as follows:

src/index.js :

new Vue({  
  el: "#app",  
  data: {  
    numbers: [1, 2, 3, 4, 5]  
  },  
  computed: {  
    reversedNumbers() {  
      return this.numbers.sort((a, b) => b - a);  
    }  
  }  
});

index.js :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
  </head> <body>  
    <div id="app">  
      <div v-for="num in reversedNumbers">  
        {{num}}  
      </div>  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

We sorted it with the computed property reversedNumbers to sort the numbers in reverse and then the numbers will be displayed in reverse:

54321

Conclusion

Vue can’t detect object property changes and trigger and view update as properties are added or removed.

Therefore, we have to use Vue.set or this.$set to update it. We can pass in the object to update as the first argument, the property name as the second argument, and the value of the property as the third argument.

To add multiple object properties at once, we can use the Object.assign method with the empty object as the first argument, the original object as the second argument, and an object with the new properties and values as the third argument.

To display sorted and filtered array results, we can use computed properties. It’s the most efficient way since only the filtered results are rendered.

If that can’t be done, then we can also use a method to return the filtered or sorted array instead.

Categories
JavaScript Vue

Vue.js Transition Effects — State Transitions

Vue.js is an easy to use web app framework that we can use to develop interactive front end apps.

In this article, we’ll look at how to create state transitions with GreenSock, tween.js, and Color.js

Animating State with Watchers

Watchers let us animate changes of any numerical property into another property.

For example, we can animate changes for a number that we input as follows with GreenSock and Vue:

src/index.js :

new Vue({  
  el: "#app",  
  data: {  
    num: 0,  
    tweenedNumber: 0  
  },  
  computed: {  
    animatedNumber() {  
      return this.tweenedNumber.toFixed(0);  
    }  
  },  
  watch: {  
    num(newValue) {  
      TweenLite.to(this.$data, 0.5, { tweenedNumber: newValue });  
    }  
  }  
});

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
    <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/1.20.3/TweenMax.min.js"></script>  
  </head>  
  <body>  
    <div id="app">  
      <input v-model.number="num" type="number" />  
      <p>{{ animatedNumber }}</p>  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

The code will above will animate the number changes as we enter a number into the input.

We watch the num value and then called:

TweenLite.to(this.$data, 0.5, { tweenedNumber: newValue });

to update the number display by incrementing or decrementing the number until it reaches the number we typed in.

TweenLite is from GreenSock.

We can also do the same thing for other things like a color string.

For example, we can write the following to animate the update of a color string:

src/index.js :

const Color = net.brehaut.Color;new Vue({  
  el: "#app",  
  data: {  
    colorQuery: "",  
    color: {  
      red: 0,  
      green: 0,  
      blue: 0,  
      alpha: 1  
    },  
    tweenedColor: {}  
  },  
  created() {  
    this.tweenedColor = Object.assign({}, this.color);  
  },  
  watch: {  
    color() {  
      const animate = () => {  
        if (TWEEN.update()) {  
          requestAnimationFrame(animate);  
        }  
      };
      new TWEEN.Tween(this.tweenedColor).to(this.color, 750).start();
      animate();  
    }  
  },  
  computed: {  
    tweenedCSSColor() {  
      return new Color({  
        red: this.tweenedColor.red,  
        green: this.tweenedColor.green,  
        blue: this.tweenedColor.blue,  
        alpha: this.tweenedColor.alpha  
      }).toCSS();  
    }  
  },  
  methods: {  
    updateColor() {  
      this.color = new Color(this.colorQuery).toRGB();  
      this.colorQuery = "";  
    }  
  }  
});

src/styles.css :

.color-preview {  
  width: 50px;  
  height: 50px;  
}

index.js :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
    <script src="https://cdn.jsdelivr.net/npm/tween.js@16.3.4"></script>  
    <script src="https://cdn.jsdelivr.net/npm/color-js@1.0.3"></script>  
    <link href="./src/styles.css" type="text/css" />  
  </head>  
  <body>  
    <div id="app">  
      <input  
        v-model="colorQuery"  
        @keyup.enter="updateColor"  
        placeholder="Enter Color"  
      />  
      <button @click="updateColor">Update</button>  
      <div  
        class="color-preview"  
        :style="{ backgroundColor: tweenedCSSColor }"  
      ></div>  
      <p>{{ tweenedCSSColor }}</p>  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

The code works above by getting the colorQuery string that was inputted, converting it to a Color object and assign it to this.color .

Once the this.color updates, The animate function in the color watcher in the watch property will be called.

Then this.tweenedColor will be updated with:

new TWEEN.Tween(this.tweenedColor).to(this.color, 750).start();

Then once this.tweenedColor is updated, then tweenedCSSColor is updated and displayed on the screen.

TWEEN.Tween is a constructor from tween.js.

Organizing Transitions into Components

We can put our transition code into its component. This way, we can reuse it and separate out the complexity of the transitions from other business logic.

For example, we can refactor our original example into the following code:

src/index.js :

Vue.component("num-transition", {  
  props: ["num"],  
  data() {  
    return {  
      tweenedNumber: 0  
    };  
  },  
  computed: {  
    animatedNumber() {  
      return this.tweenedNumber.toFixed(0);  
    }  
  },  
  watch: {  
    num(newValue) {  
      TweenLite.to(this.$data, 0.5, { tweenedNumber: newValue });  
    }  
  },  
  template: `<p>{{animatedNumber}}</p>`  
});new Vue({  
  el: "#app",  
  data: {  
    num: 0  
  }  
});

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
    <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/1.20.3/TweenMax.min.js"></script>  
  </head>  
  <body>  
    <div id="app">  
      <input v-model.number="num" type="number" />  
      <num-transition :num="num"></num-transition>  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

In the code above, we moved the transition logic into its own component by moving out all the transition logic and then passing the number that we want to watch for creating the transition as a prop.

Then we can change it slightly so that we can reuse it as follows:

src/index.js :

Vue.component("num-transition", {  
  props: ["num"],  
  data() {  
    return {  
      tweenedNumber: 0  
    };  
  },  
  computed: {  
    animatedNumber() {  
      return this.tweenedNumber.toFixed(0);  
    }  
  },  
  watch: {  
    num(newValue) {  
      TweenLite.to(this.$data, 0.5, { tweenedNumber: newValue });  
    }  
  },  
  template: `<span>{{animatedNumber}}</span>`  
});new Vue({  
  el: "#app",  
  data: {  
    num1: 0,  
    num2: 0  
  },  
  computed: {  
    result() {  
      return this.num1 + this.num2;  
    }  
  }  
});

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src=https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
    <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/1.20.3/TweenMax.min.js"></script>  
  </head>  
  <body>  
    <div id="app">  
      <input v-model.number="num1" type="number" />  
      <input v-model.number="num2" type="number" />  
      <p>  
        <num-transition :num="num1"></num-transition> +  
        <num-transition :num="num2"></num-transition> =  
        <num-transition :num="result"></num-transition>  
      </p>  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

In the code above, we changed the template of num-transition to:

<span>{{animatedNumber}}</span>

and then in index.html we changed the code to:

<div id="app">  
  <input v-model.number="num1" type="number" />  
  <input v-model.number="num2" type="number" />  
  <p>  
    <num-transition :num="num1"></num-transition> +  
    <num-transition :num="num2"></num-transition> =  
    <num-transition :num="result"></num-transition>  
  </p>  
</div>

Then we get:

https://thewebdev.info/wp-content/uploads/2020/04/calc.png

Conclusion

We can create transitions for component states by watching them and creating computed values for them.

Then we can use the GreenSock library to generate new values from the watched value.

Then that value can be displayed on the screen, creating an animation effect.

We can refactor the logic into its own component so that it won’t make a component too complex by mixing transition logic and other business logic.