Author Archives: admin

Qunit: intro to unit testing

[Fuente: https://qunitjs.com/intro/]

You probably know that testing is good, but the first hurdle to overcome when trying to write unit tests for client-side code is the lack of any actual units; JavaScript code is written for each page of a website or each module of an application and is closely intermixed with back-end logic and related HTML. In the worst case, the code is completely mixed with HTML, as inline events handlers.

This is likely the case when no JavaScript library for some DOM abstraction is being used; writing inline event handlers is much easier than using the DOM APIs to bind those events. More and more developers are picking up a library such as jQuery to handle the DOM abstraction, allowing them to move those inline events to distinct scripts, either on the same page or even in a separate JavaScript file. However, putting the code into separate files doesn’t mean that it is ready to be tested as a unit.

What is a unit anyway? In the best case, it is a pure function that you can deal with in some way — a function that always gives you the same result for a given input. This makes unit testing pretty easy, but most of the time you need to deal with side effects, which here means DOM manipulations. It’s still useful to figure out which units we can structure our code into and to build unit tests accordingly.

Building Unit Tests

With that in mind, we can obviously say that starting with unit testing is much easier when starting something from scratch. But that’s not what this article is about. This article is to help you with the harder problem: extracting existing code and testing the important parts, potentially uncovering and fixing bugs in the code.

The process of extracting code and putting it into a different form, without modifying its current behavior, is called refactoring. Refactoring is an excellent method of improving the code design of a program; and because any change could actually modify the behaviour of the program, it is safest to do when unit tests are in place.

This chicken-and-egg problem means that to add tests to existing code, you have to take the risk of breaking things. So, until you have solid coverage with unit tests, you need to continue manually testing to minimize that risk.

That should be enough theory for now. Let’s look at a practical example, testing some JavaScript code that is currently mixed in with and connected to a page. The code looks for links with title attributes, using those titles to display when something was posted, as a relative time value, like “5 days ago”:

<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <title>Mangled date examples</title>
  <script>
  function prettyDate(time){
    var date = new Date(time || ""),
      diff = (((new Date()).getTime() - date.getTime()) / 1000),
      day_diff = Math.floor(diff / 86400);
 
    if ( isNaN(day_diff) || day_diff < 0 || day_diff >= 31 )
      return;
 
    return day_diff == 0 && (
        diff < 60 && "just now" ||
        diff < 120 && "1 minute ago" ||
        diff < 3600 && Math.floor( diff / 60 ) +
          " minutes ago" ||
        diff < 7200 && "1 hour ago" ||
        diff < 86400 && Math.floor( diff / 3600 ) +
          " hours ago") ||
      day_diff == 1 && "Yesterday" ||
      day_diff < 7 && day_diff + " days ago" ||
      day_diff < 31 && Math.ceil( day_diff / 7 ) +
        " weeks ago";
  }
  window.onload = function() {
    var links = document.getElementsByTagName("a");
    for ( var i = 0; i < links.length; i++ ) {
      if ( links[i].title ) {
        var date = prettyDate(links[i].title);
        if ( date ) {
          links[i].innerHTML = date;
        }
      }
    }
  };
  </script>
</head>
<body>
 
<ul>
  <li class="entry">
    <p>blah blah blah...</p>
    <small class="extra">
      Posted <span class="time">
        <a href="#2008/01/blah/57/" title="2008-01-28T20:24:17Z">
          <span>January 28th, 2008</span>
        </a>
      </span>
      by <span class="author"><a href="#john/">John Resig</a></span>
    </small>
  </li>
  <!-- more list items -->
</ul>
 
</body>
</html>

If you ran that example, you’d see a problem: none of the dates get replaced. The code works, though. It loops through all anchors on the page and checks for a title property on each. If there is one, it passes it to the prettyDate function. If prettyDate returns a result, it updates the innerHTML of the link with the result.

Make Things Testable

The problem is that for any date older then 31 days, prettyDate just returns undefined (implicitly, with a single return statement), leaving the text of the anchor as is. So, to see what’s supposed to happen, we can hardcode a “current” date:

<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <title>Mangled date examples</title>
  <script>
  function prettyDate(now, time){
    var date = new Date(time || ""),
      diff = (((new Date(now)).getTime() - date.getTime()) / 1000),
      day_diff = Math.floor(diff / 86400);
 
    if ( isNaN(day_diff) || day_diff < 0 || day_diff >= 31 )
      return;
 
    return day_diff == 0 && (
        diff < 60 && "just now" ||
        diff < 120 && "1 minute ago" ||
        diff < 3600 && Math.floor( diff / 60 ) +
          " minutes ago" ||
        diff < 7200 && "1 hour ago" ||
        diff < 86400 && Math.floor( diff / 3600 ) +
          " hours ago") ||
      day_diff == 1 && "Yesterday" ||
      day_diff < 7 && day_diff + " days ago" ||
      day_diff < 31 && Math.ceil( day_diff / 7 ) +
        " weeks ago";
  }
  window.onload = function() {
    var links = document.getElementsByTagName("a");
    for ( var i = 0; i < links.length; i++ ) {
      if ( links[i].title ) {
        var date = prettyDate("2008-01-28T22:25:00Z",
          links[i].title);
        if ( date ) {
          links[i].innerHTML = date;
        }
      }
    }
  };
  </script>
</head>
<body>
 
<ul>
  <li class="entry">
    <p>blah blah blah...</p>
    <small class="extra">
      Posted <span class="time">
        <a href="#2008/01/blah/57/" title="2008-01-28T20:24:17Z">
          <span>January 28th, 2008</span>
        </a>
      </span>
      by <span class="author"><a href="#john/">John Resig</a></span>
    </small>
  </li>
  <!-- more list items -->
</ul>
 
</body>
</html>

Now, the links should say “2 hours ago,” “Yesterday” and so on. That’s something, but still not an actual testable unit. So, without changing the code further, all we can do is try to test the resulting DOM changes. Even if that did work, any small change to the markup would likely break the test, resulting in a really bad cost-benefit ratio for a test like that.

Refactoring, Stage 0

Instead, let’s refactor the code just enough to have something that we can unit test.

We need to make two changes for this to happen: pass the current date to the prettyDate function as an argument, instead of having it just use new Date, and extract the function to a separate file so that we can include the code on a separate page for unit tests.

<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <title>Refactored date examples</title>
  <script src="prettydate.js"></script>
  <script>
  window.onload = function() {
    var links = document.getElementsByTagName("a");
    for ( var i = 0; i < links.length; i++ ) {
      if ( links[i].title ) {
        var date = prettyDate("2008-01-28T22:25:00Z",
          links[i].title);
        if ( date ) {
          links[i].innerHTML = date;
        }
      }
    }
  };
  </script>
</head>
<body>
 
<ul>
  <li class="entry">
    <p>blah blah blah...</p>
    <small class="extra">
      Posted <span class="time">
        <a href="#2008/01/blah/57/" title="2008-01-28T20:24:17Z">
          <span>January 28th, 2008</span>
        </a>
      </span>
      by <span class="author"><a href="#john/">John Resig</a></span>
    </small>
  </li>
  <!-- more list items -->
</ul>
 
</body>
</html>

Here’s the contents of prettydate.js:

function prettyDate(now, time){
  var date = new Date(time || ""),
    diff = (((new Date(now)).getTime() - date.getTime()) / 1000),
    day_diff = Math.floor(diff / 86400);
 
  if ( isNaN(day_diff) || day_diff < 0 || day_diff >= 31 )
    return;
 
  return day_diff == 0 && (
      diff < 60 && "just now" ||
      diff < 120 && "1 minute ago" ||
      diff < 3600 && Math.floor( diff / 60 ) +
        " minutes ago" ||
      diff < 7200 && "1 hour ago" ||
      diff < 86400 && Math.floor( diff / 3600 ) +
        " hours ago") ||
    day_diff == 1 && "Yesterday" ||
    day_diff < 7 && day_diff + " days ago" ||
    day_diff < 31 && Math.ceil( day_diff / 7 ) +
      " weeks ago";
}

Now that we have something to test, let’s write some actual unit tests:

<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <title>Refactored date examples</title>
  <script src="prettydate.js"></script>
  <script>
  function test(then, expected) {
    results.total++;
    var result = prettyDate("2008/01/28 22:25:00", then);
    if (result !== expected) {
      results.bad++;
      console.log("Expected " + expected +
        ", but was " + result);
    }
  }
  var results = {
    total: 0,
    bad: 0
  };
  test("2008/01/28 22:24:30", "just now");
  test("2008/01/28 22:23:30", "1 minute ago");
  test("2008/01/28 21:23:30", "1 hour ago");
  test("2008/01/27 22:23:30", "Yesterday");
  test("2008/01/26 22:23:30", "2 days ago");
  test("2007/01/26 22:23:30", undefined);
  console.log("Of " + results.total + " tests, " +
    results.bad + " failed, " +
    (results.total - results.bad) + " passed.");
  </script>
</head>
<body>
 
</body>
</html>
  • Run this example. (Make sure to enable a console such as Firebug or Chrome’s Web Inspector.)

This will create an ad-hoc testing framework, using only the console for output. It has no dependencies to the DOM at all, so you could just as well run it in a non-browser JavaScript environment, such as Node.js or Rhino, by extracting the code in the script tag to its own file.

If a test fails, it will output the expected and actual result for that test. In the end, it will output a test summary with the total, failed and passed number of tests.

If all tests have passed, like they should here, you would see the following in the console:

Of 6 tests, 0 failed, 6 passed.

To see what a failed assertion looks like, we can change something to break it:

Expected 2 day ago, but was 2 days ago.

Of 6 tests, 1 failed, 5 passed.

While this ad-hoc approach is interesting as a proof of concept (you really can write a test runner in just a few lines of code), it’s much more practical to use an existing unit testing framework that provides better output and more infrastructure for writing and organizing tests.

The QUnit JavaScript Test Suite

The choice of framework is mostly a matter of taste. For the rest of this article, we’ll use QUnit (pronounced “q-unit”), because its style of describing tests is close to that of our ad-hoc test framework.

<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <title>Refactored date examples</title>
 
  <link rel="stylesheet" href="https://code.jquery.com/qunit/qunit-2.9.2.css">
  <script src="https://code.jquery.com/qunit/qunit-2.9.2.js"></script>
  <script src="prettydate.js"></script>
 
  <script>
  QUnit.test("prettydate basics", function( assert ) {
    var now = "2008/01/28 22:25:00";
    assert.equal(prettyDate(now, "2008/01/28 22:24:30"), "just now");
    assert.equal(prettyDate(now, "2008/01/28 22:23:30"), "1 minute ago");
    assert.equal(prettyDate(now, "2008/01/28 21:23:30"), "1 hour ago");
    assert.equal(prettyDate(now, "2008/01/27 22:23:30"), "Yesterday");
    assert.equal(prettyDate(now, "2008/01/26 22:23:30"), "2 days ago");
    assert.equal(prettyDate(now, "2007/01/26 22:23:30"), undefined);
  });
  </script>
</head>
<body>
 
<div id="qunit"></div>
 
</body>
</html>

Three sections are worth a closer look here. Along with the usual HTML boilerplate, we have three included files: two files for QUnit (qunit.css and qunit.js) and the previous prettydate.js.

Then, there’s another script block with the actual tests. The test method is called once, passing a string as the first argument (naming the test) and passing a function as the second argument (which will run the actual code for this test). This code then defines the now variable, which gets reused below, then calls the equal method a few times with varying arguments. The equal method is one of several assertions that QUnit provides through the first parameter in the callback function of the test block. The first argument is the result of a call to prettyDate, with the now variable as the first argument and a date string as the second. The second argument to equal is the expected result. If the two arguments to equal are the same value, then the assertion will pass; otherwise, it will fail.

Finally, in the body element is some QUnit-specific markup. These elements are optional. If present, QUnit will use them to output the test results.

The result is this:

With a failed test, the result would look something like this:

Because the test contains a failing assertion, QUnit doesn’t collapse the results for that test, and we can see immediately what went wrong. Along with the output of the expected and actual values, we get a diff between the two, which can be useful for comparing larger strings. Here, it’s pretty obvious what went wrong.

Refactoring, Stage 1

The assertions are currently somewhat incomplete because we aren’t yet testing the n weeks ago variant. Before adding it, we should consider refactoring the test code. Currently, we are calling prettyDate for each assertion and passing the now argument. We could easily refactor this into a custom assertion method:

QUnit.test("prettydate basics", function( assert ) {
  function date(then, expected) {
    assert.equal(prettyDate("2008/01/28 22:25:00", then), expected);
  }
  date("2008/01/28 22:24:30", "just now");
  date("2008/01/28 22:23:30", "1 minute ago");
  date("2008/01/28 21:23:30", "1 hour ago");
  date("2008/01/27 22:23:30", "Yesterday");
  date("2008/01/26 22:23:30", "2 days ago");
  date("2007/01/26 22:23:30", undefined);
});

Here we’ve extracted the call to prettyDate into the date function, inlining the now variable into the function. We end up with just the relevant data for each assertion, making it easier to read, while the underlying abstraction remains pretty obvious.

Testing The DOM manipulation

Now that the prettyDate function is tested well enough, let’s shift our focus back to the initial example. Along with the prettyDate function, it also selected some DOM elements and updated them, within the window load event handler. Applying the same principles as before, we should be able to refactor that code and test it. In addition, we’ll introduce a module for these two functions, to avoid cluttering the global namespace and to be able to give these individual functions more meaningful names.

<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <title>Refactored date examples</title>
  <link rel="stylesheet" href="https://code.jquery.com/qunit/qunit-2.9.2.css">
  <script src="https://code.jquery.com/qunit/qunit-2.9.2.js"></script>
  <script src="prettydate2.js"></script>
  <script>
  QUnit.test("prettydate.format", function( assert ) {
    function date(then, expected) {
      assert.equal(prettyDate.format("2008/01/28 22:25:00", then),
        expected);
    }
    date("2008/01/28 22:24:30", "just now");
    date("2008/01/28 22:23:30", "1 minute ago");
    date("2008/01/28 21:23:30", "1 hour ago");
    date("2008/01/27 22:23:30", "Yesterday");
    date("2008/01/26 22:23:30", "2 days ago");
    date("2007/01/26 22:23:30", undefined);
  });
 
  QUnit.test("prettyDate.update", function( assert ) {
    var links = document.getElementById("qunit-fixture")
      .getElementsByTagName("a");
    assert.equal(links[0].innerHTML, "January 28th, 2008");
    assert.equal(links[2].innerHTML, "January 27th, 2008");
    prettyDate.update("2008-01-28T22:25:00Z");
    assert.equal(links[0].innerHTML, "2 hours ago");
    assert.equal(links[2].innerHTML, "Yesterday");
  });
 
  QUnit.test("prettyDate.update, one day later", function( assert ) {
    var links = document.getElementById("qunit-fixture")
      .getElementsByTagName("a");
    assert.equal(links[0].innerHTML, "January 28th, 2008");
    assert.equal(links[2].innerHTML, "January 27th, 2008");
    prettyDate.update("2008/01/29 22:25:00");
    assert.equal(links[0].innerHTML, "Yesterday");
    assert.equal(links[2].innerHTML, "2 days ago");
  });
  </script>
</head>
<body>
 
<div id="qunit"></div>
<div id="qunit-fixture">
 
<ul>
  <li class="entry">
    <p>blah blah blah...</p>
    <small class="extra">
      Posted <span class="time">
        <a href="#2008/01/blah/57/" title="2008-01-28T20:24:17Z"
          >January 28th, 2008</a>
      </span>
      by <span class="author"><a href="#john/">John Resig</a></span>
    </small>
  </li>
  <li class="entry">
    <p>blah blah blah...</p>
    <small class="extra">
      Posted <span class="time">
        <a href="#2008/01/blah/57/" title="2008-01-27T22:24:17Z"
          >January 27th, 2008</a>
      </span>
      by <span class="author"><a href="#john/">John Resig</a></span>
    </small>
  </li>
</ul>
 
</div>
 
</body>
</html>

Here’s the contents of prettydate2.js:

var prettyDate = {
  format: function(now, time){
    var date = new Date(time || ""),
      diff = (((new Date(now)).getTime() - date.getTime()) / 1000),
      day_diff = Math.floor(diff / 86400);
 
    if ( isNaN(day_diff) || day_diff < 0 || day_diff >= 31 )
      return;
 
    return day_diff === 0 && (
        diff < 60 && "just now" ||
        diff < 120 && "1 minute ago" ||
        diff < 3600 && Math.floor( diff / 60 ) +
          " minutes ago" ||
        diff < 7200 && "1 hour ago" ||
        diff < 86400 && Math.floor( diff / 3600 ) +
          " hours ago") ||
      day_diff === 1 && "Yesterday" ||
      day_diff < 7 && day_diff + " days ago" ||
      day_diff < 31 && Math.ceil( day_diff / 7 ) +
        " weeks ago";
  },
 
  update: function(now) {
    var links = document.getElementsByTagName("a");
    for ( var i = 0; i < links.length; i++ ) {
      if ( links[i].title ) {
        var date = prettyDate.format(now, links[i].title);
        if ( date ) {
          links[i].innerHTML = date;
        }
      }
    }
  }
};

The new prettyDate.update function is an extract of the initial example, but with the now argument to pass through to prettyDate.format. The QUnit-based test for that function starts by selecting all a elements within the #qunit-fixture element. In the updated markup in the body element, the <div id="qunit-fixture">…</div> is new. It contains an extract of the markup from our initial example, enough to write useful tests against. By putting it in the #qunit-fixture element, we don’t have to worry about DOM changes from one test affecting other tests, because QUnit will automatically reset the markup after each test.

Let’s look at the first test for prettyDate.update. After selecting those anchors, two assertions verify that these have their initial text values. Afterwards, prettyDate.update is called, passing along a fixed date (the same as in previous tests). Afterwards, two more assertions are run, now verifying that the innerHTML property of these elements have the correctly formatted date, “2 hours ago” and “Yesterday.”

Refactoring, Stage 2

The next test, prettyDate.update, one day later, does nearly the same thing, except that it passes a different date to prettyDate.update and, therefore, expects different results for the two links. Let’s see if we can refactor these tests to remove the duplication.

<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <title>Refactored date examples</title>
  <link rel="stylesheet" href="https://code.jquery.com/qunit/qunit-2.9.2.css">
  <script src="https://code.jquery.com/qunit/qunit-2.9.2.js"></script>
  <script src="prettydate2.js"></script>
  <script>
  QUnit.test("prettydate.format", function( assert ) {
    function date(then, expected) {
      assert.equal(prettyDate.format("2008/01/28 22:25:00", then),
        expected);
    }
    date("2008/01/28 22:24:30", "just now");
    date("2008/01/28 22:23:30", "1 minute ago");
    date("2008/01/28 21:23:30", "1 hour ago");
    date("2008/01/27 22:23:30", "Yesterday");
    date("2008/01/26 22:23:30", "2 days ago");
    date("2007/01/26 22:23:30", undefined);
  });
 
  function domtest(name, now, first, second) {
    QUnit.test(name, function( assert ) {
      var links = document.getElementById("qunit-fixture")
        .getElementsByTagName("a");
      assert.equal(links[0].innerHTML, "January 28th, 2008");
      assert.equal(links[2].innerHTML, "January 27th, 2008");
      prettyDate.update(now);
      assert.equal(links[0].innerHTML, first);
      assert.equal(links[2].innerHTML, second);
    });
  }
  domtest("prettyDate.update", "2008-01-28T22:25:00Z",
    "2 hours ago", "Yesterday");
  domtest("prettyDate.update, one day later", "2008/01/29 22:25:00",
    "Yesterday", "2 days ago");
  </script>
</head>
<body>
 
<div id="qunit"></div>
<div id="qunit-fixture">
 
<ul>
  <li class="entry">
    <p>blah blah blah...</p>
    <small class="extra">
      Posted <span class="time">
        <a href="#2008/01/blah/57/" title="2008-01-28T20:24:17Z"
          >January 28th, 2008</a>
      </span>
      by <span class="author"><a href="#john/">John Resig</a></span>
    </small>
  </li>
  <li class="entry">
    <p>blah blah blah...</p>
    <small class="extra">
      Posted <span class="time">
        <a href="#2008/01/blah/57/" title="2008-01-27T22:24:17Z"
          >January 27th, 2008</a>
      </span>
      by <span class="author"><a href="#john/">John Resig</a></span>
    </small>
  </li>
</ul>
 
</div>
 
</body>
</html>

Here we have a new function called domtest, which encapsulates the logic of the two previous calls to test, introducing arguments for the test name, the date string and the two expected strings. It then gets called twice.

Back To The Start

With that in place, let’s go back to our initial example and see what that looks like now, after the refactoring.

<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <title>Final date examples</title>
  <script src="prettydate2.js"></script>
  <script>
  window.onload = function() {
    prettyDate.update("2008-01-28T22:25:00Z");
  };
  </script>
</head>
<body>
 
<ul>
  <li class="entry">
    <p>blah blah blah...</p>
    <small class="extra">
      Posted <span class="time">
        <a href="#2008/01/blah/57/" title="2008-01-28T20:24:17Z">
          <span>January 28th, 2008</span>
        </a>
      </span>
      by <span class="author"><a href="#john/">John Resig</a></span>
    </small>
  </li>
  <!-- more list items -->
</ul>
 
</body>
</html>

For a non-static example, we’d remove the argument to prettyDate.update. All in all, the refactoring is a huge improvement over the first example. And thanks to the prettyDate module that we introduced, we can add even more functionality without clobbering the global namespace.

Conclusion

Testing JavaScript code is not just a matter of using some test runner and writing a few tests; it usually requires some heavy structural changes when applied to code that has been tested only manually before. We’ve walked through an example of how to change the code structure of an existing module to run some tests using an ad-hoc testing framework, then replacing that with a more full-featured framework to get useful visual results.

QUnit itself has a lot more to offer, with specific support for testing asynchronous code such as timeouts, AJAX and events. Its visual test runner helps to debug code by making it easy to rerun specific tests and by providing stack traces for failed assertions and caught exceptions. For further reading, check out the QUnit Cookbook.

 

AngularJS + JEST

[Fuente: https://medium.zenika.com/testing-an-angularjs-app-with-jest-3029a613251]

I will not try to write the ultimate guide to test an AngularJS app with Jest, this article is only my feedback after trying Jest on my project.

TL;DR It works pretty well 🙂

First, let me add some context. I was a huge AngularJS fan and slowly drifted to React. That’s probably why I was aware of Jest and wanted to try it. Still, my biggest ongoing project uses AngularJS, it’s the member part of the new data exchange plateforme: Dawex (https://www.dawex.com/)

Also, I’m one of the developers of the Yeoman Fountain generator (fountainjs.io) and as such, pretty familiar with the use and configuration of new Web tooling.

The app is, I think, a pretty modern AngularJS app. It’s fully component oriented with AngularJS 1.5.x and uses NPM, Webpack and Babel for tooling. We even added some new stuff like RxJS and Redux.

The test environment is based on Karma and Jasmine. We use the karma-webpack plugin and tests are running in Chrome since we encountered some problems with PhantomJS.

Tests work with watching mode and coverage but there are some pain points remaining. The test bootstrap is very slow as Webpack has to build the whole bundle. Then, it starts a real Chrome (which need the xvfb trick on CI) and we didn’t find a way to have proper source-mapping (through Babel / Webpack / Karma) for proper error stacktraces. Finally but perhaps the most important, the global runtimes seems to be slow (real figures comes next).

Here’s an extract of the Karma config.

module.exports = function (config) {
  config.set({
    basePath: '../',
    singleRun: true,
    autoWatch: false,
    files: [
      'node_modules/angular/angular.js',
      'node_modules/angular-mocks/angular-mocks.js',
      'src/app/mock/utils.js',
      'src/index.spec.js'
    ],
    frameworks: ['jasmine'],
    preprocessors: {
      'src/index.spec.js': ['webpack']
    },
    browsers: ['Chrome'],
    reporters: ['progress', 'coverage'],
    webpack: require('./webpack-test-coverage.conf'),
    webpackMiddleware: { noInfo: true },
    junitReporter: { outputDir: 'test-reports' },
    coverageReporter: {
      reporters: [
        {type: 'cobertura', dir:'test-coverage/', subdir:'.'},
        {type: 'lcov', dir:'test-coverage/', subdir:'.'}
      ]
    },
    plugins: [
      require('karma-jasmine'),
      require('karma-junit-reporter'),
      require('karma-coverage'),
      require('karma-chrome-launcher'),
      require('karma-webpack')
    ]
  });
};

Trying Jest

In this context, I read about jsdom and thought that it could be huge to remove the real browser part since pure JavaScript implementations differences are not an every day problem anymore. jsdom is a Node library which is able to simulate the context of a Browser with its API in a pure Node execution.

I was considering trying AVA with jsdom but also looked at the testing framework used by the React team at Facebook: Jest. I was bothered at first because it looked as a nice integration of jsdom for frontend tests but I couldn’t find a blog post about using it outside the React world. As I noticed that there was no reference of React on the Jest front page, I concluded that the solution means to be framework agnostic. So, I decided to give it a try.

The PoC process was incredibly simple. I was sure I would have to deal with some AngularJS dependency injection edge cases or with the Jest mocking system. For a reason or another, I was quite sure I would have to refactor, even for a simple thing, every single test.

In reality, I only had to point my source folder and write a tiny script which imported my tests common lib and my tests were passing.

The biggest part of this script was to mock localStorage API as it’s not yet supported in jsdom.

jest.init.js

var localStorageMock = /* ... some mock code ... */
Object.defineProperty(window, 'localStorage', {
  value: localStorageMock
});

import './app/index.module';
import 'angular-mocks';
import './app/mock/utils';

package.json

"jest": {
  "testPathDirs": ["src"],
  "setupTestFrameworkScriptFile": "src/jest.init.js"
}

Figures and facts

Even if I am pretty convinced by full plugin architectures like the Karma one, the drawback on the Karma configuration is obvious: 35 lines of code, not trivial for the Karma configuration against 4 lines in package.json for Jest. (I’m not counting the jest init script as Karma / Webpack needed one too).

According to coverage reports, the project counts 5917 JavaScript lines and 43.91% (2598) is covered (56000 actual source LoC). It could be better, for sure, but at least, it’s a real world example. There is also 103 tests suites and 363 tests.

Let us speak about execution times. For the execution of all tests once without coverage, Karma takes 47s in total. The bootstrap / bundling part is the most important as Chrome only takes 10s to execute tests once everything is ready. Jest, after a bit of learning (5 executions) only takes a total of 18s.

Yep! 47s for Karma against 18s for Jest! Same code, same tests.

With coverage, the bootstrap time of Karma is less problematic. Karma takes 100s against 80s for Jest.

The watch mode is harder to compare. Karma / Webpack has a strange delay on the change detection and triggers a new execution which is not measured. I manually counted 7s followed by a full pass at 10s. Using Jasmine fdescribe or fit, the run can fall at 1s. Jest works very differently, it only runs the modified test and takes approximatively 1s to detect the change and another one to execute it.

A final but important point is that with the stack Karma / Webpack / Babel we have never figured out a good solution to have a robust source-mapping in JavaScript stack traces. To be honest, sometimes, we had references, other times not. Jest is far more straightforward, The error reports are just perfect with only references to our Babel source code et good line numbers.

Conclusion

Jest popularity is shamefully restricted to the React world and really deserves to cross that frontier since it’s a very good agnostic solution for frontend testing.

I didn’t find any reference of Jest outside React before writing this article, I hope this will be the first.

I think, but some could disagree, that modern frontend unit testing doesn’t really need anymore real browser testing as most Web API are quite stable nowadays.

Configuration, performances and features are really good in Jest and the “Developer eXperience” is better than with Karma. And yet, I only used it as a test environment and didn’t played with the mocking features!

The only problem I see is that jsdom is not working right now with Zone.js and then not working with Angular 2. I really hope that it shall be fixed even if I don’t see motivation on any side (jsdom or Angular).

 

Flutter Local Notifications Plugin

[Fuente: https://pub.dev/packages/flutter_local_notifications#-readme-tab-]

A cross platform plugin for displaying local notifications.

Supported Platforms #

  • Android API 16+ (4.1+, the minimum version supported by Flutter). Uses the NotificationCompat APIs so it can be run older Android devices
  • iOS 8.0+ (the minimum version supported by Flutter). Supports the old and new iOS notification APIs (the User Notifications Framework introduced in iOS 10 but will use the UILocalNotification APIs for devices predating iOS 10)

Features #

  • Mockable (plugin and API methods aren’t static)
  • Display basic notifications
  • Scheduling when notifications should appear
  • Periodically show a notification (interval based)
  • Schedule a notification to be shown daily at a specified time
  • Schedule a notification to be shown weekly on a specified day and time
  • Retrieve a list of pending notification requests that have been scheduled to be shown in the future
  • Cancelling/removing notification by id or all of them
  • Specify a custom notification sound
  • Ability to handle when a user has tapped on a notification, when the app is the foreground, background or terminated
  • Determine if an app was launched due to tapping on a notification
  • [Android] Configuring the importance level
  • [Android] Configuring the priority
  • [Android] Customising the vibration pattern for notifications
  • [Android] Configure the default icon for all notifications
  • [Android] Configure the icon for each notification (overrides the default when specified)
  • [Android] Configure the large icon for each notification. The icon can be a drawable or a file on the device
  • [Android] Formatting notification content via HTML markup (see https://developer.android.com/guide/topics/resources/string-resource.html#StylingWithHTML)
  • [Android] Support for the following notification styles
    • Big picture
    • Big text
    • Inbox
    • Messaging
  • [Android] Group notifications
  • [Android] Show progress notifications
  • [iOS] Customise the permissions to be requested around displaying notifications

Note that this plugin aims to provide abstractions for all platforms as opposed to having methods that only work on specific platforms. However, each method allows passing in “platform-specifics” that contains data that is specific for customising notifications on each platform. It is still under development so expect the API surface to change over time.

IMPORTANT NOTES:

  • Recurring notifications on Android use the Alarm Manager API. This is standard practice but does mean the delivery of the notifications/alarms are inexact and this is documented Android behaviour as per the previous link. Note that it’s been reported that Samsung’s implementation of Android has imposed a maximum of 500 alarms that can be scheduled via this API and exceptioms can occur when going over the limit
  • iOS has a limit on how many pending notifications it allows. This is a limit imposed by iOS where it will only keep 64 notifications that will fire the soonest
  • Known issue: There is a known issue with handling daylight savings for scheduled notifications. This functionality may be deprecated to be replaced by another that only deals with elapsed time since epoch instead of a date.

Getting Started #

The GitHub repository has an example app that should demonstrate of all the supported features of the plugin. Please check the example for more detailed code samples. If you only copy and paste the Dart code then this will not work as there’s setup required for each platform. Pub also generates API docs for the latest version here

The following samples will demonstrate the more commonly used functionalities. The first step is to create a new instance of the plugin class and then initialise it with the settings to use for each platform

FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = new FlutterLocalNotificationsPlugin();
// initialise the plugin. app_icon needs to be a added as a drawable resource to the Android head project
var initializationSettingsAndroid =
    new AndroidInitializationSettings('app_icon');
var initializationSettingsIOS = new IOSInitializationSettings(
    onDidReceiveLocalNotification: onDidReceiveLocalNotification);
var initializationSettings = new InitializationSettings(
    initializationSettingsAndroid, initializationSettingsIOS);
flutterLocalNotificationsPlugin.initialize(initializationSettings,
    onSelectNotification: onSelectNotification);

Here we specify we have specified the default icon to use for notifications on Android (refer to the Android Integration section) and designated the function (onSelectNotification) that should fire when a notification has been tapped on. Specifying this callback is entirely optional. In this example, it will trigger navigation to another page and display the payload associated with the notification.

Future onSelectNotification(String payload) async {
    if (payload != null) {
      debugPrint('notification payload: ' + payload);
    }
    await Navigator.push(
      context,
      new MaterialPageRoute(builder: (context) => new SecondScreen(payload)),
    );
}

In the real world, this payload could represent the id of the item you want to display the details of. Once the initialisation has been done, then you can manage the displaying of notifications.

Displaying a notification #

var androidPlatformChannelSpecifics = AndroidNotificationDetails(
    'your channel id', 'your channel name', 'your channel description',
    importance: Importance.Max, priority: Priority.High, ticker: 'ticker');
var iOSPlatformChannelSpecifics = IOSNotificationDetails();
var platformChannelSpecifics = NotificationDetails(
    androidPlatformChannelSpecifics, iOSPlatformChannelSpecifics);
await flutterLocalNotificationsPlugin.show(
    0, 'plain title', 'plain body', platformChannelSpecifics,
    payload: 'item x');

In this block of code, the details for each platform have been specified. This includes the channel details that is required for Android 8.0+. The payload has been specified (‘item id 2’), that will passed back through your application when the user has tapped on a notification. Note that for Android devices that notifications will only in appear in the tray and won’t appear as a toast aka heads-up notification unless things like the priority/importance has been set appropriately. Refer to the Android docs (https://developer.android.com/guide/topics/ui/notifiers/notifications.html#Heads-up) for additional information. Note that the “ticker” text is passed here though it is optional and specific to Android. This allows for text to be shown in the status bar on older versions of Android when the notification is shown.

Scheduling a notification #

var scheduledNotificationDateTime =
        new DateTime.now().add(new Duration(seconds: 5));
var androidPlatformChannelSpecifics =
    new AndroidNotificationDetails('your other channel id',
        'your other channel name', 'your other channel description');
var iOSPlatformChannelSpecifics =
    new IOSNotificationDetails();
NotificationDetails platformChannelSpecifics = new NotificationDetails(
    androidPlatformChannelSpecifics, iOSPlatformChannelSpecifics);
await flutterLocalNotificationsPlugin.schedule(
    0,
    'scheduled title',
    'scheduled body',
    scheduledNotificationDateTime,
    platformChannelSpecifics);

Periodically show a notification with a specified interval #

// Show a notification every minute with the first appearance happening a minute after invoking the method
var androidPlatformChannelSpecifics =
    new AndroidNotificationDetails('repeating channel id',
        'repeating channel name', 'repeating description');
var iOSPlatformChannelSpecifics =
    new IOSNotificationDetails();
var platformChannelSpecifics = new NotificationDetails(
    androidPlatformChannelSpecifics, iOSPlatformChannelSpecifics);
await flutterLocalNotificationsPlugin.periodicallyShow(0, 'repeating title',
    'repeating body', RepeatInterval.EveryMinute, platformChannelSpecifics);

Show a daily notification at a specific time #

var time = new Time(10, 0, 0);
var androidPlatformChannelSpecifics =
    new AndroidNotificationDetails('repeatDailyAtTime channel id',
        'repeatDailyAtTime channel name', 'repeatDailyAtTime description');
var iOSPlatformChannelSpecifics =
    new IOSNotificationDetails();
var platformChannelSpecifics = new NotificationDetails(
    androidPlatformChannelSpecifics, iOSPlatformChannelSpecifics);
await flutterLocalNotificationsPlugin.showDailyAtTime(
    0,
    'show daily title',
    'Daily notification shown at approximately ${_toTwoDigitString(time.hour)}:${_toTwoDigitString(time.minute)}:${_toTwoDigitString(time.second)}',
    time,
    platformChannelSpecifics);

Show a weekly notification on specific day and time #

var time = new Time(10, 0, 0);
var androidPlatformChannelSpecifics =
    new AndroidNotificationDetails('show weekly channel id',
        'show weekly channel name', 'show weekly description');
var iOSPlatformChannelSpecifics =
    new IOSNotificationDetails();
var platformChannelSpecifics = new NotificationDetails(
    androidPlatformChannelSpecifics, iOSPlatformChannelSpecifics);
await flutterLocalNotificationsPlugin.showWeeklyAtDayAndTime(
    0,
    'show weekly title',
    'Weekly notification shown on Monday at approximately ${_toTwoDigitString(time.hour)}:${_toTwoDigitString(time.minute)}:${_toTwoDigitString(time.second)}',
    Day.Monday,
    time,
    platformChannelSpecifics);

Retrieve pending notification requests #

var pendingNotificationRequests =
        await flutterLocalNotificationsPlugin.pendingNotificationRequests();
  • README.md
  • CHANGELOG.md
  • Example
  • Installing
  • Versions
  • 99

Flutter Local Notifications Plugin #

pub package

A cross platform plugin for displaying local notifications.

Supported Platforms #

  • Android API 16+ (4.1+, the minimum version supported by Flutter). Uses the NotificationCompat APIs so it can be run older Android devices
  • iOS 8.0+ (the minimum version supported by Flutter). Supports the old and new iOS notification APIs (the User Notifications Framework introduced in iOS 10 but will use the UILocalNotification APIs for devices predating iOS 10)

Features #

  • Mockable (plugin and API methods aren’t static)
  • Display basic notifications
  • Scheduling when notifications should appear
  • Periodically show a notification (interval based)
  • Schedule a notification to be shown daily at a specified time
  • Schedule a notification to be shown weekly on a specified day and time
  • Retrieve a list of pending notification requests that have been scheduled to be shown in the future
  • Cancelling/removing notification by id or all of them
  • Specify a custom notification sound
  • Ability to handle when a user has tapped on a notification, when the app is the foreground, background or terminated
  • Determine if an app was launched due to tapping on a notification
  • [Android] Configuring the importance level
  • [Android] Configuring the priority
  • [Android] Customising the vibration pattern for notifications
  • [Android] Configure the default icon for all notifications
  • [Android] Configure the icon for each notification (overrides the default when specified)
  • [Android] Configure the large icon for each notification. The icon can be a drawable or a file on the device
  • [Android] Formatting notification content via HTML markup (see https://developer.android.com/guide/topics/resources/string-resource.html#StylingWithHTML)
  • [Android] Support for the following notification styles
    • Big picture
    • Big text
    • Inbox
    • Messaging
  • [Android] Group notifications
  • [Android] Show progress notifications
  • [iOS] Customise the permissions to be requested around displaying notifications

Note that this plugin aims to provide abstractions for all platforms as opposed to having methods that only work on specific platforms. However, each method allows passing in “platform-specifics” that contains data that is specific for customising notifications on each platform. It is still under development so expect the API surface to change over time.

IMPORTANT NOTES:

  • Recurring notifications on Android use the Alarm Manager API. This is standard practice but does mean the delivery of the notifications/alarms are inexact and this is documented Android behaviour as per the previous link. Note that it’s been reported that Samsung’s implementation of Android has imposed a maximum of 500 alarms that can be scheduled via this API and exceptioms can occur when going over the limit
  • iOS has a limit on how many pending notifications it allows. This is a limit imposed by iOS where it will only keep 64 notifications that will fire the soonest
  • Known issue: There is a known issue with handling daylight savings for scheduled notifications. This functionality may be deprecated to be replaced by another that only deals with elapsed time since epoch instead of a date.

Acknowledgements #

  • Javier Lecuona for submitting the PR that added the ability to have notifications shown daily
  • Jeff Scaturro for submitting the PR to fix the iOS issue around showing daily and weekly notifications and migrating the plugin to AndroidX
  • Ian Cavanaugh for helping create a sample to reproduce the problem reported in issue #88
  • Zhang Jing for adding ‘ticker’ support for Android notifications
  • …and everyone else for their contributions. They are greatly appreciated

Raising issues and contributions #

If you run into issues, please raise them on the GitHub repository. Please do not email them to me as I will be ignoring emails going forward as GitHub is the appropriate place for them. It would also be much appreciated if they could be limited to actual bugs or feature requests. If you’re looking at how you could use the plugin to do a particular kind of notification, check the example app provides detailed code samples for each supported feature. Also try to check the README first in case you have missed something e.g. platform-specific setup.

Contributions are welcome by submitting a PR for me to review. If it’s to add new features, appreciate it if you could try to maintain the architecture or try to improve on it. However, do note that I will not take PRs that add methods at the Dart level that don’t work on all platforms. However, platform-specific configuration through the use parameters are fine as that’s approach being taken via this plugin.

Getting Started #

The GitHub repository has an example app that should demonstrate of all the supported features of the plugin. Please check the example for more detailed code samples. If you only copy and paste the Dart code then this will not work as there’s setup required for each platform. Pub also generates API docs for the latest version here

The following samples will demonstrate the more commonly used functionalities. The first step is to create a new instance of the plugin class and then initialise it with the settings to use for each platform

FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = new FlutterLocalNotificationsPlugin();
// initialise the plugin. app_icon needs to be a added as a drawable resource to the Android head project
var initializationSettingsAndroid =
    new AndroidInitializationSettings('app_icon');
var initializationSettingsIOS = new IOSInitializationSettings(
    onDidReceiveLocalNotification: onDidReceiveLocalNotification);
var initializationSettings = new InitializationSettings(
    initializationSettingsAndroid, initializationSettingsIOS);
flutterLocalNotificationsPlugin.initialize(initializationSettings,
    onSelectNotification: onSelectNotification);

Here we specify we have specified the default icon to use for notifications on Android (refer to the Android Integration section) and designated the function (onSelectNotification) that should fire when a notification has been tapped on. Specifying this callback is entirely optional. In this example, it will trigger navigation to another page and display the payload associated with the notification.

Future onSelectNotification(String payload) async {
    if (payload != null) {
      debugPrint('notification payload: ' + payload);
    }
    await Navigator.push(
      context,
      new MaterialPageRoute(builder: (context) => new SecondScreen(payload)),
    );
}

In the real world, this payload could represent the id of the item you want to display the details of. Once the initialisation has been done, then you can manage the displaying of notifications.

Displaying a notification #

var androidPlatformChannelSpecifics = AndroidNotificationDetails(
    'your channel id', 'your channel name', 'your channel description',
    importance: Importance.Max, priority: Priority.High, ticker: 'ticker');
var iOSPlatformChannelSpecifics = IOSNotificationDetails();
var platformChannelSpecifics = NotificationDetails(
    androidPlatformChannelSpecifics, iOSPlatformChannelSpecifics);
await flutterLocalNotificationsPlugin.show(
    0, 'plain title', 'plain body', platformChannelSpecifics,
    payload: 'item x');

In this block of code, the details for each platform have been specified. This includes the channel details that is required for Android 8.0+. The payload has been specified (‘item id 2’), that will passed back through your application when the user has tapped on a notification. Note that for Android devices that notifications will only in appear in the tray and won’t appear as a toast aka heads-up notification unless things like the priority/importance has been set appropriately. Refer to the Android docs (https://developer.android.com/guide/topics/ui/notifiers/notifications.html#Heads-up) for additional information. Note that the “ticker” text is passed here though it is optional and specific to Android. This allows for text to be shown in the status bar on older versions of Android when the notification is shown.

Scheduling a notification #

var scheduledNotificationDateTime =
        new DateTime.now().add(new Duration(seconds: 5));
var androidPlatformChannelSpecifics =
    new AndroidNotificationDetails('your other channel id',
        'your other channel name', 'your other channel description');
var iOSPlatformChannelSpecifics =
    new IOSNotificationDetails();
NotificationDetails platformChannelSpecifics = new NotificationDetails(
    androidPlatformChannelSpecifics, iOSPlatformChannelSpecifics);
await flutterLocalNotificationsPlugin.schedule(
    0,
    'scheduled title',
    'scheduled body',
    scheduledNotificationDateTime,
    platformChannelSpecifics);

Periodically show a notification with a specified interval #

// Show a notification every minute with the first appearance happening a minute after invoking the method
var androidPlatformChannelSpecifics =
    new AndroidNotificationDetails('repeating channel id',
        'repeating channel name', 'repeating description');
var iOSPlatformChannelSpecifics =
    new IOSNotificationDetails();
var platformChannelSpecifics = new NotificationDetails(
    androidPlatformChannelSpecifics, iOSPlatformChannelSpecifics);
await flutterLocalNotificationsPlugin.periodicallyShow(0, 'repeating title',
    'repeating body', RepeatInterval.EveryMinute, platformChannelSpecifics);

Show a daily notification at a specific time #

var time = new Time(10, 0, 0);
var androidPlatformChannelSpecifics =
    new AndroidNotificationDetails('repeatDailyAtTime channel id',
        'repeatDailyAtTime channel name', 'repeatDailyAtTime description');
var iOSPlatformChannelSpecifics =
    new IOSNotificationDetails();
var platformChannelSpecifics = new NotificationDetails(
    androidPlatformChannelSpecifics, iOSPlatformChannelSpecifics);
await flutterLocalNotificationsPlugin.showDailyAtTime(
    0,
    'show daily title',
    'Daily notification shown at approximately ${_toTwoDigitString(time.hour)}:${_toTwoDigitString(time.minute)}:${_toTwoDigitString(time.second)}',
    time,
    platformChannelSpecifics);

Show a weekly notification on specific day and time #

var time = new Time(10, 0, 0);
var androidPlatformChannelSpecifics =
    new AndroidNotificationDetails('show weekly channel id',
        'show weekly channel name', 'show weekly description');
var iOSPlatformChannelSpecifics =
    new IOSNotificationDetails();
var platformChannelSpecifics = new NotificationDetails(
    androidPlatformChannelSpecifics, iOSPlatformChannelSpecifics);
await flutterLocalNotificationsPlugin.showWeeklyAtDayAndTime(
    0,
    'show weekly title',
    'Weekly notification shown on Monday at approximately ${_toTwoDigitString(time.hour)}:${_toTwoDigitString(time.minute)}:${_toTwoDigitString(time.second)}',
    Day.Monday,
    time,
    platformChannelSpecifics);

Retrieve pending notification requests #

var pendingNotificationRequests =
        await flutterLocalNotificationsPlugin.pendingNotificationRequests();

[Android only] Grouping notifications

This is a “translation” of the sample available at https://developer.android.com/training/notify-user/group.html For iOS, you could just display the summary notification (not shown in the example) as otherwise the following code would show three notifications

String groupKey = 'com.android.example.WORK_EMAIL';
String groupChannelId = 'grouped channel id';
String groupChannelName = 'grouped channel name';
String groupChannelDescription = 'grouped channel description';
// example based on https://developer.android.com/training/notify-user/group.html
AndroidNotificationDetails firstNotificationAndroidSpecifics =
    new AndroidNotificationDetails(
        groupChannelId, groupChannelName, groupChannelDescription,
        importance: Importance.Max,
        priority: Priority.High,
        groupKey: groupKey);
NotificationDetails firstNotificationPlatformSpecifics =
    new NotificationDetails(firstNotificationAndroidSpecifics, null);
await flutterLocalNotificationsPlugin.show(1, 'Alex Faarborg',
    'You will not believe...', firstNotificationPlatformSpecifics);
AndroidNotificationDetails secondNotificationAndroidSpecifics =
    new AndroidNotificationDetails(
        groupChannelId, groupChannelName, groupChannelDescription,
        importance: Importance.Max,
        priority: Priority.High,
        groupKey: groupKey);
NotificationDetails secondNotificationPlatformSpecifics =
    new NotificationDetails(secondNotificationAndroidSpecifics, null);
await flutterLocalNotificationsPlugin.show(
    2,
    'Jeff Chang',
    'Please join us to celebrate the...',
    secondNotificationPlatformSpecifics);

// create the summary notification required for older devices that pre-date Android 7.0 (API level 24)
List<String> lines = new List<String>();
lines.add('Alex Faarborg  Check this out');
lines.add('Jeff Chang    Launch Party');
InboxStyleInformation inboxStyleInformation = new InboxStyleInformation(
    lines,
    contentTitle: '2 new messages',
    summaryText: 'janedoe@example.com');
AndroidNotificationDetails androidPlatformChannelSpecifics =
    new AndroidNotificationDetails(
        groupChannelId, groupChannelName, groupChannelDescription,
        style: NotificationStyleAndroid.Inbox,
        styleInformation: inboxStyleInformation,
        groupKey: groupKey,
        setAsGroupSummary: true);
NotificationDetails platformChannelSpecifics =
    new NotificationDetails(androidPlatformChannelSpecifics, null);
await flutterLocalNotificationsPlugin.show(
    3, 'Attention', 'Two new messages', platformChannelSpecifics);

Cancelling/deleting a notification #

// cancel the notification with id value of zero
await flutterLocalNotificationsPlugin.cancel(0);

Cancelling/deleting all notifications #

await flutterLocalNotificationsPlugin.cancelAll();

Get details on if the app was launched via a notification #

 var notificationAppLaunchDetails =
     await flutterLocalNotificationsPlugin.getNotificationAppLaunchDetails();

This should cover the basic functionality. Please check out the example directory for a sample app that illustrates the rest of the functionality available and refer to the API docs for more information. Also read the below on what you need to configure on each platform

 

Android Integration #

If your application needs the ability to schedule notifications then you need to request permissions to be notified when the phone has been booted as scheduled notifications uses the AlarmManager API to determine when notifications should be displayed. However, they are cleared when a phone has been turned off. Requesting permission requires adding the following to the manifest

<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>

The following is also needed to ensure scheduled notifications remain scheduled upon a reboot (this is handled by the plugin)

<receiver android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationBootReceiver">
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED"></action>
    </intent-filter>
</receiver>

Developers will also need to add the following so that plugin can handle displaying scheduled notifications

<receiver android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationReceiver" />

If the vibration pattern of an Android notification will be customised then add the following

<uses-permission android:name="android.permission.VIBRATE" />

Notification icons should be added as a drawable resource. The example project/code shows how to set default icon for all notifications and how to specify one for each notification. It is possible to use launcher icon/mipmap and this by default is @mipmap/ic_launcher in the Android manifest and can be passed AndroidInitializationSettings constructor. However, the official Android guidance is that you should use drawable resources. Custom notification sounds should be added as a raw resource and the sample illustrates how to play a notification with a custom sound. Refer to the following links around Android resources and notification icons.

When specifying the large icon bitmap or big picture bitmap (associated with the big picture style), bitmaps can be either a drawable resource or file on the device. This is specified via a single property (e.g. the largeIcon property associated with the AndroidNotificationDetails class) and there will be a corresponding property of the BitmapSource enum type (e.g. largeIconBitmapSource) that indicates if the string value represents the name of the drawable resource or the path to the bitmap file.

Note that with Android 8.0+, sounds and vibrations are associated with notification channels and can only be configured when they are first created. Showing/scheduling a notification will create a channel with the specified id if it doesn’t exist already. If another notification specifies the same channel id but tries to specify another sound or vibration pattern then nothing occurs.

When doing a release build of your app, you’ll likely need to customise your ProGuard configuration file as per this link and add the following line

-keep class com.dexterous.** { *; }

IMPORTANT: Starting from version 0.5.0, this library no longer uses the deprecated Android support libraries and has migrated to AndroidX. Developers may require migrating their apps to support this following this guide

fd
fd
fd

Angular Observables

[fuente: https://angular.io/guide/observables]

Observables

Observables provide support for passing messages between publishers and subscribers in your application. Observables offer significant benefits over other techniques for event handling, asynchronous programming, and handling multiple values.

Observables are declarative—that is, you define a function for publishing values, but it is not executed until a consumer subscribes to it. The subscribed consumer then receives notifications until the function completes, or until they unsubscribe.

An observable can deliver multiple values of any type—literals, messages, or events, depending on the context. The API for receiving values is the same whether the values are delivered synchronously or asynchronously. Because setup and teardown logic are both handled by the observable, your application code only needs to worry about subscribing to consume values, and when done, unsubscribing. Whether the stream was keystrokes, an HTTP response, or an interval timer, the interface for listening to values and stopping listening is the same.

Because of these advantages, observables are used extensively within Angular, and are recommended for app development as well.

Basic usage and terms

As a publisher, you create an Observable instance that defines a subscriber function. This is the function that is executed when a consumer calls the subscribe() method. The subscriber function defines how to obtain or generate values or messages to be published.

To execute the observable you have created and begin receiving notifications, you call its subscribe() method, passing an observer. This is a JavaScript object that defines the handlers for the notifications you receive. The subscribe() call returns a Subscription object that has an unsubscribe() method, which you call to stop receiving notifications.

Here’s an example that demonstrates the basic usage model by showing how an observable could be used to provide geolocation updates.

// Create an Observable that will start listening to geolocation updates
// when a consumer subscribes.
const locations = new Observable((observer) => {
  // Get the next and error callbacks. These will be passed in when
  // the consumer subscribes.
  const {next, error} = observer;
  let watchId;

  // Simple geolocation API check provides values to publish
  if ('geolocation' in navigator) {
    watchId = navigator.geolocation.watchPosition(next, error);
  } else {
    error('Geolocation not available');
  }

  // When the consumer unsubscribes, clean up data ready for next subscription.
  return {unsubscribe() { navigator.geolocation.clearWatch(watchId); }};
});

// Call subscribe() to start listening for updates.
const locationsSubscription = locations.subscribe({
  next(position) { console.log('Current Position: ', position); },
  error(msg) { console.log('Error Getting Location: ', msg); }
});

// Stop listening for location after 10 seconds
setTimeout(() => { locationsSubscription.unsubscribe(); }, 10000);

Defining observers

A handler for receiving observable notifications implements the Observer interface. It is an object that defines callback methods to handle the three types of notifications that an observable can send:

NOTIFICATION TYPE DESCRIPTION
next Required. A handler for each delivered value. Called zero or more times after execution starts.
error Optional. A handler for an error notification. An error halts execution of the observable instance.
complete Optional. A handler for the execution-complete notification. Delayed values can continue to be delivered to the next handler after execution is complete.

An observer object can define any combination of these handlers. If you don’t supply a handler for a notification type, the observer ignores notifications of that type.

Subscribing

An Observable instance begins publishing values only when someone subscribes to it. You subscribe by calling the subscribe() method of the instance, passing an observer object to receive the notifications.

In order to show how subscribing works, we need to create a new observable. There is a constructor that you use to create new instances, but for illustration, we can use some methods from the RxJS library that create simple observables of frequently used types:

  • of(...items)Returns an Observable instance that synchronously delivers the values provided as arguments.
  • from(iterable)Converts its argument to an Observable instance. This method is commonly used to convert an array to an observable.

Here’s an example of creating and subscribing to a simple observable, with an observer that logs the received message to the console:

// Create simple observable that emits three values
const myObservable = of(1, 2, 3);

// Create observer object
const myObserver = {
  next: x => console.log('Observer got a next value: ' + x),
  error: err => console.error('Observer got an error: ' + err),
  complete: () => console.log('Observer got a complete notification'),
};

// Execute with the observer object
myObservable.subscribe(myObserver);
// Logs:
// Observer got a next value: 1
// Observer got a next value: 2
// Observer got a next value: 3
// Observer got a complete notification

Alternatively, the subscribe() method can accept callback function definitions in line, for nexterror, and completehandlers. For example, the following subscribe() call is the same as the one that specifies the predefined observer:

myObservable.subscribe(
  x => console.log('Observer got a next value: ' + x),
  err => console.error('Observer got an error: ' + err),
  () => console.log('Observer got a complete notification')
);

In either case, a next handler is required. The error and complete handlers are optional.

Note that a next() function could receive, for instance, message strings, or event objects, numeric values, or structures, depending on context. As a general term, we refer to data published by an observable as a stream. Any type of value can be represented with an observable, and the values are published as a stream.

more in the fuente!!

 

Parent and children communicate via a service

A parent component and its children share a service whose interface enables bi-directional communication within the family.

The scope of the service instance is the parent component and its children. Components outside this component subtree have no access to the service or their communications.

This MissionService connects the MissionControlComponent to multiple AstronautComponent children.

import { Injectable } from '@angular/core';
import { Subject }    from 'rxjs';

@Injectable()
export class MissionService {

  // Observable string sources
  private missionAnnouncedSource = new Subject<string>();
  private missionConfirmedSource = new Subject<string>();

  // Observable string streams
  missionAnnounced$ = this.missionAnnouncedSource.asObservable();
  missionConfirmed$ = this.missionConfirmedSource.asObservable();

  // Service message commands
  announceMission(mission: string) {
    this.missionAnnouncedSource.next(mission);
  }

  confirmMission(astronaut: string) {
    this.missionConfirmedSource.next(astronaut);
  }
}

The MissionControlComponent both provides the instance of the service that it shares with its children (through the providers metadata array) and injects that instance into itself through its constructor:

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

import { MissionService }     from './mission.service';

@Component({
  selector: 'app-mission-control',
  template: `
    <h2>Mission Control</h2>
    <button (click)="announce()">Announce mission</button>
    <app-astronaut *ngFor="let astronaut of astronauts"
      [astronaut]="astronaut">
    </app-astronaut>
    <h3>History</h3>
    <ul>
      <li *ngFor="let event of history">{{event}}</li>
    </ul>
  `,
  providers: [MissionService]
})
export class MissionControlComponent {
  astronauts = ['Lovell', 'Swigert', 'Haise'];
  history: string[] = [];
  missions = ['Fly to the moon!',
              'Fly to mars!',
              'Fly to Vegas!'];
  nextMission = 0;

  constructor(private missionService: MissionService) {
    missionService.missionConfirmed$.subscribe(
      astronaut => {
        this.history.push(`${astronaut} confirmed the mission`);
      });
  }

  announce() {
    let mission = this.missions[this.nextMission++];
    this.missionService.announceMission(mission);
    this.history.push(`Mission "${mission}" announced`);
    if (this.nextMission >= this.missions.length) { this.nextMission = 0; }
  }
}

The AstronautComponent also injects the service in its constructor. Each AstronautComponent is a child of the MissionControlComponent and therefore receives its parent’s service instance:

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

import { MissionService } from './mission.service';
import { Subscription }   from 'rxjs';

@Component({
  selector: 'app-astronaut',
  template: `
    <p>
      {{astronaut}}: <strong>{{mission}}</strong>
      <button
        (click)="confirm()"
        [disabled]="!announced || confirmed">
        Confirm
      </button>
    </p>
  `
})
export class AstronautComponent implements OnDestroy {
  @Input() astronaut: string;
  mission = '<no mission announced>';
  confirmed = false;
  announced = false;
  subscription: Subscription;

  constructor(private missionService: MissionService) {
    this.subscription = missionService.missionAnnounced$.subscribe(
      mission => {
        this.mission = mission;
        this.announced = true;
        this.confirmed = false;
    });
  }

  confirm() {
    this.confirmed = true;
    this.missionService.confirmMission(this.astronaut);
  }

  ngOnDestroy() {
    // prevent memory leak when component destroyed
    this.subscription.unsubscribe();
  }
}

Notice that this example captures the subscription and unsubscribe() when the AstronautComponent is destroyed. This is a memory-leak guard step. There is no actual risk in this app because the lifetime of a AstronautComponent is the same as the lifetime of the app itself. That would not always be true in a more complex application.

You don’t add this guard to the MissionControlComponent because, as the parent, it controls the lifetime of the MissionService.

The History log demonstrates that messages travel in both directions between the parent MissionControlComponent and the AstronautComponent children, facilitated by the service:

bidirectional-service

Test it

Tests click buttons of both the parent MissionControlComponent and the AstronautComponent children and verify that the history meets expectations:

// ...
it('should announce a mission', function () {
  let missionControl = element(by.tagName('app-mission-control'));
  let announceButton = missionControl.all(by.tagName('button')).get(0);
  announceButton.click().then(function () {
    let history = missionControl.all(by.tagName('li'));
    expect(history.count()).toBe(1);
    expect(history.get(0).getText()).toMatch(/Mission.* announced/);
  });
});

it('should confirm the mission by Lovell', function () {
  testConfirmMission(1, 2, 'Lovell');
});

it('should confirm the mission by Haise', function () {
  testConfirmMission(3, 3, 'Haise');
});

it('should confirm the mission by Swigert', function () {
  testConfirmMission(2, 4, 'Swigert');
});

function testConfirmMission(buttonIndex: number, expectedLogCount: number, astronaut: string) {
  let _confirmedLog = ' confirmed the mission';
  let missionControl = element(by.tagName('app-mission-control'));
  let confirmButton = missionControl.all(by.tagName('button')).get(buttonIndex);
  confirmButton.click().then(function () {
    let history = missionControl.all(by.tagName('li'));
    expect(history.count()).toBe(expectedLogCount);
    expect(history.get(expectedLogCount - 1).getText()).toBe(astronaut + _confirmedLog);
  });
}
// ...

 

Create a responsive Angular D3 charts

[fuente: https://school.geekwall.in/p/SJ52XGRWX/create-a-responsive-angular-d3-charts]

While the landscape of frameworks available for structuring and building web applications is changing by the minute, D3 is still the recognized way to create visualizations using Javascript. In this tutorial, we will add a D3 chart to an Angular application and make the size of the graph dynamic.

Note: You can find the finished source code here.

Creating the Angular project

The first step is to create a new Angular project using the CLI, and to add the d3 library to it:

ng new angular-d3
npm install d3 --save
npm install @types/d3 --save-dev

Next, we will create the component that we will work with:

ng generate component bar-chart

Finally, we will replace the content of ‘src/app/app.component.html’ with the following:

<h1>Bar Chart</h1>
<app-bar-chart></app-bar-chart>

Loading and passing data

In this tutorial, we will use this bar chart from Mike Bostock as the D3 visualization. You can find the data in JSON format here, and we will put it in a new asset file at ‘src/assets/data.json’.

We can also create an interface for the data points, in a new file ‘src/app/data/data.model.ts’:

export interface DataModel {
  letter: string;
  frequency: number;
}

To load this data, we can modify the ‘src/app/app.component.ts’ file like this:
angular-d3-app.component.ts

import { HttpClient } from '@angular/common/http';
import { Component } from '@angular/core';
import { Observable } from 'rxjs';
import { DataModel } from 'src/data/data.model';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  data: Observable<DataModel>;

  constructor(private http: HttpClient) {
    this.data = this.http.get<DataModel>('data/data.json');
  }
}

For HttpClient to work, we need to add HttpClientModule to our App NgModule imports in ‘src/app/app.module.ts’.

Finally, we can pass the data to our chart component by modifying ‘src/app/app.component.html’:

<h1>Bar Chart</h1>
<app-bar-chart [data]=”data | async”></app-bar-chart>

Angular 7 Lazy loading

[Fuente: https://angular.io/guide/lazy-loading-ngmodules]

Lazy Loading Feature Modules

——– PREVIOUS start——-

Feature Modules

Feature modules are NgModules for the purpose of organizing code.

As your app grows, you can organize code relevant for a specific feature. This helps apply clear boundaries for features. With feature modules, you can keep code related to a specific functionality or feature separate from other code. Delineating areas of your app helps with collaboration between developers and teams, separating directives, and managing the size of the root module.

Feature modules vs. root modules

A feature module is an organizational best practice, as opposed to a concept of the core Angular API. A feature module delivers a cohesive set of functionality focused on a specific application need such as a user workflow, routing, or forms. While you can do everything within the root module, feature modules help you partition the app into focused areas. A feature module collaborates with the root module and with other modules through the services it provides and the components, directives, and pipes that it shares.

Types of Feature Modules

There are five general categories of feature modules which tend to fall into the following groups:

  • Domain feature modules.
  • Routed feature modules.
  • Routing modules.
  • Service feature modules.
  • Widget feature modules.

While the following guidelines describe the use of each type and their typical characteristics, in real world apps, you may see hybrids.

Domain feature modules

  • Domain feature modules deliver a user experience dedicated to a particular application domain like editing a customer or placing an order.
  • They typically have a top component that acts as the feature root and private, supporting sub-components descend from it.
  • Domain feature modules consist mostly of declarations. Only the top component is exported.
  • Domain feature modules rarely have providers. When they do, the lifetime of the provided services should be the same as the lifetime of the module.
  • Domain feature modules are typically imported exactly once by a larger feature module.
  • They might be imported by the root AppModule of a small application that lacks routing.

Routed feature modules

  • Routed feature modules are domain feature modules whose top components are the targets of router navigation routes.
  • All lazy-loaded modules are routed feature modules by definition.
  • Routed feature modules don’t export anything because their components never appear in the template of an external component.
  • A lazy-loaded routed feature module should not be imported by any module. Doing so would trigger an eager load, defeating the purpose of lazy loading.That means you won’t see them mentioned among the AppModule imports. An eager loaded routed feature module must be imported by another module so that the compiler learns about its components.
  • Routed feature modules rarely have providers for reasons explained in Lazy Loading Feature Modules. When they do, the lifetime of the provided services should be the same as the lifetime of the module. Don’t provide application-wide singleton services in a routed feature module or in a module that the routed module imports.

Routing modules

A routing module provides routing configuration for another module and separates routing concerns from its companion module.

A routing module typically does the following:

  • Defines routes.
  • Adds router configuration to the module’s imports.
  • Adds guard and resolver service providers to the module’s providers.
  • The name of the routing module should parallel the name of its companion module, using the suffix “Routing”. For example, FooModule in foo.module.ts has a routing module named FooRoutingModule in foo-routing.module.ts. If the companion module is the root AppModule, the AppRoutingModule adds router configuration to its imports with RouterModule.forRoot(routes). All other routing modules are children that import RouterModule.forChild(routes).
  • A routing module re-exports the RouterModule as a convenience so that components of the companion module have access to router directives such as RouterLink and RouterOutlet.
  • A routing module does not have its own declarations. Components, directives, and pipes are the responsibility of the feature module, not the routing module.

A routing module should only be imported by its companion module.

Service feature modules

Service modules provide utility services such as data access and messaging. Ideally, they consist entirely of providers and have no declarations. Angular’s HttpClientModule is a good example of a service module.

The root AppModule is the only module that should import service modules.

Widget feature modules

  • A widget module makes components, directives, and pipes available to external modules. Many third-party UI component libraries are widget modules.
  • A widget module should consist entirely of declarations, most of them exported.
  • A widget module should rarely have providers.
  • Import widget modules in any module whose component templates need the widgets.

——– PREVIOUS end——-

Prerequisites

A basic understanding of the following:

For the final sample app with two lazy loaded modules that this page describes, see the live example / download example.


High level view

There are three main steps to setting up a lazy loaded feature module:

  1. Create the feature module.
  2. Create the feature module’s routing module.
  3. Configure the routes.

Set up an app

If you don’t already have an app, you can follow the steps below to create one with the CLI. If you do already have an app, skip to Configure the routes. Enter the following command where customer-app is the name of your app:

ng new customer-app --routing

This creates an app called customer-app and the --routing flag generates a file called app-routing.module.ts, which is one of the files you need for setting up lazy loading for your feature module. Navigate into the project by issuing the command cd customer-app.

Create a feature module with routing

Next, you’ll need a feature module to route to. To make one, enter the following command at the terminal window prompt where customers is the name of the module:

ng generate module customers --routing

This creates a customers folder with two files inside; CustomersModule and CustomersRoutingModuleCustomersModulewill act as the gatekeeper for anything that concerns customers. CustomersRoutingModule will handle any customer-related routing. This keeps the app’s structure organized as the app grows and allows you to reuse this module while easily keeping its routing intact.

The CLI imports the CustomersRoutingModule into the CustomersModule by adding a JavaScript import statement at the top of the file and adding CustomersRoutingModule to the @NgModule imports array.

Add a component to the feature module

In order to see the module being lazy loaded in the browser, create a component to render some HTML when the app loads CustomersModule. At the command line, enter the following:

ng generate component customers/customer-list

This creates a folder inside of customers called customer-list with the four files that make up the component.

Just like with the routing module, the CLI imports the CustomerListComponent into the CustomersModule.

Add another feature module

For another place to route to, create a second feature module with routing:

ng generate module orders --routing

This makes a new folder called orders containing an OrdersModule and an OrdersRoutingModule.

Now, just like with the CustomersModule, give it some content:

ng generate component orders/order-list

Set up the UI

Though you can type the URL into the address bar, a nav is easier for the user and more common. Replace the default placeholder markup in app.component.html with a custom nav so you can easily navigate to your modules in the browser:

<h1>
  {{title}}
</h1>

<button routerLink="/customers">Customers</button>
<button routerLink="/orders">Orders</button>
<button routerLink="">Home</button>

<router-outlet></router-outlet>

To make the buttons work, you need to configure the routing modules.

Configure the routes

The two feature modules, OrdersModule and CustomersModule, have to be wired up to the AppRoutingModule so the router knows about them. The structure is as follows:

lazy loaded modules diagram

Each feature module acts as a doorway via the router. In the AppRoutingModule, you configure the routes to the feature modules, in this case OrdersModule and CustomersModule. This way, the router knows to go to the feature module. The feature module then connects the AppRoutingModule to the CustomersRoutingModule or the OrdersRoutingModule. Those routing modules tell the router where to go to load relevant components.

Routes at the app level

In AppRoutingModule, update the routes array with the following:

const routes: Routes = [
  {
    path: 'customers',
    loadChildren: './customers/customers.module#CustomersModule'
  },
  {
    path: 'orders',
    loadChildren: './orders/orders.module#OrdersModule'
  },
  {
    path: '',
    redirectTo: '',
    pathMatch: 'full'
  }
];

The import statements stay the same. The first two paths are the routes to the CustomersModule and the OrdersModulerespectively. Notice that the lazy loading syntax uses loadChildren followed by a string that is the relative path to the module, a hash mark or #, and the module’s class name.

Inside the feature module

Next, take a look at customers.module.ts. If you’re using the CLI and following the steps outlined in this page, you don’t have to do anything here. The feature module is like a connector between the AppRoutingModule and the feature routing module. The AppRoutingModule imports the feature module, CustomersModule, and CustomersModule in turn imports the CustomersRoutingModule.

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { CustomersRoutingModule } from './customers-routing.module';
import { CustomerListComponent } from './customer-list/customer-list.component';

@NgModule({
  imports: [
    CommonModule,
    CustomersRoutingModule
  ],
  declarations: [CustomerListComponent]
})
export class CustomersModule { }

The customers.module.ts file imports the CustomersRoutingModule and CustomerListComponent so the CustomersModule class can have access to them. CustomersRoutingModule is then listed in the @NgModule imports array giving CustomersModule access to its own routing module, and CustomerListComponent is in the declarations array, which means CustomerListComponent belongs to the CustomersModule.

Configure the feature module’s routes

The next step is in customers-routing.module.ts. First, import the component at the top of the file with the other JavaScript import statements. Then, add the route to CustomerListComponent.

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

import { CustomerListComponent } from './customer-list/customer-list.component';


const routes: Routes = [
  {
    path: '',
    component: CustomerListComponent
  }
];

@NgModule({
  imports: [RouterModule.forChild(routes)],
  exports: [RouterModule]
})
export class CustomersRoutingModule { }

Notice that the path is set to an empty string. This is because the path in AppRoutingModule is already set to customers, so this route in the CustomersRoutingModule, is already within the customers context. Every route in this routing module is a child route.

Repeat this last step of importing the OrdersListComponent and configuring the Routes array for the orders-routing.module.ts:

import { OrderListComponent } from './order-list/order-list.component';

const routes: Routes = [
  {
    path: '',
    component: OrderListComponent
  }
];

Now, if you view the app in the browser, the three buttons take you to each module.

Confirm it’s working

You can check to see that a module is indeed being lazy loaded with the Chrome developer tools. In Chrome, open the dev tools by pressing Cmd+Option+i on a Mac or Ctrl+Alt+i on a PC and go to the Network Tab.

lazy loaded modules diagram

Click on the Orders or Customers button. If you see a chunk appear, you’ve wired everything up properly and the feature module is being lazy loaded. A chunk should appear for Orders and for Customers but will only appear once for each.

lazy loaded modules diagram

forRoot() and forChild()

You might have noticed that the CLI adds RouterModule.forRoot(routes) to the app-routing.module.ts imports array. This lets Angular know that this module, AppRoutingModule, is a routing module and forRoot() specifies that this is the root routing module. It configures all the routes you pass to it, gives you access to the router directives, and registers the RouterService. Use forRoot() in the AppRoutingModule—that is, one time in the app at the root level.

The CLI also adds RouterModule.forChild(routes) to feature routing modules. This way, Angular knows that the route list is only responsible for providing additional routes and is intended for feature modules. You can use forChild() in multiple modules.

forRoot() contains injector configuration which is global; such as configuring the Router. forChild() has no injector configuration, only directives such as RouterOutlet and RouterLink.

Angular 7 Internationalization (i18n)

[Fuente: https://angular.io/guide/i18n]

Application internationalization is a many-faceted area of development, focused on making applications available and user-friendly to a worldwide audience. This page describes Angular’s internationalization (i18n) tools, which can help you make your app available in multiple languages.

See the i18n Example for a simple example of an AOT-compiled app, translated into French.

Angular and i18n

Internationalization is the process of designing and preparing your app to be usable in different languages.

Localization is the process of translating your internationalized app into specific languages for particular locales.

Angular simplifies the following aspects of internationalization:

  • Displaying dates, number, percentages, and currencies in a local format.
  • Preparing text in component templates for translation.
  • Handling plural forms of words.
  • Handling alternative text.

For localization, you can use the Angular CLI to generate most of the boilerplate necessary to create files for translators, and to publish your app in multiple languages. After you have set up your app to use i18n, the CLI can help you with the following steps:

  • Extracting localizable text into a file that you can send out to be translated.
  • Building and serving the app for a given locale, using the translated text.
  • Creating multiple language versions of your app.

Setting up the locale of your app

A locale is an identifier (id) that refers to a set of user preferences that tend to be shared within a region of the world, such as country. This document refers to a locale identifier as a “locale” or “locale id”.

A Unicode locale identifier is composed of a Unicode language identifier and (optionally) the character - followed by a locale extension. (For historical reasons the character _ is supported as an alternative to -.) For example, in the locale id fr-CA the fr refers to the French language identifier, and the CA refers to the locale extension Canada.

Angular follows the Unicode LDML convention that uses stable identifiers (Unicode locale identifiers) based on the norm BCP47. It is very important that you follow this convention when you define your locale, because the Angular i18n tools use this locale id to find the correct corresponding locale data.

By default, Angular uses the locale en-US, which is English as spoken in the United States of America.

To set your app’s locale to another value, use the CLI parameter --configuration with the value of the locale id that you want to use:

ng serve --configuration=fr

If you use JIT, you also need to define the LOCALE_ID provider in your main module:

src/app/app.module.ts
import { LOCALE_ID, NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

import { AppComponent } from '../src/app/app.component';

@NgModule({
  imports: [ BrowserModule ],
  declarations: [ AppComponent ],
  providers: [ { provide: LOCALE_ID, useValue: 'fr' } ],
  bootstrap: [ AppComponent ]
})
export class AppModule { }

For more information about Unicode locale identifiers, see the CLDR core spec.

For a complete list of locales supported by Angular, see the Angular repository.

The locale identifiers used by CLDR and Angular are based on BCP47. These specifications change over time; the following table maps previous identifiers to current ones at time of writing: -REMOVED-

 

i18n pipes

Angular pipes can help you with internationalization: the DatePipeCurrencyPipeDecimalPipe and PercentPipe use locale data to format data based on the LOCALE_ID.

By default, Angular only contains locale data for en-US. If you set the value of LOCALE_ID to another locale, you must import locale data for that new locale. The CLI imports the locale data for you when you use the parameter --configuration with ng serve and ng build.

If you want to import locale data for other languages, you can do it manually:

import { registerLocaleData } from '@angular/common';
import localeFr from '@angular/common/locales/fr';

// the second parameter 'fr' is optional
registerLocaleData(localeFr, 'fr');

The first parameter is an object containing the locale data imported from @angular/common/locales. By default, the imported locale data is registered with the locale id that is defined in the Angular locale data itself. If you want to register the imported locale data with another locale id, use the second parameter to specify a custom locale id. For example, Angular’s locale data defines the locale id for French as “fr”. You can use the second parameter to associate the imported French locale data with the custom locale id “fr-FR” instead of “fr”.

The files in @angular/common/locales contain most of the locale data that you need, but some advanced formatting options might only be available in the extra dataset that you can import from @angular/common/locales/extra. An error message informs you when this is the case.

import { registerLocaleData } from '@angular/common';
import localeFr from '@angular/common/locales/fr';
import localeFrExtra from '@angular/common/locales/extra/fr';

registerLocaleData(localeFr, 'fr-FR', localeFrExtra);
All locale data used by Angular are extracted from the Unicode Consortium's Common Locale Data Repository (CLDR).

NOTA: Lo de arriba es solo relativo al multidioma en los pipes de fechas, números, etc.

Template translations

This document refers to a unit of translatable text as “text,” a “message”, or a “text message.”

The i18n template translation process has four phases:

  1. Mark static text messages in your component templates for translation.
  2. Create a translation file: Use the Angular CLI xi18n command to extract the marked text into an industry-standard translation source file.
  3. Edit the generated translation file: Translate the extracted text into the target language.
  4. Merge the completed translation file into the app. To do this, use the Angular CLI build command to compile the app, choosing a locale-specific configuration, or specifying the following command options.
    • --i18nFile=path to the translation file
    • --i18nFormat=format of the translation file
    • --i18nLocalelocale id

The command replaces the original messages with translated text, and generates a new version of the app in the target language.

You need to build and deploy a separate version of the app for each supported language.

Mark text with the i18n attribute

The Angular i18n attribute marks translatable content. Place it on every element tag whose fixed text is to be translated.

In the example below, an <h1> tag displays a simple English language greeting, “Hello i18n!”

<h1>Hello i18n!</h1>

To mark the greeting for translation, add the i18n attribute to the <h1> tag.

<h1 i18n>Hello i18n!</h1>

i18n is a custom attribute, recognized by Angular tools and compilers. After translation, the compiler removes it. It is not an Angular directive.

Help the translator with a description and meaning

To translate a text message accurately, the translator may need additional information or context.

You can add a description of the text message as the value of the i18n attribute, as shown in the example below:

<h1 i18n="An introduction header for this sample">Hello i18n!</h1>

The translator may also need to know the meaning or intent of the text message within this particular app context.

You add context by beginning the i18n attribute value with the meaning and separating it from the description with the |character: <meaning>|<description>

<h1 i18n="site header|An introduction header for this sample">Hello i18n!</h1>

All occurrences of a text message that have the same meaning will have the same translation.

A text message that is associated with different meanings can have different translations.

The Angular extraction tool preserves both the meaning and the description in the translation source file to facilitate contextually-specific translations, but only the combination of meaning and text message are used to generate the specific id of a translation.

If you have two similar text messages with different meanings, they are extracted separately.

If you have two similar text messages with different descriptions (not different meanings), then they are extracted only once.

Set a custom id for persistence and maintenance

The angular i18n extractor tool generates a file with a translation unit entry for each i18n attribute in a template. By default, it assigns each translation unit a unique id such as this one:

<trans-unit id="ba0cc104d3d69bf669f97b8d96a4c5d8d9559aa3" datatype="html">

When you change the translatable text, the extractor tool generates a new id for that translation unit. You must then update the translation file with the new id.

Alternatively, you can specify a custom id in the i18n attribute by using the prefix @@. The example below defines the custom id introductionHeader:

<h1 i18n="@@introductionHeader">Hello i18n!</h1>

When you specify a custom id, the extractor tool and compiler generate a translation unit with that custom id.

<trans-unit id="introductionHeader" datatype="html">

The custom id is persistent. The extractor tool does not change it when the translatable text changes. Therefore, you do not need to update the translation. This approach makes maintenance easier.

Use a custom id with a description

–removed–

Define unique custom ids

Be sure to define custom ids that are unique. If you use the same id for two different text messages, only the first one is extracted, and its translation is used in place of both original text messages.

In the example below the custom id myId is used for two different messages:

-removed-

Translate text without creating an element

If there is a section of text that you would like to translate, you can wrap it in a <span> tag. However, if you don’t want to create a new DOM element merely to facilitate translation, you can wrap the text in an <ng-container> element. The <ng-container> is transformed into an html comment:

<ng-container i18n>I don't output any element</ng-container>

Translate attributes

Displayed text is sometimes supplied as the value of an attribute, rather than the content of tag. For example, if your template has an image with a title attribute, the text value of the title attribute needs to be translated.

<img [src]="logo" title="Angular logo">

To mark an attribute for translation, add an attribute in the form of i18n-x, where x is the name of the attribute to translate. The following example shows how to mark the title attribute for translation by adding the i18n-title attribute on the imgtag:

<img [src]="logo" i18n-title title="Angular logo" />

This technique works for any attribute of any element.

You also can assign a meaning, description, and id with the i18n-x="<meaning>|<description>@@<id>" syntax.

Regular expressions for plurals and selections

Different languages have different pluralization rules and grammatical constructions that add complexity to the translation task. You can use regular expressions with the plural and select clauses to provide patterns that aid translation in these cases.

Pluralization

Suppose that you want to say that something was “updated x minutes ago”. In English, depending upon the number of minutes, you could display “just now”, “one minute ago”, or “x minutes ago” (with x being the actual number). Other languages might express the cardinality differently.

The example below shows how to use a plural ICU expression to display one of those three options based on when the update occurred:

<span i18n>Updated {minutes, plural, =0 {just now} =1 {one minute ago} other {{{minutes}} minutes ago}}</span>
  • The first parameter is the key. It is bound to the component property (minutes), which determines the number of minutes.
  • The second parameter identifies this as a plural translation type.
  • The third parameter defines a pluralization pattern consisting of pluralization categories and their matching values.

This syntax conforms to the ICU Message Format as specified in the CLDR pluralization rules.

Pluralization categories include (depending on the language):

  • =0 (or any other number)
  • zero
  • one
  • two
  • few
  • many
  • other

After the pluralization category, put the default English text in braces ({}).

In the example above, the three options are specified according to that pluralization pattern. For talking about zero minutes, you use =0 {just now}. For one minute, you use =1 {one minute}. Any unmatched cardinality uses other {{{minutes}} minutes ago}. You could choose to add patterns for two, three, or any other number if the pluralization rules were different. For the example of “minute”, only these three patterns are necessary in English.

You can use interpolations and html markup inside of your translations.

Select among alternative text messages

If your template needs to display different text messages depending on the value of a variable, you need to translate all of those alternative text messages.

You can handle this with a select ICU expression. It is similar to the plural expressions except that you choose among alternative translations based on a string value instead of a number, and you define those string values.

The following format message in the component template binds to the component’s gender property, which outputs one of the following string values: “m”, “f” or “o”. The message maps those values to the appropriate translations:

<span i18n>The author is {gender, select, male {male} female {female} other {other}}</span>

Nesting plural and select ICU expressions

You can also nest different ICU expressions together, as shown in this example:

<span i18n>Updated: {minutes, plural,
  =0 {just now}
  =1 {one minute ago}
  other {{{minutes}} minutes ago by {gender, select, male {male} female {female} other {other}}}}
</span>

Create a translation source file

When your app is ready, you can use the Angular CLI to extract the text messages marked with i18n and attributes marked with i18n-x into a translation source file. Open a terminal window at the root of the app project and run the CLI command xi18n.

ng xi18n

By default, the command creates a file named messages.xlf in your src/ folder.

If you don’t use the CLI, you have two options:

  • You can use the ng-xi18n tool directly from the @angular/compiler-cli package. For more information, see the ng xi18n command documentation.
  • You can use the CLI Webpack plugin AngularCompilerPlugin from the @ngtools/webpack package. Set the parameters i18nOutFile and i18nOutFormat to trigger the extraction. For more information, see the Angular Ahead-of-Time Webpack Plugin documentation.

Output options

You can supply command options to change the format, the name, the location, and the source locale of the extracted file. For example, to create a file in the src/locale folder, specify the output path:

ng xi18n --output-path src/locale

By default, the xi18n command generates a translation file named messages.xlf in the XML Localization Interchange File Format (XLIFF, version 1.2).

The command can read and write files in three translation formats:

You can specify the translation format explicitly with the --i18nFormat command option, as illustrated in these example commands:

ng xi18n  --i18n-format=xlf
ng xi18n  --i18n-format=xlf2
ng xi18n  --i18n-format=xmb

The sample in this guide uses the default XLIFF 1.2 format.

XLIFF files have the extension .xlf. The XMB format generates .xmb source files but uses .xtb (XML Translation Bundle: XTB) translation files.

You can change the name of the translation source file that is generated by the extraction tool with the --outFile command option:

ng xi18n --out-file source.xlf

You can specify the base locale of your app with the--i18n-locale command option:

ng xi18n --i18n-locale fr

The extraction tool uses the locale to add the app locale information into your translation source file. This information is not used by Angular, but external translation tools may need it.

Translate the source text

The ng xi18n command generates a translation source file named messages.xlf in the project src folder. The next step is to translate the display strings in this source file into language-specific translation files. The example in this guide creates a French translation file.

Create a localization folder

Most apps are translated into more than one other language. For this reason, it is standard practice for the project structure to reflect the entire internationalization effort.

One approach is to dedicate a folder to localization and store related assets, such as internationalization files, there.

Localization and internationalization are different but closely related terms.

This guide follows that approach. It has a locale folder under src/. Assets within that folder have a filename extension that matches their associated locale.

Create the translation files

For each translation source file, there must be at least one language translation file for the resulting translation.

For this example:

  1. Make a copy of the messages.xlf file.
  2. Put the copy in the locale folder.
  3. Rename the copy to messages.fr.xlf for the French language translation.

If you were translating to other languages, you would repeat these steps for each target language.

Translate text nodes

In a large translation project, you would send the messages.fr.xlf file to a French translator who would enter the translations using an XLIFF file editor.

This sample file is easy to translate without a special editor or knowledge of French.

  1. Open messages.fr.xlf and find the first <trans-unit> section:
<trans-unit id="introductionHeader" datatype="html">
  <source>Hello i18n!</source>
  <note priority="1" from="description">An introduction header for this sample</note>
  <note priority="1" from="meaning">User welcome</note>
</trans-unit>

This XML element represents the translation of the <h1> greeting tag that you marked with the i18n attribute earlier in this guide.

Note that the translation unit id=introductionHeader is derived from the custom id that you set earlier, but without the @@ prefix required in the source HTML.

  1. Duplicate the <source/> tag, rename it target, and then replace its content with the French greeting. If you were working with a more complex translation, you could use the information and context provided by the source, description, and meaning elements to guide your selection of the appropriate French translation.
<trans-unit id="introductionHeader" datatype="html">
  <source>Hello i18n!</source>
  <target>Bonjour i18n !</target>
  <note priority="1" from="description">An introduction header for this sample</note>
  <note priority="1" from="meaning">User welcome</note>
</trans-unit>
  1. Translate the other text nodes the same way:
<trans-unit id="ba0cc104d3d69bf669f97b8d96a4c5d8d9559aa3" datatype="html">
  <source>I don&apos;t output any element</source>
  <target>Je n'affiche aucun élément</target>
</trans-unit>
<trans-unit id="701174153757adf13e7c24a248c8a873ac9f5193" datatype="html">
  <source>Angular logo</source>
  <target>Logo d'Angular</target>
</trans-unit>

The Angular i18n tools generated the ids for these translation units. Don’t change them. Each id depends upon the content of the template text and its assigned meaning. If you change either the text or the meaning, then the idchanges. For more information, see the translation file maintenance discussion.

Translating plural and select expressions

The plural and select ICU expressions are extracted separately, so they require special attention when preparing for translation.

Look for these expressions in relation to other translation units that you recognize from elsewhere in the source template. In this example, you know the translation unit for the select must be just below the translation unit for the logo.

Translate plural

To translate a plural, translate its ICU format match values:

<trans-unit id="5a134dee893586d02bffc9611056b9cadf9abfad" datatype="html">
  <source>{VAR_PLURAL, plural, =0 {just now} =1 {one minute ago} other {<x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes ago} }</source>
  <target>{VAR_PLURAL, plural, =0 {à l'instant} =1 {il y a une minute} other {il y a <x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes} }</target>
</trans-unit>

You can add or remove plural cases, with each language having its own cardinality. (See CLDR plural rules.)

Translate select

Below is the content of our example select ICU expression in the component template:

<span i18n>The author is {gender, select, male {male} female {female} other {other}}</span>

The extraction tool broke that into two translation units because ICU expressions are extracted separately.

The first unit contains the text that was outside of the select. In place of the select is a placeholder, <x id="ICU">, that represents the select message. Translate the text and move around the placeholder if necessary, but don’t remove it. If you remove the placeholder, the ICU expression will not be present in your translated app.

<trans-unit id="f99f34ac9bd4606345071bd813858dec29f3b7d1" datatype="html">
  <source>The author is <x id="ICU" equiv-text="{gender, select, male {...} female {...} other {...}}"/></source>
  <target>L'auteur est <x id="ICU" equiv-text="{gender, select, male {...} female {...} other {...}}"/></target>
</trans-unit>

The second translation unit, immediately below the first one, contains the select message. Translate that as well.

<trans-unit id="eff74b75ab7364b6fa888f1cbfae901aaaf02295" datatype="html">
  <source>{VAR_SELECT, select, male {male} female {female} other {other} }</source>
  <target>{VAR_SELECT, select, male {un homme} female {une femme} other {autre} }</target>
</trans-unit>

Here they are together, after translation:

</trans-unit>
<trans-unit id="f99f34ac9bd4606345071bd813858dec29f3b7d1" datatype="html">
  <source>The author is <x id="ICU" equiv-text="{gender, select, male {...} female {...} other {...}}"/></source>
  <target>L'auteur est <x id="ICU" equiv-text="{gender, select, male {...} female {...} other {...}}"/></target>
</trans-unit>
<trans-unit id="eff74b75ab7364b6fa888f1cbfae901aaaf02295" datatype="html">
  <source>{VAR_SELECT, select, male {male} female {female} other {other} }</source>
  <target>{VAR_SELECT, select, male {un homme} female {une femme} other {autre} }</target>
</trans-unit>

Translate a nested expression

A nested expression is similar to the previous examples. As in the previous example, there are two translation units. The first one contains the text outside of the nested expression:

<trans-unit id="972cb0cf3e442f7b1c00d7dab168ac08d6bdf20c" datatype="html">
  <source>Updated: <x id="ICU" equiv-text="{minutes, plural, =0 {...} =1 {...} other {...}}"/></source>
  <target>Mis à jour: <x id="ICU" equiv-text="{minutes, plural, =0 {...} =1 {...} other {...}}"/></target>
</trans-unit>

The second unit contains the complete nested expression:

<trans-unit id="7151c2e67748b726f0864fc443861d45df21d706" datatype="html">
  <source>{VAR_PLURAL, plural, =0 {just now} =1 {one minute ago} other {<x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes ago by {VAR_SELECT, select, male {male} female {female} other {other} }} }</source>
  <target>{VAR_PLURAL, plural, =0 {à l'instant} =1 {il y a une minute} other {il y a <x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes par {VAR_SELECT, select, male {un homme} female {une femme} other {autre} }} }</target>
</trans-unit>

And both together:

<trans-unit id="972cb0cf3e442f7b1c00d7dab168ac08d6bdf20c" datatype="html">
  <source>Updated: <x id="ICU" equiv-text="{minutes, plural, =0 {...} =1 {...} other {...}}"/></source>
  <target>Mis à jour: <x id="ICU" equiv-text="{minutes, plural, =0 {...} =1 {...} other {...}}"/></target>
</trans-unit>
<trans-unit id="7151c2e67748b726f0864fc443861d45df21d706" datatype="html">
  <source>{VAR_PLURAL, plural, =0 {just now} =1 {one minute ago} other {<x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes ago by {VAR_SELECT, select, male {male} female {female} other {other} }} }</source>
  <target>{VAR_PLURAL, plural, =0 {à l'instant} =1 {il y a une minute} other {il y a <x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes par {VAR_SELECT, select, male {un homme} female {une femme} other {autre} }} }</target>
</trans-unit>

The entire template translation is complete. The next section describes how to load that translation into the app.

The app and its translation file

The sample app and its translation file are now as follows: –removed–

Merge the completed translation file into the app

To merge the translated text into component templates, compile the app with the completed translation file.

Provide the Angular compiler with three translation-specific pieces of information:

  • The translation file.
  • The translation file format.
  • The locale (fr or en-US for instance).

The compilation process is the same whether the translation file is in .xlf format or in another format that Angular understands, such as .xtb.

How you provide this information depends upon whether you compile with the JIT compiler or the AOT compiler.

  • With AOT, you pass the information as configuration settings.
  • With JIT, you provide the information at bootstrap time.

Merge with the AOT compiler

The AOT compiler is part of a build process that produces a small, fast, ready-to-run application package, typically for production.

When you internationalize with the AOT compiler, you must pre-build a separate application package for each language and serve the appropriate package based on either server-side language detection or url parameters.

To instruct the AOT compiler to use your translation configuration, set the three “i18n” build configuration options in your angular.json file.

  • i18nFile: the path to the translation file.
  • i18nFormat: the format of the translation file.
  • i18nLocale: the locale id.

You should also direct the output to a locale-specific folder to keep it separate from other locale versions of your app, by setting the outputPath configuration option.

"build": {
  ...
  "configurations": {
    ...
    "fr": {
      "aot": true,
      "outputPath": "dist/my-project-fr/",
      "i18nFile": "src/locale/messages.fr.xlf",
      "i18nFormat": "xlf",
      "i18nLocale": "fr",
      ...
    }
  }
},
"serve": {
  ...
  "configurations": {
    ...
    "fr": {
      "browserTarget": "*project-name*:build:fr"
    }
  }
}

You can then pass this configuration to the ng serve or ng build commands. The example below shows how to serve the French language file created in previous sections of this guide:

ng serve --configuration=fr

For production builds, you define a separate production-fr build configuration in the CLI configuration file, angular.json.

...
"architect": {
  "build": {
    "builder": "@angular-devkit/build-angular:browser",
    "options": { ... },
    "configurations": {
      "fr": {
        "aot": true,
        "outputPath": "dist/my-project-fr/",
        "i18nFile": "src/locale/messages.fr.xlf",
        "i18nFormat": "xlf",
        "i18nLocale": "fr",
        "i18nMissingTranslation": "error",
      }
// ...
"serve": {
  "builder": "@angular-devkit/build-angular:dev-server",
  "options": {
    "browserTarget": "my-project:build"
  },
  "configurations": {
    "production": {
      "browserTarget": "my-project:build:production"
    },
    "fr": {
      "browserTarget": "my-project:build:fr"
    }
  }
},

The same configuration options can also be provided through the CLI with your existing production configuration.

ng build --prod --i18n-file src/locale/messages.fr.xlf --i18n-format xlf --i18n-locale fr

Report missing translations

By default, when a translation is missing, the build succeeds but generates a warning such as Missing translation formessage "foo". You can configure the level of warning that is generated by the Angular compiler:

  • Error: throw an error. If you are using AOT compilation, the build will fail. If you are using JIT compilation, the app will fail to load.
  • Warning (default): show a ‘Missing translation’ warning in the console or shell.
  • Ignore: do nothing.

You specify the warning level in the configurations section your Angular CLI build configuration. The example below shows how to set the warning level to error:

"configurations": {
  ...
  "fr": {
    ...
    "i18nMissingTranslation": "error"
  }
}

Build for multiple locales

When you use the CLI build or serve command to build your application for different locales, change the output path using the --outputPath command option (along with the i18n-specific command options), so that the translation files are saved to different locations. When you are serving a locale-specific version from a subdirectory, you can also change the base URL used by your app by specifying the --baseHref option.

For example, if the French version of your application is served from https://myapp.com/fr/, configure the build for the French version as follows.

"configurations": {
  "fr": {
    "aot": true,
    "outputPath": "dist/my-project-fr/",
    "baseHref": "/fr/",
    "i18nFile": "src/locale/messages.fr.xlf",
    "i18nFormat": "xlf",
    "i18nLocale": "fr",
    "i18nMissingTranslation": "error",
  }

For more details about how to create scripts to generate an app in multiple languages and how to set up Apache 2 to serve them from different subdirectories, read this tutorial by Philippe Martin.

ewe
w
ew
ew
ew
ewe
w

Ventajas de desarrolla web apps modernas con ReactJS

[Fuente: https://medium.com/@hamzamahmood/advantages-of-developing-modern-web-apps-with-react-js-8504c571db71]

Cómo los negocios online pueden sacar el máximo partido de utilizar React

If we talk about the 2010’s, we have observed a massive growth in modern web and mobile applications, powered by lightweight and sophisticated JavaScript libraries. Better user experience is now derived from the speed of the application and as more customers start using a particular technology, companies have to strive towards scalability. Considering there are a plethora of frameworks to choose from, software teams fall victim to the bothersome “paradox of choice”; that is, the dilemma to decide which one is better than the other before pitching it to upper management. Choosing a framework is a challenging task. It could be viewed as more of a business decision than a technological one if one thinks about it.

React.js is one such Javascript library that has achieved massive popularity within the domain of online, web-based businesses and that too for a perfectly good reason. The one thing that it does exceptionally well — carve out great looking user interfaces (UI). With the principle that HTML and JavaScript are bound to collaborate side-by-side, React was created with a business-forward mindset by leveraging faster web-page load speed, SEO friendliness and code reusability through combining the two technologies.

A Very Concise Introduction

React was developed by Facebook in 2011 and given open-source status in 2013 under the controversial BSD3 license. Since its first release, React’s Github repository has generated 96,000 stars from developers and has amassed a community of almost 1200 active contributors, regularly pushing updates to enrich the library. With over 7M downloads in the month of May alone, this library has definitely proven its preference within technology companies.

Which Companies Use React

Businesses were quick to grasp this technology because of the simplicity it offered to developers; that is, the ability to learn React in bare minimum time. Code reusability with hassle-free addition/modification of functionalities in the existing system meant allocating significantly less time and budget on development and building larger teams, respectively.

Top companies are truly enamored by React’s capabilities on a business stand point. The list of companies utilizing React in production is there to prove it, just to name a few: Facebook, Instagram, Netflix, Whatsapp, Salesforce, Uber, The New York Times, CNN, Dropbox, DailyMotion, IMDB, Venmo, and Reddit are the major league ones among the 100+ other medium to large scale companies.

To elaborate on why businesses are converging towards this technology, we need to explore React’s primary features that facilitate the creation of high performance, agile applications, then proceed on to the pros and cons associated with using React in online businesses before finally finishing it off with a conclusion.

1.0 ReactJS has Power-Packed Features

To give a high-level overview of what React is capable of, we need to understand the core concepts and features associated with the library. There are 3 primary ones, which are summarized as follows:

1.1 Component Creation

React enables the creation of module-like pieces of code called “Components”. These code snippets reflect a particular part of the user-interface, which can be repeated across several web pages. This is what we meant by reusability, which is a great way to save valuable time on development. The declarative nature of React also makes designing UI seamless and takes a major load off from product developers so they could focus on more important functions and business logic.

1.2 Virtual DOM

Considered the next biggest leap in web development since AJAX, the virtual DOM (short for Document Object Model) is the core reason why React enables the creation of fast, scalable web apps. Through React’s memory reconciliation algorithm, the library constructs a representation of the page in a virtual memory, where it performs the necessary updates before rendering the final web-page into the browser.

1.3 Easy to learn

We need to clarify that React is NOT a framework; unlike Angular or Vue.js, but a library that is consistently used in association with other Javascript libraries. Hence, there is a shorter learning curve involved in understanding React compared to other comprehensive libraries. Businesses; in turn, are able to streamline development without spending much capital on the existing system.

2.0 Analyzing the Pros and Cons from a business standpoint

React is a miniature library but serves multiple possible advantages to boost traction of online services and apps. The illustration below provides a glimpse of what React does well and where it falls short. We shall elaborate further on each particular element through the perspective of a business.

The advantages easily outweigh the disadvantages. Source: Cleveroad

2.1) Make Your Business “React”-ive

Notable Boost In Performance

The Virtual DOM mentioned earlier emphatically increases speed of modern web applications because it eliminates the usage of code heavy frameworks such as Jquery and other bootstrapping libraries. React itself is sufficient in creating awesome looking front-end designs and combined with its super fast rendering capabilities is a natural fit for companies to utilize it in their services.

Seamless SEO Integration

For any online business, content is king. Search Engine Optimization is the gateway to boost user traffic onto their platform. React significantly reduces page load time through faster rendering speed, adapts it performance in real-time based on current user traffic, features that are otherwise not perfectly handled by most frameworks.

This particular aspect is essential for the success of businesses because faster speed is directly proportional to more users which converts to main goal of companies: Revenue. This is also emphasized by Moz in their article that effective use of SEO will improve the app’s ranking on Google search and reaching the number one spot is the holy grail of every web-based platform.

Ease of Migration

When engineers and managers collaboratively decide upon migrating from an older technological infrastructure to a new one, certain questions arise on the level of effort and time required in performing the task. Google’s Angular framework; for instance, faced a very alarming compatibility issue between its first generation Angular 1.x and the futuristic Angular 2. Companies intent on shifting to the newer version, had to invest time to train developers in the latest technology.

React; on the other hand, is lightweight and practically wrapped around the same JavaScript standards that made developers and manager alike, to fall in love with this language in the first place. React code can be added anywhere onto existing infrastructure without worry of shutting down the system for maintenance and also less dependency to reinvent the wheel.

2.2 Upgrade Your Technological Assets

Fuse technologies for bigger Impact

React makes use of the ever popular HTML and Javascript frameworks by integrating them together. Extend the two technologies onto CSS facilitates the designing of advanced looking web-interfaces. React is a very API friendly library and is extensible across a multitude of frameworks. It works seamlessly with Google’s iconic Material Design and SemanticUI frameworks to leverage user interface development.

Quickly Debug Faults

Facebook has created a dedicated debugging mechanism to isolate UI errors and bugs to the exact component causing it. The browser itself spits out relevant data about the erroneous line of code, web page and/or section to make the required correction, making lives of developers a whole lot easier.

Improve Code Stability With Tests

Businesses looking to create fault tolerant user interface will find ReactJS as the go-to choice. The component creation aspect of this library allows developers to efficiently perform unit testing, making sure no system crashes occur. Code reuse enables for curtailing time performing redundant tests. Adding such tests improves standards in code quality; hence, platform stability.

2.3 Some Drawbacks

Counterproductive Documentation

The roadblock that developers and product managers are known to face is the limitation of documentation within React. When trying to ‘hook’ React with other libraries, two scenarios occur: Either the information provided on their forum is 1) inadequate or, 2) is not useful. Angular being a complete solution, and considering it has been around for a longer period of time, has better maintained resources and tutorials. Vue.js also, despite being new, is reliable with regard to documentation and code readability.

Very UI-centric

Some experts have reservations about React’s over-dependence on external libraries to add functionality to its base. Despite being lightweight, React quintessentially is a UI library. There is only so much it can do to enhance performance and robustness of a web app through its core functionalities. Angular and Vue would be considered more extensive with regard to creating full-scale projects unlike React.

GitLab cogently claims that Vue “…does not make large assumptions about much of anything either. It really only assumes that your data will change.” This is a subtle, yet critical remark on React’s superfluousness of depending on multiple libraries that may complicate matters for businesses.

3.0 Conclusion

If we look at the whole discussion holistically, React’s advantages far exceed its disadvantages making it a robust and adaptable programming library. Companies realize React’s ability to remain relevant in the market and that is why they are investing wholeheartedly in this technology.

The point; nevertheless, was to showcase the usefulness of the React library in creating modern, scalable applications. With users more quick to choose alternatives in the case of poor application performance, product developers have to make an important judgment call when deciding the ‘right’ framework for creating the best looking and performing application to drive user leads and then monetary sales, eventually. React is that library assisting companies achieve to achieve their goals, fortifying its relevance in the market for a longer time to come.



Spread and share knowledge. If this article piqued your interest and if you are kind fellow, give a few claps to this article. Follow my profile for more tech related articles. — Hamza

React native — OTA updates

[Fuente: https://docs.expo.io/versions/v31.0.0/guides/configuring-ota-updates]

Configuring OTA Updates

Expo provides various settings to configure how your app receives over-the-air (OTA) JavaScript updates. OTA updates allow you to publish a new version of your app JavaScript and assets without building a new version of your standalone app and re-submitting to app stores (read more about the limitations).

[ —- Limitations ——

If you make any of the following changes in app.json, you will need to re-build the binaries for your app for the change to take effect:

  • Increment the Expo SDK Version

  • Change anything under the ios or android keys

  • Change your app splash

  • Change your app icon

  • Change your app name

  • Change your app scheme

  • Change your facebookScheme

  • Change your bundled assets under assetBundlePatterns

—–]

To perform an over-the-air update of your app, you simply run expo publish. If you’re using release channels, specify one with --release-channel <channel-name> option. Please note that if you wish to update the SDK version which your app is using, you need to rebuild your app with expo build:* command and upload the binary file to the appropriate app store (see the docs here).

OTA updates are controlled by the updates settings in app.json, which handle the initial app load, and the Updates SDK module, which allows you to fetch updates asynchronously from your JS.

By default, Expo will check for updates automatically when your app is launched and will try to fetch the latest published version. If a new bundle is available, Expo will attempt to download it before launching the experience. If there is no network connection available, or it has not finished downloading in 30 seconds, Expo will fall back to loading a cached version of your app, and continue trying to fetch the update in the background (at which point it will be saved into the cache for the next app load).

With this automatic configuration, calling Expo.Updates.reload() will also result in Expo attempting to fetch the most up-to-date version of your app, so there is no need to use any of the other methods in the Updates module.

The timeout length is configurable by setting updates.fallbackToCacheTimeout (ms) in app.json. For example, a common pattern is to set updates.fallbackToCacheTimeout to 0. This will allow your app to start immediately with a cached bundle while downloading a newer one in the background for future use. Expo.Updates.addListener provides a hook to let you respond when the new bundle is finished downloading.

In standalone apps, it is also possible to turn off automatic updates, and to instead control updates entirely within your JS code. This is desirable if you want some custom logic around fetching updates (e.g. only over Wi-Fi).

Setting updates.checkAutomatically to "ON_ERROR_RECOVERY" in app.json will prevent Expo from automatically fetching the latest update every time your app is launched. Only the most recent cached version of your bundle will be loaded. It will only automatically fetch an update if the last run of the cached bundle produced a fatal JS error.

You can then use the Expo.Updates module to download new updates and, if appropriate, notify the user and reload the experience.

try {
  const update = await Expo.Updates.checkForUpdateAsync();
  if (update.isAvailable) {
    await Expo.Updates.fetchUpdateAsync();
    // ... notify user of update ...
    Expo.Updates.reloadFromCache();
  }
} catch (e) {
  // handle or log error
}

Note that checkAutomatically: "ON_ERROR_RECOVERY" will be ignored in the Expo client, although the imperative Updates methods will still function normally.

It is possible to entirely disable OTA JavaScript updates in a standalone app, by setting updates.enabled to false in app.json. This will ignore all code paths that fetch app bundles from Expo’s servers. In this case, all updates to your app will need to be routed through the iOS App Store and/or Google Play Store.

This setting is ignored in the Expo client.

React native – Facebook integration with expo

Facebook

Provides Facebook integration for Expo apps. Expo exposes a minimal native API since you can access Facebook’s Graph API directly through HTTP (using fetch, for example).

NOTA: React native has a built-in “Share” service that display device’s native dialog.

Follow Facebook’s developer documentation to register an application with Facebook’s API and get an application ID.

[—————–

[Fuente: https://developers.facebook.com/docs/apps#register]

Registro

Para que tu aplicación pueda acceder a nuestros productos o API, primero debes convertir tu cuenta de Facebook a una cuenta de desarrollador y registrar la aplicación mediante el panel de aplicaciones. Puedes hacerlo desde developers.facebook.com. El proceso de registro crea un identificador de la aplicación que nos permite conocer quién eres, nos ayuda a distinguir tu aplicación de otras aplicaciones y es una forma de que puedas proporcionar otros posibles materiales necesarios a la hora de configurar productos específicos o solicitar acceso a API confidenciales.

Una vez hecho el registro, haz clic en el enlace Configuración a la izquierda, y proporciona toda información adicional que tengas acerca de tu aplicación. La mayoría de los campos son sencillos de entender, pero para algunos hay más información disponible.

Identificador de la aplicación

Cuando te registres, generaremos un identificador de la aplicación único para tu aplicación. Vamos a usar mucho este identificador, ya que debe incluirse al hacer llamadas a las API. Esto puede configurarse fácilmente desde nuestros SDK, de modo que el identificador se incluya automáticamente en cualquier llamada a la API.

Plataforma

Nos permite saber cómo acceden los usuarios a tu aplicación; por ejemplo, desde un dispositivo móvil o desde un sitio web. De esta forma, más adelante podemos mostrarte productos y funciones específicos de la plataforma.

Política de privacidad y condiciones del servicio

Deben ser URL a la política de privacidad y a las condiciones del servicio que correspondan a los usuarios de tu aplicación. Supongamos que eres usuario de esta aplicación y quieres saber de qué modo el desarrollador de aplicaciones protegerá tu privacidad y qué condiciones debes aceptar. Debes buscar una política de privacidad y las condiciones del servicio. Introduce las URL aquí. Si estás usando algún producto, permisos de inicio de sesión o funciones que requieren la revisión de aplicaciones, revisaremos el contenido en estas URL.

Situaciones y productos

Después de registrar tu aplicación, tienes la opción de seleccionar una o más situaciones. Seleccionar una situación agrega los productos relacionados con la situación. Después de confirmar tus situaciones, los productos agregados aparecen a la izquierda, y la documentación aparece a la derecha.

También puedes saltear la selección de situaciones y agregar productos adicionales.

——————–]

Take note of this application ID because it will be used as the appId option in your Expo.Facebook.logInWithReadPermissionsAsync call.

NOTA IMPORTANTE:

En la consola de developers de facebook hay que añadir que el app tenga inicio de sesión con facebook, Entonces eliges aplicación nativa android y ya puedes meter información de la app, como el package name y el android hashes (que se saca con el comando ‘expo fetch:android:hashes’).

Ejemplo de hacer login dentro del API de Facebook:

async function logIn() {
  const { type, token } = await Expo.Facebook.logInWithReadPermissionsAsync('<APP_ID>', {
      permissions: ['public_profile'],
    });
  if (type === 'success') {
    // Get the user's name using Facebook's Graph API
    const response = await fetch(
      `https://graph.facebook.com/me?access_token=${token}`);
    Alert.alert(
      'Logged in!',
      `Hi ${(await response.json()).name}!`,
    );
  }
}

Given a valid Facebook application ID in place of <APP_ID>, the code above will prompt the user to log into Facebook then display the user’s name. This uses React Native’s fetch to query Facebook’s Graph API.