Angular 8

Licencia de Creative Commons mario@mariogl.com

Temario

  • Introducción a Angular vs JavaScript.
  • Angular.
  • Componentes.
  • Presentación de datos.
  • Eventos.
  • Formularios.
  • Rutas y navegación.
  • Buenas prácticas.

Introducción a Angular

¿Qué es Angular?

  • Framework JS

  • SPA: Single Page Applications

  • TypeScript

  • Código fuente y código compilado

  • ¿Angular 2? ¿8? ¿AngularJS?

Entorno de desarrollo

Entorno de desarrollo

Git

Comandos básicos

  • Clonar un repositorio:

        git clone URL
     

  • Descargar última versión del repositorio:

        git pull origin master

Configuración proxy

git config --global http.proxy http://username:password@host:port

git config --global https.proxy http://username:password@host:port

Node.js y npm

npm

  • Instalar última versión después de instalar Node.js
    (configurar proxy si es necesario): npm install -g npm

  • Repositorio de módulos distribuibles

  • Módulos globales y módulos locales

  • La carpeta node_modules

  • El archivo package.json:

    • Registro de dependencias

    • Dependencias de desarrollo y de producción

    • Versiones (SEMVER)

Comandos npm

  • Instalar un paquete globalmente:
        npm install -g paquete

  • Instalar un paquete de producción:
        npm install paquete

  • Instalar un paquete de desarrollo:
        npm install paquete --save-dev

  • Instalar todas las dependencias:
        npm install

  • Instalar las dependencias de producción:
        npm install --production

  • Listar paquetes instalados:

        npm list --depth=0        (locales)
        npm list -g --depth=0   (globales)

angular-cli

  • Instalación global:
        npm install -g @angular/cli

Configuración proxy

npm config set proxy http://username:password@host:port

npm config set https-proxy http://username:password@host:port

JavaScript

JavaScript

  • Interpretado, compilado y ejecutado en el navegador

  • Cada navegador programa su propio motor de JS

  • Estandarización: ECMAScript

  • La versión ES6 o ES2015

  • Transpiladores: Babel, TypeScript

Organización del código JavaScript

  • Ejemplo de uso clásico de JS: utilizar un plugin de jQuery en nuestra web, o implementar alguna interacción con el usuario

  • Pocas líneas de código, todas en un mismo archivo

Organización del código JavaScript

(function($) {

    $(document).ready(function() {
        
        // Al hacer clic en una pestaña
        $(".tab a").on("click", function(e) {
            // Anulamos el link
            e.preventDefault();

            // Ocultamos todos los bloques de contenido
            // y mostramos sólo el que se ha elegido
            var content_id = $(this).attr("href");
            $(".tab-content").hide();
            $(content_id).show();

            // Desmarcamos la pestaña que estuviera activa
            // y marcamos la clicada como activa
            $(".tab.active").removeClass("active");
            $(this).closest(".tab").addClass("active");
        })
        
    })

})(jQuery);
<head>
    <meta charset="UTF-8">
    <title>Mi web</title>
    <script src="vendor/jquery/jquery.min.js"></script>
    <script src="js/tabs.js"></script>
</head>

24 líneas

Organización del código JavaScript

<head>
    <meta charset="UTF-8">
    <title>Mi web</title>
    <script src="vendor/jquery/jquery.min.js"></script>
    <script src="js/ui.js"></script>
</head>
(function($) {
    
        $(document).ready(function() {
            $(document).on('click', '.tab_new', offerGroupSwitchTabs);
            $(document).on('click', '.navigationServices-li', jumpTo);
            $('.load-more_new').on('click', loadMore).each(function() {
                $(this).data('main', $(this).text());
            });
        })
    
        var loadMore = function(e) {
            e.preventDefault();
            var $list = $(this).prev('.promos-list_new');
            var button_text = $(this).data('main');
            var button_alt_text = $(this).data('alt');
            if ($(window).width() > 992) {
                var hidden_classes = ".hidden";
                var $hidden = $list.find(hidden_classes);
                var n_show = 3;
            } else if ($(window).width() > 768) {
                var hidden_classes = ".hidden, .hidden-sm";
                var $hidden = $list.find(hidden_classes);
                var n_show = 2;
            } else {
                var hidden_classes = ".hidden, .hidden-sm, .hidden-xs";
                var $hidden = $list.find(hidden_classes);
                var n_show = 1;
            }
            if ($hidden.length == 0) {
                $list.find(">li:nth-child(2)").addClass('hidden-xs');
                $list.find(">li:nth-child(3)").addClass('hidden-xs hidden-sm');
                $list.find(">li:nth-child(n+4)").addClass('hidden');
                $(this).text(button_text);
            } else {
                $hidden.slice(0, n_show).each(function () {
                    $(this).removeClass(hidden_classes.replace(/[\.,\,]/g, ''));
                });
                if ($list.find(hidden_classes).length == 0) {
                    $(this).text(button_alt_text);
                }
            }
        }
    
        var offerGroupSwitchTabs = function(e) {
            e.preventDefault();
            var $info = $(this).closest('.info_new');
            var target = $(this).attr('href');
            var targetClass = target.replace('#', '.');
            $info.find('.active').removeClass('active');
            $info.find('[href=' + target + ']').addClass('active');
            $(targetClass).addClass('active');
        }
    
        var jumpTo = function(e) {
            e.preventDefault();
            var target = $(this).find('a').attr('href');
            var $target = $(target);
            var targetTop = $target.offset().top;
            $('html, body').animate({
                scrollTop: targetTop - 200
            }, {
                duration: durationOnDistance(targetTop),
                complete: function() {
                    window.location.hash = target;
                }
            });
        }
    
        var durationOnDistance = function(elementTop) {
            var distance = elementTop - $(window).scrollTop();
            var time = distance * 0.5;
            return time;
        }
    
    })(jQuery)
  

75 líneas

Organización del código JavaScript

  • Programar toda la UI de una página

(function() {
    CodeMirror.defineMode("javascript", function(config, parserConfig) {
      var indentUnit = config.indentUnit;
      var jsonMode = parserConfig.json;
    
      // Tokenizer
    
      var keywords = function(){
        function kw(type) {return {type: type, style: "keyword"};}
        var A = kw("keyword a"), B = kw("keyword b"), C = kw("keyword c");
        var operator = kw("operator"), atom = {type: "atom", style: "atom"};
        return {
          "if": A, "while": A, "with": A, "else": B, "do": B, "try": B, "finally": B,
          "return": C, "break": C, "continue": C, "new": C, "delete": C, "throw": C,
          "var": kw("var"), "const": kw("var"), "let": kw("var"),
          "function": kw("function"), "catch": kw("catch"),
          "for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"),
          "in": operator, "typeof": operator, "instanceof": operator,
          "true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom
        };
      }();
    
      var isOperatorChar = /[+\-*&%=<>!?|]/;
    
      function chain(stream, state, f) {
        state.tokenize = f;
        return f(stream, state);
      }
    
      function nextUntilUnescaped(stream, end) {
        var escaped = false, next;
        while ((next = stream.next()) != null) {
          if (next == end && !escaped)
            return false;
          escaped = !escaped && next == "\\";
        }
        return escaped;
      }
    
      // Used as scratch variables to communicate multiple values without
      // consing up tons of objects.
      var type, content;
      function ret(tp, style, cont) {
        type = tp; content = cont;
        return style;
      }
    
      function jsTokenBase(stream, state) {
        var ch = stream.next();
        if (ch == '"' || ch == "'")
          return chain(stream, state, jsTokenString(ch));
        else if (/[\[\]{}\(\),;\:\.]/.test(ch))
          return ret(ch);
        else if (ch == "0" && stream.eat(/x/i)) {
          stream.eatWhile(/[\da-f]/i);
          return ret("number", "number");
        }      
        else if (/\d/.test(ch)) {
          stream.match(/^\d*(?:\.\d*)?(?:[eE][+\-]?\d+)?/);
          return ret("number", "number");
        }
        else if (ch == "/") {
          if (stream.eat("*")) {
            return chain(stream, state, jsTokenComment);
          }
          else if (stream.eat("/")) {
            stream.skipToEnd();
            return ret("comment", "comment");
          }
          else if (state.reAllowed) {
            nextUntilUnescaped(stream, "/");
            stream.eatWhile(/[gimy]/); // 'y' is "sticky" option in Mozilla
            return ret("regexp", "string-2");
          }
          else {
            stream.eatWhile(isOperatorChar);
            return ret("operator", null, stream.current());
          }
        }
        else if (ch == "#") {
            stream.skipToEnd();
            return ret("error", "error");
        }
        else if (isOperatorChar.test(ch)) {
          stream.eatWhile(isOperatorChar);
          return ret("operator", null, stream.current());
        }
        else {
          stream.eatWhile(/[\w\$_]/);
          var word = stream.current(), known = keywords.propertyIsEnumerable(word) && keywords[word];
          return (known && state.kwAllowed) ? ret(known.type, known.style, word) :
                         ret("variable", "variable", word);
        }
      }
    
      function jsTokenString(quote) {
        return function(stream, state) {
          if (!nextUntilUnescaped(stream, quote))
            state.tokenize = jsTokenBase;
          return ret("string", "string");
        };
      }
    
      function jsTokenComment(stream, state) {
        var maybeEnd = false, ch;
        while (ch = stream.next()) {
          if (ch == "/" && maybeEnd) {
            state.tokenize = jsTokenBase;
            break;
          }
          maybeEnd = (ch == "*");
        }
        return ret("comment", "comment");
      }
    
      // Parser
    
      var atomicTypes = {"atom": true, "number": true, "variable": true, "string": true, "regexp": true};
    
      function JSLexical(indented, column, type, align, prev, info) {
        this.indented = indented;
        this.column = column;
        this.type = type;
        this.prev = prev;
        this.info = info;
        if (align != null) this.align = align;
      }
    
      function inScope(state, varname) {
        for (var v = state.localVars; v; v = v.next)
          if (v.name == varname) return true;
      }
    
      function parseJS(state, style, type, content, stream) {
        var cc = state.cc;
        // Communicate our context to the combinators.
        // (Less wasteful than consing up a hundred closures on every call.)
        cx.state = state; cx.stream = stream; cx.marked = null, cx.cc = cc;
      
        if (!state.lexical.hasOwnProperty("align"))
          state.lexical.align = true;
    
        while(true) {
          var combinator = cc.length ? cc.pop() : jsonMode ? expression : statement;
          if (combinator(type, content)) {
            while(cc.length && cc[cc.length - 1].lex)
              cc.pop()();
            if (cx.marked) return cx.marked;
            if (type == "variable" && inScope(state, content)) return "variable-2";
            return style;
          }
        }
      }
    
      // Combinator utils
    
      var cx = {state: null, column: null, marked: null, cc: null};
      function pass() {
        for (var i = arguments.length - 1; i >= 0; i--) cx.cc.push(arguments[i]);
      }
      function cont() {
        pass.apply(null, arguments);
        return true;
      }
      function register(varname) {
        var state = cx.state;
        if (state.context) {
          cx.marked = "def";
          for (var v = state.localVars; v; v = v.next)
            if (v.name == varname) return;
          state.localVars = {name: varname, next: state.localVars};
        }
      }
    
      // Combinators
    
      var defaultVars = {name: "this", next: {name: "arguments"}};
      function pushcontext() {
        if (!cx.state.context) cx.state.localVars = defaultVars;
        cx.state.context = {prev: cx.state.context, vars: cx.state.localVars};
      }
      function popcontext() {
        cx.state.localVars = cx.state.context.vars;
        cx.state.context = cx.state.context.prev;
      }
      function pushlex(type, info) {
        var result = function() {
          var state = cx.state;
          state.lexical = new JSLexical(state.indented, cx.stream.column(), type, null, state.lexical, info)
        };
        result.lex = true;
        return result;
      }
      function poplex() {
        var state = cx.state;
        if (state.lexical.prev) {
          if (state.lexical.type == ")")
            state.indented = state.lexical.indented;
          state.lexical = state.lexical.prev;
        }
      }
      poplex.lex = true;
    
      function expect(wanted) {
        return function expecting(type) {
          if (type == wanted) return cont();
          else if (wanted == ";") return pass();
          else return cont(arguments.callee);
        };
      }
    
      function statement(type) {
        if (type == "var") return cont(pushlex("vardef"), vardef1, expect(";"), poplex);
        if (type == "keyword a") return cont(pushlex("form"), expression, statement, poplex);
        if (type == "keyword b") return cont(pushlex("form"), statement, poplex);
        if (type == "{") return cont(pushlex("}"), block, poplex);
        if (type == ";") return cont();
        if (type == "function") return cont(functiondef);
        if (type == "for") return cont(pushlex("form"), expect("("), pushlex(")"), forspec1, expect(")"),
                                          poplex, statement, poplex);
        if (type == "variable") return cont(pushlex("stat"), maybelabel);
        if (type == "switch") return cont(pushlex("form"), expression, pushlex("}", "switch"), expect("{"),
                                             block, poplex, poplex);
        if (type == "case") return cont(expression, expect(":"));
        if (type == "default") return cont(expect(":"));
        if (type == "catch") return cont(pushlex("form"), pushcontext, expect("("), funarg, expect(")"),
                                            statement, poplex, popcontext);
        return pass(pushlex("stat"), expression, expect(";"), poplex);
      }
      function expression(type) {
        if (atomicTypes.hasOwnProperty(type)) return cont(maybeoperator);
        if (type == "function") return cont(functiondef);
        if (type == "keyword c") return cont(maybeexpression);
        if (type == "(") return cont(pushlex(")"), maybeexpression, expect(")"), poplex, maybeoperator);
        if (type == "operator") return cont(expression);
        if (type == "[") return cont(pushlex("]"), commasep(expression, "]"), poplex, maybeoperator);
        if (type == "{") return cont(pushlex("}"), commasep(objprop, "}"), poplex, maybeoperator);
        return cont();
      }
      function maybeexpression(type) {
        if (type.match(/[;\}\)\],]/)) return pass();
        return pass(expression);
      }
        
      function maybeoperator(type, value) {
        if (type == "operator" && /\+\+|--/.test(value)) return cont(maybeoperator);
        if (type == "operator") return cont(expression);
        if (type == ";") return;
        if (type == "(") return cont(pushlex(")"), commasep(expression, ")"), poplex, maybeoperator);
        if (type == ".") return cont(property, maybeoperator);
        if (type == "[") return cont(pushlex("]"), expression, expect("]"), poplex, maybeoperator);
      }
      function maybelabel(type) {
        if (type == ":") return cont(poplex, statement);
        return pass(maybeoperator, expect(";"), poplex);
      }
      function property(type) {
        if (type == "variable") {cx.marked = "property"; return cont();}
      }
      function objprop(type) {
        if (type == "variable") cx.marked = "property";
        if (atomicTypes.hasOwnProperty(type)) return cont(expect(":"), expression);
      }
      function commasep(what, end) {
        function proceed(type) {
          if (type == ",") return cont(what, proceed);
          if (type == end) return cont();
          return cont(expect(end));
        }
        return function commaSeparated(type) {
          if (type == end) return cont();
          else return pass(what, proceed);
        };
      }
      function block(type) {
        if (type == "}") return cont();
        return pass(statement, block);
      }
      function vardef1(type, value) {
        if (type == "variable"){register(value); return cont(vardef2);}
        return cont();
      }
      function vardef2(type, value) {
        if (value == "=") return cont(expression, vardef2);
        if (type == ",") return cont(vardef1);
      }
      function forspec1(type) {
        if (type == "var") return cont(vardef1, forspec2);
        if (type == ";") return pass(forspec2);
        if (type == "variable") return cont(formaybein);
        return pass(forspec2);
      }
      function formaybein(type, value) {
        if (value == "in") return cont(expression);
        return cont(maybeoperator, forspec2);
      }
      function forspec2(type, value) {
        if (type == ";") return cont(forspec3);
        if (value == "in") return cont(expression);
        return cont(expression, expect(";"), forspec3);
      }
      function forspec3(type) {
        if (type != ")") cont(expression);
      }
      function functiondef(type, value) {
        if (type == "variable") {register(value); return cont(functiondef);}
        if (type == "(") return cont(pushlex(")"), pushcontext, commasep(funarg, ")"), poplex, statement, popcontext);
      }
      function funarg(type, value) {
        if (type == "variable") {register(value); return cont();}
      }
    
      // Interface
    
      return {
        startState: function(basecolumn) {
          return {
            tokenize: jsTokenBase,
            reAllowed: true,
            kwAllowed: true,
            cc: [],
            lexical: new JSLexical((basecolumn || 0) - indentUnit, 0, "block", false),
            localVars: null,
            context: null,
            indented: 0
          };
        },
    
        token: function(stream, state) {
          if (stream.sol()) {
            if (!state.lexical.hasOwnProperty("align"))
              state.lexical.align = false;
            state.indented = stream.indentation();
          }
          if (stream.eatSpace()) return null;
          var style = state.tokenize(stream, state);
          if (type == "comment") return style;
          state.reAllowed = type == "operator" || type == "keyword c" || type.match(/^[\[{}\(,;:]$/);
          state.kwAllowed = type != '.';
          return parseJS(state, style, type, content, stream);
        },
    
        indent: function(state, textAfter) {
          if (state.tokenize != jsTokenBase) return 0;
          var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical,
              type = lexical.type, closing = firstChar == type;
          if (type == "vardef") return lexical.indented + 4;
          else if (type == "form" && firstChar == "{") return lexical.indented;
          else if (type == "stat" || type == "form") return lexical.indented + indentUnit;
          else if (lexical.info == "switch" && !closing)
            return lexical.indented + (/^(?:case|default)\b/.test(textAfter) ? indentUnit : 2 * indentUnit);
          else if (lexical.align) return lexical.column + (closing ? 0 : 1);
          else return lexical.indented + (closing ? 0 : indentUnit);
        },
    
        electricChars: ":{}"
      };
    });
    
    CodeMirror.defineMIME("text/javascript", "javascript");
    CodeMirror.defineMIME("application/json", {name: "javascript", json: true});

    CodeMirror.defineMode("javascript", function(config, parserConfig) {
      var indentUnit = config.indentUnit;
      var jsonMode = parserConfig.json;
    
      // Tokenizer
    
      var keywords = function(){
        function kw(type) {return {type: type, style: "keyword"};}
        var A = kw("keyword a"), B = kw("keyword b"), C = kw("keyword c");
        var operator = kw("operator"), atom = {type: "atom", style: "atom"};
        return {
          "if": A, "while": A, "with": A, "else": B, "do": B, "try": B, "finally": B,
          "return": C, "break": C, "continue": C, "new": C, "delete": C, "throw": C,
          "var": kw("var"), "const": kw("var"), "let": kw("var"),
          "function": kw("function"), "catch": kw("catch"),
          "for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"),
          "in": operator, "typeof": operator, "instanceof": operator,
          "true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom
        };
      }();
    
      var isOperatorChar = /[+\-*&%=<>!?|]/;
    
      function chain(stream, state, f) {
        state.tokenize = f;
        return f(stream, state);
      }
    
      function nextUntilUnescaped(stream, end) {
        var escaped = false, next;
        while ((next = stream.next()) != null) {
          if (next == end && !escaped)
            return false;
          escaped = !escaped && next == "\\";
        }
        return escaped;
      }
    
      // Used as scratch variables to communicate multiple values without
      // consing up tons of objects.
      var type, content;
      function ret(tp, style, cont) {
        type = tp; content = cont;
        return style;
      }
    
      function jsTokenBase(stream, state) {
        var ch = stream.next();
        if (ch == '"' || ch == "'")
          return chain(stream, state, jsTokenString(ch));
        else if (/[\[\]{}\(\),;\:\.]/.test(ch))
          return ret(ch);
        else if (ch == "0" && stream.eat(/x/i)) {
          stream.eatWhile(/[\da-f]/i);
          return ret("number", "number");
        }      
        else if (/\d/.test(ch)) {
          stream.match(/^\d*(?:\.\d*)?(?:[eE][+\-]?\d+)?/);
          return ret("number", "number");
        }
        else if (ch == "/") {
          if (stream.eat("*")) {
            return chain(stream, state, jsTokenComment);
          }
          else if (stream.eat("/")) {
            stream.skipToEnd();
            return ret("comment", "comment");
          }
          else if (state.reAllowed) {
            nextUntilUnescaped(stream, "/");
            stream.eatWhile(/[gimy]/); // 'y' is "sticky" option in Mozilla
            return ret("regexp", "string-2");
          }
          else {
            stream.eatWhile(isOperatorChar);
            return ret("operator", null, stream.current());
          }
        }
        else if (ch == "#") {
            stream.skipToEnd();
            return ret("error", "error");
        }
        else if (isOperatorChar.test(ch)) {
          stream.eatWhile(isOperatorChar);
          return ret("operator", null, stream.current());
        }
        else {
          stream.eatWhile(/[\w\$_]/);
          var word = stream.current(), known = keywords.propertyIsEnumerable(word) && keywords[word];
          return (known && state.kwAllowed) ? ret(known.type, known.style, word) :
                         ret("variable", "variable", word);
        }
      }
    
      function jsTokenString(quote) {
        return function(stream, state) {
          if (!nextUntilUnescaped(stream, quote))
            state.tokenize = jsTokenBase;
          return ret("string", "string");
        };
      }
    
      function jsTokenComment(stream, state) {
        var maybeEnd = false, ch;
        while (ch = stream.next()) {
          if (ch == "/" && maybeEnd) {
            state.tokenize = jsTokenBase;
            break;
          }
          maybeEnd = (ch == "*");
        }
        return ret("comment", "comment");
      }
    
      // Parser
    
      var atomicTypes = {"atom": true, "number": true, "variable": true, "string": true, "regexp": true};
    
      function JSLexical(indented, column, type, align, prev, info) {
        this.indented = indented;
        this.column = column;
        this.type = type;
        this.prev = prev;
        this.info = info;
        if (align != null) this.align = align;
      }
    
      function inScope(state, varname) {
        for (var v = state.localVars; v; v = v.next)
          if (v.name == varname) return true;
      }
    
      function parseJS(state, style, type, content, stream) {
        var cc = state.cc;
        // Communicate our context to the combinators.
        // (Less wasteful than consing up a hundred closures on every call.)
        cx.state = state; cx.stream = stream; cx.marked = null, cx.cc = cc;
      
        if (!state.lexical.hasOwnProperty("align"))
          state.lexical.align = true;
    
        while(true) {
          var combinator = cc.length ? cc.pop() : jsonMode ? expression : statement;
          if (combinator(type, content)) {
            while(cc.length && cc[cc.length - 1].lex)
              cc.pop()();
            if (cx.marked) return cx.marked;
            if (type == "variable" && inScope(state, content)) return "variable-2";
            return style;
          }
        }
      }
    
      // Combinator utils
    
      var cx = {state: null, column: null, marked: null, cc: null};
      function pass() {
        for (var i = arguments.length - 1; i >= 0; i--) cx.cc.push(arguments[i]);
      }
      function cont() {
        pass.apply(null, arguments);
        return true;
      }
      function register(varname) {
        var state = cx.state;
        if (state.context) {
          cx.marked = "def";
          for (var v = state.localVars; v; v = v.next)
            if (v.name == varname) return;
          state.localVars = {name: varname, next: state.localVars};
        }
      }
    
      // Combinators
    
      var defaultVars = {name: "this", next: {name: "arguments"}};
      function pushcontext() {
        if (!cx.state.context) cx.state.localVars = defaultVars;
        cx.state.context = {prev: cx.state.context, vars: cx.state.localVars};
      }
      function popcontext() {
        cx.state.localVars = cx.state.context.vars;
        cx.state.context = cx.state.context.prev;
      }
      function pushlex(type, info) {
        var result = function() {
          var state = cx.state;
          state.lexical = new JSLexical(state.indented, cx.stream.column(), type, null, state.lexical, info)
        };
        result.lex = true;
        return result;
      }
      function poplex() {
        var state = cx.state;
        if (state.lexical.prev) {
          if (state.lexical.type == ")")
            state.indented = state.lexical.indented;
          state.lexical = state.lexical.prev;
        }
      }
      poplex.lex = true;
    
      function expect(wanted) {
        return function expecting(type) {
          if (type == wanted) return cont();
          else if (wanted == ";") return pass();
          else return cont(arguments.callee);
        };
      }
    
      function statement(type) {
        if (type == "var") return cont(pushlex("vardef"), vardef1, expect(";"), poplex);
        if (type == "keyword a") return cont(pushlex("form"), expression, statement, poplex);
        if (type == "keyword b") return cont(pushlex("form"), statement, poplex);
        if (type == "{") return cont(pushlex("}"), block, poplex);
        if (type == ";") return cont();
        if (type == "function") return cont(functiondef);
        if (type == "for") return cont(pushlex("form"), expect("("), pushlex(")"), forspec1, expect(")"),
                                          poplex, statement, poplex);
        if (type == "variable") return cont(pushlex("stat"), maybelabel);
        if (type == "switch") return cont(pushlex("form"), expression, pushlex("}", "switch"), expect("{"),
                                             block, poplex, poplex);
        if (type == "case") return cont(expression, expect(":"));
        if (type == "default") return cont(expect(":"));
        if (type == "catch") return cont(pushlex("form"), pushcontext, expect("("), funarg, expect(")"),
                                            statement, poplex, popcontext);
        return pass(pushlex("stat"), expression, expect(";"), poplex);
      }
      function expression(type) {
        if (atomicTypes.hasOwnProperty(type)) return cont(maybeoperator);
        if (type == "function") return cont(functiondef);
        if (type == "keyword c") return cont(maybeexpression);
        if (type == "(") return cont(pushlex(")"), maybeexpression, expect(")"), poplex, maybeoperator);
        if (type == "operator") return cont(expression);
        if (type == "[") return cont(pushlex("]"), commasep(expression, "]"), poplex, maybeoperator);
        if (type == "{") return cont(pushlex("}"), commasep(objprop, "}"), poplex, maybeoperator);
        return cont();
      }
      function maybeexpression(type) {
        if (type.match(/[;\}\)\],]/)) return pass();
        return pass(expression);
      }
        
      function maybeoperator(type, value) {
        if (type == "operator" && /\+\+|--/.test(value)) return cont(maybeoperator);
        if (type == "operator") return cont(expression);
        if (type == ";") return;
        if (type == "(") return cont(pushlex(")"), commasep(expression, ")"), poplex, maybeoperator);
        if (type == ".") return cont(property, maybeoperator);
        if (type == "[") return cont(pushlex("]"), expression, expect("]"), poplex, maybeoperator);
      }
      function maybelabel(type) {
        if (type == ":") return cont(poplex, statement);
        return pass(maybeoperator, expect(";"), poplex);
      }
      function property(type) {
        if (type == "variable") {cx.marked = "property"; return cont();}
      }
      function objprop(type) {
        if (type == "variable") cx.marked = "property";
        if (atomicTypes.hasOwnProperty(type)) return cont(expect(":"), expression);
      }
      function commasep(what, end) {
        function proceed(type) {
          if (type == ",") return cont(what, proceed);
          if (type == end) return cont();
          return cont(expect(end));
        }
        return function commaSeparated(type) {
          if (type == end) return cont();
          else return pass(what, proceed);
        };
      }
      function block(type) {
        if (type == "}") return cont();
        return pass(statement, block);
      }
      function vardef1(type, value) {
        if (type == "variable"){register(value); return cont(vardef2);}
        return cont();
      }
      function vardef2(type, value) {
        if (value == "=") return cont(expression, vardef2);
        if (type == ",") return cont(vardef1);
      }
      function forspec1(type) {
        if (type == "var") return cont(vardef1, forspec2);
        if (type == ";") return pass(forspec2);
        if (type == "variable") return cont(formaybein);
        return pass(forspec2);
      }
      function formaybein(type, value) {
        if (value == "in") return cont(expression);
        return cont(maybeoperator, forspec2);
      }
      function forspec2(type, value) {
        if (type == ";") return cont(forspec3);
        if (value == "in") return cont(expression);
        return cont(expression, expect(";"), forspec3);
      }
      function forspec3(type) {
        if (type != ")") cont(expression);
      }
      function functiondef(type, value) {
        if (type == "variable") {register(value); return cont(functiondef);}
        if (type == "(") return cont(pushlex(")"), pushcontext, commasep(funarg, ")"), poplex, statement, popcontext);
      }
      function funarg(type, value) {
        if (type == "variable") {register(value); return cont();}
      }
    
      // Interface
    
      return {
        startState: function(basecolumn) {
          return {
            tokenize: jsTokenBase,
            reAllowed: true,
            kwAllowed: true,
            cc: [],
            lexical: new JSLexical((basecolumn || 0) - indentUnit, 0, "block", false),
            localVars: null,
            context: null,
            indented: 0
          };
        },
    
        token: function(stream, state) {
          if (stream.sol()) {
            if (!state.lexical.hasOwnProperty("align"))
              state.lexical.align = false;
            state.indented = stream.indentation();
          }
          if (stream.eatSpace()) return null;
          var style = state.tokenize(stream, state);
          if (type == "comment") return style;
          state.reAllowed = type == "operator" || type == "keyword c" || type.match(/^[\[{}\(,;:]$/);
          state.kwAllowed = type != '.';
          return parseJS(state, style, type, content, stream);
        },
    
        indent: function(state, textAfter) {
          if (state.tokenize != jsTokenBase) return 0;
          var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical,
              type = lexical.type, closing = firstChar == type;
          if (type == "vardef") return lexical.indented + 4;
          else if (type == "form" && firstChar == "{") return lexical.indented;
          else if (type == "stat" || type == "form") return lexical.indented + indentUnit;
          else if (lexical.info == "switch" && !closing)
            return lexical.indented + (/^(?:case|default)\b/.test(textAfter) ? indentUnit : 2 * indentUnit);
          else if (lexical.align) return lexical.column + (closing ? 0 : 1);
          else return lexical.indented + (closing ? 0 : indentUnit);
        },
    
        electricChars: ":{}"
      };
    });
    
    CodeMirror.defineMIME("text/javascript", "javascript");
    CodeMirror.defineMIME("application/json", {name: "javascript", json: true});

    CodeMirror.defineMode("javascript", function(config, parserConfig) {
      var indentUnit = config.indentUnit;
      var jsonMode = parserConfig.json;
    
      // Tokenizer
    
      var keywords = function(){
        function kw(type) {return {type: type, style: "keyword"};}
        var A = kw("keyword a"), B = kw("keyword b"), C = kw("keyword c");
        var operator = kw("operator"), atom = {type: "atom", style: "atom"};
        return {
          "if": A, "while": A, "with": A, "else": B, "do": B, "try": B, "finally": B,
          "return": C, "break": C, "continue": C, "new": C, "delete": C, "throw": C,
          "var": kw("var"), "const": kw("var"), "let": kw("var"),
          "function": kw("function"), "catch": kw("catch"),
          "for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"),
          "in": operator, "typeof": operator, "instanceof": operator,
          "true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom
        };
      }();
    
      var isOperatorChar = /[+\-*&%=<>!?|]/;
    
      function chain(stream, state, f) {
        state.tokenize = f;
        return f(stream, state);
      }
    
      function nextUntilUnescaped(stream, end) {
        var escaped = false, next;
        while ((next = stream.next()) != null) {
          if (next == end && !escaped)
            return false;
          escaped = !escaped && next == "\\";
        }
        return escaped;
      }
    
      // Used as scratch variables to communicate multiple values without
      // consing up tons of objects.
      var type, content;
      function ret(tp, style, cont) {
        type = tp; content = cont;
        return style;
      }
    
      function jsTokenBase(stream, state) {
        var ch = stream.next();
        if (ch == '"' || ch == "'")
          return chain(stream, state, jsTokenString(ch));
        else if (/[\[\]{}\(\),;\:\.]/.test(ch))
          return ret(ch);
        else if (ch == "0" && stream.eat(/x/i)) {
          stream.eatWhile(/[\da-f]/i);
          return ret("number", "number");
        }      
        else if (/\d/.test(ch)) {
          stream.match(/^\d*(?:\.\d*)?(?:[eE][+\-]?\d+)?/);
          return ret("number", "number");
        }
        else if (ch == "/") {
          if (stream.eat("*")) {
            return chain(stream, state, jsTokenComment);
          }
          else if (stream.eat("/")) {
            stream.skipToEnd();
            return ret("comment", "comment");
          }
          else if (state.reAllowed) {
            nextUntilUnescaped(stream, "/");
            stream.eatWhile(/[gimy]/); // 'y' is "sticky" option in Mozilla
            return ret("regexp", "string-2");
          }
          else {
            stream.eatWhile(isOperatorChar);
            return ret("operator", null, stream.current());
          }
        }
        else if (ch == "#") {
            stream.skipToEnd();
            return ret("error", "error");
        }
        else if (isOperatorChar.test(ch)) {
          stream.eatWhile(isOperatorChar);
          return ret("operator", null, stream.current());
        }
        else {
          stream.eatWhile(/[\w\$_]/);
          var word = stream.current(), known = keywords.propertyIsEnumerable(word) && keywords[word];
          return (known && state.kwAllowed) ? ret(known.type, known.style, word) :
                         ret("variable", "variable", word);
        }
      }
    
      function jsTokenString(quote) {
        return function(stream, state) {
          if (!nextUntilUnescaped(stream, quote))
            state.tokenize = jsTokenBase;
          return ret("string", "string");
        };
      }
    
      function jsTokenComment(stream, state) {
        var maybeEnd = false, ch;
        while (ch = stream.next()) {
          if (ch == "/" && maybeEnd) {
            state.tokenize = jsTokenBase;
            break;
          }
          maybeEnd = (ch == "*");
        }
        return ret("comment", "comment");
      }
    
      // Parser
    
      var atomicTypes = {"atom": true, "number": true, "variable": true, "string": true, "regexp": true};
    
      function JSLexical(indented, column, type, align, prev, info) {
        this.indented = indented;
        this.column = column;
        this.type = type;
        this.prev = prev;
        this.info = info;
        if (align != null) this.align = align;
      }
    
      function inScope(state, varname) {
        for (var v = state.localVars; v; v = v.next)
          if (v.name == varname) return true;
      }
    
      function parseJS(state, style, type, content, stream) {
        var cc = state.cc;
        // Communicate our context to the combinators.
        // (Less wasteful than consing up a hundred closures on every call.)
        cx.state = state; cx.stream = stream; cx.marked = null, cx.cc = cc;
      
        if (!state.lexical.hasOwnProperty("align"))
          state.lexical.align = true;
    
        while(true) {
          var combinator = cc.length ? cc.pop() : jsonMode ? expression : statement;
          if (combinator(type, content)) {
            while(cc.length && cc[cc.length - 1].lex)
              cc.pop()();
            if (cx.marked) return cx.marked;
            if (type == "variable" && inScope(state, content)) return "variable-2";
            return style;
          }
        }
      }
    
      // Combinator utils
    
      var cx = {state: null, column: null, marked: null, cc: null};
      function pass() {
        for (var i = arguments.length - 1; i >= 0; i--) cx.cc.push(arguments[i]);
      }
      function cont() {
        pass.apply(null, arguments);
        return true;
      }
      function register(varname) {
        var state = cx.state;
        if (state.context) {
          cx.marked = "def";
          for (var v = state.localVars; v; v = v.next)
            if (v.name == varname) return;
          state.localVars = {name: varname, next: state.localVars};
        }
      }
    
      // Combinators
    
      var defaultVars = {name: "this", next: {name: "arguments"}};
      function pushcontext() {
        if (!cx.state.context) cx.state.localVars = defaultVars;
        cx.state.context = {prev: cx.state.context, vars: cx.state.localVars};
      }
      function popcontext() {
        cx.state.localVars = cx.state.context.vars;
        cx.state.context = cx.state.context.prev;
      }
      function pushlex(type, info) {
        var result = function() {
          var state = cx.state;
          state.lexical = new JSLexical(state.indented, cx.stream.column(), type, null, state.lexical, info)
        };
        result.lex = true;
        return result;
      }
      function poplex() {
        var state = cx.state;
        if (state.lexical.prev) {
          if (state.lexical.type == ")")
            state.indented = state.lexical.indented;
          state.lexical = state.lexical.prev;
        }
      }
      poplex.lex = true;
    
      function expect(wanted) {
        return function expecting(type) {
          if (type == wanted) return cont();
          else if (wanted == ";") return pass();
          else return cont(arguments.callee);
        };
      }
    
      function statement(type) {
        if (type == "var") return cont(pushlex("vardef"), vardef1, expect(";"), poplex);
        if (type == "keyword a") return cont(pushlex("form"), expression, statement, poplex);
        if (type == "keyword b") return cont(pushlex("form"), statement, poplex);
        if (type == "{") return cont(pushlex("}"), block, poplex);
        if (type == ";") return cont();
        if (type == "function") return cont(functiondef);
        if (type == "for") return cont(pushlex("form"), expect("("), pushlex(")"), forspec1, expect(")"),
                                          poplex, statement, poplex);
        if (type == "variable") return cont(pushlex("stat"), maybelabel);
        if (type == "switch") return cont(pushlex("form"), expression, pushlex("}", "switch"), expect("{"),
                                             block, poplex, poplex);
        if (type == "case") return cont(expression, expect(":"));
        if (type == "default") return cont(expect(":"));
        if (type == "catch") return cont(pushlex("form"), pushcontext, expect("("), funarg, expect(")"),
                                            statement, poplex, popcontext);
        return pass(pushlex("stat"), expression, expect(";"), poplex);
      }
      function expression(type) {
        if (atomicTypes.hasOwnProperty(type)) return cont(maybeoperator);
        if (type == "function") return cont(functiondef);
        if (type == "keyword c") return cont(maybeexpression);
        if (type == "(") return cont(pushlex(")"), maybeexpression, expect(")"), poplex, maybeoperator);
        if (type == "operator") return cont(expression);
        if (type == "[") return cont(pushlex("]"), commasep(expression, "]"), poplex, maybeoperator);
        if (type == "{") return cont(pushlex("}"), commasep(objprop, "}"), poplex, maybeoperator);
        return cont();
      }
      function maybeexpression(type) {
        if (type.match(/[;\}\)\],]/)) return pass();
        return pass(expression);
      }
        
      function maybeoperator(type, value) {
        if (type == "operator" && /\+\+|--/.test(value)) return cont(maybeoperator);
        if (type == "operator") return cont(expression);
        if (type == ";") return;
        if (type == "(") return cont(pushlex(")"), commasep(expression, ")"), poplex, maybeoperator);
        if (type == ".") return cont(property, maybeoperator);
        if (type == "[") return cont(pushlex("]"), expression, expect("]"), poplex, maybeoperator);
      }
      function maybelabel(type) {
        if (type == ":") return cont(poplex, statement);
        return pass(maybeoperator, expect(";"), poplex);
      }
      function property(type) {
        if (type == "variable") {cx.marked = "property"; return cont();}
      }
      function objprop(type) {
        if (type == "variable") cx.marked = "property";
        if (atomicTypes.hasOwnProperty(type)) return cont(expect(":"), expression);
      }
      function commasep(what, end) {
        function proceed(type) {
          if (type == ",") return cont(what, proceed);
          if (type == end) return cont();
          return cont(expect(end));
        }
        return function commaSeparated(type) {
          if (type == end) return cont();
          else return pass(what, proceed);
        };
      }
      function block(type) {
        if (type == "}") return cont();
        return pass(statement, block);
      }
      function vardef1(type, value) {
        if (type == "variable"){register(value); return cont(vardef2);}
        return cont();
      }
      function vardef2(type, value) {
        if (value == "=") return cont(expression, vardef2);
        if (type == ",") return cont(vardef1);
      }
      function forspec1(type) {
        if (type == "var") return cont(vardef1, forspec2);
        if (type == ";") return pass(forspec2);
        if (type == "variable") return cont(formaybein);
        return pass(forspec2);
      }
      function formaybein(type, value) {
        if (value == "in") return cont(expression);
        return cont(maybeoperator, forspec2);
      }
      function forspec2(type, value) {
        if (type == ";") return cont(forspec3);
        if (value == "in") return cont(expression);
        return cont(expression, expect(";"), forspec3);
      }
      function forspec3(type) {
        if (type != ")") cont(expression);
      }
      function functiondef(type, value) {
        if (type == "variable") {register(value); return cont(functiondef);}
        if (type == "(") return cont(pushlex(")"), pushcontext, commasep(funarg, ")"), poplex, statement, popcontext);
      }
      function funarg(type, value) {
        if (type == "variable") {register(value); return cont();}
      }
    
      // Interface
    
      return {
        startState: function(basecolumn) {
          return {
            tokenize: jsTokenBase,
            reAllowed: true,
            kwAllowed: true,
            cc: [],
            lexical: new JSLexical((basecolumn || 0) - indentUnit, 0, "block", false),
            localVars: null,
            context: null,
            indented: 0
          };
        },
    
        token: function(stream, state) {
          if (stream.sol()) {
            if (!state.lexical.hasOwnProperty("align"))
              state.lexical.align = false;
            state.indented = stream.indentation();
          }
          if (stream.eatSpace()) return null;
          var style = state.tokenize(stream, state);
          if (type == "comment") return style;
          state.reAllowed = type == "operator" || type == "keyword c" || type.match(/^[\[{}\(,;:]$/);
          state.kwAllowed = type != '.';
          return parseJS(state, style, type, content, stream);
        },
    
        indent: function(state, textAfter) {
          if (state.tokenize != jsTokenBase) return 0;
          var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical,
              type = lexical.type, closing = firstChar == type;
          if (type == "vardef") return lexical.indented + 4;
          else if (type == "form" && firstChar == "{") return lexical.indented;
          else if (type == "stat" || type == "form") return lexical.indented + indentUnit;
          else if (lexical.info == "switch" && !closing)
            return lexical.indented + (/^(?:case|default)\b/.test(textAfter) ? indentUnit : 2 * indentUnit);
          else if (lexical.align) return lexical.column + (closing ? 0 : 1);
          else return lexical.indented + (closing ? 0 : indentUnit);
        },
    
        electricChars: ":{}"
      };
    });
    
    CodeMirror.defineMIME("text/javascript", "javascript");
    CodeMirror.defineMIME("application/json", {name: "javascript", json: true});

    CodeMirror.defineMode("javascript", function(config, parserConfig) {
      var indentUnit = config.indentUnit;
      var jsonMode = parserConfig.json;
    
      // Tokenizer
    
      var keywords = function(){
        function kw(type) {return {type: type, style: "keyword"};}
        var A = kw("keyword a"), B = kw("keyword b"), C = kw("keyword c");
        var operator = kw("operator"), atom = {type: "atom", style: "atom"};
        return {
          "if": A, "while": A, "with": A, "else": B, "do": B, "try": B, "finally": B,
          "return": C, "break": C, "continue": C, "new": C, "delete": C, "throw": C,
          "var": kw("var"), "const": kw("var"), "let": kw("var"),
          "function": kw("function"), "catch": kw("catch"),
          "for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"),
          "in": operator, "typeof": operator, "instanceof": operator,
          "true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom
        };
      }();
    
      var isOperatorChar = /[+\-*&%=<>!?|]/;
    
      function chain(stream, state, f) {
        state.tokenize = f;
        return f(stream, state);
      }
    
      function nextUntilUnescaped(stream, end) {
        var escaped = false, next;
        while ((next = stream.next()) != null) {
          if (next == end && !escaped)
            return false;
          escaped = !escaped && next == "\\";
        }
        return escaped;
      }
    
      // Used as scratch variables to communicate multiple values without
      // consing up tons of objects.
      var type, content;
      function ret(tp, style, cont) {
        type = tp; content = cont;
        return style;
      }
    
      function jsTokenBase(stream, state) {
        var ch = stream.next();
        if (ch == '"' || ch == "'")
          return chain(stream, state, jsTokenString(ch));
        else if (/[\[\]{}\(\),;\:\.]/.test(ch))
          return ret(ch);
        else if (ch == "0" && stream.eat(/x/i)) {
          stream.eatWhile(/[\da-f]/i);
          return ret("number", "number");
        }      
        else if (/\d/.test(ch)) {
          stream.match(/^\d*(?:\.\d*)?(?:[eE][+\-]?\d+)?/);
          return ret("number", "number");
        }
        else if (ch == "/") {
          if (stream.eat("*")) {
            return chain(stream, state, jsTokenComment);
          }
          else if (stream.eat("/")) {
            stream.skipToEnd();
            return ret("comment", "comment");
          }
          else if (state.reAllowed) {
            nextUntilUnescaped(stream, "/");
            stream.eatWhile(/[gimy]/); // 'y' is "sticky" option in Mozilla
            return ret("regexp", "string-2");
          }
          else {
            stream.eatWhile(isOperatorChar);
            return ret("operator", null, stream.current());
          }
        }
        else if (ch == "#") {
            stream.skipToEnd();
            return ret("error", "error");
        }
        else if (isOperatorChar.test(ch)) {
          stream.eatWhile(isOperatorChar);
          return ret("operator", null, stream.current());
        }
        else {
          stream.eatWhile(/[\w\$_]/);
          var word = stream.current(), known = keywords.propertyIsEnumerable(word) && keywords[word];
          return (known && state.kwAllowed) ? ret(known.type, known.style, word) :
                         ret("variable", "variable", word);
        }
      }
    
      function jsTokenString(quote) {
        return function(stream, state) {
          if (!nextUntilUnescaped(stream, quote))
            state.tokenize = jsTokenBase;
          return ret("string", "string");
        };
      }
    
      function jsTokenComment(stream, state) {
        var maybeEnd = false, ch;
        while (ch = stream.next()) {
          if (ch == "/" && maybeEnd) {
            state.tokenize = jsTokenBase;
            break;
          }
          maybeEnd = (ch == "*");
        }
        return ret("comment", "comment");
      }
    
      // Parser
    
      var atomicTypes = {"atom": true, "number": true, "variable": true, "string": true, "regexp": true};
    
      function JSLexical(indented, column, type, align, prev, info) {
        this.indented = indented;
        this.column = column;
        this.type = type;
        this.prev = prev;
        this.info = info;
        if (align != null) this.align = align;
      }
    
      function inScope(state, varname) {
        for (var v = state.localVars; v; v = v.next)
          if (v.name == varname) return true;
      }
    
      function parseJS(state, style, type, content, stream) {
        var cc = state.cc;
        // Communicate our context to the combinators.
        // (Less wasteful than consing up a hundred closures on every call.)
        cx.state = state; cx.stream = stream; cx.marked = null, cx.cc = cc;
      
        if (!state.lexical.hasOwnProperty("align"))
          state.lexical.align = true;
    
        while(true) {
          var combinator = cc.length ? cc.pop() : jsonMode ? expression : statement;
          if (combinator(type, content)) {
            while(cc.length && cc[cc.length - 1].lex)
              cc.pop()();
            if (cx.marked) return cx.marked;
            if (type == "variable" && inScope(state, content)) return "variable-2";
            return style;
          }
        }
      }
    
      // Combinator utils
    
      var cx = {state: null, column: null, marked: null, cc: null};
      function pass() {
        for (var i = arguments.length - 1; i >= 0; i--) cx.cc.push(arguments[i]);
      }
      function cont() {
        pass.apply(null, arguments);
        return true;
      }
      function register(varname) {
        var state = cx.state;
        if (state.context) {
          cx.marked = "def";
          for (var v = state.localVars; v; v = v.next)
            if (v.name == varname) return;
          state.localVars = {name: varname, next: state.localVars};
        }
      }
    
      // Combinators
    
      var defaultVars = {name: "this", next: {name: "arguments"}};
      function pushcontext() {
        if (!cx.state.context) cx.state.localVars = defaultVars;
        cx.state.context = {prev: cx.state.context, vars: cx.state.localVars};
      }
      function popcontext() {
        cx.state.localVars = cx.state.context.vars;
        cx.state.context = cx.state.context.prev;
      }
      function pushlex(type, info) {
        var result = function() {
          var state = cx.state;
          state.lexical = new JSLexical(state.indented, cx.stream.column(), type, null, state.lexical, info)
        };
        result.lex = true;
        return result;
      }
      function poplex() {
        var state = cx.state;
        if (state.lexical.prev) {
          if (state.lexical.type == ")")
            state.indented = state.lexical.indented;
          state.lexical = state.lexical.prev;
        }
      }
      poplex.lex = true;
    
      function expect(wanted) {
        return function expecting(type) {
          if (type == wanted) return cont();
          else if (wanted == ";") return pass();
          else return cont(arguments.callee);
        };
      }
    
      function statement(type) {
        if (type == "var") return cont(pushlex("vardef"), vardef1, expect(";"), poplex);
        if (type == "keyword a") return cont(pushlex("form"), expression, statement, poplex);
        if (type == "keyword b") return cont(pushlex("form"), statement, poplex);
        if (type == "{") return cont(pushlex("}"), block, poplex);
        if (type == ";") return cont();
        if (type == "function") return cont(functiondef);
        if (type == "for") return cont(pushlex("form"), expect("("), pushlex(")"), forspec1, expect(")"),
                                          poplex, statement, poplex);
        if (type == "variable") return cont(pushlex("stat"), maybelabel);
        if (type == "switch") return cont(pushlex("form"), expression, pushlex("}", "switch"), expect("{"),
                                             block, poplex, poplex);
        if (type == "case") return cont(expression, expect(":"));
        if (type == "default") return cont(expect(":"));
        if (type == "catch") return cont(pushlex("form"), pushcontext, expect("("), funarg, expect(")"),
                                            statement, poplex, popcontext);
        return pass(pushlex("stat"), expression, expect(";"), poplex);
      }
      function expression(type) {
        if (atomicTypes.hasOwnProperty(type)) return cont(maybeoperator);
        if (type == "function") return cont(functiondef);
        if (type == "keyword c") return cont(maybeexpression);
        if (type == "(") return cont(pushlex(")"), maybeexpression, expect(")"), poplex, maybeoperator);
        if (type == "operator") return cont(expression);
        if (type == "[") return cont(pushlex("]"), commasep(expression, "]"), poplex, maybeoperator);
        if (type == "{") return cont(pushlex("}"), commasep(objprop, "}"), poplex, maybeoperator);
        return cont();
      }
      function maybeexpression(type) {
        if (type.match(/[;\}\)\],]/)) return pass();
        return pass(expression);
      }
        
      function maybeoperator(type, value) {
        if (type == "operator" && /\+\+|--/.test(value)) return cont(maybeoperator);
        if (type == "operator") return cont(expression);
        if (type == ";") return;
        if (type == "(") return cont(pushlex(")"), commasep(expression, ")"), poplex, maybeoperator);
        if (type == ".") return cont(property, maybeoperator);
        if (type == "[") return cont(pushlex("]"), expression, expect("]"), poplex, maybeoperator);
      }
      function maybelabel(type) {
        if (type == ":") return cont(poplex, statement);
        return pass(maybeoperator, expect(";"), poplex);
      }
      function property(type) {
        if (type == "variable") {cx.marked = "property"; return cont();}
      }
      function objprop(type) {
        if (type == "variable") cx.marked = "property";
        if (atomicTypes.hasOwnProperty(type)) return cont(expect(":"), expression);
      }
      function commasep(what, end) {
        function proceed(type) {
          if (type == ",") return cont(what, proceed);
          if (type == end) return cont();
          return cont(expect(end));
        }
        return function commaSeparated(type) {
          if (type == end) return cont();
          else return pass(what, proceed);
        };
      }
      function block(type) {
        if (type == "}") return cont();
        return pass(statement, block);
      }
      function vardef1(type, value) {
        if (type == "variable"){register(value); return cont(vardef2);}
        return cont();
      }
      function vardef2(type, value) {
        if (value == "=") return cont(expression, vardef2);
        if (type == ",") return cont(vardef1);
      }
      function forspec1(type) {
        if (type == "var") return cont(vardef1, forspec2);
        if (type == ";") return pass(forspec2);
        if (type == "variable") return cont(formaybein);
        return pass(forspec2);
      }
      function formaybein(type, value) {
        if (value == "in") return cont(expression);
        return cont(maybeoperator, forspec2);
      }
      function forspec2(type, value) {
        if (type == ";") return cont(forspec3);
        if (value == "in") return cont(expression);
        return cont(expression, expect(";"), forspec3);
      }
      function forspec3(type) {
        if (type != ")") cont(expression);
      }
      function functiondef(type, value) {
        if (type == "variable") {register(value); return cont(functiondef);}
        if (type == "(") return cont(pushlex(")"), pushcontext, commasep(funarg, ")"), poplex, statement, popcontext);
      }
      function funarg(type, value) {
        if (type == "variable") {register(value); return cont();}
      }
    
      // Interface
    
      return {
        startState: function(basecolumn) {
          return {
            tokenize: jsTokenBase,
            reAllowed: true,
            kwAllowed: true,
            cc: [],
            lexical: new JSLexical((basecolumn || 0) - indentUnit, 0, "block", false),
            localVars: null,
            context: null,
            indented: 0
          };
        },
    
        token: function(stream, state) {
          if (stream.sol()) {
            if (!state.lexical.hasOwnProperty("align"))
              state.lexical.align = false;
            state.indented = stream.indentation();
          }
          if (stream.eatSpace()) return null;
          var style = state.tokenize(stream, state);
          if (type == "comment") return style;
          state.reAllowed = type == "operator" || type == "keyword c" || type.match(/^[\[{}\(,;:]$/);
          state.kwAllowed = type != '.';
          return parseJS(state, style, type, content, stream);
        },
    
        indent: function(state, textAfter) {
          if (state.tokenize != jsTokenBase) return 0;
          var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical,
              type = lexical.type, closing = firstChar == type;
          if (type == "vardef") return lexical.indented + 4;
          else if (type == "form" && firstChar == "{") return lexical.indented;
          else if (type == "stat" || type == "form") return lexical.indented + indentUnit;
          else if (lexical.info == "switch" && !closing)
            return lexical.indented + (/^(?:case|default)\b/.test(textAfter) ? indentUnit : 2 * indentUnit);
          else if (lexical.align) return lexical.column + (closing ? 0 : 1);
          else return lexical.indented + (closing ? 0 : indentUnit);
        },
    
        electricChars: ":{}"
      };
    });
    
    CodeMirror.defineMIME("text/javascript", "javascript");
    CodeMirror.defineMIME("application/json", {name: "javascript", json: true});    
})();

1445
líneas

Organización del código JavaScript

  • ¿2000 líneas en un solo archivo?

Ventajas

Inconvenientes

  • Difícil de leer/entender
  • Difícil de mantener
  • Poca reusabilidad
  • Difícil encontrar código no usado
  • Colisiones de nombres
  • Una sola petición HTTP

Organización del código JavaScript

  • Optimización: dividir el código en varios archivos/módulos

<head>
    <meta charset="UTF-8">
    <title>Mi web</title>
    <script src="vendor/jquery/jquery.min.js"></script>
    <script src="js/modules/tabs.js"></script>
    <script src="js/modules/banners.js"></script>
    <script src="js/modules/lightbox.js"></script>   
    <script src="js/modules/scroll.js"></script>
    <script src="js/modules/carousel.js"></script>     
    <script src="js/modules/slideshow.js"></script>
    <script src="js/modules/gallery.js"></script>
    <script src="js/modules/navigation.js"></script>
</head>

Organización del código JavaScript

Ventajas

Inconvenientes

  • Difícil encontrar código no usado (menos difícil que antes)
  • Colisiones de nombres
  • Muchas peticiones HTTP
  • El orden importa: dependencias
  • Legible e inteligible
  • Fácil de mantener
  • Reutilizable
  • Cargamos sólo lo que necesitamos
<head>
    <meta charset="UTF-8">
    <title>Mi web</title>
    <script src="vendor/jquery/jquery.min.js"></script>
    <script src="js/modules/tabs.js"></script>
    <script src="js/modules/banners.js"></script>
    <script src="js/modules/lightbox.js"></script>   
    <script src="js/modules/scroll.js"></script>
    <script src="js/modules/carousel.js"></script>     
    <script src="js/modules/slideshow.js"></script>
    <script src="js/modules/gallery.js"></script>
    <script src="js/modules/navigation.js"></script>
</head>

Organización del código JavaScript

  • Dependencias: es difícil asegurar el orden, y no es posible tener dependencias circulares

<head>
    <meta charset="UTF-8">
    <title>Mi web</title>
    <script src="vendor/jquery/jquery.min.js"></script>
    <script src="js/modules/tabs.js"></script>
    <script src="js/modules/banners.js"></script>
    <script src="js/modules/lightbox.js"></script>   
    <script src="js/modules/scroll.js"></script>
    <script src="js/modules/carousel.js"></script>     
    <script src="js/modules/slideshow.js"></script>
    <script src="js/modules/gallery.js"></script>
    <script src="js/modules/navigation.js"></script>
</head>

Organización del código JavaScript: módulos

  • Module loaders: ellos gestionan las dependencias y cargan los módulos (RequireJS, SystemJS)

Ventajas

Inconvenientes

  • Difícil encontrar código no usado (menos difícil que antes)
  • Muchas peticiones HTTP
  • Legible e inteligible
  • Fácil de mantener
  • Reutilizable
  • Cargamos sólo lo que necesitamos
  • Gestión automática de dependencias
  • Encapsulación

Organización del código JavaScript: módulos

  • Module bundlers: además de lo anterior, generan un solo código encadenado y minificado (Browserify, webpack, Parcel)

Ventajas

  • Legible e inteligible
  • Fácil de mantener
  • Reutilizable
  • Cargamos sólo lo que necesitamos
  • Gestión automática de dependencias
  • Encapsulación
  • Una o muy pocas conexiones HTTP
  • Eliminación de código no usado (tree shaking)

Organización del código JavaScript: módulos

  • ¿Puedo escribir mis módulos como yo quiera? ¿hay un estándar?

  • AMD: Asynchronous Module Definition

  • CommonJS

  • UMD: Universal Module Definition

  • ES6 Modules

define(['myModule', 'myOtherModule'], function(myModule, myOtherModule) {

  return {
    hello: function() {
      console.log('hello');
    },
    goodbye: function() {
      console.log('goodbye');
    }
  };
});
var myModuleA = require('myModuleA');

function myModuleB() {
  this.hello = function() {
    return 'hello!';
  }

  this.goodbye = function() {
    return 'goodbye!';
  }
}

module.exports = myModuleB;
(function (root, factory) {
  if (typeof define === 'function' && define.amd) {
      // AMD
    define(['myModule', 'myOtherModule'], factory);
  } else if (typeof exports === 'object') {
      // CommonJS
    module.exports = factory(require('myModule'), require('myOtherModule'));
  } else {
    // Browser globals (Note: root is window)
    root.returnExports = factory(root.myModule, root.myOtherModule);
  }
}(this, function (myModule, myOtherModule) {
  // Methods
  function notHelloOrGoodbye(){}; // A private method
  function hello(){}; // A public method because it's returned (see below)
  function goodbye(){}; // A public method because it's returned (see below)

  // Exposed public methods
  return {
      hello: hello,
      goodbye: goodbye
  }
}));
import { method1 } from './moduleA.js';

method1("hello");

export let method2 = function() {
    console.log("Method 2");
}

Organización del código JavaScript: módulos

  • ¿AMD, CommonJS, UMD, ES6?

  • Compatibilidad de los módulos ES6 en navegadores

  • ¡Webpack!

  • TypeScript usa la sintaxis ES6

  • TS -> ES5 -> webpack -> bundle -> browser =

Angular CLI

ES6

  • let y const

let a = 3;

let a = 10;  // Error
var a = 12;  // Error

const b = 10;

b = 3; // Error

const obj = {
    x: 10,
    y: 12
}

obj.x = 15;  // OK

obj = {    // Error
    x: 15,
    y: 12
}

ES6

  • let y const

  • Template literals

let nombre = "Antonio";

let cuadrado = function(x) {
    return x * x;
}

let n = Math.floor(Math.random() * 10);

let saludo1 = "Hola, " + nombre + ". El cuadrado de " + n + " es " + cuadrado(n) + ".";
let saludo2 = `Hola, ${nombre}. El cuadrado de ${n} es ${cuadrado(n)}.`;

ES6

  • let y const

  • Template literals

  • for ... of

let nombres = ["Patricia", "Zacarías", "Miguel", "Maite"];

for (let i in nombres) {
    console.log(nombres[i]);
}

for (let nombre of nombres) {
    console.log(nombre);
}

let obj = {
    x: 3,
    y: 4
}

for (let i in obj) {
    console.log(obj[i]);
}

let nombre = "Antonio Jesús";

for (let c of nombre) {
    console.log(c);
}

ES6

  • let y const

  • Template literals

  • for ... of

  • Funciones

    • Parámetros por defecto

function potencia(x, y = 2) {
    return Math.pow(x, y);
}

console.log(`10 elevado a 8 es ${potencia(10, 8)}`);
console.log(`El cuadrado de 5 es ${potencia(5)}`);

ES6

  • let y const

  • Template literals

  • for ... of

  • Funciones

    • Parámetros por defecto

    • Función arrow:

      (parámetros) => expresión_devuelta;
const potencia = function (x, y = 2) {
    return Math.pow(x, y);
}

const potencia = (x, y = 2) => Math.pow(x, y);

setTimeout(() => console.log("pausa"), 2000);

ES6

  • Operador spread

    • Parámetros en funciones

    • Enviar varios parámetros a partir de un array

    • push y unshift

    • Intercalar un array dentro de otro

    • Copiar un array en otro

    • Copiar un objeto en otro

// function(a, b, c)
let nums = [1, 3, 6];
function sumar(a, b, c) {
  console.log(a + b + c);
}
sumar(...nums);

// function(n parámetros)
let a = 3;
let b = 7;
let c = 8;

function sumar(...nums) {
  let suma = 0;
  for (n of nums) {
    suma += n;
  }
  console.log("La suma es " + suma);
}
sumar(a, b, c);

// push y unshift
let nums1 = [1, 3, 6];
let nums2 = [0, 52, 15, 9];

nums1.push(...nums2);
console.log(nums1);
nums1.unshift(...nums2);
console.log(nums1);

// meter un array en medio de otra
let nums1 = [1, 3, 6];
let nums2 = [0, 52, 15, 9];
nums1.splice(1, 0, ...nums2);
console.log(nums1);

// copiar un array
let nums1 = [1, 3, 6];
let nums2 = [...nums1];

// mergear un objeto con defaults (ES2018)
let defaults = {
  ancho: 100,
  alto: 200,
  color: "rojo"
}

let o1 = {
  ancho: 200,
  grosor: 10000
}

o1 = { ...defaults, ...o1 };

// convertir un NodeList a un array
let lis = [...document.getElementsByTagName("li")];

ES6

  • Clases

    • Propiedades y métodos

class A {

    constructor(z) {
        this.x = 3;
        this.y = 10;
        this.z = z;
    }

    suma() {
        return this.x + this.y + this.z;
    }

}

let a = new A(20);

console.log(a.suma());

ES6

  • Clases

    • Propiedades y métodos

    • Getters y setters

class A {

    constructor(z) {
        this.x = 3;
        this.y = 10;
        this.z = z;
    }

    suma() {
        return this.x + this.y + this.z;
    }

    set zeta(z) {
        this.z = z * 2;
    }

    get zeta() {
        return this.z / 2;
    }
}

let a = new A(20);

a.zeta = 15;

console.log(a.zeta);

ES6

  • Clases

    • Propiedades y métodos

    • Getters y setters

    • Métodos estáticos

class A {

    constructor(z) {
        this.x = 3;
        this.y = 10;
        this.z = z;
    }

    static getPI() {
        return 3.14159;
    }

    suma() {
        return this.x + this.y + this.z;
    }

    set zeta(z) {
        this.z = z * 2;
    }

    get zeta() {
        return this.z / 2;
    }
}

let a = new A(20);

a.zeta = 15;

console.log(a.zeta);

console.log(A.getPI());

ES6

  • Clases

    • Propiedades y métodos

    • Getters y setters

    • Métodos estáticos

    • Herencia con extends y super()

class A {

    constructor(z) {
        this.x = 3;
        this.y = 10;
        this.z = z;
    }

    static getPI() {
        return 3.14159;
    }

    suma() {
        return this.x + this.y + this.z;
    }

    set zeta(z) {
        this.z = z * 2;
    }

    get zeta() {
        return this.z / 2;
    }
}

class B extends A {
    constructor() {
        super(100);
        this.x = 20;
    }

    suma() {
        return this.x + this.z;
    }

    resta() {
        return this.x - this.z;
    }
}

let b = new B();

console.log(b.suma());
console.log(b.resta());

ES6

  • Módulos

    • import

      import { literal } from 'ruta_modulo';
      import literal from 'ruta_modulo';
      import * as literal from 'ruta_modulo';
      import 'ruta_modulo';
    • export
      export let a = 3;
      export let class Clase {
          ...
      }
      export default {
          key: value
      }

Programación funcional con arrays

  • Métodos:

    • map

let nombres = ["juan", "luisa", "amparo", "arturo"];

nombres = nombres.map(nombre => nombre.toUpperCase());

console.log(nombres);

Programación funcional con arrays

  • Métodos:

    • map

    • filter

let personas = [
    {
        nombre: "juan",
        edad: 15
    },
    {
        nombre: "luisa",
        edad: 35
    },
    {
        nombre: "amparo",
        edad: 17
    },
    {
        nombre: "arturo",
        edad: 32
    }
];

let mayoresEdad = personas.filter(persona => persona.edad >= 18);

console.log(mayoresEdad);

Programación funcional con arrays

  • Métodos:

    • map

    • filter

    • reduce

let nums = [2, 4, 10, 15, 12];

let suma = nums.reduce((x, y) => x + y);

let objs = [
    {
        x: 3,
        y: 2
    },
    {
        x: 8,
        y: 10
    },
    {
        x: 10,
        y: 15
    }
]

let sumaX = objs.reduce((x, o2) => x + o2.x, 0);            // Método 1
let sumaX = objs.map(o => o.x).reduce((x, y) => x + y);     // Método 2

Programación funcional con arrays

  • Métodos:

    • map

    • filter

    • reduce

    • find

  • Encadenamiento

let notas = [
    {
        nombre: "juan",
        nota: 6
    },
    {
        nombre: "luisa",
        nota: 8
    },
    {
        nombre: "amparo",
        nota: 4
    },
    {
        nombre: "arturo",
        nota: 3
    }
];

let notasAprobados = notas.filter(n => n.nota >= 5).map(n => n.nota);

console.log(notasAprobados);

TypeScript

TypeScript

  • Superconjunto de JavaScript

  • Transpila a ES5 (o a otra versión)

TypeScript

  • Superconjunto de JavaScript

  • Transpila a ES5 (o a otra versión)

  • Tipado

  • Errores en tiempo de compilación

TypeScript

  • Superconjunto de JavaScript

  • Transpila a ES5 (o a otra versión)

  • Tipado

  • Errores en tiempo de compilación

  • tsconfig.json

{
  "compilerOptions": {
    "target": "es5",
    "module": "es2015",
    "moduleResolution": "node",
    "sourceMap": true,
    "outDir": "./public/js/",
  }
}

tsconfig.json

TypeScript - Tipos

  • Tipos básicos:

    • number

    • string

    • boolean

    • Array

    • any

    • void

let peso: number;
peso = 89.5;

let saludo: string;
saludo = 'Vais a petarlo con TypeScript';

let esVerano: boolean;
esVerano = false;

let nums: Array<number>;
nums = [10, 55, -3, 4.14];

let nombres: string[];
nombres = ['Juan', 'Paqui', 'Lorenzo', 'Alicia'];

let cosas: any[];
cosas = [10, 'Teruel', -5, true, [0, -10, 15], false];

function imprimeSaludo(s: string): void {
  console.log(s);
}
imprimeSaludo('Buenas tardes');

TypeScript - Tipos

  • Tipos básicos:

    • number

    • string

    • boolean

    • Array

    • any

    • void

  • Enum

enum FormasPago {
  TPV,
  PayPal,
  transferencia
}
let pago: FormasPago;

pago = FormasPago.PayPal;
procesarPago(pago);

function procesarPago(formaPago: FormasPago): void {
  switch (formaPago) {
    case FormasPago.TPV:
      // ...
      break;
    case FormasPago.PayPal:
      // ...
      break;
    case FormasPago.transferencia:
      // ...
      break;
  }
}

TypeScript - Tipos

  • Tipos básicos:

    • number

    • string

    • boolean

    • Array

    • any

    • void

  • Enum

  • Union types

let numeros: Array<number | string>;
numeros = ['3', 6, '15.8', 0];

function procesar(a: string | number): void {
  if (typeof a === 'string') {
    console.log(a.toUpperCase());
  } else {
    console.log(a.toFixed(2));
  }
}

TypeScript - Tipos

  • Tipos básicos:

    • number

    • string

    • boolean

    • Array

    • any

    • void

  • Enum

  • Union types

  • Genéricos

function verDoble<T>(elem: T): T[] {
  let elemDoble: T[] = [elem, elem];
  return elemDoble;
}

TypeScript - Tipos

  • Tipos básicos:

    • number

    • string

    • boolean

    • Array

    • any

    • void

  • Enum

  • Union types

  • Genéricos

  • Type assertion

const inputText = <HTMLInputElement>document.getElementById("nombre");

inputText.select();

TypeScript - Funciones

  • Sin flexibilidad en el número de parámetros

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

sumar();              // Error
sumar(3);             // Error
sumar(10, 2);         // OK
sumar(4, -3, 10, 8)   // Error

TypeScript - Funciones

  • Sin flexibilidad en el número de parámetros

  • Parámetros opcionales

function sumar(a: number, b: number, c?: number): number {
  if (c) {
    return a + b + c;
  } else {
    return a + b;
  }
}

sumar(10, 2);
sumar(10, 2, 15);

TypeScript - Funciones

  • Sin flexibilidad en el número de parámetros

  • Parámetros opcionales

  • Sobrecarga

function nChars(a: number): string;
function nChars(a: string): number;
function nChars(a: string | number): number | string {
  if (typeof a === 'number') {
    return '¡Es un número!';
  } else if (typeof a === 'string') {
    return a.length;
  }
}


type RGB = [number, number, number];

function convierteColor(color: string): RGB;
function convierteColor(color: RGB): string;
function convierteColor(color: string | RGB): string | RGB {
  if (typeof color === 'string') {
    return [0, 128, 0];
  } else {
    return '#006600';
  }
}

const colorRGB = convierteColor('#006600');
const colorHEX = convierteColor([0, 128, 0]);

TypeScript - Funciones

  • Sin flexibilidad en el número de parámetros

  • Parámetros opcionales

  • Sobrecarga

  • Function types

function transformaNumero(x: number, callback: (n: number) => void) {
  callback(x);
}

let a = 10;

transformaNumero(a, m => console.log(m * 2));

TypeScript - Módulos

  • Sintaxis ES6:
    • import { literal } from 'ruta_modulo';

      import literal from 'ruta_modulo';
      import * as literal from 'ruta_modulo';
      import 'ruta_modulo';
    • export let a = 3;
      export class Clase {
          ...
      }
      export default {
          key: value
      }

TypeScript - Módulos

  • Sintaxis ES6
  • Se omite la extensión .ts
  • Importar de paquetes npm: nombre del paquete
    import { } from 'paquete';
  • Importar de nuestros módulos: rutas relativas
    import { } from './modulo';

TypeScript - Clases

  • Propiedades fuera del constructor
class Factura {
  numero: string;
  base: number;
  tipoIva: number;

  constructor(numero: string, base: number, tipoIva: number = 21) {
    this.numero = numero;
    this.base = base;
    this.tipoIva = tipoIva;
  }
}

TypeScript - Clases

  • Propiedades fuera del constructor
  • Visibilidad de los miembros
  • Getters y setters
  • Modificador readonly
  • Propiedades estáticas
class Factura {
  private static caracteresSerie = 2;
  public num: string;
  public serie: string;
  public base: number;
  private readonly intTipoIva: number;

  constructor(base: number, tipoIva: number = 21) {
    this.base = base;
    this.intTipoIva = tipoIva;
  }

  get numero(): string {
    return this.serie + this.num;
  }

  set numero(n: string) {
    this.serie = n.slice(0, Factura.caracteresSerie - 1);
    this.num = n.slice(Factura.caracteresSerie);
  }
}

let f = new Factura(100);

f.numero = 'AB600';
console.log(f.numero);

TypeScript - Clases

  • Propiedades fuera del constructor
  • Visibilidad de los miembros
  • Getters y setters
  • Modificador readonly
  • Propiedades estáticas
  • Métodos abstractos
abstract class Vehiculo {

  public manual: boolean;

  constructor(public ruedas: number, public motor: Motor) {
    this.manual = this.motor === Motor.ninguno;
  }

  public abstract arrancar(): void;

}

class Bici extends Vehiculo {
  public arrancar(): void {
    console.log('Me pongo de pie y pedaleo');
  }
}

TypeScript - Clases

  • Propiedades fuera del constructor
  • Visibilidad de los miembros
  • Getters y setters
  • Modificador readonly
  • Propiedades estáticas
  • Métodos abstractos
  • Interfaces
interface Arrancable {
  arrancar(): void;
  apagar(): void;
}

abstract class Vehiculo {

  public manual: boolean;

  constructor(public ruedas: number, public motor: Motor) {
    this.manual = this.motor === Motor.ninguno;
  }

}

class Bici extends Vehiculo implements Arrancable {
  public arrancar(): void {
    console.log('Me pongo de pie y pedaleo');
  }
  public apagar(): void {
    console.log('Me bajo de la bici');
  }
}
interface Cliente {
  id: number;
  login: string;
  nombre: string;
  tipo: TiposCliente;
  fechaAlta: Date;
}

function getClientes(): Cliente[] {
  let clientes: Cliente[] = conectaBD('clientes');
  return clientes;
}

TypeScript - Decoradores

  • @

  • Asignar metadatos

  • Muy utilizados en Angular

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

@Component({
  selector: 'app-factura',
  templateUrl: './factura.component.html',
  styleUrls: ['./factura.component.css']
})
export class FacturaComponent {
  @Input()
  facturaId: number;
}

Angular

Primeros pasos

  • ng new para generar la app:
    ng new <nombre-app> --prefix <prefijo>

  • ng serve -o para ejecutarla y verla en el navegador

  • Entornos dev y prod

  • Módulos, componentes y vistas

  • Creando piezas con ng generate

  • Archivos de configuración

Esqueleto de una pieza en Angular

  • clase =>

  • => clase exportada =>

  • => clase exportada y decorada =>

  • => dependencias

Examinando un módulo

  • Metadata

    • declarations

    • imports /exports

    • providers

    • bootstrap

Examinando un componente

  • Metadata

    • selector

    • template / templateUrl

    • styles / styleUrls

  • ngOnInit

  • ngOnDestroy

Examinando un template

  • Custom elements

  • Data binding

  • Interpolation

  • Property binding

  • Class & style binding

  • Event binding

  • Two-way binding

Examinando un template

  • Directivas de atributo

    • ngClass

    • ngStyle

  • Directivas estructurales

    • ngIf

    • ngFor

    • ngSwitch

  • Pipes

    • @Pipe, PipeTransform

  • Directivas propias

    • De atributo (ElementRef.nativeElement)

    • Estructurales (ViewContainerRef y TemplateRef)

Servicios

  • Dependency Injection

  • Injectable()

  • Proveedores

  • Singleton

Formularios

  • [(ngModel)]: Two-way binding

  • ngForm, ngModel y ngSubmit

  • Variables de template con #

  • Validaciones: los diferentes estados

  • Resetear los estados

  • Template driven y Reactive forms

Conexiones con el servidor

  • Asincronía

  • Observables

  • Suscripciones

  • API REST

  • El módulo HttpClientModule

    • Módulo HttpClientModule y servicio HttpClient

    • Métodos del servicio HttpClient:
      get(), post(), put(), patch(), delete()

    • Obteniendo la respuesta completa:
      { observe: 'response' }

    • Interceptors y autentificación

Navegación por la app

  • El router

  • El RouterOutlet

  • Las rutas

Navegación por la app

  • El router

  • El RouterOutlet

  • Las rutas

    • Página por defecto

    • 404

    • Parámetros: los observables paramMap y data de ActivatedRoute

    • Guards y resolvers

  • Links de navegación: routerLink y routerLinkActive

  • router.navigate()

  • Lazy loading

  • El servicio Title

Despliegue a producción

  • Pruebas con ng build

  • ng build:

    • --prod: código optimizado para producción

    • --base-href=: cambia el directorio base

    • --sourcemaps: genera los source maps

  • Entornos propios

Links

Angular 8

By mariogl

Angular 8

Curso Angular 8 Bilbao 17 junio - 19 junio 2019

  • 2,211