In this Angular 2.0 article series, we have already discussed about different basic concepts or features of AngularJs 2.0 like data binding, Directives, pipes, Service, route, http modules etc. Now, in this article, we will discuss about Immutable JS concept. In case you have not had a look at the previous articles of this series, go through the links mentioned below.
Immutable.js is a library, which provides immutable generic collections.
What is Immutability?
Immutability is a design pattern, where something can't be modified after being instantiated. If we want to change the value of that thing, we must recreate it with the new value instead. Some JavaScript types are immutable and some are mutable, which means their value can change without having to recreate it. Let's explain this difference with some examples.
- let movie = {
- name: 'Star Wars',
- episode: 7
- };
-
- let myEp = movie.episode;
-
- movie.episode = 8;
-
- alert(myEp);
As you can see in this case, although we changed the value of movie.episode, the value of myEp didn't change. That's because movie.episode's type, number, is immutable.
- let movie1 = {
- name: 'Star Wars',
- episode: 7
- };
-
- let movie2 = movie1;
-
- movie2.episode = 8;
-
- alert(movie1.episode); // outputs 8
In this case however, changing the value of an episode on one object has also changed the value of the other. It is because movie1 and movie2 are of the Object type and Objects are mutable. Of JavaScript built-in types, the following are immutable.
- Boolean
- Number
- String
- Symbol
- Null
- Undefined
The following are mutable.
String is an unusual case, since it can be iterated over using for...of and provides numeric indexers just like an array, but doing something, as shown below.
- let message = 'Hello world';
- message[5] = '-';
- alert(message); // writes Hello world
The case for Immutability
One of the more difficult things to manage when structuring an Application is managing its state. This is especially true when your Application can execute code asynchronously. Let's say you execute some piece of code, but something causes it to wait (such as an HTTP request or a user input). After it's completed, you notice the state; it's expecting changed because some other piece of code executed asynchronously and changed its value.
Dealing with this kind of behavior on a small scale might be manageable, but this can show up all over an Application and can be a real headache as the Application gets bigger with more interactions and more complex logic. Immutability attempts to solve this by making sure that any object referenced in one part of the code can't be changed by another part of the code unless they have the ability to rebind it directly.
Object.assign
Object.assign allows us to merge one object's properties into another, replacing the values of the properties with the matching names. We can use this to copy an object's value without altering the existing one.
- let movie1 = {
- name: 'Star Wars',
- episode: 7
- };
-
- let movie2 = Object.assign({}, movie1);
-
- movie2.episode = 8;
-
- alert(movie1.episode); // writes 7
- alert(movie2.episode); // writes 8
Object.freeze
Object.freeze allows us to disable object mutation.
- let movie1 = {
- name: 'Star Wars',
- episode: 7
- };
-
- let movie2 = Object.freeze(Object.assign({}, movie1));
-
- movie2.episode = 8; // fails silently in non-strict mode,
- // throws error in strict mode
-
- alert(movie1.episode); // writes 7
- alert(movie2.episode); // writes 7
Immutable.js Basics
To solve our mutability problem, Immutable.js must provide immutable versions of the two core mutable types i.e. Object and Array.
Immutable.Map
Map is the immutable version of JavaScript's object structure. Due to JavaScript objects having the concise object literal syntax, it's often used as a key-value store with the key being type string. This pattern closely follows the map data structure. Let's revisit the previous example, but use Immutable.Mapinstead.
Instead of binding the object literal directly to movie1, we pass it as an argument to Immutable.Map. This changes how we interact with movie1's properties. To get the value of a property, we call the get method, passing the property name; which we want, like how we'd use an object's string indexer. To set the value of a property, we call the set method, pass the property name and the new value. Note that it won't mutate the existing Map object - it returns a new object with the updated property, so we must rebind the movie2 variable to the new object.
- let movie1 = Immutable.Map<string, any>({
- name: 'Star Wars',
- episode: 7
- });
Now, demonstrate this concept. Write down the code given below.
app.component.homepage.ts
- import { Component, OnInit, ViewChild } from '@angular/core';
- import { Http, Response } from '@angular/http';
- import * as Immutable from 'immutable';
-
- @Component({
- moduleId: module.id,
- selector: 'home-page',
- templateUrl: 'app.component.homepage.html'
- })
-
- export class HomePageComponent implements OnInit {
-
- private data: Array<any> = [];
-
- constructor() {
- }
-
- ngOnInit(): void {
- }
-
- private fnShow(): void {
- let movie1 = Immutable.Map<string, any>({
- name: 'Star Wars',
- episode: 7
- });
-
- let movie2 = movie1;
-
- movie2movie2 = movie2.set('episode', 8);
-
- alert(movie1.get('episode')); // writes 7
- alert(movie2.get('episode')); // writes 8
- }
-
- private fnMerge(): void {
- let baseButton = Immutable.Map<string, any>({
- text: 'Click me!',
- state: 'inactive',
- width: 200,
- height: 30
- });
-
- let submitButton = baseButton.merge({
- text: 'Submit',
- state: 'active'
- });
-
- console.log(submitButton);
- }
- }
app.component.homepage.html
- <div>
- <h3>Immutable JS</h3>
- <input type="button" value="Show Data (Map)" (click)="fnShow()"/>
- <input type="button" value="Show Data (Merge)" (click)="fnMerge()" />
- </div>
app.module.ts
- import { NgModule, NO_ERRORS_SCHEMA } from '@angular/core';
- import { BrowserModule } from '@angular/platform-browser';
- import { ReactiveFormsModule } from "@angular/forms";
-
- import { HomePageComponent } from './src/app.component.homepage';
-
- @NgModule({
- imports: [BrowserModule, ReactiveFormsModule],
- declarations: [HomePageComponent],
- bootstrap: [HomePageComponent]
- })
- export class AppModule { }
main.ts
- import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
-
- import { AppModule } from './app.module';
-
- const platform = platformBrowserDynamic();
- platform.bootstrapModule(AppModule);
index.html
- <!DOCTYPE html>
- <html>
- <head>
- <title>Angular2 - Immutable JS </title>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1">
- <link href="../resources/style/bootstrap.css" rel="stylesheet" />
- <link href="../resources/style/style1.css" rel="stylesheet" />
-
- <script src="../resources/js/jquery-2.1.1.js"></script>
- <script src="../resources/js/bootstrap.js"></script>
-
- <script src="../node_modules/core-js/client/shim.min.js"></script>
- <script src="../node_modules/zone.js/dist/zone.js"></script>
- <script src="../node_modules/reflect-metadata/Reflect.js"></script>
- <script src="../node_modules/systemjs/dist/system.src.js"></script>
- <script src="../systemjs.config.js"></script>
- <script>
- System.import('app').catch(function (err) { console.error(err); });
- </script>
-
- <script>document.write('<base href="' + document.location + '" />');</script>
- </head>
- <body>
- <home-page>Loading</home-page>
- </body>
- </html>
Now, run the code and output, as shown below.