Categories
Flow JavaScript

JavaScript Type Checking with Flow — Variables and Functions

Flow is a type checker made by Facebook for checking JavaScript data types. It has many built-in data types we can use to annotate the types of variables and function parameters.

In this article, we’ll look at data types supported by Flow for type checking, including variables and basic properties of functions.

Variable Types

Just like in JavaScript, we can declare variables and constants with Flow with the let, var, and const keywords respectively.

let and var are used to declare variables. This means that their values can be reassigned after the initial assignment.

const

The const keyword is for declaring constants. Once it’s assigned a value, it can have another value reassigned to it.

Flow can infer the data type constants without adding type annotation:

const a = 1;

It’ll know that a is a number.

Of course, we can annotate it with a type as follows:

const a: number = 1;

var and let

var and let are both for assigning to variables. The difference is that var isn’t block scoped while let is.

For example, we can write:

if (true){  
  var x = 1;  
}  
console.log(x);

However, if we replace var with let , we’ll get an error:

if (true){  
  let x = 1;  
}  
console.log(x);

We can reassign variables declared with var or let with another value. For example, we can write:

var x = 1;  
x = 2;let y = 1;  
y = 2;

Of course, if a data type annotation is added to a variable, then we must have a value with a compatible type assigned to it. For example, if we have a number variable:

let y: number = 1;

Then writing:

y = '2';

after it will create an error.

If a data type annotation isn’t added to a variable or constant, Flow will assume that the type of the variable or constant is the union of all the types of the data that had been assigned to it.

For example, if we have:

let a = 1;  
a = 'foo';  
a = true

Then we can write:

let b: number|string|boolean = a;

without getting any errors. This is because we assigned a a number, string and boolean before we assigned a to b , so a is assumed to have a union of all those types.

Flow can also figure out the type given that it’s been assigned. For example, we can write:

let foo = 1;  
let num: number = foo;foo = false;  
let boo: boolean = foo;foo = "foo";  
let str: string = foo;

However, code that’s run within blocks like functions and conditions will throw off Flow’s type inference and will throw an error when we try to assign it to something that we expect to work.

For example, if we write:

let foo = 1;if(true) {  
  foo = true;  
  foo = "foo";  
}

let str: string = foo;

We’ll get the error:

[Flow] Cannot assign `foo` to `str` because number [1] is incompatible with string [2].

since Flow doesn’t know the type of the foo variable.

Likewise, Flow can’t figure out what type foo has after it’s been reassigned values inside a function as follows:

let foo = 1;const fn = ()=> {  
  foo = true;  
  foo = "foo";  
}

fn();
let str: string = foo;

Function Types

A function can have type annotations for its parameters and the return type of the function.

For example, if we have the function:

function add(a: number, b: number): number {  
  return a + b;  
}

Then the number in a: number and b: number are the parameters’ data types, and the number after the close parentheses is the return type of the function add .

Once we annotate the parameters with types, Flow will validate the types when anything is passed in.

For example, if we have:

add(1, 2);

Then Flow will accept the code as valid since we passed in numbers as indicated in the function definition.

On the other hand, if we pass in data of any other type like string as follows:

add('a', 'b');

Then we get the error:

[Flow] Cannot call `add` with `'b'` bound to `b` because string [1] is incompatible with number [2].

Since we passed in strings for the arguments, which aren’t compatible with numbers.

Function Syntax

Flow’s function syntax is similar to JavaScript but added extra type annotations to the parameters and return type.

We can have an infinite number of parameters with the rest operator, which is indicated with the ... operator and holds the arguments that are excess of the listed parameters as an array.

For example, we can write:

function foo(str: string, bool?: boolean, ...nums: Array<number>): void {  
  console.log(nums);  
}

foo('a', false, 1, 2, 3);

Then we get [1,2,3] for nums .

However, if we pass in something invalid as follows:

foo('a', false, true, 2, 3);

Then we get an error for passing in true into the third argument since we specified that ...nums is a number array.

We can declare variables and constants like JavaScript with Flow. In addition to JavaScript’s syntax, we can add data type annotations to variables and constants.

Function syntax and definition also extends from JavaScript’s syntax. Like variables, we can add data type annotations to variables, including ones operated on by the rest operator, and also add a return type annotation to a function.

Categories
JavaScript

JavaScript Events Handlers — onvisibilitychange event, onanimationcancel and More

In JavaScript, events are actions that happen in an app. They’re triggered by various things like inputs being entered, forms being submitted, and changes in an element like resizing, or errors that happen when an app is running, etc. We can assign event handler to handle these events. Events that happen to DOM elements can be handled by assigning an event handler to properties of the DOM object for the corresponding events. In this article, we looked at the standard onvisibilitychange event, and non-standard onanimationcancel, onanimationend and onanimationiteration events.

onvisibilitychange

The onvisibilitychange is an event handler that’s run when the visibilitychange event is triggered and reaches the object. We define out own event handler function and then set it to this property. For example, we can write the following code to log the visibility of the current page:

document.onvisibilitychange = (event) => {  
  console.log("Visibility changed!");  
  console.log(event);  
};

The Event object has the details for the event. We should be able to see that the type property of the event object is 'visibilitychange’ .

onanimationcancel

The onanimationcancel event let us attach an event handler for situations when the animationcancel event, which is when then CSS animation unexpectedly aborts, like when we stop running the animation without the triggering the animationend event, which may happen when the animation name is changed, the animation is removed, or when it’s hidden directly or via change in CSS. onanimationcancel is only triggered with Firefox. For example, if we have the following HTML code to create a box and a button to hide the box:

<button id='hide-button'>  
  Hide Box  
</button><div id='container'>  
  <div id='box'> </div>  
</div>

Then we add the following CSS to style the box and change the size to 100 by 100 pixels and create the animation:

#box {  
  width: 100px;  
  height: 100px;  
  left: 0;  
  top: 0;  
  border: 1px solid red;  
  margin: 0;  
  position: relative;  
  background-color: red;  
  display: block;  
  justify-content: center;  
  animation: 5s slideBox;  
}

#container {  
  height: 500px;  
}
@keyframes slideBox {  
  from {  
    left: 0;  
    top: 0;  
  } to {  
    left: calc(100% - 100px);  
    top: calc(100% - 100px)  
  }  
}

In the CSS above, initially we have the box placed on the top left corner of the page and styled it with a red filling. We have the slideBox animation sequence to love the box from the top left corner of the page to the bottom right corner by sliding it 100 pixels right and 100 pixels down in each frame.

Then we add the following JavaScript code:

const hideButton = document.getElementById('hide-button');  
const box = document.getElementById('box');
document.onanimationcancel = () => {  
  console.log('Animation cancelled unexpectedly');  
};

hideButton.onclick = () => {  
  box.style.display = 'none';  
}

Then when we click the ‘Hide Box’ button, then we get ‘ Animation cancelled unexpectedly’ logged in Firefox. The animationcancel event doesn’t get triggered when the ‘Hide Box’ button is clicked, so it doesn’t seem to have a consistent implementation across browsers.

onanimationend

The onanimationend property is a property where we can set the value of it to an event handler function which is triggered when the animationend event is fired. It’s triggered when the CSS animation reaches the end of its active period which the the total of the animation duration multiplied by the animation iteration count and the animation delay. For example, if we have the following HTML code:

<div id='container'>  
  <div id='box'> </div>  
</div>

Then we add the following CSS to style the box and change the size to 100 by 100 pixels and create the animation:

#box {  
  width: 100px;  
  height: 100px;  
  left: 0;  
  top: 0;  
  border: 1px solid red;  
  margin: 0;  
  position: relative;  
  background-color: red;  
  display: block;  
  justify-content: center;  
  animation: 5s slideBox;  
}

#container {  
  height: 500px;  
}
@keyframes slideBox {  
  from {  
    left: 0;  
    top: 0;  
  } to {  
    left: calc(100% - 100px);  
    top: calc(100% - 100px)  
  }  
}

In the CSS above, initially we have the box placed on the top left corner of the page and styled it with a red filling. We have the slideBox animation sequence to love the box from the top left corner of the page to the bottom right corner by sliding it 100 pixels right and 100 pixels down in each frame.

Then we can add the following JavaScript code to log the animationend event via our onanimationend event handler:

const box = document.getElementById('box');document.onanimationend = () => {  
  console.log('Animation ended');  
};

Note this event handler only works on Firefox as the implementation of this event hasn’t been standardized, so we shouldn’t expect this event to be triggered and the event handler function to be called.

onanimationiteration

To do something while a CSS animation is running, we can use the onanimationiteration property by assigning an event handler function to this property. The event handler that we assign to this property will be called when the animationiteration event is being triggered.

The animationiteration is triggered when an iteration of the CSS animation ends and another one begins.

This event isn’t triggered at the same time as the animationend event, and it doesn’t get triggered if the animation iteration count is one. If we set the animation-iteration-count property of the element that’s being animated to make the element animate repeatedly, then this event will be triggered. For example, if we have the same box div element in the HTML code like we have before:

<div id='container'>  
  <div id='box'> </div>  
</div>

But we change the CSS code by adding the animation-iteration-count property to it:

#box {  
  width: 100px;  
  height: 100px;  
  left: 0;  
  top: 0;  
  border: 1px solid red;  
  margin: 0;  
  position: relative;  
  background-color: red;  
  display: block;  
  justify-content: center;  
  animation: 5s slideBox;  
  animation-iteration-count: 5;  
}

#container {  
  height: 500px;  
}

@keyframes slideBox {  
  from {  
    left: 0;  
    top: 0;  
  } to {  
    left: calc(100% - 100px);  
    top: calc(100% - 100px)  
  }  
}

And then switch the JavaScript code to have the following code to set the event handler to the onanimationiteration property:

const box = document.getElementById('box');
let iterationCount = 0;

document.onanimationiteration = () => {  
  console.log('Animation is running');  
  iterationCount++;;  
  console.log(iterationCount);  
};

Then in Firefox, we should see the following logged:

Animation is running1Animation is running2Animation is running3Animation is running4

We see that the value of iterationCount is increasing by 1 every time the animation is run, so the event handler is being run by the animationiteration event being trigger in Firefox. This event is an experimental event that’s available in Firefox only so far and it’s a work in progress like all the other animation events we looked at.

The onvisibilitychange is an event handler that’s run when the visibilitychange event is triggered and reaches the object. It’s a standard event that works on most modern browsers.

We can use it to log the situation when the page starting from it being invisible to it being visible and vice versa.

Then we looked at the non-standard onanimationcancel, onanimationend and onanimationiteration which are only triggered when used with Firefox.

Other browsers do not run these event handlers and we can infer that the corresponding animationcancel, animationend, and animationiteration events aren’t being triggered.

These events are triggered only on Firefox during CSS animations when the animation is cancelled unexpectedly like when the element being animation is abruptly removed when the animation ends, and when the animation repeats respectively.

Categories
JavaScript

JavaScript Events Handlers — ondurationchange and onemptied

In JavaScript, events are actions that happen in an app. They are triggered by various things like inputs being entered, forms being submitted, and changes in an element like resizing, or errors that happen when an app is running, etc. We can assign event handlers to respond to these events. Events that happen to DOM elements can be handled by assigning an event handler to properties of the DOM object for the corresponding events. In this article, we will look at the ondurationchange and onemptied event handlers.

ondurationchange

The ondurationchange event handler lets us set our own event handler to handle the durationchange event, which is fired when the duration attribute of the media element like video and audio has been updated. For example, we can use it to get the duration of the video by writing the following HTML code to add a video and a p element for telling us the duration of the video to a web page:

<video width="320" height="240" controls id='video'>  
  <source src="https://sample-videos.com/video123/mp4/240/big_buck_bunny_240p_1mb.mp4" type="video/mp4">  
  Your browser does not support the video tag.  
</video><p id='duration'></p>

Then in our JavaScript code, we can use the ondurationchange property to assign our own event handler to it like in the following code:

const video = document.getElementById('video');  
const duration = document.getElementById('duration');video.ondurationchange = (e) => {  
  console.log(e.srcElement.duration);  
  duration.innerHTML = `The video is ${e.srcElement.duration} seconds long`;  
};

In our event handler function, we have the e parameter, which is an Event object. In it, there’s a srcElement property that has the DOM object representation of the video element, which is the source element triggered the durationchange event to be fired. Since the srcElement is the video element, we can use it to get the duration by using the duration property, which is measured in seconds. Then inside our ondurationchange event handler function, we can display the duration of the video by adding:

duration.innerHTML = `The video is ${e.srcElement.duration} seconds long`;

After that, when we reload the page, we get the video and the ‘ The video is 13.696 seconds long’ displayed since that what we got 13.696 for the duration when we used the e.srcElement.duration to get the duration of the video in seconds. We can also use the addEventListener method to write this instead of like in the following code:

video.addEventListener('durationchange', (e) => {  
  console.log(e.srcElement.duration);  
  duration.innerHTML = `The video is ${e.srcElement.duration} seconds long`;  
})

It’s exactly the same as assigning an event handler to the ondurationchange property.

onemptied

We can set our own event handler for the onemptied property to handle the emptied event, which is fired when the media has become empty. For example, when the media has already been loaded and the load method is called to reload it. To use this property, we can add the following video to our HTML code:

<video width="320" height="240" controls id='video'>  
  <source src="https://sample-videos.com/video123/mp4/240/big_buck_bunny_240p_1mb.mp4" type="video/mp4">  
  Your browser does not support the video tag.  
</video>

Then when we call the load method our video element like in the following code:

const video = document.getElementById('video');  
const duration = document.getElementById('duration');
video.load();
video.onemptied = (e) => {  
  console.dir(e.srcElement);  
};

Then we can see the onemptied event handler being called since we will see the srcElement property being logged in the event handler function. The srcElement has the video element that fired the emptied event. We should get something like the following output from the console.dir call in our event handler function:

accessKey: ""  
assignedSlot: null  
attributeStyleMap: StylePropertyMap {size: 0}  
attributes: NamedNodeMap {0: width, 1: height, 2: controls, 3: id, width: width, height: height, controls: controls, id: id, length: 4}  
autocapitalize: ""  
autoplay: false  
baseURI: "https://fiddle.jshell.net/_display/"  
buffered: TimeRanges {length: 1}  
childElementCount: 1  
childNodes: NodeList(3) [text, source, text]  
children: HTMLCollection [source]  
classList: DOMTokenList [value: ""]  
className: ""  
clientHeight: 240  
clientLeft: 0  
clientTop: 0  
clientWidth: 320  
contentEditable: "inherit"  
controls: true  
controlsList: DOMTokenList \[value: ""\]  
crossOrigin: null  
currentSrc: "[https://sample-videos.com/video123/mp4/240/big\_buck\_bunny\_240p\_1mb.mp4](https://sample-videos.com/video123/mp4/240/big_buck_bunny_240p_1mb.mp4)"  
currentTime: 0  
dataset: DOMStringMap {}  
defaultMuted: false  
defaultPlaybackRate: 1  
dir: ""  
disablePictureInPicture: false  
disableRemotePlayback: false  
draggable: false  
duration: 13.696  
elementTiming: ""  
ended: false  
enterKeyHint: ""  
error: null  
firstChild: text  
firstElementChild: source  
height: 240  
hidden: false  
id: "video"  
innerHTML: "  
  <source src="https://sample-videos.com/video123/mp4/240/big_buck_bunny_240p_1mb.mp4" type="video/mp4">  
  Your browser does not support the video tag.  
"  
...  
offsetHeight: 240  
offsetLeft: 8  
offsetParent: body  
offsetTop: 8  
offsetWidth: 320  
...  
outerHTML: "<video width="320" height="240" controls="" id="video">  
  <source src="https://sample-videos.com/video123/mp4/240/big_buck_bunny_240p_1mb.mp4" type="video/mp4">  
  Your browser does not support the video tag.  
</video>"  
outerText: ""  
ownerDocument: document  
parentElement: body  
parentNode: body  
part: DOMTokenList [value: ""]  
paused: true  
playbackRate: 1  
played: TimeRanges {length: 0}  
playsInline: false  
poster: ""  
prefix: null  
preload: "metadata"  
previousElementSibling: null  
previousSibling: text  
readyState: 4  
remote: RemotePlayback {state: "disconnected", onconnecting: null, onconnect: null, ondisconnect: null}  
scrollHeight: 240  
scrollLeft: 0  
scrollTop: 0  
scrollWidth: 320  
seekable: TimeRanges {length: 1}  
seeking: false  
shadowRoot: null  
sinkId: ""  
slot: ""  
spellcheck: true  
src: ""  
srcObject: null  
style: CSSStyleDeclaration {alignContent: "", alignItems: "", alignSelf: "", alignmentBaseline: "", all: "", …}  
tabIndex: 0  
tagName: "VIDEO"  
textContent: "  
    
  Your browser does not support the video tag.  
"  
textTracks: TextTrackList {length: 0, onchange: null, onaddtrack: null, onremovetrack: null}  
title: ""  
translate: true  
videoHeight: 240  
videoWidth: 320  
volume: 1  
webkitAudioDecodedByteCount: 10995  
webkitDecodedFrameCount: 4  
webkitDisplayingFullscreen: false  
webkitDroppedFrameCount: 0  
webkitSupportsFullscreen: true  
webkitVideoDecodedByteCount: 37638  
width: 320

Alternatively, we can use the addEventListener method to do the same thing:

video.addEventListener('emptied, (e) => {  
  console.dir(e.srcElement);  
});

The video or other media elements like audio have some handy value properties:

  • buffered — a read only property that returns a TimeRanges object, which is an object that has a collection of time ranges for tracking which portion of the media have been buffered when loading it. We can get each range by using the start and end methods by passing an index into either method. It indicates the ranges of the media source that the browser has buffered (if any) at the moment the buffered property is accessed.
  • defaultMuted — a boolean property that reflects the muted HTML attribute, which indicates whether the media element’s audio output should be muted by default.
  • defaultPlaybackRate — a number property indicating the default playback rate for the media. We can set it by assigning a positive number to it or get the value from this property.
  • duration — a read-only double-precision floating-point value indicating the total duration of the media in seconds. If no media data is available, the returned value is NaN. If the media is of indefinite length then like a WebRTC stream, then the value is +Infinity.
  • ended — a read only property that returns a boolean that indicates whether the media element has finished playing.
  • muted — a boolean property that determines whether audio is muted. true if the audio is muted and false otherwise. This is a writable property so we can set it.
  • networkState — a read only property that returns a number that indicates the current state of fetching the media over the network. It can be 0, 1, 2, or 3. 0 means no data, 1 means the element has selected a resource, but it’s not using the network. 2 means the browser is downloading the data, and 3 means no src attribute found.
  • paused — a read only property that returns a boolean that indicates whether the media element is paused.
  • playbackRate — a number that indicates the rate at which the media is being played back. It’s a positive number, and we can set this property as well as get a value from it.
  • played — a read only property that returns a TimeRanges object that contains the ranges of the media source that the browser has played, if there’s any.

Conclusion

The ondurationchange event handler lets us set our own event handler to handle the durationchange event, which is fired when the duration attribute of the media element like video and audio has been updated. We can set our own event handler for the onemptied property to handle the emptied event, which is fired when the media has become empty. For example, when the media has already been loaded and the load method is called to reload it. We can get the element that fired these events with the srcElement property of the e parameter which is an Event object inside our event handler function. It has many useful properties to get us many pieces of information about our media element.

Categories
JavaScript React

Basic Built-in React Hooks- useState and useEffect

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 some basic built-in React hooks, including useState and useEffect .

useState

The useState hook lets us manage the internal state of a function component. It takes an initial value as an argument and returns an array with the current state and a function to update it.

It returns the initial state when the component is initially rendered.

We can pass in a function to update the value if the new value is computed using the previous state.

For example, we can write the following to update the value based on a previous one:

function App() {  
  const [count, setCount] = React.useState(0);  
  return (  
    <>  
      Count: {count}  
      <button onClick={() => setCount(0)}>Reset</button>  
      <button onClick={() => setCount(prevCount => prevCount - 1)}>  
        Decrement  
      </button>  
      <button onClick={() => setCount(prevCount => prevCount + 1)}>  
        Increment  
      </button>  
    </>  
  );  
}

In the code above, we have:

setCount(prevCount => prevCount - 1)}

and:

setCount(prevCount => prevCount + 1)}

which decrements the count and increment it respectively by passing in functions that take the previous count as the parameter and return the new count.

Otherwise, we can just pass in the new value to the state update function as we did in:

setCount(0)

useState doesn’t automatically merge update objects. We can replicate this behavior with the spread syntax:

function App() {  
  const [nums, setNums] = React.useState({});  
  return (  
    <>  
      <p>{Object.keys(nums).join(",")}</p>  
      <button  
        onClick={() =>  
          setNums(oldNums => {  
            const randomObj = { [Math.random()]: Math.random() };  
            return { ...oldNums, ...randomObj };  
          })  
        }  
      >  
        Click Me  
      </button>  
    </>  
  );  
}

In the code above, we have:

setNums(oldNums => {  
            const randomObj = { [Math.random()]: Math.random() };  
            return { ...oldNums, ...randomObj };  
          })

to create a randomObj object with a random number as the key and value, and we merge that into another object with the old value then return it.

Then we display it with:

Object.keys(nums).join(",")

by getting the keys and joining them together.

Lazy initial state

We can pass in a function to useState if we want to delay the setting of the initial state.

It’ll be ignored after the initial render if we pass in a function.

This is useful if the initial state is computed from some expensive operation.

For example, we can write:

function App() {  
  const [count, setCount] = React.useState(() => 0);  
  return (  
    <>  
      Count: {count}  
      <button onClick={() => setCount(() => 0)}>Reset</button>  
      <button onClick={() => setCount(prevCount => prevCount - 1)}>  
        Decrement  
      </button>  
      <button onClick={() => setCount(prevCount => prevCount + 1)}>  
        Increment  
      </button>  
    </>  
  );  
}

In the code above, we have:

React.useState(() => 0)

which is a function that just returns 0.

We’ll see the same results as before.

Bailing out of a state update

If we update a state hook with the same value as the current. React will skip updating the state without rendering the children or firing effects.

React uses Object.is() to compare the current and new states, which is close to the === operator except that +0 and -0 are treated to be not equal and NaN is equal to itself.

React may still render before bailing out but it won’t go deeper into the tree if it finds that the old and new values are the same.

useEffect

We can use the useEffect hook to do various operations that aren’t allowed inside the main body of the function component, which is anything outside the rendering phase.

Therefore, we can use this to do any mutations, subscriptions, setting timers, and other side effects.

It takes a callback to run the code.

We can return a function inside to run any cleanup code after each render and also when the component unmounts.

The callback passed into useEffect fires after layout and paint, during a deferred event.

This makes this suitable for running operations that shouldn’t block the browser from updating the screen.

Code that must be run synchronously can be put into the callback of the useLayoutEffect hook instead, which is the synchronous version of useEffect .

It’s guaranteed to fire before any new renders. React will always flush the previous render’s effects before starting a new update.

Conditionally firing an effect

We can pass in a second argument to useEffect with an array of values that requires an effect to be run when they change.

For example, we can use it to get data from an API on initial render as follows:

function App() {  
  const [joke, setJoke] = React.useState({});  
  useEffect(() => {  
    (async () => {  
      const response = await fetch("https://api.icndb.com/jokes/random");  
      const res = await response.json();  
      console.log(res);  
      setJoke(res);  
    })();  
  }, []);  
  return (  
    <>  
      <p>{joke.value && joke.value.joke}</p>  
    </>  
  );  
}

Passing an empty array as the second argument will stop it from loading in subsequent renders.

We can pass in a value to the array to watch the value in the array change and then run the callback function:

function App() {  
  const [joke, setJoke] = React.useState({});  
  const [id, setId] = React.useState(1);  
  useEffect(() => {  
    (async () => {  
      const response = await fetch(`https://api.icndb.com/jokes/${id}`);  
      const res = await response.json();  
      console.log(res);  
      setJoke(res);  
    })();  
  }, [id]);  
  return (  
    <>  
      <button onClick={() => setId(Math.ceil(Math.random() * 100))}>  
        Random Joke  
      </button>  
      <p>{joke.value && joke.value.joke}</p>  
    </>  
  );  
}

In the code above, when we click the Random Joke button, setId is called with a new number between 1 and 100. Then id changes, which triggers the useEffect callback to run.

Then joke is set with a new value, then the new joke is displayed on the screen.

Conclusion

useState and useEffect are the most basic built-in React hooks.

useState lets us update the state by passing in a function which returns the initial state, or passing in the initial state directly.

It returns an array with the current state and a function to update the state. The state update function returned takes in a value or a function that has the previous value as the parameter and returns the new value if it depends on the previous value.

useEffect lets us run any side effect, mutation, or anything outside of the rendering phase.

It takes a callback with the code to run as the first argument, and an array with the values to watch for changes for before the callback is run.

If an empty array is passed in, then the callback only runs on initial render.

Categories
Angular JavaScript

Loading Angular Components Dynamically

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 load and display Angular components dynamically.

Dynamic Component Loader

We can load components by using the ComponentFactoryResolver to load multiple dynamic components.

Also, we have to make a directive to host the components.

First, we have to create the following components and directives by running the following commands:

ng g directive BannerHost  
ng g component Foo  
ng g component Bar

Then we write the following code:

app.module.ts :

import { BrowserModule } from '@angular/platform-browser';  
import { NgModule } from '@angular/core';
import { AppRoutingModule } from './app-routing.module';  
import { AppComponent } from './app.component';  
import { FooComponent } from './foo/foo.component';  
import { BarComponent } from './bar/bar.component';  
import { BannerHostDirective } from './banner-host.directive';

@NgModule({  
  declarations: [  
    AppComponent,  
    FooComponent,  
    BarComponent,  
    BannerHostDirective  
  ],  
  imports: [  
    BrowserModule,  
    AppRoutingModule  
  ],  
  providers: [],  
  bootstrap: [AppComponent],  
  entryComponents: [  
    FooComponent,  
    BarComponent,  
  ]  
})  
export class AppModule { }

banner-host.directive.ts :

import { Directive, ViewContainerRef } from '@angular/core';

@Directive({  
  selector: '[banner-host]'  
})  
export class BannerHostDirective {
  constructor(public viewContainerRef: ViewContainerRef) { }
}

bar.component.html :

bar

foo.component.ts :

import { Component, OnInit, Input } from '@angular/core';

@Component({  
  selector: 'app-foo',  
  templateUrl: './foo.component.html',  
  styleUrls: ['./foo.component.css']  
})  
export class FooComponent implements OnInit {  
  @Input() data: string; constructor() { } 

  ngOnInit() {  
  }
}

foo.component.html :

foo {{data}}

app.component.ts :

import { Component, ComponentFactoryResolver, ViewChild } from '@angular/core';  
import { FooComponent } from './foo/foo.component';  
import { BannerHostDirective } from './banner-host.directive';  
import { BarComponent } from './bar/bar.component';

@Component({  
  selector: 'app-root',  
  templateUrl: './app.component.html',  
  styleUrls: ['./app.component.css']  
})  
export class AppComponent {  
  interval: any;  
  currentBannerIndex: number = -1;  
  banners = [FooComponent, BarComponent]  
  @ViewChild(BannerHostDirective, { static: true }) bannerHost: BannerHostDirective;  

  constructor(private componentFactoryResolver: ComponentFactoryResolver) { } 

  ngOnInit() {  
    this.loadComponent();  
    this.getBanners();  
  } ngOnDestroy() {  
    clearInterval(this.interval);  
  }

  loadComponent() {  
    this.currentBannerIndex = (this.currentBannerIndex + 1) % this.banners.length;  
    const bannerItem = this.banners[this.currentBannerIndex];
    const componentFactory = this.componentFactoryResolver.resolveComponentFactory(bannerItem);  
    const viewContainerRef = this.bannerHost.viewContainerRef;  
    viewContainerRef.clear();  
    const componentRef = viewContainerRef.createComponent(componentFactory);  
    if (componentRef.instance.constructor === FooComponent) {  
      (<FooComponent>componentRef.instance).data = 'hello';  
    }  
  } 

  getBanners() {  
    this.interval = setInterval(() => {  
      this.loadComponent();  
    }, 3000);  
  }  
}

app.component.html :

<ng-template banner-host></ng-template>

In the code above, we created the banner-host directive to inject the component that we’re going to load.

We applied that to ng-template in app.component.html .

FooComponent takes a data input that we’re going to set.

Then the components are loaded in AppComponent .

The loadComponent method in AppComponent by going through the banners that we defined in the banners array.

We changed the currentBannerIndex every 3 seconds as indicated by the getBanners method.

The actual component loading is done by the following code:

const componentFactory = this.componentFactoryResolver.resolveComponentFactory(bannerItem);  
const viewContainerRef = this.bannerHost.viewContainerRef;  
viewContainerRef.clear();  
const componentRef = viewContainerRef.createComponent(componentFactory);  
if (componentRef.instance.constructor === FooComponent) {  
  (<FooComponent>componentRef.instance).data = 'hello';  
}

In the code above, we run the this.componentFactoryResolver.resolveComponentFactory method with the bannerItem as set by:

const bannerItem = this.banners[this.currentBannerIndex];

Then we clear the current view by writing:

const viewContainerRef = this.bannerHost.viewContainerRef;  
viewContainerRef.clear();

Next, we load the component by writing:

const componentRef = viewContainerRef.createComponent(componentFactory);

Finally, we check that the component is an instance of the FooComponent . If it is, then we load the string data property as indicated in the data field of FooComponent as follows:

if (componentRef.instance.constructor === FooComponent) {  
  (<FooComponent>componentRef.instance).data = 'hello';  
}

We used componentRef.instance.constructor to get which component instance is loaded.

Then if the component is an instance of FooComponent , we cast it to the FooComponent and set the data property of it to 'hello' as follows:

(<FooComponent>componentRef.instance).data = 'hello';

In the end, we should see foo hello and bar displayed one after the other in a 3-second cycle.

Conclusion

Displaying multiple Angular components dynamically involves several steps.

First, we have to create a directive to inject the components that we want to load.

Then we have to add an ng-template with the directive that we just created applied to it.

Next, we clear the existing view and use the ComponentFactoryResolver to clear the screen and then load the component that we want to load.

We can check the instance of the component that’s loaded and then do different things according to what component instance is loaded.