pax_global_header00006660000000000000000000000064135647232520014523gustar00rootroot0000000000000052 comment=d257c4564d069a279c5625ad6b68008d4a4dbb11 angular.js-1.7.9/000077500000000000000000000000001356472325200136055ustar00rootroot00000000000000angular.js-1.7.9/.editorconfig000066400000000000000000000004611356472325200162630ustar00rootroot00000000000000# https://editorconfig.org root = true [*] charset = utf-8 indent_style = space indent_size = 2 end_of_line = lf insert_final_newline = true trim_trailing_whitespace = true [dropdown-toggle.js] trim_trailing_whitespace = false insert_final_newline = false [htmlparser.js] insert_final_newline = false angular.js-1.7.9/.eslintignore000066400000000000000000000002671356472325200163150ustar00rootroot00000000000000build/** docs/app/assets/js/angular-bootstrap/** docs/config/templates/** node_modules/** lib/htmlparser/** src/angular.bind.js src/ngParseExt/ucd.js i18n/closure/** tmp/** vendor/** angular.js-1.7.9/.eslintrc-base.json000066400000000000000000000072621356472325200173200ustar00rootroot00000000000000{ "rules": { // Rules are divided into sections from http://eslint.org/docs/rules/ // Possible errors "comma-dangle": ["error", "never"], "no-cond-assign": ["error", "except-parens"], "no-constant-condition": ["error", {"checkLoops": false}], "no-control-regex": "error", "no-debugger": "error", "no-dupe-args": "error", "no-dupe-keys": "error", "no-duplicate-case": "error", "no-empty-character-class": "error", "no-empty": "error", "no-ex-assign": "error", "no-extra-boolean-cast": "error", "no-extra-semi": "error", "no-func-assign": "error", "no-inner-declarations": "error", "no-invalid-regexp": "error", "no-irregular-whitespace": "error", "no-negated-in-lhs": "error", "no-obj-calls": "error", "no-regex-spaces": "error", "no-sparse-arrays": "error", "no-unreachable": "error", "use-isnan": "error", "no-unsafe-finally": "error", "valid-typeof": "error", "no-unexpected-multiline": "error", // Best practices "accessor-pairs": "error", "array-callback-return": "error", "eqeqeq": ["error", "allow-null"], "no-alert": "error", "no-caller": "error", "no-case-declarations": "error", "no-eval": "error", "no-extend-native": "error", "no-extra-bind": "error", "no-extra-label": "error", "no-fallthrough": "error", "no-floating-decimal": "error", "no-implied-eval": "error", "no-invalid-this": "error", "no-iterator": "error", "no-multi-str": "error", "no-new-func": "error", "no-new-wrappers": "error", "no-new": "error", "no-octal-escape": "error", "no-octal": "error", "no-proto": "error", "no-redeclare": "error", "no-return-assign": "error", "no-script-url": "error", "no-self-assign": "error", "no-self-compare": "error", "no-sequences": "error", "no-throw-literal": "error", "no-unmodified-loop-condition": "error", "no-unused-expressions": "error", "no-unused-labels": "error", "no-useless-call": "error", "no-useless-concat": "error", "no-useless-escape": "error", "no-void": "error", "no-with": "error", "radix": "error", "wrap-iife": ["error", "inside"], // Strict mode "strict": ["error", "global"], // Variables "no-delete-var": "error", "no-label-var": "error", "no-restricted-globals": ["error", "event"], "no-shadow-restricted-names": "error", "no-undef-init": "error", "no-undef": "error", "no-unused-vars": ["error", { "vars": "local", "args": "none" }], // Node.js "handle-callback-err": "error", // Stylistic issues "array-bracket-spacing": ["error", "never"], "brace-style": ["error", "1tbs", { "allowSingleLine": true }], "comma-style": ["error", "last"], "eol-last": "error", "keyword-spacing": "error", "linebreak-style": ["error", "unix"], "max-len": ["error", { "code": 200, "ignoreComments": true, "ignoreUrls": true }], "new-cap": "error", "new-parens": "error", "no-array-constructor": "error", "no-bitwise": "error", "no-mixed-spaces-and-tabs": "error", "no-multiple-empty-lines": ["error", { "max": 3, "maxEOF": 1 }], "no-whitespace-before-property": "error", "no-spaced-func": "error", "no-trailing-spaces": "error", "no-unneeded-ternary": "error", "quotes": ["error", "single"], "semi-spacing": "error", "semi": "error", "space-before-blocks": ["error", "always"], "space-before-function-paren": ["error", "never"], "space-in-parens": ["error", "never"], "space-infix-ops": "error", "space-unary-ops": ["error", { "words": true, "nonwords": false }], "unicode-bom": ["error", "never"] } } angular.js-1.7.9/.eslintrc-browser.json000066400000000000000000000006121356472325200200610ustar00rootroot00000000000000{ "extends": "./.eslintrc-base.json", "env": { // Note: don't set `"browser": true`; code in "src/" should be compatible with // non-browser environments like Node.js with a custom window implementation // like jsdom. All browser globals should be taken from window. "browser": false, "node": false }, "globals": { "window": false, "angular": false } } angular.js-1.7.9/.eslintrc-node.json000066400000000000000000000002641356472325200173260ustar00rootroot00000000000000{ "extends": "./.eslintrc-base.json", "env": { "browser": false, "node": true }, "parserOptions": { "ecmaVersion": 2017 }, "plugins": [ "promise" ] } angular.js-1.7.9/.eslintrc-todo.json000066400000000000000000000020231356472325200173410ustar00rootroot00000000000000{ // This config contains proposed rules that we'd like to have enabled but haven't // converted the code to adhere yet. If a decision comes to not enable one of these // rules, it should be removed from the file. Every rule that got enabled in the // end should be moved from here to a respective section in .eslintrc.json "rules": { // Rules are divided into sections from http://eslint.org/docs/rules/ // Best practices "complexity": ["error", 10], "dot-notation": "error", "dot-location": ["error", "property"], // Stylistic issues "block-spacing": ["error", "always"], "comma-spacing": "error", "id-blacklist": ["error", "event"], "indent": ["error", 2], "key-spacing": ["error", { "beforeColon": false, "afterColon": true, "mode": "minimum" }], "object-curly-spacing": ["error", "never"], "object-property-newline": ["error", { "allowMultiplePropertiesPerLine": true }], "operator-linebreak": ["error", "after", { "overrides": { "?": "before", ":": "before" }}] } } angular.js-1.7.9/.eslintrc.json000066400000000000000000000000711356472325200163770ustar00rootroot00000000000000{ "root": true, "extends": "./.eslintrc-node.json" } angular.js-1.7.9/.gitattributes000066400000000000000000000001771356472325200165050ustar00rootroot00000000000000# Auto detect text files and perform LF normalization * text=auto # JS files must always use LF for tools to work *.js eol=lf angular.js-1.7.9/.github/000077500000000000000000000000001356472325200151455ustar00rootroot00000000000000angular.js-1.7.9/.github/ISSUE_TEMPLATE.md000066400000000000000000000035701356472325200176570ustar00rootroot00000000000000# AngularJS is in LTS mode We are no longer accepting changes that are not critical bug fixes into this project. See https://blog.angular.io/stable-angularjs-and-long-term-support-7e077635ee9c for more detail. **I'm submitting a ...** - [ ] regression from 1.7.0 - [ ] security issue - [ ] issue caused by a new browser version - [ ] other **Current behavior:** **Expected / new behavior:** **Minimal reproduction of the problem with instructions:** **AngularJS version:** 1.7.x **Browser:** [all | Chrome XX | Firefox XX | Edge XX | IE XX | Safari XX | Mobile Chrome XX | Android X.X Web Browser | iOS XX Safari | iOS XX UIWebView | iOS XX WKWebView | Opera XX ] **Anything else:** angular.js-1.7.9/.github/PULL_REQUEST_TEMPLATE.md000066400000000000000000000021001356472325200207370ustar00rootroot00000000000000# AngularJS is in LTS mode We are no longer accepting changes that are not critical bug fixes into this project. See https://blog.angular.io/stable-angularjs-and-long-term-support-7e077635ee9c for more detail. **Does this PR fix a regression since 1.7.0, a security flaw, or a problem caused by a new browser version?** **What is the current behavior? (You can also link to an open issue here)** **What is the new behavior (if this is a feature change)?** **Does this PR introduce a breaking change?** **Please check if the PR fulfills these requirements** - [ ] The commit message follows our [guidelines](https://github.com/angular/angular.js/blob/master/DEVELOPERS.md#commits) - [ ] Fix/Feature: [Docs](https://github.com/angular/angular.js/blob/master/DEVELOPERS.md#documentation) have been added/updated - [ ] Fix/Feature: Tests have been added; existing tests pass **Other information**: angular.js-1.7.9/.gitignore000066400000000000000000000004521356472325200155760ustar00rootroot00000000000000/build/ /deploy/ /benchpress-build/ .DS_Store gen_docs.disable test.disable regression/temp*.html performance/temp*.html .idea/workspace.xml *~ *.swp angular.js.tmproj node_modules/ angular.xcodeproj .idea *.iml .agignore .lvimrc libpeerconnection.log npm-debug.log /tmp/ .vscode *.log *.stackdump angular.js-1.7.9/.mailmap000066400000000000000000000025101356472325200152240ustar00rootroot00000000000000Andres Ornelas Caitlin Potter Caitlin Potter Di Peng Di Peng Georgios Kalpakas Georgios Kalpakas Julie Ralph Lucas Galfaso Martin Staffa Martin Staffa Matias Niemelä Michał Gołębiowski-Owczarek Misko Hevery Misko Hevery Igor Minar Igor Minar Igor Minar Igor Minar Pawel Kozlowski Peter Bacon Darwin Rodric Haddad Shahar Talmi Shahar Talmi Shyam Seshadri Shyam Seshadri Vojta Jina Vojta Jina Vojta Jina angular.js-1.7.9/.nvmrc000066400000000000000000000000021356472325200147230ustar00rootroot000000000000008 angular.js-1.7.9/.travis.yml000066400000000000000000000071771356472325200157320ustar00rootroot00000000000000language: node_js sudo: false node_js: - '8' cache: yarn: true branches: except: - "/^g3_.*$/" env: matrix: - JOB=ci-checks - JOB=unit-core BROWSER_PROVIDER=saucelabs - JOB=unit-jquery BROWSER_PROVIDER=saucelabs - JOB=unit-modules BROWSER_PROVIDER=saucelabs - JOB=docs-app BROWSER_PROVIDER=saucelabs - JOB=e2e TEST_TARGET=jqlite BROWSER_PROVIDER=saucelabs - JOB=e2e TEST_TARGET=jquery BROWSER_PROVIDER=saucelabs global: - SAUCE_USERNAME=angular-ci - SAUCE_ACCESS_KEY=9b988f434ff8-fbca-8aa4-4ae3-35442987 - LOGS_DIR=/tmp/angular-build/logs - BROWSER_PROVIDER_READY_FILE=/tmp/browsersprovider-tunnel-ready - secure: oTBjhnOKhs0qDSKTf7fE4f6DYiNDPycvB7qfSF5QRIbJK/LK/J4UtFwetXuXj79HhUZG9qnoT+5e7lPaiaMlpsIKn9ann7ffqFWN1E8TMtpJF+AGigx3djYElwfgf5nEnFUFhwjFzvbfpZNnxVGgX5YbIZpe/WUbHkP4ffU0Wks= before_install: - curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version 1.10.1 - export PATH="$HOME/.yarn/bin:$PATH" before_script: - du -sh ./node_modules || true - "./scripts/travis/before_build.sh" script: - "./scripts/travis/build.sh" after_script: - "./scripts/travis/tear_down_browser_provider.sh" - "./scripts/travis/print_logs.sh" notifications: webhooks: urls: - https://webhooks.gitter.im/e/d2120f3f2bb39a4531b2 - http://104.197.9.155:8484/hubot/travis/activity #hubot-server on_success: always # options: [always|never|change] default: always on_failure: always # options: [always|never|change] default: always on_start: always # default: false jobs: include: - stage: deploy # Don't deploy from PRs. Only deploy from our default branches, or if commit is tagged. # This is a Travis-specific boolean language: https://docs.travis-ci.com/user/conditional-builds-stages-jobs#Specifying-conditions # The deployment logic for pushed branches is further defined in scripts\travis\build.sh if: type != pull_request and (branch =~ ^(v1\.\d+\.x|master)$ or tag IS present) env: - JOB=deploy before_script: skip script: # Export the variables into the current process - . ./scripts/travis/build.sh - "echo DEPLOY_DOCS: $DEPLOY_DOCS, DEPLOY_CODE: $DEPLOY_CODE" after_script: skip # Work around the 10min Travis timeout so the code.angularjs firebase+gcs code deploy can complete # Only run the keep_alive once (before_deploy is run for each provider) before_deploy: | if ! [ "$BEFORE_DEPLOY_RUN" ]; then export BEFORE_DEPLOY_RUN=1; function keep_alive() { while true; do echo -en "\a" sleep 10 done } keep_alive & fi deploy: - provider: firebase # the upload folder for firebase is configured in /firebase.json skip_cleanup: true project: docs-angularjs-org-9p2 token: secure: $FIREBASE_TOKEN on: repo: angular/angular.js all_branches: true condition: "$DEPLOY_DOCS == true" - provider: gcs skip_cleanup: true access_key_id: GOOGLDB7W2J3LFHICF3R secret_access_key: secure: tHIFdSq55qkyZf9zT/3+VkhUrTvOTMuswxXU3KyWaBrSieZqG0UnUDyNm+n3lSfX95zEl/+rJAWbfvhVSxZi13ndOtvRF+MdI1cvow2JynP0aDSiPffEvVrZOmihD6mt2SlMfhskr5FTduQ69kZG6DfLcve1PPDaIwnbOv3phb8= bucket: code-angularjs-org-338b8.appspot.com local-dir: deploy/code detect_encoding: true # detects gzip compression on: repo: angular/angular.js all_branches: true condition: "$DEPLOY_CODE == true" angular.js-1.7.9/CHANGELOG.md000066400000000000000000035142251356472325200154320ustar00rootroot00000000000000 # 1.7.9 pollution-eradication (2019-11-19) ## Bug Fixes - **angular.merge:** do not merge __proto__ property ([726f49](https://github.com/angular/angular.js/commit/726f49dcf6c23106ddaf5cfd5e2e592841db743a)) - **ngStyle:** correctly remove old style when new style value is invalid ([5edd25](https://github.com/angular/angular.js/commit/5edd25364f617083363dc2bd61f9230b38267578), [#16860](https://github.com/angular/angular.js/issues/16860), [#16868](https://github.com/angular/angular.js/issues/16868)) # 1.7.8 enthusiastic-oblation (2019-03-11) ## Bug Fixes - **required:** correctly validate required on non-input element surrounded by ngIf ([a4c7bd](https://github.com/angular/angular.js/commit/a4c7bdccd76c39c30e33f6215da9a00cc8acde2c), [#16830](https://github.com/angular/angular.js/issues/16830), [#16836](https://github.com/angular/angular.js/issues/16836)) # 1.7.7 kingly-exiting (2019-02-04) ## Bug Fixes - **ngRequired:** set error correctly when inside ngRepeat and false by default ([5ad4f5](https://github.com/angular/angular.js/commit/5ad4f5562c37b1cb575e3e5fddd96e9dd10408e2), [#16814](https://github.com/angular/angular.js/issues/16814), [#16820](https://github.com/angular/angular.js/issues/16820)) # 1.7.6 gravity-manipulation (2019-01-17) ## Bug Fixes - **$compile:** fix ng-prop-* with undefined values ([772440](https://github.com/angular/angular.js/commit/772440cdaf9a9bfa40de1675e20a5f0e356089ed), [#16797](https://github.com/angular/angular.js/issues/16797), [#16798](https://github.com/angular/angular.js/issues/16798)) - **compile:** properly handle false value for boolean attrs with jQuery ([27486b](https://github.com/angular/angular.js/commit/27486bd15e70946ece2ba713e4e8654b7f9bddad), [#16778](https://github.com/angular/angular.js/issues/16778), [#16779](https://github.com/angular/angular.js/issues/16779)) - **ngRepeat:** - fix reference to last collection value remaining across linkages ([cf919a](https://github.com/angular/angular.js/commit/cf919a6fb7fc655f3fa37a74899a797ea5b8073e)) - fix trackBy function being invoked with incorrect scope ([d4d103](https://github.com/angular/angular.js/commit/d4d1031bcd9b30ae6a58bd60a79bcc9d20f0f2b7), [#16776](https://github.com/angular/angular.js/issues/16776), [#16777](https://github.com/angular/angular.js/issues/16777)) - **aria/ngClick:** check if element is `contenteditable` before blocking spacebar ([289374](https://github.com/angular/angular.js/commit/289374a43c1b2fd715ddf7455db225b17afebbaf), [#16762](https://github.com/angular/angular.js/issues/16762)) - **input:** prevent browsers from autofilling hidden inputs ([7cbb10](https://github.com/angular/angular.js/commit/7cbb1044fcb3576cdad791bd22ebea3dfd533ff8)) - **Angular:** add workaround for Safari / Webdriver problem ([eb49f6](https://github.com/angular/angular.js/commit/eb49f6b7555cfd7ab03fd35581adb6b4bd49044e)) - **$browser:** normalize inputted URLs ([2f72a6](https://github.com/angular/angular.js/commit/2f72a69ded53a122afad3ec28d91f9bd2f41eb4f), [#16606](https://github.com/angular/angular.js/issues/16606)) - **interpolate:** do not create directives for constant media URL attributes ([90a41d](https://github.com/angular/angular.js/commit/90a41d415c83abdbf28317f49df0fd0a7e07db86), [#16734](https://github.com/angular/angular.js/issues/16734)) - **$q:** allow third-party promise libraries ([eefaa7](https://github.com/angular/angular.js/commit/eefaa76a90dbef08fdc7d734a205cc2de50d9f91), [#16164](https://github.com/angular/angular.js/issues/16164), [#16471](https://github.com/angular/angular.js/issues/16471)) - **urlUtils:** make IPv6 URL's hostname wrapped in square brackets in IE/Edge ([0e1bd7](https://github.com/angular/angular.js/commit/0e1bd7822e61822a48b8fd7ba5913a8702e6dabf), [#16692](https://github.com/angular/angular.js/issues/16692), [#16715](https://github.com/angular/angular.js/issues/16715)) - **ngAnimateSwap:** make it compatible with `ngIf` on the same element ([b27080](https://github.com/angular/angular.js/commit/b27080d52546409fb4e483f212f03616e2ca8037), [#16616](https://github.com/angular/angular.js/issues/16616), [#16729](https://github.com/angular/angular.js/issues/16729)) - **ngMock:** make matchLatestDefinitionEnabled work ([3cdffc](https://github.com/angular/angular.js/commit/3cdffcecbae71189b4db69b57fadda6608a23b61), [#16702](https://github.com/angular/angular.js/issues/16702)) - **ngStyle:** skip setting empty value when new style has the property ([d6098e](https://github.com/angular/angular.js/commit/d6098eeb1c9510d599e9bd3cfdba7dd21e7a55a5), [#16709](https://github.com/angular/angular.js/issues/16709)) ## Performance Improvements - **input:** prevent multiple validations on initialization ([692622](https://github.com/angular/angular.js/commit/69262239632027b373258e75c670b89132ad9edb), [#14691](https://github.com/angular/angular.js/issues/14691), [#16760](https://github.com/angular/angular.js/issues/16760)) # 1.7.5 anti-prettification (2018-10-04) ## Bug Fixes - **ngClass:** do not break on invalid values ([f3a565](https://github.com/angular/angular.js/commit/f3a565872d802c94bb213944791b11b483d52f73), [#16697](https://github.com/angular/angular.js/issues/16697), [#16699](https://github.com/angular/angular.js/issues/16699)) # 1.7.4 interstellar-exploration (2018-09-07) ## Bug Fixes - **ngAria.ngClick:** prevent default event on space/enter only for non-interactive elements ([61b335](https://github.com/angular/angular.js/commit/61b33543ff8e7f32464dec98a46bf0a35e9b03a4), [#16664](https://github.com/angular/angular.js/issues/16664), [#16680](https://github.com/angular/angular.js/issues/16680)) - **ngAnimate:** remove the "prepare" classes with multiple structural animations ([3105b2](https://github.com/angular/angular.js/commit/3105b2c26a71594c4e7904efc18f4b2e9da25b1b), [#16681](https://github.com/angular/angular.js/issues/16681), [#16677](https://github.com/angular/angular.js/issues/16677)) - **$route:** correctly extract path params if the path contains a question mark or a hash ([2ceeb7](https://github.com/angular/angular.js/commit/2ceeb739f35e01fcebcabac4beeeb7684ae9f86d)) - **ngHref:** allow numbers and other objects in interpolation ([30084c](https://github.com/angular/angular.js/commit/30084c13699c814ff6703d7aa2d3947a9b2f7067), [#16652](https://github.com/angular/angular.js/issues/16652), [#16626](https://github.com/angular/angular.js/issues/16626)) - **select:** allow to select first option with value `undefined` ([668a33](https://github.com/angular/angular.js/commit/668a33da3439f17e61dfa8f6d9b114ebde8c9d87), [#16653](https://github.com/angular/angular.js/issues/16653), [#16656](https://github.com/angular/angular.js/issues/16656)) # 1.7.3 eventful-proposal (2018-08-03) ## Bug Fixes - **$location:** - fix infinite recursion/digest on URLs with special characters ([e68697](https://github.com/angular/angular.js/commit/e68697e2e30695f509e6c2c1e43c2c02b7af41f0), [#16592](https://github.com/angular/angular.js/issues/16592), [#16611](https://github.com/angular/angular.js/issues/16611)) - avoid unnecessary `$locationChange*` events due to empty hash ([1144b1](https://github.com/angular/angular.js/commit/1144b1eccb886ea0e4a80bcb07d38a305c3263b4), [#16632](https://github.com/angular/angular.js/issues/16632), [#16636](https://github.com/angular/angular.js/issues/16636)) - **ngMock.$httpBackend:** - pass failed HTTP expectations to `$exceptionHandler` ([4adbf8](https://github.com/angular/angular.js/commit/4adbf82a84a564a8d3f0982c17a64c6163200bcd), [#16644](https://github.com/angular/angular.js/issues/16644)) - correctly ignore query params in {expect,when}Route ([be417f](https://github.com/angular/angular.js/commit/be417f28549e184fbc3c7f74251ac21fca965ae8), [#14173](https://github.com/angular/angular.js/issues/14173), [#16589](https://github.com/angular/angular.js/issues/16589)) - **Angular:** add workaround for Safari / Webdriver problem ([0a1db2](https://github.com/angular/angular.js/commit/0a1db2ad5f8da6902b1711a738ae4177ce9685fa), [#16645](https://github.com/angular/angular.js/issues/16645)) - **$animate:** avoid memory leak with `$animate.enabled(element, enabled)` ([4bd424](https://github.com/angular/angular.js/commit/4bd424690612885ca06028e9b27de585edc3d3c3), [#16649](https://github.com/angular/angular.js/issues/16649)) - **$compile:** - use correct parent element when requiring on html element ([05ac70](https://github.com/angular/angular.js/commit/05ac702bc7edae5f89c363ea661774910735ea8b), [#16535](https://github.com/angular/angular.js/issues/16535), [#16647](https://github.com/angular/angular.js/issues/16647)) - work around Firefox `DocumentFragment` bug ([10973c](https://github.com/angular/angular.js/commit/10973c3366676ac8e5b2728b1e006cdef4ea197e), [#16607](https://github.com/angular/angular.js/issues/16607), [#16615](https://github.com/angular/angular.js/issues/16615)) - **ngEventDirs:** - pass error in handler to $exceptionHandler when event was triggered in a digest ([688211](https://github.com/angular/angular.js/commit/6882113bc194fb10081db9bab3dd7d69dd59f311)) - don't wrap the event handler in $apply if already in $digest ([535ee3](https://github.com/angular/angular.js/commit/535ee32a0b4881c9fd526fb5e0ffc10919ba1800), [#14673](https://github.com/angular/angular.js/issues/14673), [#14674](https://github.com/angular/angular.js/issues/14674)) - **angular.element:** do not break on `cleanData()` if `_data()` returns undefined ([7cf4a2](https://github.com/angular/angular.js/commit/7cf4a2933cb017e45b0c97b0a836cbbd905ee31a), [#16641](https://github.com/angular/angular.js/issues/16641), [#16642](https://github.com/angular/angular.js/issues/16642)) - **ngAria:** do not scroll when pressing spacebar on custom buttons ([3a517c](https://github.com/angular/angular.js/commit/3a517c25f677294a7a9eca1660654a3edcc9e103), [#14665](https://github.com/angular/angular.js/issues/14665), [#16604](https://github.com/angular/angular.js/issues/16604)) ## New Features - **$compile:** add support for arbitrary DOM property and event bindings ([a5914c](https://github.com/angular/angular.js/commit/a5914c94a8fa5b1eceeab9e4e6849cbf467bc26d), [#16428](https://github.com/angular/angular.js/issues/16428), [#16235](https://github.com/angular/angular.js/issues/16235), [#16614](https://github.com/angular/angular.js/issues/16614)) - **ngMock:** add `$flushPendingTasks()` and `$verifyNoPendingTasks()` ([6f7674](https://github.com/angular/angular.js/commit/6f7674a7d063d434205f75f5b861f167e8125999), [#14336](https://github.com/angular/angular.js/issues/14336)) - **core:** implement more granular pending task tracking ([17b139](https://github.com/angular/angular.js/commit/17b139f107e5471a9351af638093a8e13a69e42a)) - **$animate:** add option data to event callbacks ([fc64e6](https://github.com/angular/angular.js/commit/fc64e6807642512b567deb52b497bd2bff570a1f), [#12697](https://github.com/angular/angular.js/issues/12697), [#13059](https://github.com/angular/angular.js/issues/13059)) - **form.FormController:** add $getControls() ([c9d1e6](https://github.com/angular/angular.js/commit/c9d1e690aa597283373b78e646676fa8f1ba1b4d), [#16601](https://github.com/angular/angular.js/issues/16601), [#14749](https://github.com/angular/angular.js/issues/14749), [#14517](https://github.com/angular/angular.js/issues/14517), [#13202](https://github.com/angular/angular.js/issues/13202)) - **ngModelOptions:** add `timeStripZeroSeconds` and `timeSecondsFormat` ([b68221](https://github.com/angular/angular.js/commit/b682213d72d65c996a6a31ea57b79d4c4f4e3c98), [#10721](https://github.com/angular/angular.js/issues/10721), [#16510](https://github.com/angular/angular.js/issues/16510), [#16584](https://github.com/angular/angular.js/issues/16584)) ## Performance Improvements - **ngAnimate:** avoid repeated calls to addClass/removeClass when animation has no duration ([093635](https://github.com/angular/angular.js/commit/0936353e9a03f072bc3c4056888fd154a96530ef), [#14165](https://github.com/angular/angular.js/issues/14165), [#14166](https://github.com/angular/angular.js/issues/14166), [#16613](https://github.com/angular/angular.js/issues/16613)) # 1.7.2 extreme-compatiplication (2018-06-12) In the previous release, we removed a private, undocumented API that was no longer used by AngularJS. It turned out that several popular UI libraries (such as [AngularJS Material](https://material.angularjs.org/), [UI Bootstrap](https://angular-ui.github.io/bootstrap/), [ngDialog](http://likeastore.github.io/ngDialog/) and probably others) relied on that API. In order to avoid unnecessary pain for developers, this release reverts the removal of the private API and restores compatibility of the aforementioned libraries with the latest AngularJS. ## Reverts - **$compile:** remove `preAssignBindingsEnabled` leftovers ([2da495](https://github.com/angular/angular.js/commit/2da49504065e9e2b71a7a5622e45118d8abbe87e), [#16580](https://github.com/angular/angular.js/pull/16580), [a81232](https://github.com/angular/angular.js/commit/a812327acda8bc890a4c4e809f0debb761c29625), [#16595](https://github.com/angular/angular.js/pull/16595)) # 1.7.1 momentum-defiance (2018-06-08) ## Bug Fixes - **$compile:** support transcluding multi-element directives ([789db8](https://github.com/angular/angular.js/commit/789db83a8ae0e2db5db13289b2c29e56093d967a), [#15554](https://github.com/angular/angular.js/issues/15554), [#15555](https://github.com/angular/angular.js/issues/15555)) - **ngModel:** do not throw if view value changes on destroyed scope ([2b6c98](https://github.com/angular/angular.js/commit/2b6c9867369fd3ef1ddb687af1153478ab62ee1b), [#16583](https://github.com/angular/angular.js/issues/16583), [#16585](https://github.com/angular/angular.js/issues/16585)) ## New Features - **$compile:** add one-way collection bindings ([f9d1ca](https://github.com/angular/angular.js/commit/f9d1ca20c38f065f15769fbe23aee5314cb58bd4), [#14039](https://github.com/angular/angular.js/issues/14039), [#16553](https://github.com/angular/angular.js/issues/16553), [#15874](https://github.com/angular/angular.js/issues/15874)) - **ngRef:** add directive to publish controller, or element into scope ([bf841d](https://github.com/angular/angular.js/commit/bf841d35120bf3c4655fde46af4105c85a0f1cdc), [#16511](https://github.com/angular/angular.js/issues/16511)) - **errorHandlingConfig:** add option to exclude error params from url ([3d6c45](https://github.com/angular/angular.js/commit/3d6c45d76e30b1b3c4eb9672cf4a93e5251c06b3), [#14744](https://github.com/angular/angular.js/issues/14744), [#15707](https://github.com/angular/angular.js/issues/15707), [#16283](https://github.com/angular/angular.js/issues/16283), [#16299](https://github.com/angular/angular.js/issues/16299), [#16591](https://github.com/angular/angular.js/issues/16591)) - **ngAria:** add support for ignoring a specific element ([7d9d38](https://github.com/angular/angular.js/commit/7d9d387195292cb5e04984602b752d31853cfea6), [#14602](https://github.com/angular/angular.js/issues/14602), [#14672](https://github.com/angular/angular.js/issues/14672), [#14833](https://github.com/angular/angular.js/issues/14833)) - **ngCookies:** support samesite option ([10a229](https://github.com/angular/angular.js/commit/10a229ce1befdeaf6295d1635dc11391c252a91a), [#16543](https://github.com/angular/angular.js/issues/16543), [#16544](https://github.com/angular/angular.js/issues/16544)) - **ngMessages:** add support for default message ([a8c263](https://github.com/angular/angular.js/commit/a8c263c1947cc85ee60b4732f7e4bcdc7ba463e8), [#12008](https://github.com/angular/angular.js/issues/12008), [#12213](https://github.com/angular/angular.js/issues/12213), [#16587](https://github.com/angular/angular.js/issues/16587)) - **ngMock, ngMockE2E:** add option to match latest definition for `$httpBackend` request ([773f39](https://github.com/angular/angular.js/commit/773f39c9345479f5f8b6321236ce6ad96f77aa92), [#16251](https://github.com/angular/angular.js/issues/16251), [#11637](https://github.com/angular/angular.js/issues/11637), [#16560](https://github.com/angular/angular.js/issues/16560)) - **$route:** add support for the `reloadOnUrl` configuration option ([f4f571](https://github.com/angular/angular.js/commit/f4f571efdf86d6acbcd5c6b1de66b4b33a259125), [#7925](https://github.com/angular/angular.js/issues/7925), [#15002](https://github.com/angular/angular.js/issues/15002)) # 1.7.0 nonexistent-physiology (2018-05-11) **Here are the full changes for the release of 1.7.0 that are not already released in the 1.6.x branch, which includes commits from 1.7.0-rc.0 and commits from 1.7.0 directly.** 1.7.0 is the last scheduled release of AngularJS that includes breaking changes. 1.7.x patch releases will continue to receive bug fixes and non-breaking features until AngularJS enters Long Term Support mode (LTS) on July 1st 2018. ## Bug Fixes - **input:** - listen on "change" instead of "click" for radio/checkbox ngModels ([656c8f](https://github.com/angular/angular.js/commit/656c8fa8f23b1277cc5c214c4d0237f3393afa1e), [#4516](https://github.com/angular/angular.js/issues/4516), [#14667](https://github.com/angular/angular.js/issues/14667), [#14685](https://github.com/angular/angular.js/issues/14685)) - **input\[number\]:** validate min/max against viewValue ([aa3f95](https://github.com/angular/angular.js/commit/aa3f951330ec7b10b43ea884d9b5754e296770ec), [#12761](https://github.com/angular/angular.js/issues/12761), [#16325](https://github.com/angular/angular.js/issues/16325)) - **input\[date\]:** correctly parse 2-digit years ([627180](https://github.com/angular/angular.js/commit/627180fb71b92048d5b9ca2606b9eff1fd99387e), [#16537](https://github.com/angular/angular.js/issues/16537), [#16539](https://github.com/angular/angular.js/issues/16539)) - **jqLite:** make removeData() not remove event handlers ([b7d396](https://github.com/angular/angular.js/commit/b7d396b8b6e8f27a1f4556d58fc903321e8d532a), [#15869](https://github.com/angular/angular.js/issues/15869), [#16512](https://github.com/angular/angular.js/issues/16512)) - **$compile:** - remove the preAssignBindingsEnabled flag ([38f8c9](https://github.com/angular/angular.js/commit/38f8c97af74649ce224b6dd45f433cc665acfbfb), [#15782](https://github.com/angular/angular.js/issues/15782)) - add `base[href]` to the list of RESOURCE_URL context attributes ([1cf728](https://github.com/angular/angular.js/commit/1cf728e209a9e0016068fac2769827e8f747760e), [#15597](https://github.com/angular/angular.js/issues/15597)) - **$interval:** throw when trying to cancel non-$interval promise ([a8bef9](https://github.com/angular/angular.js/commit/a8bef95127775d83d80daa4617c33227c4b443d4), [#16424](https://github.com/angular/angular.js/issues/16424), [#16476](https://github.com/angular/angular.js/issues/16476)) - **$timeout:** throw when trying to cancel non-$timeout promise ([336525](https://github.com/angular/angular.js/commit/3365256502344970f86355d3ace1cb4251ae9828), [#16424](https://github.com/angular/angular.js/issues/16424), [#16476](https://github.com/angular/angular.js/issues/16476)) - **$cookies:** remove the deprecated $cookieStore factory ([73c646](https://github.com/angular/angular.js/commit/73c6467f1468353215dc689c019ed83aa4993c77), [#16465](https://github.com/angular/angular.js/issues/16465)) - **$resource:** fix interceptors and success/error callbacks ([ea0585](https://github.com/angular/angular.js/commit/ea0585773bb93fd891576e2271254a17e15f1ddd), [#6731](https://github.com/angular/angular.js/issues/6731), [#9334](https://github.com/angular/angular.js/issues/9334), [#6865](https://github.com/angular/angular.js/issues/6865), [#16446](https://github.com/angular/angular.js/issues/16446)) - **$templateRequest:** - give tpload error the correct namespace ([c617d6](https://github.com/angular/angular.js/commit/c617d6dceee5b000bfceda44ced22fc16b48b18b)) - always return the template that is stored in the cache ([fb0099](https://github.com/angular/angular.js/commit/fb00991460cf69ae8bc7f1f826363d09c73c0d5e), [#16225](https://github.com/angular/angular.js/issues/16225)) - **$animate:** let cancel() reject the runner promise ([16b82c](https://github.com/angular/angular.js/commit/16b82c6afe0ab916fef1d6ca78053b00bf5ada83), [#14204](https://github.com/angular/angular.js/issues/14204), [#16373](https://github.com/angular/angular.js/issues/16373)) - **ngTouch:** - deprecate the module and its contents ([67f54b](https://github.com/angular/angular.js/commit/67f54b660038de2b4346b3e76d66a8dc8ccb1f9b), [#16427](https://github.com/angular/angular.js/issues/16427), [#16431](https://github.com/angular/angular.js/issues/16431)) - remove ngClick override, `$touchProvider`, and `$touch` ([11d9ad](https://github.com/angular/angular.js/commit/11d9ad1eb25eaf5967195e424108207427835d50), [#15761](https://github.com/angular/angular.js/issues/15761), [#15755](https://github.com/angular/angular.js/issues/15755)) - **ngScenario:** completely remove the angular scenario runner ([0cd392](https://github.com/angular/angular.js/commit/0cd39217828b0ad53eaf731576af17d66c18ff60), [#9405](https://github.com/angular/angular.js/issues/9405)) - **form:** set $submitted to true on child forms when parent is submitted ([223de5](https://github.com/angular/angular.js/commit/223de59e988dc0cc8b4ec3a045b7c0735eba1c77), [#10071](https://github.com/angular/angular.js/issues/10071)) - **$rootScope:** - provide correct value of one-time bindings in watchGroup ([c2b8fa](https://github.com/angular/angular.js/commit/c2b8fab0a480204374d561d6b9b3d47347ac5570)) - don't allow explicit digest calls to affect $evalAsync ([02c046](https://github.com/angular/angular.js/commit/02c04690da16a9bef55694f5db0b8368dc0125c9), [#15127](https://github.com/angular/angular.js/issues/15127), [#15494](https://github.com/angular/angular.js/issues/15494)) - **ngAria:** do not set aria attributes on input[type="hidden"] ([6d5ef3](https://github.com/angular/angular.js/commit/6d5ef34fc6a974cde73157ba94f9706723dd8f5b), [#15113](https://github.com/angular/angular.js/issues/15113), [#16367](https://github.com/angular/angular.js/issues/16367)) - **ngModel, input:** improve handling of built-in named parsers ([74b04c](https://github.com/angular/angular.js/commit/74b04c9403af4fc7df5b6420f22c9f45a3e84140), [#14292](https://github.com/angular/angular.js/issues/14292), [#10076](https://github.com/angular/angular.js/issues/10076), [#16347](https://github.com/angular/angular.js/issues/16347)) - **$httpParamSerializerJQLike:** - call functions as jQuery does ([a784fa](https://github.com/angular/angular.js/commit/a784fab605d825f1158c6292b3c42f8c4a502fdf), [#16138](https://github.com/angular/angular.js/issues/16138), [#16139](https://github.com/angular/angular.js/issues/16139)) - follow jQuery for `null` and `undefined` ([301fdd](https://github.com/angular/angular.js/commit/301fdda648680d89ccab607c413a7ddede7b0165)) - **$parse:** - do not pass scope/locals to interceptors of one-time bindings ([87a586](https://github.com/angular/angular.js/commit/87a586eb9a23cfd0d0bb681cc778b4b8e5c8451d)) - always pass the intercepted value to watchers ([2ee503](https://github.com/angular/angular.js/commit/2ee5033967d5f87a516bad137686b0592e25d26b), [#16021](https://github.com/angular/angular.js/issues/16021)) - respect the interceptor.$stateful flag ([de7403](https://github.com/angular/angular.js/commit/de74034ddf6f92505ccdb61be413a6df2c723f87)) - **Angular:** remove `angular.lowercase` and `angular.uppercase` ([1daa4f](https://github.com/angular/angular.js/commit/1daa4f2231a89ee88345689f001805ffffa9e7de), [#15445](https://github.com/angular/angular.js/issues/15445)) - **$controller:** remove instantiating controllers defined on window ([e269c1](https://github.com/angular/angular.js/commit/e269c14425a3209040f65c022658770e00a36f16), [#15349](https://github.com/angular/angular.js/issues/15349), [#15762](https://github.com/angular/angular.js/issues/15762)) ## New Features - **angular.isArray:** support Array subclasses in `angular.isArray()` ([e3ece2](https://github.com/angular/angular.js/commit/e3ece2fad9e1e6d47b5f06815ff186d7e6f44948), [#15533](https://github.com/angular/angular.js/issues/15533), [#15541](https://github.com/angular/angular.js/issues/15541)) - **$sce:** handle URL sanitization through the `$sce` service ([1e9ead](https://github.com/angular/angular.js/commit/1e9eadcd72dbbd5c67dae8328a63e535cfa91ff9)) - **orderBy:** consider `null` and `undefined` greater than other values ([1d8046](https://github.com/angular/angular.js/commit/1d804645f7656d592c90216a0355b4948807f6b8), [#15294](https://github.com/angular/angular.js/issues/15294), [#16376](https://github.com/angular/angular.js/issues/16376)) - **$resource:** add support for `request` and `requestError` interceptors (#15674) ([240a3d](https://github.com/angular/angular.js/commit/240a3ddbf12a9bb79754031be95dae4b6bd2dded), [#5146](https://github.com/angular/angular.js/issues/5146)) - **ngModelOptions:** add debounce catch-all + allow debouncing 'default' only ([55ba44](https://github.com/angular/angular.js/commit/55ba44913e02650b56410aa9ab5eeea5d3492b68), [#15411](https://github.com/angular/angular.js/issues/15411), [#16335](https://github.com/angular/angular.js/issues/16335)) - **$compile:** lower the `xlink:href` security context for SVG's `a` and `image` elements ([6ccbfa](https://github.com/angular/angular.js/commit/6ccbfa65d60a3dc396d0cf6da21b993ad74653fd), [#15736](https://github.com/angular/angular.js/issues/15736)) ## Performance Improvements - **$rootScope:** allow $watchCollection use of expression input watching ([97b00c](https://github.com/angular/angular.js/commit/97b00ca497676aaff8a803762a9f8c7ff4aa24dd)) - **ngStyle:** use $watchCollection ([15bbd3](https://github.com/angular/angular.js/commit/15bbd3e18cd89b91f7206a06c73d40e54a8a48a0), [#15947](https://github.com/angular/angular.js/issues/15947)) - **$compile:** do not use deepWatch in literal one-way bindings ([fd4f01](https://github.com/angular/angular.js/commit/fd4f0111188b62773b99ab6eab38b4d2b5d8d727), [#15301](https://github.com/angular/angular.js/issues/15301)) ## Breaking Changes ### **jqLite** due to: - **[b7d396](https://github.com/angular/angular.js/commit/b7d396b8b6e8f27a1f4556d58fc903321e8d532a)**: make removeData() not remove event handlers Before this commit `removeData()` invoked on an element removed its event handlers as well. If you want to trigger a full cleanup of an element, change: ```js elem.removeData(); ``` to: ```js angular.element.cleanData(elem); ``` In most cases, though, cleaning up after an element is supposed to be done only when it's removed from the DOM as well; in such cases the following: ```js elem.remove(); ``` will remove event handlers as well. ### **$cookies** due to: - **[73c646](https://github.com/angular/angular.js/commit/73c6467f1468353215dc689c019ed83aa4993c77)**: remove the deprecated $cookieStore factory The $cookieStore has been removed. Migrate to the $cookies service. Note that for object values you need to use the `putObject` & `getObject` methods as `get`/`put` will not correctly save/retrieve them. Before: ```js $cookieStore.put('name', {key: 'value'}); $cookieStore.get('name'); // {key: 'value'} $cookieStore.remove('name'); ``` After: ```js $cookies.putObject('name', {key: 'value'}); $cookies.getObject('name'); // {key: 'value'} $cookies.remove('name'); ``` ### **$resource** due to: - **[ea0585](https://github.com/angular/angular.js/commit/ea0585773bb93fd891576e2271254a17e15f1ddd)**: fix interceptors and success/error callbacks If you are not using `success` or `error` callbacks with `$resource`, your app should not be affected by this change. If you are using `success` or `error` callbacks (with or without response interceptors), one (subtle) difference is that throwing an error inside the callbacks will not propagate to the returned `$promise`. Therefore, you should try to use the promises whenever possible. E.g.: ```js // Avoid User.query(function onSuccess(users) { throw new Error(); }). $promise. catch(function onError() { /* Will not be called. */ }); // Prefer User.query(). $promise. then(function onSuccess(users) { throw new Error(); }). catch(function onError() { /* Will be called. */ }); ``` Finally, if you are using `success` or `error` callbacks with response interceptors, the callbacks will now always run _after_ the interceptors (and wait for them to resolve in case they return a promise). Previously, the `error` callback was called before the `responseError` interceptor and the `success` callback was synchronously called after the `response` interceptor. E.g.: ```js var User = $resource('/api/users/:id', {id: '@id'}, { get: { method: 'get', interceptor: { response: function(response) { console.log('responseInterceptor-1'); return $timeout(1000).then(function() { console.log('responseInterceptor-2'); return response.resource; }); }, responseError: function(response) { console.log('responseErrorInterceptor-1'); return $timeout(1000).then(function() { console.log('responseErrorInterceptor-2'); return $q.reject('Ooops!'); }); } } } }); var onSuccess = function(value) { console.log('successCallback', value); }; var onError = function(error) { console.log('errorCallback', error); }; // Assuming the following call is successful... User.get({id: 1}, onSuccess, onError); // Old behavior: // responseInterceptor-1 // successCallback, {/* Promise object */} // responseInterceptor-2 // New behavior: // responseInterceptor-1 // responseInterceptor-2 // successCallback, {/* User object */} // Assuming the following call returns an error... User.get({id: 2}, onSuccess, onError); // Old behavior: // errorCallback, {/* Response object */} // responseErrorInterceptor-1 // responseErrorInterceptor-2 // New behavior: // responseErrorInterceptor-1 // responseErrorInterceptor-2 // errorCallback, Ooops! ``` - **[240a3d](https://github.com/angular/angular.js/commit/240a3ddbf12a9bb79754031be95dae4b6bd2dded)**: add support for `request` and `requestError` interceptors (#15674) Previously, calling a `$resource` method would synchronously call `$http`. Now, it will be called asynchronously (regardless if a `request`/`requestError` interceptor has been defined. This is not expected to affect applications at runtime, since the overall operation is asynchronous already, but may affect assertions in tests. For example, if you want to assert that `$http` has been called with specific arguments as a result of a `$resource` call, you now need to run a `$digest` first, to ensure the (possibly empty) request interceptor promise has been resolved. Before: ```js it('...', function() { $httpBackend.expectGET('/api/things').respond(...); var Things = $resource('/api/things'); Things.query(); expect($http).toHaveBeenCalledWith(...); }); ``` After: ```js it('...', function() { $httpBackend.expectGET('/api/things').respond(...); var Things = $resource('/api/things'); Things.query(); $rootScope.$digest(); expect($http).toHaveBeenCalledWith(...); }); ``` ### **$templateRequest**: - due to **[c617d6](https://github.com/angular/angular.js/commit/c617d6dceee5b000bfceda44ced22fc16b48b18b)**: give tpload error the correct namespace Previously the `tpload` error was namespaced to `$compile`. If you have code that matches errors of the form `[$compile:tpload]` it will no longer run. You should change the code to match `[$templateRequest:tpload]`. - due to **([fb0099](https://github.com/angular/angular.js/commit/fb00991460cf69ae8bc7f1f826363d09c73c0d5e)**: always return the template that is stored in the cache The service now returns the result of `$templateCache.put()` when making a server request to the template. Previously it would return the content of the response directly. This now means if you are decorating `$templateCache.put()` to manipulate the template, you will now get this manipulated result also on the first `$templateRequest` rather than only on subsequent calls (when the template is retrived from the cache). In practice this should not affect any apps, as it is unlikely that they rely on the template being different in the first and subsequent calls. ### **$animate** due to: - **[16b82c](https://github.com/angular/angular.js/commit/16b82c6afe0ab916fef1d6ca78053b00bf5ada83)**: let cancel() reject the runner promise $animate.cancel(runner) now rejects the underlying promise and calls the catch() handler on the runner returned by $animate functions (enter, leave, move, addClass, removeClass, setClass, animate). Previously it would resolve the promise as if the animation had ended successfully. Example: ```js var runner = $animate.addClass('red'); runner.then(function() { console.log('success')}); runner.catch(function() { console.log('cancelled')}); runner.cancel(); ``` Pre-1.7.0, this logs 'success', 1.7.0 and later it logs 'cancelled'. To migrate, add a catch() handler to your animation runners. ### **angular.isArray** due to: - **[e3ece2](https://github.com/angular/angular.js/commit/e3ece2fad9e1e6d47b5f06815ff186d7e6f44948)**: support Array subclasses in `angular.isArray()` Previously, `angular.isArray()` was an alias for `Array.isArray()`. Therefore, objects that prototypally inherit from `Array` where not considered arrays. Now such objects are considered arrays too. This change affects several other methods that use `angular.isArray()` under the hood, such as `angular.copy()`, `angular.equals()`, `angular.forEach()`, and `angular.merge()`. This in turn affects how dirty checking treats objects that prototypally inherit from `Array` (e.g. MobX observable arrays). AngularJS will now be able to handle these objects better when copying or watching. ### **$sce** : - due to **[1e9ead](https://github.com/angular/angular.js/commit/1e9eadcd72dbbd5c67dae8328a63e535cfa91ff9)**: handle URL sanitization through the `$sce` service If you use `attrs.$set` for URL attributes (a[href] and img[src]) there will no longer be any automated sanitization of the value. This is in line with other programmatic operations, such as writing to the innerHTML of an element. If you are programmatically writing URL values to attributes from untrusted input then you must sanitize it yourself. You could write your own sanitizer or copy the private `$$sanitizeUri` service. Note that values that have been passed through the `$interpolate` service within the `URL` or `MEDIA_URL` will have already been sanitized, so you would not need to sanitize these values again. - due to **[1e9ead](https://github.com/angular/angular.js/commit/1e9eadcd72dbbd5c67dae8328a63e535cfa91ff9)**: handle URL sanitization through the `$sce` service binding `trustAs()` and the short versions (`trustAsResourceUrl()` et al.) to `ngSrc`, `ngSrcset`, and `ngHref` will now raise an infinite digest error: ```js $scope.imgThumbFn = function(id) { return $sce.trustAsResourceUrl(someService.someUrl(id)); }; ``` ```html ``` This is because the `$interpolate` service is now responsible for sanitizing the attribute value, and its watcher receives a new object from `trustAs()` on every digest. To migrate, compute the trusted value only when the input value changes: ```js $scope.$watch('imgId', function(id) { $scope.imgThumb = $sce.trustAsResourceUrl(someService.someUrl(id)); }); ``` ```html ``` ### **orderBy** due to: - **[1d8046](https://github.com/angular/angular.js/commit/1d804645f7656d592c90216a0355b4948807f6b8)**: consider `null` and `undefined` greater than other values When using `orderBy` to sort arrays containing `null` values, the `null` values will be considered "greater than" all other values, except for `undefined`. Previously, they were sorted as strings. This will result in different (but more intuitive) sorting order. Before: ```js orderByFilter(['a', undefined, 'o', null, 'z']); //--> 'a', null, 'o', 'z', undefined ``` After: ```js orderByFilter(['a', undefined, 'o', null, 'z']); //--> 'a', 'o', 'z', null, undefined ``` ### **ngScenario** due to: - **[0cd392](https://github.com/angular/angular.js/commit/0cd39217828b0ad53eaf731576af17d66c18ff60)**: completely remove the angular scenario runner The angular scenario runner end-to-end test framework has been removed from the project and will no longer be available on npm or bower starting with 1.7.0. It was deprecated and removed from the documentation in 2014. Applications that still use it should migrate to [Protractor](http://www.protractortest.org). Technically, it should also be possible to continue using an older version of the scenario runner, as the underlying APIs have not changed. However, we do not guarantee future compatibility. ### **form** due to: - **[223de5](https://github.com/angular/angular.js/commit/223de59e988dc0cc8b4ec3a045b7c0735eba1c77)**: set $submitted to true on child forms when parent is submitted Forms will now set $submitted on child forms when they are submitted. For example: ```
``` Submitting this form will set $submitted on "parentform" and "childform". Previously, it was only set on "parentform". This change was introduced because mixing form and ngForm does not create logically separate forms, but rather something like input groups. Therefore, child forms should inherit the submission state from their parent form. ### **ngAria** due to: - **[6d5ef3](https://github.com/angular/angular.js/commit/6d5ef34fc6a974cde73157ba94f9706723dd8f5b)**: do not set aria attributes on input[type="hidden"] ngAria no longer sets aria-* attributes on input[type="hidden"] with ngModel. This can affect apps that test for the presence of aria attributes on hidden inputs. To migrate, remove these assertions. In actual apps, this should not have a user-facing effect, as the previous behavior was incorrect, and the new behavior is correct for accessibility. ### **ngModel, input** due to: - **[74b04c](https://github.com/angular/angular.js/commit/74b04c9403af4fc7df5b6420f22c9f45a3e84140)**: improve handling of built-in named parsers *Custom* parsers that fail to parse on input types "email", "url", "number", "date", "month", "time", "datetime-local", "week", do no longer set `ngModelController.$error[inputType]`, and the `ng-invalid-[inputType]` class. Also, custom parsers on input type "range" do no longer set `ngModelController.$error.number` and the `ng-invalid-number` class. Instead, any custom parsers on these inputs set `ngModelController.$error.parse` and `ng-invalid-parse`. This change was made to make distinguishing errors from built-in parsers and custom parsers easier. ### **ngModelOptions** due to: - **[55ba44](https://github.com/angular/angular.js/commit/55ba44913e02650b56410aa9ab5eeea5d3492b68)**: add debounce catch-all + allow debouncing 'default' only the 'default' key in 'debounce' now only debounces the default event, i.e. the event that is added as an update trigger by the different input directives automatically. Previously, it also applied to other update triggers defined in 'updateOn' that did not have a corresponding key in the 'debounce'. This behavior is now supported via a special wildcard / catch-all key: '*'. See the following example: Pre-1.7: 'mouseup' is also debounced by 500 milliseconds because 'default' is applied: ``` ng-model-options="{ updateOn: 'default blur mouseup', debounce: { 'default': 500, 'blur': 0 } } ``` 1.7: The pre-1.7 behavior can be re-created by setting '*' as a catch-all debounce value: ``` ng-model-options="{ updateOn: 'default blur mouseup', debounce: { '*': 500, 'blur': 0 } } ``` In contrast, when only 'default' is used, 'blur' and 'mouseup' are not debounced: ``` ng-model-options="{ updateOn: 'default blur mouseup', debounce: { 'default': 500 } } ``` ### **input\[number\]** due to: - **[aa3f95](https://github.com/angular/angular.js/commit/aa3f951330ec7b10b43ea884d9b5754e296770ec)**: validate min/max against viewValue `input[type=number]` with `ngModel` now validates the input for the `max`/`min` restriction against the `ngModelController.$viewValue` instead of against the `ngModelController.$modelValue`. This affects apps that use `$parsers` or `$formatters` to transform the input / model value. If you rely on the $modelValue validation, you can overwrite the `min`/`max` validator from a custom directive, as seen in the following example directive definition object: ``` { restrict: 'A', require: 'ngModel', link: function(scope, element, attrs, ctrl) { var maxValidator = ctrl.$validators.max; ctrl.$validators.max = function(modelValue, viewValue) { return maxValidator(modelValue, modelValue); }; } } ``` ### **input** due to: - **[656c8f](https://github.com/angular/angular.js/commit/656c8fa8f23b1277cc5c214c4d0237f3393afa1e)**: listen on "change" instead of "click" for radio/checkbox ngModels `input[radio]` and `input[checkbox]` now listen to the "change" event instead of the "click" event. Most apps should not be affected, as "change" is automatically fired by browsers after "click" happens. Two scenarios might need migration: - Custom click events: Before this change, custom click event listeners on radio / checkbox would be called after the input element and `ngModel` had been updated, unless they were specifically registered before the built-in click handlers. After this change, they are called before the input is updated, and can call event.preventDefault() to prevent the input from updating. If an app uses a click event listener that expects ngModel to be updated when it is called, it now needs to register a change event listener instead. - Triggering click events: Conventional trigger functions: The change event might not be fired when the input element is not attached to the document. This can happen in **tests** that compile input elements and trigger click events on them. Depending on the browser (Chrome and Safari) and the trigger method, the change event will not be fired when the input isn't attached to the document. Before: ```js it('should update the model', inject(function($compile, $rootScope) { var inputElm = $compile('')($rootScope); inputElm[0].click(); // Or different trigger mechanisms, such as jQuery.trigger() expect($rootScope.checkbox).toBe(true); }); ``` With this patch, `$rootScope.checkbox` might not be true, because the click event hasn't triggered the change event. To make the test, work append the inputElm to the app's `$rootElement`, and the `$rootElement` to the `$document`. After: ```js it('should update the model', inject(function($compile, $rootScope, $rootElement, $document) { var inputElm = $compile('')($rootScope); $rootElement.append(inputElm); $document.append($rootElement); inputElm[0].click(); // Or different trigger mechanisms, such as jQuery.trigger() expect($rootScope.checkbox).toBe(true); }); ``` `triggerHandler()`: If you are using this jQuery / jqLite function on the input elements, you don't have to attach the elements to the document, but instead change the triggered event to "change". This is because `triggerHandler(event)` only triggers the exact event when it has been added by jQuery / jqLite. ### **ngStyle** due to: - **[15bbd3](https://github.com/angular/angular.js/commit/15bbd3e18cd89b91f7206a06c73d40e54a8a48a0)**: use $watchCollection Previously the use of deep watch by ng-style would trigger styles to be re-applied when nested state changed. Now only changes to direct properties of the watched object will trigger changes. ### **$compile** due to: - **[38f8c9](https://github.com/angular/angular.js/commit/38f8c97af74649ce224b6dd45f433cc665acfbfb)**: remove the preAssignBindingsEnabled flag Previously, the `$compileProvider.preAssignBindingsEnabled` flag was supported. The flag controlled whether bindings were available inside the controller constructor or only in the `$onInit` hook. The bindings are now no longer available in the constructor. To migrate your code: 1. If you haven't invoked `$compileProvider.preAssignBindingsEnabled()` you don't have to do anything to migrate. 2. If you specified `$compileProvider.preAssignBindingsEnabled(false)`, you can remove that statement - since AngularJS 1.6.0 this is the default so your app should still work even in AngularJS 1.6 after such removal. Afterwards, migrating to AngularJS 1.7.0 shouldn't require any further action. 3. If you specified `$compileProvider.preAssignBindingsEnabled(true)` you need to first migrate your code so that the flag can be flipped to `false`. The instructions on how to do that are available in the "Migrating from 1.5 to 1.6" guide: https://docs.angularjs.org/guide/migration#migrating-from-1-5-to-1-6 Afterwards, remove the `$compileProvider.preAssignBindingsEnabled(true)` statement. - **[6ccbfa](https://github.com/angular/angular.js/commit/6ccbfa65d60a3dc396d0cf6da21b993ad74653fd)**: lower the `xlink:href` security context for SVG's `a` and `image` elements In the unlikely case that an app relied on RESOURCE_URL whitelisting for the purpose of binding to the `xlink:href` property of SVG's `` or `` elements and if the values do not pass the regular URL sanitization, they will break. To fix this you need to ensure that the values used for binding to the affected `xlink:href` contexts are considered safe URLs, e.g. by whitelisting them in `$compileProvider`'s `aHrefSanitizationWhitelist` (for `` elements) or `imgSrcSanitizationWhitelist` (for `` elements). - **[fd4f01](https://github.com/angular/angular.js/commit/fd4f0111188b62773b99ab6eab38b4d2b5d8d727)**: do not use deepWatch in literal one-way bindings Previously when a literal value was passed into a directive/component via one-way binding it would be watched with a deep watcher. For example, for ``, a new instance of the array would be passed into the directive/component (and trigger $onChanges) not only if `a` changed but also if any sub property of `a` changed such as `a.b` or `a.b.c.d.e` etc. This also means a new but equal value for `a` would NOT trigger such a change. Now literal values use an input-based watch similar to other directive/component one-way bindings. In this context inputs are the non-constant parts of the literal. In the example above the input would be `a`. Changes are only triggered when the inputs to the literal change. - **[1cf728](https://github.com/angular/angular.js/commit/1cf728e209a9e0016068fac2769827e8f747760e)**: add `base[href]` to the list of RESOURCE_URL context attributes Previously, `` would not require `baseUrl` to be trusted as a RESOURCE_URL. Now, `baseUrl` will be sent to `$sce`'s RESOURCE_URL checks. By default, it will break unless `baseUrl` is of the same origin as the application document. Refer to the [`$sce` API docs](https://code.angularjs.org/snapshot/docs/api/ng/service/$sce) for more info on how to trust a value in a RESOURCE_URL context. Also, concatenation in trusted contexts is not allowed, which means that the following won't work: ``. Either construct complex values in a controller (recommended): ```js this.baseUrl = '/something/' + this.partialPath; ``` ```html ``` Or use string concatenation in the interpolation expression (not recommended except for the simplest of cases): ```html ``` ### **ngTouch** due to: - **[11d9ad](https://github.com/angular/angular.js/commit/11d9ad1eb25eaf5967195e424108207427835d50)**: remove ngClick override, `$touchProvider`, and `$touch` The `ngClick` directive from the ngTouch module has been removed, and with it the corresponding `$touchProvider` and `$touch` service. If you have included ngTouch v1.5.0 or higher in your application, and have not changed the value of `$touchProvider.ngClickOverrideEnabled()`, or injected and used the `$touch` service, then there are no migration steps for your code. Otherwise you must remove references to the provider and service. The `ngClick` override directive had been deprecated and by default disabled since v1.5.0, because of buggy behavior in edge cases, and a general trend to avoid special touch based overrides of click events. In modern browsers, it should not be necessary to use a touch override library: - Chrome, Firefox, Edge, and Safari remove the 300ms delay when `` is set. - Internet Explorer 10+, Edge, Safari, and Chrome remove the delay on elements that have the `touch-action` css property is set to `manipulation`. You can find out more in these articles: https://developers.google.com/web/updates/2013/12/300ms-tap-delay-gone-away https://developer.apple.com/library/content/releasenotes/General/WhatsNewInSafari/Articles/Safari_9_1.html#//apple_ref/doc/uid/TP40014305-CH10-SW8 https://blogs.msdn.microsoft.com/ie/2015/02/24/pointer-events-w3c-recommendation-interoperable-touch-and-removing-the-dreaded-300ms-tap-delay/ ### **Angular** due to: - **[1daa4f](https://github.com/angular/angular.js/commit/1daa4f2231a89ee88345689f001805ffffa9e7de)**: remove `angular.lowercase` and `angular.uppercase` The helper functions `angular.lowercase` `and angular.uppercase` have been removed. These functions have been deprecated since 1.5.0. They are internally used, but should not be exposed as they contain special locale handling (for Turkish) to maintain internal consistency regardless of user-set locale. Developers should generally use the built-ins `toLowerCase` and `toUpperCase` or `toLocaleLowerCase` and `toLocaleUpperCase` for special cases. Further, we generally discourage using the angular.x helpers in application code. ### **$controller** due to: - **[e269c1](https://github.com/angular/angular.js/commit/e269c14425a3209040f65c022658770e00a36f16)**: remove instantiating controllers defined on window The option to instantiate controllers from constructors on the global `window` object has been removed. Likewise, the deprecated `$controllerProvider.allowGlobals()` method that could enable this behavior, has been removed. This behavior had been deprecated since AngularJS v1.3.0, because polluting the global scope is bad. To migrate, remove the call to $controllerProvider.allowGlobals() in the config, and register your controller via the Module API or the $controllerProvider, e.g. ``` angular.module('myModule', []).controller('myController', function() {...}); angular.module('myModule', []).config(function($controllerProvider) { $controllerProvider.register('myController', function() {...}); }); ``` ### **$rootScope** due to: - **[c2b8fa](https://github.com/angular/angular.js/commit/c2b8fab0a480204374d561d6b9b3d47347ac5570)**: provide correct value of one-time bindings in watchGroup Previously when using `$watchGroup` the entries in `newValues` and `oldValues` represented the *most recent change of each entry*. Now the entries in `oldValues` will always equal the `newValues` of the previous call of the listener. This means comparing the entries in `newValues` and `oldValues` can be used to determine which individual expressions changed. For example `$scope.$watchGroup(['a', 'b'], fn)` would previously: | Action | newValue | oldValue | |----------|------------|------------| | (init) | [undefined, undefined] | [undefined, undefined] | | `a=1` | [1, undefined] | [undefined, undefined] | | `a=2` | [2, undefined] | [1, undefined] | | `b=3` | [2, 3] | [1, undefined] | Now the `oldValue` will always equal the previous `newValue`: | Action | newValue | oldValue | |----------|------------|------------| | (init) | [undefined, undefined] | [undefined, undefined] | | `a=1` | [1, undefined] | [undefined, undefined] | | `a=2` | [2, undefined] | [1, undefined] | | `b=3` | [2, 3] | [2, undefined] | Note the last call now shows `a === 2` in the `oldValues` array. This also makes the `oldValue` of one-time watchers more clear. Previously the `oldValue` of a one-time watcher would remain `undefined` forever. For example `$scope.$watchGroup(['a', '::b'], fn)` would previously: | Action | newValue | oldValue | |----------|------------|------------| | (init) | [undefined, undefined] | [undefined, undefined] | | `a=1` | [1, undefined] | [undefined, undefined] | | `b=2` | [1, 2] | [undefined, undefined] | | `a=b=3` | [3, 2] | [1, undefined] | Where now the `oldValue` will always equal the previous `newValue`: | Action | newValue | oldValue | |----------|------------|------------| | (init) | [undefined, undefined] | [undefined, undefined] | | `a=1` | [1, undefined] | [undefined, undefined] | | `b=2` | [1, 2] | [1, undefined] | | `a=b=3` | [3, 2] | [1, 2] | ### **$interval** due to: - **[a8bef9](https://github.com/angular/angular.js/commit/a8bef95127775d83d80daa4617c33227c4b443d4)**: throw when trying to cancel non-$interval promise `$interval.cancel()` will throw an error if called with a promise that was not generated by `$interval()`. Previously, it would silently do nothing. Before: ```js var promise = $interval(doSomething, 1000, 5).then(doSomethingElse); $interval.cancel(promise); // No error; interval NOT canceled. ``` After: ```js var promise = $interval(doSomething, 1000, 5).then(doSomethingElse); $interval.cancel(promise); // Throws error. ``` Correct usage: ```js var promise = $interval(doSomething, 1000, 5); var newPromise = promise.then(doSomethingElse); $interval.cancel(promise); // Interval canceled. ``` ### **$timeout** due to: - **[336525](https://github.com/angular/angular.js/commit/3365256502344970f86355d3ace1cb4251ae9828)**: throw when trying to cancel non-$timeout promise `$timeout.cancel()` will throw an error if called with a promise that was not generated by `$timeout()`. Previously, it would silently do nothing. Before: ```js var promise = $timeout(doSomething, 1000).then(doSomethingElse); $timeout.cancel(promise); // No error; timeout NOT canceled. ``` After: ```js var promise = $timeout(doSomething, 1000).then(doSomethingElse); $timeout.cancel(promise); // Throws error. ``` Correct usage: ```js var promise = $timeout(doSomething, 1000); var newPromise = promise.then(doSomethingElse); $timeout.cancel(promise); // Timeout canceled. ``` # 1.7.0-rc.0 maximum-overdrive (2018-04-19) ## Bug Fixes - **input:** - listen on "change" instead of "click" for radio/checkbox ngModels ([656c8f](https://github.com/angular/angular.js/commit/656c8fa8f23b1277cc5c214c4d0237f3393afa1e), [#4516](https://github.com/angular/angular.js/issues/4516), [#14667](https://github.com/angular/angular.js/issues/14667), [#14685](https://github.com/angular/angular.js/issues/14685)) - **input\[number\]:** validate min/max against viewValue ([aa3f95](https://github.com/angular/angular.js/commit/aa3f951330ec7b10b43ea884d9b5754e296770ec), [#12761](https://github.com/angular/angular.js/issues/12761), [#16325](https://github.com/angular/angular.js/issues/16325)) - **jqLite:** make removeData() not remove event handlers ([b7d396](https://github.com/angular/angular.js/commit/b7d396b8b6e8f27a1f4556d58fc903321e8d532a), [#15869](https://github.com/angular/angular.js/issues/15869), [#16512](https://github.com/angular/angular.js/issues/16512)) - **$compile:** - remove the preAssignBindingsEnabled flag ([38f8c9](https://github.com/angular/angular.js/commit/38f8c97af74649ce224b6dd45f433cc665acfbfb), [#15782](https://github.com/angular/angular.js/issues/15782)) - add `base[href]` to the list of RESOURCE_URL context attributes ([1cf728](https://github.com/angular/angular.js/commit/1cf728e209a9e0016068fac2769827e8f747760e), [#15597](https://github.com/angular/angular.js/issues/15597)) - **$interval:** throw when trying to cancel non-$interval promise ([a8bef9](https://github.com/angular/angular.js/commit/a8bef95127775d83d80daa4617c33227c4b443d4), [#16424](https://github.com/angular/angular.js/issues/16424), [#16476](https://github.com/angular/angular.js/issues/16476)) - **$timeout:** throw when trying to cancel non-$timeout promise ([336525](https://github.com/angular/angular.js/commit/3365256502344970f86355d3ace1cb4251ae9828), [#16424](https://github.com/angular/angular.js/issues/16424), [#16476](https://github.com/angular/angular.js/issues/16476)) - **$cookies:** remove the deprecated $cookieStore factory ([73c646](https://github.com/angular/angular.js/commit/73c6467f1468353215dc689c019ed83aa4993c77), [#16465](https://github.com/angular/angular.js/issues/16465)) - **$resource:** fix interceptors and success/error callbacks ([ea0585](https://github.com/angular/angular.js/commit/ea0585773bb93fd891576e2271254a17e15f1ddd), [#6731](https://github.com/angular/angular.js/issues/6731), [#9334](https://github.com/angular/angular.js/issues/9334), [#6865](https://github.com/angular/angular.js/issues/6865), [#16446](https://github.com/angular/angular.js/issues/16446)) - **$templateRequest:** - give tpload error the correct namespace ([c617d6](https://github.com/angular/angular.js/commit/c617d6dceee5b000bfceda44ced22fc16b48b18b)) - always return the template that is stored in the cache ([fb0099](https://github.com/angular/angular.js/commit/fb00991460cf69ae8bc7f1f826363d09c73c0d5e), [#16225](https://github.com/angular/angular.js/issues/16225)) - **$animate:** let cancel() reject the runner promise ([16b82c](https://github.com/angular/angular.js/commit/16b82c6afe0ab916fef1d6ca78053b00bf5ada83), [#14204](https://github.com/angular/angular.js/issues/14204), [#16373](https://github.com/angular/angular.js/issues/16373)) - **ngTouch:** - deprecate the module and its contents ([67f54b](https://github.com/angular/angular.js/commit/67f54b660038de2b4346b3e76d66a8dc8ccb1f9b), [#16427](https://github.com/angular/angular.js/issues/16427), [#16431](https://github.com/angular/angular.js/issues/16431)) - remove ngClick override, `$touchProvider`, and `$touch` ([11d9ad](https://github.com/angular/angular.js/commit/11d9ad1eb25eaf5967195e424108207427835d50), [#15761](https://github.com/angular/angular.js/issues/15761), [#15755](https://github.com/angular/angular.js/issues/15755)) - **ngScenario:** completely remove the angular scenario runner ([0cd392](https://github.com/angular/angular.js/commit/0cd39217828b0ad53eaf731576af17d66c18ff60), [#9405](https://github.com/angular/angular.js/issues/9405)) - **form:** set $submitted to true on child forms when parent is submitted ([223de5](https://github.com/angular/angular.js/commit/223de59e988dc0cc8b4ec3a045b7c0735eba1c77), [#10071](https://github.com/angular/angular.js/issues/10071)) - **$rootScope:** - provide correct value of one-time bindings in watchGroup ([c2b8fa](https://github.com/angular/angular.js/commit/c2b8fab0a480204374d561d6b9b3d47347ac5570)) - **ngAria:** do not set aria attributes on input[type="hidden"] ([6d5ef3](https://github.com/angular/angular.js/commit/6d5ef34fc6a974cde73157ba94f9706723dd8f5b), [#15113](https://github.com/angular/angular.js/issues/15113), [#16367](https://github.com/angular/angular.js/issues/16367)) - **ngModel, input:** improve handling of built-in named parsers ([74b04c](https://github.com/angular/angular.js/commit/74b04c9403af4fc7df5b6420f22c9f45a3e84140), [#14292](https://github.com/angular/angular.js/issues/14292), [#10076](https://github.com/angular/angular.js/issues/10076), [#16347](https://github.com/angular/angular.js/issues/16347)) - **$httpParamSerializerJQLike:** - call functions as jQuery does ([a784fa](https://github.com/angular/angular.js/commit/a784fab605d825f1158c6292b3c42f8c4a502fdf), [#16138](https://github.com/angular/angular.js/issues/16138), [#16139](https://github.com/angular/angular.js/issues/16139)) - follow jQuery for `null` and `undefined` ([301fdd](https://github.com/angular/angular.js/commit/301fdda648680d89ccab607c413a7ddede7b0165)) - **$parse:** - do not pass scope/locals to interceptors of one-time bindings ([87a586](https://github.com/angular/angular.js/commit/87a586eb9a23cfd0d0bb681cc778b4b8e5c8451d)) - always pass the intercepted value to watchers ([2ee503](https://github.com/angular/angular.js/commit/2ee5033967d5f87a516bad137686b0592e25d26b), [#16021](https://github.com/angular/angular.js/issues/16021)) - respect the interceptor.$stateful flag ([de7403](https://github.com/angular/angular.js/commit/de74034ddf6f92505ccdb61be413a6df2c723f87)) - **Angular:** remove `angular.lowercase` and `angular.uppercase` ([1daa4f](https://github.com/angular/angular.js/commit/1daa4f2231a89ee88345689f001805ffffa9e7de), [#15445](https://github.com/angular/angular.js/issues/15445)) - **$controller:** remove instantiating controllers defined on window ([e269c1](https://github.com/angular/angular.js/commit/e269c14425a3209040f65c022658770e00a36f16), [#15349](https://github.com/angular/angular.js/issues/15349), [#15762](https://github.com/angular/angular.js/issues/15762)) ## New Features - **angular.isArray:** support Array subclasses in `angular.isArray()` ([e3ece2](https://github.com/angular/angular.js/commit/e3ece2fad9e1e6d47b5f06815ff186d7e6f44948), [#15533](https://github.com/angular/angular.js/issues/15533), [#15541](https://github.com/angular/angular.js/issues/15541)) - **$sce:** handle URL sanitization through the `$sce` service ([1e9ead](https://github.com/angular/angular.js/commit/1e9eadcd72dbbd5c67dae8328a63e535cfa91ff9)) - **orderBy:** consider `null` and `undefined` greater than other values ([1d8046](https://github.com/angular/angular.js/commit/1d804645f7656d592c90216a0355b4948807f6b8), [#15294](https://github.com/angular/angular.js/issues/15294), [#16376](https://github.com/angular/angular.js/issues/16376)) - **$resource:** add support for `request` and `requestError` interceptors (#15674) ([240a3d](https://github.com/angular/angular.js/commit/240a3ddbf12a9bb79754031be95dae4b6bd2dded), [#5146](https://github.com/angular/angular.js/issues/5146)) - **ngModelOptions:** add debounce catch-all + allow debouncing 'default' only ([55ba44](https://github.com/angular/angular.js/commit/55ba44913e02650b56410aa9ab5eeea5d3492b68), [#15411](https://github.com/angular/angular.js/issues/15411), [#16335](https://github.com/angular/angular.js/issues/16335)) - **$compile:** lower the `xlink:href` security context for SVG's `a` and `image` elements ([6ccbfa](https://github.com/angular/angular.js/commit/6ccbfa65d60a3dc396d0cf6da21b993ad74653fd), [#15736](https://github.com/angular/angular.js/issues/15736)) ## Performance Improvements - **$rootScope:** allow $watchCollection use of expression input watching ([97b00c](https://github.com/angular/angular.js/commit/97b00ca497676aaff8a803762a9f8c7ff4aa24dd)) - **ngStyle:** use $watchCollection ([15bbd3](https://github.com/angular/angular.js/commit/15bbd3e18cd89b91f7206a06c73d40e54a8a48a0), [#15947](https://github.com/angular/angular.js/issues/15947)) - **$compile:** do not use deepWatch in literal one-way bindings ([fd4f01](https://github.com/angular/angular.js/commit/fd4f0111188b62773b99ab6eab38b4d2b5d8d727), [#15301](https://github.com/angular/angular.js/issues/15301)) ## Breaking Changes ### **jqLite** due to: - **[b7d396](https://github.com/angular/angular.js/commit/b7d396b8b6e8f27a1f4556d58fc903321e8d532a)**: make removeData() not remove event handlers Before this commit `removeData()` invoked on an element removed its event handlers as well. If you want to trigger a full cleanup of an element, change: ```js elem.removeData(); ``` to: ```js angular.element.cleanData(elem); ``` In most cases, though, cleaning up after an element is supposed to be done only when it's removed from the DOM as well; in such cases the following: ```js elem.remove(); ``` will remove event handlers as well. ### **$cookies** due to: - **[73c646](https://github.com/angular/angular.js/commit/73c6467f1468353215dc689c019ed83aa4993c77)**: remove the deprecated $cookieStore factory The $cookieStore has been removed. Migrate to the $cookies service. Note that for object values you need to use the `putObject` & `getObject` methods as `get`/`put` will not correctly save/retrieve them. Before: ```js $cookieStore.put('name', {key: 'value'}); $cookieStore.get('name'); // {key: 'value'} $cookieStore.remove('name'); ``` After: ```js $cookies.putObject('name', {key: 'value'}); $cookies.getObject('name'); // {key: 'value'} $cookies.remove('name'); ``` ### **$resource** due to: - **[ea0585](https://github.com/angular/angular.js/commit/ea0585773bb93fd891576e2271254a17e15f1ddd)**: fix interceptors and success/error callbacks If you are not using `success` or `error` callbacks with `$resource`, your app should not be affected by this change. If you are using `success` or `error` callbacks (with or without response interceptors), one (subtle) difference is that throwing an error inside the callbacks will not propagate to the returned `$promise`. Therefore, you should try to use the promises whenever possible. E.g.: ```js // Avoid User.query(function onSuccess(users) { throw new Error(); }). $promise. catch(function onError() { /* Will not be called. */ }); // Prefer User.query(). $promise. then(function onSuccess(users) { throw new Error(); }). catch(function onError() { /* Will be called. */ }); ``` Finally, if you are using `success` or `error` callbacks with response interceptors, the callbacks will now always run _after_ the interceptors (and wait for them to resolve in case they return a promise). Previously, the `error` callback was called before the `responseError` interceptor and the `success` callback was synchronously called after the `response` interceptor. E.g.: ```js var User = $resource('/api/users/:id', {id: '@id'}, { get: { method: 'get', interceptor: { response: function(response) { console.log('responseInterceptor-1'); return $timeout(1000).then(function() { console.log('responseInterceptor-2'); return response.resource; }); }, responseError: function(response) { console.log('responseErrorInterceptor-1'); return $timeout(1000).then(function() { console.log('responseErrorInterceptor-2'); return $q.reject('Ooops!'); }); } } } }); var onSuccess = function(value) { console.log('successCallback', value); }; var onError = function(error) { console.log('errorCallback', error); }; // Assuming the following call is successful... User.get({id: 1}, onSuccess, onError); // Old behavior: // responseInterceptor-1 // successCallback, {/* Promise object */} // responseInterceptor-2 // New behavior: // responseInterceptor-1 // responseInterceptor-2 // successCallback, {/* User object */} // Assuming the following call returns an error... User.get({id: 2}, onSuccess, onError); // Old behavior: // errorCallback, {/* Response object */} // responseErrorInterceptor-1 // responseErrorInterceptor-2 // New behavior: // responseErrorInterceptor-1 // responseErrorInterceptor-2 // errorCallback, Ooops! ``` - **[240a3d](https://github.com/angular/angular.js/commit/240a3ddbf12a9bb79754031be95dae4b6bd2dded)**: add support for `request` and `requestError` interceptors (#15674) Previously, calling a `$resource` method would synchronously call `$http`. Now, it will be called asynchronously (regardless if a `request`/`requestError` interceptor has been defined. This is not expected to affect applications at runtime, since the overall operation is asynchronous already, but may affect assertions in tests. For example, if you want to assert that `$http` has been called with specific arguments as a result of a `$resource` call, you now need to run a `$digest` first, to ensure the (possibly empty) request interceptor promise has been resolved. Before: ```js it('...', function() { $httpBackend.expectGET('/api/things').respond(...); var Things = $resource('/api/things'); Things.query(); expect($http).toHaveBeenCalledWith(...); }); ``` After: ```js it('...', function() { $httpBackend.expectGET('/api/things').respond(...); var Things = $resource('/api/things'); Things.query(); $rootScope.$digest(); expect($http).toHaveBeenCalledWith(...); }); ``` ### **$templateRequest**: - due to **[c617d6](https://github.com/angular/angular.js/commit/c617d6dceee5b000bfceda44ced22fc16b48b18b)**: give tpload error the correct namespace Previously the `tpload` error was namespaced to `$compile`. If you have code that matches errors of the form `[$compile:tpload]` it will no longer run. You should change the code to match `[$templateRequest:tpload]`. - due to **([fb0099](https://github.com/angular/angular.js/commit/fb00991460cf69ae8bc7f1f826363d09c73c0d5e)**: always return the template that is stored in the cache The service now returns the result of `$templateCache.put()` when making a server request to the template. Previously it would return the content of the response directly. This now means if you are decorating `$templateCache.put()` to manipulate the template, you will now get this manipulated result also on the first `$templateRequest` rather than only on subsequent calls (when the template is retrived from the cache). In practice this should not affect any apps, as it is unlikely that they rely on the template being different in the first and subsequent calls. ### **$animate** due to: - **[16b82c](https://github.com/angular/angular.js/commit/16b82c6afe0ab916fef1d6ca78053b00bf5ada83)**: let cancel() reject the runner promise $animate.cancel(runner) now rejects the underlying promise and calls the catch() handler on the runner returned by $animate functions (enter, leave, move, addClass, removeClass, setClass, animate). Previously it would resolve the promise as if the animation had ended successfully. Example: ```js var runner = $animate.addClass('red'); runner.then(function() { console.log('success')}); runner.catch(function() { console.log('cancelled')}); runner.cancel(); ``` Pre-1.7.0, this logs 'success', 1.7.0 and later it logs 'cancelled'. To migrate, add a catch() handler to your animation runners. ### **angular.isArray** due to: - **[e3ece2](https://github.com/angular/angular.js/commit/e3ece2fad9e1e6d47b5f06815ff186d7e6f44948)**: support Array subclasses in `angular.isArray()` Previously, `angular.isArray()` was an alias for `Array.isArray()`. Therefore, objects that prototypally inherit from `Array` where not considered arrays. Now such objects are considered arrays too. This change affects several other methods that use `angular.isArray()` under the hood, such as `angular.copy()`, `angular.equals()`, `angular.forEach()`, and `angular.merge()`. This in turn affects how dirty checking treats objects that prototypally inherit from `Array` (e.g. MobX observable arrays). AngularJS will now be able to handle these objects better when copying or watching. ### **$sce** due to: - **[1e9ead](https://github.com/angular/angular.js/commit/1e9eadcd72dbbd5c67dae8328a63e535cfa91ff9)**: handle URL sanitization through the `$sce` service If you use `attrs.$set` for URL attributes (a[href] and img[src]) there will no longer be any automated sanitization of the value. This is in line with other programmatic operations, such as writing to the innerHTML of an element. If you are programmatically writing URL values to attributes from untrusted input then you must sanitize it yourself. You could write your own sanitizer or copy the private `$$sanitizeUri` service. Note that values that have been passed through the `$interpolate` service within the `URL` or `MEDIA_URL` will have already been sanitized, so you would not need to sanitize these values again. ### **orderBy** due to: - **[1d8046](https://github.com/angular/angular.js/commit/1d804645f7656d592c90216a0355b4948807f6b8)**: consider `null` and `undefined` greater than other values When using `orderBy` to sort arrays containing `null` values, the `null` values will be considered "greater than" all other values, except for `undefined`. Previously, they were sorted as strings. This will result in different (but more intuitive) sorting order. Before: ```js orderByFilter(['a', undefined, 'o', null, 'z']); //--> 'a', null, 'o', 'z', undefined ``` After: ```js orderByFilter(['a', undefined, 'o', null, 'z']); //--> 'a', 'o', 'z', null, undefined ``` ### **ngScenario** due to: - **[0cd392](https://github.com/angular/angular.js/commit/0cd39217828b0ad53eaf731576af17d66c18ff60)**: completely remove the angular scenario runner The angular scenario runner end-to-end test framework has been removed from the project and will no longer be available on npm or bower starting with 1.7.0. It was deprecated and removed from the documentation in 2014. Applications that still use it should migrate to [Protractor](http://www.protractortest.org). Technically, it should also be possible to continue using an older version of the scenario runner, as the underlying APIs have not changed. However, we do not guarantee future compatibility. ### **form** due to: - **[223de5](https://github.com/angular/angular.js/commit/223de59e988dc0cc8b4ec3a045b7c0735eba1c77)**: set $submitted to true on child forms when parent is submitted Forms will now set $submitted on child forms when they are submitted. For example: ```
``` Submitting this form will set $submitted on "parentform" and "childform". Previously, it was only set on "parentform". This change was introduced because mixing form and ngForm does not create logically separate forms, but rather something like input groups. Therefore, child forms should inherit the submission state from their parent form. ### **ngAria** due to: - **[6d5ef3](https://github.com/angular/angular.js/commit/6d5ef34fc6a974cde73157ba94f9706723dd8f5b)**: do not set aria attributes on input[type="hidden"] ngAria no longer sets aria-* attributes on input[type="hidden"] with ngModel. This can affect apps that test for the presence of aria attributes on hidden inputs. To migrate, remove these assertions. In actual apps, this should not have a user-facing effect, as the previous behavior was incorrect, and the new behavior is correct for accessibility. ### **ngModel, input** due to: - **[74b04c](https://github.com/angular/angular.js/commit/74b04c9403af4fc7df5b6420f22c9f45a3e84140)**: improve handling of built-in named parsers *Custom* parsers that fail to parse on input types "email", "url", "number", "date", "month", "time", "datetime-local", "week", do no longer set `ngModelController.$error[inputType]`, and the `ng-invalid-[inputType]` class. Also, custom parsers on input type "range" do no longer set `ngModelController.$error.number` and the `ng-invalid-number` class. Instead, any custom parsers on these inputs set `ngModelController.$error.parse` and `ng-invalid-parse`. This change was made to make distinguishing errors from built-in parsers and custom parsers easier. ### **ngModelOptions** due to: - **[55ba44](https://github.com/angular/angular.js/commit/55ba44913e02650b56410aa9ab5eeea5d3492b68)**: add debounce catch-all + allow debouncing 'default' only the 'default' key in 'debounce' now only debounces the default event, i.e. the event that is added as an update trigger by the different input directives automatically. Previously, it also applied to other update triggers defined in 'updateOn' that did not have a corresponding key in the 'debounce'. This behavior is now supported via a special wildcard / catch-all key: '*'. See the following example: Pre-1.7: 'mouseup' is also debounced by 500 milliseconds because 'default' is applied: ``` ng-model-options="{ updateOn: 'default blur mouseup', debounce: { 'default': 500, 'blur': 0 } } ``` 1.7: The pre-1.7 behavior can be re-created by setting '*' as a catch-all debounce value: ``` ng-model-options="{ updateOn: 'default blur mouseup', debounce: { '*': 500, 'blur': 0 } } ``` In contrast, when only 'default' is used, 'blur' and 'mouseup' are not debounced: ``` ng-model-options="{ updateOn: 'default blur mouseup', debounce: { 'default': 500 } } ``` ### **input\[number\]** due to: - **[aa3f95](https://github.com/angular/angular.js/commit/aa3f951330ec7b10b43ea884d9b5754e296770ec)**: validate min/max against viewValue `input[type=number]` with `ngModel` now validates the input for the `max`/`min` restriction against the `ngModelController.$viewValue` instead of against the `ngModelController.$modelValue`. This affects apps that use `$parsers` or `$formatters` to transform the input / model value. If you rely on the $modelValue validation, you can overwrite the `min`/`max` validator from a custom directive, as seen in the following example directive definition object: ``` { restrict: 'A', require: 'ngModel', link: function(scope, element, attrs, ctrl) { var maxValidator = ctrl.$validators.max; ctrl.$validators.max = function(modelValue, viewValue) { return maxValidator(modelValue, modelValue); }; } } ``` ### **input** due to: - **[656c8f](https://github.com/angular/angular.js/commit/656c8fa8f23b1277cc5c214c4d0237f3393afa1e)**: listen on "change" instead of "click" for radio/checkbox ngModels `input[radio]` and `input[checkbox]` now listen to the "change" event instead of the "click" event. Most apps should not be affected, as "change" is automatically fired by browsers after "click" happens. Two scenarios might need migration: - Custom click events: Before this change, custom click event listeners on radio / checkbox would be called after the input element and `ngModel` had been updated, unless they were specifically registered before the built-in click handlers. After this change, they are called before the input is updated, and can call event.preventDefault() to prevent the input from updating. If an app uses a click event listener that expects ngModel to be updated when it is called, it now needs to register a change event listener instead. - Triggering click events: Conventional trigger functions: The change event might not be fired when the input element is not attached to the document. This can happen in **tests** that compile input elements and trigger click events on them. Depending on the browser (Chrome and Safari) and the trigger method, the change event will not be fired when the input isn't attached to the document. Before: ```js it('should update the model', inject(function($compile, $rootScope) { var inputElm = $compile('')($rootScope); inputElm[0].click(); // Or different trigger mechanisms, such as jQuery.trigger() expect($rootScope.checkbox).toBe(true); }); ``` With this patch, `$rootScope.checkbox` might not be true, because the click event hasn't triggered the change event. To make the test, work append the inputElm to the app's `$rootElement`, and the `$rootElement` to the `$document`. After: ```js it('should update the model', inject(function($compile, $rootScope, $rootElement, $document) { var inputElm = $compile('')($rootScope); $rootElement.append(inputElm); $document.append($rootElement); inputElm[0].click(); // Or different trigger mechanisms, such as jQuery.trigger() expect($rootScope.checkbox).toBe(true); }); ``` `triggerHandler()`: If you are using this jQuery / jqLite function on the input elements, you don't have to attach the elements to the document, but instead change the triggered event to "change". This is because `triggerHandler(event)` only triggers the exact event when it has been added by jQuery / jqLite. ### **ngStyle** due to: - **[15bbd3](https://github.com/angular/angular.js/commit/15bbd3e18cd89b91f7206a06c73d40e54a8a48a0)**: use $watchCollection Previously the use of deep watch by ng-style would trigger styles to be re-applied when nested state changed. Now only changes to direct properties of the watched object will trigger changes. ### **$compile** due to: - **[38f8c9](https://github.com/angular/angular.js/commit/38f8c97af74649ce224b6dd45f433cc665acfbfb)**: remove the preAssignBindingsEnabled flag Previously, the `$compileProvider.preAssignBindingsEnabled` flag was supported. The flag controlled whether bindings were available inside the controller constructor or only in the `$onInit` hook. The bindings are now no longer available in the constructor. To migrate your code: 1. If you haven't invoked `$compileProvider.preAssignBindingsEnabled()` you don't have to do anything to migrate. 2. If you specified `$compileProvider.preAssignBindingsEnabled(false)`, you can remove that statement - since AngularJS 1.6.0 this is the default so your app should still work even in AngularJS 1.6 after such removal. Afterwards, migrating to AngularJS 1.7.0 shouldn't require any further action. 3. If you specified `$compileProvider.preAssignBindingsEnabled(true)` you need to first migrate your code so that the flag can be flipped to `false`. The instructions on how to do that are available in the "Migrating from 1.5 to 1.6" guide: https://docs.angularjs.org/guide/migration#migrating-from-1-5-to-1-6 Afterwards, remove the `$compileProvider.preAssignBindingsEnabled(true)` statement. - **[6ccbfa](https://github.com/angular/angular.js/commit/6ccbfa65d60a3dc396d0cf6da21b993ad74653fd)**: lower the `xlink:href` security context for SVG's `a` and `image` elements In the unlikely case that an app relied on RESOURCE_URL whitelisting for the purpose of binding to the `xlink:href` property of SVG's `` or `` elements and if the values do not pass the regular URL sanitization, they will break. To fix this you need to ensure that the values used for binding to the affected `xlink:href` contexts are considered safe URLs, e.g. by whitelisting them in `$compileProvider`'s `aHrefSanitizationWhitelist` (for `` elements) or `imgSrcSanitizationWhitelist` (for `` elements). - **[fd4f01](https://github.com/angular/angular.js/commit/fd4f0111188b62773b99ab6eab38b4d2b5d8d727)**: do not use deepWatch in literal one-way bindings Previously when a literal value was passed into a directive/component via one-way binding it would be watched with a deep watcher. For example, for ``, a new instance of the array would be passed into the directive/component (and trigger $onChanges) not only if `a` changed but also if any sub property of `a` changed such as `a.b` or `a.b.c.d.e` etc. This also means a new but equal value for `a` would NOT trigger such a change. Now literal values use an input-based watch similar to other directive/component one-way bindings. In this context inputs are the non-constant parts of the literal. In the example above the input would be `a`. Changes are only triggered when the inputs to the literal change. - **[1cf728](https://github.com/angular/angular.js/commit/1cf728e209a9e0016068fac2769827e8f747760e)**: add `base[href]` to the list of RESOURCE_URL context attributes Previously, `` would not require `baseUrl` to be trusted as a RESOURCE_URL. Now, `baseUrl` will be sent to `$sce`'s RESOURCE_URL checks. By default, it will break unless `baseUrl` is of the same origin as the application document. Refer to the [`$sce` API docs](https://code.angularjs.org/snapshot/docs/api/ng/service/$sce) for more info on how to trust a value in a RESOURCE_URL context. Also, concatenation in trusted contexts is not allowed, which means that the following won't work: ``. Either construct complex values in a controller (recommended): ```js this.baseUrl = '/something/' + this.partialPath; ``` ```html ``` Or use string concatenation in the interpolation expression (not recommended except for the simplest of cases): ```html ``` ### **ngTouch** due to: - **[11d9ad](https://github.com/angular/angular.js/commit/11d9ad1eb25eaf5967195e424108207427835d50)**: remove ngClick override, `$touchProvider`, and `$touch` The `ngClick` directive from the ngTouch module has been removed, and with it the corresponding `$touchProvider` and `$touch` service. If you have included ngTouch v1.5.0 or higher in your application, and have not changed the value of `$touchProvider.ngClickOverrideEnabled()`, or injected and used the `$touch` service, then there are no migration steps for your code. Otherwise you must remove references to the provider and service. The `ngClick` override directive had been deprecated and by default disabled since v1.5.0, because of buggy behavior in edge cases, and a general trend to avoid special touch based overrides of click events. In modern browsers, it should not be necessary to use a touch override library: - Chrome, Firefox, Edge, and Safari remove the 300ms delay when `` is set. - Internet Explorer 10+, Edge, Safari, and Chrome remove the delay on elements that have the `touch-action` css property is set to `manipulation`. You can find out more in these articles: https://developers.google.com/web/updates/2013/12/300ms-tap-delay-gone-away https://developer.apple.com/library/content/releasenotes/General/WhatsNewInSafari/Articles/Safari_9_1.html#//apple_ref/doc/uid/TP40014305-CH10-SW8 https://blogs.msdn.microsoft.com/ie/2015/02/24/pointer-events-w3c-recommendation-interoperable-touch-and-removing-the-dreaded-300ms-tap-delay/ ### **Angular** due to: - **[1daa4f](https://github.com/angular/angular.js/commit/1daa4f2231a89ee88345689f001805ffffa9e7de)**: remove `angular.lowercase` and `angular.uppercase` The helper functions `angular.lowercase` `and angular.uppercase` have been removed. These functions have been deprecated since 1.5.0. They are internally used, but should not be exposed as they contain special locale handling (for Turkish) to maintain internal consistency regardless of user-set locale. Developers should generally use the built-ins `toLowerCase` and `toUpperCase` or `toLocaleLowerCase` and `toLocaleUpperCase` for special cases. Further, we generally discourage using the angular.x helpers in application code. ### **$controller** due to: - **[e269c1](https://github.com/angular/angular.js/commit/e269c14425a3209040f65c022658770e00a36f16)**: remove instantiating controllers defined on window The option to instantiate controllers from constructors on the global `window` object has been removed. Likewise, the deprecated `$controllerProvider.allowGlobals()` method that could enable this behavior, has been removed. This behavior had been deprecated since AngularJS v1.3.0, because polluting the global scope is bad. To migrate, remove the call to $controllerProvider.allowGlobals() in the config, and register your controller via the Module API or the $controllerProvider, e.g. ``` angular.module('myModule', []).controller('myController', function() {...}); angular.module('myModule', []).config(function($controllerProvider) { $controllerProvider.register('myController', function() {...}); }); ``` ### **$rootScope** due to: - **[c2b8fa](https://github.com/angular/angular.js/commit/c2b8fab0a480204374d561d6b9b3d47347ac5570)**: provide correct value of one-time bindings in watchGroup Previously when using `$watchGroup` the entries in `newValues` and `oldValues` represented the *most recent change of each entry*. Now the entries in `oldValues` will always equal the `newValues` of the previous call of the listener. This means comparing the entries in `newValues` and `oldValues` can be used to determine which individual expressions changed. For example `$scope.$watchGroup(['a', 'b'], fn)` would previously: | Action | newValue | oldValue | |----------|------------|------------| | (init) | [undefined, undefined] | [undefined, undefined] | | `a=1` | [1, undefined] | [undefined, undefined] | | `a=2` | [2, undefined] | [1, undefined] | | `b=3` | [2, 3] | [1, undefined] | Now the `oldValue` will always equal the previous `newValue`: | Action | newValue | oldValue | |----------|------------|------------| | (init) | [undefined, undefined] | [undefined, undefined] | | `a=1` | [1, undefined] | [undefined, undefined] | | `a=2` | [2, undefined] | [1, undefined] | | `b=3` | [2, 3] | [2, undefined] | Note the last call now shows `a === 2` in the `oldValues` array. This also makes the `oldValue` of one-time watchers more clear. Previously the `oldValue` of a one-time watcher would remain `undefined` forever. For example `$scope.$watchGroup(['a', '::b'], fn)` would previously: | Action | newValue | oldValue | |----------|------------|------------| | (init) | [undefined, undefined] | [undefined, undefined] | | `a=1` | [1, undefined] | [undefined, undefined] | | `b=2` | [1, 2] | [undefined, undefined] | | `a=b=3` | [3, 2] | [1, undefined] | Where now the `oldValue` will always equal the previous `newValue`: | Action | newValue | oldValue | |----------|------------|------------| | (init) | [undefined, undefined] | [undefined, undefined] | | `a=1` | [1, undefined] | [undefined, undefined] | | `b=2` | [1, 2] | [1, undefined] | | `a=b=3` | [3, 2] | [1, 2] | ### **$interval** due to: - **[a8bef9](https://github.com/angular/angular.js/commit/a8bef95127775d83d80daa4617c33227c4b443d4)**: throw when trying to cancel non-$interval promise `$interval.cancel()` will throw an error if called with a promise that was not generated by `$interval()`. Previously, it would silently do nothing. Before: ```js var promise = $interval(doSomething, 1000, 5).then(doSomethingElse); $interval.cancel(promise); // No error; interval NOT canceled. ``` After: ```js var promise = $interval(doSomething, 1000, 5).then(doSomethingElse); $interval.cancel(promise); // Throws error. ``` Correct usage: ```js var promise = $interval(doSomething, 1000, 5); var newPromise = promise.then(doSomethingElse); $interval.cancel(promise); // Interval canceled. ``` ### **$timeout** due to: - **[336525](https://github.com/angular/angular.js/commit/3365256502344970f86355d3ace1cb4251ae9828)**: throw when trying to cancel non-$timeout promise `$timeout.cancel()` will throw an error if called with a promise that was not generated by `$timeout()`. Previously, it would silently do nothing. Before: ```js var promise = $timeout(doSomething, 1000).then(doSomethingElse); $timeout.cancel(promise); // No error; timeout NOT canceled. ``` After: ```js var promise = $timeout(doSomething, 1000).then(doSomethingElse); $timeout.cancel(promise); // Throws error. ``` Correct usage: ```js var promise = $timeout(doSomething, 1000); var newPromise = promise.then(doSomethingElse); $timeout.cancel(promise); // Timeout canceled. ``` # 1.6.10 crystalline-persuasion (2018-04-17) ## Bug Fixes - **$compile:** - correctly handle `null`/`undefined` href `attrs.$set()` ([f04e04](https://github.com/angular/angular.js/commit/f04e04e0e63e0d30c29718abd5cae634901793b2), [#16520](https://github.com/angular/angular.js/issues/16520)) - throw error in `$onChanges` immediately ([b7d1e0fbd](https://github.com/angular/angular.js/commit/983e27b628fd1eab653e2b3966d90a270f27cc93), [#15578](https://github.com/angular/angular.js/issues/15578), [#16492](https://github.com/angular/angular.js/issues/16492)) - **input:** - allow overriding timezone for date input types ([4355de](https://github.com/angular/angular.js/commit/4355dee21d26667bb7f6f21bf75c081351315033), [#16181](https://github.com/angular/angular.js/issues/16181), [#13382](https://github.com/angular/angular.js/issues/13382), [#16336](https://github.com/angular/angular.js/issues/16336)) - take timezone into account when validating minimum and maximum in date types ([2f0ac6](https://github.com/angular/angular.js/commit/2f0ac696cb09aec3e291bb8c9c8a1092cbe3a061), [#16342](https://github.com/angular/angular.js/issues/16342), [#16390](https://github.com/angular/angular.js/issues/16390)) - fix composition mode in IE for Korean input ([9a1b7c](https://github.com/angular/angular.js/commit/9a1b7c9fa135d1dae3f9b4ccf48f081675796e92), [#6656](https://github.com/angular/angular.js/issues/6656), [#16273](https://github.com/angular/angular.js/issues/16273)) - **jqLite:** use XHTML-compliant HTML as input for jqLite ([a0c55a](https://github.com/angular/angular.js/commit/a0c55af9858075ab268a88dd7a4464788a46f4b7), [#6917](https://github.com/angular/angular.js/issues/6917), [#16518](https://github.com/angular/angular.js/issues/16518)) - **minErr:** update url to https ([52e466](https://github.com/angular/angular.js/commit/52e46683bfcc0ce0dc9a3d2ee42b389508423799)) - **$http:** set correct xhrStatus in response when using 'timeout' ([1faf7e](https://github.com/angular/angular.js/commit/1faf7ec30d55bba107b18efbcf0ef07732c55b91)) - **browserTrigger:** support CompositionEvent ([c33fd1](https://github.com/angular/angular.js/commit/c33fd1325417fdc6d7d6abc90cd935130653b149)) ## New Features - **$http:** support sending XSRF token to whitelisted origins ([bc7757](https://github.com/angular/angular.js/commit/bc775759c88b2221c2bb71d2335bc233c93f43b0), [#7862](https://github.com/angular/angular.js/issues/7862)) - **minErr:** strip error url from error parameters ([980b69](https://github.com/angular/angular.js/commit/980b69dcae73dd8a3d0b9d91b63fa7711cd0ba36)) - **$sanitize:** support enhancing elements/attributes white-lists ([ee8e05](https://github.com/angular/angular.js/commit/ee8e05cfafe086188fc318ed4115fb56ba335112), [#5900](https://github.com/angular/angular.js/issues/5900), [#16326](https://github.com/angular/angular.js/issues/16326)) - **$rootScope:** allow suspending and resuming watchers on scope ([efb822c58](https://github.com/angular/angular.js/commit/41d5c90f170cc054b0f8f88220c22ef1ef6cc0a6), [#16308](https://github.com/angular/angular.js/issues/5301)) # 1.6.9 fiery-basilisk (2018-02-02) ## Bug Fixes - **input:** add `drop` event support for IE ([5dc076](https://github.com/angular/angular.js/commit/5dc07667de00c5e85fd69c5b7b7fe4fb5fd65a77)) - **ngMessages:** prevent memory leak from messages that are never attached ([9d058d](https://github.com/angular/angular.js/commit/9d058de04bb78694b83179e9b97bc40214eca01a), [#16389](https://github.com/angular/angular.js/issues/16389), [#16404](https://github.com/angular/angular.js/issues/16404), [#16406](https://github.com/angular/angular.js/issues/16406)) - **ngTransclude:** remove terminal: true ([1d826e](https://github.com/angular/angular.js/commit/1d826e2f1e941d14c3c56d7a0249f5796ba11f85), [#16411](https://github.com/angular/angular.js/issues/16411), [#16412](https://github.com/angular/angular.js/issues/16412)) - **$sanitize:** sanitize `xml:base` attributes ([b9ef65](https://github.com/angular/angular.js/commit/b9ef6585e10477fbbf912a971fe0b390bca692a6)) ## New Features - **currencyFilter:** trim whitespace around an empty currency symbol ([367390](https://github.com/angular/angular.js/commit/3673909896efb6ff47546caf7fc61549f193e043), [#15018](https://github.com/angular/angular.js/issues/15018), [#15085](https://github.com/angular/angular.js/issues/15085), [#15105](https://github.com/angular/angular.js/issues/15105)) # 1.6.8 beneficial-tincture (2017-12-18) ## Bug Fixes - **$location:** - always decode special chars in `$location.url(value)` ([2bdf71](https://github.com/angular/angular.js/commit/2bdf7126878c87474bb7588ce093d0a3c57b0026)) - decode non-component special chars in Hashbang URLS ([57b626](https://github.com/angular/angular.js/commit/57b626a673b7530399d3377dfe770165bec35f8a)) - **ngModelController:** allow $overrideModelOptions to set updateOn ([55516d](https://github.com/angular/angular.js/commit/55516da2dfc7c5798dce24e9fa930c5ac90c900c), [#16351](https://github.com/angular/angular.js/issues/16351), [#16364](https://github.com/angular/angular.js/issues/16364)) ## New Features - **$parse:** add a hidden interface to retrieve an expression's AST ([f33d95](https://github.com/angular/angular.js/commit/f33d95cfcff6fd0270f92a142df8794cca2013ad), [#16253](https://github.com/angular/angular.js/issues/16253), [#16260](https://github.com/angular/angular.js/issues/16260)) # 1.6.7 imperial-backstroke (2017-11-24) ## Bug Fixes - **$compile:** sanitize special chars in directive name ([c4003f](https://github.com/angular/angular.js/commit/c4003fd03489f876b646f06838f4edb576bacf6f), [#16314](https://github.com/angular/angular.js/issues/16314), [#16278](https://github.com/angular/angular.js/issues/16278)) - **$location:** do not decode forward slashes in the path in HTML5 mode ([e06ebf](https://github.com/angular/angular.js/commit/e06ebfdbb558544602fe9da4d7d98045a965f468), [#16312](https://github.com/angular/angular.js/issues/16312)) - **sanitizeUri:** sanitize URIs that contain IDEOGRAPHIC SPACE chars ([ddeb1d](https://github.com/angular/angular.js/commit/ddeb1df15a23de93eb95dbe202e83e93673e1c4e), [#16288](https://github.com/angular/angular.js/issues/16288)) - **$rootScope:** fix potential memory leak when removing scope listeners ([358a69](https://github.com/angular/angular.js/commit/358a69fa8b89b251ee44e523458d6c7f40b92b2d), [#16135](https://github.com/angular/angular.js/issues/16135), [#16161](https://github.com/angular/angular.js/issues/16161)) - **http:** do not allow encoded callback params in jsonp requests ([569e90](https://github.com/angular/angular.js/commit/569e906a5818271416ad0b749be2f58dc34938bd)) - **ngMock:** pass unexpected request failures in `$httpBackend` to the error handler ([1555a4](https://github.com/angular/angular.js/commit/1555a4911ad5360c145c0ddc8ec6c4bf9a381c13), [#16150](https://github.com/angular/angular.js/issues/16150), [#15855](https://github.com/angular/angular.js/issues/15855)) - **ngAnimate:** don't close transitions when child transitions close ([1391e9](https://github.com/angular/angular.js/commit/1391e99c7f73795180b792af21ad4402f96e225d), [#16210](https://github.com/angular/angular.js/issues/16210)) - **ngMock.browserTrigger:** add 'bubbles' to Transition/Animation Event ([7a5f06](https://github.com/angular/angular.js/commit/7a5f06d55d123a39bb7b030667fb1ab672939598)) ## New Features - **$sanitize, $compileProvider, linky:** add support for the "sftp" protocol in links ([a675ea](https://github.com/angular/angular.js/commit/a675ea034366fbb0fcf0d73fed65216aa99bce11), [#16102](https://github.com/angular/angular.js/issues/16102)) - **ngModel.NgModelController:** expose $processModelValue to run model -> view pipeline ([145194](https://github.com/angular/angular.js/commit/14519488ce9218aa891d34e89fc3271fd4ed0f04), [#3407](https://github.com/angular/angular.js/issues/3407), [#10764](https://github.com/angular/angular.js/issues/10764), [#16237](https://github.com/angular/angular.js/issues/16237)) - **$injector:** ability to load new modules after bootstrapping ([6e78fe](https://github.com/angular/angular.js/commit/6e78fee73258bb0ae36414f9db2e8734273e481b)) ## Performance Improvements - **jqLite:** - avoid setting class attribute when not changed ([9c95f6](https://github.com/angular/angular.js/commit/9c95f6d5e00ee7e054aabb3e363f5bfb3b7b4103)) - avoid repeated add/removeAttribute in jqLiteRemoveClass ([cab9eb](https://github.com/angular/angular.js/commit/cab9ebfd5a02e897f802bf6321b8471e4843c5d3), [#16078](https://github.com/angular/angular.js/issues/16078), [#16131](https://github.com/angular/angular.js/issues/16131)) # 1.6.6 interdimensional-cable (2017-08-18) ## Bug Fixes - **$httpParamSerializer:** ignore functions ([b51ded](https://github.com/angular/angular.js/commit/b51ded67366865f36c5781dd5d9b801488ec95ea), [#16133](https://github.com/angular/angular.js/issues/16133)) - **$resource:** do not throw when calling old `$cancelRequest()` ([009ebe](https://github.com/angular/angular.js/commit/009ebec64c81d11b280c635167050e8906e191c6), [#16037](https://github.com/angular/angular.js/issues/16037)) - **$parse:** - do not shallow-watch computed property keys ([750465](https://github.com/angular/angular.js/commit/7504656a26202de591e4ac9674333254304edf8a)) - support constants in computed keys ([9d6c3f](https://github.com/angular/angular.js/commit/9d6c3f3ec233279885e37a250d25860d5c15f716)) - **$http:** do not throw error if `Content-Type` is not `application/json` but response is JSON-like ([2e1163](https://github.com/angular/angular.js/commit/2e1163ef5cb56d1933e8ecd7b74020b9df9c6693), [#16027](https://github.com/angular/angular.js/issues/16027), [#16075](https://github.com/angular/angular.js/issues/16075)) ## New Features - **$compile:** add `strictComponentBindingsEnabled()` method ([3ec181](https://github.com/angular/angular.js/commit/3ec1819b913c8edf0649e06217dbd5920f29f126), [#16129](https://github.com/angular/angular.js/issues/16129)) - **$resource:** add resource to response for error interceptors ([9256db](https://github.com/angular/angular.js/commit/9256dbc4201343ce5cd63a9eadf98da4793f45af), [#16109](https://github.com/angular/angular.js/issues/16109)) - **$http:** allow differentiation between XHR completion, error, abort, timeout ([5e2bc5](https://github.com/angular/angular.js/commit/5e2bc5bbf347a9dfadc08b1514b8be06fd550913), [#15924](https://github.com/angular/angular.js/issues/15924), [#15847](https://github.com/angular/angular.js/issues/15847)) # 1.6.5 toffee-salinization (2017-07-03) ## Bug Fixes - **core:** - correctly detect Error instances from different contexts ([6daca0](https://github.com/angular/angular.js/commit/6daca023e42098f7098b9bf153c8e53a17af84f1), [#15868](https://github.com/angular/angular.js/issues/15868), [#15872](https://github.com/angular/angular.js/issues/15872)) - deprecate `angular.merge` ([dc41f4](https://github.com/angular/angular.js/commit/dc41f465baae9bc91418a61f446596157c530b6e), [#12653](https://github.com/angular/angular.js/issues/12653), [#14941](https://github.com/angular/angular.js/issues/14941), [#15180](https://github.com/angular/angular.js/issues/15180), [#15992](https://github.com/angular/angular.js/issues/15992), [#16036](https://github.com/angular/angular.js/issues/16036)) - **ngOptions:** - re-render after empty option has been removed ([510d0f](https://github.com/angular/angular.js/commit/510d0f946fa1a443ad43fa31bc9337676ef31332)) - allow empty option to be removed and re-added ([71b4da](https://github.com/angular/angular.js/commit/71b4daa4e10b6912891927ee2a7930c604b538f8)) - select unknown option if unmatched model does not match empty option ([17d34b](https://github.com/angular/angular.js/commit/17d34b7a983a0ef63f6cf404490385c696fb0da1)) - **orderBy:** guarantee stable sort ([e50ed4](https://github.com/angular/angular.js/commit/e50ed4da9e8177168f67da68bdf02f07da4e7bcf), [#14881](https://github.com/angular/angular.js/issues/14881), [#15914](https://github.com/angular/angular.js/issues/15914)) - **$parse:** - do not shallow-watch inputs to one-time intercepted expressions ([6e3b5a](https://github.com/angular/angular.js/commit/6e3b5a57cd921823f3eca7200a79ac5c2ef0567a)) - standardize one-time literal vs non-literal and interceptors ([f003d9](https://github.com/angular/angular.js/commit/f003d93a3dd052dccddef41125d9c51034ac3605)) - do not shallow-watch inputs when wrapped in an interceptor fn ([aac562](https://github.com/angular/angular.js/commit/aac5623247a86681cbe0e1c8179617b816394c1d), [#15905](https://github.com/angular/angular.js/issues/15905)) - always re-evaluate filters within literals when an input is an object ([ec9768](https://github.com/angular/angular.js/commit/ec97686f2f4a5481cc806462313a664fc7a1c893), [#15964](https://github.com/angular/angular.js/issues/15964), [#15990](https://github.com/angular/angular.js/issues/15990)) - **$sanitize:** use appropriate inert document strategy for Firefox and Safari ([8f31f1](https://github.com/angular/angular.js/commit/8f31f1ff43b673a24f84422d5c13d6312b2c4d94)) - **$timeout/$interval:** do not trigger a digest on cancel ([a222d0](https://github.com/angular/angular.js/commit/a222d0b452622624dc498ef0b9d3c43647fd4fbc), [#16057](https://github.com/angular/angular.js/issues/16057), [#16064](https://github.com/angular/angular.js/issues/16064))
This change might affect the use of `$timeout.flush()` in unit tests. See the commit message for more info. - **ngMock/$interval:** add support for zero-delay intervals in tests ([a1e3f8](https://github.com/angular/angular.js/commit/a1e3f8728e0a80396f980e48f8dc68dde6721b2b), [#15952](https://github.com/angular/angular.js/issues/15952), [#15953](https://github.com/angular/angular.js/issues/15953)) - **angular-loader:** do not depend on "closure" globals that may not be available ([a3226d](https://github.com/angular/angular.js/commit/a3226d01fadaf145713518dc5b8022b581c34e81), [#15880](https://github.com/angular/angular.js/issues/15880), [#15881](https://github.com/angular/angular.js/issues/15881)) ## New Features - **select:** expose info about selection state in controller ([0b962d](https://github.com/angular/angular.js/commit/0b962d4881e98327a91c37f7317da557aa991663), [#13172](https://github.com/angular/angular.js/issues/13172), [#10127](https://github.com/angular/angular.js/issues/10127)) - **$animate:** add support for `customFilter` ([ab114a](https://github.com/angular/angular.js/commit/ab114af8508bdbdb1fa5fd1e070d08818d882e28), [#14891](https://github.com/angular/angular.js/issues/14891)) - **$compile:** overload `.component()` to accept object map of components ([210112](https://github.com/angular/angular.js/commit/2101126ce72308d8fc468ca2411bb9972e614f79), [#14579](https://github.com/angular/angular.js/issues/14579), [#16062](https://github.com/angular/angular.js/issues/16062)) - **$log:** log all parameters in IE 9, not just the first two. ([3671a4](https://github.com/angular/angular.js/commit/3671a43be43d05b00c90dfb3a3f746c013139581)) - **ngMock:** describe unflushed http requests ([d9128e](https://github.com/angular/angular.js/commit/d9128e7b2371ab2bb5169ba854b21c78baa784d2), [#10596](https://github.com/angular/angular.js/issues/10596), [#15928](https://github.com/angular/angular.js/issues/15928)) ## Performance Improvements - **ngOptions:** prevent initial options repainting ([ff52b1](https://github.com/angular/angular.js/commit/ff52b188a759f2cc7ee6ee78a8c646c2354a47eb), [#15801](https://github.com/angular/angular.js/issues/15801), [#15812](https://github.com/angular/angular.js/issues/15812), [#16071](https://github.com/angular/angular.js/issues/16071)) - **$animate:** - avoid unnecessary computations if animations are globally disabled ([ce5ffb](https://github.com/angular/angular.js/commit/ce5ffbf667464bd58eae4c4af0917eb2685f1f6a), [#14914](https://github.com/angular/angular.js/issues/14914)) - do not retrieve `className` unless `classNameFilter` is used ([275978](https://github.com/angular/angular.js/commit/27597887379a1904cd86832602e286894b449a75)) # 1.6.4 phenomenal-footnote (2017-03-31) ## Bug Fixes - **$parse:** - standardize one-time literal vs non-literal and interceptors ([60394a](https://github.com/angular/angular.js/commit/60394a9d91dad8932fa900af7c8529837f1d4557), [#15858](https://github.com/angular/angular.js/issues/15858)) - fix infinite digest errors when watching objects with .valueOf in literals ([f5ddb1](https://github.com/angular/angular.js/commit/f5ddb10b56676c2ad912ce453acb87f0a7a94e01), [#15867](https://github.com/angular/angular.js/issues/15867)) - **ngModel:** prevent internal scope reference from being copied ([e1f8a6](https://github.com/angular/angular.js/commit/e1f8a6e82bb8a70079ef3db9a891b1c08b5bae31), [#15833](https://github.com/angular/angular.js/issues/15833)) - **jqLite:** make jqLite invoke jqLite.cleanData as a method ([9cde98](https://github.com/angular/angular.js/commit/9cde98cbc770f8d33fc074ba563b7ab6e2baaf8b), [#15846](https://github.com/angular/angular.js/issues/15846)) - **$http:** throw more informative error on invalid JSON response ([df8887](https://github.com/angular/angular.js/commit/df88873bb79213057057adb47151b626a7ec0e5d), [#15695](https://github.com/angular/angular.js/issues/15695), [#15724](https://github.com/angular/angular.js/issues/15724)) - **dateFilter:** correctly handle newlines in `format` string ([982271](https://github.com/angular/angular.js/commit/9822711ad2a401c2449239edc13d18b301714757), [#15794](https://github.com/angular/angular.js/issues/15794), [#15792](https://github.com/angular/angular.js/issues/15792)) ## New Features - **$resource:** add `hasBody` action configuration option ([a9f987](https://github.com/angular/angular.js/commit/a9f987a0c9653246ea471a89197907d94c0cea2a), [#10128](https://github.com/angular/angular.js/issues/10128), [#12181](https://github.com/angular/angular.js/issues/12181)) # 1.6.3 scriptalicious-bootstrapping (2017-03-08) ## Bug Fixes - **AngularJS:** - do not auto-bootstrap if the `src` exists but is empty ([3536e8](https://github.com/angular/angular.js/commit/3536e83d8a085b02bd6dcec8324800b7e6c734e4)) - do not auto bootstrap if the currentScript has been clobbered ([95f964](https://github.com/angular/angular.js/commit/95f964b827b6f5b5aab10af54f7831316c7a9935)) - do not auto-bootstrap if the script source is bad and inside SVG ([c8f78a](https://github.com/angular/angular.js/commit/c8f78a8ca9debc33a6deaf951f344b8d372bf210)) - **$log:** don't parse error stacks manually outside of IE/Edge ([64e5af](https://github.com/angular/angular.js/commit/64e5afc4786fdfd850c6bdb488a5aa2b8b077f74), [#15590](https://github.com/angular/angular.js/issues/15590), [#15767](https://github.com/angular/angular.js/issues/15767)) - **$sanitize:** prevent clobbered elements from freezing the browser ([3bb1dd](https://github.com/angular/angular.js/commit/3bb1dd5d7f7dcde6fea5a3148f8f10e92f451e9d), [#15699](https://github.com/angular/angular.js/issues/15699)) - **$animate:** - reset `classNameFilter` to `null` when a disallowed RegExp is used ([a584fb](https://github.com/angular/angular.js/commit/a584fb6e1569fc1dd85e23b251a7c126edc2dd5b), [#14913](https://github.com/angular/angular.js/issues/14913)) - improve detection on `ng-animate` in `classNameFilter` RegExp ([1f1331](https://github.com/angular/angular.js/commit/1f13313f403381581e1c31c57ebfe7a96546c6e4), [#14806](https://github.com/angular/angular.js/issues/14806)) - **filterFilter:** don't throw if `key.charAt` is not a function ([f27d19](https://github.com/angular/angular.js/commit/f27d19ed606bf05ba41698159ebbc5fbc195033e), [#15644](https://github.com/angular/angular.js/issues/15644), [#15660](https://github.com/angular/angular.js/issues/15660)) - **select:** - add attribute "selected" for `select[multiple]` ([851367](https://github.com/angular/angular.js/commit/8513674911300b27d518383a905fde9b3f25f7ae)) - keep original selection when using shift to add options in IE/Edge ([97b74a](https://github.com/angular/angular.js/commit/97b74ad6fbcbc4b63e37e9eb44962d6f8de83e8b), [#15675](https://github.com/angular/angular.js/issues/15675), [#15676](https://github.com/angular/angular.js/issues/15676)) - **$jsonpCallbacks:** allow `$window` to be mocked in unit tests ([5ca0de](https://github.com/angular/angular.js/commit/5ca0de64873c32ab2f540a3226e73c4175a15c50), [#15685](https://github.com/angular/angular.js/issues/15685), [#15686](https://github.com/angular/angular.js/issues/15686)) ## New Features - **info:** add `angularVersion` info to each module ([1e582e](https://github.com/angular/angular.js/commit/1e582e4fa486f340150bba95927f1b26d9142de2)) - **$injector:** add new `modules` property ([742123](https://github.com/angular/angular.js/commit/7421235f247e5b7113345401bc5727cfbf81ddc2)) - **Module:** add `info()` method ([09ba69](https://github.com/angular/angular.js/commit/09ba69078de6ba52c70571b82b6205929f6facc5), [#15225](https://github.com/angular/angular.js/issues/15225)) - **errorHandlingConfig:** make the depth for object stringification in errors configurable ([4a5eaf](https://github.com/angular/angular.js/commit/4a5eaf7bec85ceca8b934ebaff4d1834a1a09f57), [#15402](https://github.com/angular/angular.js/issues/15402), [#15433](https://github.com/angular/angular.js/issues/15433)) # 1.6.2 llamacorn-lovehug (2017-02-07) ## Bug Fixes - **$compile:** - do not swallow thrown errors in testsg ([0377c6](https://github.com/angular/angular.js/commit/0377c6f0e890cb4ed3eb020b96720b4b34f75df3), [#15629](https://github.com/angular/angular.js/issues/15629), [#15631](https://github.com/angular/angular.js/issues/15631)) - allow the usage of "$" in isolate scope property alias ([7f2af3](https://github.com/angular/angular.js/commit/7f2af3f923e7a3f85c8862d0ed57d21c72eae904), [#15594](https://github.com/angular/angular.js/issues/15594)) - **$location:** correctly handle external URL change during `$digest` ([b60761](https://github.com/angular/angular.js/commit/b607618342d6c4fab364966fe05f152be6bd4d5f), [#11075](https://github.com/angular/angular.js/issues/11075), [#12571](https://github.com/angular/angular.js/issues/12571), [#15556](https://github.com/angular/angular.js/issues/15556), [#15561](https://github.com/angular/angular.js/issues/15561)) - **$browser:** detect external changes in `history.state` ([fa50fb](https://github.com/angular/angular.js/commit/fa50fbaf57b3437be7a410ecaba7008dbe0ef239)) - **$resource:** - do not swallow errors in `success` callback ([27146e](https://github.com/angular/angular.js/commit/27146e8a7fad54c1342179b6d291b1b5c2ebe816), [#15624](https://github.com/angular/angular.js/issues/15624), [#15628](https://github.com/angular/angular.js/issues/15628)) - correctly unescape `/\.` even if `\.` comes from a param value ([419a48](https://github.com/angular/angular.js/commit/419a4813e354496bdf0df44e3f8afaa198df1ab1), [#15627](https://github.com/angular/angular.js/issues/15627)) - delete `$cancelRequest()` in `toJSON()` ([086c5d](https://github.com/angular/angular.js/commit/086c5d0354db8cb3d106b9ff966fb48d6fb46ef8), [#15244](https://github.com/angular/angular.js/issues/15244)) - **$animate:** correctly animate transcluded clones with `templateUrl` ([f01212](https://github.com/angular/angular.js/commit/f01212ab5287ac7a154da7d75037ed444e81eb34), [#15510](https://github.com/angular/angular.js/issues/15510), [#15514](https://github.com/angular/angular.js/issues/15514)) - **$route:** make asynchronous tasks count as pending requests ([eb968c](https://github.com/angular/angular.js/commit/eb968c4a6884838db05369a04459066424c5bba8), [#14159](https://github.com/angular/angular.js/issues/14159)) - **$parse:** make sure ES6 object computed properties are watched ([5e418b](https://github.com/angular/angular.js/commit/5e418b1145a1045da598c7863e785d647ea83850), [#15678](https://github.com/angular/angular.js/issues/15678)) - **$sniffer:** allow `history` for NW.js apps ([4a593d](https://github.com/angular/angular.js/commit/4a593db79ba1e21a6aa600a82cf6d757cad94d01), [#15474](https://github.com/angular/angular.js/issues/15474), [#15633](https://github.com/angular/angular.js/issues/15633)) - **input:** fix `step` validation for `input[type=number/range]` ([c95a67](https://github.com/angular/angular.js/commit/c95a6737fbd277e40c064bd9f68f383bf119505c), [#15504](https://github.com/angular/angular.js/issues/15504), [#15506](https://github.com/angular/angular.js/issues/15506)) - **select:** keep `ngModel` when selected option is recreated by `ngRepeat` ([131af8](https://github.com/angular/angular.js/commit/131af8272d269a541d04cb522c264a91e0ec8b6a), [#15630](https://github.com/angular/angular.js/issues/15630), [#15632](https://github.com/angular/angular.js/issues/15632)) - **ngValue:** correctly update the `value` property when `value` is undefined ([05aab6](https://github.com/angular/angular.js/commit/05aab660ce74f526f2110d3b5faf9a5b4f4e664b) [#15603](https://github.com/angular/angular.js/issues/15603), [#15605](https://github.com/angular/angular.js/issues/15605)) - **angularInit:** allow auto-bootstrapping from inline script ([bb464d](https://github.com/angular/angular.js/commit/bb464d16b434b9e2de2fecf80c192d4741cba879), [#15567](https://github.com/angular/angular.js/issues/15567), [#15571](https://github.com/angular/angular.js/issues/15571)) - **ngMockE2E:** ensure that mocked `$httpBackend` uses correct `$browser` ([bd63b2](https://github.com/angular/angular.js/commit/bd63b2235cd410251cb83eebd9a47d3102830b6b), [#15593](https://github.com/angular/angular.js/issues/15593)) ## New Features - **ngModel:** add `$overrideModelOptions` support ([2546c2](https://github.com/angular/angular.js/commit/2546c29f811b68eea4d68be7fa1c8f7bb562dc11), [#15415](https://github.com/angular/angular.js/issues/15415)) - **$parse:** allow watching array/object literals with non-primitive values ([25f008](https://github.com/angular/angular.js/commit/25f008f541d68b09efd7b428b648c6d4899e6972), [#15301](https://github.com/angular/angular.js/issues/15301)) # 1.5.11 princely-quest (2017-01-13) ## Bug Fixes - **$compile:** allow the usage of "$" in isolate scope property alias ([e75fbc](https://github.com/angular/angular.js/commit/e75fbc494e6a0da6a9231b40bb0382431b62be07), [#15586](https://github.com/angular/angular.js/issues/15586), [#15594](https://github.com/angular/angular.js/issues/15594)) - **angularInit:** allow auto-bootstrapping from inline script ([41aa91](https://github.com/angular/angular.js/commit/41aa9125b9aaf771addb250642f524a4e6f9d8d3), [#15567](https://github.com/angular/angular.js/issues/15567), [#15571](https://github.com/angular/angular.js/issues/15571)) - **$resource:** delete `$cancelRequest()` in `toJSON()` ([4f3858](https://github.com/angular/angular.js/commit/4f3858e7c371f87534397f45b9d002add33b00cc), [#15244](https://github.com/angular/angular.js/issues/15244)) - **$$cookieReader:** correctly handle forbidden access to `document.cookie` ([6933cf](https://github.com/angular/angular.js/commit/6933cf64fe51f54b10d1639f2b95bab3c1178df9), [#15523](https://github.com/angular/angular.js/issues/15523), [#15532](https://github.com/angular/angular.js/issues/15532)) # 1.6.1 promise-rectification (2016-12-23) ## Bug Fixes - **$q:** Add traceback to unhandled promise rejections ([174cb4](https://github.com/angular/angular.js/commit/174cb4a8c81e25581da5b452c2bb43b0fa377a9b), [#14631](https://github.com/angular/angular.js/issues/14631)) - **$$cookieReader:** correctly handle forbidden access to `document.cookie` ([33f769](https://github.com/angular/angular.js/commit/33f769b0a1214055c16fb59adad4897bf53d62bf), [#15523](https://github.com/angular/angular.js/issues/15523)) - **ngOptions:** do not unset the `selected` property unless necessary ([bc4844](https://github.com/angular/angular.js/commit/bc4844d3b297d80aecef89aa1b32615024decedc), [#15477](https://github.com/angular/angular.js/issues/15477)) - **ngModelOptions:** work correctly when on the template of `replace` directives ([5f8ed6](https://github.com/angular/angular.js/commit/5f8ed63f2ab02ffb9c21bf9c29d27c851d162e26), [#15492](https://github.com/angular/angular.js/issues/15492)) - **ngClassOdd/Even:** add/remove the correct classes when expression/`$index` change simultaneously ([d52864](https://github.com/angular/angular.js/commit/d528644fe3e9ffd43999e7fc67806059f9e1083e)) - **jqLite:** silently ignore `after()` if element has no parent ([3d68b9](https://github.com/angular/angular.js/commit/3d68b9502848ff6714ef89bfb95b8e70ae34eff6), [#15331](https://github.com/angular/angular.js/issues/15331), [#15475](https://github.com/angular/angular.js/issues/15475)) - **$rootScope:** when adding/removing watchers during $digest ([163aca](https://github.com/angular/angular.js/commit/163aca336d7586a45255787af41b14b2a12361dd), [#15422](https://github.com/angular/angular.js/issues/15422)) ## Performance Improvements - **ngClass:** avoid unnecessary `.data()` accesses, deep-watching and copies ([1d3b65](https://github.com/angular/angular.js/commit/1d3b65adc2c22ff662159ef910089cf10d1edb7b), [#14404](https://github.com/angular/angular.js/issues/14404)) # 1.5.10 asynchronous-synchronization (2016-12-15) ## Bug Fixes - **$compile:** - don't throw tplrt error when there is whitespace around a top-level comment ([12752f](https://github.com/angular/angular.js/commit/12752f66ac425ab38a5ee574a4bfbf3516adc42c), [#15108](https://github.com/angular/angular.js/issues/15108)) - clean up `@`-binding observers when re-assigning bindings ([f3cb6e](https://github.com/angular/angular.js/commit/f3cb6e309aa1f676e5951ac745fa886d3581c2f4), [#15268](https://github.com/angular/angular.js/issues/15268)) - set attribute value even if `ngAttr*` contains no interpolation ([229799](https://github.com/angular/angular.js/commit/22979904fb754c59e9f6ee5d8763e3b8de0e18c2), [#15133](https://github.com/angular/angular.js/issues/15133)) - `bindToController` should work without `controllerAs` ([944989](https://github.com/angular/angular.js/commit/9449893763a4fd95ee8ff78b53c6966a874ec9ae), [#15088](https://github.com/angular/angular.js/issues/15088)) - do not overwrite values set in `$onInit()` for `<`-bound literals ([07e1ba](https://github.com/angular/angular.js/commit/07e1ba365fb5e8a049be732bd7b62f71e0aa1672), [#15118](https://github.com/angular/angular.js/issues/15118)) - avoid calling `$onChanges()` twice for `NaN` initial values ([0cf5be](https://github.com/angular/angular.js/commit/0cf5be52642f7e9d81a708b3005042eac6492572)) - **$location:** prevent infinite digest with IDN urls in Edge ([4bf892](https://github.com/angular/angular.js/commit/4bf89218130d434771089fdfe643490b8d2ee259), [#15217](https://github.com/angular/angular.js/issues/15217)) - **$rootScope:** correctly handle adding/removing watchers during `$digest` ([a9708d](https://github.com/angular/angular.js/commit/a9708de84b50f06eacda33834d5bbdfc97c97f37), [#15422](https://github.com/angular/angular.js/issues/15422)) - **$sce:** fix `adjustMatcher` to replace multiple `*` and `**` ([78eecb](https://github.com/angular/angular.js/commit/78eecb43dbb0500358d333aea8955bd0646a7790)) - **jqLite:** silently ignore `after()` if element has no parent ([77ed85](https://github.com/angular/angular.js/commit/77ed85bcd3be057a5a79231565ac7accc6d644c6), [#15331](https://github.com/angular/angular.js/issues/15331)) - **input[radio]:** use non-strict comparison for checkedness ([593a50](https://github.com/angular/angular.js/commit/593a5034841b3b7661d3bcbdd06b7a9d0876fd34)) - **select, ngOptions:** - let `ngValue` take precedence over option text with multiple interpolations ([5b7ec8](https://github.com/angular/angular.js/commit/5b7ec8c84e88ee08aacaf9404853eda0016093f5), [#15413](https://github.com/angular/angular.js/issues/15413)) - don't add comment nodes as empty options ([1d29c9](https://github.com/angular/angular.js/commit/1d29c91c3429de96e4103533752700d1266741be), [#15454](https://github.com/angular/angular.js/issues/15454)) - **ngClassOdd/Even:** add/remove the correct classes when expression/`$index` change simultaneously ([e3d020](https://github.com/angular/angular.js/commit/e3d02070ab8a02c818dcc5114db6fba9d3f385d6)) - **$sanitize:** reduce stack height in IE <= 11 ([862dc2](https://github.com/angular/angular.js/commit/862dc2532f8126a4a71fd3d957884ba6f11f591c), [#14928](https://github.com/angular/angular.js/issues/14928)) - **ngMock/$controller:** respect `$compileProvider.preAssignBindingsEnabled()` ([75c83f](https://github.com/angular/angular.js/commit/75c83ff3195931859a099f7a95bf81d32abf2eb3)) ## New Features - **bootstrap:** do not bootstrap from unknown schemes with a different origin ([bdeb33](https://github.com/angular/angular.js/commit/bdeb3392a8719131ab2b993f2a881c43a2860f92), [#15428](https://github.com/angular/angular.js/issues/15428)) - **$anchorScroll:** convert numeric hash targets to string ([a52640](https://github.com/angular/angular.js/commit/a5264090b66ad0cf9a93de84bb7b307868c0edef), [#14680](https://github.com/angular/angular.js/issues/14680)) - **$compile:** - add `preAssignBindingsEnabled` option ([f86576](https://github.com/angular/angular.js/commit/f86576def44005f180a66e3aa12d6cc73c1ac72c)) - throw error when directive name or factory function is invalid ([5c9399](https://github.com/angular/angular.js/commit/5c9399d18ae5cd79e6cf6fc4377d66df00f6fcc7), [#15056](https://github.com/angular/angular.js/issues/15056)) - **$controller:** throw when requested controller is not registered ([9ae793](https://github.com/angular/angular.js/commit/9ae793d8a69afe84370b601e07fc375fc18a576a), [#14980](https://github.com/angular/angular.js/issues/14980)) - **$location:** add support for selectively rewriting links based on attribute ([a4a222](https://github.com/angular/angular.js/commit/a4a22266f127d3b9a6818e6f4754f048e253f693)) - **$resource:** pass `status`/`statusText` to success callbacks ([a8da25](https://github.com/angular/angular.js/commit/a8da25c74d2c1f6265f0fafd95bf72c981d9d678), [#8341](https://github.com/angular/angular.js/issues/8841), [#8841](https://github.com/angular/angular.js/issues/8841)) - **ngSwitch:** allow multiple case matches via optional attribute `ngSwitchWhenSeparator` ([0e1651](https://github.com/angular/angular.js/commit/0e1651bfd28ba73ebd0e4943d85af48c4506e02c), [#3410](https://github.com/angular/angular.js/issues/3410), [#3516](https://github.com/angular/angular.js/issues/3516)) ## Performance Improvements - **all:** don't trigger digests after enter/leave of structural directives ([c57779](https://github.com/angular/angular.js/commit/c57779d8725493c5853dceda0105dafd5c0e3a7c), [#15322](https://github.com/angular/angular.js/issues/15322)) - **$compile:** validate `directive.restrict` property on directive init ([31d464](https://github.com/angular/angular.js/commit/31d464feef38b1cc950da6c8dccd0f194ebfc68b)) - **ngOptions:** avoid calls to `element.value` ([e269ad](https://github.com/angular/angular.js/commit/e269ad1244bc50fee9218f7c18fab3e9ab063aab)) - **jqLite:** move bind/unbind definitions out of the loop ([7717b9](https://github.com/angular/angular.js/commit/7717b96e950a5916a5f12fd611c73d3b06a8d717)) # 1.6.0 rainbow-tsunami (2016-12-08) **Here are the full changes for the release of 1.6.0 that are not already released in the 1.5.x branch, consolidating all the changes shown in the previous 1.6.0 release candidates.** ## New Features - **ngModelOptions:** allow options to be inherited from ancestor `ngModelOptions` ([296cfc](https://github.com/angular/angular.js/commit/296cfce40c25e9438bfa46a0eb27240707a10ffa), [#10922](https://github.com/angular/angular.js/issues/10922)) - **$compile:** - add `preAssignBindingsEnabled` option ([dfb8cf](https://github.com/angular/angular.js/commit/dfb8cf6402678206132e5bc603764d21e0f986ef)) - set `preAssignBindingsEnabled` to false by default ([bcd0d4](https://github.com/angular/angular.js/commit/bcd0d4d896d0dfdd988ff4f849c1d40366125858), [#15352](https://github.com/angular/angular.js/issues/15352)) - throw error when directive name or factory function is invalid ([53a3bf](https://github.com/angular/angular.js/commit/53a3bf6634600c3aeff092eacc35edf399b27aec) [#15056](https://github.com/angular/angular.js/issues/15056)) - **jqLite:** - implement `jqLite(f)` as an alias to `jqLite(document).ready(f)` ([369fb7](https://github.com/angular/angular.js/commit/369fb7f4f73664bcdab0350701552d8bef6f605e)) - don't throw for elements with missing `getAttribute` ([4e6c14](https://github.com/angular/angular.js/commit/4e6c14dcae4a9a30b3610a288ef8d20db47c4417)) - don't get/set properties when getting/setting boolean attributes ([7ceb5f](https://github.com/angular/angular.js/commit/7ceb5f6fcc43d35d1b66c3151ce6a71c60309304), [#14126](https://github.com/angular/angular.js/issues/14126)) - don't remove a boolean attribute for `.attr(attrName, '')` ([3faf45](https://github.com/angular/angular.js/commit/3faf4505732758165083c9d21de71fa9b6983f4a)) - remove the attribute for `.attr(attribute, null)` ([4e3624](https://github.com/angular/angular.js/commit/4e3624552284d0e725bf6262b2e468cd2c7682fa)) - return `[]` for `.val()` on ` ``` The migration strategy is to convert values that matched with non-strict conversion so that they will match with strict conversion. - **feat(ngModelOptions): allow options to be inherited from ancestor `ngModelOptions` ([296cfc](https://github.com/angular/angular.js/commit/296cfce40c25e9438bfa46a0eb27240707a10ffa))**: The programmatic API for `ngModelOptions` has changed. You must now read options via the `ngModelController.$options.getOption(name)` method, rather than accessing the option directly as a property of the `ngModelContoller.$options` object. This does not affect the usage in templates and only affects custom directives that might have been reading options for their own purposes. One benefit of these changes, though, is that the `ngModelControler.$options` property is now guaranteed to be defined so there is no need to check before accessing. So, previously: ``` var myOption = ngModelController.$options && ngModelController.$options['my-option']; ``` and now: ``` var myOption = ngModelController.$options.getOption('my-option'); ``` ### **jqLite** due to: - **[fc0c11](https://github.com/angular/angular.js/commit/fc0c11db845d53061430b7f05e773dcb3fb5b860)**: camelCase keys in `jqLite#data` Previously, keys passed to the data method were left untouched. Now they are internally camelCased similarly to how jQuery handles it, i.e. only single (!) hyphens followed by a lowercase letter get converted to an uppercase letter. This means keys `a-b` and `aB` represent the same data piece; writing to one of them will also be reflected if you ask for the other one. If you use Angular with jQuery, it already behaved in this way so no changes are required on your part. To migrate the code follow the examples below: BEFORE: ```js /* 1 */ elem.data('my-key', 2); elem.data('myKey', 3); /* 2 */ elem.data('foo-bar', 42); elem.data()['foo-bar']; // 42 elem.data()['fooBar']; // undefined /* 3 */ elem.data()['foo-bar'] = 1; elem.data()['fooBar'] = 2; elem.data('foo-bar'); // 1 ``` AFTER: ```js /* 1 */ // Rename one of the keys as they would now map to the same data slot. elem.data('my-key', 2); elem.data('my-key2', 3); /* 2 */ elem.data('foo-bar', 42); elem.data()['foo-bar']; // undefined elem.data()['fooBar']; // 42 /* 3 */ elem.data()['foo-bar'] = 1; elem.data()['fooBar'] = 2; elem.data('foo-bar'); // 2 ``` - **[73050c](https://github.com/angular/angular.js/commit/73050cdda04675bfa6705dc841ddbbb6919eb048)**: align jqLite camelCasing logic with JQuery Before, when Angular was used without jQuery, the key passed to the css method was more heavily camelCased; now only a single (!) hyphen followed by a lowercase letter is getting transformed. This also affects APIs that rely on the css method, like ngStyle. If you use Angular with jQuery, it already behaved in this way so no changes are needed on your part. To migrate the code follow the example below: Before: HTML: ```html // All five versions used to be equivalent.
``` JS: ```js // All five versions used to be equivalent. elem.css('background_color', 'blue'); elem.css('background:color', 'blue'); elem.css('background-color', 'blue'); elem.css('background--color', 'blue'); elem.css('backgroundColor', 'blue'); // All five versions used to be equivalent. var bgColor = elem.css('background_color'); var bgColor = elem.css('background:color'); var bgColor = elem.css('background-color'); var bgColor = elem.css('background--color'); var bgColor = elem.css('backgroundColor'); ``` After: HTML: ```html // Previous five versions are no longer equivalent but these two still are.
``` JS: ```js // Previous five versions are no longer equivalent but these two still are. elem.css('background-color', 'blue'); elem.css('backgroundColor', 'blue'); // Previous five versions are no longer equivalent but these two still are. var bgColor = elem.css('background-color'); var bgColor = elem.css('backgroundColor'); ``` - **[7ceb5f](https://github.com/angular/angular.js/commit/7ceb5f6fcc43d35d1b66c3151ce6a71c60309304)**: don't get/set properties when getting/setting boolean attributes Previously, all boolean attributes were reflected into the corresponding property when calling a setter and from the corresponding property when calling a getter, even on elements that don't treat those attributes in a special way. Now Angular doesn't do it by itself, but relies on browsers to know when to reflect the property. Note that this browser-level conversion differs between browsers; if you need to dynamically change the state of an element, you should modify the property, not the attribute. See https://jquery.com/upgrade-guide/1.9/#attr-versus-prop- for a more detailed description about a related change in jQuery 1.9. This change aligns jqLite with jQuery 3. To migrate the code follow the example below: Before: CSS: ```css input[checked="checked"] { ... } ``` JS: ```js elem1.attr('checked', 'checked'); elem2.attr('checked', false); ``` After: CSS: ```css input:checked { ... } ``` JS: ```js elem1.prop('checked', true); elem2.prop('checked', false); ``` - **[3faf45](https://github.com/angular/angular.js/commit/3faf4505732758165083c9d21de71fa9b6983f4a)**: don't remove a boolean attribute for `.attr(attrName, '')` Before, using the `attr` method with an empty string as a value would remove the boolean attribute. Now it sets it to its lowercase name as was happening for every non-empty string so far. The only two values that remove the boolean attribute are now null & false, just like in jQuery. To migrate the code follow the example below: Before: ```js elem.attr(booleanAttrName, ''); ``` After: ```js elem.attr(booleanAttrName, false); ``` or: ```js elem.attr(booleanAttrName, null); ``` - **[4e3624](https://github.com/angular/angular.js/commit/4e3624552284d0e725bf6262b2e468cd2c7682fa)**: remove the attribute for `.attr(attribute, null)` Invoking `elem.attr(attributeName, null)` would set the `attributeName` attribute value to a string `"null"`, now it removes the attribute instead. To migrate the code follow the example below: Before: ```js elem.attr(attributeName, null); ``` After: ```js elem.attr(attributeName, "null"); ``` - **[d882fd](https://github.com/angular/angular.js/commit/d882fde2e532216e7cf424495db1ccb5be1789f8)**: return [] for .val() on ` ``` JavaScript: ```js var value = $element.val(); if (value) { /* do something */ } ``` After: HTML: ```html ``` JavaScript: ```js var value = $element.val(); if (value.length > 0) { /* do something */ } ``` ### `ngModel` due to: - **[7bc71a](https://github.com/angular/angular.js/commit/7bc71adc63bb6bb609b44dd2d3ea8fb0cd3f300b)**: treat synchronous validators as boolean always Previously, only a literal `false` return would resolve as the synchronous validator failing. Now, all falsy JavaScript values are treated as failing the validator, as one would naturally expect. Specifically, the values `0` (the number zero), `null`, `NaN` and `''` (the empty string) used to be considered valid (passing) and they are now considered invalid (failing). The value `undefined` was treated similarly to a pending asynchronous validator, causing the validation to be pending. `undefined` is also now considered invalid. To migrate, make sure your synchronous validators are returning either a literal `true` or a literal `false` value. For most code, we expect this to already be the case. Only a very small subset of projects will be affected. Namely, anyone using `undefined` or any falsy value as a return will now see their validation failing, whereas previously falsy values other than `undefined` would have been seen as passing and `undefined` would have been seen as pending. - **[9e24e7](https://github.com/angular/angular.js/commit/9e24e774a558143b3478536911a3a4c1714564ba)**: change controllers to use prototype methods The use of prototype methods instead of new methods per instance removes the ability to pass NgModelController and FormController methods without context. For example ```js $scope.$watch('something', myNgModelCtrl.$render) ``` will no longer work because the `$render` method is passed without any context. This must now be replaced with ```js $scope.$watch('something', function() { myNgModelCtrl.$render(); }) ``` or possibly by using `Function.prototype.bind` or `angular.bind`. ### `aria/ngModel` due to: - **[975a61](https://github.com/angular/angular.js/commit/975a6170efceb2a5e6377c57329731c0636eb8c8)**: do not overwrite the default `$isEmpty()` method for checkboxes Custom `checkbox`-shaped controls (e.g. checkboxes, menuitemcheckboxes), no longer have a custom `$isEmpty()` method on their `NgModelController` that checks for `value === false`. Unless overwritten, the default `$isEmpty()` method will be used, which treats `undefined`, `null`, `NaN` and `''` as "empty". **Note:** The `$isEmpty()` method is used to determine if the checkbox is checked ("not empty" means "checked") and thus it can indirectly affect other things, such as the control's validity with respect to the `required` validator (e.g. "empty" + "required" --> "invalid"). Before: ```js var template = ''; var customCheckbox = $compile(template)(scope); var ctrl = customCheckbox.controller('ngModel'); scope.$apply('value = false'); console.log(ctrl.$isEmpty()); //--> true scope.$apply('value = true'); console.log(ctrl.$isEmpty()); //--> false scope.$apply('value = undefined'/* or null or NaN or '' */); console.log(ctrl.$isEmpty()); //--> false ``` After: ```js var template = ''; var customCheckbox = $compile(template)(scope); var ctrl = customCheckbox.controller('ngModel'); scope.$apply('value = false'); console.log(ctrl.$isEmpty()); //--> false scope.$apply('value = true'); console.log(ctrl.$isEmpty()); //--> false scope.$apply('value = undefined'/* or null or NaN or '' */); console.log(ctrl.$isEmpty()); //--> true ``` -- If you want to have a custom `$isEmpty()` method, you need to overwrite the default. For example: ```js .directive('myCheckbox', function myCheckboxDirective() { return { require: 'ngModel', link: function myCheckboxPostLink(scope, elem, attrs, ngModelCtrl) { ngModelCtrl.$isEmpty = function myCheckboxIsEmpty(value) { return !value; // Any falsy value means "empty" // Or to restore the previous behavior: // return value === false; }; } }; }) ``` ### `$http` due to: - **[b54a39](https://github.com/angular/angular.js/commit/b54a39e2029005e0572fbd2ac0e8f6a4e5d69014)**: remove deprecated callback methods: `success()/error()` `$http`'s deprecated custom callback methods - `success()` and `error()` - have been removed. You can use the standard `then()`/`catch()` promise methods instead, but note that the method signatures and return values are different. `success(fn)` can be replaced with `then(fn)`, and `error(fn)` can be replaced with either `then(null, fn)` or `catch(fn)`. Before: ```js $http(...). success(function onSuccess(data, status, headers, config) { // Handle success ... }). error(function onError(data, status, headers, config) { // Handle error ... }); ``` After: ```js $http(...). then(function onSuccess(response) { // Handle success var data = response.data; var status = response.status; var statusText = response.statusText; var headers = response.headers; var config = response.config; ... }, function onError(response) { // Handle error var data = response.data; var status = response.status; var statusText = response.statusText; var headers = response.headers; var config = response.config; ... }); // or $http(...). then(function onSuccess(response) { // Handle success var data = response.data; var status = response.status; var statusText = response.statusText; var headers = response.headers; var config = response.config; ... }). catch(function onError(response) { // Handle error var data = response.data; var status = response.status; var statusText = response.statusText; var headers = response.headers; var config = response.config; ... }); ``` **Note:** There is a subtle difference between the variations showed above. When using `$http(...).success(onSuccess).error(onError)` or `$http(...).then(onSuccess, onError)`, the `onError()` callback will only handle errors/rejections produced by the `$http()` call. If the `onSuccess()` callback produces an error/rejection, it won't be handled by `onError()` and might go unnoticed. In contrast, when using `$http(...).then(onSuccess).catch(onError)`, `onError()` will handle errors/rejections produced by both `$http()` _and_ `onSuccess()`. - **[fb6634](https://github.com/angular/angular.js/commit/fb663418710736161a6b5da49c345e92edf58dcb)**: JSONP callback must be specified by `jsonpCallbackParam` config You can no longer use the `JSON_CALLBACK` placeholder in your JSONP requests. Instead you must provide the name of the query parameter that will pass the callback via the `jsonpCallbackParam` property of the config object, or app-wide via the `$http.defaults.jsonpCallbackParam` property, which is `"callback"` by default. Before this change: ```js $http.json('trusted/url?callback=JSON_CALLBACK'); $http.json('other/trusted/url', {params: {cb:'JSON_CALLBACK'}}); ``` After this change: ```js $http.json('trusted/url'); $http.json('other/trusted/url', {jsonpCallbackParam:'cb'}); ``` - **[6476af](https://github.com/angular/angular.js/commit/6476af83cd0418c84e034a955b12a842794385c4)**: JSONP requests now require a trusted resource URL All JSONP requests now require the URL to be trusted as resource URLs. There are two approaches to trust a URL: **Whitelisting with the `$sceDelegateProvider.resourceUrlWhitelist()` method.** You configure this list in a module configuration block: ```js appModule.config(['$sceDelegateProvider', function($sceDelegateProvider) { $sceDelegateProvider.resourceUrlWhitelist([ // Allow same origin resource loads. 'self', // Allow JSONP calls that match this pattern 'https://some.dataserver.com/**.jsonp?**' ]); }]); ``` **Explicitly trusting the URL via the `$sce.trustAsResourceUrl(url)` method.** You can pass a trusted object instead of a string as a URL to the `$http` service: ```js var promise = $http.jsonp($sce.trustAsResourceUrl(url)); ``` - **[4f6f2b](https://github.com/angular/angular.js/commit/4f6f2bce4ac93b85320e42e5023c09d099779b7d)**: properly increment/decrement `$browser.outstandingRequestCount` HTTP requests now update the outstanding request count synchronously. Previously the request count would not have been updated until the request to the server is actually in flight. Now the request count is updated before the async interceptor is called. The new behaviour is correct but it may change the expected behaviour in a small number of e2e test cases where an async request interceptor is being used. ### `$q` due to: - **[e13eea](https://github.com/angular/angular.js/commit/e13eeabd7e34a78becec06cfbe72c23f2dcb85f9)**: treat thrown errors as regular rejections Previously, throwing an error from a promise's `onFulfilled` or `onRejection` handlers, would result in passing the error to the `$exceptionHandler()` (in addition to rejecting the promise with the error as reason). Now, a thrown error is treated exactly the same as a regular rejection. This applies to all services/controllers/filters etc that rely on `$q` (including built-in services, such as `$http` and `$route`). For example, `$http`'s `transformRequest/Response` functions or a route's `redirectTo` function as well as functions specified in a route's `resolve` object, will no longer result in a call to `$exceptionHandler()` if they throw an error. Other than that, everything will continue to behave in the same way; i.e. the promises will be rejected, route transition will be cancelled, `$routeChangeError` events will be broadcasted etc. - **[c9dffd](https://github.com/angular/angular.js/commit/c9dffde1cb167660120753181cb6d01dc1d1b3d0)**: report promises with non rejection callback Unhandled rejected promises will be logged to $exceptionHandler. Tests that depend on specific order or number of messages in $exceptionHandler will need to handle rejected promises report. ### `ngTransclude` due to: - **[32aa7e](https://github.com/angular/angular.js/commit/32aa7e7395527624119e3917c54ee43b4d219301)**: use fallback content if only whitespace is provided Previously whitespace only transclusion would be treated as the transclusion being "not empty", which meant that fallback content was not used in that case. Now if you only provide whitespace as the transclusion content, it will be assumed to be empty and the fallback content will be used instead. If you really do want whitespace then you can force it to be used by adding a comment to the whitespace. Previously this would not fallback to default content: ```html ``` Now the whitespace between the opening and closing tags is treated as empty. To force the previous behaviour simply add a comment: ```html ``` ### `$compile` due to: - **[13c252](https://github.com/angular/angular.js/commit/13c2522baf7c8f616b2efcaab4bffd54c8736591)**: correctly merge consecutive text nodes on IE11 **Note:** Everything described below affects **IE11 only**. Previously, consecutive text nodes would not get merged if they had no parent. They will now, which might have unexpected side effects in the following cases: 1. Passing an array or jqLite/jQuery collection of parent-less text nodes to `$compile` directly: ```js // Assuming: var textNodes = [ document.createTextNode('{{'), document.createTextNode('"foo:"'), document.createTextNode('}}') ]; var compiledNodes = $compile(textNodes)($rootScope); // Before: console.log(compiledNodes.length); // 3 console.log(compiledNodes.text()); // {{'foo'}} // After: console.log(compiledNodes.length); // 1 console.log(compiledNodes.text()); // foo // To get the old behavior, compile each node separately: var textNodes = [ document.createTextNode('{{'), document.createTextNode('"foo"'), document.createTextNode('}}') ]; var compiledNodes = angular.element(textNodes.map(function (node) { return $compile(node)($rootScope)[0]; })); ``` 2. Using multi-slot transclusion with non-consecutive, default-content text nodes (that form interpolated expressions when merged): ```js // Assuming the following component: .component('someThing', { template: '' transclude: { ignored: 'veryImportantContent' } }) ``` ```html {{ Nooot 'foo'}} {{ <-- Two separate 'foo'}} <-- text nodes foo <-- The text nodes were merged into `{{'foo'}}`, which was then interpolated {{ Nooot 'foo'}} {{ <-- Two separate 'foo'}} <-- nodes ``` - **[b89c21](https://github.com/angular/angular.js/commit/b89c2181a9a165e06c027390164e08635ec449f4)**: move check for interpolation of `on-"event"` attributes to compile time Using interpolation in any on* event attributes (e.g. ` ``` In this setup, there are two ways to trigger ngFocus. First from a user interaction: * Click on the input control * The input control gets focus * The `ngFocus` directive is triggered, setting `$scope.msg='has focus'` from within a new call to `$apply()` Second programmatically: * Click the button * The `ngClick` directive sets the value of `$scope.hasFocus` to true inside a call to `$apply` * The `$digest` runs, which triggers the watch inside the `setFocusIf` directive * The watch's handle runs, which gives the focus to the input * The `ngFocus` directive is triggered, setting `$scope.msg='has focus'` from within a new call to `$apply()` In this second scenario, we are already inside a `$digest` when the ngFocus directive makes another call to `$apply()`, causing this error to be thrown. It is possible to workaround this problem by moving the call to set the focus outside of the digest, by using `$timeout(fn, 0, false)`, where the `false` value tells AngularJS not to wrap this `fn` in an `$apply` block: ``` myApp.directive('setFocusIf', function($timeout) { return { link: function($scope, $element, $attr) { $scope.$watch($attr.setFocusIf, function(value) { if ( value ) { $timeout(function() { // We must reevaluate the value in case it was changed by a subsequent // watch handler in the digest. if ( $scope.$eval($attr.setFocusIf) ) { $element[0].focus(); } }, 0, false); } }); } } }); ``` ## Diagnosing This Error When you get this error it can be rather daunting to diagnose the cause of the issue. The best course of action is to investigate the stack trace from the error. You need to look for places where `$apply` or `$digest` have been called and find the context in which this occurred. There should be two calls: * The first call is the good `$apply`/`$digest` and would normally be triggered by some event near the top of the call stack. * The second call is the bad `$apply`/`$digest` and this is the one to investigate. Once you have identified this call you work your way up the stack to see what the problem is. * If the second call was made in your application code then you should look at why this code has been called from within an `$apply`/`$digest`. It may be a simple oversight or maybe it fits with the sync/async scenario described earlier. * If the second call was made inside an AngularJS directive then it is likely that it matches the second programmatic event trigger scenario described earlier. In this case you may need to look further up the tree to what triggered the event in the first place. ### Example Problem Let's look at how to investigate this error using the `setFocusIf` example from above. This example defines a new `setFocusIf` directive that sets the focus on the element where it is defined when the value of its attribute becomes true. angular.module('app', []).directive('setFocusIf', function() { return function link($scope, $element, $attr) { $scope.$watch($attr.setFocusIf, function(value) { if (value) { $element[0].focus(); } }); }; }); When you click on the button to cause the focus to occur we get our `$rootScope:inprog` error. The stacktrace looks like this: ``` Error: [$rootScope:inprog] at Error (native) at angular.min.js:6:467 at n (angular.min.js:105:60) at g.$get.g.$apply (angular.min.js:113:195) at HTMLInputElement. (angular.min.js:198:401) at angular.min.js:32:32 at Array.forEach (native) at q (angular.min.js:7:295) at HTMLInputElement.c (angular.min.js:32:14) at Object.fn (app.js:12:38) angular.js:10111 (anonymous function) angular.js:10111 $get angular.js:7412 $get.g.$apply angular.js:12738 <--- $apply (anonymous function) angular.js:19833 <--- called here (anonymous function) angular.js:2890 q angular.js:320 c angular.js:2889 (anonymous function) app.js:12 $get.g.$digest angular.js:12469 $get.g.$apply angular.js:12742 <--- $apply (anonymous function) angular.js:19833 <--- called here (anonymous function) angular.js:2890 q angular.js:320 ``` We can see (even though the AngularJS code is minified) that there were two calls to `$apply`, first on line `19833`, then on line `12738` of `angular.js`. It is this second call that caused the error. If we look at the angular.js code, we can see that this call is made by an AngularJS directive. ``` var ngEventDirectives = {}; forEach( 'click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste'.split(' '), function(name) { var directiveName = directiveNormalize('ng-' + name); ngEventDirectives[directiveName] = ['$parse', function($parse) { return { compile: function($element, attr) { var fn = $parse(attr[directiveName]); return function(scope, element, attr) { element.on(lowercase(name), function(event) { scope.$apply(function() { fn(scope, {$event:event}); }); }); }; } }; }]; } ); ``` It is not possible to tell which from the stack trace, but we happen to know in this case that it is the `ngFocus` directive. Now look up the stack to see that our application code is only entered once in `app.js` at line `12`. This is where our problem is: ``` 10: link: function($scope, $element, $attr) { 11: $scope.$watch($attr.setFocusIf, function(value) { 12: if ( value ) { $element[0].focus(); } <---- This is the source of the problem 13: }); 14: } ``` We can now see that the second `$apply` was caused by us programmatically triggering a DOM event (i.e. focus) to occur. We must fix this by moving the code outside of the $apply block using `$timeout` as described above. ## Further Reading To learn more about AngularJS processing model please check out the {@link guide/concepts concepts doc} as well as the {@link ng.$rootScope.Scope api} doc. angular.js-1.7.9/docs/content/error/$route/000077500000000000000000000000001356472325200205425ustar00rootroot00000000000000angular.js-1.7.9/docs/content/error/$route/norout.ngdoc000066400000000000000000000004751356472325200231120ustar00rootroot00000000000000@ngdoc error @name $route:norout @fullName Tried updating route with no current route @description Occurs when an attempt is made to update the parameters on the current route when there is no current route. This can happen if you try to call `$route.updateParams();` before the first route transition has completed.angular.js-1.7.9/docs/content/error/$sanitize/000077500000000000000000000000001356472325200212325ustar00rootroot00000000000000angular.js-1.7.9/docs/content/error/$sanitize/elclob.ngdoc000066400000000000000000000011351356472325200235060ustar00rootroot00000000000000@ngdoc error @name $sanitize:elclob @fullName Failed to sanitize html because the element is clobbered @description This error occurs when `$sanitize` sanitizer is unable to traverse the HTML because one or more of the elements in the HTML have been "clobbered". This could be a sign that the payload contains code attempting to cause a DoS attack on the browser. Typically clobbering breaks the `nextSibling` property on an element so that it points to one of its child nodes. This makes it impossible to walk the HTML tree without getting stuck in an infinite loop, which causes the browser to freeze.angular.js-1.7.9/docs/content/error/$sanitize/noinert.ngdoc000066400000000000000000000010561356472325200237260ustar00rootroot00000000000000@ngdoc error @name $sanitize:noinert @fullName Can't create an inert html document @description This error occurs when `$sanitize` sanitizer determines that `document.implementation.createHTMLDocument ` api is not supported by the current browser. This api is necessary for safe parsing of HTML strings into DOM trees and without it the sanitizer can't sanitize the input. The api is present in all supported browsers including IE 9.0, so the presence of this error usually indicates that AngularJS's `$sanitize` is being used on an unsupported platform. angular.js-1.7.9/docs/content/error/$sanitize/uinput.ngdoc000066400000000000000000000013511356472325200235720ustar00rootroot00000000000000@ngdoc error @name $sanitize:uinput @fullName Failed to sanitize html because the input is unstable @description This error occurs when `$sanitize` sanitizer tries to check the input for possible mXSS payload and the verification errors due to the input mutating indefinitely. This could be a sign that the payload contains code exploiting an mXSS vulnerability in the browser. mXSS attack exploit browser bugs that cause some browsers parse a certain html strings into DOM, which once serialized doesn't match the original input. These browser bugs can be exploited by attackers to create payload which looks harmless to sanitizers, but due to mutations caused by the browser are turned into dangerous code once processed after sanitization. angular.js-1.7.9/docs/content/error/$sce/000077500000000000000000000000001356472325200201565ustar00rootroot00000000000000angular.js-1.7.9/docs/content/error/$sce/icontext.ngdoc000066400000000000000000000004251356472325200230300ustar00rootroot00000000000000@ngdoc error @name $sce:icontext @fullName Invalid / Unknown SCE context @description The context enum passed to {@link ng.$sce#trustAs $sce.trustAs} was not recognized. Please consult the list of {@link ng.$sce#contexts supported Strict Contextual Escaping (SCE) contexts}. angular.js-1.7.9/docs/content/error/$sce/iequirks.ngdoc000066400000000000000000000012231356472325200230240ustar00rootroot00000000000000@ngdoc error @name $sce:iequirks @fullName IE<11 in quirks mode is unsupported @description This error occurs when you are using AngularJS with {@link ng.$sce Strict Contextual Escaping (SCE)} mode enabled (the default) on IE10 or lower in quirks mode. In this mode, IE<11 allow one to execute arbitrary javascript by the use of the `expression()` syntax and is not supported. Refer [CSS expressions no longer supported for the Internet zone](http://msdn.microsoft.com/en-us/library/ie/dn384050(v=vs.85).aspx) to learn more about them. To resolve this error please specify the proper doctype at the top of your main html document: ``` ``` angular.js-1.7.9/docs/content/error/$sce/imatcher.ngdoc000066400000000000000000000005541356472325200227720ustar00rootroot00000000000000@ngdoc error @name $sce:imatcher @fullName Invalid matcher (only string patterns and RegExp instances are supported) @description Please see {@link $sceDelegateProvider#resourceUrlWhitelist $sceDelegateProvider.resourceUrlWhitelist} and {@link $sceDelegateProvider#resourceUrlBlacklist $sceDelegateProvider.resourceUrlBlacklist} for the list of acceptable items. angular.js-1.7.9/docs/content/error/$sce/insecurl.ngdoc000066400000000000000000000030551356472325200230210ustar00rootroot00000000000000@ngdoc error @name $sce:insecurl @fullName Processing of a Resource from Untrusted Source Blocked @description AngularJS' {@link ng.$sce Strict Contextual Escaping (SCE)} mode (enabled by default) has blocked loading a resource from an insecure URL. Typically, this would occur if you're attempting to load an AngularJS template from an untrusted source. It's also possible that a custom directive threw this error for a similar reason. AngularJS only loads templates from trusted URLs (by calling {@link ng.$sce#getTrustedResourceUrl $sce.getTrustedResourceUrl} on the template URL). By default, only URLs that belong to the same origin are trusted. These are urls with the same domain, protocol and port as the application document. The {@link ng.directive:ngInclude ngInclude} directive and {@link guide/directive directives} that specify a `templateUrl` require a trusted resource URL. To load templates from other domains and/or protocols, either adjust the {@link ng.$sceDelegateProvider#resourceUrlWhitelist whitelist}/ {@link ng.$sceDelegateProvider#resourceUrlBlacklist blacklist} or wrap the URL with a call to {@link ng.$sce#trustAsResourceUrl $sce.trustAsResourceUrl}. **Note**: The browser's [Same Origin Policy](https://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest) and [Cross-Origin Resource Sharing (CORS)](http://www.w3.org/TR/cors/) policy apply that may further restrict whether the template is successfully loaded. (e.g. neither cross-domain requests won't work on all browsers nor `file://` requests on some browsers) angular.js-1.7.9/docs/content/error/$sce/itype.ngdoc000066400000000000000000000003601356472325200223230ustar00rootroot00000000000000@ngdoc error @name $sce:itype @fullName String Value is Required for SCE Trust Call @description {@link ng.$sce#trustAs $sce.trustAs} requires a string value. Read more about {@link ng.$sce Strict Contextual Escaping (SCE)} in AngularJS. angular.js-1.7.9/docs/content/error/$sce/iwcard.ngdoc000066400000000000000000000006231356472325200224440ustar00rootroot00000000000000@ngdoc error @name $sce:iwcard @fullName The sequence *** is not a valid pattern wildcard @description The strings in {@link $sceDelegateProvider#resourceUrlWhitelist $sceDelegateProvider.resourceUrlWhitelist} and {@link $sceDelegateProvider#resourceUrlBlacklist $sceDelegateProvider.resourceUrlBlacklist} may not contain the undefined sequence `***`. Only `*` and `**` wildcard patterns are defined. angular.js-1.7.9/docs/content/error/$sce/unsafe.ngdoc000066400000000000000000000012551356472325200224560ustar00rootroot00000000000000@ngdoc error @name $sce:unsafe @fullName Require a safe/trusted value @description The value provided for use in a specific context was not found to be safe/trusted for use. AngularJS's {@link ng.$sce Strict Contextual Escaping (SCE)} mode (enabled by default), requires bindings in certain contexts to result in a value that is trusted as safe for use in such a context. (e.g. loading an AngularJS template from a URL requires that the URL is one considered safe for loading resources.) This helps prevent XSS and other security issues. Read more at {@link ng.$sce Strict Contextual Escaping (SCE)} You may want to include the ngSanitize module to use the automatic sanitizing. angular.js-1.7.9/docs/content/error/$templateRequest/000077500000000000000000000000001356472325200225705ustar00rootroot00000000000000angular.js-1.7.9/docs/content/error/$templateRequest/tpload.ngdoc000066400000000000000000000014561356472325200250750ustar00rootroot00000000000000@ngdoc error @name $templateRequest:tpload @fullName Error Loading Template @description This error occurs when {@link $templateRequest} attempts to fetch a template from some URL, and the request fails. The template URL might be defined in a directive/component definition, an instance of `ngInclude`, an instance of `ngMessagesInclude` or a templated route in a `$route` route definition. To resolve this error, ensure that the URL of the template is spelled correctly and resolves to correct absolute URL. The [Chrome Developer Tools](https://developers.google.com/chrome-developer-tools/docs/network#network_panel_overview) might also be helpful in determining why the request failed. If you are using {@link ng.$templateCache} to pre-load templates, ensure that the cache was populated with the template. angular.js-1.7.9/docs/content/error/$timeout/000077500000000000000000000000001356472325200210725ustar00rootroot00000000000000angular.js-1.7.9/docs/content/error/$timeout/badprom.ngdoc000066400000000000000000000016121356472325200235320ustar00rootroot00000000000000@ngdoc error @name $timeout:badprom @fullName Non-$timeout promise @description This error occurs when calling {@link ng.$timeout#cancel $timeout.cancel()} with a promise that was not generated by the {@link ng.$timeout $timeout} service. This can, for example, happen when calling {@link ng.$q#the-promise-api then()/catch()} on the returned promise, which creates a new promise, and pass that new promise to {@link ng.$timeout#cancel $timeout.cancel()}. Example of incorrect usage that leads to this error: ```js var promise = $timeout(doSomething, 1000).then(doSomethingElse); $timeout.cancel(promise); ``` To fix the example above, keep a reference to the promise returned by {@link ng.$timeout $timeout()} and pass that to {@link ng.$timeout#cancel $timeout.cancel()}: ```js var promise = $timeout(doSomething, 1000); var newPromise = promise.then(doSomethingElse); $timeout.cancel(promise); ``` angular.js-1.7.9/docs/content/error/filter/000077500000000000000000000000001356472325200206255ustar00rootroot00000000000000angular.js-1.7.9/docs/content/error/filter/notarray.ngdoc000066400000000000000000000026401356472325200235020ustar00rootroot00000000000000@ngdoc error @name filter:notarray @fullName Not an array @description This error occurs when {@link ng.filter filter} is not used with an array: ```html
{{ key }} : {{ value }}
``` Filter must be used with an array so a subset of items can be returned. The array can be initialized asynchronously and therefore null or undefined won't throw this error. To filter an object by the value of its properties you can create your own custom filter: ```js angular.module('customFilter',[]) .filter('custom', function() { return function(input, search) { if (!input) return input; if (!search) return input; var expected = ('' + search).toLowerCase(); var result = {}; angular.forEach(input, function(value, key) { var actual = ('' + value).toLowerCase(); if (actual.indexOf(expected) !== -1) { result[key] = value; } }); return result; } }); ``` That can be used as: ```html
{{ key }} : {{ value }}
``` You could as well convert the object to an array using a filter such as [toArrayFilter](https://github.com/petebacondarwin/angular-toArrayFilter): ```html
{{ item }}
``` angular.js-1.7.9/docs/content/error/index.ngdoc000066400000000000000000000007651356472325200214730ustar00rootroot00000000000000@ngdoc overview @name Error Reference @description # Error Reference Use the Error Reference manual to find information about error conditions in your AngularJS app. Errors thrown in production builds of AngularJS will log links to this site on the console. Other useful references for debugging your app include: - {@link api/ API Reference} for detailed information about specific features - {@link guide/ Developer Guide} for AngularJS concepts - {@link tutorial/ Tutorial} for getting started angular.js-1.7.9/docs/content/error/jqLite/000077500000000000000000000000001356472325200205705ustar00rootroot00000000000000angular.js-1.7.9/docs/content/error/jqLite/nosel.ngdoc000066400000000000000000000011341356472325200227230ustar00rootroot00000000000000@ngdoc error @name jqLite:nosel @fullName Unsupported Selector Lookup @description In order to keep AngularJS small, AngularJS implements only a subset of the selectors in {@link angular.element#angularjs-s-jqlite jqLite}. This error occurs when a jqLite instance is invoked with a selector other than this subset. In order to resolve this error, rewrite your code to only use tag name selectors and manually traverse the DOM using the APIs provided by jqLite. Alternatively, you can include a full version of jQuery, which AngularJS will automatically use and that will make all selectors available. angular.js-1.7.9/docs/content/error/jqLite/offargs.ngdoc000066400000000000000000000003601356472325200232320ustar00rootroot00000000000000@ngdoc error @name jqLite:offargs @fullName Invalid jqLite#off() parameter @description This error occurs when trying to pass too many arguments to `jqLite#off`. Note that `jqLite#off` does not support namespaces or selectors like jQuery. angular.js-1.7.9/docs/content/error/jqLite/onargs.ngdoc000066400000000000000000000004011356472325200230700ustar00rootroot00000000000000@ngdoc error @name jqLite:onargs @fullName Invalid jqLite#on() Parameters @description This error occurs when trying to pass too many arguments to `jqLite#on`. Note that `jqLite#on` does not support the `selector` or `eventData` parameters as jQuery does. angular.js-1.7.9/docs/content/error/linky/000077500000000000000000000000001356472325200204665ustar00rootroot00000000000000angular.js-1.7.9/docs/content/error/linky/notstring.ngdoc000066400000000000000000000011661356472325200235350ustar00rootroot00000000000000@ngdoc error @name linky:notstring @fullName Not a string @description This error occurs when {@link ngSanitize.linky linky} is used with a non-empty, non-string value: ```html
``` `linky` is supposed to be used with string values only, and therefore assumes that several methods (such as `.match()`) are available on the passed in value. The value can be initialized asynchronously and therefore null or undefined won't throw this error. If you want to pass non-string values to `linky` (e.g. Objects whose `.toString()` should be utilized), you need to manually convert them to strings. angular.js-1.7.9/docs/content/error/ng/000077500000000000000000000000001356472325200177445ustar00rootroot00000000000000angular.js-1.7.9/docs/content/error/ng/aobj.ngdoc000066400000000000000000000002721356472325200216740ustar00rootroot00000000000000@ngdoc error @name ng:aobj @fullName Invalid Argument @description The argument passed should be an object. Check the value that was passed to the function where this error was thrown. angular.js-1.7.9/docs/content/error/ng/areq.ngdoc000066400000000000000000000007511356472325200217130ustar00rootroot00000000000000@ngdoc error @name ng:areq @fullName Bad Argument @description AngularJS often asserts that certain values will be present and truthy using a helper function. If the assertion fails, this error is thrown. To fix this problem, make sure that the value the assertion expects is defined and matches the type mentioned in the error. If the type is `undefined`, make sure any newly added controllers/directives/services are properly defined and included in the script(s) loaded by your page. angular.js-1.7.9/docs/content/error/ng/badname.ngdoc000066400000000000000000000005011356472325200223430ustar00rootroot00000000000000@ngdoc error @name ng:badname @fullName Bad `hasOwnProperty` Name @description Occurs when you try to use the name `hasOwnProperty` in a context where it is not allowed. Generally, a name cannot be `hasOwnProperty` because it is used, internally, on a object and allowing such a name would break lookups on this object. angular.js-1.7.9/docs/content/error/ng/btstrpd.ngdoc000066400000000000000000000016321356472325200224440ustar00rootroot00000000000000@ngdoc error @name ng:btstrpd @fullName App Already Bootstrapped with this Element @description Occurs when calling {@link angular.bootstrap} on an element that has already been bootstrapped. This usually happens when you accidentally use both `ng-app` and `angular.bootstrap` to bootstrap an application. ``` ... ``` Note that for bootstrapping purposes, the `` element is the same as `document`, so the following will also throw an error. ``` ... ``` You can also get this error if you accidentally load AngularJS itself more than once. ``` ... ... ``` angular.js-1.7.9/docs/content/error/ng/cpi.ngdoc000066400000000000000000000006421356472325200215350ustar00rootroot00000000000000@ngdoc error @name ng:cpi @fullName Bad Copy @description This error occurs when attempting to copy an object to itself. Calling {@link angular.copy angular.copy} with a `destination` object deletes all of the elements or properties on `destination` before copying to it. Copying an object to itself is not supported. Make sure to check your calls to `angular.copy` and avoid copying objects or arrays to themselves. angular.js-1.7.9/docs/content/error/ng/cpta.ngdoc000066400000000000000000000003051356472325200217050ustar00rootroot00000000000000@ngdoc error @name ng:cpta @fullName Copying TypedArray @description Copying TypedArray's with a destination is not supported because TypedArray objects can not be mutated, they are fixed length. angular.js-1.7.9/docs/content/error/ng/cpws.ngdoc000066400000000000000000000006471356472325200217430ustar00rootroot00000000000000@ngdoc error @name ng:cpws @fullName Copying Window or Scope @description Copying Window or Scope instances is not supported because of cyclical and self references. Avoid copying windows and scopes, as well as any other cyclical or self-referential structures. Note that trying to deep copy an object containing cyclical references that is neither a window nor a scope will cause infinite recursion and a stack overflow. angular.js-1.7.9/docs/content/error/ng/test.ngdoc000066400000000000000000000005561356472325200217450ustar00rootroot00000000000000@ngdoc error @name ng:test @fullName Testability Not Found @description AngularJS's testability helper, getTestability, requires a root element to be passed in. This helps differentiate between different AngularJS apps on the same page. This error is thrown when no injector is found for root element. It is often because the root element is outside of the ng-app. angular.js-1.7.9/docs/content/error/ngModel/000077500000000000000000000000001356472325200207255ustar00rootroot00000000000000angular.js-1.7.9/docs/content/error/ngModel/constexpr.ngdoc000066400000000000000000000011201356472325200237600ustar00rootroot00000000000000@ngdoc error @name ngModel:constexpr @fullName Non-Constant Expression @description Some attributes used in conjunction with ngModel (such as ngTrueValue or ngFalseValue) will only accept constant expressions. Examples using constant expressions include: ``` ``` Examples of non-constant expressions include: ``` ``` angular.js-1.7.9/docs/content/error/ngModel/datefmt.ngdoc000066400000000000000000000006631356472325200233720ustar00rootroot00000000000000@ngdoc error @name ngModel:datefmt @fullName Model is not a date object @description All date-related inputs like `` require the model to be a `Date` object. If the model is something else, this error will be thrown. AngularJS does not set validation errors on the `` in this case as those errors are shown to the user, but the erroneous state was caused by incorrect application logic and not by the user. angular.js-1.7.9/docs/content/error/ngModel/nonassign.ngdoc000066400000000000000000000013031356472325200237350ustar00rootroot00000000000000@ngdoc error @name ngModel:nonassign @fullName Non-Assignable Expression @description This error occurs when expression the {@link ng.directive:ngModel ngModel} directive is bound to is a non-assignable expression. Examples using assignable expressions include: ``` ``` Examples of non-assignable expressions include: ``` ``` Always make sure that the expression bound via `ngModel` directive can be assigned to. For more information, see the {@link ng.directive:ngModel ngModel API doc}. angular.js-1.7.9/docs/content/error/ngModel/nopromise.ngdoc000066400000000000000000000014611356472325200237560ustar00rootroot00000000000000@ngdoc error @name ngModel:nopromise @fullName No promise @description The return value of an async validator, must always be a promise. If you want to return a non-promise value, you can convert it to a promise using {@link ng.$q#resolve `$q.resolve()`} or {@link ng.$q#reject `$q.reject()`}. Example: ``` .directive('asyncValidator', function($q) { return { require: 'ngModel', link: function(scope, elem, attrs, ngModel) { ngModel.$asyncValidators.myAsyncValidation = function(modelValue, viewValue) { if (/* I don't need to hit the backend API */) { return $q.resolve(); // to mark as valid or // return $q.reject(); // to mark as invalid } else { // ...send a request to the backend and return a promise } }; } }; }) ``` angular.js-1.7.9/docs/content/error/ngModel/numfmt.ngdoc000066400000000000000000000031031356472325200232440ustar00rootroot00000000000000@ngdoc error @name ngModel:numfmt @fullName Model is not of type `number` @description The `input[number]` and `input[range]` directives require the model to be a `number`. If the model is something else, this error will be thrown. AngularJS does not set validation errors on the `` in this case as this error is caused by incorrect application logic and not by bad input from the user. If your model does not contain actual numbers then it is up to the application developer to use a directive that will do the conversion in the `ngModel` `$formatters` and `$parsers` pipeline. ## Example In this example, our model stores the number as a string, so we provide the `stringToNumber` directive to convert it into the format the `input[number]` directive expects.
{{ x }} : {{ typeOf(x) }}
angular.module('numfmt-error-module', []) .run(function($rootScope) { $rootScope.typeOf = function(value) { return typeof value; }; }) .directive('stringToNumber', function() { return { require: 'ngModel', link: function(scope, element, attrs, ngModel) { ngModel.$parsers.push(function(value) { return '' + value; }); ngModel.$formatters.push(function(value) { return parseFloat(value); }); } }; });
angular.js-1.7.9/docs/content/error/ngOptions/000077500000000000000000000000001356472325200213205ustar00rootroot00000000000000angular.js-1.7.9/docs/content/error/ngOptions/iexp.ngdoc000066400000000000000000000006141356472325200233020ustar00rootroot00000000000000@ngdoc error @name ngOptions:iexp @fullName Invalid Expression @description This error occurs when 'ngOptions' is passed an expression that isn't in an expected form. Here's an example of correct syntax: ```
[{{ item.id }}] {{ item.name }}
``` You could as well convert the object to an array using a filter such as [toArrayFilter](https://github.com/petebacondarwin/angular-toArrayFilter): ```html
[{{ item.id }}] {{ item.name }}
``` angular.js-1.7.9/docs/content/guide/000077500000000000000000000000001356472325200173045ustar00rootroot00000000000000angular.js-1.7.9/docs/content/guide/$location.ngdoc000066400000000000000000000733331356472325200222050ustar00rootroot00000000000000@ngdoc overview @name Using $location @sortOrder 500 @description # Using the `$location` service The `$location` service parses the URL in the browser address bar (based on [`window.location`](https://developer.mozilla.org/en/window.location)) and makes the URL available to your application. Changes to the URL in the address bar are reflected into the `$location` service and changes to `$location` are reflected into the browser address bar. **The $location service:** - Exposes the current URL in the browser address bar, so you can - Watch and observe the URL. - Change the URL. - Maintains synchronization between itself and the browser's URL when the user - Changes the address in the browser's address bar. - Clicks the back or forward button in the browser (or clicks a History link). - Clicks on a link in the page. - Represents the URL object as a set of methods (protocol, host, port, path, search, hash). ## Comparing `$location` to `window.location`
window.location $location service
purpose allow read/write access to the current browser location same
API exposes "raw" object with properties that can be directly modified exposes jQuery-style getters and setters
integration with AngularJS application life-cycle none knows about all internal life-cycle phases, integrates with {@link ng.$rootScope.Scope#$watch $watch}, ...
seamless integration with HTML5 API no yes (with a fallback for legacy browsers)
aware of docroot/context from which the application is loaded no - window.location.pathname returns "/docroot/actual/path" yes - $location.path() returns "/actual/path"
## When should I use `$location`? Any time your application needs to react to a change in the current URL or if you want to change the current URL in the browser. ## What does it not do? It does not cause a full page reload when the browser URL is changed. To reload the page after changing the URL, use the lower-level API, `$window.location.href`. ## General overview of the API The `$location` service can behave differently, depending on the configuration that was provided to it when it was instantiated. The default configuration is suitable for many applications, for others customizing the configuration can enable new features. Once the `$location` service is instantiated, you can interact with it via jQuery-style getter and setter methods that allow you to get or change the current URL in the browser. ### `$location` service configuration To configure the `$location` service, retrieve the {@link ng.$locationProvider $locationProvider} and set the parameters as follows: - **html5Mode(mode)**: `{boolean|Object}`
`false` or `{enabled: false}` (default) - see [Hashbang mode](guide/$location#hashbang-mode-default-mode-)
`true` or `{enabled: true}` - see [HTML5 mode](guide/$location#html5-mode)
`{..., requireBase: true/false}` (only affects HTML5 mode) - see [Relative links](guide/$location#relative-links)
`{..., rewriteLinks: true/false/'string'}` (only affects HTML5 mode) - see [HTML link rewriting](guide/$location#html-link-rewriting)
Default: ```j { enabled: false, requireBase: true, rewriteLinks: true } ``` - **hashPrefix(prefix)**: `{string}`
Prefix used for Hashbang URLs (used in Hashbang mode or in legacy browsers in HTML5 mode).
Default: `'!'` #### Example configuration ```js $locationProvider.html5Mode(true).hashPrefix('*'); ``` ### Getter and setter methods `$location` service provides getter methods for read-only parts of the URL (absUrl, protocol, host, port) and getter / setter methods for url, path, search, hash: ```js // get the current path $location.path(); // change the path $location.path('/newValue') ``` All of the setter methods return the same `$location` object to allow chaining. For example, to change multiple segments in one go, chain setters like this: ```js $location.path('/newValue').search({key: value}); ``` ### Replace method There is a special `replace` method which can be used to tell the $location service that the next time the $location service is synced with the browser, the last history record should be replaced instead of creating a new one. This is useful when you want to implement redirection, which would otherwise break the back button (navigating back would retrigger the redirection). To change the current URL without creating a new browser history record you can call: ```js $location.path('/someNewPath'); $location.replace(); // or you can chain these as: $location.path('/someNewPath').replace(); ``` Note that the setters don't update `window.location` immediately. Instead, the `$location` service is aware of the {@link ng.$rootScope.Scope scope} life-cycle and coalesces multiple `$location` mutations into one "commit" to the `window.location` object during the scope `$digest` phase. Since multiple changes to the $location's state will be pushed to the browser as a single change, it's enough to call the `replace()` method just once to make the entire "commit" a replace operation rather than an addition to the browser history. Once the browser is updated, the $location service resets the flag set by `replace()` method and future mutations will create new history records, unless `replace()` is called again. ### Setters and character encoding You can pass special characters to `$location` service and it will encode them according to rules specified in [RFC 3986](http://www.ietf.org/rfc/rfc3986.txt). When you access the methods: - All values that are passed to `$location` setter methods, `path()`, `search()`, `hash()`, are encoded. - Getters (calls to methods without parameters) return decoded values for the following methods `path()`, `search()`, `hash()`. - When you call the `absUrl()` method, the returned value is a full url with its segments encoded. - When you call the `url()` method, the returned value is path, search and hash, in the form `/path?search=a&b=c#hash`. The segments are encoded as well. ## Hashbang and HTML5 Modes `$location` service has two configuration modes which control the format of the URL in the browser address bar: **Hashbang mode** (the default) and the **HTML5 mode** which is based on using the [HTML5 History API](https://html.spec.whatwg.org/multipage/browsers.html#the-history-interface). Applications use the same API in both modes and the `$location` service will work with appropriate URL segments and browser APIs to facilitate the browser URL change and history management.
Hashbang mode HTML5 mode
configuration the default { html5Mode: true }
URL format hashbang URLs in all browsers regular URLs in modern browser, hashbang URLs in old browser
<a href=""> link rewriting no yes
requires server-side configuration no yes
### Hashbang mode (default mode) In this mode, `$location` uses Hashbang URLs in all browsers. AngularJS also does not intercept and rewrite links in this mode. I.e. links work as expected and also perform full page reloads when other parts of the url than the hash fragment was changed. #### Example ```js it('should show example', function() { module(function($locationProvider) { $locationProvider.html5Mode(false); $locationProvider.hashPrefix('!'); }); inject(function($location) { // open http://example.com/base/index.html#!/a expect($location.absUrl()).toBe('http://example.com/base/index.html#!/a'); expect($location.path()).toBe('/a'); $location.path('/foo'); expect($location.absUrl()).toBe('http://example.com/base/index.html#!/foo'); expect($location.search()).toEqual({}); $location.search({a: 'b', c: true}); expect($location.absUrl()).toBe('http://example.com/base/index.html#!/foo?a=b&c'); $location.path('/new').search('x=y'); expect($location.absUrl()).toBe('http://example.com/base/index.html#!/new?x=y'); }); }); ``` ### HTML5 mode In HTML5 mode, the `$location` service getters and setters interact with the browser URL address through the HTML5 history API. This allows for use of regular URL path and search segments, instead of their hashbang equivalents. If the HTML5 History API is not supported by a browser, the `$location` service will fall back to using the hashbang URLs automatically. This frees you from having to worry about whether the browser displaying your app supports the history API or not; the `$location` service transparently uses the best available option. - Opening a regular URL in a legacy browser -> redirects to a hashbang URL - Opening hashbang URL in a modern browser -> rewrites to a regular URL Note that in this mode, AngularJS intercepts all links (subject to the "Html link rewriting" rules below) and updates the url in a way that never performs a full page reload. #### Example ```js it('should show example', function() { module(function($locationProvider) { $locationProvider.html5Mode(true); $locationProvider.hashPrefix('!'); }); inject(function($location) { // in browser with HTML5 history support: // open http://example.com/#!/a -> rewrite to http://example.com/a // (replacing the http://example.com/#!/a history record) expect($location.path()).toBe('/a'); $location.path('/foo'); expect($location.absUrl()).toBe('http://example.com/foo'); expect($location.search()).toEqual({}); $location.search({a: 'b', c: true}); expect($location.absUrl()).toBe('http://example.com/foo?a=b&c'); $location.path('/new').search('x=y'); expect($location.url()).toBe('/new?x=y'); expect($location.absUrl()).toBe('http://example.com/new?x=y'); }); }); it('should show example (when browser doesn\'t support HTML5 mode', function() { module(function($provide, $locationProvider) { $locationProvider.html5Mode(true); $locationProvider.hashPrefix('!'); $provide.value('$sniffer', {history: false}); }); inject(initBrowser({ url: 'http://example.com/new?x=y', basePath: '/' }), function($location) { // in browser without html5 history support: // open http://example.com/new?x=y -> redirect to http://example.com/#!/new?x=y // (again replacing the http://example.com/new?x=y history item) expect($location.path()).toBe('/new'); expect($location.search()).toEqual({x: 'y'}); $location.path('/foo/bar'); expect($location.path()).toBe('/foo/bar'); expect($location.url()).toBe('/foo/bar?x=y'); expect($location.absUrl()).toBe('http://example.com/#!/foo/bar?x=y'); }); }); ``` #### Fallback for legacy browsers For browsers that support the HTML5 history API, `$location` uses the HTML5 history API to write path and search. If the history API is not supported by a browser, `$location` supplies a Hashbang URL. This frees you from having to worry about whether the browser viewing your app supports the history API or not; the `$location` service makes this transparent to you. #### HTML link rewriting When you use HTML5 history API mode, you will not need special hashbang links. All you have to do is specify regular URL links, such as: `link` When a user clicks on this link, - In a legacy browser, the URL changes to `/index.html#!/some?foo=bar` - In a modern browser, the URL changes to `/some?foo=bar` In cases like the following, links are not rewritten; instead, the browser will perform a full page reload to the original link. - Links that contain `target` element
Example: `link` - Absolute links that go to a different domain
Example: `link` - Links starting with '/' that lead to a different base path
Example: `link` If `mode.rewriteLinks` is set to `false` in the `mode` configuration object passed to `$locationProvider.html5Mode()`, the browser will perform a full page reload for every link. `mode.rewriteLinks` can also be set to a string, which will enable link rewriting only on anchor elements that have the given attribute. For example, if `mode.rewriteLinks` is set to `'internal-link'`: - `link` will be rewritten - `link` will perform a full page reload Note that [attribute name normalization](guide/directive#normalization) does not apply here, so `'internalLink'` will **not** match `'internal-link'`. #### Relative links Be sure to check all relative links, images, scripts etc. AngularJS requires you to specify the url base in the head of your main html file (``) unless `html5Mode.requireBase` is set to `false` in the html5Mode definition object passed to `$locationProvider.html5Mode()`. With that, relative urls will always be resolved to this base url, even if the initial url of the document was different. There is one exception: Links that only contain a hash fragment (e.g. ``) will only change `$location.hash()` and not modify the url otherwise. This is useful for scrolling to anchors on the same page without needing to know on which page the user currently is. #### Server side Using this mode requires URL rewriting on server side, basically you have to rewrite all your links to entry point of your application (e.g. index.html). Requiring a `` tag is also important for this case, as it allows AngularJS to differentiate between the part of the url that is the application base and the path that should be handled by the application. #### Base href constraints The `$location` service is not able to function properly if the current URL is outside the URL given as the base href. This can have subtle confusing consequences... Consider a base href set as follows: `` (i.e. the application exists in the "folder" called `/base`). The URL `/base` is actually outside the application (it refers to the `base` file found in the root `/` folder). If you wish to be able to navigate to the application via a URL such as `/base` then you should ensure that your server is setup to redirect such requests to `/base/`. See https://github.com/angular/angular.js/issues/14018 for more information. ### Sending links among different browsers Because of rewriting capability in HTML5 mode, your users will be able to open regular url links in legacy browsers and hashbang links in modern browser: - Modern browser will rewrite hashbang URLs to regular URLs. - Older browsers will redirect regular URLs to hashbang URLs. #### Example Here you can see two `$location` instances that show the difference between **Html5 mode** and **Html5 Fallback mode**. Note that to simulate different levels of browser support, the `$location` instances are connected to a fakeBrowser service, which you don't have to set up in actual projects. Note that when you type hashbang url into the first browser (or vice versa) it doesn't rewrite / redirect to regular / hashbang url, as this conversion happens only during parsing the initial URL = on page reload. In these examples we use ``. The inputs represent the address bar of the browser. ##### Browser in HTML5 mode angular.module('html5-mode', ['fake-browser', 'address-bar']) // Configure the fakeBrowser. Do not set these values in actual projects. .constant('initUrl', 'http://www.example.com/base/path?a=b#h') .constant('baseHref', '/base/index.html') .value('$sniffer', { history: true }) .controller('LocationController', function($scope, $location) { $scope.$location = {}; angular.forEach('protocol host port path search hash'.split(' '), function(method) { $scope.$location[method] = function() { var result = $location[method](); return angular.isObject(result) ? angular.toJson(result) : result; }; }); }) .config(function($locationProvider) { $locationProvider.html5Mode(true).hashPrefix('!'); }) .run(function($rootElement) { $rootElement.on('click', function(e) { e.stopPropagation(); }); }); angular.module('fake-browser', []) .config(function($provide) { $provide.decorator('$browser', function($delegate, baseHref, initUrl) { $delegate.onUrlChange = function(fn) { this.urlChange = fn; }; $delegate.url = function() { return initUrl; }; $delegate.defer = function(fn, delay) { setTimeout(function() { fn(); }, delay || 0); }; $delegate.baseHref = function() { return baseHref; }; return $delegate; }); }); angular.module('address-bar', []) .directive('ngAddressBar', function($browser, $timeout) { return { template: 'Address: ', link: function(scope, element, attrs) { var input = element.children('input'), delay; input.on('keypress keyup keydown', function(event) { delay = (!delay ? $timeout(fireUrlChange, 250) : null); event.stopPropagation(); }) .val($browser.url()); $browser.url = function(url) { return url ? input.val(url) : input.val(); }; function fireUrlChange() { delay = null; $browser.urlChange(input.val()); } } }; }); var addressBar = element(by.css("#addressBar")), url = 'http://www.example.com/base/path?a=b#h'; it("should show fake browser info on load", function() { expect(addressBar.getAttribute('value')).toBe(url); expect(element(by.binding('$location.protocol()')).getText()).toBe('http'); expect(element(by.binding('$location.host()')).getText()).toBe('www.example.com'); expect(element(by.binding('$location.port()')).getText()).toBe('80'); expect(element(by.binding('$location.path()')).getText()).toBe('/path'); expect(element(by.binding('$location.search()')).getText()).toBe('{"a":"b"}'); expect(element(by.binding('$location.hash()')).getText()).toBe('h'); }); it("should change $location accordingly", function() { var navigation = element.all(by.css("#navigation a")); navigation.get(0).click(); expect(addressBar.getAttribute('value')).toBe("http://www.example.com/base/first?a=b"); expect(element(by.binding('$location.protocol()')).getText()).toBe('http'); expect(element(by.binding('$location.host()')).getText()).toBe('www.example.com'); expect(element(by.binding('$location.port()')).getText()).toBe('80'); expect(element(by.binding('$location.path()')).getText()).toBe('/first'); expect(element(by.binding('$location.search()')).getText()).toBe('{"a":"b"}'); expect(element(by.binding('$location.hash()')).getText()).toBe(''); navigation.get(1).click(); expect(addressBar.getAttribute('value')).toBe("http://www.example.com/base/sec/ond?flag#hash"); expect(element(by.binding('$location.protocol()')).getText()).toBe('http'); expect(element(by.binding('$location.host()')).getText()).toBe('www.example.com'); expect(element(by.binding('$location.port()')).getText()).toBe('80'); expect(element(by.binding('$location.path()')).getText()).toBe('/sec/ond'); expect(element(by.binding('$location.search()')).getText()).toBe('{"flag":true}'); expect(element(by.binding('$location.hash()')).getText()).toBe('hash'); }); ##### Browser in HTML5 Fallback mode (Hashbang mode)


$location.protocol() =
$location.host() =
$location.port() =
$location.path() =
$location.search() =
$location.hash() =
angular.module('hashbang-mode', ['fake-browser', 'address-bar']) // Configure the fakeBrowser. Do not set these values in actual projects. .constant('initUrl', 'http://www.example.com/base/index.html#!/path?a=b#h') .constant('baseHref', '/base/index.html') .value('$sniffer', { history: false }) .config(function($locationProvider) { $locationProvider.html5Mode(true).hashPrefix('!'); }) .controller('LocationController', function($scope, $location) { $scope.$location = {}; angular.forEach('protocol host port path search hash'.split(' '), function(method) { $scope.$location[method] = function() { var result = $location[method](); return angular.isObject(result) ? angular.toJson(result) : result; }; }); }) .run(function($rootElement) { $rootElement.on('click', function(e) { e.stopPropagation(); }); }); angular.module('fake-browser', []) .config(function($provide) { $provide.decorator('$browser', function($delegate, baseHref, initUrl) { $delegate.onUrlChange = function(fn) { this.urlChange = fn; }; $delegate.url = function() { return initUrl; }; $delegate.defer = function(fn, delay) { setTimeout(function() { fn(); }, delay || 0); }; $delegate.baseHref = function() { return baseHref; }; return $delegate; }); }); angular.module('address-bar', []) .directive('ngAddressBar', function($browser, $timeout) { return { template: 'Address: ', link: function(scope, element, attrs) { var input = element.children('input'), delay; input.on('keypress keyup keydown', function(event) { delay = (!delay ? $timeout(fireUrlChange, 250) : null); event.stopPropagation(); }) .val($browser.url()); $browser.url = function(url) { return url ? input.val(url) : input.val(); }; function fireUrlChange() { delay = null; $browser.urlChange(input.val()); } } }; }); var addressBar = element(by.css("#addressBar")), url = 'http://www.example.com/base/index.html#!/path?a=b#h'; it("should show fake browser info on load", function() { expect(addressBar.getAttribute('value')).toBe(url); expect(element(by.binding('$location.protocol()')).getText()).toBe('http'); expect(element(by.binding('$location.host()')).getText()).toBe('www.example.com'); expect(element(by.binding('$location.port()')).getText()).toBe('80'); expect(element(by.binding('$location.path()')).getText()).toBe('/path'); expect(element(by.binding('$location.search()')).getText()).toBe('{"a":"b"}'); expect(element(by.binding('$location.hash()')).getText()).toBe('h'); }); it("should change $location accordingly", function() { var navigation = element.all(by.css("#navigation a")); navigation.get(0).click(); expect(addressBar.getAttribute('value')).toBe("http://www.example.com/base/index.html#!/first?a=b"); expect(element(by.binding('$location.protocol()')).getText()).toBe('http'); expect(element(by.binding('$location.host()')).getText()).toBe('www.example.com'); expect(element(by.binding('$location.port()')).getText()).toBe('80'); expect(element(by.binding('$location.path()')).getText()).toBe('/first'); expect(element(by.binding('$location.search()')).getText()).toBe('{"a":"b"}'); expect(element(by.binding('$location.hash()')).getText()).toBe(''); navigation.get(1).click(); expect(addressBar.getAttribute('value')).toBe("http://www.example.com/base/index.html#!/sec/ond?flag#hash"); expect(element(by.binding('$location.protocol()')).getText()).toBe('http'); expect(element(by.binding('$location.host()')).getText()).toBe('www.example.com'); expect(element(by.binding('$location.port()')).getText()).toBe('80'); expect(element(by.binding('$location.path()')).getText()).toBe('/sec/ond'); expect(element(by.binding('$location.search()')).getText()).toBe('{"flag":true}'); expect(element(by.binding('$location.hash()')).getText()).toBe('hash'); });
## Caveats ### Page reload navigation The `$location` service allows you to change only the URL; it does not allow you to reload the page. When you need to change the URL and reload the page or navigate to a different page, please use a lower level API, {@link ng.$window $window.location.href}. ### Using $location outside of the scope life-cycle `$location` knows about AngularJS's {@link ng.$rootScope.Scope scope} life-cycle. When a URL changes in the browser it updates the `$location` and calls `$apply` so that all {@link ng.$rootScope.Scope#$watch $watchers} / {@link ng.$compile.directive.Attributes#$observe $observers} are notified. When you change the `$location` inside the `$digest` phase everything is ok; `$location` will propagate this change into browser and will notify all the {@link ng.$rootScope.Scope#$watch $watchers} / {@link ng.$compile.directive.Attributes#$observe $observers}. When you want to change the `$location` from outside AngularJS (for example, through a DOM Event or during testing) - you must call `$apply` to propagate the changes. ### $location.path() and ! or / prefixes A path should always begin with forward slash (`/`); the `$location.path()` setter will add the forward slash if it is missing. Note that the `!` prefix in the hashbang mode is not part of `$location.path()`; it is actually `hashPrefix`. ### Crawling your app Most modern search engines are able to crawl AJAX applications with dynamic content, provided all included resources are available to the crawler bots. There also exists a special [AJAX crawling scheme](http://code.google.com/web/ajaxcrawling/docs/specification.html) developed by Google that allows bots to crawl the static equivalent of a dynamically generated page, but this schema has been deprecated, and support for it may vary by search engine. ## Testing with the $location service When using `$location` service during testing, you are outside of the angular's {@link ng.$rootScope.Scope scope} life-cycle. This means it's your responsibility to call `scope.$apply()`. ```js describe('serviceUnderTest', function() { beforeEach(module(function($provide) { $provide.factory('serviceUnderTest', function($location) { // whatever it does... }); }); it('should...', inject(function($location, $rootScope, serviceUnderTest) { $location.path('/new/path'); $rootScope.$apply(); // test whatever the service should do... })); }); ``` ## Two-way binding to $location Because `$location` uses getters/setters, you can use `ng-model-options="{ getterSetter: true }"` to bind it to `ngModel`:
angular.module('locationExample', []) .controller('LocationController', ['$scope', '$location', function($scope, $location) { $scope.locationPath = function(newLocation) { return $location.path(newLocation); }; }]);
## Related API * {@link ng.$location `$location` API} angular.js-1.7.9/docs/content/guide/accessibility.ngdoc000066400000000000000000000404521356472325200231540ustar00rootroot00000000000000@ngdoc overview @name Accessibility @sortOrder 530 @description # Accessibility with ngAria The goal of ngAria is to improve AngularJS's default accessibility by enabling common [ARIA](http://www.w3.org/TR/wai-aria/) attributes that convey state or semantic information for assistive technologies used by persons with disabilities. ## Including ngAria Using {@link ngAria ngAria} is as simple as requiring the ngAria module in your application. ngAria hooks into standard AngularJS directives and quietly injects accessibility support into your application at runtime. ```js angular.module('myApp', ['ngAria'])... ``` ### Using ngAria Most of what ngAria does is only visible "under the hood". To see the module in action, once you've added it as a dependency, you can test a few things: * Using your favorite element inspector, look for attributes added by ngAria in your own code. * Test using your keyboard to ensure `tabindex` is used correctly. * Fire up a screen reader such as VoiceOver or NVDA to check for ARIA support. [Helpful screen reader tips.](http://webaim.org/articles/screenreader_testing/) ## Supported directives Currently, ngAria interfaces with the following directives: * {@link guide/accessibility#ngmodel ngModel} * {@link guide/accessibility#ngdisabled ngDisabled} * {@link guide/accessibility#ngrequired ngRequired} * {@link guide/accessibility#ngreadonly ngReadonly} * {@link guide/accessibility#ngvaluechecked ngChecked} * {@link guide/accessibility#ngvaluechecked ngValue} * {@link guide/accessibility#ngshow ngShow} * {@link guide/accessibility#nghide ngHide} * {@link guide/accessibility#ngclick ngClick} * {@link guide/accessibility#ngdblclick ngDblClick} * {@link guide/accessibility#ngmessages ngMessages}

ngModel

Much of ngAria's heavy lifting happens in the {@link ng.ngModel ngModel} directive. For elements using ngModel, special attention is paid by ngAria if that element also has a role or type of `checkbox`, `radio`, `range` or `textbox`. For those elements using ngModel, ngAria will dynamically bind and update the following ARIA attributes (if they have not been explicitly specified by the developer): * aria-checked * aria-valuemin * aria-valuemax * aria-valuenow * aria-invalid * aria-required * aria-readonly * aria-disabled ### Example
Custom checkbox

Is checked: {{ !!checked }}
angular. module('ngAria_ngModelExample', ['ngAria']). directive('customCheckbox', customCheckboxDirective). directive('showAttrs', showAttrsDirective); function customCheckboxDirective() { return { restrict: 'E', require: 'ngModel', transclude: true, template: ' ' + '', link: function(scope, elem, attrs, ctrl) { // Overwrite necessary `NgModelController` methods ctrl.$isEmpty = isEmpty; ctrl.$render = render; // Bind to events elem.on('click', function(event) { event.preventDefault(); scope.$apply(toggleCheckbox); }); elem.on('keypress', function(event) { event.preventDefault(); if (event.keyCode === 32 || event.keyCode === 13) { scope.$apply(toggleCheckbox); } }); // Helpers function isEmpty(value) { return !value; } function render() { elem[ctrl.$viewValue ? 'addClass' : 'removeClass']('checked'); } function toggleCheckbox() { ctrl.$setViewValue(!ctrl.$viewValue); ctrl.$render(); } } }; } function showAttrsDirective($timeout) { return function(scope, elem, attrs) { var pre = document.createElement('pre'); elem.after(pre); scope.$watchCollection(function() { return Array.prototype.slice.call(elem[0].attributes).reduce(function(aggr, attr) { if (attr.name !== attrs.$attr.showAttrs) aggr[attr.name] = attr.value; return aggr; }, {}); }, function(newValues) { $timeout(function() { pre.textContent = angular.toJson(newValues, 2); }); }); }; } custom-checkbox { cursor: pointer; display: inline-block; } custom-checkbox .icon:before { content: '\2610'; display: inline-block; font-size: 2em; line-height: 1; speak: none; vertical-align: middle; } custom-checkbox.checked .icon:before { content: '\2611'; } var checkbox = element(by.css('custom-checkbox')); var checkedCheckbox = element(by.css('custom-checkbox.checked')); it('should have the `checked` class only when checked', function() { expect(checkbox.isPresent()).toBe(true); expect(checkedCheckbox.isPresent()).toBe(false); checkbox.click(); expect(checkedCheckbox.isPresent()).toBe(true); checkbox.click(); expect(checkedCheckbox.isPresent()).toBe(false); }); it('should have the `aria-checked` attribute set to the appropriate value', function() { expect(checkedCheckbox.isPresent()).toBe(false); expect(checkbox.getAttribute('aria-checked')).toBe('false'); checkbox.click(); expect(checkedCheckbox.isPresent()).toBe(true); expect(checkbox.getAttribute('aria-checked')).toBe('true'); checkbox.click(); expect(checkedCheckbox.isPresent()).toBe(false); expect(checkbox.getAttribute('aria-checked')).toBe('false'); });
ngAria will also add `tabIndex`, ensuring custom elements with these roles will be reachable from the keyboard. It is still up to **you** as a developer to **ensure custom controls will be accessible**. As a rule, any time you create a widget involving user interaction, be sure to test it with your keyboard and at least one mobile and desktop screen reader.

ngValue and ngChecked

To ease the transition between native inputs and custom controls, ngAria now supports {@link ng.ngValue ngValue} and {@link ng.ngChecked ngChecked}. The original directives were created for native inputs only, so ngAria extends support to custom elements by managing `aria-checked` for accessibility. ### Example ```html ``` Becomes: ```html ```

ngDisabled

The `disabled` attribute is only valid for certain elements such as `button`, `input` and `textarea`. To properly disable custom element directives such as `` or ``, using ngAria with {@link ng.ngDisabled ngDisabled} will also add `aria-disabled`. This tells assistive technologies when a non-native input is disabled, helping custom controls to be more accessible. ### Example ```html ``` Becomes: ```html ```
You can check whether a control is legitimately disabled for a screen reader by visiting [chrome://accessibility](chrome://accessibility) and inspecting [the accessibility tree](http://www.paciellogroup.com/blog/2015/01/the-browser-accessibility-tree/).

ngRequired

The boolean `required` attribute is only valid for native form controls such as `input` and `textarea`. To properly indicate custom element directives such as `` or `` as required, using ngAria with {@link ng.ngRequired ngRequired} will also add `aria-required`. This tells accessibility APIs when a custom control is required. ### Example ```html ``` Becomes: ```html ```

ngReadonly

The boolean `readonly` attribute is only valid for native form controls such as `input` and `textarea`. To properly indicate custom element directives such as `` or `` as required, using ngAria with {@link ng.ngReadonly ngReadonly} will also add `aria-readonly`. This tells accessibility APIs when a custom control is read-only. ### Example ```html ``` Becomes: ```html ```

ngShow

The {@link ng.ngShow ngShow} directive shows or hides the given HTML element based on the expression provided to the `ngShow` attribute. The element is shown or hidden by removing or adding the `.ng-hide` CSS class onto the element. In its default setup, ngAria for `ngShow` is actually redundant. It toggles `aria-hidden` on the directive when it is hidden or shown. However, the default CSS of `display: none !important`, already hides child elements from a screen reader. It becomes more useful when the default CSS is overridden with properties that don’t affect assistive technologies, such as `opacity` or `transform`. By toggling `aria-hidden` dynamically with ngAria, we can ensure content visually hidden with this technique will not be read aloud in a screen reader. One caveat with this combination of CSS and `aria-hidden`: you must also remove links and other interactive child elements from the tab order using `tabIndex=“-1”` on each control. This ensures screen reader users won't accidentally focus on "mystery elements". Managing tab index on every child control can be complex and affect performance, so it’s best to just stick with the default `display: none` CSS. See the [fourth rule of ARIA use](http://www.w3.org/TR/aria-in-html/#fourth-rule-of-aria-use). ### Example ```css .ng-hide { display: block; opacity: 0; } ``` ```html ``` Becomes: ```html
``` *Note: Child links, buttons or other interactive controls must also be removed from the tab order.*

ngHide

The {@link ng.ngHide ngHide} directive shows or hides the given HTML element based on the expression provided to the `ngHide` attribute. The element is shown or hidden by removing or adding the `.ng-hide` CSS class onto the element. The default CSS for `ngHide`, the inverse method to `ngShow`, makes ngAria redundant. It toggles `aria-hidden` on the directive when it is hidden or shown, but the content is already hidden with `display: none`. See explanation for {@link guide/accessibility#ngshow ngShow} when overriding the default CSS.

ngClick and ngDblclick

If `ng-click` or `ng-dblclick` is encountered, ngAria will add `tabindex="0"` to any element not in a node blacklist: * Button * Anchor * Input * Textarea * Select * Details/Summary To fix widespread accessibility problems with `ng-click` on `div` elements, ngAria will dynamically bind a keypress event by default as long as the element isn't in the node blacklist. You can turn this functionality on or off with the `bindKeypress` configuration option. ngAria will also add the `button` role to communicate to users of assistive technologies. This can be disabled with the `bindRoleForClick` configuration option. For `ng-dblclick`, you must still manually add `ng-keypress` and a role to non-interactive elements such as `div` or `taco-button` to enable keyboard access.

Example

```html
``` Becomes: ```html
```

ngMessages

The ngMessages module makes it easy to display form validation or other messages with priority sequencing and animation. To expose these visual messages to screen readers, ngAria injects `aria-live="assertive"`, causing them to be read aloud any time a message is shown, regardless of the user's focus location. ### Example ```html
You did not enter a field
Your field is too long
``` Becomes: ```html
You did not enter a field
Your field is too long
``` ## Disabling attributes The attribute magic of ngAria may not work for every scenario. To disable individual attributes, you can use the {@link ngAria.$ariaProvider#config config} method. Just keep in mind this will tell ngAria to ignore the attribute globally.
<div> with ng-click and bindRoleForClick, tabindex set to false
## Common Accessibility Patterns Accessibility best practices that apply to web apps in general also apply to AngularJS. * **Text alternatives**: Add alternate text content to make visual information accessible using [these W3C guidelines](http://www.w3.org/TR/html-alt-techniques/). The appropriate technique depends on the specific markup but can be accomplished using offscreen spans, `aria-label` or label elements, image `alt` attributes, `figure`/`figcaption` elements and more. * **HTML Semantics**: If you're creating custom element directives, Web Components or HTML in general, use native elements wherever possible to utilize built-in events and properties. Alternatively, use ARIA to communicate semantic meaning. See [notes on ARIA use](http://www.w3.org/TR/aria-in-html/#notes-on-aria-use-in-html). * **Focus management**: Guide the user around the app as views are appended/removed. Focus should *never* be lost, as this causes unexpected behavior and much confusion (referred to as "freak-out mode"). * **Announcing changes**: When filtering or other UI messaging happens away from the user's focus, notify with [ARIA Live Regions](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Live_Regions). * **Color contrast and scale**: Make sure content is legible and interactive controls are usable at all screen sizes. Consider configurable UI themes for people with color blindness, low vision or other visual impairments. * **Progressive enhancement**: Some users do not browse with JavaScript enabled or do not have the latest browser. An accessible message about site requirements can inform users and improve the experience. ## Additional Resources * [Using ARIA in HTML](http://www.w3.org/TR/aria-in-html/) * [AngularJS Accessibility at ngEurope](https://www.youtube.com/watch?v=dmYDggEgU-s&list=UUEGUP3TJJfMsEM_1y8iviSQ) * [Testing with Screen Readers](http://webaim.org/articles/screenreader_testing/) * [Chrome Accessibility Developer Tools](https://chrome.google.com/webstore/detail/accessibility-developer-t/fpkknkljclfencbdbgkenhalefipecmb?hl=en) * [W3C Accessibility Testing](http://www.w3.org/wiki/Accessibility_testing) * [WebAIM](http://webaim.org) * [A11y Project](http://a11yproject.com) angular.js-1.7.9/docs/content/guide/animations.ngdoc000066400000000000000000000512511356472325200224660ustar00rootroot00000000000000@ngdoc overview @name Animations @sortOrder 310 @description # Animations AngularJS provides animation hooks for common directives such as {@link ng.directive:ngRepeat ngRepeat}, {@link ng.directive:ngSwitch ngSwitch}, and {@link ngRoute.directive:ngView ngView}, as well as custom directives via the `$animate` service. These animation hooks are set in place to trigger animations during the life cycle of various directives and when triggered, will attempt to perform a CSS Transition, CSS Keyframe Animation or a JavaScript callback Animation (depending on whether an animation is placed on the given directive). Animations can be placed using vanilla CSS by following the naming conventions set in place by AngularJS or with JavaScript code, defined as a factory.
Note that we have used non-prefixed CSS transition properties in our examples as the major browsers now support non-prefixed properties. If you intend to support older browsers or certain mobile browsers then you will need to include prefixed versions of the transition properties. Take a look at http://caniuse.com/#feat=css-transitions for what browsers require prefixes, and https://github.com/postcss/autoprefixer for a tool that can automatically generate the prefixes for you.
Animations are not available unless you include the {@link ngAnimate `ngAnimate` module} as a dependency of your application. Below is a quick example of animations being enabled for `ngShow` and `ngHide`:
Content...
.content-area { border: 1px solid black; margin-top: 10px; padding: 10px; } .sample-show-hide { transition: all linear 0.5s; } .sample-show-hide.ng-hide { opacity: 0; }
## Installation See the {@link ngAnimate API docs for `ngAnimate`} for instructions on installing the module. You may also want to setup a separate CSS file for defining CSS-based animations. ## How they work Animations in AngularJS are completely based on CSS classes. As long as you have a CSS class attached to an HTML element within your application, you can apply animations to it. Let's say for example that we have an HTML template with a repeater like so: ```html
{{ item.id }}
``` As you can see, the `repeated-item` class is present on the element that will be repeated and this class will be used as a reference within our application's CSS and/or JavaScript animation code to tell AngularJS to perform an animation. As `ngRepeat` does its thing, each time a new item is added into the list, `ngRepeat` will add an `ng-enter` class to the element that is being added. When removed it will apply an `ng-leave` class and when moved around it will apply an `ng-move` class. Taking a look at the following CSS code, we can see some transition and keyframe animation code set up for each of those events that occur when `ngRepeat` triggers them: ```css /* We are using CSS transitions for when the enter and move events are triggered for the element that has the `repeated-item` class */ .repeated-item.ng-enter, .repeated-item.ng-move { transition: all 0.5s linear; opacity: 0; } /* `.ng-enter-active` and `.ng-move-active` are where the transition destination properties are set so that the animation knows what to animate */ .repeated-item.ng-enter.ng-enter-active, .repeated-item.ng-move.ng-move-active { opacity: 1; } /* We are using CSS keyframe animations for when the `leave` event is triggered for the element that has the `repeated-item` class */ .repeated-item.ng-leave { animation: 0.5s my_animation; } @keyframes my_animation { from { opacity: 1; } to { opacity: 0; } } ``` The same approach to animation can be used using JavaScript code (**for simplicity, we rely on jQuery to perform animations here**): ```js myModule.animation('.repeated-item', function() { return { enter: function(element, done) { // Initialize the element's opacity element.css('opacity', 0); // Animate the element's opacity // (`element.animate()` is provided by jQuery) element.animate({opacity: 1}, done); // Optional `onDone`/`onCancel` callback function // to handle any post-animation cleanup operations return function(isCancelled) { if (isCancelled) { // Abort the animation if cancelled // (`element.stop()` is provided by jQuery) element.stop(); } }; }, leave: function(element, done) { // Initialize the element's opacity element.css('opacity', 1); // Animate the element's opacity // (`element.animate()` is provided by jQuery) element.animate({opacity: 0}, done); // Optional `onDone`/`onCancel` callback function // to handle any post-animation cleanup operations return function(isCancelled) { if (isCancelled) { // Abort the animation if cancelled // (`element.stop()` is provided by jQuery) element.stop(); } }; }, // We can also capture the following animation events: move: function(element, done) {}, addClass: function(element, className, done) {}, removeClass: function(element, className, done) {} } }); ``` With these generated CSS class names present on the element at the time, AngularJS automatically figures out whether to perform a CSS and/or JavaScript animation. Note that you can't have both CSS and JavaScript animations based on the same CSS class. See {@link ngAnimate#css-js-animations-together here} for more details. ## Class and `ngClass` animation hooks AngularJS also pays attention to CSS class changes on elements by triggering the **add** and **remove** hooks. This means that if a CSS class is added to or removed from an element then an animation can be executed in between, before the CSS class addition or removal is finalized. (Keep in mind that AngularJS will only be able to capture class changes if an **interpolated expression** or the **ng-class** directive is used on the element.) The example below shows how to perform animations during class changes:


CSS-Animated Text

.css-class-add, .css-class-remove { transition: all 0.5s cubic-bezier(0.250, 0.460, 0.450, 0.940); } .css-class, .css-class-add.css-class-add-active { color: red; font-size: 3em; } .css-class-remove.css-class-remove-active { font-size: 1em; color: black; }
Although the CSS is a little different than what we saw before, the idea is the same. ## Which directives support animations? A handful of common AngularJS directives support and trigger animation hooks whenever any major event occurs during their life cycle. The table below explains in detail which animation events are triggered: | Directive | Supported Animations | |-------------------------------------------------------------------------------|---------------------------------------------------------------------------| | {@link ng.directive:form#animations form / ngForm} | add and remove ({@link ng.directive:form#css-classes various classes}) | | {@link ngAnimate.directive:ngAnimateSwap#animations ngAnimateSwap} | enter and leave | | {@link ng.directive:ngClass#animations ngClass / {{class}​}} | add and remove | | {@link ng.directive:ngClassEven#animations ngClassEven} | add and remove | | {@link ng.directive:ngClassOdd#animations ngClassOdd} | add and remove | | {@link ng.directive:ngHide#animations ngHide} | add and remove (the `ng-hide` class) | | {@link ng.directive:ngIf#animations ngIf} | enter and leave | | {@link ng.directive:ngInclude#animations ngInclude} | enter and leave | | {@link module:ngMessages#animations ngMessage / ngMessageExp} | enter and leave | | {@link module:ngMessages#animations ngMessages} | add and remove (the `ng-active`/`ng-inactive` classes) | | {@link ng.directive:ngModel#animations ngModel} | add and remove ({@link ng.directive:ngModel#css-classes various classes}) | | {@link ng.directive:ngRepeat#animations ngRepeat} | enter, leave, and move | | {@link ng.directive:ngShow#animations ngShow} | add and remove (the `ng-hide` class) | | {@link ng.directive:ngSwitch#animations ngSwitch} | enter and leave | | {@link ngRoute.directive:ngView#animations ngView} | enter and leave | (More information can be found by visiting the documentation associated with each directive.) For a full breakdown of the steps involved during each animation event, refer to the {@link ng.$animate `$animate` API docs}. ## How do I use animations in my own directives? Animations within custom directives can also be established by injecting `$animate` directly into your directive and making calls to it when needed. ```js myModule.directive('my-directive', ['$animate', function($animate) { return function(scope, element) { element.on('click', function() { if (element.hasClass('clicked')) { $animate.removeClass(element, 'clicked'); } else { $animate.addClass(element, 'clicked'); } }); }; }]); ``` ## Animations on app bootstrap / page load By default, animations are disabled when the AngularJS app {@link guide/bootstrap bootstraps}. If you are using the {@link ngApp} directive, this happens in the `DOMContentLoaded` event, so immediately after the page has been loaded. Animations are disabled, so that UI and content are instantly visible. Otherwise, with many animations on the page, the loading process may become too visually overwhelming, and the performance may suffer. Internally, `ngAnimate` waits until all template downloads that are started right after bootstrap have finished. Then, it waits for the currently running {@link ng.$rootScope.Scope#$digest $digest} and one more after that, to finish. This ensures that the whole app has been compiled fully before animations are attempted. If you do want your animations to play when the app bootstraps, you can enable animations globally in your main module's {@link angular.Module#run run} function: ```js myModule.run(function($animate) { $animate.enabled(true); }); ``` ## How to (selectively) enable, disable and skip animations There are several different ways to disable animations, both globally and for specific animations. Disabling specific animations can help to speed up the render performance, for example for large `ngRepeat` lists that don't actually have animations. Because `ngAnimate` checks at runtime if animations are present, performance will take a hit even if an element has no animation. ### During the config: {@link $animateProvider#customFilter $animateProvider.customFilter()} This function can be called during the {@link angular.Module#config config} phase of an app. It takes a filter function as the only argument, which will then be used to "filter" animations (based on the animated element, the event type, and the animation options). Only when the filter function returns `true`, will the animation be performed. This allows great flexibility - you can easily create complex rules, such as allowing specific events only or enabling animations on specific subtrees of the DOM, and dynamically modify them, for example disabling animations at certain points in time or under certain circumstances. ```js app.config(function($animateProvider) { $animateProvider.customFilter(function(node, event, options) { // Example: Only animate `enter` and `leave` operations. return event === 'enter' || event === 'leave'; }); }); ``` The `customFilter` approach generally gives a big speed boost compared to other strategies, because the matching is done before other animation disabling strategies are checked.
**Best Practice:** Keep the filtering function as lean as possible, because it will be called for each DOM action (e.g. insertion, removal, class change) performed by "animation-aware" directives. See {@link guide/animations#which-directives-support-animations- here} for a list of built-in directives that support animations. Performing computationally expensive or time-consuming operations on each call of the filtering function can make your animations sluggish.
### During the config: {@link $animateProvider#classNameFilter $animateProvider.classNameFilter()} This function too can be called during the {@link angular.Module#config config} phase of an app. It takes a regex as the only argument, which will then be matched against the classes of any element that is about to be animated. The regex allows a lot of flexibility - you can either allow animations for specific classes only (useful when you are working with 3rd party animations), or exclude specific classes from getting animated. ```js app.config(function($animateProvider) { $animateProvider.classNameFilter(/animate-/); }); ``` ```css /* prefixed with `animate-` */ .animate-fade-add.animate-fade-add-active { transition: all 1s linear; opacity: 0; } ``` The `classNameFilter` approach generally gives a big speed boost compared to other strategies, because the matching is done before other animation disabling strategies are checked. However, that also means it is not possible to override class name matching with the two following strategies. It's of course still possible to enable / disable animations by changing an element's class name at runtime. ### At runtime: {@link ng.$animate#enabled $animate.enabled()} This function can be used to enable / disable animations in two different ways: With a single `boolean` argument, it enables / disables animations globally: `$animate.enabled(false)` disables all animations in your app. When the first argument is a native DOM or jqLite/jQuery element, the function enables / disables animations on this element *and all its children*: `$animate.enabled(myElement, false)`. You can still use it to re-enable animations for a child element, even if you have disabled them on a parent element. And compared to the `classNameFilter`, you can change the animation status at runtime instead of during the config phase. Note however that the `$animate.enabled()` state for individual elements does not overwrite disabling rules that have been set in the {@link $animateProvider#classNameFilter classNameFilter}. ### Via CSS styles: overwriting styles in the `ng-animate` CSS class Whenever an animation is started, `ngAnimate` applies the `ng-animate` class to the element for the whole duration of the animation. By applying CSS transition / animation styling to that class, you can skip an animation: ```css .my-class { transition: transform 2s; } .my-class:hover { transform: translateX(50px); } my-class.ng-animate { transition: 0s; } ``` By setting `transition: 0s`, `ngAnimate` will ignore the existing transition styles, and not try to animate them (Javascript animations will still execute, though). This can be used to prevent {@link guide/animations#preventing-collisions-with-existing-animations-and-third-party-libraries issues with existing animations interfering with `ngAnimate`}. ## Preventing flicker before an animation starts When nesting elements with structural animations, such as `ngIf`, into elements that have class-based animations such as `ngClass`, it sometimes happens that before the actual animation starts, there is a brief flicker or flash of content where the animated element is briefly visible. To prevent this, you can apply styles to the `ng-[event]-prepare` class, which is added as soon as an animation is initialized, but removed before the actual animation starts (after waiting for a `$digest`). This class is only added for *structural* animations (`enter`, `move`, and `leave`). Here's an example where you might see flickering: ```html
``` It is possible that during the `enter` event, the `.message` div will be briefly visible before it starts animating. In that case, you can add styles to the CSS that make sure the element stays hidden before the animation starts: ```css .message.ng-enter-prepare { opacity: 0; } /* Other animation styles ... */ ``` ## Preventing collisions with existing animations and third-party libraries By default, any `ngAnimate`-enabled directives will assume that `transition` / `animation` styles on the element are part of an `ngAnimate` animation. This can lead to problems when the styles are actually for animations that are independent of `ngAnimate`. For example, an element acts as a loading spinner. It has an infinite css animation on it, and also an {@link ngIf `ngIf`} directive, for which no animations are defined: ```css .spinner { animation: rotating 2s linear infinite; } @keyframes rotating { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } ``` Now, when the `ngIf` expression changes, `ngAnimate` will see the spinner animation and use it to animate the `enter`/`leave` event, which doesn't work because the animation is infinite. The element will still be added / removed after a timeout, but there will be a noticeable delay. This might also happen because some third-party frameworks place animation duration defaults across many element or className selectors in order to make their code small and reusable. You can prevent this unwanted behavior by adding CSS to the `.ng-animate` class, that is added for the whole duration of each animation. Simply overwrite the transition / animation duration. In the case of the spinner, this would be: ```css .spinner.ng-animate { animation: 0s none; transition: 0s none; } ``` If you do have CSS transitions / animations defined for the animation events, make sure they have a higher priority than any styles that are not related to `ngAnimate`. You can also use one of the other {@link guide/animations#how-to-selectively-enable-disable-and-skip-animations strategies to disable animations}. ## Enable animations outside of the application DOM tree: {@link ng.$animate#pin $animate.pin()} Before animating, `ngAnimate` checks if the animated element is inside the application DOM tree. If not, no animation is run. Usually, this is not a problem since most apps use the `html` or `body` elements as their root. Problems arise when the application is bootstrapped on a different element, and animations are attempted on elements that are outside the application tree, e.g. when libraries append popup or modal elements to the body tag. You can use {@link ng.$animate#pin `$animate.pin(element, parentHost)`} to associate an element with another element that belongs to your application. Simply call it before the element is added to the DOM / before the animation starts, with the element you want to animate, and the element which should be its assumed parent. ## More about animations For a full breakdown of each method available on `$animate`, see the {@link ng.$animate API documentation}. To see a complete demo, see the {@link tutorial/step_14 animation step in the phonecat tutorial}. angular.js-1.7.9/docs/content/guide/bootstrap.ngdoc000066400000000000000000000141511356472325200223370ustar00rootroot00000000000000@ngdoc overview @name Bootstrap @sortOrder 350 @description # Bootstrap This page explains the AngularJS initialization process and how you can manually initialize AngularJS if necessary. ## AngularJS ` ``` 1. Place the `script` tag at the bottom of the page. Placing script tags at the end of the page improves app load time because the HTML loading is not blocked by loading of the `angular.js` script. You can get the latest bits from http://code.angularjs.org. Please don't link your production code to this URL, as it will expose a security hole on your site. For experimental development linking to our site is fine. * Choose: `angular-[version].js` for a human-readable file, suitable for development and debugging. * Choose: `angular-[version].min.js` for a compressed and obfuscated file, suitable for use in production. 2. Place `ng-app` to the root of your application, typically on the `` tag if you want AngularJS to auto-bootstrap your application. 3. If you choose to use the old style directive syntax `ng:` then include xml-namespace in `html` when running the page in the XHTML mode. (This is here for historical reasons, and we no longer recommend use of `ng:`.) ## Automatic Initialization AngularJS initializes automatically upon `DOMContentLoaded` event or when the `angular.js` script is evaluated if at that time `document.readyState` is set to `'complete'`. At this point AngularJS looks for the {@link ng.directive:ngApp `ngApp`} directive which designates your application root. If the {@link ng.directive:ngApp `ngApp`} directive is found then AngularJS will: * load the {@link guide/module module} associated with the directive. * create the application {@link auto.$injector injector} * compile the DOM treating the {@link ng.directive:ngApp `ngApp`} directive as the root of the compilation. This allows you to tell it to treat only a portion of the DOM as an AngularJS application. ```html I can add: {{ 1+2 }}. ``` As a best practice, consider adding an `ng-strict-di` directive on the same element as `ng-app`: ```html I can add: {{ 1+2 }}. ``` This will ensure that all services in your application are properly annotated. See the {@link guide/di#using-strict-dependency-injection dependency injection strict mode} docs for more. ## Manual Initialization If you need to have more control over the initialization process, you can use a manual bootstrapping method instead. Examples of when you'd need to do this include using script loaders or the need to perform an operation before AngularJS compiles a page. Here is an example of manually initializing AngularJS: ```html
Hello {{greetMe}}!
``` Note that we provided the name of our application module to be loaded into the injector as the second parameter of the {@link angular.bootstrap} function. Notice that `angular.bootstrap` will not create modules on the fly. You must create any custom {@link guide/module modules} before you pass them as a parameter. You should call `angular.bootstrap()` *after* you've loaded or defined your modules. You cannot add controllers, services, directives, etc after an application bootstraps.
**Note:** You should not use the ng-app directive when manually bootstrapping your app.
This is the sequence that your code should follow: 1. After the page and all of the code is loaded, find the root element of your AngularJS application, which is typically the root of the document. 2. Call {@link angular.bootstrap} to {@link compiler compile} the element into an executable, bi-directionally bound application. ## Things to keep in mind There are a few things to keep in mind regardless of automatic or manual bootstrapping: - While it's possible to bootstrap more than one AngularJS application per page, we don't actively test against this scenario. It's possible that you'll run into problems, especially with complex apps, so caution is advised. - Do not bootstrap your app on an element with a directive that uses {@link ng.$compile#transclusion transclusion}, such as {@link ng.ngIf `ngIf`}, {@link ng.ngInclude `ngInclude`} and {@link ngRoute.ngView `ngView`}. Doing this misplaces the app {@link ng.$rootElement `$rootElement`} and the app's {@link auto.$injector injector}, causing animations to stop working and making the injector inaccessible from outside the app. ## Deferred Bootstrap This feature enables tools like [Batarang](https://github.com/angular/angularjs-batarang) and test runners to hook into angular's bootstrap process and sneak in more modules into the DI registry which can replace or augment DI services for the purpose of instrumentation or mocking out heavy dependencies. If `window.name` contains prefix `NG_DEFER_BOOTSTRAP!` when {@link angular.bootstrap} is called, the bootstrap process will be paused until `angular.resumeBootstrap()` is called. `angular.resumeBootstrap()` takes an optional array of modules that should be added to the original list of modules that the app was about to be bootstrapped with. angular.js-1.7.9/docs/content/guide/compiler.ngdoc000066400000000000000000000466101356472325200221410ustar00rootroot00000000000000@ngdoc overview @name HTML Compiler @sortOrder 330 @description # HTML Compiler
**Note:** this guide is targeted towards developers who are already familiar with AngularJS basics. If you're just getting started, we recommend the {@link tutorial/ tutorial} first. If you just want to create custom directives, we recommend the {@link guide/directive directives guide}. If you want a deeper look into AngularJS's compilation process, you're in the right place.
## Overview AngularJS's {@link ng.$compile HTML compiler} allows the developer to teach the browser new HTML syntax. The compiler allows you to attach behavior to any HTML element or attribute and even create new HTML elements or attributes with custom behavior. AngularJS calls these behavior extensions {@link ng.$compileProvider#directive directives}. HTML has a lot of constructs for formatting the HTML for static documents in a declarative fashion. For example if something needs to be centered, there is no need to provide instructions to the browser how the window size needs to be divided in half so that the center is found, and that this center needs to be aligned with the text's center. Simply add an `align="center"` attribute to any element to achieve the desired behavior. Such is the power of declarative language. However, the declarative language is also limited, as it does not allow you to teach the browser new syntax. For example, there is no easy way to get the browser to align the text at 1/3 the position instead of 1/2. What is needed is a way to teach the browser new HTML syntax. AngularJS comes pre-bundled with common directives which are useful for building any app. We also expect that you will create directives that are specific to your app. These extensions become a Domain Specific Language for building your application. All of this compilation takes place in the web browser; no server side or pre-compilation step is involved. ## Compiler Compiler is an AngularJS service which traverses the DOM looking for attributes. The compilation process happens in two phases. 1. **Compile:** traverse the DOM and collect all of the directives. The result is a linking function. 2. **Link:** combine the directives with a scope and produce a live view. Any changes in the scope model are reflected in the view, and any user interactions with the view are reflected in the scope model. This makes the scope model the single source of truth. Some directives such as {@link ng.directive:ngRepeat `ng-repeat`} clone DOM elements once for each item in a collection. Having a compile and link phase improves performance since the cloned template only needs to be compiled once, and then linked once for each clone instance. ## Directive A directive is a behavior which should be triggered when specific HTML constructs are encountered during the compilation process. The directives can be placed in element names, attributes, class names, as well as comments. Here are some equivalent examples of invoking the {@link ng.directive:ngBind `ng-bind`} directive. ```html ``` A directive is just a function which executes when the compiler encounters it in the DOM. See {@link ng.$compileProvider#directive directive API} for in-depth documentation on how to write directives. Here is a directive which makes any element draggable. Notice the `draggable` attribute on the `` element. angular.module('drag', []). directive('draggable', function($document) { return function(scope, element, attr) { var startX = 0, startY = 0, x = 0, y = 0; element.css({ position: 'relative', border: '1px solid red', backgroundColor: 'lightgrey', cursor: 'pointer', display: 'block', width: '65px' }); element.on('mousedown', function(event) { // Prevent default dragging of selected content event.preventDefault(); startX = event.screenX - x; startY = event.screenY - y; $document.on('mousemove', mousemove); $document.on('mouseup', mouseup); }); function mousemove(event) { y = event.screenY - startY; x = event.screenX - startX; element.css({ top: y + 'px', left: x + 'px' }); } function mouseup() { $document.off('mousemove', mousemove); $document.off('mouseup', mouseup); } }; }); Drag ME The presence of the `draggable` attribute on any element gives the element new behavior. We extended the vocabulary of the browser in a way which is natural to anyone who is familiar with the principles of HTML. ## Understanding View Most other templating systems consume a static string template and combine it with data, resulting in a new string. The resulting text is then `innerHTML`ed into an element. This means that any changes to the data need to be re-merged with the template and then `innerHTML`ed into the DOM. Some of the issues with this approach are: 1. reading user input and merging it with data 2. clobbering user input by overwriting it 3. managing the whole update process 4. lack of behavior expressiveness AngularJS is different. The AngularJS compiler consumes the DOM, not string templates. The result is a linking function, which when combined with a scope model results in a live view. The view and scope model bindings are transparent. The developer does not need to make any special calls to update the view. And because `innerHTML` is not used, you won't accidentally clobber user input. Furthermore, AngularJS directives can contain not just text bindings, but behavioral constructs as well. The AngularJS approach produces a stable DOM. The DOM element instance bound to a model item instance does not change for the lifetime of the binding. This means that the code can get hold of the elements and register event handlers and know that the reference will not be destroyed by template data merge. ## How directives are compiled It's important to note that AngularJS operates on DOM nodes rather than strings. Usually, you don't notice this restriction because when a page loads, the web browser parses HTML into the DOM automatically. HTML compilation happens in three phases: 1. {@link ng.$compile `$compile`} traverses the DOM and matches directives. If the compiler finds that an element matches a directive, then the directive is added to the list of directives that match the DOM element. A single element may match multiple directives. 2. Once all directives matching a DOM element have been identified, the compiler sorts the directives by their `priority`. Each directive's `compile` functions are executed. Each `compile` function has a chance to modify the DOM. Each `compile` function returns a `link` function. These functions are composed into a "combined" link function, which invokes each directive's returned `link` function. 3. `$compile` links the template with the scope by calling the combined linking function from the previous step. This in turn will call the linking function of the individual directives, registering listeners on the elements and setting up {@link ng.$rootScope.Scope#$watch `$watch`s} with the {@link ng.$rootScope.Scope `scope`} as each directive is configured to do. The result of this is a live binding between the scope and the DOM. So at this point, a change in a model on the compiled scope will be reflected in the DOM. Below is the corresponding code using the `$compile` service. This should help give you an idea of what AngularJS does internally. ```js var $compile = ...; // injected into your code var scope = ...; var parent = ...; // DOM element where the compiled template can be appended var html = '
'; // Step 1: parse HTML into DOM element var template = angular.element(html); // Step 2: compile the template var linkFn = $compile(template); // Step 3: link the compiled template with the scope. var element = linkFn(scope); // Step 4: Append to DOM (optional) parent.appendChild(element); ``` ### The difference between Compile and Link At this point you may wonder why the compile process has separate compile and link phases. The short answer is that compile and link separation is needed any time a change in a model causes a change in the **structure** of the DOM. It's rare for directives to have a **compile function**, since most directives are concerned with working with a specific DOM element instance rather than changing its overall structure. Directives often have a **link function**. A link function allows the directive to register listeners to the specific cloned DOM element instance as well as to copy content into the DOM from the scope.
**Best Practice:** Any operation which can be shared among the instance of directives should be moved to the compile function for performance reasons.
#### An Example of "Compile" Versus "Link" To understand, let's look at a real-world example with `ngRepeat`: ```html Hello {{user.name}}, you have these actions:
  • {{action.description}}
``` When the above example is compiled, the compiler visits every node and looks for directives. `{{user.name}}` matches the {@link ng.$interpolate interpolation directive} and `ng-repeat` matches the {@link ng.directive:ngRepeat `ngRepeat` directive}. But {@link ng.directive:ngRepeat ngRepeat} has a dilemma. It needs to be able to clone new `
  • ` elements for every `action` in `user.actions`. This initially seems trivial, but it becomes more complicated when you consider that `user.actions` might have items added to it later. This means that it needs to save a clean copy of the `
  • ` element for cloning purposes. As new `action`s are inserted, the template `
  • ` element needs to be cloned and inserted into `ul`. But cloning the `
  • ` element is not enough. It also needs to compile the `
  • ` so that its directives, like `{{action.description}}`, evaluate against the right {@link ng.$rootScope.Scope scope}. A naive approach to solving this problem would be to simply insert a copy of the `
  • ` element and then compile it. The problem with this approach is that compiling on every `
  • ` element that we clone would duplicate a lot of the work. Specifically, we'd be traversing `
  • ` each time before cloning it to find the directives. This would cause the compilation process to be slower, in turn making applications less responsive when inserting new nodes. The solution is to break the compilation process into two phases: the **compile phase** where all of the directives are identified and sorted by priority, and a **linking phase** where any work which "links" a specific instance of the {@link ng.$rootScope.Scope scope} and the specific instance of an `
  • ` is performed.
    **Note:** *Link* means setting up listeners on the DOM and setting up `$watch` on the Scope to keep the two in sync.
    {@link ng.directive:ngRepeat `ngRepeat`} works by preventing the compilation process from descending into the `
  • ` element so it can make a clone of the original and handle inserting and removing DOM nodes itself. Instead the {@link ng.directive:ngRepeat `ngRepeat`} directive compiles `
  • ` separately. The result of the `
  • ` element compilation is a linking function which contains all of the directives contained in the `
  • ` element, ready to be attached to a specific clone of the `
  • ` element. At runtime the {@link ng.directive:ngRepeat `ngRepeat`} watches the expression and as items are added to the array it clones the `
  • ` element, creates a new {@link ng.$rootScope.Scope scope} for the cloned `
  • ` element and calls the link function on the cloned `
  • `. ### Understanding How Scopes Work with Transcluded Directives One of the most common use cases for directives is to create reusable components. Below is a pseudo code showing how a simplified dialog component may work. ```html
    Body goes here: {{username}} is {{title}}.
    ``` Clicking on the "show" button will open the dialog. The dialog will have a title, which is data bound to `username`, and it will also have a body which we would like to transclude into the dialog. Here is an example of what the template definition for the `dialog` widget may look like. ```html

    {{title}}

    ``` This will not render properly, unless we do some scope magic. The first issue we have to solve is that the dialog box template expects `title` to be defined. But we would like the template's scope property `title` to be the result of interpolating the `` element's `title` attribute (i.e. `"Hello {{username}}"`). Furthermore, the buttons expect the `onOk` and `onCancel` functions to be present in the scope. This limits the usefulness of the widget. To solve the mapping issue we use the `scope` to create local variables which the template expects as follows: ```js scope: { title: '@', // the title uses the data-binding from the parent scope onOk: '&', // create a delegate onOk function onCancel: '&', // create a delegate onCancel function visible: '=' // set up visible to accept data-binding } ``` Creating local properties on widget scope creates two problems: 1. isolation - if the user forgets to set `title` attribute of the dialog widget the dialog template will bind to parent scope property. This is unpredictable and undesirable. 2. transclusion - the transcluded DOM can see the widget locals, which may overwrite the properties which the transclusion needs for data-binding. In our example the `title` property of the widget clobbers the `title` property of the transclusion. To solve the issue of lack of isolation, the directive declares a new `isolated` scope. An isolated scope does not prototypically inherit from the parent scope, and therefore we don't have to worry about accidentally clobbering any properties. However `isolated` scope creates a new problem: if a transcluded DOM is a child of the widget isolated scope then it will not be able to bind to anything. For this reason the transcluded scope is a child of the original scope, before the widget created an isolated scope for its local variables. This makes the transcluded and widget isolated scope siblings. This may seem to be unexpected complexity, but it gives the widget user and developer the least surprise. Therefore the final directive definition looks something like this: ```js transclude: true, scope: { title: '@', // the title uses the data-binding from the parent scope onOk: '&', // create a delegate onOk function onCancel: '&', // create a delegate onCancel function visible: '=' // set up visible to accept data-binding }, restrict: 'E', replace: true ``` ### Double Compilation, and how to avoid it Double compilation occurs when an already compiled part of the DOM gets compiled again. This is an undesired effect and can lead to misbehaving directives, performance issues, and memory leaks. A common scenario where this happens is a directive that calls `$compile` in a directive link function on the directive element. In the following **faulty example**, a directive adds a mouseover behavior to a button with `ngClick` on it: ``` angular.module('app').directive('addMouseover', function($compile) { return { link: function(scope, element, attrs) { var newEl = angular.element(' My Hint'); element.on('mouseenter mouseleave', function() { scope.$apply('showHint = !showHint'); }); attrs.$set('addMouseover', null); // To stop infinite compile loop element.append(newEl); $compile(element)(scope); // Double compilation } } }) ``` At first glance, it looks like removing the original `addMouseover` attribute is all there is needed to make this example work. However, if the directive element or its children have other directives attached, they will be compiled and linked again, because the compiler doesn't keep track of which directives have been assigned to which elements. This can cause unpredictable behavior, e.g. `ngClick` or other event handlers will be attached again. It can also degrade performance, as watchers for text interpolation are added twice to the scope. Double compilation should therefore be avoided. In the above example, only the new element should be compiled: ``` angular.module('app').directive('addMouseover', function($compile) { return { link: function(scope, element, attrs) { var newEl = angular.element(' My Hint'); element.on('mouseenter mouseleave', function() { scope.$apply('showHint = !showHint'); }); element.append(newEl); $compile(newEl)(scope); // Only compile the new element } } }) ``` Another scenario is adding a directive programmatically to a compiled element and then executing compile again. See the following **faulty example**: ```html ``` ``` angular.module('app').directive('addOptions', function($compile) { return { link: function(scope, element, attrs) { attrs.$set('addOptions', null) // To stop infinite compile loop attrs.$set('ngModelOptions', '{debounce: 1000}'); $compile(element)(scope); // Double compilation } } }); ``` In that case, it is necessary to intercept the *initial* compilation of the element: 1. Give your directive the `terminal` property and a higher priority than directives that should not be compiled twice. In the example, the compiler will only compile directives which have a priority of 100 or higher. 2. Inside this directive's compile function, add any other directive attributes to the template. 3. Compile the element, but restrict the maximum priority, so that any already compiled directives (including the `addOptions` directive) are not compiled again. 4. In the link function, link the compiled element with the element's scope. ``` angular.module('app').directive('addOptions', function($compile) { return { priority: 100, // ngModel has priority 1 terminal: true, compile: function(templateElement, templateAttributes) { templateAttributes.$set('ngModelOptions', '{debounce: 1000}'); // The third argument is the max priority. Only directives with priority < 100 will be compiled, // therefore we don't need to remove the attribute var compiled = $compile(templateElement, null, 100); return function linkFn(scope) { compiled(scope) // Link compiled element to scope } } } }); ``` angular.js-1.7.9/docs/content/guide/component-router.ngdoc000066400000000000000000001115271356472325200236470ustar00rootroot00000000000000@ngdoc overview @name Component Router @sortOrder 306 @description # Component Router
    **Deprecation Notice:** In an effort to keep synchronized with router changes in the new Angular, this implementation of the Component Router (ngComponentRouter module) has been deprecated and will not receive further updates. We are investigating backporting the new Angular Router to AngularJS, but alternatively, use the {@link ngRoute} module or community developed projects (e.g. [ui-router](https://github.com/angular-ui/ui-router)).
    This guide describes the Component Router for AngularJS.
    If you are looking for information about the default router for AngularJS have a look at the {@link ngRoute} module. If you are looking for information about the Component Router for the new Angular then check out the [Angular Router Guide](https://angular.io/docs/ts/latest/guide/router.html).
    ## Overview Here is a table of the main concepts used in the Component Router. | Concept | Description | | ----------------------|-------------------------------------------------------------------------------------- | | Router | Displays the Routing Components for the active Route. Manages navigation from one component to the next. | | RootRouter | The top level Router that interacts with the current URL location | | RouteConfig | Configures a Router with RouteDefinitions, each mapping a URL path to a component. | | Routing Component | An AngularJS component with a RouteConfig and an associated Router. | | RouteDefinition | Defines how the router should navigate to a component based on a URL pattern. | | ngOutlet | The directive (``) that marks where the router should display a view. | | ngLink | The directive (`ng-link="..."`) for binding a clickable HTML element to a route, via a Link Parameters Array. | | Link Parameters Array | An array that the router interprets into a routing instruction. We can bind a RouterLink to that array or pass the array as an argument to the Router.navigate method. | ## Component-based Applications It is recommended to develop AngularJS applications as a hierarchy of Components. Each Component is an isolated part of the application, which is responsible for its own user interface and has a well defined programmatic interface to the Component that contains it. Take a look at the {@link guide/component component guide} for more information. ![Component Based Architecture](img/guide/component-based-architecture.svg) ## URLs and Navigation In most applications, users navigate from one view to the next as they perform application tasks. The browser provides a familiar model of application navigation. We enter a URL in the address bar or click on a link and the browser navigates to a new page. We click the browser's back and forward buttons and the browser navigates backward and forward through the history of pages we've seen. We understand that each view corresponds to a particular URL. In a Component-based application, each of these views is implemented by one or more Components. ## Component Routes **How do we choose which Components to display given a particular URL?** When using the Component Router, each **Component** in the application can have a **Router** associated with it. This **Router** contains a mapping of URL segments to child **Components**. ```js $routeConfig: [ { path: '/a/b/c', component: 'someComponent' }, ... ] ``` This means that for a given URL the **Router** will render an associated child **Component**. ## Outlets **How do we know where to render a child Component?** Each **Routing Component**, needs to have a template that contains one or more **Outlets**, which is where its child **Components** are rendered. We specify the **Outlet** in the template using the {@link ngOutlet ``} directive. ```html ``` *In the future `ng-outlet` will be able to render different child **Components** for a given **Route** by specifying a `name` attribute.* ## Root Router and Component **How does the Component Router know which Component to render first?** All Component Router applications must contain a top level **Routing Component**, which is associated with a top level **Root Router**. The **Root Router** is the starting point for all navigation. You can access this **Router** by injecting the `$rootRouter` service. We define the top level **Root Component** by providing a value for the {@link $routerRootComponent} service. ```js myModule.value('$routerRootComponent', 'myApp'); ``` Here we have specified that the **Root Component** is the component directive with the name `myApp`. Remember to instantiate this **Root Component** in our `index.html` file. ```html ``` ## Route Matching When we navigate to any given URL, the {@link $rootRouter} matches its **Route Config** against the URL. If a **Route Definition** in the **Route Config** recognizes a part of the URL then the **Component** associated with the **Route Definition** is instantiated and rendered in the **Outlet**. If the new **Component** contains routes of its own then a new **Router ({@link ChildRouter})** is created for this **Routing Component**. The {@link ChildRouter} for the new **Routing Component** then attempts to match its **Route Config** against the parts of the URL that have not already been matched by the previous **Router**. This process continues until we run out of **Routing Components** or consume the entire URL. ![Routed Components](img/guide/component-routes.svg) In the previous diagram, we can see that the URL `/heros/4` has been matched against the `App`, `Heroes` and `HeroDetail` **Routing Components**. The **Routers** for each of the **Routing Components** consumed a part of the URL: "/", "/heroes" and "/4" respectively. The result is that we end up with a hierarchy of **Routing Components** rendered in **Outlets**, via the {@link ngOutlet} directive, in each **Routing Component's** template, as you can see in the following diagram. ![Component Hierarchy](img/guide/component-hierarchy.svg) ## Example Heroes App You can see the complete application running below.

    Component Router

    angular.module('app', ['ngComponentRouter', 'heroes', 'crisis-center']) .config(function($locationProvider) { $locationProvider.html5Mode(true); }) .value('$routerRootComponent', 'app') .component('app', { template: '\n' + '\n', $routeConfig: [ {path: '/crisis-center/...', name: 'CrisisCenter', component: 'crisisCenter', useAsDefault: true}, {path: '/heroes/...', name: 'Heroes', component: 'heroes' } ] }); angular.module('heroes', []) .service('heroService', HeroService) .component('heroes', { template: '

    Heroes

    ', $routeConfig: [ {path: '/', name: 'HeroList', component: 'heroList', useAsDefault: true}, {path: '/:id', name: 'HeroDetail', component: 'heroDetail'} ] }) .component('heroList', { template: '
    \n' + '{{hero.name}}\n' + '
    ', controller: HeroListComponent }) .component('heroDetail', { template: '
    \n' + '

    "{{$ctrl.hero.name}}"

    \n' + '
    \n' + ' {{$ctrl.hero.id}}
    \n' + '
    \n' + ' \n' + ' \n' + '
    \n' + ' \n' + '
    \n', bindings: { $router: '<' }, controller: HeroDetailComponent }); function HeroService($q) { var heroesPromise = $q.resolve([ { id: 11, name: 'Mr. Nice' }, { id: 12, name: 'Narco' }, { id: 13, name: 'Bombasto' }, { id: 14, name: 'Celeritas' }, { id: 15, name: 'Magneta' }, { id: 16, name: 'RubberMan' } ]); this.getHeroes = function() { return heroesPromise; }; this.getHero = function(id) { return heroesPromise.then(function(heroes) { for (var i = 0; i < heroes.length; i++) { if (heroes[i].id === id) return heroes[i]; } }); }; } function HeroListComponent(heroService) { var selectedId = null; var $ctrl = this; this.$routerOnActivate = function(next) { // Load up the heroes for this view heroService.getHeroes().then(function(heroes) { $ctrl.heroes = heroes; selectedId = next.params.id; }); }; this.isSelected = function(hero) { return (hero.id === selectedId); }; } function HeroDetailComponent(heroService) { var $ctrl = this; this.$routerOnActivate = function(next) { // Get the hero identified by the route parameter var id = next.params.id; heroService.getHero(id).then(function(hero) { $ctrl.hero = hero; }); }; this.gotoHeroes = function() { var heroId = this.hero && this.hero.id; this.$router.navigate(['HeroList', {id: heroId}]); }; }
    angular.module('crisis-center', ['dialog']) .service('crisisService', CrisisService) .component('crisisCenter', { template: '

    Crisis Center

    ', $routeConfig: [ {path:'/', name: 'CrisisList', component: 'crisisList', useAsDefault: true}, {path:'/:id', name: 'CrisisDetail', component: 'crisisDetail'} ] }) .component('crisisList', { template: '
      \n' + '
    • \n' + ' {{crisis.id}} {{crisis.name}}\n' + '
    • \n' + '
    \n', bindings: { $router: '<' }, controller: CrisisListComponent, $canActivate: function($nextInstruction, $prevInstruction) { console.log('$canActivate', arguments); } }) .component('crisisDetail', { templateUrl: 'crisisDetail.html', bindings: { $router: '<' }, controller: CrisisDetailComponent }); function CrisisService($q) { var crisesPromise = $q.resolve([ {id: 1, name: 'Princess Held Captive'}, {id: 2, name: 'Dragon Burning Cities'}, {id: 3, name: 'Giant Asteroid Heading For Earth'}, {id: 4, name: 'Release Deadline Looms'} ]); this.getCrises = function() { return crisesPromise; }; this.getCrisis = function(id) { return crisesPromise.then(function(crises) { for (var i = 0; i < crises.length; i++) { if (crises[i].id === id) return crises[i]; } }); }; } function CrisisListComponent(crisisService) { var selectedId = null; var ctrl = this; this.$routerOnActivate = function(next) { console.log('$routerOnActivate', this, arguments); // Load up the crises for this view crisisService.getCrises().then(function(crises) { ctrl.crises = crises; selectedId = next.params.id; }); }; this.isSelected = function(crisis) { return (crisis.id === selectedId); }; this.onSelect = function(crisis) { this.$router.navigate(['CrisisDetail', { id: crisis.id }]); }; } function CrisisDetailComponent(crisisService, dialogService) { var ctrl = this; this.$routerOnActivate = function(next) { // Get the crisis identified by the route parameter var id = next.params.id; crisisService.getCrisis(id).then(function(crisis) { if (crisis) { ctrl.editName = crisis.name; ctrl.crisis = crisis; } else { // id not found ctrl.gotoCrises(); } }); }; this.$routerCanDeactivate = function() { // Allow synchronous navigation (`true`) if no crisis or the crisis is unchanged. if (!this.crisis || this.crisis.name === this.editName) { return true; } // Otherwise ask the user with the dialog service and return its // promise which resolves to true or false when the user decides return dialogService.confirm('Discard changes?'); }; this.cancel = function() { ctrl.editName = ctrl.crisis.name; ctrl.gotoCrises(); }; this.save = function() { ctrl.crisis.name = ctrl.editName; ctrl.gotoCrises(); }; this.gotoCrises = function() { var crisisId = ctrl.crisis && ctrl.crisis.id; // Pass along the hero id if available // so that the CrisisListComponent can select that hero. this.$router.navigate(['CrisisList', {id: crisisId}]); }; }

    "{{$ctrl.editName}}"

    {{$ctrl.crisis.id}}
    angular.module('dialog', []) .service('dialogService', DialogService); function DialogService($q) { this.confirm = function(message) { return $q.resolve(window.confirm(message || 'Is it OK?')); }; } h1 {color: #369; font-family: Arial, Helvetica, sans-serif; font-size: 250%;} h2 { color: #369; font-family: Arial, Helvetica, sans-serif; } h3 { color: #444; font-weight: lighter; } body { margin: 2em; } body, input[text], button { color: #888; font-family: Cambria, Georgia; } button {padding: 0.2em; font-size: 14px} ul {list-style-type: none; margin-left: 1em; padding: 0; width: 20em;} li { cursor: pointer; position: relative; left: 0; transition: all 0.2s ease; } li:hover {color: #369; background-color: #EEE; left: .2em;} /* route-link anchor tags */ a {padding: 5px; text-decoration: none; font-family: Arial, Helvetica, sans-serif; } a:visited, a:link {color: #444;} a:hover {color: white; background-color: #1171a3; } a.router-link-active {color: white; background-color: #52b9e9; } .selected { background-color: #EEE; color: #369; } .badge { font-size: small; color: white; padding: 0.1em 0.7em; background-color: #369; line-height: 1em; position: relative; left: -1px; top: -1px; } crisis-detail input { width: 20em; }
    ### Getting Started In the following sections we will step through building this application. The finished application has views to display list and detail views of Heroes and Crises. #### Install the libraries It is easier to use [Yarn](https://yarnpkg.com) or [npm](https://www.npmjs.com) to install the **Component Router** module. For this guide we will also install AngularJS itself via Yarn: ```bash yarn init yarn add angular@1.5.x @angular/router@0.2.0 ``` #### Load the scripts Just like any AngularJS application, we load the JavaScript files into our `index.html`: ```html ``` You also need to include ES6 shims for browsers that do not support ES6 code (Internet Explorer, iOs < 8, Android < 5.0, Windows Mobile < 10): ```html ``` #### Create the `app` module In the app.js file, create the main application module `app` which depends on the `ngComponentRouter` module, which is provided by the **Component Router** script. ```js angular.module('app', ['ngComponentRouter']) ``` We must choose what **Location Mode** the **Router** should use. We are going to use HTML5 mode locations, so that we will not have hash-based paths. We must rely on the browser to provide `pushState` support, which is true for most modern browsers. See {@link $locationProvider#html5Mode} for more information.
    Using HTML5 mode means that we can have clean URLs for our application routes. However, HTML5 mode does require that our web server, which hosts the application, understands that it must respond with the index.html file for requests to URLs that represent all our application routes. We are going to use the `lite-server` web server to do this for us.
    ```js .config(function($locationProvider) { $locationProvider.html5Mode(true); }) ``` Configure the top level routed `App` Component. ```js .value('$routerRootComponent', 'app') ``` Create a very simple App Component to test that the application is working. We are using the AngularJS {@link $compileProvider#component `.component()`} helper method to create all the **Components** in our application. It is perfectly suited to this task. ```js .component('app', { template: 'It worked!' }); ``` Add a `` element to the head of our index.html. Remember that we have chosen to use HTML5 mode for the `$location` service. This means that our HTML must have a base URL. ```html ... ``` #### Bootstrap AngularJS Bootstrap the AngularJS application and add the top level App Component. ```html

    Component Router

    ``` ### Implementing the AppComponent In the previous section we have created a single top level **App Component**. Let's now create some more **Routing Components** and wire up **Route Config** for those. We start with a Heroes Feature, which will display one of two views. * A list of Heroes that are available: ![Heroes List View](img/guide/heroes-list.png) * A detailed view of a single Hero: ![Heroes List View](img/guide/hero-detail.png) We are going to have a `Heroes` Component for the Heroes feature of our application, and then `HeroList` and `HeroDetail` **Components** that will actually display the two different views. #### App Component Configure the **App Component** with a template and **Route Config**: ```js .component('app', { template: '\n' + '\n', $routeConfig: [ {path: '/heroes/...', name: 'Heroes', component: 'heroes'}, ] }); ``` The **App Component** has an `` directive in its template. This is where the child **Components** of this view will be rendered. #### ngLink We have used the `ng-link` directive to create a link to navigate to the Heroes Component. By using this directive we don't need to know what the actual URL will be. We can let the Router generate that for us. We have included a link to the Crisis Center but have not included the `ng-link` directive as we have not yet implemented the CrisisCenter component. #### Non-terminal Routes We need to tell the **Router** that the `Heroes` **Route Definition** is **non-terminal**, that it should continue to match **Routes** in its child **Components**. We do this by adding a **continuation ellipsis (`...`)** to the path of the Heroes Route, `/heroes/...`. Without the **continuation ellipsis** the `HeroList` **Route** will never be matched because the Router will stop at the `Heroes` **Routing Component** and not try to match the rest of the URL. ### Heroes Feature Now we can implement our Heroes Feature which consists of three **Components**: `Heroes`, `HeroList` and `HeroDetail`. The `Heroes` **Routing Component** simply provides a template containing the {@link ngOutlet} directive and a **Route Config** that defines a set of child **Routes** which delegate through to the `HeroList` and `HeroDetail` **Components**. ### HeroesComponent Create a new file `heroes.js`, which defines a new AngularJS module for the **Components** of this feature and registers the Heroes **Component**. ```js angular.module('heroes', []) .component('heroes', { template: '

    Heroes

    ', $routeConfig: [ {path: '/', name: 'HeroList', component: 'heroList', useAsDefault: true}, {path: '/:id', name: 'HeroDetail', component: 'heroDetail'} ] }) ``` Remember to load this file in the index.html: ```html ``` and also to add the module as a dependency of the `app` module: ```js angular.module('app', ['ngComponentRouter', 'heroes']) ``` #### Use As Default The `useAsDefault` property on the `HeroList` **Route Definition**, indicates that if no other **Route Definition** matches the URL, then this **Route Definition** should be used by default. #### Route Parameters The `HeroDetail` Route has a named parameter (`id`), indicated by prefixing the URL segment with a colon, as part of its `path` property. The **Router** will match anything in this segment and make that value available to the HeroDetail **Component**. #### Terminal Routes Both the Routes in the `HeroesComponent` are terminal, i.e. their routes do not end with `...`. This is because the `HeroList` and `HeroDetail` will not contain any child routes. #### Route Names **What is the difference between the `name` and `component` properties on a Route Definition?** The `component` property in a **Route Definition** defines the **Component** directive that will be rendered into the DOM via the **Outlet**. For example the `heroDetail` **Component** will be rendered into the page where the `` lives as ``. The `name` property is used to reference the **Route Definition** when generating URLs or navigating to **Routes**. For example this link will `Heroes` navigate the **Route Definition** that has the `name` property of `"Heroes"`. ### HeroList Component The HeroList **Component** is the first component in the application that actually contains significant functionality. It loads up a list of heroes from a `heroService` and displays them using `ng-repeat`. Add it to the `heroes.js` file: ```js .component('heroList', { template: '
    \n' + '{{hero.name}}\n' + '
    ', controller: HeroListComponent }) ``` The `ng-link` directive creates links to a more detailed view of each hero, via the expression `['HeroDetail', {id: hero.id}]`. This expression is an array describing what Routes to use to generate the link. The first item is the name of the HeroDetail **Route Definition** and the second is a parameter object that will be available to the HeroDetail **Component**. *The HeroDetail section below explains how to get hold of the `id` parameter of the HeroDetail Route.* The template iterates through each `hero` object of the array in the `$ctrl.heroes` property. *Remember that the `module.component()` helper automatically provides the **Component's Controller** as the `$ctrl` property on the scope of the template.* ### HeroService Our HeroService simulates requesting a list of heroes from a server. In a real application this would be making an actual server request, perhaps over HTTP. ```js function HeroService($q) { var heroesPromise = $q.resolve([ { id: 11, name: 'Mr. Nice' }, ... ]); this.getHeroes = function() { return heroesPromise; }; this.getHero = function(id) { return heroesPromise.then(function(heroes) { for (var i = 0; i < heroes.length; i++) { if (heroes[i].id === id) return heroes[i]; } }); }; } ``` Note that both the `getHeroes()` and `getHero(id)` methods return a promise for the data. This is because in real-life we would have to wait for the server to respond with the data. ### Router Lifecycle Hooks **How do I know when my Component is active?** To deal with initialization and tidy up of **Components** that are rendered by a **Router**, we can implement one or more **Lifecycle Hooks** on the **Component**. These will be called at well defined points in the lifecycle of the **Component**. The **Lifecycle Hooks** that can be implemented as instance methods on the **Component** are as follows: * `$routerCanReuse` : called to to determine whether a **Component** can be reused across **Route Definitions** that match the same type of **Component**, or whether to destroy and instantiate a new **Component** every time. * `$routerOnActivate` / `$routerOnReuse` : called by the **Router** at the end of a successful navigation. Only one of `$routerOnActivate` and `$routerOnReuse` will be called depending upon the result of a call to `$routerCanReuse`. * `$routerCanDeactivate` : called by the **Router** to determine if a **Component** can be removed as part of a navigation. * `$routerOnDeactivate` : called by the **Router** before destroying a **Component** as part of a navigation. We can also provide an **Injectable function** (`$routerCanActivate`) on the **Component Definition Object**, or as a static method on the **Component**, that will determine whether this **Component** is allowed to be activated. If any of the `$routerCan...` methods return false or a promise that resolves to false, the navigation will be cancelled. For our HeroList **Component** we want to load up the list of heroes when the **Component** is activated. So we implement the `$routerOnActivate()` instance method. ```js function HeroListComponent(heroService) { var $ctrl = this; this.$routerOnActivate = function() { return heroService.getHeroes().then(function(heroes) { $ctrl.heroes = heroes; }); } } ``` Running the application should update the browser's location to `/heroes` and display the list of heroes returned from the `heroService`. By returning a promise for the list of heroes from `$routerOnActivate()` we can delay the activation of the Route until the heroes have arrived successfully. This is similar to how a `resolve` works in {@link ngRoute}. ### Route Parameters **How do I access parameters for the current route?** The HeroDetailComponent displays details of an individual hero. The `id` of the hero to display is passed as part of the URL, for example **/heroes/12**. The **Router** parses the id from the URL when it recognizes the **Route Definition** and provides it to the **Component** as part of the parameters of the `$routerOnActivate()` hook. ```js function HeroDetailComponent(heroService) { var $ctrl = this; this.$routerOnActivate = function(next, previous) { // Get the hero identified by the route parameter var id = next.params.id; return heroService.getHero(id).then(function(hero) { $ctrl.hero = hero; }); }; ``` The `$routerOnActivate(next, previous)` hook receives two parameters, which hold the `next` and `previous` **Instruction** objects for the **Route** that is being activated. These parameters have a property called `params` which will hold the `id` parameter extracted from the URL by the **Router**. In this code it is used to identify a specific Hero to retrieve from the `heroService`. This hero is then attached to the **Component** so that it can be accessed in the template. ### Access to the Current Router **How do I get hold of the current router for my component?** Each component has its own Router. Unlike in the new Angular, we cannot use the dependency injector to get hold of a component's Router. We can only inject the `$rootRouter`. Instead we use the fact that the `ng-outlet` directive binds the current router to a `$router` attribute on our component. ```html ``` We can then specify a `bindings` property on our component definition to bind the current router to our component: ```js bindings: { $router: '<' } ``` This sets up a one-way binding of the current Router to the `$router` property of our Component. The binding is available once the component has been activated, and the `$routerOnActivate` hook is called. As you might know from reading the {@link guide/component component guide}, the binding is actually available by the time the `$onInit` hook is called, which is before the call to `$routerOnActivate`. ### HeroDetailComponent The `HeroDetailComponent` displays a form that allows the Hero to be modified. ```js .component('heroDetail', { template: '
    \n' + '

    "{{$ctrl.hero.name}}"

    \n' + '
    \n' + ' {{$ctrl.hero.id}}
    \n' + '
    \n' + ' \n' + ' \n' + '
    \n' + ' \n' + '
    \n', bindings: { $router: '<' }, controller: HeroDetailComponent }); ``` The template contains a button to navigate back to the HeroList. We could have styled an anchor to look like a button and used `ng-link="['HeroList']" but here we demonstrate programmatic navigation via the Router itself, which was made available by the binding in the **Component Definition Object**. ```js function HeroDetailComponent(heroService) { ... this.gotoHeroes = function() { this.$router.navigate(['HeroList']); }; ``` Here we are asking the Router to navigate to a route defined by `['HeroList']`. This is the same kind of array used by the `ng-link` directive. Other options for generating this navigation are: * manually create the URL and call `this.$router.navigateByUrl(url)` - this is discouraged because it couples the code of your component to the router URLs. * generate an Instruction for a route and navigate directly with this instruction. ```js var instruction = this.$router.generate(['HeroList']); this.$router.navigateByInstruction(instruction); ``` this form gives you the possibility of caching the instruction, but is more verbose. #### Absolute vs Relative Navigation **Why not use `$rootRouter` to do the navigation?** Instead of binding to the current **Router**, we can inject the `$rootRouter` into our **Component** and use that: `$rootRouter.navigate(...)`. The trouble with doing this is that navigation is always relative to the **Router**. So in order to navigate to the `HeroListComponent` with the `$rootRouter`, we would have to provide a complete path of Routes: `['App','Heroes','HeroList']`. ### Extra Parameters We can also pass additional optional parameters to routes, which get encoded into the URL and are again available to the `$routerOnActivate(next, previous)` hook. If we pass the current `id` from the HeroDetailComponent back to the HeroListComponent we can use it to highlight the previously selected hero. ```js this.gotoHeroes = function() { var heroId = this.hero && this.hero.id; this.$router.navigate(['HeroList', {id: heroId}]); }; ``` Then in the HeroList component we can extract this `id` in the `$routerOnActivate()` hook. ```js function HeroListComponent(heroService) { var selectedId = null; var $ctrl = this; this.$routerOnActivate = function(next) { heroService.getHeroes().then(function(heroes) { $ctrl.heroes = heroes; selectedId = next.params.id; }); }; this.isSelected = function(hero) { return (hero.id === selectedId); }; } ``` Finally, we can use this information to highlight the current hero in the template. ```html ``` ### Crisis Center Let's implement the Crisis Center feature, which displays a list if crises that need to be dealt with by a hero. The detailed crisis view has an additional feature where it blocks you from navigating if you have not saved changes to the crisis being edited. * A list of Crises that are happening: ![Crisis List View](img/guide/crisis-list.png) * A detailed view of a single Crisis: ![Crisis Detail View](img/guide/crisis-detail.png) ### Crisis Feature This feature is very similar to the Heroes feature. It contains the following **Components**: * CrisisService: contains method for getting a list of crises and an individual crisis. * CrisisListComponent: displays the list of crises, similar to HeroListComponent. * CrisisDetailComponent: displays a specific crisis CrisisService and CrisisListComponent are basically the same as HeroService and HeroListComponent respectively. ### Navigation Control Hooks **How do I prevent navigation from occurring?** Each **Component** can provide the `$canActivate` and `$routerCanDeactivate` **Lifecycle Hooks**. The `$routerCanDeactivate` hook is an instance method on the **Component**. The `$canActivate` hook is used as a static method defined on the **Component Definition Object**. The **Router** will call these hooks to control navigation from one **Route** to another. Each of these hooks can return a `boolean` or a Promise that will resolve to a `boolean`. During a navigation, some **Components** will become inactive and some will become active. Before the navigation can complete, all the **Components** must agree that they can be deactivated or activated, respectively. The **Router** will call the `$routerCanDeactivate` and `$canActivate` hooks, if they are provided. If any of the hooks resolve to `false` then the navigation is cancelled. #### Dialog Box Service We can implement a very simple dialog box that will prompt the user whether they are happy to lose changes they have made. The result of the prompt is a promise that can be used in a `$routerCanDeactivate` hook. ```js .service('dialogService', DialogService); function DialogService($q) { this.confirm = function(message) { return $q.resolve(window.confirm(message || 'Is it OK?')); }; } ``` ### CrisisDetailComponent We put the template into its own file by using a `templateUrl` property in the **Component Definition Object**: ```js .component('crisisDetail', { templateUrl: 'app/crisisDetail.html', bindings: { $router: '<' }, controller: CrisisDetailComponent }); ``` In the `$routerOnActivate` hook, we make a local copy of the `crisis.name` property to compare with the original value so that we can determine whether the name has changed. ```js this.$routerOnActivate = function(next) { // Get the crisis identified by the route parameter var id = next.params.id; crisisService.getCrisis(id).then(function(crisis) { if (crisis) { ctrl.editName = crisis.name; // Make a copy of the crisis name for editing ctrl.crisis = crisis; } else { // id not found ctrl.gotoCrises(); } }); }; ``` In the `$routerCanDeactivate` we check whether the name has been modified and ask whether the user wishes to discard the changes. ```js this.$routerCanDeactivate = function() { // Allow synchronous navigation (`true`) if no crisis or the crisis is unchanged. if (!this.crisis || this.crisis.name === this.editName) { return true; } // Otherwise ask the user with the dialog service and return its // promise which resolves to true or false when the user decides return dialogService.confirm('Discard changes?'); }; ``` You can test this check by navigating to a crisis detail page, modifying the name and then either pressing the browser's back button to navigate back to the previous page, or by clicking on one of the links to the Crisis Center or Heroes features. The Save and Cancel buttons update the `editName` and/or `crisis.name` properties before navigating to prevent the `$routerCanDeactivate` hook from displaying the dialog box. ## Summary This guide has given an overview of the features of the Component Router and how to implement a simple application. angular.js-1.7.9/docs/content/guide/component.ngdoc000066400000000000000000000446111356472325200223300ustar00rootroot00000000000000@ngdoc overview @name Components @sortOrder 305 @description # Understanding Components In AngularJS, a Component is a special kind of {@link guide/directive directive} that uses a simpler configuration which is suitable for a component-based application structure. This makes it easier to write an app in a way that's similar to using Web Components or using the new Angular's style of application architecture. Advantages of Components: - simpler configuration than plain directives - promote sane defaults and best practices - optimized for component-based architecture - writing component directives will make it easier to upgrade to Angular When not to use Components: - for directives that need to perform actions in compile and pre-link functions, because they aren't available - when you need advanced directive definition options like priority, terminal, multi-element - when you want a directive that is triggered by an attribute or CSS class, rather than an element ## Creating and configuring a Component Components can be registered using the {@link ng.$compileProvider#component `.component()`} method of an AngularJS module (returned by {@link module `angular.module()`}). The method takes two arguments: * The name of the Component (as string). * The Component config object. (Note that, unlike the `.directive()` method, this method does **not** take a factory function.) angular.module('heroApp', []).controller('MainCtrl', function MainCtrl() { this.hero = { name: 'Spawn' }; }); angular.module('heroApp').component('heroDetail', { templateUrl: 'heroDetail.html', bindings: { hero: '=' } });
    Hero
    Name: {{$ctrl.hero.name}}
    It's also possible to add components via {@link $compileProvider#component} in a module's config phase. ### Comparison between Directive definition and Component definition | | Directive | Component | |-------------------|----------------------|-----------------| | bindings | No | Yes (binds to controller) | | bindToController | Yes (default: false) | No (use bindings instead) | | compile function | Yes | No | | controller | Yes | Yes (default `function() {}`) | | controllerAs | Yes (default: false) | Yes (default: `$ctrl`) | | link functions | Yes | No | | multiElement | Yes | No | | priority | Yes | No | | replace | Yes (deprecated) | No | | require | Yes | Yes | | restrict | Yes | No (restricted to elements only) | | scope | Yes (default: false) | No (scope is always isolate) | | template | Yes | Yes, injectable | | templateNamespace | Yes | No | | templateUrl | Yes | Yes, injectable | | terminal | Yes | No | | transclude | Yes (default: false) | Yes (default: false) | ## Component-based application architecture As already mentioned, the component helper makes it easier to structure your application with a component-based architecture. But what makes a component beyond the options that the component helper has? - **Components only control their own View and Data:** Components should never modify any data or DOM that is out of their own scope. Normally, in AngularJS it is possible to modify data anywhere in the application through scope inheritance and watches. This is practical, but can also lead to problems when it is not clear which part of the application is responsible for modifying the data. That is why component directives use an isolate scope, so a whole class of scope manipulation is not possible. - **Components have a well-defined public API - Inputs and Outputs:** However, scope isolation only goes so far, because AngularJS uses two-way binding. So if you pass an object to a component like this - `bindings: {item: '='}`, and modify one of its properties, the change will be reflected in the parent component. For components however, only the component that owns the data should modify it, to make it easy to reason about what data is changed, and when. For that reason, components should follow a few simple conventions: - Inputs should be using `<` and `@` bindings. The `<` symbol denotes {@link $compile#-scope- one-way bindings} which are available since 1.5. The difference to `=` is that the bound properties in the component scope are not watched, which means if you assign a new value to the property in the component scope, it will not update the parent scope. Note however, that both parent and component scope reference the same object, so if you are changing object properties or array elements in the component, the parent will still reflect that change. The general rule should therefore be to never change an object or array property in the component scope. `@` bindings can be used when the input is a string, especially when the value of the binding doesn't change. ```js bindings: { hero: '<', comment: '@' } ``` - Outputs are realized with `&` bindings, which function as callbacks to component events. ```js bindings: { onDelete: '&', onUpdate: '&' } ``` - Instead of manipulating Input Data, the component calls the correct Output Event with the changed data. For a deletion, that means the component doesn't delete the `hero` itself, but sends it back to the owner component via the correct event. ```html
    ``` - That way, the parent component can decide what to do with the event (e.g. delete an item or update the properties) ```js ctrl.deleteHero(hero) { $http.delete(...).then(function() { var idx = ctrl.list.indexOf(hero); if (idx >= 0) { ctrl.list.splice(idx, 1); } }); } ``` - **Components have a well-defined lifecycle:** Each component can implement "lifecycle hooks". These are methods that will be called at certain points in the life of the component. The following hook methods can be implemented: * `$onInit()` - Called on each controller after all the controllers on an element have been constructed and had their bindings initialized (and before the pre & post linking functions for the directives on this element). This is a good place to put initialization code for your controller. * `$onChanges(changesObj)` - Called whenever one-way bindings are updated. The `changesObj` is a hash whose keys are the names of the bound properties that have changed, and the values are an object of the form `{ currentValue, previousValue, isFirstChange() }`. Use this hook to trigger updates within a component such as cloning the bound value to prevent accidental mutation of the outer value. * `$doCheck()` - Called on each turn of the digest cycle. Provides an opportunity to detect and act on changes. Any actions that you wish to take in response to the changes that you detect must be invoked from this hook; implementing this has no effect on when `$onChanges` is called. For example, this hook could be useful if you wish to perform a deep equality check, or to check a Date object, changes to which would not be detected by AngularJS's change detector and thus not trigger `$onChanges`. This hook is invoked with no arguments; if detecting changes, you must store the previous value(s) for comparison to the current values. * `$onDestroy()` - Called on a controller when its containing scope is destroyed. Use this hook for releasing external resources, watches and event handlers. * `$postLink()` - Called after this controller's element and its children have been linked. Similar to the post-link function this hook can be used to set up DOM event handlers and do direct DOM manipulation. Note that child elements that contain `templateUrl` directives will not have been compiled and linked since they are waiting for their template to load asynchronously and their own compilation and linking has been suspended until that occurs. This hook can be considered analogous to the `ngAfterViewInit` and `ngAfterContentInit` hooks in Angular. Since the compilation process is rather different in AngularJS there is no direct mapping and care should be taken when upgrading. By implementing these methods, your component can hook into its lifecycle. - **An application is a tree of components:** Ideally, the whole application should be a tree of components that implement clearly defined inputs and outputs, and minimize two-way data binding. That way, it's easier to predict when data changes and what the state of a component is. ## Example of a component tree The following example expands on the simple component example and incorporates the concepts we introduced above: Instead of an ngController, we now have a heroList component that holds the data of different heroes, and creates a heroDetail for each of them. The heroDetail component now contains new functionality: - a delete button that calls the bound `onDelete` function of the heroList component - an input to change the hero location, in the form of a reusable editableField component. Instead of manipulating the hero object itself, it sends a changeset upwards to the heroDetail, which sends it upwards to the heroList component, which updates the original data. angular.module('heroApp', []); function HeroListController($scope, $element, $attrs) { var ctrl = this; // This would be loaded by $http etc. ctrl.list = [ { name: 'Superman', location: '' }, { name: 'Batman', location: 'Wayne Manor' } ]; ctrl.updateHero = function(hero, prop, value) { hero[prop] = value; }; ctrl.deleteHero = function(hero) { var idx = ctrl.list.indexOf(hero); if (idx >= 0) { ctrl.list.splice(idx, 1); } }; } angular.module('heroApp').component('heroList', { templateUrl: 'heroList.html', controller: HeroListController }); function HeroDetailController() { var ctrl = this; ctrl.delete = function() { ctrl.onDelete({hero: ctrl.hero}); }; ctrl.update = function(prop, value) { ctrl.onUpdate({hero: ctrl.hero, prop: prop, value: value}); }; } angular.module('heroApp').component('heroDetail', { templateUrl: 'heroDetail.html', controller: HeroDetailController, bindings: { hero: '<', onDelete: '&', onUpdate: '&' } }); function EditableFieldController($scope, $element, $attrs) { var ctrl = this; ctrl.editMode = false; ctrl.handleModeChange = function() { if (ctrl.editMode) { ctrl.onUpdate({value: ctrl.fieldValue}); ctrl.fieldValueCopy = ctrl.fieldValue; } ctrl.editMode = !ctrl.editMode; }; ctrl.reset = function() { ctrl.fieldValue = ctrl.fieldValueCopy; }; ctrl.$onInit = function() { // Make a copy of the initial value to be able to reset it later ctrl.fieldValueCopy = ctrl.fieldValue; // Set a default fieldType if (!ctrl.fieldType) { ctrl.fieldType = 'text'; } }; } angular.module('heroApp').component('editableField', { templateUrl: 'editableField.html', controller: EditableFieldController, bindings: { fieldValue: '<', fieldType: '@?', onUpdate: '&' } }); Heroes

    Name: {{$ctrl.hero.name}}
    Location:
    {{$ctrl.fieldValue}}
    ## Components as route templates Components are also useful as route templates (e.g. when using {@link ngRoute ngRoute}). In a component-based application, every view is a component: ```js var myMod = angular.module('myMod', ['ngRoute']); myMod.component('home', { template: '

    Home

    Hello, {{ $ctrl.user.name }} !

    ', controller: function() { this.user = {name: 'world'}; } }); myMod.config(function($routeProvider) { $routeProvider.when('/', { template: '' }); }); ```
    When using {@link ngRoute.$routeProvider $routeProvider}, you can often avoid some boilerplate, by passing the resolved route dependencies directly to the component. Since 1.5, ngRoute automatically assigns the resolves to the route scope property `$resolve` (you can also configure the property name via `resolveAs`). When using components, you can take advantage of this and pass resolves directly into your component without creating an extra route controller: ```js var myMod = angular.module('myMod', ['ngRoute']); myMod.component('home', { template: '

    Home

    Hello, {{ $ctrl.user.name }} !

    ', bindings: { user: '<' } }); myMod.config(function($routeProvider) { $routeProvider.when('/', { template: '', resolve: { user: function($http) { return $http.get('...'); } } }); }); ``` ## Intercomponent Communication Directives can require the controllers of other directives to enable communication between each other. This can be achieved in a component by providing an object mapping for the `require` property. The object keys specify the property names under which the required controllers (object values) will be bound to the requiring component's controller.
    Note that the required controllers will not be available during the instantiation of the controller, but they are guaranteed to be available just before the `$onInit` method is executed!
    Here is a tab pane example built from components: angular.module('docsTabsExample', []) .component('myTabs', { transclude: true, controller: function MyTabsController() { var panes = this.panes = []; this.select = function(pane) { angular.forEach(panes, function(pane) { pane.selected = false; }); pane.selected = true; }; this.addPane = function(pane) { if (panes.length === 0) { this.select(pane); } panes.push(pane); }; }, templateUrl: 'my-tabs.html' }) .component('myPane', { transclude: true, require: { tabsCtrl: '^myTabs' }, bindings: { title: '@' }, controller: function() { this.$onInit = function() { this.tabsCtrl.addPane(this); console.log(this); }; }, templateUrl: 'my-pane.html' });

    Hello

    Lorem ipsum dolor sit amet

    World

    Mauris elementum elementum enim at suscipit.

    counter: {{i || 0}}

    ## Unit-testing Component Controllers The easiest way to unit-test a component controller is by using the {@link ngMock.$componentController $componentController} that is included in {@link ngMock}. The advantage of this method is that you do not have to create any DOM elements. The following example shows how to do this for the `heroDetail` component from above. The examples use the [Jasmine](http://jasmine.github.io/) testing framework. **Controller Test:** ```js describe('HeroDetailController', function() { var $componentController; beforeEach(module('heroApp')); beforeEach(inject(function(_$componentController_) { $componentController = _$componentController_; })); it('should call the `onDelete` binding, when deleting the hero', function() { var onDeleteSpy = jasmine.createSpy('onDelete'); var bindings = {hero: {}, onDelete: onDeleteSpy}; var ctrl = $componentController('heroDetail', null, bindings); ctrl.delete(); expect(onDeleteSpy).toHaveBeenCalledWith({hero: ctrl.hero}); }); it('should call the `onUpdate` binding, when updating a property', function() { var onUpdateSpy = jasmine.createSpy('onUpdate'); var bindings = {hero: {}, onUpdate: onUpdateSpy}; var ctrl = $componentController('heroDetail', null, bindings); ctrl.update('foo', 'bar'); expect(onUpdateSpy).toHaveBeenCalledWith({ hero: ctrl.hero, prop: 'foo', value: 'bar' }); }); }); ``` angular.js-1.7.9/docs/content/guide/concepts.ngdoc000066400000000000000000000433541356472325200221470ustar00rootroot00000000000000@ngdoc overview @name Conceptual Overview @sortOrder 200 @description # Conceptual Overview This section briefly touches on all of the important parts of AngularJS using a simple example. For a more in-depth explanation, see the {@link tutorial/ tutorial}. | Concept | Description | |--------------------------------------------|--------------------------------------------------------------------------| |{@link concepts#template Template} | HTML with additional markup | |{@link concepts#directive Directives} | extend HTML with custom attributes and elements | |{@link concepts#model Model} | the data shown to the user in the view and with which the user interacts | |{@link concepts#scope Scope} | context where the model is stored so that controllers, directives and expressions can access it | |{@link concepts#expression Expressions} | access variables and functions from the scope | |{@link concepts#compiler Compiler} | parses the template and instantiates directives and expressions | |{@link concepts#filter Filter} | formats the value of an expression for display to the user | |{@link concepts#view View} | what the user sees (the DOM) | |{@link concepts#databinding Data Binding} | sync data between the model and the view | |{@link concepts#controller Controller} | the business logic behind views | |{@link concepts#di Dependency Injection} | Creates and wires objects and functions | |{@link concepts#injector Injector} | dependency injection container | |{@link concepts#module Module} | a container for the different parts of an app including controllers, services, filters, directives which configures the Injector | |{@link concepts#service Service} | reusable business logic independent of views | ## A first example: Data binding In the following example we will build a form to calculate the costs of an invoice in different currencies. Let's start with input fields for quantity and cost whose values are multiplied to produce the total of the invoice:
    Invoice:
    Quantity:
    Costs:
    Total: {{qty * cost | currency}}
    Try out the Live Preview above, and then let's walk through the example and describe what's going on. This looks like normal HTML, with some new markup. In AngularJS, a file like this is called a {@link templates template}. When AngularJS starts your application, it parses and processes this new markup from the template using the {@link compiler compiler}. The loaded, transformed and rendered DOM is then called the *view*. The first kind of new markup are the {@link directive directives}. They apply special behavior to attributes or elements in the HTML. In the example above we use the {@link ng.directive:ngApp `ng-app`} attribute, which is linked to a directive that automatically initializes our application. AngularJS also defines a directive for the {@link ng.directive:input `input`} element that adds extra behavior to the element. The {@link ng.directive:ngModel `ng-model`} directive stores/updates the value of the input field into/from a variable.
    **Custom directives to access the DOM**: In AngularJS, the only place where an application should access the DOM is within directives. This is important because artifacts that access the DOM are hard to test. If you need to access the DOM directly you should write a custom directive for this. The {@link directive directives guide} explains how to do this.
    The second kind of new markup are the double curly braces `{{ expression | filter }}`: When the compiler encounters this markup, it will replace it with the evaluated value of the markup. An {@link expression expression} in a template is a JavaScript-like code snippet that allows AngularJS to read and write variables. Note that those variables are not global variables. Just like variables in a JavaScript function live in a scope, AngularJS provides a {@link scope scope} for the variables accessible to expressions. The values that are stored in variables on the scope are referred to as the *model* in the rest of the documentation. Applied to the example above, the markup directs AngularJS to "take the data we got from the input widgets and multiply them together". The example above also contains a {@link guide/filter filter}. A filter formats the value of an expression for display to the user. In the example above, the filter {@link ng.filter:currency `currency`} formats a number into an output that looks like money. The important thing in the example is that AngularJS provides _live_ bindings: Whenever the input values change, the value of the expressions are automatically recalculated and the DOM is updated with their values. The concept behind this is {@link databinding two-way data binding}. ## Adding UI logic: Controllers Let's add some more logic to the example that allows us to enter and calculate the costs in different currencies and also pay the invoice. angular.module('invoice1', []) .controller('InvoiceController', function InvoiceController() { this.qty = 1; this.cost = 2; this.inCurr = 'EUR'; this.currencies = ['USD', 'EUR', 'CNY']; this.usdToForeignRates = { USD: 1, EUR: 0.74, CNY: 6.09 }; this.total = function total(outCurr) { return this.convertCurrency(this.qty * this.cost, this.inCurr, outCurr); }; this.convertCurrency = function convertCurrency(amount, inCurr, outCurr) { return amount * this.usdToForeignRates[outCurr] / this.usdToForeignRates[inCurr]; }; this.pay = function pay() { window.alert('Thanks!'); }; });
    Invoice:
    Quantity:
    Costs:
    Total: {{invoice.total(c) | currency:c}}
    What changed? First, there is a new JavaScript file that contains a {@link controller controller}. More accurately, the file specifies a constructor function that will be used to create the actual controller instance. The purpose of controllers is to expose variables and functionality to expressions and directives. Besides the new file that contains the controller code, we also added an {@link ng.directive:ngController `ng-controller`} directive to the HTML. This directive tells AngularJS that the new `InvoiceController` is responsible for the element with the directive and all of the element's children. The syntax `InvoiceController as invoice` tells AngularJS to instantiate the controller and save it in the variable `invoice` in the current scope. We also changed all expressions in the page to read and write variables within that controller instance by prefixing them with `invoice.` . The possible currencies are defined in the controller and added to the template using {@link ng.directive:ngRepeat `ng-repeat`}. As the controller contains a `total` function we are also able to bind the result of that function to the DOM using `{{ invoice.total(...) }}`. Again, this binding is live, i.e. the DOM will be automatically updated whenever the result of the function changes. The button to pay the invoice uses the directive {@link ng.directive:ngClick `ngClick`}. This will evaluate the corresponding expression whenever the button is clicked. In the new JavaScript file we are also creating a {@link concepts#module module} at which we register the controller. We will talk about modules in the next section. The following graphic shows how everything works together after we introduced the controller: ## View-independent business logic: Services Right now, the `InvoiceController` contains all logic of our example. When the application grows it is a good practice to move view-independent logic from the controller into a {@link services service}, so it can be reused by other parts of the application as well. Later on, we could also change that service to load the exchange rates from the web, e.g. by calling the [exchangeratesapi.io](https://exchangeratesapi.io) exchange rate API, without changing the controller. Let's refactor our example and move the currency conversion into a service in another file: angular.module('finance2', []) .factory('currencyConverter', function() { var currencies = ['USD', 'EUR', 'CNY']; var usdToForeignRates = { USD: 1, EUR: 0.74, CNY: 6.09 }; var convert = function(amount, inCurr, outCurr) { return amount * usdToForeignRates[outCurr] / usdToForeignRates[inCurr]; }; return { currencies: currencies, convert: convert }; }); angular.module('invoice2', ['finance2']) .controller('InvoiceController', ['currencyConverter', function InvoiceController(currencyConverter) { this.qty = 1; this.cost = 2; this.inCurr = 'EUR'; this.currencies = currencyConverter.currencies; this.total = function total(outCurr) { return currencyConverter.convert(this.qty * this.cost, this.inCurr, outCurr); }; this.pay = function pay() { window.alert('Thanks!'); }; }]);
    Invoice:
    Quantity:
    Costs:
    Total: {{invoice.total(c) | currency:c}}
    What changed? We moved the `convertCurrency` function and the definition of the existing currencies into the new file `finance2.js`. But how does the controller get a hold of the now separated function? This is where {@link di Dependency Injection} comes into play. Dependency Injection (DI) is a software design pattern that deals with how objects and functions get created and how they get a hold of their dependencies. Everything within AngularJS (directives, filters, controllers, services, ...) is created and wired using dependency injection. Within AngularJS, the DI container is called the {@link di injector}. To use DI, there needs to be a place where all the things that should work together are registered. In AngularJS, this is the purpose of the {@link module modules}. When AngularJS starts, it will use the configuration of the module with the name defined by the `ng-app` directive, including the configuration of all modules that this module depends on. In the example above: The template contains the directive `ng-app="invoice2"`. This tells AngularJS to use the `invoice2` module as the main module for the application. The code snippet `angular.module('invoice2', ['finance2'])` specifies that the `invoice2` module depends on the `finance2` module. By this, AngularJS uses the `InvoiceController` as well as the `currencyConverter` service. Now that AngularJS knows of all the parts of the application, it needs to create them. In the previous section we saw that controllers are created using a constructor function. For services, there are multiple ways to specify how they are created (see the {@link services service guide}). In the example above, we are using an anonymous function as the factory function for the `currencyConverter` service. This function should return the `currencyConverter` service instance. Back to the initial question: How does the `InvoiceController` get a reference to the `currencyConverter` function? In AngularJS, this is done by simply defining arguments on the constructor function. With this, the injector is able to create the objects in the right order and pass the previously created objects into the factories of the objects that depend on them. In our example, the `InvoiceController` has an argument named `currencyConverter`. By this, AngularJS knows about the dependency between the controller and the service and calls the controller with the service instance as argument. The last thing that changed in the example between the previous section and this section is that we now pass an array to the `module.controller` function, instead of a plain function. The array first contains the names of the service dependencies that the controller needs. The last entry in the array is the controller constructor function. AngularJS uses this array syntax to define the dependencies so that the DI also works after minifying the code, which will most probably rename the argument name of the controller constructor function to something shorter like `a`. ## Accessing the backend Let's finish our example by fetching the exchange rates from the [exchangeratesapi.io](https://exchangeratesapi.io) exchange rate API. The following example shows how this is done with AngularJS: angular.module('invoice3', ['finance3']) .controller('InvoiceController', ['currencyConverter', function InvoiceController(currencyConverter) { this.qty = 1; this.cost = 2; this.inCurr = 'EUR'; this.currencies = currencyConverter.currencies; this.total = function total(outCurr) { return currencyConverter.convert(this.qty * this.cost, this.inCurr, outCurr); }; this.pay = function pay() { window.alert('Thanks!'); }; }]); angular.module('finance3', []) .factory('currencyConverter', ['$http', function($http) { var currencies = ['USD', 'EUR', 'CNY']; var usdToForeignRates = {}; var convert = function(amount, inCurr, outCurr) { return amount * usdToForeignRates[outCurr] / usdToForeignRates[inCurr]; }; var refresh = function() { var url = 'https://api.exchangeratesapi.io/latest?base=USD&symbols=' + currencies.join(","); return $http.get(url).then(function(response) { usdToForeignRates = response.data.rates; usdToForeignRates['USD'] = 1; }); }; refresh(); return { currencies: currencies, convert: convert }; }]);
    Invoice:
    Quantity:
    Costs:
    Total: {{invoice.total(c) | currency:c}}
    What changed? Our `currencyConverter` service of the `finance` module now uses the {@link ng.$http `$http`}, a built-in service provided by AngularJS for accessing a server backend. `$http` is a wrapper around [`XMLHttpRequest`](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest) and [JSONP](http://en.wikipedia.org/wiki/JSONP) transports. angular.js-1.7.9/docs/content/guide/controller.ngdoc000066400000000000000000000303001356472325200224770ustar00rootroot00000000000000@ngdoc overview @name Controllers @sortOrder 220 @description # Understanding Controllers In AngularJS, a Controller is defined by a JavaScript **constructor function** that is used to augment the {@link scope AngularJS Scope}. Controllers can be attached to the DOM in different ways. For each of them, AngularJS will instantiate a new Controller object, using the specified Controller's **constructor function**: - the {@link ng.directive:ngController ngController} directive. A new **child scope** will be created and made available as an injectable parameter to the Controller's constructor function as `$scope`. - a route controller in a {@link ngRoute.$routeProvider $route definition}. - the controller of a {@link guide/directive regular directive}, or a {@link guide/component component directive}. If the controller has been attached using the `controller as` syntax then the controller instance will be assigned to a property on the scope. Use controllers to: - Set up the initial state of the `$scope` object. - Add behavior to the `$scope` object. Do not use controllers to: - Manipulate DOM — Controllers should contain only business logic. Putting any presentation logic into Controllers significantly affects its testability. AngularJS has {@link databinding databinding} for most cases and {@link guide/directive directives} to encapsulate manual DOM manipulation. - Format input — Use {@link forms AngularJS form controls} instead. - Filter output — Use {@link guide/filter AngularJS filters} instead. - Share code or state across controllers — Use {@link services AngularJS services} instead. - Manage the life-cycle of other components (for example, to create service instances). In general, a Controller shouldn't try to do too much. It should contain only the business logic needed for a single view. The most common way to keep Controllers slim is by encapsulating work that doesn't belong to controllers into services and then using these services in Controllers via dependency injection. This is discussed in the {@link di Dependency Injection} and {@link services Services} sections of this guide. ## Setting up the initial state of a `$scope` object Typically, when you create an application you need to set up the initial state for the AngularJS `$scope`. You set up the initial state of a scope by attaching properties to the `$scope` object. The properties contain the **view model** (the model that will be presented by the view). All the `$scope` properties will be available to the {@link templates template} at the point in the DOM where the Controller is registered. The following example demonstrates creating a `GreetingController`, which attaches a `greeting` property containing the string `'Hola!'` to the `$scope`: ```js var myApp = angular.module('myApp',[]); myApp.controller('GreetingController', ['$scope', function($scope) { $scope.greeting = 'Hola!'; }]); ``` We create an {@link module AngularJS Module}, `myApp`, for our application. Then we add the controller's constructor function to the module using the `.controller()` method. This keeps the controller's constructor function out of the global scope.
    We have used an **inline injection annotation** to explicitly specify the dependency of the Controller on the `$scope` service provided by AngularJS. See the guide on {@link guide/di Dependency Injection} for more information.
    We attach our controller to the DOM using the `ng-controller` directive. The `greeting` property can now be data-bound to the template: ```js
    {{ greeting }}
    ``` ## Adding Behavior to a Scope Object In order to react to events or execute computation in the view we must provide behavior to the scope. We add behavior to the scope by attaching methods to the `$scope` object. These methods are then available to be called from the template/view. The following example uses a Controller to add a method, which doubles a number, to the scope: ```js var myApp = angular.module('myApp',[]); myApp.controller('DoubleController', ['$scope', function($scope) { $scope.double = function(value) { return value * 2; }; }]); ``` Once the Controller has been attached to the DOM, the `double` method can be invoked in an AngularJS expression in the template: ```js
    Two times equals {{ double(num) }}
    ``` As discussed in the {@link concepts Concepts} section of this guide, any objects (or primitives) assigned to the scope become model properties. Any methods assigned to the scope are available in the template/view, and can be invoked via AngularJS expressions and `ng` event handler directives (e.g. {@link ng.directive:ngClick ngClick}). ## Simple Spicy Controller Example To illustrate further how Controller components work in AngularJS, let's create a little app with the following components: - A {@link templates template} with two buttons and a simple message - A model consisting of a string named `spice` - A Controller with two functions that set the value of `spice` The message in our template contains a binding to the `spice` model which, by default, is set to the string "very". Depending on which button is clicked, the `spice` model is set to `chili` or `jalapeño`, and the message is automatically updated by data-binding.

    The food is {{spice}} spicy!

    var myApp = angular.module('spicyApp1', []); myApp.controller('SpicyController', ['$scope', function($scope) { $scope.spice = 'very'; $scope.chiliSpicy = function() { $scope.spice = 'chili'; }; $scope.jalapenoSpicy = function() { $scope.spice = 'jalapeño'; }; }]);
    Things to notice in the example above: - The `ng-controller` directive is used to (implicitly) create a scope for our template, and the scope is augmented (managed) by the `SpicyController` Controller. - `SpicyController` is just a plain JavaScript function. As an (optional) naming convention the name starts with capital letter and ends with "Controller". - Assigning a property to `$scope` creates or updates the model. - Controller methods can be created through direct assignment to scope (see the `chiliSpicy` method) - The Controller methods and properties are available in the template (for both the `
    ` element and its children). ## Spicy Arguments Example Controller methods can also take arguments, as demonstrated in the following variation of the previous example.

    The food is {{spice}} spicy!

    var myApp = angular.module('spicyApp2', []); myApp.controller('SpicyController', ['$scope', function($scope) { $scope.customSpice = 'wasabi'; $scope.spice = 'very'; $scope.spicy = function(spice) { $scope.spice = spice; }; }]);
    Notice that the `SpicyController` Controller now defines just one method called `spicy`, which takes one argument called `spice`. The template then refers to this Controller method and passes in a string constant `'chili'` in the binding for the first button and a model property `customSpice` (bound to an input box) in the second button. ## Scope Inheritance Example It is common to attach Controllers at different levels of the DOM hierarchy. Since the {@link ng.directive:ngController ng-controller} directive creates a new child scope, we get a hierarchy of scopes that inherit from each other. The `$scope` that each Controller receives will have access to properties and methods defined by Controllers higher up the hierarchy. See [Understanding Scopes](https://github.com/angular/angular.js/wiki/Understanding-Scopes) for more information about scope inheritance.

    Good {{timeOfDay}}, {{name}}!

    Good {{timeOfDay}}, {{name}}!

    Good {{timeOfDay}}, {{name}}!

    div.spicy div { padding: 10px; border: solid 2px blue; } var myApp = angular.module('scopeInheritance', []); myApp.controller('MainController', ['$scope', function($scope) { $scope.timeOfDay = 'morning'; $scope.name = 'Nikki'; }]); myApp.controller('ChildController', ['$scope', function($scope) { $scope.name = 'Mattie'; }]); myApp.controller('GrandChildController', ['$scope', function($scope) { $scope.timeOfDay = 'evening'; $scope.name = 'Gingerbread Baby'; }]);
    Notice how we nested three `ng-controller` directives in our template. This will result in four scopes being created for our view: - The root scope - The `MainController` scope, which contains `timeOfDay` and `name` properties - The `ChildController` scope, which inherits the `timeOfDay` property but overrides (shadows) the `name` property from the previous scope - The `GrandChildController` scope, which overrides (shadows) both the `timeOfDay` property defined in `MainController` and the `name` property defined in `ChildController` Inheritance works with methods in the same way as it does with properties. So in our previous examples, all of the properties could be replaced with methods that return string values. ## Testing Controllers Although there are many ways to test a Controller, one of the best conventions, shown below, involves injecting the {@link ng.$rootScope $rootScope} and {@link ng.$controller $controller}: **Controller Definition:** ```js var myApp = angular.module('myApp',[]); myApp.controller('MyController', function($scope) { $scope.spices = [{"name":"pasilla", "spiciness":"mild"}, {"name":"jalapeno", "spiciness":"hot hot hot!"}, {"name":"habanero", "spiciness":"LAVA HOT!!"}]; $scope.spice = "habanero"; }); ``` **Controller Test:** ```js describe('myController function', function() { describe('myController', function() { var $scope; beforeEach(module('myApp')); beforeEach(inject(function($rootScope, $controller) { $scope = $rootScope.$new(); $controller('MyController', {$scope: $scope}); })); it('should create "spices" model with 3 spices', function() { expect($scope.spices.length).toBe(3); }); it('should set the default value of spice', function() { expect($scope.spice).toBe('habanero'); }); }); }); ``` If you need to test a nested Controller you must create the same scope hierarchy in your test that exists in the DOM: ```js describe('state', function() { var mainScope, childScope, grandChildScope; beforeEach(module('myApp')); beforeEach(inject(function($rootScope, $controller) { mainScope = $rootScope.$new(); $controller('MainController', {$scope: mainScope}); childScope = mainScope.$new(); $controller('ChildController', {$scope: childScope}); grandChildScope = childScope.$new(); $controller('GrandChildController', {$scope: grandChildScope}); })); it('should have over and selected', function() { expect(mainScope.timeOfDay).toBe('morning'); expect(mainScope.name).toBe('Nikki'); expect(childScope.timeOfDay).toBe('morning'); expect(childScope.name).toBe('Mattie'); expect(grandChildScope.timeOfDay).toBe('evening'); expect(grandChildScope.name).toBe('Gingerbread Baby'); }); }); ``` angular.js-1.7.9/docs/content/guide/css-styling.ngdoc000066400000000000000000000032061356472325200226000ustar00rootroot00000000000000@ngdoc overview @name Working With CSS @sortOrder 510 @description AngularJS sets these CSS classes. It is up to your application to provide useful styling. # CSS classes used by AngularJS * `ng-scope` - **Usage:** AngularJS applies this class to any element for which a new {@link $rootScope scope} is defined. (see {@link guide/scope scope} guide for more information about scopes) * `ng-isolate-scope` - **Usage:** AngularJS applies this class to any element for which a new {@link guide/directive#isolating-the-scope-of-a-directive isolate scope} is defined. * `ng-binding` - **Usage:** AngularJS applies this class to any element that is attached to a data binding, via `ng-bind` or `{{}}` curly braces, for example. (see {@link guide/databinding databinding} guide) * `ng-invalid`, `ng-valid` - **Usage:** AngularJS applies this class to a form control widget element if that element's input does not pass validation. (see {@link ng.directive:input input} directive) * `ng-pristine`, `ng-dirty` - **Usage:** AngularJS {@link ng.directive:ngModel ngModel} directive applies `ng-pristine` class to a new form control widget which did not have user interaction. Once the user interacts with the form control, the class is changed to `ng-dirty`. * `ng-touched`, `ng-untouched` - **Usage:** AngularJS {@link ng.directive:ngModel ngModel} directive applies `ng-untouched` class to a new form control widget which has not been blurred. Once the user blurs the form control, the class is changed to `ng-touched`. ## Related Topics * {@link guide/templates AngularJS Templates} * {@link guide/forms AngularJS Forms} angular.js-1.7.9/docs/content/guide/databinding.ngdoc000066400000000000000000000037011356472325200225650ustar00rootroot00000000000000@ngdoc overview @name Data Binding @sortOrder 210 @description # Data Binding Data-binding in AngularJS apps is the automatic synchronization of data between the model and view components. The way that AngularJS implements data-binding lets you treat the model as the single-source-of-truth in your application. The view is a projection of the model at all times. When the model changes, the view reflects the change, and vice versa. ## Data Binding in Classical Template Systems
    Most templating systems bind data in only one direction: they merge template and model components together into a view. After the merge occurs, changes to the model or related sections of the view are NOT automatically reflected in the view. Worse, any changes that the user makes to the view are not reflected in the model. This means that the developer has to write code that constantly syncs the view with the model and the model with the view. ## Data Binding in AngularJS Templates
    AngularJS templates work differently. First the template (which is the uncompiled HTML along with any additional markup or directives) is compiled on the browser. The compilation step produces a live view. Any changes to the view are immediately reflected in the model, and any changes in the model are propagated to the view. The model is the single-source-of-truth for the application state, greatly simplifying the programming model for the developer. You can think of the view as simply an instant projection of your model. Because the view is just a projection of the model, the controller is completely separated from the view and unaware of it. This makes testing a snap because it is easy to test your controller in isolation without the view and the related DOM/browser dependency. ## Related Topics * {@link scope AngularJS Scopes} * {@link templates AngularJS Templates} angular.js-1.7.9/docs/content/guide/decorators.ngdoc000066400000000000000000000377671356472325200225110ustar00rootroot00000000000000@ngdoc overview @name Decorators @sortOrder 345 @description # Decorators in AngularJS
    **NOTE:** This guide is targeted towards developers who are already familiar with AngularJS basics. If you're just getting started, we recommend the {@link tutorial/ tutorial} first.
    ## What are decorators? Decorators are a design pattern that is used to separate modification or *decoration* of a class without modifying the original source code. In AngularJS, decorators are functions that allow a service, directive or filter to be modified prior to its usage. ## How to use decorators There are two ways to register decorators - `$provide.decorator`, and - `module.decorator` Each provide access to a `$delegate`, which is the instantiated service/directive/filter, prior to being passed to the service that required it. ### $provide.decorator The {@link api/auto/service/$provide#decorator decorator function} allows access to a $delegate of the service once it has been instantiated. For example: ```js angular.module('myApp', []) .config([ '$provide', function($provide) { $provide.decorator('$log', [ '$delegate', function $logDecorator($delegate) { var originalWarn = $delegate.warn; $delegate.warn = function decoratedWarn(msg) { msg = 'Decorated Warn: ' + msg; originalWarn.apply($delegate, arguments); }; return $delegate; } ]); }]); ``` After the `$log` service has been instantiated the decorator is fired. The decorator function has a `$delegate` object injected to provide access to the service that matches the selector in the decorator. This `$delegate` will be the service you are decorating. The return value of the function *provided to the decorator* will take place of the service, directive, or filter being decorated.
    The `$delegate` may be either modified or completely replaced. Given a service `myService` with a method `someFn`, the following could all be viable solutions: #### Completely Replace the $delegate ```js angular.module('myApp', []) .config([ '$provide', function($provide) { $provide.decorator('myService', [ '$delegate', function myServiceDecorator($delegate) { var myDecoratedService = { // new service object to replace myService }; return myDecoratedService; } ]); }]); ``` #### Patch the $delegate ```js angular.module('myApp', []) .config([ '$provide', function($provide) { $provide.decorator('myService', [ '$delegate', function myServiceDecorator($delegate) { var someFn = $delegate.someFn; function aNewFn() { // new service function someFn.apply($delegate, arguments); } $delegate.someFn = aNewFn; return $delegate; } ]); }]); ``` #### Augment the $delegate ```js angular.module('myApp', []) .config([ '$provide', function($provide) { $provide.decorator('myService', [ '$delegate', function myServiceDecorator($delegate) { function helperFn() { // an additional fn to add to the service } $delegate.aHelpfulAddition = helperFn; return $delegate; } ]); }]); ```
    Note that whatever is returned by the decorator function will replace that which is being decorated. For example, a missing return statement will wipe out the entire object being decorated.

    Decorators have different rules for different services. This is because services are registered in different ways. Services are selected by name, however filters and directives are selected by appending `"Filter"` or `"Directive"` to the end of the name. The `$delegate` provided is dictated by the type of service. | Service Type | Selector | $delegate | |--------------|-------------------------------|-----------------------------------------------------------------------| | Service | `serviceName` | The `object` or `function` returned by the service | | Directive | `directiveName + 'Directive'` | An `Array.`{@link guide/decorators#drtvArray 1} | | Filter | `filterName + 'Filter'` | The `function` returned by the filter | 1. Multiple directives may be registered to the same selector/name
    **NOTE:** Developers should take care in how and why they are modifying the `$delegate` for the service. Not only should expectations for the consumer be kept, but some functionality (such as directive registration) does not take place after decoration, but during creation/registration of the original service. This means, for example, that an action such as pushing a directive object to a directive `$delegate` will likely result in unexpected behavior. Furthermore, great care should be taken when decorating core services, directives, or filters as this may unexpectedly or adversely affect the functionality of the framework.
    ### module.decorator This {@link api/ng/type/angular.Module#decorator function} is the same as the `$provide.decorator` function except it is exposed through the module API. This allows you to separate your decorator patterns from your module config blocks. Like with `$provide.decorator`, the `module.decorator` function runs during the config phase of the app. That means you can define a `module.decorator` before the decorated service is defined. Since you can apply multiple decorators, it is noteworthy that decorator application always follows order of declaration: - If a service is decorated by both `$provide.decorator` and `module.decorator`, the decorators are applied in order: ```js angular .module('theApp', []) .factory('theFactory', theFactoryFn) .config(function($provide) { $provide.decorator('theFactory', provideDecoratorFn); // runs first }) .decorator('theFactory', moduleDecoratorFn); // runs seconds ``` - If the service has been declared multiple times, a decorator will decorate the service that has been declared last: ```js angular .module('theApp', []) .factory('theFactory', theFactoryFn) .decorator('theFactory', moduleDecoratorFn) .factory('theFactory', theOtherFactoryFn); // `theOtherFactoryFn` is selected as 'theFactory' provider and it is decorated via `moduleDecoratorFn`. ``` ## Example Applications The following sections provide examples each of a service decorator, a directive decorator, and a filter decorator. ### Service Decorator Example This example shows how we can replace the $log service with our own to display log messages. angular.module('myServiceDecorator', []). controller('Ctrl', [ '$scope', '$log', '$timeout', function($scope, $log, $timeout) { var types = ['error', 'warn', 'log', 'info' ,'debug'], i; for (i = 0; i < types.length; i++) { $log[types[i]](types[i] + ': message ' + (i + 1)); } $timeout(function() { $log.info('info: message logged in timeout'); }); } ]). directive('myLog', [ '$log', function($log) { return { restrict: 'E', template: '
    • {{l.message}}
    ', scope: {}, compile: function() { return function(scope) { scope.myLog = $log.stack; }; } }; } ]). config([ '$provide', function($provide) { $provide.decorator('$log', [ '$delegate', function logDecorator($delegate) { var myLog = { warn: function(msg) { log(msg, 'warn'); }, error: function(msg) { log(msg, 'error'); }, info: function(msg) { log(msg, 'info'); }, debug: function(msg) { log(msg, 'debug'); }, log: function(msg) { log(msg, 'log'); }, stack: [] }; function log(msg, type) { myLog.stack.push({ type: type, message: msg.toString() }); if (console && console[type]) console[type](msg); } return myLog; } ]); } ]);

    Logs

    li.warn { color: yellow; } li.error { color: red; } li.info { color: blue } li.log { color: black } li.debug { color: green } it('should display log messages in dom', function() { element.all(by.repeater('l in myLog')).count().then(function(count) { expect(count).toEqual(6); }); });
    ### Directive Decorator Example Failed interpolated expressions in `ng-href` attributes can easily go unnoticed. We can decorate `ngHref` to warn us of those conditions. angular.module('urlDecorator', []). controller('Ctrl', ['$scope', function($scope) { $scope.id = 3; $scope.warnCount = 0; // for testing }]). config(['$provide', function($provide) { // matchExpressions looks for interpolation markup in the directive attribute, extracts the expressions // from that markup (if they exist) and returns an array of those expressions function matchExpressions(str) { var exps = str.match(/{{([^}]+)}}/g); // if there isn't any, get out of here if (exps === null) return; exps = exps.map(function(exp) { var prop = exp.match(/[^{}]+/); return prop === null ? null : prop[0]; }); return exps; } // remember: directives must be selected by appending 'Directive' to the directive selector $provide.decorator('ngHrefDirective', [ '$delegate', '$log', '$parse', function($delegate, $log, $parse) { // store the original link fn var originalLinkFn = $delegate[0].link; // replace the compile fn $delegate[0].compile = function(tElem, tAttr) { // store the original exp in the directive attribute for our warning message var originalExp = tAttr.ngHref; // get the interpolated expressions var exps = matchExpressions(originalExp); // create and store the getters using $parse var getters = exps.map(function(exp) { return exp && $parse(exp); }); return function newLinkFn(scope, elem, attr) { // fire the originalLinkFn originalLinkFn.apply($delegate[0], arguments); // observe the directive attr and check the expressions attr.$observe('ngHref', function(val) { // if we have getters and getters is an array... if (getters && angular.isArray(getters)) { // loop through the getters and process them angular.forEach(getters, function(g, idx) { // if val is truthy, then the warning won't log var val = angular.isFunction(g) ? g(scope) : true; if (!val) { $log.warn('NgHref Warning: "' + exps[idx] + '" in the expression "' + originalExp + '" is falsy!'); scope.warnCount++; // for testing } }); } }); }; }; // get rid of the old link function since we return a link function in compile delete $delegate[0].link; // return the $delegate return $delegate; } ]); }]);
    View Product {{ id }} - id === 3, so no warning
    View Product {{ id + 5 }} - id + 5 === 8, so no warning
    View Product {{ someOtherId }} - someOtherId === undefined, so warn
    View Product {{ someOtherId + 5 }} - someOtherId + 5 === 5, so no warning
    Warn Count: {{ warnCount }}
    it('should warn when an expression in the interpolated value is falsy', function() { var id3 = element(by.id('id3')); var id8 = element(by.id('id8')); var someOther = element(by.id('someOtherId')); var someOther5 = element(by.id('someOtherId5')); expect(id3.getText()).toEqual('View Product 3'); expect(id3.getAttribute('href')).toContain('/products/3/view'); expect(id8.getText()).toEqual('View Product 8'); expect(id8.getAttribute('href')).toContain('/products/8/view'); expect(someOther.getText()).toEqual('View Product'); expect(someOther.getAttribute('href')).toContain('/products//view'); expect(someOther5.getText()).toEqual('View Product 5'); expect(someOther5.getAttribute('href')).toContain('/products/5/view'); expect(element(by.binding('warnCount')).getText()).toEqual('Warn Count: 1'); });
    ### Filter Decorator Example Let's say we have created an app that uses the default format for many of our `Date` filters. Suddenly requirements have changed (that never happens) and we need all of our default dates to be `'shortDate'` instead of `'mediumDate'`. angular.module('filterDecorator', []). controller('Ctrl', ['$scope', function($scope) { $scope.genesis = new Date(2010, 0, 5); $scope.ngConf = new Date(2016, 4, 4); }]). config(['$provide', function($provide) { $provide.decorator('dateFilter', [ '$delegate', function dateDecorator($delegate) { // store the original filter var originalFilter = $delegate; // return our filter return shortDateDefault; // shortDateDefault sets the format to shortDate if it is falsy function shortDateDefault(date, format, timezone) { if (!format) format = 'shortDate'; // return the result of the original filter return originalFilter(date, format, timezone); } } ]); }]);
    Initial Commit default to short date: {{ genesis | date }}
    ng-conf 2016 default short date: {{ ngConf | date }}
    ng-conf 2016 with full date format: {{ ngConf | date:'fullDate' }}
    it('should default date filter to short date format', function() { expect(element(by.id('genesis')).getText()) .toMatch(/Initial Commit default to short date: \d{1,2}\/\d{1,2}\/\d{2}/); }); it('should still allow dates to be formatted', function() { expect(element(by.id('ngConf')).getText()) .toMatch(/ng-conf 2016 with full date format: [A-Za-z]+, [A-Za-z]+ \d{1,2}, \d{4}/); });
    angular.js-1.7.9/docs/content/guide/di.ngdoc000066400000000000000000000306421356472325200207210ustar00rootroot00000000000000@ngdoc overview @name Dependency Injection @sortOrder 250 @description # Dependency Injection Dependency Injection (DI) is a software design pattern that deals with how components get hold of their dependencies. The AngularJS injector subsystem is in charge of creating components, resolving their dependencies, and providing them to other components as requested. ## Using Dependency Injection Dependency Injection is pervasive throughout AngularJS. You can use it when defining components or when providing `run` and `config` blocks for a module. - {@link angular.Module#service Services}, {@link angular.Module#directive directives}, {@link angular.Module#filter filters}, and {@link angular.Module#animation animations} are defined by an injectable factory method or constructor function, and can be injected with "services", "values", and "constants" as dependencies. - {@link ng.$controller Controllers} are defined by a constructor function, which can be injected with any of the "service" and "value" as dependencies, but they can also be provided with "special dependencies". See {@link di#controllers Controllers} below for a list of these special dependencies. - The {@link angular.Module#run `run`} method accepts a function, which can be injected with "services", "values" and, "constants" as dependencies. Note that you cannot inject "providers" into `run` blocks. - The {@link angular.Module#config `config`} method accepts a function, which can be injected with "providers" and "constants" as dependencies. Note that you cannot inject "services" or "values" into configuration. - The {@link angular.Module#provider `provider`} method can only be injected with other "providers". However, only those that have been **registered beforehand** can be injected. This is different from services, where the order of registration does not matter. See {@link module#module-loading Modules} for more details about `run` and `config` blocks and {@link guide/providers Providers} for more information about the different provider types. ### Factory Methods The way you define a directive, service, or filter is with a factory function. The factory methods are registered with modules. The recommended way of declaring factories is: ```js angular.module('myModule', []) .factory('serviceId', ['depService', function(depService) { // ... }]) .directive('directiveName', ['depService', function(depService) { // ... }]) .filter('filterName', ['depService', function(depService) { // ... }]); ``` ### Module Methods We can specify functions to run at configuration and run time for a module by calling the `config` and `run` methods. These functions are injectable with dependencies just like the factory functions above. ```js angular.module('myModule', []) .config(['depProvider', function(depProvider) { // ... }]) .run(['depService', function(depService) { // ... }]); ``` ### Controllers Controllers are "classes" or "constructor functions" that are responsible for providing the application behavior that supports the declarative markup in the template. The recommended way of declaring Controllers is using the array notation: ```js someModule.controller('MyController', ['$scope', 'dep1', 'dep2', function($scope, dep1, dep2) { ... $scope.aMethod = function() { ... } ... }]); ``` Unlike services, there can be many instances of the same type of controller in an application. Moreover, additional dependencies are made available to Controllers: * {@link scope `$scope`}: Controllers are associated with an element in the DOM and so are provided with access to the {@link scope scope}. Other components (like services) only have access to the {@link $rootScope `$rootScope`} service. * {@link ngRoute.$routeProvider#when resolves}: If a controller is instantiated as part of a route, then any values that are resolved as part of the route are made available for injection into the controller. ## Dependency Annotation AngularJS invokes certain functions (like service factories and controllers) via the injector. You need to annotate these functions so that the injector knows what services to inject into the function. There are three ways of annotating your code with service name information: - Using the inline array annotation (preferred) - Using the `$inject` property annotation - Implicitly from the function parameter names (has caveats) ### Inline Array Annotation This is the preferred way to annotate application components. This is how the examples in the documentation are written. For example: ```js someModule.controller('MyController', ['$scope', 'greeter', function($scope, greeter) { // ... }]); ``` Here we pass an array whose elements consist of a list of strings (the names of the dependencies) followed by the function itself. When using this type of annotation, take care to keep the annotation array in sync with the parameters in the function declaration. ### `$inject` Property Annotation To allow the minifiers to rename the function parameters and still be able to inject the right services, the function needs to be annotated with the `$inject` property. The `$inject` property is an array of service names to inject. ```js var MyController = function($scope, greeter) { // ... } MyController.$inject = ['$scope', 'greeter']; someModule.controller('MyController', MyController); ``` In this scenario the ordering of the values in the `$inject` array must match the ordering of the parameters in `MyController`. Just like with the array annotation, you'll need to take care to keep the `$inject` in sync with the parameters in the function declaration. ### Implicit Annotation
    **Careful:** If you plan to [minify](http://en.wikipedia.org/wiki/Minification_(programming)) your code, your service names will get renamed and break your app.
    The simplest way to get hold of the dependencies is to assume that the function parameter names are the names of the dependencies. ```js someModule.controller('MyController', function($scope, greeter) { // ... }); ``` Given a function, the injector can infer the names of the services to inject by examining the function declaration and extracting the parameter names. In the above example, `$scope` and `greeter` are two services which need to be injected into the function. One advantage of this approach is that there's no array of names to keep in sync with the function parameters. You can also freely reorder dependencies. However this method will not work with JavaScript minifiers/obfuscators because of how they rename parameters. Tools like [ng-annotate](https://github.com/olov/ng-annotate) let you use implicit dependency annotations in your app and automatically add inline array annotations prior to minifying. If you decide to take this approach, you probably want to use `ng-strict-di`. Because of these caveats, we recommend avoiding this style of annotation. ## Using Strict Dependency Injection You can add an `ng-strict-di` directive on the same element as `ng-app` to opt into strict DI mode: ```html I can add: {{ 1 + 2 }}. ``` Strict mode throws an error whenever a service tries to use implicit annotations. Consider this module, which includes a `willBreak` service that uses implicit DI: ```js angular.module('myApp', []) .factory('willBreak', function($rootScope) { // $rootScope is implicitly injected }) .run(['willBreak', function(willBreak) { // AngularJS will throw when this runs }]); ``` When the `willBreak` service is instantiated, AngularJS will throw an error because of strict mode. This is useful when using a tool like [ng-annotate](https://github.com/olov/ng-annotate) to ensure that all of your application components have annotations. If you're using manual bootstrapping, you can also use strict DI by providing `strictDi: true` in the optional config argument: ```js angular.bootstrap(document, ['myApp'], { strictDi: true }); ``` ## Why Dependency Injection? This section motivates and explains AngularJS's use of DI. For how to use DI, see above. For in-depth discussion about DI, see [Dependency Injection](http://en.wikipedia.org/wiki/Dependency_injection) at Wikipedia, [Inversion of Control](http://martinfowler.com/articles/injection.html) by Martin Fowler, or read about DI in your favorite software design pattern book. There are only three ways a component (object or function) can get a hold of its dependencies: 1. The component can create the dependency, typically using the `new` operator. 2. The component can look up the dependency, by referring to a global variable. 3. The component can have the dependency passed to it where it is needed. The first two options of creating or looking up dependencies are not optimal because they hard code the dependency to the component. This makes it difficult, if not impossible, to modify the dependencies. This is especially problematic in tests, where it is often desirable to provide mock dependencies for test isolation. The third option is the most viable, since it removes the responsibility of locating the dependency from the component. The dependency is simply handed to the component. ```js function SomeClass(greeter) { this.greeter = greeter; } SomeClass.prototype.doSomething = function(name) { this.greeter.greet(name); } ``` In the above example `SomeClass` is not concerned with creating or locating the `greeter` dependency, it is simply handed the `greeter` when it is instantiated. This is desirable, but it puts the responsibility of getting hold of the dependency on the code that constructs `SomeClass`. To manage the responsibility of dependency creation, each AngularJS application has an {@link angular.injector injector}. The injector is a [service locator](http://en.wikipedia.org/wiki/Service_locator_pattern) that is responsible for construction and lookup of dependencies. Here is an example of using the injector service: First create an AngularJS module that will hold the service definition. (The empty array passed as the second parameter means that this module does not depend on any other modules.) ```js // Create a module to hold the service definition var myModule = angular.module('myModule', []); ``` Teach the injector how to build a `greeter` service, which is just an object that contains a `greet` method. Notice that `greeter` is dependent on the `$window` service, which will be provided (injected into `greeter`) by the injector. ```js // Define the `greeter` service myModule.factory('greeter', function($window) { return { greet: function(text) { $window.alert(text); } }; }); ``` Create a new injector that can provide components defined in our `myModule` module and request our `greeter` service from the injector. (This is usually done automatically by AngularJS bootstrap). ```js var injector = angular.injector(['ng', 'myModule']); var greeter = injector.get('greeter'); ``` Asking for dependencies solves the issue of hard coding, but it also means that the injector needs to be passed throughout the application. Passing the injector breaks the [Law of Demeter](http://en.wikipedia.org/wiki/Law_of_Demeter). To remedy this, we use a declarative notation in our HTML templates, to hand the responsibility of creating components over to the injector, as in this example: ```html
    ``` ```js function MyController($scope, greeter) { $scope.sayHello = function() { greeter.greet('Hello World'); }; } ``` When AngularJS compiles the HTML, it processes the `ng-controller` directive, which in turn asks the injector to create an instance of the controller and its dependencies. ```js injector.instantiate(MyController); ``` This is all done behind the scenes. Notice that by having the `ng-controller` ask the injector to instantiate the class, it can satisfy all of the dependencies of `MyController` without the controller ever knowing about the injector. This is the best outcome. The application code simply declares the dependencies it needs, without having to deal with the injector. This setup does not break the Law of Demeter.
    **Note:** AngularJS uses [**constructor injection**](http://misko.hevery.com/2009/02/19/constructor-injection-vs-setter-injection/).
    angular.js-1.7.9/docs/content/guide/directive.ngdoc000066400000000000000000001067151356472325200223100ustar00rootroot00000000000000@ngdoc overview @name Directives @sortOrder 300 @description # Creating Custom Directives
    **Note:** this guide is targeted towards developers who are already familiar with AngularJS basics. If you're just getting started, we recommend the {@link tutorial/ tutorial} first. If you're looking for the **directives API**, you can find it in the {@link ng.$compile `$compile` API docs}.
    This document explains when you'd want to create your own directives in your AngularJS app, and how to implement them. ## What are Directives? At a high level, directives are markers on a DOM element (such as an attribute, element name, comment or CSS class) that tell AngularJS's **HTML compiler** ({@link ng.$compile `$compile`}) to attach a specified behavior to that DOM element (e.g. via event listeners), or even to transform the DOM element and its children. AngularJS comes with a set of these directives built-in, like `ngBind`, `ngModel`, and `ngClass`. Much like you create controllers and services, you can create your own directives for AngularJS to use. When AngularJS {@link guide/bootstrap bootstraps} your application, the {@link guide/compiler HTML compiler} traverses the DOM matching directives against the DOM elements.
    **What does it mean to "compile" an HTML template?** For AngularJS, "compilation" means attaching directives to the HTML to make it interactive. The reason we use the term "compile" is that the recursive process of attaching directives mirrors the process of compiling source code in [compiled programming languages](http://en.wikipedia.org/wiki/Compiled_languages).
    ## Matching Directives Before we can write a directive, we need to know how AngularJS's {@link guide/compiler HTML compiler} determines when to use a given directive. Similar to the terminology used when an [element **matches** a selector](https://developer.mozilla.org/en-US/docs/Web/API/Element.matches), we say an element **matches** a directive when the directive is part of its declaration. In the following example, we say that the `` element **matches** the `ngModel` directive ```html ``` The following `` element also **matches** `ngModel`: ```html ``` And the following `` element **matches** the `person` directive: ```html {{name}} ``` ### Normalization AngularJS **normalizes** an element's tag and attribute name to determine which elements match which directives. We typically refer to directives by their case-sensitive [camelCase](http://en.wikipedia.org/wiki/CamelCase) **normalized** name (e.g. `ngModel`). However, since HTML is case-insensitive, we refer to directives in the DOM by lower-case forms, typically using [dash-delimited](https://en.wikipedia.org/wiki/Letter_case#Special_case_styles) attributes on DOM elements (e.g. `ng-model`). The **normalization** process is as follows: 1. Strip `x-` and `data-` from the front of the element/attributes. 2. Convert the `:`, `-`, or `_`-delimited name to `camelCase`. For example, the following forms are all equivalent and match the {@link ngBind} directive:
    Hello





    angular.module('docsBindExample', []) .controller('Controller', ['$scope', function($scope) { $scope.name = 'Max Karl Ernst Ludwig Planck (April 23, 1858 – October 4, 1947)'; }]); it('should show off bindings', function() { var containerElm = element(by.css('div[ng-controller="Controller"]')); var nameBindings = containerElm.all(by.binding('name')); expect(nameBindings.count()).toBe(5); nameBindings.each(function(elem) { expect(elem.getText()).toEqual('Max Karl Ernst Ludwig Planck (April 23, 1858 – October 4, 1947)'); }); });
    **Best Practice:** Prefer using the dash-delimited format (e.g. `ng-bind` for `ngBind`). If you want to use an HTML validating tool, you can instead use the `data`-prefixed version (e.g. `data-ng-bind` for `ngBind`). The other forms shown above are accepted for legacy reasons but we advise you to avoid them.
    ### Directive types `$compile` can match directives based on element names (E), attributes (A), class names (C), and comments (M). The built-in AngularJS directives show in their documentation page which type of matching they support. The following demonstrates the various ways a directive (`myDir` in this case) that matches all 4 types can be referenced from within a template. ```html ``` A directive can specify which of the 4 matching types it supports in the {@link ng.$compile#-restrict- `restrict`} property of the directive definition object. The default is `EA`.
    **Best Practice:** Prefer using directives via tag name and attributes over comment and class names. Doing so generally makes it easier to determine what directives a given element matches.
    **Best Practice:** Comment directives were commonly used in places where the DOM API limits the ability to create directives that spanned multiple elements (e.g. inside `` elements). AngularJS 1.2 introduces {@link ng.directive:ngRepeat `ng-repeat-start` and `ng-repeat-end`} as a better solution to this problem. Developers are encouraged to use this over custom comment directives when possible. ## Creating Directives First let's talk about the {@link ng.$compileProvider#directive API for registering directives}. Much like controllers, directives are registered on modules. To register a directive, you use the `module.directive` API. `module.directive` takes the {@link guide/directive#matching-directives normalized} directive name followed by a **factory function.** This factory function should return an object with the different options to tell `$compile` how the directive should behave when matched. The factory function is invoked only once when the {@link ng.$compile compiler} matches the directive for the first time. You can perform any initialization work here. The function is invoked using {@link auto.$injector#invoke $injector.invoke} which makes it injectable just like a controller. We'll go over a few common examples of directives, then dive deep into the different options and compilation process.
    **Best Practice:** In order to avoid collisions with some future standard, it's best to prefix your own directive names. For instance, if you created a `` directive, it would be problematic if HTML7 introduced the same element. A two or three letter prefix (e.g. `btfCarousel`) works well. Similarly, do not prefix your own directives with `ng` or they might conflict with directives included in a future version of AngularJS.
    For the following examples, we'll use the prefix `my` (e.g. `myCustomer`). ### Template-expanding directive Let's say you have a chunk of your template that represents a customer's information. This template is repeated many times in your code. When you change it in one place, you have to change it in several others. This is a good opportunity to use a directive to simplify your template. Let's create a directive that simply replaces its contents with a static template: angular.module('docsSimpleDirective', []) .controller('Controller', ['$scope', function($scope) { $scope.customer = { name: 'Naomi', address: '1600 Amphitheatre' }; }]) .directive('myCustomer', function() { return { template: 'Name: {{customer.name}} Address: {{customer.address}}' }; });
    Notice that we have bindings in this directive. After `$compile` compiles and links `
    `, it will try to match directives on the element's children. This means you can compose directives of other directives. We'll see how to do that in {@link guide/directive#creating-directives-that-communicate an example} below. In the example above we in-lined the value of the `template` option, but this will become annoying as the size of your template grows.
    **Best Practice:** Unless your template is very small, it's typically better to break it apart into its own HTML file and load it with the `templateUrl` option.
    If you are familiar with `ngInclude`, `templateUrl` works just like it. Here's the same example using `templateUrl` instead: angular.module('docsTemplateUrlDirective', []) .controller('Controller', ['$scope', function($scope) { $scope.customer = { name: 'Naomi', address: '1600 Amphitheatre' }; }]) .directive('myCustomer', function() { return { templateUrl: 'my-customer.html' }; });
    Name: {{customer.name}} Address: {{customer.address}}
    `templateUrl` can also be a function which returns the URL of an HTML template to be loaded and used for the directive. AngularJS will call the `templateUrl` function with two parameters: the element that the directive was called on, and an `attr` object associated with that element.
    **Note:** You do not currently have the ability to access scope variables from the `templateUrl` function, since the template is requested before the scope is initialized.
    angular.module('docsTemplateUrlDirective', []) .controller('Controller', ['$scope', function($scope) { $scope.customer = { name: 'Naomi', address: '1600 Amphitheatre' }; }]) .directive('myCustomer', function() { return { templateUrl: function(elem, attr) { return 'customer-' + attr.type + '.html'; } }; });
    Name: {{customer.name}} Address: {{customer.address}}
    **Note:** When you create a directive, it is restricted to attribute and elements only by default. In order to create directives that are triggered by class name, you need to use the `restrict` option.
    The `restrict` option is typically set to: * `'A'` - only matches attribute name * `'E'` - only matches element name * `'C'` - only matches class name * `'M'` - only matches comment These restrictions can all be combined as needed: * `'AEC'` - matches either attribute or element or class name Let's change our directive to use `restrict: 'E'`: angular.module('docsRestrictDirective', []) .controller('Controller', ['$scope', function($scope) { $scope.customer = { name: 'Naomi', address: '1600 Amphitheatre' }; }]) .directive('myCustomer', function() { return { restrict: 'E', templateUrl: 'my-customer.html' }; });
    Name: {{customer.name}} Address: {{customer.address}}
    For more on the `restrict` property, see the {@link ng.$compile#directive-definition-object API docs}.
    **When should I use an attribute versus an element?** Use an element when you are creating a component that is in control of the template. The common case for this is when you are creating a Domain-Specific Language for parts of your template. Use an attribute when you are decorating an existing element with new functionality.
    Using an element for the `myCustomer` directive is clearly the right choice because you're not decorating an element with some "customer" behavior; you're defining the core behavior of the element as a customer component. ### Isolating the Scope of a Directive Our `myCustomer` directive above is great, but it has a fatal flaw. We can only use it once within a given scope. In its current implementation, we'd need to create a different controller each time in order to re-use such a directive: angular.module('docsScopeProblemExample', []) .controller('NaomiController', ['$scope', function($scope) { $scope.customer = { name: 'Naomi', address: '1600 Amphitheatre' }; }]) .controller('IgorController', ['$scope', function($scope) { $scope.customer = { name: 'Igor', address: '123 Somewhere' }; }]) .directive('myCustomer', function() { return { restrict: 'E', templateUrl: 'my-customer.html' }; });

    Name: {{customer.name}} Address: {{customer.address}}
    This is clearly not a great solution. What we want to be able to do is separate the scope inside a directive from the scope outside, and then map the outer scope to a directive's inner scope. We can do this by creating what we call an **isolate scope**. To do this, we can use a {@link $compile#-scope- directive's `scope`} option: angular.module('docsIsolateScopeDirective', []) .controller('Controller', ['$scope', function($scope) { $scope.naomi = { name: 'Naomi', address: '1600 Amphitheatre' }; $scope.igor = { name: 'Igor', address: '123 Somewhere' }; }]) .directive('myCustomer', function() { return { restrict: 'E', scope: { customerInfo: '=info' }, templateUrl: 'my-customer-iso.html' }; });

    Name: {{customerInfo.name}} Address: {{customerInfo.address}}
    Looking at `index.html`, the first `` element binds the `info` attribute to `naomi`, which we have exposed on our controller's scope. The second binds `info` to `igor`. Let's take a closer look at the scope option: ```javascript //... scope: { customerInfo: '=info' }, //... ``` The **scope option** is an object that contains a property for each isolate scope binding. In this case it has just one property: - Its name (`customerInfo`) corresponds to the directive's **isolate scope** property, `customerInfo`. - Its value (`=info`) tells `$compile` to bind to the `info` attribute.
    **Note:** These `=attr` attributes in the `scope` option of directives are normalized just like directive names. To bind to the attribute in `
    `, you'd specify a binding of `=bindToThis`.
    For cases where the attribute name is the same as the value you want to bind to inside the directive's scope, you can use this shorthand syntax: ```javascript ... scope: { // same as '=customer' customer: '=' }, ... ``` Besides making it possible to bind different data to the scope inside a directive, using an isolated scope has another effect. We can show this by adding another property, `vojta`, to our scope and trying to access it from within our directive's template: angular.module('docsIsolationExample', []) .controller('Controller', ['$scope', function($scope) { $scope.naomi = { name: 'Naomi', address: '1600 Amphitheatre' }; $scope.vojta = { name: 'Vojta', address: '3456 Somewhere Else' }; }]) .directive('myCustomer', function() { return { restrict: 'E', scope: { customerInfo: '=info' }, templateUrl: 'my-customer-plus-vojta.html' }; });
    Name: {{customerInfo.name}} Address: {{customerInfo.address}}
    Name: {{vojta.name}} Address: {{vojta.address}}
    Notice that `{{vojta.name}}` and `{{vojta.address}}` are empty, meaning they are undefined. Although we defined `vojta` in the controller, it's not available within the directive. As the name suggests, the **isolate scope** of the directive isolates everything except models that you've explicitly added to the `scope: {}` hash object. This is helpful when building reusable components because it prevents a component from changing your model state except for the models that you explicitly pass in.
    **Note:** Normally, a scope prototypically inherits from its parent. An isolated scope does not. See the {@link $compile#directive-definition-object "Directive Definition Object - scope"} section for more information about isolate scopes.
    **Best Practice:** Use the `scope` option to create isolate scopes when making components that you want to reuse throughout your app.
    ### Creating a Directive that Manipulates the DOM In this example we will build a directive that displays the current time. Once a second, it updates the DOM to reflect the current time. Directives that want to modify the DOM typically use the `link` option to register DOM listeners as well as update the DOM. It is executed after the template has been cloned and is where directive logic will be put. `link` takes a function with the following signature, `function link(scope, element, attrs, controller, transcludeFn) { ... }`, where: * `scope` is an AngularJS scope object. * `element` is the jqLite-wrapped element that this directive matches. * `attrs` is a hash object with key-value pairs of normalized attribute names and their corresponding attribute values. * `controller` is the directive's required controller instance(s) or its own controller (if any). The exact value depends on the directive's require property. * `transcludeFn` is a transclude linking function pre-bound to the correct transclusion scope.
    For more details on the `link` option refer to the {@link ng.$compile#-link- `$compile` API} page.
    In our `link` function, we want to update the displayed time once a second, or whenever a user changes the time formatting string that our directive binds to. We will use the `$interval` service to call a handler on a regular basis. This is easier than using `$timeout` but also works better with end-to-end testing, where we want to ensure that all `$timeout`s have completed before completing the test. We also want to remove the `$interval` if the directive is deleted so we don't introduce a memory leak. angular.module('docsTimeDirective', []) .controller('Controller', ['$scope', function($scope) { $scope.format = 'M/d/yy h:mm:ss a'; }]) .directive('myCurrentTime', ['$interval', 'dateFilter', function($interval, dateFilter) { function link(scope, element, attrs) { var format, timeoutId; function updateTime() { element.text(dateFilter(new Date(), format)); } scope.$watch(attrs.myCurrentTime, function(value) { format = value; updateTime(); }); element.on('$destroy', function() { $interval.cancel(timeoutId); }); // start the UI update process; save the timeoutId for canceling timeoutId = $interval(function() { updateTime(); // update DOM }, 1000); } return { link: link }; }]);
    Date format:
    Current time is:
    There are a couple of things to note here. Just like the `module.controller` API, the function argument in `module.directive` is dependency injected. Because of this, we can use `$interval` and `dateFilter` inside our directive's `link` function. We register an event `element.on('$destroy', ...)`. What fires this `$destroy` event? There are a few special events that AngularJS emits. When a DOM node that has been compiled with AngularJS's compiler is destroyed, it emits a `$destroy` event. Similarly, when an AngularJS scope is destroyed, it broadcasts a `$destroy` event to listening scopes. By listening to this event, you can remove event listeners that might cause memory leaks. Listeners registered to scopes and elements are automatically cleaned up when they are destroyed, but if you registered a listener on a service, or registered a listener on a DOM node that isn't being deleted, you'll have to clean it up yourself or you risk introducing a memory leak.
    **Best Practice:** Directives should clean up after themselves. You can use `element.on('$destroy', ...)` or `scope.$on('$destroy', ...)` to run a clean-up function when the directive is removed.
    ### Creating a Directive that Wraps Other Elements We've seen that you can pass in models to a directive using the isolate scope, but sometimes it's desirable to be able to pass in an entire template rather than a string or an object. Let's say that we want to create a "dialog box" component. The dialog box should be able to wrap any arbitrary content. To do this, we need to use the `transclude` option. angular.module('docsTransclusionDirective', []) .controller('Controller', ['$scope', function($scope) { $scope.name = 'Tobias'; }]) .directive('myDialog', function() { return { restrict: 'E', transclude: true, scope: {}, templateUrl: 'my-dialog.html' }; });
    Check out the contents, {{name}}!
    What does this `transclude` option do, exactly? `transclude` makes the contents of a directive with this option have access to the scope **outside** of the directive rather than inside. To illustrate this, see the example below. Notice that we've added a `link` function in `script.js` that redefines `name` as `Jeff`. What do you think the `{{name}}` binding will resolve to now? angular.module('docsTransclusionExample', []) .controller('Controller', ['$scope', function($scope) { $scope.name = 'Tobias'; }]) .directive('myDialog', function() { return { restrict: 'E', transclude: true, scope: {}, templateUrl: 'my-dialog.html', link: function(scope) { scope.name = 'Jeff'; } }; });
    Check out the contents, {{name}}!
    Ordinarily, we would expect that `{{name}}` would be `Jeff`. However, we see in this example that the `{{name}}` binding is still `Tobias`. The `transclude` option changes the way scopes are nested. It makes it so that the **contents** of a transcluded directive have whatever scope is outside the directive, rather than whatever scope is on the inside. In doing so, it gives the contents access to the outside scope. Note that if the directive did not create its own scope, then `scope` in `scope.name = 'Jeff'` would reference the outside scope and we would see `Jeff` in the output. This behavior makes sense for a directive that wraps some content, because otherwise you'd have to pass in each model you wanted to use separately. If you have to pass in each model that you want to use, then you can't really have arbitrary contents, can you?
    **Best Practice:** only use `transclude: true` when you want to create a directive that wraps arbitrary content.
    Next, we want to add buttons to this dialog box, and allow someone using the directive to bind their own behavior to it. angular.module('docsIsoFnBindExample', []) .controller('Controller', ['$scope', '$timeout', function($scope, $timeout) { $scope.name = 'Tobias'; $scope.message = ''; $scope.hideDialog = function(message) { $scope.message = message; $scope.dialogIsHidden = true; $timeout(function() { $scope.message = ''; $scope.dialogIsHidden = false; }, 2000); }; }]) .directive('myDialog', function() { return { restrict: 'E', transclude: true, scope: { 'close': '&onClose' }, templateUrl: 'my-dialog-close.html' }; });
    {{message}} Check out the contents, {{name}}!
    ×
    We want to run the function we pass by invoking it from the directive's scope, but have it run in the context of the scope where it's registered. We saw earlier how to use `=attr` in the `scope` option, but in the above example, we're using `&attr` instead. The `&` binding allows a directive to trigger evaluation of an expression in the context of the original scope, at a specific time. Any legal expression is allowed, including an expression which contains a function call. Because of this, `&` bindings are ideal for binding callback functions to directive behaviors. When the user clicks the `x` in the dialog, the directive's `close` function is called, thanks to `ng-click.` This call to `close` on the isolated scope actually evaluates the expression `hideDialog(message)` in the context of the original scope, thus running `Controller`'s `hideDialog` function. Often it's desirable to pass data from the isolate scope via an expression to the parent scope, this can be done by passing a map of local variable names and values into the expression wrapper function. For example, the `hideDialog` function takes a message to display when the dialog is hidden. This is specified in the directive by calling `close({message: 'closing for now'})`. Then the local variable `message` will be available within the `on-close` expression.
    **Best Practice:** use `&attr` in the `scope` option when you want your directive to expose an API for binding to behaviors.
    ### Creating a Directive that Adds Event Listeners Previously, we used the `link` function to create a directive that manipulated its DOM elements. Building upon that example, let's make a directive that reacts to events on its elements. For instance, what if we wanted to create a directive that lets a user drag an element? angular.module('dragModule', []) .directive('myDraggable', ['$document', function($document) { return { link: function(scope, element, attr) { var startX = 0, startY = 0, x = 0, y = 0; element.css({ position: 'relative', border: '1px solid red', backgroundColor: 'lightgrey', cursor: 'pointer' }); element.on('mousedown', function(event) { // Prevent default dragging of selected content event.preventDefault(); startX = event.pageX - x; startY = event.pageY - y; $document.on('mousemove', mousemove); $document.on('mouseup', mouseup); }); function mousemove(event) { y = event.pageY - startY; x = event.pageX - startX; element.css({ top: y + 'px', left: x + 'px' }); } function mouseup() { $document.off('mousemove', mousemove); $document.off('mouseup', mouseup); } } }; }]); Drag Me ### Creating Directives that Communicate You can compose any directives by using them within templates. Sometimes, you want a component that's built from a combination of directives. Imagine you want to have a container with tabs in which the contents of the container correspond to which tab is active. angular.module('docsTabsExample', []) .directive('myTabs', function() { return { restrict: 'E', transclude: true, scope: {}, controller: ['$scope', function MyTabsController($scope) { var panes = $scope.panes = []; $scope.select = function(pane) { angular.forEach(panes, function(pane) { pane.selected = false; }); pane.selected = true; }; this.addPane = function(pane) { if (panes.length === 0) { $scope.select(pane); } panes.push(pane); }; }], templateUrl: 'my-tabs.html' }; }) .directive('myPane', function() { return { require: '^^myTabs', restrict: 'E', transclude: true, scope: { title: '@' }, link: function(scope, element, attrs, tabsCtrl) { tabsCtrl.addPane(scope); }, templateUrl: 'my-pane.html' }; });

    Lorem ipsum dolor sit amet

    Mauris elementum elementum enim at suscipit.

    counter: {{i || 0}}

    {{title}}

    The `myPane` directive has a `require` option with value `^^myTabs`. When a directive uses this option, `$compile` will throw an error unless the specified controller is found. The `^^` prefix means that this directive searches for the controller on its parents. (A `^` prefix would make the directive look for the controller on its own element or its parents; without any prefix, the directive would look on its own element only.) So where does this `myTabs` controller come from? Directives can specify controllers using the unsurprisingly named `controller` option. As you can see, the `myTabs` directive uses this option. Just like `ngController`, this option attaches a controller to the template of the directive. If it is necessary to reference the controller or any functions bound to the controller from the template, you can use the option `controllerAs` to specify the name of the controller as an alias. The directive needs to define a scope for this configuration to be used. This is particularly useful in the case when the directive is used as a component. Looking back at `myPane`'s definition, notice the last argument in its `link` function: `tabsCtrl`. When a directive requires a controller, it receives that controller as the fourth argument of its `link` function. Taking advantage of this, `myPane` can call the `addPane` function of `myTabs`. If multiple controllers are required, the `require` option of the directive can take an array argument. The corresponding parameter being sent to the `link` function will also be an array. ```js angular.module('docsTabsExample', []) .directive('myPane', function() { return { require: ['^^myTabs', 'ngModel'], restrict: 'E', transclude: true, scope: { title: '@' }, link: function(scope, element, attrs, controllers) { var tabsCtrl = controllers[0], modelCtrl = controllers[1]; tabsCtrl.addPane(scope); }, templateUrl: 'my-pane.html' }; }); ``` Savvy readers may be wondering what the difference is between `link` and `controller`. The basic difference is that `controller` can expose an API, and `link` functions can interact with controllers using `require`.
    **Best Practice:** use `controller` when you want to expose an API to other directives. Otherwise use `link`.
    ## Summary Here we've seen the main use cases for directives. Each of these samples acts as a good starting point for creating your own directives. You might also be interested in an in-depth explanation of the compilation process that's available in the {@link guide/compiler compiler guide}. The {@link ng.$compile `$compile` API} page has a comprehensive list of directive options for reference. angular.js-1.7.9/docs/content/guide/e2e-testing.ngdoc000066400000000000000000000065001356472325200224470ustar00rootroot00000000000000@ngdoc overview @name E2E Testing @sortOrder 420 @description # E2E Testing As applications grow in size and complexity, it becomes unrealistic to rely on manual testing to verify the correctness of new features, catch bugs and notice regressions. Unit tests are the first line of defense for catching bugs, but sometimes issues come up with integration between components which can't be captured in a unit test. End-to-end tests are made to find these problems. We have built [Protractor](https://github.com/angular/protractor), an end to end test runner which simulates user interactions that will help you verify the health of your AngularJS application. ## Using Protractor Protractor is a [Node.js](http://nodejs.org) program, and runs end-to-end tests that are also written in JavaScript and run with node. Protractor uses [WebDriver](https://code.google.com/p/selenium/wiki/GettingStarted) to control browsers and simulate user actions. For more information on Protractor, view [getting started](http://angular.github.io/protractor/#/getting-started) or the [api docs](http://angular.github.io/protractor/#/api). Protractor uses [Jasmine](http://jasmine.github.io/1.3/introduction.html) for its test syntax. As in unit testing, a test file is comprised of one or more `it` blocks that describe the requirements of your application. `it` blocks are made of **commands** and **expectations**. Commands tell Protractor to do something with the application such as navigate to a page or click on a button. Expectations tell Protractor to assert something about the application's state, such as the value of a field or the current URL. If any expectation within an `it` block fails, the runner marks the `it` as "failed" and continues on to the next block. Test files may also have `beforeEach` and `afterEach` blocks, which will be run before or after each `it` block regardless of whether the block passes or fails. In addition to the above elements, tests may also contain helper functions to avoid duplicating code in the `it` blocks. Here is an example of a simple test: ```js describe('TODO list', function() { it('should filter results', function() { // Find the element with ng-model="user" and type "jacksparrow" into it element(by.model('user')).sendKeys('jacksparrow'); // Find the first (and only) button on the page and click it element(by.css(':button')).click(); // Verify that there are 10 tasks expect(element.all(by.repeater('task in tasks')).count()).toEqual(10); // Enter 'groceries' into the element with ng-model="filterText" element(by.model('filterText')).sendKeys('groceries'); // Verify that now there is only one item in the task list expect(element.all(by.repeater('task in tasks')).count()).toEqual(1); }); }); ``` This test describes the requirements of a ToDo list, specifically, that it should be able to filter the list of items. ## Example See the [angular-seed](https://github.com/angular/angular-seed) project for more examples, or look at the embedded examples in the AngularJS documentation (For example, {@link $http $http} has an end-to-end test in the example under the `protractor.js` tag). ## Caveats Protractor does not work out-of-the-box with apps that bootstrap manually using `angular.bootstrap`. You must use the `ng-app` directive. angular.js-1.7.9/docs/content/guide/expression.ngdoc000066400000000000000000000322631356472325200225250ustar00rootroot00000000000000@ngdoc overview @name Expressions @sortOrder 270 @description # AngularJS Expressions AngularJS expressions are JavaScript-like code snippets that are mainly placed in interpolation bindings such as `{{ textBinding }}`, but also used directly in directive attributes such as `ng-click="functionExpression()"`. For example, these are valid expressions in AngularJS: * `1+2` * `a+b` * `user.name` * `items[index]` ## AngularJS Expressions vs. JavaScript Expressions AngularJS expressions are like JavaScript expressions with the following differences: * **Context:** JavaScript expressions are evaluated against the global `window`. In AngularJS, expressions are evaluated against a {@link ng.$rootScope.Scope `scope`} object. * **Forgiving:** In JavaScript, trying to evaluate undefined properties generates `ReferenceError` or `TypeError`. In AngularJS, expression evaluation is forgiving to `undefined` and `null`. * **Filters:** You can use {@link guide/filter filters} within expressions to format data before displaying it. * **No Control Flow Statements:** You cannot use the following in an AngularJS expression: conditionals, loops, or exceptions. * **No Function Declarations:** You cannot declare functions in an AngularJS expression, even inside `ng-init` directive. * **No RegExp Creation With Literal Notation:** You cannot create regular expressions in an AngularJS expression. An exception to this rule is {@link ngPattern `ng-pattern`} which accepts valid RegExp. * **No Object Creation With New Operator:** You cannot use `new` operator in an AngularJS expression. * **No Bitwise, Comma, And Void Operators:** You cannot use [Bitwise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators), `,` or `void` operators in an AngularJS expression. If you want to run more complex JavaScript code, you should make it a controller method and call the method from your view. If you want to `eval()` an AngularJS expression yourself, use the {@link ng.$rootScope.Scope#$eval `$eval()`} method. ## Example 1+2={{1+2}} it('should calculate expression in binding', function() { expect(element(by.binding('1+2')).getText()).toEqual('1+2=3'); }); You can try evaluating different expressions here:
    Expression:
    • [ X ] {{expr}} =>
    angular.module('expressionExample', []) .controller('ExampleController', ['$scope', function($scope) { var exprs = $scope.exprs = []; $scope.expr = '3*10|currency'; $scope.addExp = function(expr) { exprs.push(expr); }; $scope.removeExp = function(index) { exprs.splice(index, 1); }; }]); it('should allow user expression testing', function() { element(by.css('.expressions button')).click(); var lis = element(by.css('.expressions ul')).all(by.repeater('expr in exprs')); expect(lis.count()).toBe(1); expect(lis.get(0).getText()).toEqual('[ X ] 3*10|currency => $30.00'); });
    ## Context AngularJS does not use JavaScript's `eval()` to evaluate expressions. Instead AngularJS's {@link ng.$parse $parse} service processes these expressions. AngularJS expressions do not have direct access to global variables like `window`, `document` or `location`. This restriction is intentional. It prevents accidental access to the global state – a common source of subtle bugs. Instead use services like `$window` and `$location` in functions on controllers, which are then called from expressions. Such services provide mockable access to globals. It is possible to access the context object using the identifier `this` and the locals object using the identifier `$locals`.
    Name:
    angular.module('expressionExample', []) .controller('ExampleController', ['$window', '$scope', function($window, $scope) { $scope.name = 'World'; $scope.greet = function() { $window.alert('Hello ' + $scope.name); }; }]); it('should calculate expression in binding', function() { if (browser.params.browser === 'safari') { // Safari can't handle dialogs. return; } element(by.css('[ng-click="greet()"]')).click(); // We need to give the browser time to display the alert browser.wait(protractor.ExpectedConditions.alertIsPresent(), 1000); var alertDialog = browser.switchTo().alert(); expect(alertDialog.getText()).toEqual('Hello World'); alertDialog.accept(); });
    ## Forgiving Expression evaluation is forgiving to undefined and null. In JavaScript, evaluating `a.b.c` throws an exception if `a` is not an object. While this makes sense for a general purpose language, the expression evaluations are primarily used for data binding, which often look like this: {{a.b.c}} It makes more sense to show nothing than to throw an exception if `a` is undefined (perhaps we are waiting for the server response, and it will become defined soon). If expression evaluation wasn't forgiving we'd have to write bindings that clutter the code, for example: `{{((a||{}).b||{}).c}}` Similarly, invoking a function `a.b.c()` on `undefined` or `null` simply returns `undefined`. ## No Control Flow Statements Apart from the ternary operator (`a ? b : c`), you cannot write a control flow statement in an expression. The reason behind this is core to the AngularJS philosophy that application logic should be in controllers, not the views. If you need a real conditional, loop, or to throw from a view expression, delegate to a JavaScript method instead. ## No function declarations or RegExp creation with literal notation You can't declare functions or create regular expressions from within AngularJS expressions. This is to avoid complex model transformation logic inside templates. Such logic is better placed in a controller or in a dedicated filter where it can be tested properly. ## `$event` Directives like {@link ng.directive:ngClick `ngClick`} and {@link ng.directive:ngFocus `ngFocus`} expose a `$event` object within the scope of that expression. The object is an instance of a [jQuery Event Object](http://api.jquery.com/category/events/event-object/) when jQuery is present or a similar jqLite object.

    $event:

     {{$event | json}}

    clickEvent:

    {{clickEvent | json}}

    angular.module('eventExampleApp', []). controller('EventController', ['$scope', function($scope) { /* * expose the event object to the scope */ $scope.clickMe = function(clickEvent) { $scope.clickEvent = simpleKeys(clickEvent); console.log(clickEvent); }; /* * return a copy of an object with only non-object keys * we need this to avoid circular references */ function simpleKeys(original) { return Object.keys(original).reduce(function(obj, key) { obj[key] = typeof original[key] === 'object' ? '{ ... }' : original[key]; return obj; }, {}); } }]);
    Note in the example above how we can pass in `$event` to `clickMe`, but how it does not show up in `{{$event}}`. This is because `$event` is outside the scope of that binding. ## One-time binding An expression that starts with `::` is considered a one-time expression. One-time expressions will stop recalculating once they are stable, which happens after the first digest if the expression result is a non-undefined value (see value stabilization algorithm below).

    One time binding: {{::name}}

    Normal binding: {{name}}

    angular.module('oneTimeBindingExampleApp', []). controller('EventController', ['$scope', function($scope) { var counter = 0; var names = ['Igor', 'Misko', 'Chirayu', 'Lucas']; /* * expose the event object to the scope */ $scope.clickMe = function(clickEvent) { $scope.name = names[counter % names.length]; counter++; }; }]); it('should freeze binding after its value has stabilized', function() { var oneTimeBinding = element(by.id('one-time-binding-example')); var normalBinding = element(by.id('normal-binding-example')); expect(oneTimeBinding.getText()).toEqual('One time binding:'); expect(normalBinding.getText()).toEqual('Normal binding:'); element(by.buttonText('Click Me')).click(); expect(oneTimeBinding.getText()).toEqual('One time binding: Igor'); expect(normalBinding.getText()).toEqual('Normal binding: Igor'); element(by.buttonText('Click Me')).click(); expect(oneTimeBinding.getText()).toEqual('One time binding: Igor'); expect(normalBinding.getText()).toEqual('Normal binding: Misko'); element(by.buttonText('Click Me')).click(); element(by.buttonText('Click Me')).click(); expect(oneTimeBinding.getText()).toEqual('One time binding: Igor'); expect(normalBinding.getText()).toEqual('Normal binding: Lucas'); });
    ### Reasons for using one-time binding The main purpose of one-time binding expression is to provide a way to create a binding that gets deregistered and frees up resources once the binding is stabilized. Reducing the number of expressions being watched makes the digest loop faster and allows more information to be displayed at the same time. ### Value stabilization algorithm One-time binding expressions will retain the value of the expression at the end of the digest cycle as long as that value is not undefined. If the value of the expression is set within the digest loop and later, within the same digest loop, it is set to undefined, then the expression is not fulfilled and will remain watched. 1. Given an expression that starts with `::`, when a digest loop is entered and expression is dirty-checked, store the value as V 2. If V is not undefined, mark the result of the expression as stable and schedule a task to deregister the watch for this expression when we exit the digest loop 3. Process the digest loop as normal 4. When digest loop is done and all the values have settled, process the queue of watch deregistration tasks. For each watch to be deregistered, check if it still evaluates to a value that is not `undefined`. If that's the case, deregister the watch. Otherwise, keep dirty-checking the watch in the future digest loops by following the same algorithm starting from step 1 #### Special case for object literals Unlike simple values, object-literals are watched until every key is defined. See http://www.bennadel.com/blog/2760-one-time-data-bindings-for-object-literal-expressions-in-angularjs-1-3.htm ### How to benefit from one-time binding If the expression will not change once set, it is a candidate for one-time binding. Here are three example cases. When interpolating text or attributes: ```html
    text: {{::name | uppercase}}
    ``` When using a directive with bidirectional binding and parameters that will not change: ```js someModule.directive('someDirective', function() { return { scope: { name: '=', color: '@' }, template: '{{name}}: {{color}}' }; }); ``` ```html
    ``` When using a directive that takes an expression: ```html
    • {{item.name}};
    ``` angular.js-1.7.9/docs/content/guide/external-resources.ngdoc000066400000000000000000000254271356472325200241640ustar00rootroot00000000000000@ngdoc overview @name External Resources @sortOrder 150 @description # External AngularJS Resources This is a collection of external, 3rd party resources for learning and developing AngularJS. ## Articles, Videos, and Projects ### Introductory Material * [10 Reasons Why You Should Use AngularJS](http://www.sitepoint.com/10-reasons-use-angularjs/) * [10 Reasons Why Developers Should Learn AngularJS](http://wintellect.com/blogs/jlikness/10-reasons-web-developers-should-learn-angularjs) * [Design Principles of AngularJS (video)](https://www.youtube.com/watch?v=HCR7i5F5L8c) * [Fundamentals in 60 Minutes (video)](http://www.youtube.com/watch?v=i9MHigUZKEM) * [For folks with a jQuery background](http://stackoverflow.com/questions/14994391/how-do-i-think-in-angularjs-if-i-have-a-jquery-background) ### Specific Topics #### Application Structure & Style Guides * [AngularJS Styleguide](https://github.com/johnpapa/angular-styleguide/blob/master/a1/README.md) * [Architecture, file structure, components, one-way dataflow and best practices](https://github.com/toddmotto/angular-styleguide) * [When to use directives, controllers or services](http://kirkbushell.me/when-to-use-directives-controllers-or-services-in-angular/) * [Service vs Factory](http://blog.thoughtram.io/angular/2015/07/07/service-vs-factory-once-and-for-all.html) #### Testing * **Unit testing:** [Using Karma (video)](http://www.youtube.com/watch?v=YG5DEzaQBIc), [Karma in Webstorm](http://blog.jetbrains.com/webstorm/2013/10/running-javascript-tests-with-karma-in-webstorm-7/) #### Mobile * [AngularJS on Mobile Guide](http://www.ng-newsletter.com/posts/angular-on-mobile.html) * [AngularJS and Cordova](http://devgirl.org/2013/06/10/quick-start-guide-phonegap-and-angularjs/) * [Ionic Framework](http://ionicframework.com/) #### Deployment ##### General * **Javascript minification: **[Background](http://thegreenpizza.github.io/2013/05/25/building-minification-safe-angular.js-applications/), [ng-annotate automation tool](https://github.com/olov/ng-annotate) * **Analytics and Logging:** [Angularytics (Google Analytics)](http://ngmodules.org/modules/angularytics), [Angulartics (Analytics)](https://github.com/luisfarzati/angulartics), [Logging Client-Side Errors](http://www.bennadel.com/blog/2542-Logging-Client-Side-Errors-With-AngularJS-And-Stacktrace-js.htm) * **SEO:** [By hand](http://www.yearofmoo.com/2012/11/angularjs-and-seo.html), [prerender.io](http://prerender.io/), [Brombone](http://www.brombone.com/), [SEO.js](http://getseojs.com/), [SEO4Ajax](http://www.seo4ajax.com/) ##### Server-Specific * **Django:** [Tutorial](http://blog.mourafiq.com/post/55034504632/end-to-end-web-app-with-django-rest-framework), [Integrating AngularJS with Django](http://django-angular.readthedocs.org/en/latest/integration.html), [Getting Started with Django Rest Framework and AngularJS](http://blog.kevinastone.com/getting-started-with-django-rest-framework-and-angularjs.html) * **FireBase:** [AngularFire](http://angularfire.com/), [Realtime Apps with AngularJS and FireBase (video)](http://www.youtube.com/watch?v=C7ZI7z7qnHU) * **Google Cloud Platform:** [with Go](https://github.com/GoogleCloudPlatform/appengine-angular-gotodos) * **Hood.ie:** [60 Minutes to Awesome](http://www.roberthorvick.com/2013/06/30/todomvc-angularjs-hood-ie-60-minutes-to-awesome/) * **MEAN Stack: **[Blog post](http://blog.mongodb.org/post/49262866911/the-mean-stack-mongodb-expressjs-angularjs-and), [Setup](http://thecodebarbarian.wordpress.com/2013/07/22/introduction-to-the-mean-stack-part-one-setting-up-your-tools/), [GDL Video](https://developers.google.com/live/shows/913996610) * **Rails: **[Tutorial](http://coderberry.me/blog/2013/04/22/angularjs-on-rails-4-part-1/), [AngularJS with Rails4](https://shellycloud.com/blog/2013/10/how-to-integrate-angularjs-with-rails-4), [angularjs-rails](https://github.com/hiravgandhi/angularjs-rails) * **PHP: **[Building a RESTful web service](http://blog.brunoscopelliti.com/building-a-restful-web-service-with-angularjs-and-php-more-power-with-resource), [End to End with Laravel 4 (video)](http://www.youtube.com/watch?v=hqAyiqUs93c) * **Meteor: **[angular-meteor package](https://github.com/Urigo/angular-meteor) ### Other Languages * [ES6, Webpack, and JSPM Starter Project](https://github.com/AngularClass/NG6-starter) * [ES6/Typescript Best Practices](https://codepen.io/martinmcwhorter/post/angularjs-1-x-with-typescript-or-es6-best-practices) * [Dart](https://github.com/angular/angular.dart.tutorial/wiki) * [CoffeeScript Tutorial](http://www.coffeescriptlove.com/2013/08/angularjs-and-coffeescript-tutorials.html) ### More Topics * **Security:** [video](https://www.youtube.com/watch?v=18ifoT-Id54) * **Internationalization and Localization:** [Creating multilingual support](http://www.novanet.no/blog/hallstein-brotan/dates/2013/10/creating-multilingual-support-using-angularjs/) * **Authentication/Login: **[Google example](https://developers.google.com/+/photohunt/python), [AngularJS Facebook library](https://github.com/pc035860/angular-easyfb), [Facebook example](http://blog.brunoscopelliti.com/facebook-authentication-in-your-angularjs-web-app), [authentication strategy](http://blog.brunoscopelliti.com/deal-with-users-authentication-in-an-angularjs-web-app), [unix-style authorization](http://frederiknakstad.com/authentication-in-single-page-applications-with-angular-js/) * **Visualization:** [SVG](http://gaslight.co/blog/angular-backed-svgs), [D3.js](http://www.ng-newsletter.com/posts/d3-on-angular.html) * **Realtime Communication: **[Socket.io](http://www.creativebloq.com/javascript/angularjs-collaboration-board-socketio-2132885), [OmniBinder](https://github.com/jeffbcross/omnibinder) ## Tools * **Getting Started:** [Comparison of the options for starting a new project](http://www.dancancro.com/comparison-of-angularjs-application-starters/) * **Debugging:** [Batarang](https://chrome.google.com/webstore/detail/angularjs-batarang/ighdmehidhipcmcojjgiloacoafjmpfk?hl=en) * **Editor support:** [Webstorm](http://plugins.jetbrains.com/plugin/6971) (and [video](http://www.youtube.com/watch?v=LJOyrSh1kDU)), [Sublime Text](https://github.com/angular-ui/AngularJS-sublime-package), [Visual Studio](http://madskristensen.net/post/angularjs-intellisense-in-visual-studio-2012), [Atom](https://github.com/angular-ui/AngularJS-Atom), [Vim](https://github.com/burnettk/vim-angular) * **Workflow:** [Yeoman.io](https://github.com/yeoman/generator-angular) and [AngularJS Yeoman Tutorial](http://www.sitepoint.com/kickstart-your-angularjs-development-with-yeoman-grunt-and-bower/) ## Complementary Libraries This is a list of libraries that enhance AngularJS, add common UI components or integrate with other libraries. You can find a larger list of AngularJS external libraries at [ngmodules.org](http://ngmodules.org/). * **Advanced Routing:** [UI-Router](https://github.com/angular-ui/ui-router) * **Authentication:** [Http Auth Interceptor](https://github.com/witoldsz/angular-http-auth) * **Internationalization:** - [angular-translate](http://angular-translate.github.io) - [angular-gettext](http://angular-gettext.rocketeer.be/) - [angular-localization](http://doshprompt.github.io/angular-localization/) * **RESTful services:** [Restangular](https://github.com/mgonto/restangular) * **SQL and NoSQL backends:** - [BreezeJS](http://www.breezejs.com/) - [AngularFire](http://angularfire.com/) * **Data Handling** - Local Storage and session: [ngStorage](https://github.com/gsklee/ngStorage) - [angular-cache](https://github.com/jmdobry/angular-cache) - Data Modeling [JS-Data-Angular](https://github.com/js-data/js-data-angular) * **Fileupload:** - [ng-file-upload](https://github.com/danialfarid/ng-file-upload) - [blueimp-fileupload for AngularJS](https://blueimp.github.io/jQuery-File-Upload/angularjs.html) * **General UI Libraries:** - [AngularJS Material](https://material.angularjs.org/latest/) - [AngularJS UI Bootstrap](http://angular-ui.github.io/) - [AngularStrap for Bootstrap 3](http://mgcrea.github.io/angular-strap/) - [KendoUI](http://kendo-labs.github.io/angular-kendo/#/) - [Wijmo](http://wijmo.com/tag/angularjs-2/) * **Specific UI Elements:** - [ngInfiniteScroll](https://sroze.github.io/ngInfiniteScroll/) - [ngTable](https://github.com/esvit/ng-table) - [AngularJS UI Grid](http://angular-ui.github.io/grid) - [Toaster Notifications](https://github.com/jirikavi/AngularJS-Toaster) - [textAngular Rich Text Editor / contenteditable](http://textangular.com/) (Rich Text Editor / binding to contenteditable) - [AngularJS UI Map (Google Maps)](https://github.com/angular-ui/ui-map) ## General Learning Resources ### Books * [AngularJS Directives](http://www.amazon.com/AngularJS-Directives-Alex-Vanston/dp/1783280336) by Alex Vanston * [AngularJS Essentials (Free eBook)](https://www.packtpub.com/packt/free-ebook/angularjs-essentials) by Rodrigo Branas * [AngularJS in Action](https://www.manning.com/books/angularjs-in-action) by Lukas Ruebbelke * [AngularJS: Novice to Ninja](http://www.amazon.in/AngularJS-Novice-Ninja-Sandeep-Panda/dp/0992279453) by Sandeep Panda * [AngularJS UI Development](http://www.amazon.com/AngularJS-UI-Development-Amit-Ghart-ebook/dp/B00OXVAK7A) by Amit Gharat and Matthias Nehlsen * [AngularJS: Up and Running](http://www.amazon.com/AngularJS-Running-Enhanced-Productivity-Structured/dp/1491901942) by Brad Green and Shyam Seshadri * [Developing an AngularJS Edge](http://www.amazon.com/Developing-AngularJS-Edge-Christopher-Hiller-ebook/dp/B00CJLFF8K) by Christopher Hiller * [Mastering Web App Development](http://www.amazon.com/Mastering-Web-Application-Development-AngularJS/dp/1782161821) by Pawel Kozlowski and Pete Bacon Darwin * [ng-book: The Complete Book on AngularJS](http://ng-book.com/) by Ari Lerner * [Professional AngularJS](http://www.amazon.com/Professional-AngularJS-Valeri-Karpov/dp/1118832078/) * [Recipes With AngularJS](http://www.amazon.co.uk/Recipes-Angular-js-Frederik-Dietz-ebook/dp/B00DK95V48) by Frederik Dietz * [Responsive Web Design with AngularJS](http://www.amazon.com/Responsive-Design-AngularJS-Sandeep-Kumar/dp/178439842X) by Sandeep Kumar Patel ### Videos: * [egghead.io](http://egghead.io/) ### Courses * **Free online:** [thinkster.io](http://thinkster.io), [CodeAcademy](http://www.codecademy.com/courses/javascript-advanced-en-2hJ3J/0/1), [CodeSchool](https://www.codeschool.com/courses/shaping-up-with-angular-js) * **Paid online:** [Pluralsight](https://www.pluralsight.com/search?q=angularjs), [Tuts+](https://tutsplus.com/course/easier-js-apps-with-angular/), [lynda.com](http://www.lynda.com/AngularJS-tutorials/Up-Running-AngularJS/133318-2.html), [WintellectNOW (4 lessons)](http://www.wintellectnow.com/Course/Detail/mastering-angularjs), [Packt](https://www.packtpub.com/web-development/angularjs-maintaining-web-applications) * **Paid onsite:** [angularbootcamp.com](http://angularbootcamp.com/) angular.js-1.7.9/docs/content/guide/filter.ngdoc000066400000000000000000000175731356472325200216220ustar00rootroot00000000000000@ngdoc overview @name Filters @sortOrder 280 @description # Filters Filters format the value of an expression for display to the user. They can be used in view templates, controllers or services. AngularJS comes with a collection of [built-in filters](api/ng/filter), but it is easy to define your own as well. The underlying API is the {@link ng.$filterProvider}. ## Using filters in view templates Filters can be applied to expressions in view templates using the following syntax: {{ expression | filter }} E.g. the markup `{{ 12 | currency }}` formats the number 12 as a currency using the {@link ng.filter:currency `currency`} filter. The resulting value is `$12.00`. Filters can be applied to the result of another filter. This is called "chaining" and uses the following syntax: {{ expression | filter1 | filter2 | ... }} Filters may have arguments. The syntax for this is {{ expression | filter:argument1:argument2:... }} E.g. the markup `{{ 1234 | number:2 }}` formats the number 1234 with 2 decimal points using the {@link ng.filter:number `number`} filter. The resulting value is `1,234.00`. ### When filters are executed In templates, filters are only executed when their inputs have changed. This is more performant than executing a filter on each {@link ng.$rootScope.Scope#$digest `$digest`} as is the case with {@link guide/expression expressions}. There are two exceptions to this rule: 1. In general, this applies only to filters that take [primitive values](https://developer.mozilla.org/docs/Glossary/Primitive) as inputs. Filters that receive [Objects](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Objects) as input are executed on each `$digest`, as it would be too costly to track if the inputs have changed. 2. Filters that are marked as `$stateful` are also executed on each $digest. See {@link guide/filter#stateful-filters Stateful filters} for more information. Note that no AngularJS core filters are $stateful. ## Using filters in controllers, services, and directives You can also use filters in controllers, services, and directives.
    For this, inject a dependency with the name `Filter` into your controller/service/directive. E.g. a filter called `number` is injected by using the dependency `numberFilter`. The injected argument is a function that takes the value to format as first argument, and filter parameters starting with the second argument.
    The example below uses the filter called {@link ng.filter:filter `filter`}. This filter reduces arrays into sub arrays based on conditions. The filter can be applied in the view template with markup like `{{ctrl.array | filter:'a'}}`, which would do a fulltext search for "a". However, using a filter in a view template will reevaluate the filter on every digest, which can be costly if the array is big. The example below therefore calls the filter directly in the controller. By this, the controller is able to call the filter only when needed (e.g. when the data is loaded from the backend or the filter expression is changed).
    All entries: {{entry.name}}
    Entries that contain an "a": {{entry.name}}
    angular.module('FilterInControllerModule', []). controller('FilterController', ['filterFilter', function FilterController(filterFilter) { this.array = [ {name: 'Tobias'}, {name: 'Jeff'}, {name: 'Brian'}, {name: 'Igor'}, {name: 'James'}, {name: 'Brad'} ]; this.filteredArray = filterFilter(this.array, 'a'); }]);
    ## Creating custom filters Writing your own filter is very easy: just register a new filter factory function with your module. Internally, this uses the {@link ng.$filterProvider `filterProvider`}. This factory function should return a new filter function which takes the input value as the first argument. Any filter arguments are passed in as additional arguments to the filter function. The filter function should be a [pure function](http://en.wikipedia.org/wiki/Pure_function), which means that it should always return the same result given the same input arguments and should not affect external state, for example, other AngularJS services. AngularJS relies on this contract and will by default execute a filter only when the inputs to the function change. {@link guide/filter#stateful-filters Stateful filters} are possible, but less performant.
    **Note:** Filter names must be valid AngularJS {@link expression} identifiers, such as `uppercase` or `orderBy`. Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores (`myapp_subsection_filterx`).
    The following sample filter reverses a text string. In addition, it conditionally makes the text upper-case.

    No filter: {{greeting}}
    Reverse: {{greeting|reverse}}
    Reverse + uppercase: {{greeting|reverse:true}}
    Reverse, filtered in controller: {{filteredGreeting}}
    angular.module('myReverseFilterApp', []) .filter('reverse', function() { return function(input, uppercase) { input = input || ''; var out = ''; for (var i = 0; i < input.length; i++) { out = input.charAt(i) + out; } // conditional based on optional argument if (uppercase) { out = out.toUpperCase(); } return out; }; }) .controller('MyController', ['$scope', 'reverseFilter', function($scope, reverseFilter) { $scope.greeting = 'hello'; $scope.filteredGreeting = reverseFilter($scope.greeting); }]);
    ### Stateful filters It is strongly discouraged to write filters that are stateful, because the execution of those can't be optimized by AngularJS, which often leads to performance issues. Many stateful filters can be converted into stateless filters just by exposing the hidden state as a model and turning it into an argument for the filter. If you however do need to write a stateful filter, you have to mark the filter as `$stateful`, which means that it will be executed one or more times during the each `$digest` cycle.
    Input:
    Decoration:
    No filter: {{greeting}}
    Decorated: {{greeting | decorate}}
    angular.module('myStatefulFilterApp', []) .filter('decorate', ['decoration', function(decoration) { function decorateFilter(input) { return decoration.symbol + input + decoration.symbol; } decorateFilter.$stateful = true; return decorateFilter; }]) .controller('MyController', ['$scope', 'decoration', function($scope, decoration) { $scope.greeting = 'hello'; $scope.decoration = decoration; }]) .value('decoration', {symbol: '*'});
    ## Testing custom filters See the [phonecat tutorial](http://docs.angularjs.org/tutorial/step_11#testing) for an example. angular.js-1.7.9/docs/content/guide/forms.ngdoc000066400000000000000000000463311356472325200214550ustar00rootroot00000000000000@ngdoc overview @name Forms @sortOrder 290 @description # Forms Controls (`input`, `select`, `textarea`) are ways for a user to enter data. A Form is a collection of controls for the purpose of grouping related controls together. Form and controls provide validation services, so that the user can be notified of invalid input before submitting a form. This provides a better user experience than server-side validation alone because the user gets instant feedback on how to correct the error. Keep in mind that while client-side validation plays an important role in providing good user experience, it can easily be circumvented and thus can not be trusted. Server-side validation is still necessary for a secure application. ## Simple form The key directive in understanding two-way data-binding is {@link ng.directive:ngModel ngModel}. The `ngModel` directive provides the two-way data-binding by synchronizing the model to the view, as well as view to the model. In addition it provides an {@link ngModel.NgModelController API} for other directives to augment its behavior.


    Best Editor:
    user = {{user | json}}
    master = {{master | json}}
    Note that `novalidate` is used to disable browser's native form validation. The value of `ngModel` won't be set unless it passes validation for the input field. For example: inputs of type `email` must have a value in the form of `user@domain`. ## Using CSS classes To allow styling of form as well as controls, `ngModel` adds these CSS classes: - `ng-valid`: the model is valid - `ng-invalid`: the model is invalid - `ng-valid-[key]`: for each valid key added by `$setValidity` - `ng-invalid-[key]`: for each invalid key added by `$setValidity` - `ng-pristine`: the control hasn't been interacted with yet - `ng-dirty`: the control has been interacted with - `ng-touched`: the control has been blurred - `ng-untouched`: the control hasn't been blurred - `ng-pending`: any `$asyncValidators` are unfulfilled The following example uses the CSS to display validity of each form control. In the example both `user.name` and `user.email` are required, but are rendered with red background only after the input is blurred (loses focus). This ensures that the user is not distracted with an error until after interacting with the control, and failing to satisfy its validity.


    Gender:
    user = {{user | json}}
    master = {{master | json}}
    ## Binding to form and control state A form is an instance of {@link form.FormController FormController}. The form instance can optionally be published into the scope using the `name` attribute. Similarly, an input control that has the {@link ng.directive:ngModel ngModel} directive holds an instance of {@link ngModel.NgModelController NgModelController}. Such a control instance can be published as a property of the form instance using the `name` attribute on the input control. The name attribute specifies the name of the property on the form instance. This implies that the internal state of both the form and the control is available for binding in the view using the standard binding primitives. This allows us to extend the above example with these features: - Custom error message displayed after the user interacted with a control (i.e. when `$touched` is set) - Custom error message displayed upon submitting the form (`$submitted` is set), even if the user didn't interact with a control

    Tell us your name.

    Tell us your email. This is not a valid email.
    Gender:

    Please agree and sign.
    user = {{user | json}}
    master = {{master | json}}
    angular.module('formExample', []) .controller('ExampleController', ['$scope', function($scope) { $scope.master = {}; $scope.update = function(user) { $scope.master = angular.copy(user); }; $scope.reset = function(form) { if (form) { form.$setPristine(); form.$setUntouched(); } $scope.user = angular.copy($scope.master); }; $scope.reset(); }]);
    ## Custom model update triggers By default, any change to the content will trigger a model update and form validation. You can override this behavior using the {@link ng.directive:ngModelOptions ngModelOptions} directive to bind only to specified list of events. I.e. `ng-model-options="{ updateOn: 'blur' }"` will update and validate only after the control loses focus. You can set several events using a space delimited list. I.e. `ng-model-options="{ updateOn: 'mousedown blur' }"` animation showing debounced input If you want to keep the default behavior and just add new events that may trigger the model update and validation, add "default" as one of the specified events. I.e. `ng-model-options="{ updateOn: 'default blur' }"` The following example shows how to override immediate updates. Changes on the inputs within the form will update the model only when the control loses focus (blur event).


    username = "{{user.name}}"
    userdata = "{{user.data}}"
    angular.module('customTriggerExample', []) .controller('ExampleController', ['$scope', function($scope) { $scope.user = {}; }]);
    ## Non-immediate (debounced) model updates You can delay the model update/validation by using the `debounce` key with the {@link ng.directive:ngModelOptions ngModelOptions} directive. This delay will also apply to parsers, validators and model flags like `$dirty` or `$pristine`. animation showing debounced input I.e. `ng-model-options="{ debounce: 500 }"` will wait for half a second since the last content change before triggering the model update and form validation. If custom triggers are used, custom debouncing timeouts can be set for each event using an object in `debounce`. This can be useful to force immediate updates on some specific circumstances (like blur events). I.e. `ng-model-options="{ updateOn: 'default blur', debounce: { default: 500, blur: 0 } }"` If those attributes are added to an element, they will be applied to all the child elements and controls that inherit from it unless they are overridden. This example shows how to debounce model changes. Model will be updated only 250 milliseconds after last change.

    username = "{{user.name}}"
    angular.module('debounceExample', []) .controller('ExampleController', ['$scope', function($scope) { $scope.user = {}; }]);
    ## Custom Validation AngularJS provides basic implementation for most common HTML5 {@link ng.directive:input input} types: ({@link input[text] text}, {@link input[number] number}, {@link input[url] url}, {@link input[email] email}, {@link input[date] date}, {@link input[radio] radio}, {@link input[checkbox] checkbox}), as well as some directives for validation (`required`, `pattern`, `minlength`, `maxlength`, `min`, `max`). With a custom directive, you can add your own validation functions to the `$validators` object on the {@link ngModel.NgModelController `ngModelController`}. To get a hold of the controller, you require it in the directive as shown in the example below. Each function in the `$validators` object receives the `modelValue` and the `viewValue` as parameters. AngularJS will then call `$setValidity` internally with the function's return value (`true`: valid, `false`: invalid). The validation functions are executed every time an input is changed (`$setViewValue` is called) or whenever the bound `model` changes. Validation happens after successfully running `$parsers` and `$formatters`, respectively. Failed validators are stored by key in {@link ngModel.NgModelController#$error `ngModelController.$error`}. Additionally, there is the `$asyncValidators` object which handles asynchronous validation, such as making an `$http` request to the backend. Functions added to the object must return a promise that must be `resolved` when valid or `rejected` when invalid. In-progress async validations are stored by key in {@link ngModel.NgModelController#$pending `ngModelController.$pending`}. In the following example we create two directives: * An `integer` directive that validates whether the input is a valid integer. For example, `1.23` is an invalid value, since it contains a fraction. Note that we validate the viewValue (the string value of the control), and not the modelValue. This is because input[number] converts the viewValue to a number when running the `$parsers`. * A `username` directive that asynchronously checks if a user-entered value is already taken. We mock the server request with a `$q` deferred.

    The value is not a valid integer! The value must be in range 0 to 10!

    Checking if this name is available... This username is already taken!
    var app = angular.module('form-example1', []); var INTEGER_REGEXP = /^-?\d+$/; app.directive('integer', function() { return { require: 'ngModel', link: function(scope, elm, attrs, ctrl) { ctrl.$validators.integer = function(modelValue, viewValue) { if (ctrl.$isEmpty(modelValue)) { // consider empty models to be valid return true; } if (INTEGER_REGEXP.test(viewValue)) { // it is valid return true; } // it is invalid return false; }; } }; }); app.directive('username', function($q, $timeout) { return { require: 'ngModel', link: function(scope, elm, attrs, ctrl) { var usernames = ['Jim', 'John', 'Jill', 'Jackie']; ctrl.$asyncValidators.username = function(modelValue, viewValue) { if (ctrl.$isEmpty(modelValue)) { // consider empty model valid return $q.resolve(); } var def = $q.defer(); $timeout(function() { // Mock a delayed response if (usernames.indexOf(modelValue) === -1) { // The username is available def.resolve(); } else { def.reject(); } }, 2000); return def.promise; }; } }; });
    ## Modifying built-in validators Since AngularJS itself uses `$validators`, you can easily replace or remove built-in validators, should you find it necessary. The following example shows you how to overwrite the email validator in `input[email]` from a custom directive so that it requires a specific top-level domain, `example.com` to be present. Note that you can alternatively use `ng-pattern` to further restrict the validation.
    This email format is invalid!
    Model: {{myEmail}}
    var app = angular.module('form-example-modify-validators', []); app.directive('overwriteEmail', function() { var EMAIL_REGEXP = /^[a-z0-9!#$%&'*+/=?^_`{|}~.-]+@example\.com$/i; return { require: '?ngModel', link: function(scope, elm, attrs, ctrl) { // only apply the validator if ngModel is present and AngularJS has added the email validator if (ctrl && ctrl.$validators.email) { // this will overwrite the default AngularJS email validator ctrl.$validators.email = function(modelValue) { return ctrl.$isEmpty(modelValue) || EMAIL_REGEXP.test(modelValue); }; } } }; });
    ## Implementing custom form controls (using `ngModel`) AngularJS implements all of the basic HTML form controls ({@link ng.directive:input input}, {@link ng.directive:select select}, {@link ng.directive:textarea textarea}), which should be sufficient for most cases. However, if you need more flexibility, you can write your own form control as a directive. In order for custom control to work with `ngModel` and to achieve two-way data-binding it needs to: - implement `$render` method, which is responsible for rendering the data after it passed the {@link ngModel.NgModelController#$formatters `NgModelController.$formatters`}, - call `$setViewValue` method, whenever the user interacts with the control and model needs to be updated. This is usually done inside a DOM Event listener. See {@link guide/directive `$compileProvider.directive`} for more info. The following example shows how to add two-way data-binding to contentEditable elements.
    Some
    model = {{content}}
    angular.module('form-example2', []).directive('contenteditable', function() { return { require: 'ngModel', link: function(scope, elm, attrs, ctrl) { // view -> model elm.on('blur', function() { ctrl.$setViewValue(elm.html()); }); // model -> view ctrl.$render = function() { elm.html(ctrl.$viewValue); }; // load init value from DOM ctrl.$setViewValue(elm.html()); } }; });
    angular.js-1.7.9/docs/content/guide/i18n.ngdoc000066400000000000000000000502451356472325200211050ustar00rootroot00000000000000@ngdoc overview @name i18n and l10n @sortOrder 520 @description # i18n and l10n Internationalization (i18n) is the process of developing products in such a way that they can be localized for languages and cultures easily. Localization (l10n), is the process of adapting applications and text to enable their usability in a particular cultural or linguistic market. For application developers, internationalizing an application means abstracting all of the strings and other locale-specific bits (such as date or currency formats) out of the application. Localizing an application means providing translations and localized formats for the abstracted bits. ## How does AngularJS support i18n/l10n? AngularJS supports i18n/l10n for {@link ng.filter:date date}, {@link ng.filter:number number} and {@link ng.filter:currency currency} filters. Localizable pluralization is supported via the {@link ng.directive:ngPluralize `ngPluralize` directive}. Additionally, you can use {@link guide/i18n#messageformat-extensions MessageFormat extensions} to `$interpolate` for localizable pluralization and gender support in all interpolations via the `ngMessageFormat` module. All localizable AngularJS components depend on locale-specific rule sets managed by the {@link ng.$locale `$locale` service}. There are a few examples that showcase how to use AngularJS filters with various locale rule sets in the [`i18n/e2e` directory](https://github.com/angular/angular.js/tree/master/i18n/e2e) of the AngularJS source code. ## What is a locale ID? A locale is a specific geographical, political, or cultural region. The most commonly used locale ID consists of two parts: language code and country code. For example, `en-US`, `en-AU`, and `zh-CN` are all valid locale IDs that have both language codes and country codes. Because specifying a country code in locale ID is optional, locale IDs such as `en`, `zh`, and `sk` are also valid. See the [ICU](http://userguide.icu-project.org/locale) website for more information about using locale IDs. ## Supported locales in AngularJS AngularJS separates number and datetime format rule sets into different files, each file for a particular locale. You can find a list of currently supported locales [here](https://github.com/angular/angular.js/tree/master/src/ngLocale) ## Providing locale rules to AngularJS There are two approaches to providing locale rules to AngularJS: ### 1. Pre-bundled rule sets You can pre-bundle the desired locale file with AngularJS by concatenating the content of the locale-specific file to the end of `angular.js` or `angular.min.js` file. For example on *nix, to create an angular.js file that contains localization rules for german locale, you can do the following: `cat angular.js i18n/angular-locale_de-de.js > angular_de-de.js` When the application containing `angular_de-de.js` script instead of the generic angular.js script starts, AngularJS is automatically pre-configured with localization rules for the german locale. ### 2. Including a locale script in `index.html` You can also include the locale specific js file in the index.html page. For example, if one client requires German locale, you would serve index_de-de.html which will look something like this: ```html …. …. ``` ### Comparison of the two approaches Both approaches described above require you to prepare different `index.html` pages or JavaScript files for each locale that your app may use. You also need to configure your server to serve the correct file that corresponds to the desired locale. The second approach (including the locale JavaScript file in `index.html`) may be slower because an extra script needs to be loaded. ## Caveats Although AngularJS makes i18n convenient, there are several things you need to be conscious of as you develop your app. ### Currency symbol AngularJS's {@link ng.filter:currency currency filter} allows you to use the default currency symbol from the {@link ng.$locale locale service}, or you can provide the filter with a custom currency symbol.
    **Best Practice:** If your app will be used only in one locale, it is fine to rely on the default currency symbol. If you anticipate that viewers in other locales might use your app, you should explicitly provide a currency symbol.
    Let's say you are writing a banking app and you want to display an account balance of 1000 dollars. You write the following binding using the currency filter: ```html {{ 1000 | currency }} ``` If your app is currently in the `en-US` locale, the browser will show `$1000.00`. If someone in the Japanese locale (`ja`) views your app, their browser will show a balance of `¥1000.00` instead. This is problematic because $1000 is not the same as ¥1000. In this case, you need to override the default currency symbol by providing the {@link ng.filter:currency} currency filter with a currency symbol as a parameter. If we change the above to `{{ 1000 | currency:"USD$"}}`, AngularJS will always show a balance of `USD$1000` regardless of locale. ### Translation length Translated strings/datetime formats can vary greatly in length. For example, `June 3, 1977` will be translated to Spanish as `3 de junio de 1977`. When internationalizing your app, you need to do thorough testing to make sure UI components behave as expected even when their contents vary greatly in content size. ### Timezones The AngularJS datetime filter uses the time zone settings of the browser. The same application will show different time information depending on the time zone settings of the computer that the application is running on. Neither JavaScript nor AngularJS currently supports displaying the date with a timezone specified by the developer. ## MessageFormat extensions You can write localizable plural and gender based messages in AngularJS interpolation expressions and `$interpolate` calls. This syntax extension is provided by way of the `ngMessageFormat` module that your application can depend upon (shipped separately as `angular-message-format.min.js` and `angular-message-format.js`.) A current limitation of the `ngMessageFormat` module, is that it does not support redefining the `$interpolate` start and end symbols. Only the default `{{` and `}}` are allowed. The syntax extension is based on a subset of the ICU MessageFormat syntax that covers plurals and gender selections. Please refer to the links in the “Further Reading” section at the bottom of this section. You may find it helpful to play with the following example as you read the explanations below:
    Set number of recipients

    Sender's name:   

    Recipients
    Name:    Gender:


    Message
    {{recipients.length, plural, offset:1 =0 {You ({{sender.name}}) gave no gifts} =1 { {{ recipients[0].gender, select, male {You ({{sender.name}}) gave him ({{recipients[0].name}}) a gift.} female {You ({{sender.name}}) gave her ({{recipients[0].name}}) a gift.} other {You ({{sender.name}}) gave them ({{recipients[0].name}}) a gift.} }} } one { {{ recipients[0].gender, select, male {You ({{sender.name}}) gave him ({{recipients[0].name}}) and one other person a gift.} female {You ({{sender.name}}) gave her ({{recipients[0].name}}) and one other person a gift.} other {You ({{sender.name}}) gave them ({{recipients[0].name}}) and one other person a gift.} }} } other {You ({{sender.name}}) gave {{recipients.length}} people gifts. } }}

    In an attribute
    This div has an attribute interpolated with messageformat. Use the DOM inspector to check it out.
    function Person(name, gender) { this.name = name; this.gender = gender; } angular.module('messageFormatExample', ['ngMessageFormat']) .controller('ckCtrl', function($scope, $injector, $parse) { var people = [new Person('Alice', 'female'), new Person('Bob', 'male'), new Person('Charlie', 'male')]; $scope.sender = new Person('Harry Potter', 'male'); $scope.recipients = people.slice(); $scope.setNumRecipients = function(n) { n = n > people.length ? people.length : n; $scope.recipients = people.slice(0, n); }; $scope.setGender = function(person, gender) { person.gender = gender; }; });
    ### Plural Syntax The syntax for plural based message selection looks like the following: ```text {{NUMERIC_EXPRESSION, plural, =0 {MESSAGE_WHEN_VALUE_IS_0} =1 {MESSAGE_WHEN_VALUE_IS_1} =2 {MESSAGE_WHEN_VALUE_IS_2} =3 {MESSAGE_WHEN_VALUE_IS_3} ... zero {MESSAGE_WHEN_PLURAL_CATEGORY_IS_ZERO} one {MESSAGE_WHEN_PLURAL_CATEGORY_IS_ONE} two {MESSAGE_WHEN_PLURAL_CATEGORY_IS_TWO} few {MESSAGE_WHEN_PLURAL_CATEGORY_IS_FEW} many {MESSAGE_WHEN_PLURAL_CATEGORY_IS_MANY} other {MESSAGE_WHEN_THERE_IS_NO_MATCH} }} ``` Please note that whitespace (including newline) is generally insignificant except as part of the actual message text that occurs in curly braces. Whitespace is generally used to aid readability. Here, `NUMERIC_EXPRESSION` is an expression that evaluates to a numeric value based on which the displayed message should change based on pluralization rules. Following the AngularJS expression, you would denote the plural extension syntax by the `, plural,` syntax element. The spaces there are optional. This is followed by a list of selection keyword and corresponding message pairs. The "other" keyword and corresponding message are **required** but you may have as few or as many of the other categories as you need. #### Selection Keywords The selection keywords can be either exact matches or language dependent [plural categories](http://cldr.unicode.org/index/cldr-spec/plural-rules). Exact matches are written as the equal sign followed by the exact value. `=0`, `=1`, `=2` and `=123` are all examples of exact matches. Note that there should be no space between the equal sign and the numeric value. Plural category matches are single words corresponding to the [plural categories](http://cldr.unicode.org/index/cldr-spec/plural-rules) of the CLDR plural category spec. These categories vary by locale. The "en" (English) locale, for example, defines just "one" and "other" while the "ga" (Irish) locale defines "one", "two", "few", "many" and "other". Typically, you would just write the categories for your language. During translation, the translators will add or remove more categories depending on the target locale. Exact matches always win over keyword matches. Therefore, if you define both `=0` and `zero`, when the value of the expression is zero, the `=0` message is the one that will be selected. (The duplicate keyword categories are helpful when used with the optional `offset` syntax described later.) #### Messages Messages immediately follow a selection keyword and are optionally preceded by whitespace. They are written in single curly braces (`{}`). They may contain AngularJS interpolation syntax inside them. In addition, the `#` symbol is a placeholder for the actual numeric value of the expression. ### Simple plural example ```text {{numMessages, plural, =0 {You have no new messages} =1 {You have one new message} other {You have # new messages} }} ``` Because these messages can themselves contain AngularJS expressions, you could also write this as follows: ```text {{numMessages, plural, =0 {You have no new messages} =1 {You have one new message} other {You have {{numMessages}} new messages} }} ``` ### Plural syntax with optional `offset` The plural syntax supports an optional `offset` syntax that is used in matching. It's simpler to explain this with an example. ```text {{recipients.length, plural, offset:1 =0 {You gave no gifts} =1 {You gave {{recipients[0].name}} a gift} one {You gave {{recipients[0].name}} and one other person a gift} other {You gave {{recipients[0].name}} and # other people a gift} }} ``` When an `offset` is specified, the matching works as follows. First, the exact value of the AngularJS expression is matched against the exact matches (i.e. `=N` selectors) to find a match. If there is one, that message is used. If there was no match, then the offset value is subtracted from the value of the expression and locale specific pluralization rules are applied to this new value to obtain its plural category (such as “one”, “few”, “many”, etc.) and a match is attempted against the keyword selectors and the matching message is used. If there was no match, then the “other” category (required) is used. The value of the `#` character inside a message is the value of original expression reduced by the offset value that was specified. ### Escaping / Quoting You will need to escape curly braces or the `#` character inside message texts if you want them to be treated literally with no special meaning. You may quote/escape any character in your message text by preceding it with a `\` (backslash) character. The backslash character removes any special meaning to the character that immediately follows it. Therefore, you can escape or quote the backslash itself by preceding it with another backslash character. ### Gender (aka select) Syntax The gender support is provided by the more generic "select" syntax that is more akin to a switch statement. It is general enough to support use for gender based messages. The syntax for gender based message selection looks like the following: ```text {{EXPRESSION, select, male {MESSAGE_WHEN_EXPRESSION_IS_MALE} female {MESSAGE_WHEN_EXPRESSION_IS_FEMALE} ... other {MESSAGE_WHEN_THERE_IS_NO_GENDER_MATCH} }} ``` Please note that whitespace (including newline) is generally insignificant except as part of the actual message text that occurs in curly braces. Whitespace is generally used to aid readability. Here, `EXPRESSION` is an AngularJS expression that evaluates to the gender of the person that is used to select the message that should be displayed. The AngularJS expression is followed by `, select,` where the spaces are optional. This is followed by a list of selection keyword and corresponding message pairs. The "other" keyword and corresponding message are **required** but you may have as few or as many of the other gender values as you need (i.e. it isn't restricted to male/female.) Note however, that the matching is **case-sensitive**. #### Selection Keywords Selection keywords are simple words like "male" and "female". The keyword, "other", and its corresponding message are required while others are optional. It is used when the AngularJS expression does not match (case-insensitively) any of the other keywords specified. #### Messages Messages immediately follow a selection keyword and are optionally preceded by whitespace. They are written in single curly braces (`{}`). They may contain AngularJS interpolation syntax inside them. ### Simple gender example ```text {{friendGender, select, male {Invite him} female {Invite her} other {Invite them} }} ``` ### Nesting As mentioned in the syntax for plural and select, the embedded messages can contain AngularJS interpolation syntax. Since you can use MessageFormat extensions in AngularJS interpolation, this allows you to nest plural and gender expressions in any order. Please note that if these are intended to reach a translator and be translated, it is recommended that the messages appear as a whole and not be split up. ### Demonstration of nesting This is taken from the above example. ```text {{recipients.length, plural, offset:1 =0 {You ({{sender.name}}) gave no gifts} =1 { {{ recipients[0].gender, select, male {You ({{sender.name}}) gave him ({{recipients[0].name}}) a gift.} female {You ({{sender.name}}) gave her ({{recipients[0].name}}) a gift.} other {You ({{sender.name}}) gave them ({{recipients[0].name}}) a gift.} }} } one { {{ recipients[0].gender, select, male {You ({{sender.name}}) gave him ({{recipients[0].name}}) and one other person a gift.} female {You ({{sender.name}}) gave her ({{recipients[0].name}}) and one other person a gift.} other {You ({{sender.name}}) gave them ({{recipients[0].name}}) and one other person a gift.} }} } other {You ({{sender.name}}) gave {{recipients.length}} people gifts. } }} ``` ### Differences from the ICU MessageFormat syntax This section is useful to you if you're already familiar with the ICU MessageFormat syntax. This syntax extension, while based on MessageFormat, has been designed to be backwards compatible with existing AngularJS interpolation expressions. The key rule is simply this: **All interpolations are done inside double curlies.** The top level comma operator after an expression inside the double curlies causes MessageFormat extensions to be recognized. Such a top level comma is otherwise illegal in an AngularJS expression and is used by MessageFormat to specify the function (such as plural/select) and it's related syntax. To understand the extension, take a look at the ICU MessageFormat syntax as specified by the ICU documentation. Anywhere in that MessageFormat that you have regular message text and you want to substitute an expression, just put it in double curlies instead of single curlies that MessageFormat dictates. This has a huge advantage. **You are no longer limited to simple identifiers for substitutions**. Because you are using double curlies, you can stick in any arbitrary interpolation syntax there, including nesting more MessageFormat expressions! ### Further Reading For more details, please refer to our [design doc](https://docs.google.com/a/google.com/document/d/1pbtW2yvtmFBikfRrJd8VAsabiFkKezmYZ_PbgdjQOVU/edit). You can read more about the ICU MessageFormat syntax at [Formatting Messages | ICU User Guide](http://userguide.icu-project.org/formatparse/messages#TOC-MessageFormat). angular.js-1.7.9/docs/content/guide/ie.ngdoc000066400000000000000000000056731356472325200207300ustar00rootroot00000000000000@ngdoc overview @name Internet Explorer Compatibility @sortOrder 530 @description # Internet Explorer Compatibility
    **Note:** AngularJS 1.3 has dropped support for IE8. Read more about it on [our blog](https://blog.angularjs.org/2013/12/angularjs-13-new-release-approaches.html). AngularJS 1.2 will continue to support IE8, but the core team does not plan to spend time addressing issues specific to IE8 or earlier.
    This document describes the Internet Explorer (IE) idiosyncrasies when dealing with custom HTML attributes and tags. Read this document if you are planning on deploying your AngularJS application on IE. The project currently supports and will attempt to fix bugs for IE9 and above. The continuous integration server runs all the tests against IE9, IE10, and IE11. See [Travis CI](https://travis-ci.org/angular/angular.js) and [ci.angularjs.org](https://ci.angularjs.org). We do not run tests on IE8 and below. A subset of the AngularJS functionality may work on these browsers, but it is up to you to test and decide whether it works for your particular app. To ensure your AngularJS application works on IE please consider: 1. Use `ng-style` tags instead of `style="{{ someCss }}"`. The latter works in Chrome, Firefox, Safari and Edge but does not work in Internet Explorer (even 11). 2. For the `type` attribute of buttons, use `ng-attr-type` tags instead of `type="{{ someExpression }}"`. If using the latter, Internet Explorer overwrites the expression with `type="submit"` before AngularJS has a chance to interpolate it. 3. For the `value` attribute of progress, use `ng-attr-value` tags instead of `value="{{ someExpression}}"`. If using the latter, Internet Explorer overwrites the expression with `value="0"` before AngularJS has a chance to interpolate it. 4. For the `placeholder` attribute of textarea, use `ng-attr-placeholder` tags instead of `placeholder="{{ someExpression }}"`. If using the latter, Internet Explorer will error on accessing the `nodeValue` on a parentless `TextNode` in Internet Explorer 10 & 11 (see [issue 5025](https://github.com/angular/angular.js/issues/5025)). 5. Using the `disabled` attribute on an element that has descendant form controls can result in unexpected behavior in Internet Explorer 11. For example, the value of descendant input elements with `ng-model` will not reflect the model (or changes to the model), and the value of the `placeholder` attribute will be inserted as the input's value. Descendant select elements will also be inoperable, as if they had the `disabled` attribute applied to them, which may not be the intended effect. To work around this unexpected behavior, 1) avoid using the identifier `disabled` for custom attribute directives that are on elements with descendant form controls, and 2) avoid using `disabled` as an identifier for an attribute passed to a custom directive that has descendant form controls. angular.js-1.7.9/docs/content/guide/index.ngdoc000066400000000000000000000101201356472325200214210ustar00rootroot00000000000000@ngdoc overview @name Developer Guide @description # Guide to AngularJS Documentation On this page, you will find a list of official AngularJS resources on various topics. Just starting out with AngularJS? Try working through our step by step tutorial or try building on our seed project. * {@link tutorial/index Official AngularJS Tutorial} * [AngularJS Seed](https://github.com/angular/angular-seed) Ready to find out more about AngularJS? * {@link guide/introduction What is AngularJS?} * {@link guide/concepts Conceptual Overview} ## Core Concepts ### Templates In AngularJS applications, you move the job of filling page templates with data from the server to the client. The result is a system better structured for dynamic page updates. Below are the core features you'll use. * {@link guide/databinding Data binding} * {@link guide/expression Expressions} * {@link guide/interpolation Interpolation} * {@link guide/directive Directives} * {@link ngRoute.$route Views and routes (see the example)} * {@link guide/filter Filters} * {@link guide/compiler HTML compiler} * {@link guide/forms Forms} ### Application Structure * **App wiring:** {@link guide/di Dependency injection} * **Exposing model to templates:** {@link guide/scope Scopes} * **Bootstrap:** {@link guide/bootstrap Bootstrapping an app} * **Communicating with servers:** {@link ng.$http $http}, {@link ngResource.$resource $resource} ### Other Features * **Animation:** {@link guide/animations Core concepts}, {@link ngAnimate ngAnimate API} * **Security:** {@link guide/security Security Docs}, {@link ng.$sce Strict Contextual Escaping}, {@link ng.directive:ngCsp Content Security Policy}, {@link ngSanitize.$sanitize $sanitize}, [video](https://www.youtube.com/watch?v=18ifoT-Id54) * **Internationalization and Localization:** {@link guide/i18n AngularJS Guide to i18n and l10n}, {@link ng.filter:date date filter}, {@link ng.filter:currency currency filter}, [Creating multilingual support](https://blog.novanet.no/creating-multilingual-support-using-angularjs/) * **Touch events:** {@link ngTouch Touch events} * **Accessibility:** {@link guide/accessibility ngAria} ### Testing * **Unit testing:** [Karma](http://karma-runner.github.io), {@link guide/unit-testing Unit testing}, {@link guide/services#unit-testing Testing services}, * **End-to-End Testing:** [Protractor](https://github.com/angular/protractor), {@link guide/e2e-testing e2e testing guide} ## Community Resources We have set up a guide to many resources provided by the community, where you can find lots of additional information and material on these topics, a list of complimentary libraries, and much more. * {@link guide/external-resources External AngularJS resources} ## Getting Help The recipe for getting help on your unique issue is to create an example that could work (even if it doesn't) in a shareable example on [Plunker](http://plnkr.co/), [JSFiddle](http://jsfiddle.net/), or similar site and then post to one of the following: * [Stackoverflow.com](http://stackoverflow.com/search?q=angularjs) * [AngularJS mailing list](https://groups.google.com/forum/#!forum/angular) * [AngularJS IRC channel](http://webchat.freenode.net/?channels=angularjs&uio=d4) ## Official Communications Official announcements, news and releases are posted to our blog, G+ and Twitter: * [AngularJS Blog](http://blog.angularjs.org/) * [Google+](https://plus.google.com/u/0/+AngularJS) * [Twitter](https://twitter.com/angular) * [AngularJS on YouTube](http://youtube.com/angularjs) ## Contributing to AngularJS Though we have a core group of core contributors at Google, AngularJS is an open source project with hundreds of contributors. We'd love you to be one of them. When you're ready, please read the {@link misc/contribute Guide for contributing to AngularJS}. ## Something Missing? Didn't find what you're looking for here? Check out the {@link guide/external-resources External AngularJS resources guide}. If you have awesome AngularJS resources that belong on that page, please tell us about them on [Google+](https://plus.google.com/u/0/+AngularJS) or [Twitter](https://twitter.com/angularjs). angular.js-1.7.9/docs/content/guide/interpolation.ngdoc000066400000000000000000000154661356472325200232230ustar00rootroot00000000000000@ngdoc overview @name Interpolation @sortOrder 275 @description # Interpolation and data-binding Interpolation markup with embedded {@link guide/expression expressions} is used by AngularJS to provide data-binding to text nodes and attribute values. An example of interpolation is shown below: ```html Hello {{username}}! ``` ### How text and attribute bindings work During the compilation process the {@link ng.$compile compiler} uses the {@link ng.$interpolate $interpolate} service to see if text nodes and element attributes contain interpolation markup with embedded expressions. If that is the case, the compiler adds an interpolateDirective to the node and registers {@link ng.$rootScope.Scope#$watch watches} on the computed interpolation function, which will update the corresponding text nodes or attribute values as part of the normal {@link ng.$rootScope.Scope#$digest digest} cycle. Note that the interpolateDirective has a priority of 100 and sets up the watch in the preLink function. ### How the string representation is computed If the interpolated value is not a `String`, it is computed as follows: - `undefined` and `null` are converted to `''` - if the value is an object that is not a `Number`, `Date` or `Array`, $interpolate looks for a custom `toString()` function on the object, and uses that. Custom means that `myObject.toString !== Object.prototype.toString`. - if the above doesn't apply, `JSON.stringify` is used. ### Binding to boolean attributes Attributes such as `disabled` are called `boolean` attributes, because their presence means `true` and their absence means `false`. We cannot use normal attribute bindings with them, because the HTML specification does not require browsers to preserve the values of boolean attributes. This means that if we put an AngularJS interpolation expression into such an attribute then the binding information would be lost, because the browser ignores the attribute value. In the following example, the interpolation information would be ignored and the browser would simply interpret the attribute as present, meaning that the button would always be disabled. ```html Disabled: ``` For this reason, AngularJS provides special `ng`-prefixed directives for the following boolean attributes: {@link ngDisabled `disabled`}, {@link ngRequired `required`}, {@link ngSelected `selected`}, {@link ngChecked `checked`}, {@link ngReadonly `readOnly`} , and {@link ngOpen `open`}. These directives take an expression inside the attribute, and set the corresponding boolean attribute to true when the expression evaluates to truthy. ```html Disabled: ``` ### `ngAttr` for binding to arbitrary attributes Web browsers are sometimes picky about what values they consider valid for attributes. For example, considering this template: ```html ``` We would expect AngularJS to be able to bind to this, but when we check the console we see something like `Error: Invalid value for attribute cx="{{cx}}"`. Because of the SVG DOM API's restrictions, you cannot simply write `cx="{{cx}}"`. With `ng-attr-cx` you can work around this problem. If an attribute with a binding is prefixed with the `ngAttr` prefix (denormalized as `ng-attr-`) then during the binding it will be applied to the corresponding unprefixed attribute. This allows you to bind to attributes that would otherwise be eagerly processed by browsers (e.g. an SVG element's `circle[cx]` attributes). When using `ngAttr`, the `allOrNothing` flag of {@link ng.$interpolate $interpolate} is used, so if any expression in the interpolated string results in `undefined`, the attribute is removed and not added to the element. For example, we could fix the example above by instead writing: ```html ``` If one wants to modify a camelcased attribute (SVG elements have valid camelcased attributes), such as `viewBox` on the `svg` element, one can use underscores to denote that the attribute to bind to is naturally camelcased. For example, to bind to `viewBox`, we can write: ```html ``` Other attributes may also not work as expected when they contain interpolation markup, and can be used with `ngAttr` instead. The following is a list of known problematic attributes: - **size** in `
    it('should auto compile', function() { var textarea = $('textarea'); var output = $('div[compile]'); // The initial state reads 'Hello AngularJS'. expect(output.getText()).toBe('Hello AngularJS'); textarea.clear(); textarea.sendKeys('{{name}}!'); expect(output.getText()).toBe('AngularJS!'); }); * * * @param {string|DOMElement} element Element or HTML string to compile into a template function. * @param {function(angular.Scope, cloneAttachFn=)} transclude function available to directives - DEPRECATED. * *
    * **Note:** Passing a `transclude` function to the $compile function is deprecated, as it * e.g. will not use the right outer scope. Please pass the transclude function as a * `parentBoundTranscludeFn` to the link function instead. *
    * * @param {number} maxPriority only apply directives lower than given priority (Only effects the * root element(s), not their children) * @returns {function(scope, cloneAttachFn=, options=)} a link function which is used to bind template * (a DOM element/tree) to a scope. Where: * * * `scope` - A {@link ng.$rootScope.Scope Scope} to bind to. * * `cloneAttachFn` - If `cloneAttachFn` is provided, then the link function will clone the * `template` and call the `cloneAttachFn` function allowing the caller to attach the * cloned elements to the DOM document at the appropriate place. The `cloneAttachFn` is * called as:
    `cloneAttachFn(clonedElement, scope)` where: * * * `clonedElement` - is a clone of the original `element` passed into the compiler. * * `scope` - is the current scope with which the linking function is working with. * * * `options` - An optional object hash with linking options. If `options` is provided, then the following * keys may be used to control linking behavior: * * * `parentBoundTranscludeFn` - the transclude function made available to * directives; if given, it will be passed through to the link functions of * directives found in `element` during compilation. * * `transcludeControllers` - an object hash with keys that map controller names * to a hash with the key `instance`, which maps to the controller instance; * if given, it will make the controllers available to directives on the compileNode: * ``` * { * parent: { * instance: parentControllerInstance * } * } * ``` * * `futureParentElement` - defines the parent to which the `cloneAttachFn` will add * the cloned elements; only needed for transcludes that are allowed to contain non HTML * elements (e.g. SVG elements). See also the `directive.controller` property. * * Calling the linking function returns the element of the template. It is either the original * element passed in, or the clone of the element if the `cloneAttachFn` is provided. * * After linking the view is not updated until after a call to `$digest`, which typically is done by * AngularJS automatically. * * If you need access to the bound view, there are two ways to do it: * * - If you are not asking the linking function to clone the template, create the DOM element(s) * before you send them to the compiler and keep this reference around. * ```js * var element = angular.element('

    {{total}}

    '); * $compile(element)(scope); * ``` * * - if on the other hand, you need the element to be cloned, the view reference from the original * example would not point to the clone, but rather to the original template that was cloned. In * this case, you can access the clone either via the `cloneAttachFn` or the value returned by the * linking function: * ```js * var templateElement = angular.element('

    {{total}}

    '); * var clonedElement = $compile(templateElement)(scope, function(clonedElement, scope) { * // Attach the clone to DOM document at the right place. * }); * * // Now we have reference to the cloned DOM via `clonedElement`. * // NOTE: The `clonedElement` returned by the linking function is the same as the * // `clonedElement` passed to `cloneAttachFn`. * ``` * * * For information on how the compiler works, see the * {@link guide/compiler AngularJS HTML Compiler} section of the Developer Guide. * * @knownIssue * * ### Double Compilation * Double compilation occurs when an already compiled part of the DOM gets compiled again. This is an undesired effect and can lead to misbehaving directives, performance issues, and memory leaks. Refer to the Compiler Guide {@link guide/compiler#double-compilation-and-how-to-avoid-it section on double compilation} for an in-depth explanation and ways to avoid it. * @knownIssue ### Issues with `replace: true` * *
    * **Note**: {@link $compile#-replace- `replace: true`} is deprecated and not recommended to use, * mainly due to the issues listed here. It has been completely removed in the new Angular. *
    * * #### Attribute values are not merged * * When a `replace` directive encounters the same attribute on the original and the replace node, * it will simply deduplicate the attribute and join the values with a space or with a `;` in case of * the `style` attribute. * ```html * Original Node: * Replace Template: * Result: * ``` * * That means attributes that contain AngularJS expressions will not be merged correctly, e.g. * {@link ngShow} or {@link ngClass} will cause a {@link $parse} error: * * ```html * Original Node: * Replace Template: * Result: * ``` * * See issue [#5695](https://github.com/angular/angular.js/issues/5695). * * #### Directives are not deduplicated before compilation * * When the original node and the replace template declare the same directive(s), they will be * {@link guide/compiler#double-compilation-and-how-to-avoid-it compiled twice} because the compiler * does not deduplicate them. In many cases, this is not noticable, but e.g. {@link ngModel} will * attach `$formatters` and `$parsers` twice. * * See issue [#2573](https://github.com/angular/angular.js/issues/2573). * * #### `transclude: element` in the replace template root can have unexpected effects * * When the replace template has a directive at the root node that uses * {@link $compile#-transclude- `transclude: element`}, e.g. * {@link ngIf} or {@link ngRepeat}, the DOM structure or scope inheritance can be incorrect. * See the following issues: * * - Incorrect scope on replaced element: * [#9837](https://github.com/angular/angular.js/issues/9837) * - Different DOM between `template` and `templateUrl`: * [#10612](https://github.com/angular/angular.js/issues/14326) * */ /** * @ngdoc directive * @name ngProp * @restrict A * @element ANY * * @usage * * ```html * * * ``` * * or with uppercase letters in property (e.g. "propName"): * * * ```html * * * ``` * * * @description * The `ngProp` directive binds an expression to a DOM element property. * `ngProp` allows writing to arbitrary properties by including * the property name in the attribute, e.g. `ng-prop-value="'my value'"` binds 'my value' to * the `value` property. * * Usually, it's not necessary to write to properties in AngularJS, as the built-in directives * handle the most common use cases (instead of the above example, you would use {@link ngValue}). * * However, [custom elements](https://developer.mozilla.org/docs/Web/Web_Components/Using_custom_elements) * often use custom properties to hold data, and `ngProp` can be used to provide input to these * custom elements. * * ## Binding to camelCase properties * * Since HTML attributes are case-insensitive, camelCase properties like `innerHTML` must be escaped. * AngularJS uses the underscore (_) in front of a character to indicate that it is uppercase, so * `innerHTML` must be written as `ng-prop-inner_h_t_m_l="expression"` (Note that this is just an * example, and for binding HTML {@link ngBindHtml} should be used. * * ## Security * * Binding expressions to arbitrary properties poses a security risk, as properties like `innerHTML` * can insert potentially dangerous HTML into the application, e.g. script tags that execute * malicious code. * For this reason, `ngProp` applies Strict Contextual Escaping with the {@link ng.$sce $sce service}. * This means vulnerable properties require their content to be "trusted", based on the * context of the property. For example, the `innerHTML` is in the `HTML` context, and the * `iframe.src` property is in the `RESOURCE_URL` context, which requires that values written to * this property are trusted as a `RESOURCE_URL`. * * This can be set explicitly by calling $sce.trustAs(type, value) on the value that is * trusted before passing it to the `ng-prop-*` directive. There are exist shorthand methods for * each context type in the form of {@link ng.$sce#trustAsResourceUrl $sce.trustAsResourceUrl()} et al. * * In some cases you can also rely upon automatic sanitization of untrusted values - see below. * * Based on the context, other options may exist to mark a value as trusted / configure the behavior * of {@link ng.$sce}. For example, to restrict the `RESOURCE_URL` context to specific origins, use * the {@link $sceDelegateProvider#resourceUrlWhitelist resourceUrlWhitelist()} * and {@link $sceDelegateProvider#resourceUrlBlacklist resourceUrlBlacklist()}. * * {@link ng.$sce#what-trusted-context-types-are-supported- Find out more about the different context types}. * * ### HTML Sanitization * * By default, `$sce` will throw an error if it detects untrusted HTML content, and will not bind the * content. * However, if you include the {@link ngSanitize ngSanitize module}, it will try to sanitize the * potentially dangerous HTML, e.g. strip non-whitelisted tags and attributes when binding to * `innerHTML`. * * @example * ### Binding to different contexts * * * * angular.module('exampleNgProp', []) * .component('main', { * templateUrl: 'main.html', * controller: function($sce) { * this.safeContent = 'Safe content'; * this.unsafeContent = ''; * this.trustedUnsafeContent = $sce.trustAsHtml(this.unsafeContent); * } * }); * * *
    *
    * Binding to a property without security context: *
    * innerText (safeContent) *
    * *
    * "Safe" content that requires a security context will throw because the contents could potentially be dangerous ... *
    * innerHTML (safeContent) *
    * *
    * ... so that actually dangerous content cannot be executed: *
    * innerHTML (unsafeContent) *
    * *
    * ... but unsafe Content that has been trusted explicitly works - only do this if you are 100% sure! *
    * innerHTML (trustedUnsafeContent) *
    *
    *
    * *
    *
    * * .prop-unit { * margin-bottom: 10px; * } * * .prop-binding { * min-height: 30px; * border: 1px solid blue; * } * * .prop-note { * font-family: Monospace; * } * *
    * * * @example * ### Binding to innerHTML with ngSanitize * * * * angular.module('exampleNgProp', ['ngSanitize']) * .component('main', { * templateUrl: 'main.html', * controller: function($sce) { * this.safeContent = 'Safe content'; * this.unsafeContent = ''; * this.trustedUnsafeContent = $sce.trustAsHtml(this.unsafeContent); * } * }); * * *
    *
    * "Safe" content will be sanitized ... *
    * innerHTML (safeContent) *
    * *
    * ... as will dangerous content: *
    * innerHTML (unsafeContent) *
    * *
    * ... and content that has been trusted explicitly works the same as without ngSanitize: *
    * innerHTML (trustedUnsafeContent) *
    *
    *
    * *
    *
    * * .prop-unit { * margin-bottom: 10px; * } * * .prop-binding { * min-height: 30px; * border: 1px solid blue; * } * * .prop-note { * font-family: Monospace; * } * *
    * */ /** @ngdoc directive * @name ngOn * @restrict A * @element ANY * * @usage * * ```html * * * ``` * * or with uppercase letters in property (e.g. "eventName"): * * * ```html * * * ``` * * @description * The `ngOn` directive adds an event listener to a DOM element via * {@link angular.element angular.element().on()}, and evaluates an expression when the event is * fired. * `ngOn` allows adding listeners for arbitrary events by including * the event name in the attribute, e.g. `ng-on-drop="onDrop()"` executes the 'onDrop()' expression * when the `drop` event is fired. * * AngularJS provides specific directives for many events, such as {@link ngClick}, so in most * cases it is not necessary to use `ngOn`. However, AngularJS does not support all events * (e.g. the `drop` event in the example above), and new events might be introduced in later DOM * standards. * * Another use-case for `ngOn` is listening to * [custom events](https://developer.mozilla.org/docs/Web/Guide/Events/Creating_and_triggering_events) * fired by * [custom elements](https://developer.mozilla.org/docs/Web/Web_Components/Using_custom_elements). * * ## Binding to camelCase properties * * Since HTML attributes are case-insensitive, camelCase properties like `myEvent` must be escaped. * AngularJS uses the underscore (_) in front of a character to indicate that it is uppercase, so * `myEvent` must be written as `ng-on-my_event="expression"`. * * @example * ### Bind to built-in DOM events * * * * angular.module('exampleNgOn', []) * .component('main', { * templateUrl: 'main.html', * controller: function() { * this.clickCount = 0; * this.mouseoverCount = 0; * * this.loadingState = 0; * } * }); * * *
    * This is equivalent to `ngClick` and `ngMouseover`:
    *
    * clickCount: {{$ctrl.clickCount}}
    * mouseover: {{$ctrl.mouseoverCount}} * *
    * * For the `error` and `load` event on images no built-in AngularJS directives exist:
    *
    *
    * Image is loading * Image load error * Image loaded successfully *
    *
    *
    * *
    *
    *
    * * * @example * ### Bind to custom DOM events * * * * angular.module('exampleNgOn', []) * .component('main', { * templateUrl: 'main.html', * controller: function() { * this.eventLog = ''; * * this.listener = function($event) { * this.eventLog = 'Event with type "' + $event.type + '" fired at ' + $event.detail; * }; * } * }) * .component('childComponent', { * templateUrl: 'child.html', * controller: function($element) { * this.fireEvent = function() { * var event = new CustomEvent('customtype', { detail: new Date()}); * * $element[0].dispatchEvent(event); * }; * } * }); * * *
    * Event log: {{$ctrl.eventLog}} *
    * * * *
    *
    *
    */ var $compileMinErr = minErr('$compile'); function UNINITIALIZED_VALUE() {} var _UNINITIALIZED_VALUE = new UNINITIALIZED_VALUE(); /** * @ngdoc provider * @name $compileProvider * * @description */ $CompileProvider.$inject = ['$provide', '$$sanitizeUriProvider']; /** @this */ function $CompileProvider($provide, $$sanitizeUriProvider) { var hasDirectives = {}, Suffix = 'Directive', COMMENT_DIRECTIVE_REGEXP = /^\s*directive:\s*([\w-]+)\s+(.*)$/, CLASS_DIRECTIVE_REGEXP = /(([\w-]+)(?::([^;]+))?;?)/, ALL_OR_NOTHING_ATTRS = makeMap('ngSrc,ngSrcset,src,srcset'), REQUIRE_PREFIX_REGEXP = /^(?:(\^\^?)?(\?)?(\^\^?)?)?/; // Ref: http://developers.whatwg.org/webappapis.html#event-handler-idl-attributes // The assumption is that future DOM event attribute names will begin with // 'on' and be composed of only English letters. var EVENT_HANDLER_ATTR_REGEXP = /^(on[a-z]+|formaction)$/; var bindingCache = createMap(); function parseIsolateBindings(scope, directiveName, isController) { var LOCAL_REGEXP = /^([@&]|[=<](\*?))(\??)\s*([\w$]*)$/; var bindings = createMap(); forEach(scope, function(definition, scopeName) { definition = definition.trim(); if (definition in bindingCache) { bindings[scopeName] = bindingCache[definition]; return; } var match = definition.match(LOCAL_REGEXP); if (!match) { throw $compileMinErr('iscp', 'Invalid {3} for directive \'{0}\'.' + ' Definition: {... {1}: \'{2}\' ...}', directiveName, scopeName, definition, (isController ? 'controller bindings definition' : 'isolate scope definition')); } bindings[scopeName] = { mode: match[1][0], collection: match[2] === '*', optional: match[3] === '?', attrName: match[4] || scopeName }; if (match[4]) { bindingCache[definition] = bindings[scopeName]; } }); return bindings; } function parseDirectiveBindings(directive, directiveName) { var bindings = { isolateScope: null, bindToController: null }; if (isObject(directive.scope)) { if (directive.bindToController === true) { bindings.bindToController = parseIsolateBindings(directive.scope, directiveName, true); bindings.isolateScope = {}; } else { bindings.isolateScope = parseIsolateBindings(directive.scope, directiveName, false); } } if (isObject(directive.bindToController)) { bindings.bindToController = parseIsolateBindings(directive.bindToController, directiveName, true); } if (bindings.bindToController && !directive.controller) { // There is no controller throw $compileMinErr('noctrl', 'Cannot bind to controller without directive \'{0}\'s controller.', directiveName); } return bindings; } function assertValidDirectiveName(name) { var letter = name.charAt(0); if (!letter || letter !== lowercase(letter)) { throw $compileMinErr('baddir', 'Directive/Component name \'{0}\' is invalid. The first character must be a lowercase letter', name); } if (name !== name.trim()) { throw $compileMinErr('baddir', 'Directive/Component name \'{0}\' is invalid. The name should not contain leading or trailing whitespaces', name); } } function getDirectiveRequire(directive) { var require = directive.require || (directive.controller && directive.name); if (!isArray(require) && isObject(require)) { forEach(require, function(value, key) { var match = value.match(REQUIRE_PREFIX_REGEXP); var name = value.substring(match[0].length); if (!name) require[key] = match[0] + key; }); } return require; } function getDirectiveRestrict(restrict, name) { if (restrict && !(isString(restrict) && /[EACM]/.test(restrict))) { throw $compileMinErr('badrestrict', 'Restrict property \'{0}\' of directive \'{1}\' is invalid', restrict, name); } return restrict || 'EA'; } /** * @ngdoc method * @name $compileProvider#directive * @kind function * * @description * Register a new directive with the compiler. * * @param {string|Object} name Name of the directive in camel-case (i.e. `ngBind` which will match * as `ng-bind`), or an object map of directives where the keys are the names and the values * are the factories. * @param {Function|Array} directiveFactory An injectable directive factory function. See the * {@link guide/directive directive guide} and the {@link $compile compile API} for more info. * @returns {ng.$compileProvider} Self for chaining. */ this.directive = function registerDirective(name, directiveFactory) { assertArg(name, 'name'); assertNotHasOwnProperty(name, 'directive'); if (isString(name)) { assertValidDirectiveName(name); assertArg(directiveFactory, 'directiveFactory'); if (!hasDirectives.hasOwnProperty(name)) { hasDirectives[name] = []; $provide.factory(name + Suffix, ['$injector', '$exceptionHandler', function($injector, $exceptionHandler) { var directives = []; forEach(hasDirectives[name], function(directiveFactory, index) { try { var directive = $injector.invoke(directiveFactory); if (isFunction(directive)) { directive = { compile: valueFn(directive) }; } else if (!directive.compile && directive.link) { directive.compile = valueFn(directive.link); } directive.priority = directive.priority || 0; directive.index = index; directive.name = directive.name || name; directive.require = getDirectiveRequire(directive); directive.restrict = getDirectiveRestrict(directive.restrict, name); directive.$$moduleName = directiveFactory.$$moduleName; directives.push(directive); } catch (e) { $exceptionHandler(e); } }); return directives; }]); } hasDirectives[name].push(directiveFactory); } else { forEach(name, reverseParams(registerDirective)); } return this; }; /** * @ngdoc method * @name $compileProvider#component * @module ng * @param {string|Object} name Name of the component in camelCase (i.e. `myComp` which will match ``), * or an object map of components where the keys are the names and the values are the component definition objects. * @param {Object} options Component definition object (a simplified * {@link ng.$compile#directive-definition-object directive definition object}), * with the following properties (all optional): * * - `controller` – `{(string|function()=}` – controller constructor function that should be * associated with newly created scope or the name of a {@link ng.$compile#-controller- * registered controller} if passed as a string. An empty `noop` function by default. * - `controllerAs` – `{string=}` – identifier name for to reference the controller in the component's scope. * If present, the controller will be published to scope under the `controllerAs` name. * If not present, this will default to be `$ctrl`. * - `template` – `{string=|function()=}` – html template as a string or a function that * returns an html template as a string which should be used as the contents of this component. * Empty string by default. * * If `template` is a function, then it is {@link auto.$injector#invoke injected} with * the following locals: * * - `$element` - Current element * - `$attrs` - Current attributes object for the element * * - `templateUrl` – `{string=|function()=}` – path or function that returns a path to an html * template that should be used as the contents of this component. * * If `templateUrl` is a function, then it is {@link auto.$injector#invoke injected} with * the following locals: * * - `$element` - Current element * - `$attrs` - Current attributes object for the element * * - `bindings` – `{object=}` – defines bindings between DOM attributes and component properties. * Component properties are always bound to the component controller and not to the scope. * See {@link ng.$compile#-bindtocontroller- `bindToController`}. * - `transclude` – `{boolean=}` – whether {@link $compile#transclusion content transclusion} is enabled. * Disabled by default. * - `require` - `{Object=}` - requires the controllers of other directives and binds them to * this component's controller. The object keys specify the property names under which the required * controllers (object values) will be bound. See {@link ng.$compile#-require- `require`}. * - `$...` – additional properties to attach to the directive factory function and the controller * constructor function. (This is used by the component router to annotate) * * @returns {ng.$compileProvider} the compile provider itself, for chaining of function calls. * @description * Register a **component definition** with the compiler. This is a shorthand for registering a special * type of directive, which represents a self-contained UI component in your application. Such components * are always isolated (i.e. `scope: {}`) and are always restricted to elements (i.e. `restrict: 'E'`). * * Component definitions are very simple and do not require as much configuration as defining general * directives. Component definitions usually consist only of a template and a controller backing it. * * In order to make the definition easier, components enforce best practices like use of `controllerAs`, * `bindToController`. They always have **isolate scope** and are restricted to elements. * * Here are a few examples of how you would usually define components: * * ```js * var myMod = angular.module(...); * myMod.component('myComp', { * template: '
    My name is {{$ctrl.name}}
    ', * controller: function() { * this.name = 'shahar'; * } * }); * * myMod.component('myComp', { * template: '
    My name is {{$ctrl.name}}
    ', * bindings: {name: '@'} * }); * * myMod.component('myComp', { * templateUrl: 'views/my-comp.html', * controller: 'MyCtrl', * controllerAs: 'ctrl', * bindings: {name: '@'} * }); * * ``` * For more examples, and an in-depth guide, see the {@link guide/component component guide}. * *
    * See also {@link ng.$compileProvider#directive $compileProvider.directive()}. */ this.component = function registerComponent(name, options) { if (!isString(name)) { forEach(name, reverseParams(bind(this, registerComponent))); return this; } var controller = options.controller || function() {}; function factory($injector) { function makeInjectable(fn) { if (isFunction(fn) || isArray(fn)) { return /** @this */ function(tElement, tAttrs) { return $injector.invoke(fn, this, {$element: tElement, $attrs: tAttrs}); }; } else { return fn; } } var template = (!options.template && !options.templateUrl ? '' : options.template); var ddo = { controller: controller, controllerAs: identifierForController(options.controller) || options.controllerAs || '$ctrl', template: makeInjectable(template), templateUrl: makeInjectable(options.templateUrl), transclude: options.transclude, scope: {}, bindToController: options.bindings || {}, restrict: 'E', require: options.require }; // Copy annotations (starting with $) over to the DDO forEach(options, function(val, key) { if (key.charAt(0) === '$') ddo[key] = val; }); return ddo; } // TODO(pete) remove the following `forEach` before we release 1.6.0 // The component-router@0.2.0 looks for the annotations on the controller constructor // Nothing in AngularJS looks for annotations on the factory function but we can't remove // it from 1.5.x yet. // Copy any annotation properties (starting with $) over to the factory and controller constructor functions // These could be used by libraries such as the new component router forEach(options, function(val, key) { if (key.charAt(0) === '$') { factory[key] = val; // Don't try to copy over annotations to named controller if (isFunction(controller)) controller[key] = val; } }); factory.$inject = ['$injector']; return this.directive(name, factory); }; /** * @ngdoc method * @name $compileProvider#aHrefSanitizationWhitelist * @kind function * * @description * Retrieves or overrides the default regular expression that is used for whitelisting of safe * urls during a[href] sanitization. * * The sanitization is a security measure aimed at preventing XSS attacks via html links. * * Any url about to be assigned to a[href] via data-binding is first normalized and turned into * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist` * regular expression. If a match is found, the original url is written into the dom. Otherwise, * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. * * @param {RegExp=} regexp New regexp to whitelist urls with. * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for * chaining otherwise. */ this.aHrefSanitizationWhitelist = function(regexp) { if (isDefined(regexp)) { $$sanitizeUriProvider.aHrefSanitizationWhitelist(regexp); return this; } else { return $$sanitizeUriProvider.aHrefSanitizationWhitelist(); } }; /** * @ngdoc method * @name $compileProvider#imgSrcSanitizationWhitelist * @kind function * * @description * Retrieves or overrides the default regular expression that is used for whitelisting of safe * urls during img[src] sanitization. * * The sanitization is a security measure aimed at prevent XSS attacks via html links. * * Any url about to be assigned to img[src] via data-binding is first normalized and turned into * an absolute url. Afterwards, the url is matched against the `imgSrcSanitizationWhitelist` * regular expression. If a match is found, the original url is written into the dom. Otherwise, * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. * * @param {RegExp=} regexp New regexp to whitelist urls with. * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for * chaining otherwise. */ this.imgSrcSanitizationWhitelist = function(regexp) { if (isDefined(regexp)) { $$sanitizeUriProvider.imgSrcSanitizationWhitelist(regexp); return this; } else { return $$sanitizeUriProvider.imgSrcSanitizationWhitelist(); } }; /** * @ngdoc method * @name $compileProvider#debugInfoEnabled * * @param {boolean=} enabled update the debugInfoEnabled state if provided, otherwise just return the * current debugInfoEnabled state * @returns {*} current value if used as getter or itself (chaining) if used as setter * * @kind function * * @description * Call this method to enable/disable various debug runtime information in the compiler such as adding * binding information and a reference to the current scope on to DOM elements. * If enabled, the compiler will add the following to DOM elements that have been bound to the scope * * `ng-binding` CSS class * * `ng-scope` and `ng-isolated-scope` CSS classes * * `$binding` data property containing an array of the binding expressions * * Data properties used by the {@link angular.element#methods `scope()`/`isolateScope()` methods} to return * the element's scope. * * Placeholder comments will contain information about what directive and binding caused the placeholder. * E.g. ``. * * You may want to disable this in production for a significant performance boost. See * {@link guide/production#disabling-debug-data Disabling Debug Data} for more. * * The default value is true. */ var debugInfoEnabled = true; this.debugInfoEnabled = function(enabled) { if (isDefined(enabled)) { debugInfoEnabled = enabled; return this; } return debugInfoEnabled; }; /** * @ngdoc method * @name $compileProvider#strictComponentBindingsEnabled * * @param {boolean=} enabled update the strictComponentBindingsEnabled state if provided, * otherwise return the current strictComponentBindingsEnabled state. * @returns {*} current value if used as getter or itself (chaining) if used as setter * * @kind function * * @description * Call this method to enable / disable the strict component bindings check. If enabled, the * compiler will enforce that all scope / controller bindings of a * {@link $compileProvider#directive directive} / {@link $compileProvider#component component} * that are not set as optional with `?`, must be provided when the directive is instantiated. * If not provided, the compiler will throw the * {@link error/$compile/missingattr $compile:missingattr error}. * * The default value is false. */ var strictComponentBindingsEnabled = false; this.strictComponentBindingsEnabled = function(enabled) { if (isDefined(enabled)) { strictComponentBindingsEnabled = enabled; return this; } return strictComponentBindingsEnabled; }; var TTL = 10; /** * @ngdoc method * @name $compileProvider#onChangesTtl * @description * * Sets the number of times `$onChanges` hooks can trigger new changes before giving up and * assuming that the model is unstable. * * The current default is 10 iterations. * * In complex applications it's possible that dependencies between `$onChanges` hooks and bindings will result * in several iterations of calls to these hooks. However if an application needs more than the default 10 * iterations to stabilize then you should investigate what is causing the model to continuously change during * the `$onChanges` hook execution. * * Increasing the TTL could have performance implications, so you should not change it without proper justification. * * @param {number} limit The number of `$onChanges` hook iterations. * @returns {number|object} the current limit (or `this` if called as a setter for chaining) */ this.onChangesTtl = function(value) { if (arguments.length) { TTL = value; return this; } return TTL; }; var commentDirectivesEnabledConfig = true; /** * @ngdoc method * @name $compileProvider#commentDirectivesEnabled * @description * * It indicates to the compiler * whether or not directives on comments should be compiled. * Defaults to `true`. * * Calling this function with false disables the compilation of directives * on comments for the whole application. * This results in a compilation performance gain, * as the compiler doesn't have to check comments when looking for directives. * This should however only be used if you are sure that no comment directives are used in * the application (including any 3rd party directives). * * @param {boolean} enabled `false` if the compiler may ignore directives on comments * @returns {boolean|object} the current value (or `this` if called as a setter for chaining) */ this.commentDirectivesEnabled = function(value) { if (arguments.length) { commentDirectivesEnabledConfig = value; return this; } return commentDirectivesEnabledConfig; }; var cssClassDirectivesEnabledConfig = true; /** * @ngdoc method * @name $compileProvider#cssClassDirectivesEnabled * @description * * It indicates to the compiler * whether or not directives on element classes should be compiled. * Defaults to `true`. * * Calling this function with false disables the compilation of directives * on element classes for the whole application. * This results in a compilation performance gain, * as the compiler doesn't have to check element classes when looking for directives. * This should however only be used if you are sure that no class directives are used in * the application (including any 3rd party directives). * * @param {boolean} enabled `false` if the compiler may ignore directives on element classes * @returns {boolean|object} the current value (or `this` if called as a setter for chaining) */ this.cssClassDirectivesEnabled = function(value) { if (arguments.length) { cssClassDirectivesEnabledConfig = value; return this; } return cssClassDirectivesEnabledConfig; }; /** * The security context of DOM Properties. * @private */ var PROP_CONTEXTS = createMap(); /** * @ngdoc method * @name $compileProvider#addPropertySecurityContext * @description * * Defines the security context for DOM properties bound by ng-prop-*. * * @param {string} elementName The element name or '*' to match any element. * @param {string} propertyName The DOM property name. * @param {string} ctx The {@link $sce} security context in which this value is safe for use, e.g. `$sce.URL` * @returns {object} `this` for chaining */ this.addPropertySecurityContext = function(elementName, propertyName, ctx) { var key = (elementName.toLowerCase() + '|' + propertyName.toLowerCase()); if (key in PROP_CONTEXTS && PROP_CONTEXTS[key] !== ctx) { throw $compileMinErr('ctxoverride', 'Property context \'{0}.{1}\' already set to \'{2}\', cannot override to \'{3}\'.', elementName, propertyName, PROP_CONTEXTS[key], ctx); } PROP_CONTEXTS[key] = ctx; return this; }; /* Default property contexts. * * Copy of https://github.com/angular/angular/blob/6.0.6/packages/compiler/src/schema/dom_security_schema.ts#L31-L58 * Changing: * - SecurityContext.* => SCE_CONTEXTS/$sce.* * - STYLE => CSS * - various URL => MEDIA_URL * - *|formAction, form|action URL => RESOURCE_URL (like the attribute) */ (function registerNativePropertyContexts() { function registerContext(ctx, values) { forEach(values, function(v) { PROP_CONTEXTS[v.toLowerCase()] = ctx; }); } registerContext(SCE_CONTEXTS.HTML, [ 'iframe|srcdoc', '*|innerHTML', '*|outerHTML' ]); registerContext(SCE_CONTEXTS.CSS, ['*|style']); registerContext(SCE_CONTEXTS.URL, [ 'area|href', 'area|ping', 'a|href', 'a|ping', 'blockquote|cite', 'body|background', 'del|cite', 'input|src', 'ins|cite', 'q|cite' ]); registerContext(SCE_CONTEXTS.MEDIA_URL, [ 'audio|src', 'img|src', 'img|srcset', 'source|src', 'source|srcset', 'track|src', 'video|src', 'video|poster' ]); registerContext(SCE_CONTEXTS.RESOURCE_URL, [ '*|formAction', 'applet|code', 'applet|codebase', 'base|href', 'embed|src', 'frame|src', 'form|action', 'head|profile', 'html|manifest', 'iframe|src', 'link|href', 'media|src', 'object|codebase', 'object|data', 'script|src' ]); })(); this.$get = [ '$injector', '$interpolate', '$exceptionHandler', '$templateRequest', '$parse', '$controller', '$rootScope', '$sce', '$animate', function($injector, $interpolate, $exceptionHandler, $templateRequest, $parse, $controller, $rootScope, $sce, $animate) { var SIMPLE_ATTR_NAME = /^\w/; var specialAttrHolder = window.document.createElement('div'); var commentDirectivesEnabled = commentDirectivesEnabledConfig; var cssClassDirectivesEnabled = cssClassDirectivesEnabledConfig; var onChangesTtl = TTL; // The onChanges hooks should all be run together in a single digest // When changes occur, the call to trigger their hooks will be added to this queue var onChangesQueue; // This function is called in a $$postDigest to trigger all the onChanges hooks in a single digest function flushOnChangesQueue() { try { if (!(--onChangesTtl)) { // We have hit the TTL limit so reset everything onChangesQueue = undefined; throw $compileMinErr('infchng', '{0} $onChanges() iterations reached. Aborting!\n', TTL); } // We must run this hook in an apply since the $$postDigest runs outside apply $rootScope.$apply(function() { for (var i = 0, ii = onChangesQueue.length; i < ii; ++i) { try { onChangesQueue[i](); } catch (e) { $exceptionHandler(e); } } // Reset the queue to trigger a new schedule next time there is a change onChangesQueue = undefined; }); } finally { onChangesTtl++; } } function sanitizeSrcset(value, invokeType) { if (!value) { return value; } if (!isString(value)) { throw $compileMinErr('srcset', 'Can\'t pass trusted values to `{0}`: "{1}"', invokeType, value.toString()); } // Such values are a bit too complex to handle automatically inside $sce. // Instead, we sanitize each of the URIs individually, which works, even dynamically. // It's not possible to work around this using `$sce.trustAsMediaUrl`. // If you want to programmatically set explicitly trusted unsafe URLs, you should use // `$sce.trustAsHtml` on the whole `img` tag and inject it into the DOM using the // `ng-bind-html` directive. var result = ''; // first check if there are spaces because it's not the same pattern var trimmedSrcset = trim(value); // ( 999x ,| 999w ,| ,|, ) var srcPattern = /(\s+\d+x\s*,|\s+\d+w\s*,|\s+,|,\s+)/; var pattern = /\s/.test(trimmedSrcset) ? srcPattern : /(,)/; // split srcset into tuple of uri and descriptor except for the last item var rawUris = trimmedSrcset.split(pattern); // for each tuples var nbrUrisWith2parts = Math.floor(rawUris.length / 2); for (var i = 0; i < nbrUrisWith2parts; i++) { var innerIdx = i * 2; // sanitize the uri result += $sce.getTrustedMediaUrl(trim(rawUris[innerIdx])); // add the descriptor result += ' ' + trim(rawUris[innerIdx + 1]); } // split the last item into uri and descriptor var lastTuple = trim(rawUris[i * 2]).split(/\s/); // sanitize the last uri result += $sce.getTrustedMediaUrl(trim(lastTuple[0])); // and add the last descriptor if any if (lastTuple.length === 2) { result += (' ' + trim(lastTuple[1])); } return result; } function Attributes(element, attributesToCopy) { if (attributesToCopy) { var keys = Object.keys(attributesToCopy); var i, l, key; for (i = 0, l = keys.length; i < l; i++) { key = keys[i]; this[key] = attributesToCopy[key]; } } else { this.$attr = {}; } this.$$element = element; } Attributes.prototype = { /** * @ngdoc method * @name $compile.directive.Attributes#$normalize * @kind function * * @description * Converts an attribute name (e.g. dash/colon/underscore-delimited string, optionally prefixed with `x-` or * `data-`) to its normalized, camelCase form. * * Also there is special case for Moz prefix starting with upper case letter. * * For further information check out the guide on {@link guide/directive#matching-directives Matching Directives} * * @param {string} name Name to normalize */ $normalize: directiveNormalize, /** * @ngdoc method * @name $compile.directive.Attributes#$addClass * @kind function * * @description * Adds the CSS class value specified by the classVal parameter to the element. If animations * are enabled then an animation will be triggered for the class addition. * * @param {string} classVal The className value that will be added to the element */ $addClass: function(classVal) { if (classVal && classVal.length > 0) { $animate.addClass(this.$$element, classVal); } }, /** * @ngdoc method * @name $compile.directive.Attributes#$removeClass * @kind function * * @description * Removes the CSS class value specified by the classVal parameter from the element. If * animations are enabled then an animation will be triggered for the class removal. * * @param {string} classVal The className value that will be removed from the element */ $removeClass: function(classVal) { if (classVal && classVal.length > 0) { $animate.removeClass(this.$$element, classVal); } }, /** * @ngdoc method * @name $compile.directive.Attributes#$updateClass * @kind function * * @description * Adds and removes the appropriate CSS class values to the element based on the difference * between the new and old CSS class values (specified as newClasses and oldClasses). * * @param {string} newClasses The current CSS className value * @param {string} oldClasses The former CSS className value */ $updateClass: function(newClasses, oldClasses) { var toAdd = tokenDifference(newClasses, oldClasses); if (toAdd && toAdd.length) { $animate.addClass(this.$$element, toAdd); } var toRemove = tokenDifference(oldClasses, newClasses); if (toRemove && toRemove.length) { $animate.removeClass(this.$$element, toRemove); } }, /** * Set a normalized attribute on the element in a way such that all directives * can share the attribute. This function properly handles boolean attributes. * @param {string} key Normalized key. (ie ngAttribute) * @param {string|boolean} value The value to set. If `null` attribute will be deleted. * @param {boolean=} writeAttr If false, does not write the value to DOM element attribute. * Defaults to true. * @param {string=} attrName Optional none normalized name. Defaults to key. */ $set: function(key, value, writeAttr, attrName) { // TODO: decide whether or not to throw an error if "class" // is set through this function since it may cause $updateClass to // become unstable. var node = this.$$element[0], booleanKey = getBooleanAttrName(node, key), aliasedKey = getAliasedAttrName(key), observer = key, nodeName; if (booleanKey) { this.$$element.prop(key, value); attrName = booleanKey; } else if (aliasedKey) { this[aliasedKey] = value; observer = aliasedKey; } this[key] = value; // translate normalized key to actual key if (attrName) { this.$attr[key] = attrName; } else { attrName = this.$attr[key]; if (!attrName) { this.$attr[key] = attrName = snake_case(key, '-'); } } nodeName = nodeName_(this.$$element); // Sanitize img[srcset] values. if (nodeName === 'img' && key === 'srcset') { this[key] = value = sanitizeSrcset(value, '$set(\'srcset\', value)'); } if (writeAttr !== false) { if (value === null || isUndefined(value)) { this.$$element.removeAttr(attrName); } else { if (SIMPLE_ATTR_NAME.test(attrName)) { // jQuery skips special boolean attrs treatment in XML nodes for // historical reasons and hence AngularJS cannot freely call // `.attr(attrName, false) with such attributes. To avoid issues // in XHTML, call `removeAttr` in such cases instead. // See https://github.com/jquery/jquery/issues/4249 if (booleanKey && value === false) { this.$$element.removeAttr(attrName); } else { this.$$element.attr(attrName, value); } } else { setSpecialAttr(this.$$element[0], attrName, value); } } } // fire observers var $$observers = this.$$observers; if ($$observers) { forEach($$observers[observer], function(fn) { try { fn(value); } catch (e) { $exceptionHandler(e); } }); } }, /** * @ngdoc method * @name $compile.directive.Attributes#$observe * @kind function * * @description * Observes an interpolated attribute. * * The observer function will be invoked once during the next `$digest` following * compilation. The observer is then invoked whenever the interpolated value * changes. * * @param {string} key Normalized key. (ie ngAttribute) . * @param {function(interpolatedValue)} fn Function that will be called whenever the interpolated value of the attribute changes. * See the {@link guide/interpolation#how-text-and-attribute-bindings-work Interpolation * guide} for more info. * @returns {function()} Returns a deregistration function for this observer. */ $observe: function(key, fn) { var attrs = this, $$observers = (attrs.$$observers || (attrs.$$observers = createMap())), listeners = ($$observers[key] || ($$observers[key] = [])); listeners.push(fn); $rootScope.$evalAsync(function() { if (!listeners.$$inter && attrs.hasOwnProperty(key) && !isUndefined(attrs[key])) { // no one registered attribute interpolation function, so lets call it manually fn(attrs[key]); } }); return function() { arrayRemove(listeners, fn); }; } }; function setSpecialAttr(element, attrName, value) { // Attributes names that do not start with letters (such as `(click)`) cannot be set using `setAttribute` // so we have to jump through some hoops to get such an attribute // https://github.com/angular/angular.js/pull/13318 specialAttrHolder.innerHTML = ''; var attributes = specialAttrHolder.firstChild.attributes; var attribute = attributes[0]; // We have to remove the attribute from its container element before we can add it to the destination element attributes.removeNamedItem(attribute.name); attribute.value = value; element.attributes.setNamedItem(attribute); } function safeAddClass($element, className) { try { $element.addClass(className); } catch (e) { // ignore, since it means that we are trying to set class on // SVG element, where class name is read-only. } } var startSymbol = $interpolate.startSymbol(), endSymbol = $interpolate.endSymbol(), denormalizeTemplate = (startSymbol === '{{' && endSymbol === '}}') ? identity : function denormalizeTemplate(template) { return template.replace(/\{\{/g, startSymbol).replace(/}}/g, endSymbol); }, NG_PREFIX_BINDING = /^ng(Attr|Prop|On)([A-Z].*)$/; var MULTI_ELEMENT_DIR_RE = /^(.+)Start$/; compile.$$addBindingInfo = debugInfoEnabled ? function $$addBindingInfo($element, binding) { var bindings = $element.data('$binding') || []; if (isArray(binding)) { bindings = bindings.concat(binding); } else { bindings.push(binding); } $element.data('$binding', bindings); } : noop; compile.$$addBindingClass = debugInfoEnabled ? function $$addBindingClass($element) { safeAddClass($element, 'ng-binding'); } : noop; compile.$$addScopeInfo = debugInfoEnabled ? function $$addScopeInfo($element, scope, isolated, noTemplate) { var dataName = isolated ? (noTemplate ? '$isolateScopeNoTemplate' : '$isolateScope') : '$scope'; $element.data(dataName, scope); } : noop; compile.$$addScopeClass = debugInfoEnabled ? function $$addScopeClass($element, isolated) { safeAddClass($element, isolated ? 'ng-isolate-scope' : 'ng-scope'); } : noop; compile.$$createComment = function(directiveName, comment) { var content = ''; if (debugInfoEnabled) { content = ' ' + (directiveName || '') + ': '; if (comment) content += comment + ' '; } return window.document.createComment(content); }; return compile; //================================ function compile($compileNodes, transcludeFn, maxPriority, ignoreDirective, previousCompileContext) { if (!($compileNodes instanceof jqLite)) { // jquery always rewraps, whereas we need to preserve the original selector so that we can // modify it. $compileNodes = jqLite($compileNodes); } var compositeLinkFn = compileNodes($compileNodes, transcludeFn, $compileNodes, maxPriority, ignoreDirective, previousCompileContext); compile.$$addScopeClass($compileNodes); var namespace = null; return function publicLinkFn(scope, cloneConnectFn, options) { if (!$compileNodes) { throw $compileMinErr('multilink', 'This element has already been linked.'); } assertArg(scope, 'scope'); if (previousCompileContext && previousCompileContext.needsNewScope) { // A parent directive did a replace and a directive on this element asked // for transclusion, which caused us to lose a layer of element on which // we could hold the new transclusion scope, so we will create it manually // here. scope = scope.$parent.$new(); } options = options || {}; var parentBoundTranscludeFn = options.parentBoundTranscludeFn, transcludeControllers = options.transcludeControllers, futureParentElement = options.futureParentElement; // When `parentBoundTranscludeFn` is passed, it is a // `controllersBoundTransclude` function (it was previously passed // as `transclude` to directive.link) so we must unwrap it to get // its `boundTranscludeFn` if (parentBoundTranscludeFn && parentBoundTranscludeFn.$$boundTransclude) { parentBoundTranscludeFn = parentBoundTranscludeFn.$$boundTransclude; } if (!namespace) { namespace = detectNamespaceForChildElements(futureParentElement); } var $linkNode; if (namespace !== 'html') { // When using a directive with replace:true and templateUrl the $compileNodes // (or a child element inside of them) // might change, so we need to recreate the namespace adapted compileNodes // for call to the link function. // Note: This will already clone the nodes... $linkNode = jqLite( wrapTemplate(namespace, jqLite('
    ').append($compileNodes).html()) ); } else if (cloneConnectFn) { // important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart // and sometimes changes the structure of the DOM. $linkNode = JQLitePrototype.clone.call($compileNodes); } else { $linkNode = $compileNodes; } if (transcludeControllers) { for (var controllerName in transcludeControllers) { $linkNode.data('$' + controllerName + 'Controller', transcludeControllers[controllerName].instance); } } compile.$$addScopeInfo($linkNode, scope); if (cloneConnectFn) cloneConnectFn($linkNode, scope); if (compositeLinkFn) compositeLinkFn(scope, $linkNode, $linkNode, parentBoundTranscludeFn); if (!cloneConnectFn) { $compileNodes = compositeLinkFn = null; } return $linkNode; }; } function detectNamespaceForChildElements(parentElement) { // TODO: Make this detect MathML as well... var node = parentElement && parentElement[0]; if (!node) { return 'html'; } else { return nodeName_(node) !== 'foreignobject' && toString.call(node).match(/SVG/) ? 'svg' : 'html'; } } /** * Compile function matches each node in nodeList against the directives. Once all directives * for a particular node are collected their compile functions are executed. The compile * functions return values - the linking functions - are combined into a composite linking * function, which is the a linking function for the node. * * @param {NodeList} nodeList an array of nodes or NodeList to compile * @param {function(angular.Scope, cloneAttachFn=)} transcludeFn A linking function, where the * scope argument is auto-generated to the new child of the transcluded parent scope. * @param {DOMElement=} $rootElement If the nodeList is the root of the compilation tree then * the rootElement must be set the jqLite collection of the compile root. This is * needed so that the jqLite collection items can be replaced with widgets. * @param {number=} maxPriority Max directive priority. * @returns {Function} A composite linking function of all of the matched directives or null. */ function compileNodes(nodeList, transcludeFn, $rootElement, maxPriority, ignoreDirective, previousCompileContext) { var linkFns = [], // `nodeList` can be either an element's `.childNodes` (live NodeList) // or a jqLite/jQuery collection or an array notLiveList = isArray(nodeList) || (nodeList instanceof jqLite), attrs, directives, nodeLinkFn, childNodes, childLinkFn, linkFnFound, nodeLinkFnFound; for (var i = 0; i < nodeList.length; i++) { attrs = new Attributes(); // Support: IE 11 only // Workaround for #11781 and #14924 if (msie === 11) { mergeConsecutiveTextNodes(nodeList, i, notLiveList); } // We must always refer to `nodeList[i]` hereafter, // since the nodes can be replaced underneath us. directives = collectDirectives(nodeList[i], [], attrs, i === 0 ? maxPriority : undefined, ignoreDirective); nodeLinkFn = (directives.length) ? applyDirectivesToNode(directives, nodeList[i], attrs, transcludeFn, $rootElement, null, [], [], previousCompileContext) : null; if (nodeLinkFn && nodeLinkFn.scope) { compile.$$addScopeClass(attrs.$$element); } childLinkFn = (nodeLinkFn && nodeLinkFn.terminal || !(childNodes = nodeList[i].childNodes) || !childNodes.length) ? null : compileNodes(childNodes, nodeLinkFn ? ( (nodeLinkFn.transcludeOnThisElement || !nodeLinkFn.templateOnThisElement) && nodeLinkFn.transclude) : transcludeFn); if (nodeLinkFn || childLinkFn) { linkFns.push(i, nodeLinkFn, childLinkFn); linkFnFound = true; nodeLinkFnFound = nodeLinkFnFound || nodeLinkFn; } //use the previous context only for the first element in the virtual group previousCompileContext = null; } // return a linking function if we have found anything, null otherwise return linkFnFound ? compositeLinkFn : null; function compositeLinkFn(scope, nodeList, $rootElement, parentBoundTranscludeFn) { var nodeLinkFn, childLinkFn, node, childScope, i, ii, idx, childBoundTranscludeFn; var stableNodeList; if (nodeLinkFnFound) { // copy nodeList so that if a nodeLinkFn removes or adds an element at this DOM level our // offsets don't get screwed up var nodeListLength = nodeList.length; stableNodeList = new Array(nodeListLength); // create a sparse array by only copying the elements which have a linkFn for (i = 0; i < linkFns.length; i += 3) { idx = linkFns[i]; stableNodeList[idx] = nodeList[idx]; } } else { stableNodeList = nodeList; } for (i = 0, ii = linkFns.length; i < ii;) { node = stableNodeList[linkFns[i++]]; nodeLinkFn = linkFns[i++]; childLinkFn = linkFns[i++]; if (nodeLinkFn) { if (nodeLinkFn.scope) { childScope = scope.$new(); compile.$$addScopeInfo(jqLite(node), childScope); } else { childScope = scope; } if (nodeLinkFn.transcludeOnThisElement) { childBoundTranscludeFn = createBoundTranscludeFn( scope, nodeLinkFn.transclude, parentBoundTranscludeFn); } else if (!nodeLinkFn.templateOnThisElement && parentBoundTranscludeFn) { childBoundTranscludeFn = parentBoundTranscludeFn; } else if (!parentBoundTranscludeFn && transcludeFn) { childBoundTranscludeFn = createBoundTranscludeFn(scope, transcludeFn); } else { childBoundTranscludeFn = null; } nodeLinkFn(childLinkFn, childScope, node, $rootElement, childBoundTranscludeFn); } else if (childLinkFn) { childLinkFn(scope, node.childNodes, undefined, parentBoundTranscludeFn); } } } } function mergeConsecutiveTextNodes(nodeList, idx, notLiveList) { var node = nodeList[idx]; var parent = node.parentNode; var sibling; if (node.nodeType !== NODE_TYPE_TEXT) { return; } while (true) { sibling = parent ? node.nextSibling : nodeList[idx + 1]; if (!sibling || sibling.nodeType !== NODE_TYPE_TEXT) { break; } node.nodeValue = node.nodeValue + sibling.nodeValue; if (sibling.parentNode) { sibling.parentNode.removeChild(sibling); } if (notLiveList && sibling === nodeList[idx + 1]) { nodeList.splice(idx + 1, 1); } } } function createBoundTranscludeFn(scope, transcludeFn, previousBoundTranscludeFn) { function boundTranscludeFn(transcludedScope, cloneFn, controllers, futureParentElement, containingScope) { if (!transcludedScope) { transcludedScope = scope.$new(false, containingScope); transcludedScope.$$transcluded = true; } return transcludeFn(transcludedScope, cloneFn, { parentBoundTranscludeFn: previousBoundTranscludeFn, transcludeControllers: controllers, futureParentElement: futureParentElement }); } // We need to attach the transclusion slots onto the `boundTranscludeFn` // so that they are available inside the `controllersBoundTransclude` function var boundSlots = boundTranscludeFn.$$slots = createMap(); for (var slotName in transcludeFn.$$slots) { if (transcludeFn.$$slots[slotName]) { boundSlots[slotName] = createBoundTranscludeFn(scope, transcludeFn.$$slots[slotName], previousBoundTranscludeFn); } else { boundSlots[slotName] = null; } } return boundTranscludeFn; } /** * Looks for directives on the given node and adds them to the directive collection which is * sorted. * * @param node Node to search. * @param directives An array to which the directives are added to. This array is sorted before * the function returns. * @param attrs The shared attrs object which is used to populate the normalized attributes. * @param {number=} maxPriority Max directive priority. */ function collectDirectives(node, directives, attrs, maxPriority, ignoreDirective) { var nodeType = node.nodeType, attrsMap = attrs.$attr, match, nodeName, className; switch (nodeType) { case NODE_TYPE_ELEMENT: /* Element */ nodeName = nodeName_(node); // use the node name: addDirective(directives, directiveNormalize(nodeName), 'E', maxPriority, ignoreDirective); // iterate over the attributes for (var attr, name, nName, value, ngPrefixMatch, nAttrs = node.attributes, j = 0, jj = nAttrs && nAttrs.length; j < jj; j++) { var attrStartName = false; var attrEndName = false; var isNgAttr = false, isNgProp = false, isNgEvent = false; var multiElementMatch; attr = nAttrs[j]; name = attr.name; value = attr.value; nName = directiveNormalize(name.toLowerCase()); // Support ng-attr-*, ng-prop-* and ng-on-* if ((ngPrefixMatch = nName.match(NG_PREFIX_BINDING))) { isNgAttr = ngPrefixMatch[1] === 'Attr'; isNgProp = ngPrefixMatch[1] === 'Prop'; isNgEvent = ngPrefixMatch[1] === 'On'; // Normalize the non-prefixed name name = name.replace(PREFIX_REGEXP, '') .toLowerCase() .substr(4 + ngPrefixMatch[1].length).replace(/_(.)/g, function(match, letter) { return letter.toUpperCase(); }); // Support *-start / *-end multi element directives } else if ((multiElementMatch = nName.match(MULTI_ELEMENT_DIR_RE)) && directiveIsMultiElement(multiElementMatch[1])) { attrStartName = name; attrEndName = name.substr(0, name.length - 5) + 'end'; name = name.substr(0, name.length - 6); } if (isNgProp || isNgEvent) { attrs[nName] = value; attrsMap[nName] = attr.name; if (isNgProp) { addPropertyDirective(node, directives, nName, name); } else { addEventDirective(directives, nName, name); } } else { // Update nName for cases where a prefix was removed // NOTE: the .toLowerCase() is unnecessary and causes https://github.com/angular/angular.js/issues/16624 for ng-attr-* nName = directiveNormalize(name.toLowerCase()); attrsMap[nName] = name; if (isNgAttr || !attrs.hasOwnProperty(nName)) { attrs[nName] = value; if (getBooleanAttrName(node, nName)) { attrs[nName] = true; // presence means true } } addAttrInterpolateDirective(node, directives, value, nName, isNgAttr); addDirective(directives, nName, 'A', maxPriority, ignoreDirective, attrStartName, attrEndName); } } if (nodeName === 'input' && node.getAttribute('type') === 'hidden') { // Hidden input elements can have strange behaviour when navigating back to the page // This tells the browser not to try to cache and reinstate previous values node.setAttribute('autocomplete', 'off'); } // use class as directive if (!cssClassDirectivesEnabled) break; className = node.className; if (isObject(className)) { // Maybe SVGAnimatedString className = className.animVal; } if (isString(className) && className !== '') { while ((match = CLASS_DIRECTIVE_REGEXP.exec(className))) { nName = directiveNormalize(match[2]); if (addDirective(directives, nName, 'C', maxPriority, ignoreDirective)) { attrs[nName] = trim(match[3]); } className = className.substr(match.index + match[0].length); } } break; case NODE_TYPE_TEXT: /* Text Node */ addTextInterpolateDirective(directives, node.nodeValue); break; case NODE_TYPE_COMMENT: /* Comment */ if (!commentDirectivesEnabled) break; collectCommentDirectives(node, directives, attrs, maxPriority, ignoreDirective); break; } directives.sort(byPriority); return directives; } function collectCommentDirectives(node, directives, attrs, maxPriority, ignoreDirective) { // function created because of performance, try/catch disables // the optimization of the whole function #14848 try { var match = COMMENT_DIRECTIVE_REGEXP.exec(node.nodeValue); if (match) { var nName = directiveNormalize(match[1]); if (addDirective(directives, nName, 'M', maxPriority, ignoreDirective)) { attrs[nName] = trim(match[2]); } } } catch (e) { // turns out that under some circumstances IE9 throws errors when one attempts to read // comment's node value. // Just ignore it and continue. (Can't seem to reproduce in test case.) } } /** * Given a node with a directive-start it collects all of the siblings until it finds * directive-end. * @param node * @param attrStart * @param attrEnd * @returns {*} */ function groupScan(node, attrStart, attrEnd) { var nodes = []; var depth = 0; if (attrStart && node.hasAttribute && node.hasAttribute(attrStart)) { do { if (!node) { throw $compileMinErr('uterdir', 'Unterminated attribute, found \'{0}\' but no matching \'{1}\' found.', attrStart, attrEnd); } if (node.nodeType === NODE_TYPE_ELEMENT) { if (node.hasAttribute(attrStart)) depth++; if (node.hasAttribute(attrEnd)) depth--; } nodes.push(node); node = node.nextSibling; } while (depth > 0); } else { nodes.push(node); } return jqLite(nodes); } /** * Wrapper for linking function which converts normal linking function into a grouped * linking function. * @param linkFn * @param attrStart * @param attrEnd * @returns {Function} */ function groupElementsLinkFnWrapper(linkFn, attrStart, attrEnd) { return function groupedElementsLink(scope, element, attrs, controllers, transcludeFn) { element = groupScan(element[0], attrStart, attrEnd); return linkFn(scope, element, attrs, controllers, transcludeFn); }; } /** * A function generator that is used to support both eager and lazy compilation * linking function. * @param eager * @param $compileNodes * @param transcludeFn * @param maxPriority * @param ignoreDirective * @param previousCompileContext * @returns {Function} */ function compilationGenerator(eager, $compileNodes, transcludeFn, maxPriority, ignoreDirective, previousCompileContext) { var compiled; if (eager) { return compile($compileNodes, transcludeFn, maxPriority, ignoreDirective, previousCompileContext); } return /** @this */ function lazyCompilation() { if (!compiled) { compiled = compile($compileNodes, transcludeFn, maxPriority, ignoreDirective, previousCompileContext); // Null out all of these references in order to make them eligible for garbage collection // since this is a potentially long lived closure $compileNodes = transcludeFn = previousCompileContext = null; } return compiled.apply(this, arguments); }; } /** * Once the directives have been collected, their compile functions are executed. This method * is responsible for inlining directive templates as well as terminating the application * of the directives if the terminal directive has been reached. * * @param {Array} directives Array of collected directives to execute their compile function. * this needs to be pre-sorted by priority order. * @param {Node} compileNode The raw DOM node to apply the compile functions to * @param {Object} templateAttrs The shared attribute function * @param {function(angular.Scope, cloneAttachFn=)} transcludeFn A linking function, where the * scope argument is auto-generated to the new * child of the transcluded parent scope. * @param {JQLite} jqCollection If we are working on the root of the compile tree then this * argument has the root jqLite array so that we can replace nodes * on it. * @param {Object=} originalReplaceDirective An optional directive that will be ignored when * compiling the transclusion. * @param {Array.} preLinkFns * @param {Array.} postLinkFns * @param {Object} previousCompileContext Context used for previous compilation of the current * node * @returns {Function} linkFn */ function applyDirectivesToNode(directives, compileNode, templateAttrs, transcludeFn, jqCollection, originalReplaceDirective, preLinkFns, postLinkFns, previousCompileContext) { previousCompileContext = previousCompileContext || {}; var terminalPriority = -Number.MAX_VALUE, newScopeDirective = previousCompileContext.newScopeDirective, controllerDirectives = previousCompileContext.controllerDirectives, newIsolateScopeDirective = previousCompileContext.newIsolateScopeDirective, templateDirective = previousCompileContext.templateDirective, nonTlbTranscludeDirective = previousCompileContext.nonTlbTranscludeDirective, hasTranscludeDirective = false, hasTemplate = false, hasElementTranscludeDirective = previousCompileContext.hasElementTranscludeDirective, $compileNode = templateAttrs.$$element = jqLite(compileNode), directive, directiveName, $template, replaceDirective = originalReplaceDirective, childTranscludeFn = transcludeFn, linkFn, didScanForMultipleTransclusion = false, mightHaveMultipleTransclusionError = false, directiveValue; // executes all directives on the current element for (var i = 0, ii = directives.length; i < ii; i++) { directive = directives[i]; var attrStart = directive.$$start; var attrEnd = directive.$$end; // collect multiblock sections if (attrStart) { $compileNode = groupScan(compileNode, attrStart, attrEnd); } $template = undefined; if (terminalPriority > directive.priority) { break; // prevent further processing of directives } directiveValue = directive.scope; if (directiveValue) { // skip the check for directives with async templates, we'll check the derived sync // directive when the template arrives if (!directive.templateUrl) { if (isObject(directiveValue)) { // This directive is trying to add an isolated scope. // Check that there is no scope of any kind already assertNoDuplicate('new/isolated scope', newIsolateScopeDirective || newScopeDirective, directive, $compileNode); newIsolateScopeDirective = directive; } else { // This directive is trying to add a child scope. // Check that there is no isolated scope already assertNoDuplicate('new/isolated scope', newIsolateScopeDirective, directive, $compileNode); } } newScopeDirective = newScopeDirective || directive; } directiveName = directive.name; // If we encounter a condition that can result in transclusion on the directive, // then scan ahead in the remaining directives for others that may cause a multiple // transclusion error to be thrown during the compilation process. If a matching directive // is found, then we know that when we encounter a transcluded directive, we need to eagerly // compile the `transclude` function rather than doing it lazily in order to throw // exceptions at the correct time if (!didScanForMultipleTransclusion && ((directive.replace && (directive.templateUrl || directive.template)) || (directive.transclude && !directive.$$tlb))) { var candidateDirective; for (var scanningIndex = i + 1; (candidateDirective = directives[scanningIndex++]);) { if ((candidateDirective.transclude && !candidateDirective.$$tlb) || (candidateDirective.replace && (candidateDirective.templateUrl || candidateDirective.template))) { mightHaveMultipleTransclusionError = true; break; } } didScanForMultipleTransclusion = true; } if (!directive.templateUrl && directive.controller) { controllerDirectives = controllerDirectives || createMap(); assertNoDuplicate('\'' + directiveName + '\' controller', controllerDirectives[directiveName], directive, $compileNode); controllerDirectives[directiveName] = directive; } directiveValue = directive.transclude; if (directiveValue) { hasTranscludeDirective = true; // Special case ngIf and ngRepeat so that we don't complain about duplicate transclusion. // This option should only be used by directives that know how to safely handle element transclusion, // where the transcluded nodes are added or replaced after linking. if (!directive.$$tlb) { assertNoDuplicate('transclusion', nonTlbTranscludeDirective, directive, $compileNode); nonTlbTranscludeDirective = directive; } if (directiveValue === 'element') { hasElementTranscludeDirective = true; terminalPriority = directive.priority; $template = $compileNode; $compileNode = templateAttrs.$$element = jqLite(compile.$$createComment(directiveName, templateAttrs[directiveName])); compileNode = $compileNode[0]; replaceWith(jqCollection, sliceArgs($template), compileNode); childTranscludeFn = compilationGenerator(mightHaveMultipleTransclusionError, $template, transcludeFn, terminalPriority, replaceDirective && replaceDirective.name, { // Don't pass in: // - controllerDirectives - otherwise we'll create duplicates controllers // - newIsolateScopeDirective or templateDirective - combining templates with // element transclusion doesn't make sense. // // We need only nonTlbTranscludeDirective so that we prevent putting transclusion // on the same element more than once. nonTlbTranscludeDirective: nonTlbTranscludeDirective }); } else { var slots = createMap(); if (!isObject(directiveValue)) { $template = jqLite(jqLiteClone(compileNode)).contents(); } else { // We have transclusion slots, // collect them up, compile them and store their transclusion functions $template = window.document.createDocumentFragment(); var slotMap = createMap(); var filledSlots = createMap(); // Parse the element selectors forEach(directiveValue, function(elementSelector, slotName) { // If an element selector starts with a ? then it is optional var optional = (elementSelector.charAt(0) === '?'); elementSelector = optional ? elementSelector.substring(1) : elementSelector; slotMap[elementSelector] = slotName; // We explicitly assign `null` since this implies that a slot was defined but not filled. // Later when calling boundTransclusion functions with a slot name we only error if the // slot is `undefined` slots[slotName] = null; // filledSlots contains `true` for all slots that are either optional or have been // filled. This is used to check that we have not missed any required slots filledSlots[slotName] = optional; }); // Add the matching elements into their slot forEach($compileNode.contents(), function(node) { var slotName = slotMap[directiveNormalize(nodeName_(node))]; if (slotName) { filledSlots[slotName] = true; slots[slotName] = slots[slotName] || window.document.createDocumentFragment(); slots[slotName].appendChild(node); } else { $template.appendChild(node); } }); // Check for required slots that were not filled forEach(filledSlots, function(filled, slotName) { if (!filled) { throw $compileMinErr('reqslot', 'Required transclusion slot `{0}` was not filled.', slotName); } }); for (var slotName in slots) { if (slots[slotName]) { // Only define a transclusion function if the slot was filled var slotCompileNodes = jqLite(slots[slotName].childNodes); slots[slotName] = compilationGenerator(mightHaveMultipleTransclusionError, slotCompileNodes, transcludeFn); } } $template = jqLite($template.childNodes); } $compileNode.empty(); // clear contents childTranscludeFn = compilationGenerator(mightHaveMultipleTransclusionError, $template, transcludeFn, undefined, undefined, { needsNewScope: directive.$$isolateScope || directive.$$newScope}); childTranscludeFn.$$slots = slots; } } if (directive.template) { hasTemplate = true; assertNoDuplicate('template', templateDirective, directive, $compileNode); templateDirective = directive; directiveValue = (isFunction(directive.template)) ? directive.template($compileNode, templateAttrs) : directive.template; directiveValue = denormalizeTemplate(directiveValue); if (directive.replace) { replaceDirective = directive; if (jqLiteIsTextNode(directiveValue)) { $template = []; } else { $template = removeComments(wrapTemplate(directive.templateNamespace, trim(directiveValue))); } compileNode = $template[0]; if ($template.length !== 1 || compileNode.nodeType !== NODE_TYPE_ELEMENT) { throw $compileMinErr('tplrt', 'Template for directive \'{0}\' must have exactly one root element. {1}', directiveName, ''); } replaceWith(jqCollection, $compileNode, compileNode); var newTemplateAttrs = {$attr: {}}; // combine directives from the original node and from the template: // - take the array of directives for this element // - split it into two parts, those that already applied (processed) and those that weren't (unprocessed) // - collect directives from the template and sort them by priority // - combine directives as: processed + template + unprocessed var templateDirectives = collectDirectives(compileNode, [], newTemplateAttrs); var unprocessedDirectives = directives.splice(i + 1, directives.length - (i + 1)); if (newIsolateScopeDirective || newScopeDirective) { // The original directive caused the current element to be replaced but this element // also needs to have a new scope, so we need to tell the template directives // that they would need to get their scope from further up, if they require transclusion markDirectiveScope(templateDirectives, newIsolateScopeDirective, newScopeDirective); } directives = directives.concat(templateDirectives).concat(unprocessedDirectives); mergeTemplateAttributes(templateAttrs, newTemplateAttrs); ii = directives.length; } else { $compileNode.html(directiveValue); } } if (directive.templateUrl) { hasTemplate = true; assertNoDuplicate('template', templateDirective, directive, $compileNode); templateDirective = directive; if (directive.replace) { replaceDirective = directive; } // eslint-disable-next-line no-func-assign nodeLinkFn = compileTemplateUrl(directives.splice(i, directives.length - i), $compileNode, templateAttrs, jqCollection, hasTranscludeDirective && childTranscludeFn, preLinkFns, postLinkFns, { controllerDirectives: controllerDirectives, newScopeDirective: (newScopeDirective !== directive) && newScopeDirective, newIsolateScopeDirective: newIsolateScopeDirective, templateDirective: templateDirective, nonTlbTranscludeDirective: nonTlbTranscludeDirective }); ii = directives.length; } else if (directive.compile) { try { linkFn = directive.compile($compileNode, templateAttrs, childTranscludeFn); var context = directive.$$originalDirective || directive; if (isFunction(linkFn)) { addLinkFns(null, bind(context, linkFn), attrStart, attrEnd); } else if (linkFn) { addLinkFns(bind(context, linkFn.pre), bind(context, linkFn.post), attrStart, attrEnd); } } catch (e) { $exceptionHandler(e, startingTag($compileNode)); } } if (directive.terminal) { nodeLinkFn.terminal = true; terminalPriority = Math.max(terminalPriority, directive.priority); } } nodeLinkFn.scope = newScopeDirective && newScopeDirective.scope === true; nodeLinkFn.transcludeOnThisElement = hasTranscludeDirective; nodeLinkFn.templateOnThisElement = hasTemplate; nodeLinkFn.transclude = childTranscludeFn; previousCompileContext.hasElementTranscludeDirective = hasElementTranscludeDirective; // might be normal or delayed nodeLinkFn depending on if templateUrl is present return nodeLinkFn; //////////////////// function addLinkFns(pre, post, attrStart, attrEnd) { if (pre) { if (attrStart) pre = groupElementsLinkFnWrapper(pre, attrStart, attrEnd); pre.require = directive.require; pre.directiveName = directiveName; if (newIsolateScopeDirective === directive || directive.$$isolateScope) { pre = cloneAndAnnotateFn(pre, {isolateScope: true}); } preLinkFns.push(pre); } if (post) { if (attrStart) post = groupElementsLinkFnWrapper(post, attrStart, attrEnd); post.require = directive.require; post.directiveName = directiveName; if (newIsolateScopeDirective === directive || directive.$$isolateScope) { post = cloneAndAnnotateFn(post, {isolateScope: true}); } postLinkFns.push(post); } } function nodeLinkFn(childLinkFn, scope, linkNode, $rootElement, boundTranscludeFn) { var i, ii, linkFn, isolateScope, controllerScope, elementControllers, transcludeFn, $element, attrs, scopeBindingInfo; if (compileNode === linkNode) { attrs = templateAttrs; $element = templateAttrs.$$element; } else { $element = jqLite(linkNode); attrs = new Attributes($element, templateAttrs); } controllerScope = scope; if (newIsolateScopeDirective) { isolateScope = scope.$new(true); } else if (newScopeDirective) { controllerScope = scope.$parent; } if (boundTranscludeFn) { // track `boundTranscludeFn` so it can be unwrapped if `transcludeFn` // is later passed as `parentBoundTranscludeFn` to `publicLinkFn` transcludeFn = controllersBoundTransclude; transcludeFn.$$boundTransclude = boundTranscludeFn; // expose the slots on the `$transclude` function transcludeFn.isSlotFilled = function(slotName) { return !!boundTranscludeFn.$$slots[slotName]; }; } if (controllerDirectives) { elementControllers = setupControllers($element, attrs, transcludeFn, controllerDirectives, isolateScope, scope, newIsolateScopeDirective); } if (newIsolateScopeDirective) { // Initialize isolate scope bindings for new isolate scope directive. compile.$$addScopeInfo($element, isolateScope, true, !(templateDirective && (templateDirective === newIsolateScopeDirective || templateDirective === newIsolateScopeDirective.$$originalDirective))); compile.$$addScopeClass($element, true); isolateScope.$$isolateBindings = newIsolateScopeDirective.$$isolateBindings; scopeBindingInfo = initializeDirectiveBindings(scope, attrs, isolateScope, isolateScope.$$isolateBindings, newIsolateScopeDirective); if (scopeBindingInfo.removeWatches) { isolateScope.$on('$destroy', scopeBindingInfo.removeWatches); } } // Initialize bindToController bindings for (var name in elementControllers) { var controllerDirective = controllerDirectives[name]; var controller = elementControllers[name]; var bindings = controllerDirective.$$bindings.bindToController; controller.instance = controller(); $element.data('$' + controllerDirective.name + 'Controller', controller.instance); controller.bindingInfo = initializeDirectiveBindings(controllerScope, attrs, controller.instance, bindings, controllerDirective); } // Bind the required controllers to the controller, if `require` is an object and `bindToController` is truthy forEach(controllerDirectives, function(controllerDirective, name) { var require = controllerDirective.require; if (controllerDirective.bindToController && !isArray(require) && isObject(require)) { extend(elementControllers[name].instance, getControllers(name, require, $element, elementControllers)); } }); // Handle the init and destroy lifecycle hooks on all controllers that have them forEach(elementControllers, function(controller) { var controllerInstance = controller.instance; if (isFunction(controllerInstance.$onChanges)) { try { controllerInstance.$onChanges(controller.bindingInfo.initialChanges); } catch (e) { $exceptionHandler(e); } } if (isFunction(controllerInstance.$onInit)) { try { controllerInstance.$onInit(); } catch (e) { $exceptionHandler(e); } } if (isFunction(controllerInstance.$doCheck)) { controllerScope.$watch(function() { controllerInstance.$doCheck(); }); controllerInstance.$doCheck(); } if (isFunction(controllerInstance.$onDestroy)) { controllerScope.$on('$destroy', function callOnDestroyHook() { controllerInstance.$onDestroy(); }); } }); // PRELINKING for (i = 0, ii = preLinkFns.length; i < ii; i++) { linkFn = preLinkFns[i]; invokeLinkFn(linkFn, linkFn.isolateScope ? isolateScope : scope, $element, attrs, linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers), transcludeFn ); } // RECURSION // We only pass the isolate scope, if the isolate directive has a template, // otherwise the child elements do not belong to the isolate directive. var scopeToChild = scope; if (newIsolateScopeDirective && (newIsolateScopeDirective.template || newIsolateScopeDirective.templateUrl === null)) { scopeToChild = isolateScope; } if (childLinkFn) { childLinkFn(scopeToChild, linkNode.childNodes, undefined, boundTranscludeFn); } // POSTLINKING for (i = postLinkFns.length - 1; i >= 0; i--) { linkFn = postLinkFns[i]; invokeLinkFn(linkFn, linkFn.isolateScope ? isolateScope : scope, $element, attrs, linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers), transcludeFn ); } // Trigger $postLink lifecycle hooks forEach(elementControllers, function(controller) { var controllerInstance = controller.instance; if (isFunction(controllerInstance.$postLink)) { controllerInstance.$postLink(); } }); // This is the function that is injected as `$transclude`. // Note: all arguments are optional! function controllersBoundTransclude(scope, cloneAttachFn, futureParentElement, slotName) { var transcludeControllers; // No scope passed in: if (!isScope(scope)) { slotName = futureParentElement; futureParentElement = cloneAttachFn; cloneAttachFn = scope; scope = undefined; } if (hasElementTranscludeDirective) { transcludeControllers = elementControllers; } if (!futureParentElement) { futureParentElement = hasElementTranscludeDirective ? $element.parent() : $element; } if (slotName) { // slotTranscludeFn can be one of three things: // * a transclude function - a filled slot // * `null` - an optional slot that was not filled // * `undefined` - a slot that was not declared (i.e. invalid) var slotTranscludeFn = boundTranscludeFn.$$slots[slotName]; if (slotTranscludeFn) { return slotTranscludeFn(scope, cloneAttachFn, transcludeControllers, futureParentElement, scopeToChild); } else if (isUndefined(slotTranscludeFn)) { throw $compileMinErr('noslot', 'No parent directive that requires a transclusion with slot name "{0}". ' + 'Element: {1}', slotName, startingTag($element)); } } else { return boundTranscludeFn(scope, cloneAttachFn, transcludeControllers, futureParentElement, scopeToChild); } } } } function getControllers(directiveName, require, $element, elementControllers) { var value; if (isString(require)) { var match = require.match(REQUIRE_PREFIX_REGEXP); var name = require.substring(match[0].length); var inheritType = match[1] || match[3]; var optional = match[2] === '?'; //If only parents then start at the parent element if (inheritType === '^^') { $element = $element.parent(); //Otherwise attempt getting the controller from elementControllers in case //the element is transcluded (and has no data) and to avoid .data if possible } else { value = elementControllers && elementControllers[name]; value = value && value.instance; } if (!value) { var dataName = '$' + name + 'Controller'; if (inheritType === '^^' && $element[0] && $element[0].nodeType === NODE_TYPE_DOCUMENT) { // inheritedData() uses the documentElement when it finds the document, so we would // require from the element itself. value = null; } else { value = inheritType ? $element.inheritedData(dataName) : $element.data(dataName); } } if (!value && !optional) { throw $compileMinErr('ctreq', 'Controller \'{0}\', required by directive \'{1}\', can\'t be found!', name, directiveName); } } else if (isArray(require)) { value = []; for (var i = 0, ii = require.length; i < ii; i++) { value[i] = getControllers(directiveName, require[i], $element, elementControllers); } } else if (isObject(require)) { value = {}; forEach(require, function(controller, property) { value[property] = getControllers(directiveName, controller, $element, elementControllers); }); } return value || null; } function setupControllers($element, attrs, transcludeFn, controllerDirectives, isolateScope, scope, newIsolateScopeDirective) { var elementControllers = createMap(); for (var controllerKey in controllerDirectives) { var directive = controllerDirectives[controllerKey]; var locals = { $scope: directive === newIsolateScopeDirective || directive.$$isolateScope ? isolateScope : scope, $element: $element, $attrs: attrs, $transclude: transcludeFn }; var controller = directive.controller; if (controller === '@') { controller = attrs[directive.name]; } var controllerInstance = $controller(controller, locals, true, directive.controllerAs); // For directives with element transclusion the element is a comment. // In this case .data will not attach any data. // Instead, we save the controllers for the element in a local hash and attach to .data // later, once we have the actual element. elementControllers[directive.name] = controllerInstance; $element.data('$' + directive.name + 'Controller', controllerInstance.instance); } return elementControllers; } // Depending upon the context in which a directive finds itself it might need to have a new isolated // or child scope created. For instance: // * if the directive has been pulled into a template because another directive with a higher priority // asked for element transclusion // * if the directive itself asks for transclusion but it is at the root of a template and the original // element was replaced. See https://github.com/angular/angular.js/issues/12936 function markDirectiveScope(directives, isolateScope, newScope) { for (var j = 0, jj = directives.length; j < jj; j++) { directives[j] = inherit(directives[j], {$$isolateScope: isolateScope, $$newScope: newScope}); } } /** * looks up the directive and decorates it with exception handling and proper parameters. We * call this the boundDirective. * * @param {string} name name of the directive to look up. * @param {string} location The directive must be found in specific format. * String containing any of theses characters: * * * `E`: element name * * `A': attribute * * `C`: class * * `M`: comment * @returns {boolean} true if directive was added. */ function addDirective(tDirectives, name, location, maxPriority, ignoreDirective, startAttrName, endAttrName) { if (name === ignoreDirective) return null; var match = null; if (hasDirectives.hasOwnProperty(name)) { for (var directive, directives = $injector.get(name + Suffix), i = 0, ii = directives.length; i < ii; i++) { directive = directives[i]; if ((isUndefined(maxPriority) || maxPriority > directive.priority) && directive.restrict.indexOf(location) !== -1) { if (startAttrName) { directive = inherit(directive, {$$start: startAttrName, $$end: endAttrName}); } if (!directive.$$bindings) { var bindings = directive.$$bindings = parseDirectiveBindings(directive, directive.name); if (isObject(bindings.isolateScope)) { directive.$$isolateBindings = bindings.isolateScope; } } tDirectives.push(directive); match = directive; } } } return match; } /** * looks up the directive and returns true if it is a multi-element directive, * and therefore requires DOM nodes between -start and -end markers to be grouped * together. * * @param {string} name name of the directive to look up. * @returns true if directive was registered as multi-element. */ function directiveIsMultiElement(name) { if (hasDirectives.hasOwnProperty(name)) { for (var directive, directives = $injector.get(name + Suffix), i = 0, ii = directives.length; i < ii; i++) { directive = directives[i]; if (directive.multiElement) { return true; } } } return false; } /** * When the element is replaced with HTML template then the new attributes * on the template need to be merged with the existing attributes in the DOM. * The desired effect is to have both of the attributes present. * * @param {object} dst destination attributes (original DOM) * @param {object} src source attributes (from the directive template) */ function mergeTemplateAttributes(dst, src) { var srcAttr = src.$attr, dstAttr = dst.$attr; // reapply the old attributes to the new element forEach(dst, function(value, key) { if (key.charAt(0) !== '$') { if (src[key] && src[key] !== value) { if (value.length) { value += (key === 'style' ? ';' : ' ') + src[key]; } else { value = src[key]; } } dst.$set(key, value, true, srcAttr[key]); } }); // copy the new attributes on the old attrs object forEach(src, function(value, key) { // Check if we already set this attribute in the loop above. // `dst` will never contain hasOwnProperty as DOM parser won't let it. // You will get an "InvalidCharacterError: DOM Exception 5" error if you // have an attribute like "has-own-property" or "data-has-own-property", etc. if (!dst.hasOwnProperty(key) && key.charAt(0) !== '$') { dst[key] = value; if (key !== 'class' && key !== 'style') { dstAttr[key] = srcAttr[key]; } } }); } function compileTemplateUrl(directives, $compileNode, tAttrs, $rootElement, childTranscludeFn, preLinkFns, postLinkFns, previousCompileContext) { var linkQueue = [], afterTemplateNodeLinkFn, afterTemplateChildLinkFn, beforeTemplateCompileNode = $compileNode[0], origAsyncDirective = directives.shift(), derivedSyncDirective = inherit(origAsyncDirective, { templateUrl: null, transclude: null, replace: null, $$originalDirective: origAsyncDirective }), templateUrl = (isFunction(origAsyncDirective.templateUrl)) ? origAsyncDirective.templateUrl($compileNode, tAttrs) : origAsyncDirective.templateUrl, templateNamespace = origAsyncDirective.templateNamespace; $compileNode.empty(); $templateRequest(templateUrl) .then(function(content) { var compileNode, tempTemplateAttrs, $template, childBoundTranscludeFn; content = denormalizeTemplate(content); if (origAsyncDirective.replace) { if (jqLiteIsTextNode(content)) { $template = []; } else { $template = removeComments(wrapTemplate(templateNamespace, trim(content))); } compileNode = $template[0]; if ($template.length !== 1 || compileNode.nodeType !== NODE_TYPE_ELEMENT) { throw $compileMinErr('tplrt', 'Template for directive \'{0}\' must have exactly one root element. {1}', origAsyncDirective.name, templateUrl); } tempTemplateAttrs = {$attr: {}}; replaceWith($rootElement, $compileNode, compileNode); var templateDirectives = collectDirectives(compileNode, [], tempTemplateAttrs); if (isObject(origAsyncDirective.scope)) { // the original directive that caused the template to be loaded async required // an isolate scope markDirectiveScope(templateDirectives, true); } directives = templateDirectives.concat(directives); mergeTemplateAttributes(tAttrs, tempTemplateAttrs); } else { compileNode = beforeTemplateCompileNode; $compileNode.html(content); } directives.unshift(derivedSyncDirective); afterTemplateNodeLinkFn = applyDirectivesToNode(directives, compileNode, tAttrs, childTranscludeFn, $compileNode, origAsyncDirective, preLinkFns, postLinkFns, previousCompileContext); forEach($rootElement, function(node, i) { if (node === compileNode) { $rootElement[i] = $compileNode[0]; } }); afterTemplateChildLinkFn = compileNodes($compileNode[0].childNodes, childTranscludeFn); while (linkQueue.length) { var scope = linkQueue.shift(), beforeTemplateLinkNode = linkQueue.shift(), linkRootElement = linkQueue.shift(), boundTranscludeFn = linkQueue.shift(), linkNode = $compileNode[0]; if (scope.$$destroyed) continue; if (beforeTemplateLinkNode !== beforeTemplateCompileNode) { var oldClasses = beforeTemplateLinkNode.className; if (!(previousCompileContext.hasElementTranscludeDirective && origAsyncDirective.replace)) { // it was cloned therefore we have to clone as well. linkNode = jqLiteClone(compileNode); } replaceWith(linkRootElement, jqLite(beforeTemplateLinkNode), linkNode); // Copy in CSS classes from original node safeAddClass(jqLite(linkNode), oldClasses); } if (afterTemplateNodeLinkFn.transcludeOnThisElement) { childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn); } else { childBoundTranscludeFn = boundTranscludeFn; } afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, linkNode, $rootElement, childBoundTranscludeFn); } linkQueue = null; }).catch(function(error) { if (isError(error)) { $exceptionHandler(error); } }); return function delayedNodeLinkFn(ignoreChildLinkFn, scope, node, rootElement, boundTranscludeFn) { var childBoundTranscludeFn = boundTranscludeFn; if (scope.$$destroyed) return; if (linkQueue) { linkQueue.push(scope, node, rootElement, childBoundTranscludeFn); } else { if (afterTemplateNodeLinkFn.transcludeOnThisElement) { childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn); } afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, rootElement, childBoundTranscludeFn); } }; } /** * Sorting function for bound directives. */ function byPriority(a, b) { var diff = b.priority - a.priority; if (diff !== 0) return diff; if (a.name !== b.name) return (a.name < b.name) ? -1 : 1; return a.index - b.index; } function assertNoDuplicate(what, previousDirective, directive, element) { function wrapModuleNameIfDefined(moduleName) { return moduleName ? (' (module: ' + moduleName + ')') : ''; } if (previousDirective) { throw $compileMinErr('multidir', 'Multiple directives [{0}{1}, {2}{3}] asking for {4} on: {5}', previousDirective.name, wrapModuleNameIfDefined(previousDirective.$$moduleName), directive.name, wrapModuleNameIfDefined(directive.$$moduleName), what, startingTag(element)); } } function addTextInterpolateDirective(directives, text) { var interpolateFn = $interpolate(text, true); if (interpolateFn) { directives.push({ priority: 0, compile: function textInterpolateCompileFn(templateNode) { var templateNodeParent = templateNode.parent(), hasCompileParent = !!templateNodeParent.length; // When transcluding a template that has bindings in the root // we don't have a parent and thus need to add the class during linking fn. if (hasCompileParent) compile.$$addBindingClass(templateNodeParent); return function textInterpolateLinkFn(scope, node) { var parent = node.parent(); if (!hasCompileParent) compile.$$addBindingClass(parent); compile.$$addBindingInfo(parent, interpolateFn.expressions); scope.$watch(interpolateFn, function interpolateFnWatchAction(value) { node[0].nodeValue = value; }); }; } }); } } function wrapTemplate(type, template) { type = lowercase(type || 'html'); switch (type) { case 'svg': case 'math': var wrapper = window.document.createElement('div'); wrapper.innerHTML = '<' + type + '>' + template + ''; return wrapper.childNodes[0].childNodes; default: return template; } } function getTrustedAttrContext(nodeName, attrNormalizedName) { if (attrNormalizedName === 'srcdoc') { return $sce.HTML; } // All nodes with src attributes require a RESOURCE_URL value, except for // img and various html5 media nodes, which require the MEDIA_URL context. if (attrNormalizedName === 'src' || attrNormalizedName === 'ngSrc') { if (['img', 'video', 'audio', 'source', 'track'].indexOf(nodeName) === -1) { return $sce.RESOURCE_URL; } return $sce.MEDIA_URL; } else if (attrNormalizedName === 'xlinkHref') { // Some xlink:href are okay, most aren't if (nodeName === 'image') return $sce.MEDIA_URL; if (nodeName === 'a') return $sce.URL; return $sce.RESOURCE_URL; } else if ( // Formaction (nodeName === 'form' && attrNormalizedName === 'action') || // If relative URLs can go where they are not expected to, then // all sorts of trust issues can arise. (nodeName === 'base' && attrNormalizedName === 'href') || // links can be stylesheets or imports, which can run script in the current origin (nodeName === 'link' && attrNormalizedName === 'href') ) { return $sce.RESOURCE_URL; } else if (nodeName === 'a' && (attrNormalizedName === 'href' || attrNormalizedName === 'ngHref')) { return $sce.URL; } } function getTrustedPropContext(nodeName, propNormalizedName) { var prop = propNormalizedName.toLowerCase(); return PROP_CONTEXTS[nodeName + '|' + prop] || PROP_CONTEXTS['*|' + prop]; } function sanitizeSrcsetPropertyValue(value) { return sanitizeSrcset($sce.valueOf(value), 'ng-prop-srcset'); } function addPropertyDirective(node, directives, attrName, propName) { if (EVENT_HANDLER_ATTR_REGEXP.test(propName)) { throw $compileMinErr('nodomevents', 'Property bindings for HTML DOM event properties are disallowed'); } var nodeName = nodeName_(node); var trustedContext = getTrustedPropContext(nodeName, propName); var sanitizer = identity; // Sanitize img[srcset] + source[srcset] values. if (propName === 'srcset' && (nodeName === 'img' || nodeName === 'source')) { sanitizer = sanitizeSrcsetPropertyValue; } else if (trustedContext) { sanitizer = $sce.getTrusted.bind($sce, trustedContext); } directives.push({ priority: 100, compile: function ngPropCompileFn(_, attr) { var ngPropGetter = $parse(attr[attrName]); var ngPropWatch = $parse(attr[attrName], function sceValueOf(val) { // Unwrap the value to compare the actual inner safe value, not the wrapper object. return $sce.valueOf(val); }); return { pre: function ngPropPreLinkFn(scope, $element) { function applyPropValue() { var propValue = ngPropGetter(scope); $element[0][propName] = sanitizer(propValue); } applyPropValue(); scope.$watch(ngPropWatch, applyPropValue); } }; } }); } function addEventDirective(directives, attrName, eventName) { directives.push( createEventDirective($parse, $rootScope, $exceptionHandler, attrName, eventName, /*forceAsync=*/false) ); } function addAttrInterpolateDirective(node, directives, value, name, isNgAttr) { var nodeName = nodeName_(node); var trustedContext = getTrustedAttrContext(nodeName, name); var mustHaveExpression = !isNgAttr; var allOrNothing = ALL_OR_NOTHING_ATTRS[name] || isNgAttr; var interpolateFn = $interpolate(value, mustHaveExpression, trustedContext, allOrNothing); // no interpolation found -> ignore if (!interpolateFn) return; if (name === 'multiple' && nodeName === 'select') { throw $compileMinErr('selmulti', 'Binding to the \'multiple\' attribute is not supported. Element: {0}', startingTag(node)); } if (EVENT_HANDLER_ATTR_REGEXP.test(name)) { throw $compileMinErr('nodomevents', 'Interpolations for HTML DOM event attributes are disallowed'); } directives.push({ priority: 100, compile: function() { return { pre: function attrInterpolatePreLinkFn(scope, element, attr) { var $$observers = (attr.$$observers || (attr.$$observers = createMap())); // If the attribute has changed since last $interpolate()ed var newValue = attr[name]; if (newValue !== value) { // we need to interpolate again since the attribute value has been updated // (e.g. by another directive's compile function) // ensure unset/empty values make interpolateFn falsy interpolateFn = newValue && $interpolate(newValue, true, trustedContext, allOrNothing); value = newValue; } // if attribute was updated so that there is no interpolation going on we don't want to // register any observers if (!interpolateFn) return; // initialize attr object so that it's ready in case we need the value for isolate // scope initialization, otherwise the value would not be available from isolate // directive's linking fn during linking phase attr[name] = interpolateFn(scope); ($$observers[name] || ($$observers[name] = [])).$$inter = true; (attr.$$observers && attr.$$observers[name].$$scope || scope). $watch(interpolateFn, function interpolateFnWatchAction(newValue, oldValue) { //special case for class attribute addition + removal //so that class changes can tap into the animation //hooks provided by the $animate service. Be sure to //skip animations when the first digest occurs (when //both the new and the old values are the same) since //the CSS classes are the non-interpolated values if (name === 'class' && newValue !== oldValue) { attr.$updateClass(newValue, oldValue); } else { attr.$set(name, newValue); } }); } }; } }); } /** * This is a special jqLite.replaceWith, which can replace items which * have no parents, provided that the containing jqLite collection is provided. * * @param {JqLite=} $rootElement The root of the compile tree. Used so that we can replace nodes * in the root of the tree. * @param {JqLite} elementsToRemove The jqLite element which we are going to replace. We keep * the shell, but replace its DOM node reference. * @param {Node} newNode The new DOM node. */ function replaceWith($rootElement, elementsToRemove, newNode) { var firstElementToRemove = elementsToRemove[0], removeCount = elementsToRemove.length, parent = firstElementToRemove.parentNode, i, ii; if ($rootElement) { for (i = 0, ii = $rootElement.length; i < ii; i++) { if ($rootElement[i] === firstElementToRemove) { $rootElement[i++] = newNode; for (var j = i, j2 = j + removeCount - 1, jj = $rootElement.length; j < jj; j++, j2++) { if (j2 < jj) { $rootElement[j] = $rootElement[j2]; } else { delete $rootElement[j]; } } $rootElement.length -= removeCount - 1; // If the replaced element is also the jQuery .context then replace it // .context is a deprecated jQuery api, so we should set it only when jQuery set it // http://api.jquery.com/context/ if ($rootElement.context === firstElementToRemove) { $rootElement.context = newNode; } break; } } } if (parent) { parent.replaceChild(newNode, firstElementToRemove); } // Append all the `elementsToRemove` to a fragment. This will... // - remove them from the DOM // - allow them to still be traversed with .nextSibling // - allow a single fragment.qSA to fetch all elements being removed var fragment = window.document.createDocumentFragment(); for (i = 0; i < removeCount; i++) { fragment.appendChild(elementsToRemove[i]); } if (jqLite.hasData(firstElementToRemove)) { // Copy over user data (that includes AngularJS's $scope etc.). Don't copy private // data here because there's no public interface in jQuery to do that and copying over // event listeners (which is the main use of private data) wouldn't work anyway. jqLite.data(newNode, jqLite.data(firstElementToRemove)); // Remove $destroy event listeners from `firstElementToRemove` jqLite(firstElementToRemove).off('$destroy'); } // Cleanup any data/listeners on the elements and children. // This includes invoking the $destroy event on any elements with listeners. jqLite.cleanData(fragment.querySelectorAll('*')); // Update the jqLite collection to only contain the `newNode` for (i = 1; i < removeCount; i++) { delete elementsToRemove[i]; } elementsToRemove[0] = newNode; elementsToRemove.length = 1; } function cloneAndAnnotateFn(fn, annotation) { return extend(function() { return fn.apply(null, arguments); }, fn, annotation); } function invokeLinkFn(linkFn, scope, $element, attrs, controllers, transcludeFn) { try { linkFn(scope, $element, attrs, controllers, transcludeFn); } catch (e) { $exceptionHandler(e, startingTag($element)); } } function strictBindingsCheck(attrName, directiveName) { if (strictComponentBindingsEnabled) { throw $compileMinErr('missingattr', 'Attribute \'{0}\' of \'{1}\' is non-optional and must be set!', attrName, directiveName); } } // Set up $watches for isolate scope and controller bindings. function initializeDirectiveBindings(scope, attrs, destination, bindings, directive) { var removeWatchCollection = []; var initialChanges = {}; var changes; forEach(bindings, function initializeBinding(definition, scopeName) { var attrName = definition.attrName, optional = definition.optional, mode = definition.mode, // @, =, <, or & lastValue, parentGet, parentSet, compare, removeWatch; switch (mode) { case '@': if (!optional && !hasOwnProperty.call(attrs, attrName)) { strictBindingsCheck(attrName, directive.name); destination[scopeName] = attrs[attrName] = undefined; } removeWatch = attrs.$observe(attrName, function(value) { if (isString(value) || isBoolean(value)) { var oldValue = destination[scopeName]; recordChanges(scopeName, value, oldValue); destination[scopeName] = value; } }); attrs.$$observers[attrName].$$scope = scope; lastValue = attrs[attrName]; if (isString(lastValue)) { // If the attribute has been provided then we trigger an interpolation to ensure // the value is there for use in the link fn destination[scopeName] = $interpolate(lastValue)(scope); } else if (isBoolean(lastValue)) { // If the attributes is one of the BOOLEAN_ATTR then AngularJS will have converted // the value to boolean rather than a string, so we special case this situation destination[scopeName] = lastValue; } initialChanges[scopeName] = new SimpleChange(_UNINITIALIZED_VALUE, destination[scopeName]); removeWatchCollection.push(removeWatch); break; case '=': if (!hasOwnProperty.call(attrs, attrName)) { if (optional) break; strictBindingsCheck(attrName, directive.name); attrs[attrName] = undefined; } if (optional && !attrs[attrName]) break; parentGet = $parse(attrs[attrName]); if (parentGet.literal) { compare = equals; } else { compare = simpleCompare; } parentSet = parentGet.assign || function() { // reset the change, or we will throw this exception on every $digest lastValue = destination[scopeName] = parentGet(scope); throw $compileMinErr('nonassign', 'Expression \'{0}\' in attribute \'{1}\' used with directive \'{2}\' is non-assignable!', attrs[attrName], attrName, directive.name); }; lastValue = destination[scopeName] = parentGet(scope); var parentValueWatch = function parentValueWatch(parentValue) { if (!compare(parentValue, destination[scopeName])) { // we are out of sync and need to copy if (!compare(parentValue, lastValue)) { // parent changed and it has precedence destination[scopeName] = parentValue; } else { // if the parent can be assigned then do so parentSet(scope, parentValue = destination[scopeName]); } } lastValue = parentValue; return lastValue; }; parentValueWatch.$stateful = true; if (definition.collection) { removeWatch = scope.$watchCollection(attrs[attrName], parentValueWatch); } else { removeWatch = scope.$watch($parse(attrs[attrName], parentValueWatch), null, parentGet.literal); } removeWatchCollection.push(removeWatch); break; case '<': if (!hasOwnProperty.call(attrs, attrName)) { if (optional) break; strictBindingsCheck(attrName, directive.name); attrs[attrName] = undefined; } if (optional && !attrs[attrName]) break; parentGet = $parse(attrs[attrName]); var isLiteral = parentGet.literal; var initialValue = destination[scopeName] = parentGet(scope); initialChanges[scopeName] = new SimpleChange(_UNINITIALIZED_VALUE, destination[scopeName]); removeWatch = scope[definition.collection ? '$watchCollection' : '$watch'](parentGet, function parentValueWatchAction(newValue, oldValue) { if (oldValue === newValue) { if (oldValue === initialValue || (isLiteral && equals(oldValue, initialValue))) { return; } oldValue = initialValue; } recordChanges(scopeName, newValue, oldValue); destination[scopeName] = newValue; }); removeWatchCollection.push(removeWatch); break; case '&': if (!optional && !hasOwnProperty.call(attrs, attrName)) { strictBindingsCheck(attrName, directive.name); } // Don't assign Object.prototype method to scope parentGet = attrs.hasOwnProperty(attrName) ? $parse(attrs[attrName]) : noop; // Don't assign noop to destination if expression is not valid if (parentGet === noop && optional) break; destination[scopeName] = function(locals) { return parentGet(scope, locals); }; break; } }); function recordChanges(key, currentValue, previousValue) { if (isFunction(destination.$onChanges) && !simpleCompare(currentValue, previousValue)) { // If we have not already scheduled the top level onChangesQueue handler then do so now if (!onChangesQueue) { scope.$$postDigest(flushOnChangesQueue); onChangesQueue = []; } // If we have not already queued a trigger of onChanges for this controller then do so now if (!changes) { changes = {}; onChangesQueue.push(triggerOnChangesHook); } // If the has been a change on this property already then we need to reuse the previous value if (changes[key]) { previousValue = changes[key].previousValue; } // Store this change changes[key] = new SimpleChange(previousValue, currentValue); } } function triggerOnChangesHook() { destination.$onChanges(changes); // Now clear the changes so that we schedule onChanges when more changes arrive changes = undefined; } return { initialChanges: initialChanges, removeWatches: removeWatchCollection.length && function removeWatches() { for (var i = 0, ii = removeWatchCollection.length; i < ii; ++i) { removeWatchCollection[i](); } } }; } }]; } function SimpleChange(previous, current) { this.previousValue = previous; this.currentValue = current; } SimpleChange.prototype.isFirstChange = function() { return this.previousValue === _UNINITIALIZED_VALUE; }; var PREFIX_REGEXP = /^((?:x|data)[:\-_])/i; var SPECIAL_CHARS_REGEXP = /[:\-_]+(.)/g; /** * Converts all accepted directives format into proper directive name. * @param name Name to normalize */ function directiveNormalize(name) { return name .replace(PREFIX_REGEXP, '') .replace(SPECIAL_CHARS_REGEXP, function(_, letter, offset) { return offset ? letter.toUpperCase() : letter; }); } /** * @ngdoc type * @name $compile.directive.Attributes * * @description * A shared object between directive compile / linking functions which contains normalized DOM * element attributes. The values reflect current binding state `{{ }}`. The normalization is * needed since all of these are treated as equivalent in AngularJS: * * ``` * * ``` */ /** * @ngdoc property * @name $compile.directive.Attributes#$attr * * @description * A map of DOM element attribute names to the normalized name. This is * needed to do reverse lookup from normalized name back to actual name. */ /** * @ngdoc method * @name $compile.directive.Attributes#$set * @kind function * * @description * Set DOM element attribute value. * * * @param {string} name Normalized element attribute name of the property to modify. The name is * reverse-translated using the {@link ng.$compile.directive.Attributes#$attr $attr} * property to the original name. * @param {string} value Value to set the attribute to. The value can be an interpolated string. */ /** * Closure compiler type information */ function nodesetLinkingFn( /* angular.Scope */ scope, /* NodeList */ nodeList, /* Element */ rootElement, /* function(Function) */ boundTranscludeFn ) {} function directiveLinkingFn( /* nodesetLinkingFn */ nodesetLinkingFn, /* angular.Scope */ scope, /* Node */ node, /* Element */ rootElement, /* function(Function) */ boundTranscludeFn ) {} function tokenDifference(str1, str2) { var values = '', tokens1 = str1.split(/\s+/), tokens2 = str2.split(/\s+/); outer: for (var i = 0; i < tokens1.length; i++) { var token = tokens1[i]; for (var j = 0; j < tokens2.length; j++) { if (token === tokens2[j]) continue outer; } values += (values.length > 0 ? ' ' : '') + token; } return values; } function removeComments(jqNodes) { jqNodes = jqLite(jqNodes); var i = jqNodes.length; if (i <= 1) { return jqNodes; } while (i--) { var node = jqNodes[i]; if (node.nodeType === NODE_TYPE_COMMENT || (node.nodeType === NODE_TYPE_TEXT && node.nodeValue.trim() === '')) { splice.call(jqNodes, i, 1); } } return jqNodes; } angular.js-1.7.9/src/ng/controller.js000066400000000000000000000142371356472325200175300ustar00rootroot00000000000000'use strict'; var $controllerMinErr = minErr('$controller'); var CNTRL_REG = /^(\S+)(\s+as\s+([\w$]+))?$/; function identifierForController(controller, ident) { if (ident && isString(ident)) return ident; if (isString(controller)) { var match = CNTRL_REG.exec(controller); if (match) return match[3]; } } /** * @ngdoc provider * @name $controllerProvider * @this * * @description * The {@link ng.$controller $controller service} is used by AngularJS to create new * controllers. * * This provider allows controller registration via the * {@link ng.$controllerProvider#register register} method. */ function $ControllerProvider() { var controllers = {}; /** * @ngdoc method * @name $controllerProvider#has * @param {string} name Controller name to check. */ this.has = function(name) { return controllers.hasOwnProperty(name); }; /** * @ngdoc method * @name $controllerProvider#register * @param {string|Object} name Controller name, or an object map of controllers where the keys are * the names and the values are the constructors. * @param {Function|Array} constructor Controller constructor fn (optionally decorated with DI * annotations in the array notation). */ this.register = function(name, constructor) { assertNotHasOwnProperty(name, 'controller'); if (isObject(name)) { extend(controllers, name); } else { controllers[name] = constructor; } }; this.$get = ['$injector', function($injector) { /** * @ngdoc service * @name $controller * @requires $injector * * @param {Function|string} constructor If called with a function then it's considered to be the * controller constructor function. Otherwise it's considered to be a string which is used * to retrieve the controller constructor using the following steps: * * * check if a controller with given name is registered via `$controllerProvider` * * check if evaluating the string on the current scope returns a constructor * * The string can use the `controller as property` syntax, where the controller instance is published * as the specified property on the `scope`; the `scope` must be injected into `locals` param for this * to work correctly. * * @param {Object} locals Injection locals for Controller. * @return {Object} Instance of given controller. * * @description * `$controller` service is responsible for instantiating controllers. * * It's just a simple call to {@link auto.$injector $injector}, but extracted into * a service, so that one can override this service with [BC version](https://gist.github.com/1649788). */ return function $controller(expression, locals, later, ident) { // PRIVATE API: // param `later` --- indicates that the controller's constructor is invoked at a later time. // If true, $controller will allocate the object with the correct // prototype chain, but will not invoke the controller until a returned // callback is invoked. // param `ident` --- An optional label which overrides the label parsed from the controller // expression, if any. var instance, match, constructor, identifier; later = later === true; if (ident && isString(ident)) { identifier = ident; } if (isString(expression)) { match = expression.match(CNTRL_REG); if (!match) { throw $controllerMinErr('ctrlfmt', 'Badly formed controller string \'{0}\'. ' + 'Must match `__name__ as __id__` or `__name__`.', expression); } constructor = match[1]; identifier = identifier || match[3]; expression = controllers.hasOwnProperty(constructor) ? controllers[constructor] : getter(locals.$scope, constructor, true); if (!expression) { throw $controllerMinErr('ctrlreg', 'The controller with the name \'{0}\' is not registered.', constructor); } assertArgFn(expression, constructor, true); } if (later) { // Instantiate controller later: // This machinery is used to create an instance of the object before calling the // controller's constructor itself. // // This allows properties to be added to the controller before the constructor is // invoked. Primarily, this is used for isolate scope bindings in $compile. // // This feature is not intended for use by applications, and is thus not documented // publicly. // Object creation: http://jsperf.com/create-constructor/2 var controllerPrototype = (isArray(expression) ? expression[expression.length - 1] : expression).prototype; instance = Object.create(controllerPrototype || null); if (identifier) { addIdentifier(locals, identifier, instance, constructor || expression.name); } return extend(function $controllerInit() { var result = $injector.invoke(expression, instance, locals, constructor); if (result !== instance && (isObject(result) || isFunction(result))) { instance = result; if (identifier) { // If result changed, re-assign controllerAs value to scope. addIdentifier(locals, identifier, instance, constructor || expression.name); } } return instance; }, { instance: instance, identifier: identifier }); } instance = $injector.instantiate(expression, locals, constructor); if (identifier) { addIdentifier(locals, identifier, instance, constructor || expression.name); } return instance; }; function addIdentifier(locals, identifier, instance, name) { if (!(locals && isObject(locals.$scope))) { throw minErr('$controller')('noscp', 'Cannot export controller \'{0}\' as \'{1}\'! No $scope object provided via `locals`.', name, identifier); } locals.$scope[identifier] = instance; } }]; } angular.js-1.7.9/src/ng/cookieReader.js000066400000000000000000000032101356472325200177260ustar00rootroot00000000000000'use strict'; /** * @name $$cookieReader * @requires $document * * @description * This is a private service for reading cookies used by $http and ngCookies * * @return {Object} a key/value map of the current cookies */ function $$CookieReader($document) { var rawDocument = $document[0] || {}; var lastCookies = {}; var lastCookieString = ''; function safeGetCookie(rawDocument) { try { return rawDocument.cookie || ''; } catch (e) { return ''; } } function safeDecodeURIComponent(str) { try { return decodeURIComponent(str); } catch (e) { return str; } } return function() { var cookieArray, cookie, i, index, name; var currentCookieString = safeGetCookie(rawDocument); if (currentCookieString !== lastCookieString) { lastCookieString = currentCookieString; cookieArray = lastCookieString.split('; '); lastCookies = {}; for (i = 0; i < cookieArray.length; i++) { cookie = cookieArray[i]; index = cookie.indexOf('='); if (index > 0) { //ignore nameless cookies name = safeDecodeURIComponent(cookie.substring(0, index)); // the first value that is seen for a cookie is the most // specific one. values for the same cookie name that // follow are for less specific paths. if (isUndefined(lastCookies[name])) { lastCookies[name] = safeDecodeURIComponent(cookie.substring(index + 1)); } } } } return lastCookies; }; } $$CookieReader.$inject = ['$document']; /** @this */ function $$CookieReaderProvider() { this.$get = $$CookieReader; } angular.js-1.7.9/src/ng/directive/000077500000000000000000000000001356472325200167565ustar00rootroot00000000000000angular.js-1.7.9/src/ng/directive/a.js000066400000000000000000000021031356472325200175300ustar00rootroot00000000000000'use strict'; /** * @ngdoc directive * @name a * @restrict E * * @description * Modifies the default behavior of the html a tag so that the default action is prevented when * the href attribute is empty. * * For dynamically creating `href` attributes for a tags, see the {@link ng.ngHref `ngHref`} directive. */ var htmlAnchorDirective = valueFn({ restrict: 'E', compile: function(element, attr) { if (!attr.href && !attr.xlinkHref) { return function(scope, element) { // If the linked element is not an anchor tag anymore, do nothing if (element[0].nodeName.toLowerCase() !== 'a') return; // SVGAElement does not use the href attribute, but rather the 'xlinkHref' attribute. var href = toString.call(element.prop('href')) === '[object SVGAnimatedString]' ? 'xlink:href' : 'href'; element.on('click', function(event) { // if we have no href url, then don't navigate anywhere. if (!element.attr(href)) { event.preventDefault(); } }); }; } } }); angular.js-1.7.9/src/ng/directive/attrs.js000066400000000000000000000371731356472325200204640ustar00rootroot00000000000000'use strict'; /** * @ngdoc directive * @name ngHref * @restrict A * @priority 99 * * @description * Using AngularJS markup like `{{hash}}` in an href attribute will * make the link go to the wrong URL if the user clicks it before * AngularJS has a chance to replace the `{{hash}}` markup with its * value. Until AngularJS replaces the markup the link will be broken * and will most likely return a 404 error. The `ngHref` directive * solves this problem. * * The wrong way to write it: * ```html * link1 * ``` * * The correct way to write it: * ```html * link1 * ``` * * @element A * @param {template} ngHref any string which can contain `{{}}` markup. * * @example * This example shows various combinations of `href`, `ng-href` and `ng-click` attributes * in links and their different behaviors:
    link 1 (link, don't reload)
    link 2 (link, don't reload)
    link 3 (link, reload!)
    anchor (link, don't reload)
    anchor (no link)
    link (link, change location)
    it('should execute ng-click but not reload when href without value', function() { element(by.id('link-1')).click(); expect(element(by.model('value')).getAttribute('value')).toEqual('1'); expect(element(by.id('link-1')).getAttribute('href')).toBe(''); }); it('should execute ng-click but not reload when href empty string', function() { element(by.id('link-2')).click(); expect(element(by.model('value')).getAttribute('value')).toEqual('2'); expect(element(by.id('link-2')).getAttribute('href')).toBe(''); }); it('should execute ng-click and change url when ng-href specified', function() { expect(element(by.id('link-3')).getAttribute('href')).toMatch(/\/123$/); element(by.id('link-3')).click(); // At this point, we navigate away from an AngularJS page, so we need // to use browser.driver to get the base webdriver. browser.wait(function() { return browser.driver.getCurrentUrl().then(function(url) { return url.match(/\/123$/); }); }, 5000, 'page should navigate to /123'); }); it('should execute ng-click but not reload when href empty string and name specified', function() { element(by.id('link-4')).click(); expect(element(by.model('value')).getAttribute('value')).toEqual('4'); expect(element(by.id('link-4')).getAttribute('href')).toBe(''); }); it('should execute ng-click but not reload when no href but name specified', function() { element(by.id('link-5')).click(); expect(element(by.model('value')).getAttribute('value')).toEqual('5'); expect(element(by.id('link-5')).getAttribute('href')).toBe(null); }); it('should only change url when only ng-href', function() { element(by.model('value')).clear(); element(by.model('value')).sendKeys('6'); expect(element(by.id('link-6')).getAttribute('href')).toMatch(/\/6$/); element(by.id('link-6')).click(); // At this point, we navigate away from an AngularJS page, so we need // to use browser.driver to get the base webdriver. browser.wait(function() { return browser.driver.getCurrentUrl().then(function(url) { return url.match(/\/6$/); }); }, 5000, 'page should navigate to /6'); });
    */ /** * @ngdoc directive * @name ngSrc * @restrict A * @priority 99 * * @description * Using AngularJS markup like `{{hash}}` in a `src` attribute doesn't * work right: The browser will fetch from the URL with the literal * text `{{hash}}` until AngularJS replaces the expression inside * `{{hash}}`. The `ngSrc` directive solves this problem. * * The buggy way to write it: * ```html * Description * ``` * * The correct way to write it: * ```html * Description * ``` * * @element IMG * @param {template} ngSrc any string which can contain `{{}}` markup. */ /** * @ngdoc directive * @name ngSrcset * @restrict A * @priority 99 * * @description * Using AngularJS markup like `{{hash}}` in a `srcset` attribute doesn't * work right: The browser will fetch from the URL with the literal * text `{{hash}}` until AngularJS replaces the expression inside * `{{hash}}`. The `ngSrcset` directive solves this problem. * * The buggy way to write it: * ```html * Description * ``` * * The correct way to write it: * ```html * Description * ``` * * @element IMG * @param {template} ngSrcset any string which can contain `{{}}` markup. */ /** * @ngdoc directive * @name ngDisabled * @restrict A * @priority 100 * * @description * * This directive sets the `disabled` attribute on the element (typically a form control, * e.g. `input`, `button`, `select` etc.) if the * {@link guide/expression expression} inside `ngDisabled` evaluates to truthy. * * A special directive is necessary because we cannot use interpolation inside the `disabled` * attribute. See the {@link guide/interpolation interpolation guide} for more info. * * @example
    it('should toggle button', function() { expect(element(by.css('button')).getAttribute('disabled')).toBeFalsy(); element(by.model('checked')).click(); expect(element(by.css('button')).getAttribute('disabled')).toBeTruthy(); });
    * * @param {expression} ngDisabled If the {@link guide/expression expression} is truthy, * then the `disabled` attribute will be set on the element */ /** * @ngdoc directive * @name ngChecked * @restrict A * @priority 100 * * @description * Sets the `checked` attribute on the element, if the expression inside `ngChecked` is truthy. * * Note that this directive should not be used together with {@link ngModel `ngModel`}, * as this can lead to unexpected behavior. * * A special directive is necessary because we cannot use interpolation inside the `checked` * attribute. See the {@link guide/interpolation interpolation guide} for more info. * * @example
    it('should check both checkBoxes', function() { expect(element(by.id('checkFollower')).getAttribute('checked')).toBeFalsy(); element(by.model('leader')).click(); expect(element(by.id('checkFollower')).getAttribute('checked')).toBeTruthy(); });
    * * @element INPUT * @param {expression} ngChecked If the {@link guide/expression expression} is truthy, * then the `checked` attribute will be set on the element */ /** * @ngdoc directive * @name ngReadonly * @restrict A * @priority 100 * * @description * * Sets the `readonly` attribute on the element, if the expression inside `ngReadonly` is truthy. * Note that `readonly` applies only to `input` elements with specific types. [See the input docs on * MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#attr-readonly) for more information. * * A special directive is necessary because we cannot use interpolation inside the `readonly` * attribute. See the {@link guide/interpolation interpolation guide} for more info. * * @example
    it('should toggle readonly attr', function() { expect(element(by.css('[type="text"]')).getAttribute('readonly')).toBeFalsy(); element(by.model('checked')).click(); expect(element(by.css('[type="text"]')).getAttribute('readonly')).toBeTruthy(); });
    * * @element INPUT * @param {expression} ngReadonly If the {@link guide/expression expression} is truthy, * then special attribute "readonly" will be set on the element */ /** * @ngdoc directive * @name ngSelected * @restrict A * @priority 100 * * @description * * Sets the `selected` attribute on the element, if the expression inside `ngSelected` is truthy. * * A special directive is necessary because we cannot use interpolation inside the `selected` * attribute. See the {@link guide/interpolation interpolation guide} for more info. * *
    * **Note:** `ngSelected` does not interact with the `select` and `ngModel` directives, it only * sets the `selected` attribute on the element. If you are using `ngModel` on the select, you * should not use `ngSelected` on the options, as `ngModel` will set the select value and * selected options. *
    * * @example
    it('should select Greetings!', function() { expect(element(by.id('greet')).getAttribute('selected')).toBeFalsy(); element(by.model('selected')).click(); expect(element(by.id('greet')).getAttribute('selected')).toBeTruthy(); });
    * * @element OPTION * @param {expression} ngSelected If the {@link guide/expression expression} is truthy, * then special attribute "selected" will be set on the element */ /** * @ngdoc directive * @name ngOpen * @restrict A * @priority 100 * * @description * * Sets the `open` attribute on the element, if the expression inside `ngOpen` is truthy. * * A special directive is necessary because we cannot use interpolation inside the `open` * attribute. See the {@link guide/interpolation interpolation guide} for more info. * * ## A note about browser compatibility * * Internet Explorer and Edge do not support the `details` element, it is * recommended to use {@link ng.ngShow} and {@link ng.ngHide} instead. * * @example
    List
    • Apple
    • Orange
    • Durian
    it('should toggle open', function() { expect(element(by.id('details')).getAttribute('open')).toBeFalsy(); element(by.model('open')).click(); expect(element(by.id('details')).getAttribute('open')).toBeTruthy(); });
    * * @element DETAILS * @param {expression} ngOpen If the {@link guide/expression expression} is truthy, * then special attribute "open" will be set on the element */ var ngAttributeAliasDirectives = {}; // boolean attrs are evaluated forEach(BOOLEAN_ATTR, function(propName, attrName) { // binding to multiple is not supported if (propName === 'multiple') return; function defaultLinkFn(scope, element, attr) { scope.$watch(attr[normalized], function ngBooleanAttrWatchAction(value) { attr.$set(attrName, !!value); }); } var normalized = directiveNormalize('ng-' + attrName); var linkFn = defaultLinkFn; if (propName === 'checked') { linkFn = function(scope, element, attr) { // ensuring ngChecked doesn't interfere with ngModel when both are set on the same input if (attr.ngModel !== attr[normalized]) { defaultLinkFn(scope, element, attr); } }; } ngAttributeAliasDirectives[normalized] = function() { return { restrict: 'A', priority: 100, link: linkFn }; }; }); // aliased input attrs are evaluated forEach(ALIASED_ATTR, function(htmlAttr, ngAttr) { ngAttributeAliasDirectives[ngAttr] = function() { return { priority: 100, link: function(scope, element, attr) { //special case ngPattern when a literal regular expression value //is used as the expression (this way we don't have to watch anything). if (ngAttr === 'ngPattern' && attr.ngPattern.charAt(0) === '/') { var match = attr.ngPattern.match(REGEX_STRING_REGEXP); if (match) { attr.$set('ngPattern', new RegExp(match[1], match[2])); return; } } scope.$watch(attr[ngAttr], function ngAttrAliasWatchAction(value) { attr.$set(ngAttr, value); }); } }; }; }); // ng-src, ng-srcset, ng-href are interpolated forEach(['src', 'srcset', 'href'], function(attrName) { var normalized = directiveNormalize('ng-' + attrName); ngAttributeAliasDirectives[normalized] = ['$sce', function($sce) { return { priority: 99, // it needs to run after the attributes are interpolated link: function(scope, element, attr) { var propName = attrName, name = attrName; if (attrName === 'href' && toString.call(element.prop('href')) === '[object SVGAnimatedString]') { name = 'xlinkHref'; attr.$attr[name] = 'xlink:href'; propName = null; } // We need to sanitize the url at least once, in case it is a constant // non-interpolated attribute. attr.$set(normalized, $sce.getTrustedMediaUrl(attr[normalized])); attr.$observe(normalized, function(value) { if (!value) { if (attrName === 'href') { attr.$set(name, null); } return; } attr.$set(name, value); // Support: IE 9-11 only // On IE, if "ng:src" directive declaration is used and "src" attribute doesn't exist // then calling element.setAttribute('src', 'foo') doesn't do anything, so we need // to set the property as well to achieve the desired effect. // We use attr[attrName] value since $set might have sanitized the url. if (msie && propName) element.prop(propName, attr[name]); }); } }; }]; }); angular.js-1.7.9/src/ng/directive/directives.js000066400000000000000000000003251356472325200214550ustar00rootroot00000000000000'use strict'; function ngDirective(directive) { if (isFunction(directive)) { directive = { link: directive }; } directive.restrict = directive.restrict || 'AC'; return valueFn(directive); } angular.js-1.7.9/src/ng/directive/form.js000066400000000000000000000643371356472325200202740ustar00rootroot00000000000000'use strict'; /* global -nullFormCtrl, -PENDING_CLASS, -SUBMITTED_CLASS */ var nullFormCtrl = { $addControl: noop, $getControls: valueFn([]), $$renameControl: nullFormRenameControl, $removeControl: noop, $setValidity: noop, $setDirty: noop, $setPristine: noop, $setSubmitted: noop, $$setSubmitted: noop }, PENDING_CLASS = 'ng-pending', SUBMITTED_CLASS = 'ng-submitted'; function nullFormRenameControl(control, name) { control.$name = name; } /** * @ngdoc type * @name form.FormController * * @property {boolean} $pristine True if user has not interacted with the form yet. * @property {boolean} $dirty True if user has already interacted with the form. * @property {boolean} $valid True if all of the containing forms and controls are valid. * @property {boolean} $invalid True if at least one containing control or form is invalid. * @property {boolean} $submitted True if user has submitted the form even if its invalid. * * @property {Object} $pending An object hash, containing references to controls or forms with * pending validators, where: * * - keys are validations tokens (error names). * - values are arrays of controls or forms that have a pending validator for the given error name. * * See {@link form.FormController#$error $error} for a list of built-in validation tokens. * * @property {Object} $error An object hash, containing references to controls or forms with failing * validators, where: * * - keys are validation tokens (error names), * - values are arrays of controls or forms that have a failing validator for the given error name. * * Built-in validation tokens: * - `email` * - `max` * - `maxlength` * - `min` * - `minlength` * - `number` * - `pattern` * - `required` * - `url` * - `date` * - `datetimelocal` * - `time` * - `week` * - `month` * * @description * `FormController` keeps track of all its controls and nested forms as well as the state of them, * such as being valid/invalid or dirty/pristine. * * Each {@link ng.directive:form form} directive creates an instance * of `FormController`. * */ //asks for $scope to fool the BC controller module FormController.$inject = ['$element', '$attrs', '$scope', '$animate', '$interpolate']; function FormController($element, $attrs, $scope, $animate, $interpolate) { this.$$controls = []; // init state this.$error = {}; this.$$success = {}; this.$pending = undefined; this.$name = $interpolate($attrs.name || $attrs.ngForm || '')($scope); this.$dirty = false; this.$pristine = true; this.$valid = true; this.$invalid = false; this.$submitted = false; this.$$parentForm = nullFormCtrl; this.$$element = $element; this.$$animate = $animate; setupValidity(this); } FormController.prototype = { /** * @ngdoc method * @name form.FormController#$rollbackViewValue * * @description * Rollback all form controls pending updates to the `$modelValue`. * * Updates may be pending by a debounced event or because the input is waiting for a some future * event defined in `ng-model-options`. This method is typically needed by the reset button of * a form that uses `ng-model-options` to pend updates. */ $rollbackViewValue: function() { forEach(this.$$controls, function(control) { control.$rollbackViewValue(); }); }, /** * @ngdoc method * @name form.FormController#$commitViewValue * * @description * Commit all form controls pending updates to the `$modelValue`. * * Updates may be pending by a debounced event or because the input is waiting for a some future * event defined in `ng-model-options`. This method is rarely needed as `NgModelController` * usually handles calling this in response to input events. */ $commitViewValue: function() { forEach(this.$$controls, function(control) { control.$commitViewValue(); }); }, /** * @ngdoc method * @name form.FormController#$addControl * @param {object} control control object, either a {@link form.FormController} or an * {@link ngModel.NgModelController} * * @description * Register a control with the form. Input elements using ngModelController do this automatically * when they are linked. * * Note that the current state of the control will not be reflected on the new parent form. This * is not an issue with normal use, as freshly compiled and linked controls are in a `$pristine` * state. * * However, if the method is used programmatically, for example by adding dynamically created controls, * or controls that have been previously removed without destroying their corresponding DOM element, * it's the developers responsibility to make sure the current state propagates to the parent form. * * For example, if an input control is added that is already `$dirty` and has `$error` properties, * calling `$setDirty()` and `$validate()` afterwards will propagate the state to the parent form. */ $addControl: function(control) { // Breaking change - before, inputs whose name was "hasOwnProperty" were quietly ignored // and not added to the scope. Now we throw an error. assertNotHasOwnProperty(control.$name, 'input'); this.$$controls.push(control); if (control.$name) { this[control.$name] = control; } control.$$parentForm = this; }, /** * @ngdoc method * @name form.FormController#$getControls * @returns {Array} the controls that are currently part of this form * * @description * This method returns a **shallow copy** of the controls that are currently part of this form. * The controls can be instances of {@link form.FormController `FormController`} * ({@link ngForm "child-forms"}) and of {@link ngModel.NgModelController `NgModelController`}. * If you need access to the controls of child-forms, you have to call `$getControls()` * recursively on them. * This can be used for example to iterate over all controls to validate them. * * The controls can be accessed normally, but adding to, or removing controls from the array has * no effect on the form. Instead, use {@link form.FormController#$addControl `$addControl()`} and * {@link form.FormController#$removeControl `$removeControl()`} for this use-case. * Likewise, adding a control to, or removing a control from the form is not reflected * in the shallow copy. That means you should get a fresh copy from `$getControls()` every time * you need access to the controls. */ $getControls: function() { return shallowCopy(this.$$controls); }, // Private API: rename a form control $$renameControl: function(control, newName) { var oldName = control.$name; if (this[oldName] === control) { delete this[oldName]; } this[newName] = control; control.$name = newName; }, /** * @ngdoc method * @name form.FormController#$removeControl * @param {object} control control object, either a {@link form.FormController} or an * {@link ngModel.NgModelController} * * @description * Deregister a control from the form. * * Input elements using ngModelController do this automatically when they are destroyed. * * Note that only the removed control's validation state (`$errors`etc.) will be removed from the * form. `$dirty`, `$submitted` states will not be changed, because the expected behavior can be * different from case to case. For example, removing the only `$dirty` control from a form may or * may not mean that the form is still `$dirty`. */ $removeControl: function(control) { if (control.$name && this[control.$name] === control) { delete this[control.$name]; } forEach(this.$pending, function(value, name) { // eslint-disable-next-line no-invalid-this this.$setValidity(name, null, control); }, this); forEach(this.$error, function(value, name) { // eslint-disable-next-line no-invalid-this this.$setValidity(name, null, control); }, this); forEach(this.$$success, function(value, name) { // eslint-disable-next-line no-invalid-this this.$setValidity(name, null, control); }, this); arrayRemove(this.$$controls, control); control.$$parentForm = nullFormCtrl; }, /** * @ngdoc method * @name form.FormController#$setDirty * * @description * Sets the form to a dirty state. * * This method can be called to add the 'ng-dirty' class and set the form to a dirty * state (ng-dirty class). This method will also propagate to parent forms. */ $setDirty: function() { this.$$animate.removeClass(this.$$element, PRISTINE_CLASS); this.$$animate.addClass(this.$$element, DIRTY_CLASS); this.$dirty = true; this.$pristine = false; this.$$parentForm.$setDirty(); }, /** * @ngdoc method * @name form.FormController#$setPristine * * @description * Sets the form to its pristine state. * * This method sets the form's `$pristine` state to true, the `$dirty` state to false, removes * the `ng-dirty` class and adds the `ng-pristine` class. Additionally, it sets the `$submitted` * state to false. * * This method will also propagate to all the controls contained in this form. * * Setting a form back to a pristine state is often useful when we want to 'reuse' a form after * saving or resetting it. */ $setPristine: function() { this.$$animate.setClass(this.$$element, PRISTINE_CLASS, DIRTY_CLASS + ' ' + SUBMITTED_CLASS); this.$dirty = false; this.$pristine = true; this.$submitted = false; forEach(this.$$controls, function(control) { control.$setPristine(); }); }, /** * @ngdoc method * @name form.FormController#$setUntouched * * @description * Sets the form to its untouched state. * * This method can be called to remove the 'ng-touched' class and set the form controls to their * untouched state (ng-untouched class). * * Setting a form controls back to their untouched state is often useful when setting the form * back to its pristine state. */ $setUntouched: function() { forEach(this.$$controls, function(control) { control.$setUntouched(); }); }, /** * @ngdoc method * @name form.FormController#$setSubmitted * * @description * Sets the form to its `$submitted` state. This will also set `$submitted` on all child and * parent forms of the form. */ $setSubmitted: function() { var rootForm = this; while (rootForm.$$parentForm && (rootForm.$$parentForm !== nullFormCtrl)) { rootForm = rootForm.$$parentForm; } rootForm.$$setSubmitted(); }, $$setSubmitted: function() { this.$$animate.addClass(this.$$element, SUBMITTED_CLASS); this.$submitted = true; forEach(this.$$controls, function(control) { if (control.$$setSubmitted) { control.$$setSubmitted(); } }); } }; /** * @ngdoc method * @name form.FormController#$setValidity * * @description * Change the validity state of the form, and notify the parent form (if any). * * Application developers will rarely need to call this method directly. It is used internally, by * {@link ngModel.NgModelController#$setValidity NgModelController.$setValidity()}, to propagate a * control's validity state to the parent `FormController`. * * @param {string} validationErrorKey Name of the validator. The `validationErrorKey` will be * assigned to either `$error[validationErrorKey]` or `$pending[validationErrorKey]` (for * unfulfilled `$asyncValidators`), so that it is available for data-binding. The * `validationErrorKey` should be in camelCase and will get converted into dash-case for * class name. Example: `myError` will result in `ng-valid-my-error` and * `ng-invalid-my-error` classes and can be bound to as `{{ someForm.$error.myError }}`. * @param {boolean} isValid Whether the current state is valid (true), invalid (false), pending * (undefined), or skipped (null). Pending is used for unfulfilled `$asyncValidators`. * Skipped is used by AngularJS when validators do not run because of parse errors and when * `$asyncValidators` do not run because any of the `$validators` failed. * @param {NgModelController | FormController} controller - The controller whose validity state is * triggering the change. */ addSetValidityMethod({ clazz: FormController, set: function(object, property, controller) { var list = object[property]; if (!list) { object[property] = [controller]; } else { var index = list.indexOf(controller); if (index === -1) { list.push(controller); } } }, unset: function(object, property, controller) { var list = object[property]; if (!list) { return; } arrayRemove(list, controller); if (list.length === 0) { delete object[property]; } } }); /** * @ngdoc directive * @name ngForm * @restrict EAC * * @description * Helper directive that makes it possible to create control groups inside a * {@link ng.directive:form `form`} directive. * These "child forms" can be used, for example, to determine the validity of a sub-group of * controls. * *
    * **Note**: `ngForm` cannot be used as a replacement for `
    `, because it lacks its * [built-in HTML functionality](https://html.spec.whatwg.org/#the-form-element). * Specifically, you cannot submit `ngForm` like a `` tag. That means, * you cannot send data to the server with `ngForm`, or integrate it with * {@link ng.directive:ngSubmit `ngSubmit`}. *
    * * @param {string=} ngForm|name Name of the form. If specified, the form controller will * be published into the related scope, under this name. * */ /** * @ngdoc directive * @name form * @restrict E * * @description * Directive that instantiates * {@link form.FormController FormController}. * * If the `name` attribute is specified, the form controller is published onto the current scope under * this name. * * ## Alias: {@link ng.directive:ngForm `ngForm`} * * In AngularJS, forms can be nested. This means that the outer form is valid when all of the child * forms are valid as well. However, browsers do not allow nesting of `` elements, so * AngularJS provides the {@link ng.directive:ngForm `ngForm`} directive, which behaves identically to * `form` but can be nested. Nested forms can be useful, for example, if the validity of a sub-group * of controls needs to be determined. * * ## CSS classes * - `ng-valid` is set if the form is valid. * - `ng-invalid` is set if the form is invalid. * - `ng-pending` is set if the form is pending. * - `ng-pristine` is set if the form is pristine. * - `ng-dirty` is set if the form is dirty. * - `ng-submitted` is set if the form was submitted. * * Keep in mind that ngAnimate can detect each of these classes when added and removed. * * * ## Submitting a form and preventing the default action * * Since the role of forms in client-side AngularJS applications is different than in classical * roundtrip apps, it is desirable for the browser not to translate the form submission into a full * page reload that sends the data to the server. Instead some javascript logic should be triggered * to handle the form submission in an application-specific way. * * For this reason, AngularJS prevents the default action (form submission to the server) unless the * `` element has an `action` attribute specified. * * You can use one of the following two ways to specify what javascript method should be called when * a form is submitted: * * - {@link ng.directive:ngSubmit ngSubmit} directive on the form element * - {@link ng.directive:ngClick ngClick} directive on the first * button or input field of type submit (input[type=submit]) * * To prevent double execution of the handler, use only one of the {@link ng.directive:ngSubmit ngSubmit} * or {@link ng.directive:ngClick ngClick} directives. * This is because of the following form submission rules in the HTML specification: * * - If a form has only one input field then hitting enter in this field triggers form submit * (`ngSubmit`) * - if a form has 2+ input fields and no buttons or input[type=submit] then hitting enter * doesn't trigger submit * - if a form has one or more input fields and one or more buttons or input[type=submit] then * hitting enter in any of the input fields will trigger the click handler on the *first* button or * input[type=submit] (`ngClick`) *and* a submit handler on the enclosing form (`ngSubmit`) * * Any pending `ngModelOptions` changes will take place immediately when an enclosing form is * submitted. Note that `ngClick` events will occur before the model is updated. Use `ngSubmit` * to have access to the updated model. * * @animations * Animations in ngForm are triggered when any of the associated CSS classes are added and removed. * These classes are: `.ng-pristine`, `.ng-dirty`, `.ng-invalid` and `.ng-valid` as well as any * other validations that are performed within the form. Animations in ngForm are similar to how * they work in ngClass and animations can be hooked into using CSS transitions, keyframes as well * as JS animations. * * The following example shows a simple way to utilize CSS transitions to style a form element * that has been rendered as invalid after it has been validated: * *
     * //be sure to include ngAnimate as a module to hook into more
     * //advanced animations
     * .my-form {
     *   transition:0.5s linear all;
     *   background: white;
     * }
     * .my-form.ng-invalid {
     *   background: red;
     *   color:white;
     * }
     * 
    * * @example userType: Required!
    userType = {{userType}}
    myForm.input.$valid = {{myForm.input.$valid}}
    myForm.input.$error = {{myForm.input.$error}}
    myForm.$valid = {{myForm.$valid}}
    myForm.$error.required = {{!!myForm.$error.required}}
    it('should initialize to model', function() { var userType = element(by.binding('userType')); var valid = element(by.binding('myForm.input.$valid')); expect(userType.getText()).toContain('guest'); expect(valid.getText()).toContain('true'); }); it('should be invalid if empty', function() { var userType = element(by.binding('userType')); var valid = element(by.binding('myForm.input.$valid')); var userInput = element(by.model('userType')); userInput.clear(); userInput.sendKeys(''); expect(userType.getText()).toEqual('userType ='); expect(valid.getText()).toContain('false'); });
    * * @param {string=} name Name of the form. If specified, the form controller will be published into * related scope, under this name. */ var formDirectiveFactory = function(isNgForm) { return ['$timeout', '$parse', function($timeout, $parse) { var formDirective = { name: 'form', restrict: isNgForm ? 'EAC' : 'E', require: ['form', '^^?form'], //first is the form's own ctrl, second is an optional parent form controller: FormController, compile: function ngFormCompile(formElement, attr) { // Setup initial state of the control formElement.addClass(PRISTINE_CLASS).addClass(VALID_CLASS); var nameAttr = attr.name ? 'name' : (isNgForm && attr.ngForm ? 'ngForm' : false); return { pre: function ngFormPreLink(scope, formElement, attr, ctrls) { var controller = ctrls[0]; // if `action` attr is not present on the form, prevent the default action (submission) if (!('action' in attr)) { // we can't use jq events because if a form is destroyed during submission the default // action is not prevented. see #1238 // // IE 9 is not affected because it doesn't fire a submit event and try to do a full // page reload if the form was destroyed by submission of the form via a click handler // on a button in the form. Looks like an IE9 specific bug. var handleFormSubmission = function(event) { scope.$apply(function() { controller.$commitViewValue(); controller.$setSubmitted(); }); event.preventDefault(); }; formElement[0].addEventListener('submit', handleFormSubmission); // unregister the preventDefault listener so that we don't not leak memory but in a // way that will achieve the prevention of the default action. formElement.on('$destroy', function() { $timeout(function() { formElement[0].removeEventListener('submit', handleFormSubmission); }, 0, false); }); } var parentFormCtrl = ctrls[1] || controller.$$parentForm; parentFormCtrl.$addControl(controller); var setter = nameAttr ? getSetter(controller.$name) : noop; if (nameAttr) { setter(scope, controller); attr.$observe(nameAttr, function(newValue) { if (controller.$name === newValue) return; setter(scope, undefined); controller.$$parentForm.$$renameControl(controller, newValue); setter = getSetter(controller.$name); setter(scope, controller); }); } formElement.on('$destroy', function() { controller.$$parentForm.$removeControl(controller); setter(scope, undefined); extend(controller, nullFormCtrl); //stop propagating child destruction handlers upwards }); } }; } }; return formDirective; function getSetter(expression) { if (expression === '') { //create an assignable expression, so forms with an empty name can be renamed later return $parse('this[""]').assign; } return $parse(expression).assign || noop; } }]; }; var formDirective = formDirectiveFactory(); var ngFormDirective = formDirectiveFactory(true); // helper methods function setupValidity(instance) { instance.$$classCache = {}; instance.$$classCache[INVALID_CLASS] = !(instance.$$classCache[VALID_CLASS] = instance.$$element.hasClass(VALID_CLASS)); } function addSetValidityMethod(context) { var clazz = context.clazz, set = context.set, unset = context.unset; clazz.prototype.$setValidity = function(validationErrorKey, state, controller) { if (isUndefined(state)) { createAndSet(this, '$pending', validationErrorKey, controller); } else { unsetAndCleanup(this, '$pending', validationErrorKey, controller); } if (!isBoolean(state)) { unset(this.$error, validationErrorKey, controller); unset(this.$$success, validationErrorKey, controller); } else { if (state) { unset(this.$error, validationErrorKey, controller); set(this.$$success, validationErrorKey, controller); } else { set(this.$error, validationErrorKey, controller); unset(this.$$success, validationErrorKey, controller); } } if (this.$pending) { cachedToggleClass(this, PENDING_CLASS, true); this.$valid = this.$invalid = undefined; toggleValidationCss(this, '', null); } else { cachedToggleClass(this, PENDING_CLASS, false); this.$valid = isObjectEmpty(this.$error); this.$invalid = !this.$valid; toggleValidationCss(this, '', this.$valid); } // re-read the state as the set/unset methods could have // combined state in this.$error[validationError] (used for forms), // where setting/unsetting only increments/decrements the value, // and does not replace it. var combinedState; if (this.$pending && this.$pending[validationErrorKey]) { combinedState = undefined; } else if (this.$error[validationErrorKey]) { combinedState = false; } else if (this.$$success[validationErrorKey]) { combinedState = true; } else { combinedState = null; } toggleValidationCss(this, validationErrorKey, combinedState); this.$$parentForm.$setValidity(validationErrorKey, combinedState, this); }; function createAndSet(ctrl, name, value, controller) { if (!ctrl[name]) { ctrl[name] = {}; } set(ctrl[name], value, controller); } function unsetAndCleanup(ctrl, name, value, controller) { if (ctrl[name]) { unset(ctrl[name], value, controller); } if (isObjectEmpty(ctrl[name])) { ctrl[name] = undefined; } } function cachedToggleClass(ctrl, className, switchValue) { if (switchValue && !ctrl.$$classCache[className]) { ctrl.$$animate.addClass(ctrl.$$element, className); ctrl.$$classCache[className] = true; } else if (!switchValue && ctrl.$$classCache[className]) { ctrl.$$animate.removeClass(ctrl.$$element, className); ctrl.$$classCache[className] = false; } } function toggleValidationCss(ctrl, validationErrorKey, isValid) { validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : ''; cachedToggleClass(ctrl, VALID_CLASS + validationErrorKey, isValid === true); cachedToggleClass(ctrl, INVALID_CLASS + validationErrorKey, isValid === false); } } function isObjectEmpty(obj) { if (obj) { for (var prop in obj) { if (obj.hasOwnProperty(prop)) { return false; } } } return true; } angular.js-1.7.9/src/ng/directive/input.js000066400000000000000000003102111356472325200204510ustar00rootroot00000000000000'use strict'; /* global VALID_CLASS: false, INVALID_CLASS: false, PRISTINE_CLASS: false, DIRTY_CLASS: false, ngModelMinErr: false */ // Regex code was initially obtained from SO prior to modification: https://stackoverflow.com/questions/3143070/javascript-regex-iso-datetime#answer-3143231 var ISO_DATE_REGEXP = /^\d{4,}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+(?:[+-][0-2]\d:[0-5]\d|Z)$/; // See valid URLs in RFC3987 (http://tools.ietf.org/html/rfc3987) // Note: We are being more lenient, because browsers are too. // 1. Scheme // 2. Slashes // 3. Username // 4. Password // 5. Hostname // 6. Port // 7. Path // 8. Query // 9. Fragment // 1111111111111111 222 333333 44444 55555555555555555555555 666 77777777 8888888 999 var URL_REGEXP = /^[a-z][a-z\d.+-]*:\/*(?:[^:@]+(?::[^@]+)?@)?(?:[^\s:/?#]+|\[[a-f\d:]+])(?::\d+)?(?:\/[^?#]*)?(?:\?[^#]*)?(?:#.*)?$/i; // eslint-disable-next-line max-len var EMAIL_REGEXP = /^(?=.{1,254}$)(?=.{1,64}@)[-!#$%&'*+/0-9=?A-Z^_`a-z{|}~]+(\.[-!#$%&'*+/0-9=?A-Z^_`a-z{|}~]+)*@[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?(\.[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?)*$/; var NUMBER_REGEXP = /^\s*(-|\+)?(\d+|(\d*(\.\d*)))([eE][+-]?\d+)?\s*$/; var DATE_REGEXP = /^(\d{4,})-(\d{2})-(\d{2})$/; var DATETIMELOCAL_REGEXP = /^(\d{4,})-(\d\d)-(\d\d)T(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/; var WEEK_REGEXP = /^(\d{4,})-W(\d\d)$/; var MONTH_REGEXP = /^(\d{4,})-(\d\d)$/; var TIME_REGEXP = /^(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/; var PARTIAL_VALIDATION_EVENTS = 'keydown wheel mousedown'; var PARTIAL_VALIDATION_TYPES = createMap(); forEach('date,datetime-local,month,time,week'.split(','), function(type) { PARTIAL_VALIDATION_TYPES[type] = true; }); var inputType = { /** * @ngdoc input * @name input[text] * * @description * Standard HTML text input with AngularJS data binding, inherited by most of the `input` elements. * * * @param {string} ngModel Assignable AngularJS expression to data-bind to. * @param {string=} name Property name of the form under which the control is published. * @param {string=} required Adds `required` validation error key if the value is not entered. * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of * `required` when you want to data-bind to the `required` attribute. * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than * minlength. * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of * any length. * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string * that contains the regular expression body that will be converted to a regular expression * as in the ngPattern directive. * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue} * does not match a RegExp found by evaluating the AngularJS expression given in the attribute value. * If the expression evaluates to a RegExp object, then this is used directly. * If the expression evaluates to a string, then it will be converted to a RegExp * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to * `new RegExp('^abc$')`.
    * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to * start at the index of the last search's match, thus not taking the whole input value into * account. * @param {string=} ngChange AngularJS expression to be executed when input changes due to user * interaction with the input element. * @param {boolean=} [ngTrim=true] If set to false AngularJS will not automatically trim the input. * This parameter is ignored for input[type=password] controls, which will never trim the * input. * * @example
    Required! Single word only!
    text = {{example.text}}
    myForm.input.$valid = {{myForm.input.$valid}}
    myForm.input.$error = {{myForm.input.$error}}
    myForm.$valid = {{myForm.$valid}}
    myForm.$error.required = {{!!myForm.$error.required}}
    var text = element(by.binding('example.text')); var valid = element(by.binding('myForm.input.$valid')); var input = element(by.model('example.text')); it('should initialize to model', function() { expect(text.getText()).toContain('guest'); expect(valid.getText()).toContain('true'); }); it('should be invalid if empty', function() { input.clear(); input.sendKeys(''); expect(text.getText()).toEqual('text ='); expect(valid.getText()).toContain('false'); }); it('should be invalid if multi word', function() { input.clear(); input.sendKeys('hello world'); expect(valid.getText()).toContain('false'); });
    */ 'text': textInputType, /** * @ngdoc input * @name input[date] * * @description * Input with date validation and transformation. In browsers that do not yet support * the HTML5 date input, a text element will be used. In that case, text must be entered in a valid ISO-8601 * date format (yyyy-MM-dd), for example: `2009-01-06`. Since many * modern browsers do not yet support this input type, it is important to provide cues to users on the * expected input format via a placeholder or label. * * The model must always be a Date object, otherwise AngularJS will throw an error. * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. * * The timezone to be used to read/write the `Date` instance in the model can be defined using * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. * * @param {string} ngModel Assignable AngularJS expression to data-bind to. * @param {string=} name Property name of the form under which the control is published. * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. This must be a * valid ISO date string (yyyy-MM-dd). You can also use interpolation inside this attribute * (e.g. `min="{{minDate | date:'yyyy-MM-dd'}}"`). Note that `min` will also add native HTML5 * constraint validation. * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. This must be * a valid ISO date string (yyyy-MM-dd). You can also use interpolation inside this attribute * (e.g. `max="{{maxDate | date:'yyyy-MM-dd'}}"`). Note that `max` will also add native HTML5 * constraint validation. * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO date string * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute. * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO date string * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute. * @param {string=} required Sets `required` validation error key if the value is not entered. * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of * `required` when you want to data-bind to the `required` attribute. * @param {string=} ngChange AngularJS expression to be executed when input changes due to user * interaction with the input element. * * @example
    Required! Not a valid date!
    value = {{example.value | date: "yyyy-MM-dd"}}
    myForm.input.$valid = {{myForm.input.$valid}}
    myForm.input.$error = {{myForm.input.$error}}
    myForm.$valid = {{myForm.$valid}}
    myForm.$error.required = {{!!myForm.$error.required}}
    var value = element(by.binding('example.value | date: "yyyy-MM-dd"')); var valid = element(by.binding('myForm.input.$valid')); // currently protractor/webdriver does not support // sending keys to all known HTML5 input controls // for various browsers (see https://github.com/angular/protractor/issues/562). function setInput(val) { // set the value of the element and force validation. var scr = "var ipt = document.getElementById('exampleInput'); " + "ipt.value = '" + val + "';" + "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; browser.executeScript(scr); } it('should initialize to model', function() { expect(value.getText()).toContain('2013-10-22'); expect(valid.getText()).toContain('myForm.input.$valid = true'); }); it('should be invalid if empty', function() { setInput(''); expect(value.getText()).toEqual('value ='); expect(valid.getText()).toContain('myForm.input.$valid = false'); }); it('should be invalid if over max', function() { setInput('2015-01-01'); expect(value.getText()).toContain(''); expect(valid.getText()).toContain('myForm.input.$valid = false'); });
    */ 'date': createDateInputType('date', DATE_REGEXP, createDateParser(DATE_REGEXP, ['yyyy', 'MM', 'dd']), 'yyyy-MM-dd'), /** * @ngdoc input * @name input[datetime-local] * * @description * Input with datetime validation and transformation. In browsers that do not yet support * the HTML5 date input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 * local datetime format (yyyy-MM-ddTHH:mm:ss), for example: `2010-12-28T14:57:00`. * * The model must always be a Date object, otherwise AngularJS will throw an error. * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. * * The timezone to be used to read/write the `Date` instance in the model can be defined using * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. * * The format of the displayed time can be adjusted with the * {@link ng.directive:ngModelOptions#ngModelOptions-arguments ngModelOptions} `timeSecondsFormat` * and `timeStripZeroSeconds`. * * @param {string} ngModel Assignable AngularJS expression to data-bind to. * @param {string=} name Property name of the form under which the control is published. * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. * This must be a valid ISO datetime format (yyyy-MM-ddTHH:mm:ss). You can also use interpolation * inside this attribute (e.g. `min="{{minDatetimeLocal | date:'yyyy-MM-ddTHH:mm:ss'}}"`). * Note that `min` will also add native HTML5 constraint validation. * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. * This must be a valid ISO datetime format (yyyy-MM-ddTHH:mm:ss). You can also use interpolation * inside this attribute (e.g. `max="{{maxDatetimeLocal | date:'yyyy-MM-ddTHH:mm:ss'}}"`). * Note that `max` will also add native HTML5 constraint validation. * @param {(date|string)=} ngMin Sets the `min` validation error key to the Date / ISO datetime string * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute. * @param {(date|string)=} ngMax Sets the `max` validation error key to the Date / ISO datetime string * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute. * @param {string=} required Sets `required` validation error key if the value is not entered. * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of * `required` when you want to data-bind to the `required` attribute. * @param {string=} ngChange AngularJS expression to be executed when input changes due to user * interaction with the input element. * * @example
    Required! Not a valid date!
    value = {{example.value | date: "yyyy-MM-ddTHH:mm:ss"}}
    myForm.input.$valid = {{myForm.input.$valid}}
    myForm.input.$error = {{myForm.input.$error}}
    myForm.$valid = {{myForm.$valid}}
    myForm.$error.required = {{!!myForm.$error.required}}
    var value = element(by.binding('example.value | date: "yyyy-MM-ddTHH:mm:ss"')); var valid = element(by.binding('myForm.input.$valid')); // currently protractor/webdriver does not support // sending keys to all known HTML5 input controls // for various browsers (https://github.com/angular/protractor/issues/562). function setInput(val) { // set the value of the element and force validation. var scr = "var ipt = document.getElementById('exampleInput'); " + "ipt.value = '" + val + "';" + "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; browser.executeScript(scr); } it('should initialize to model', function() { expect(value.getText()).toContain('2010-12-28T14:57:00'); expect(valid.getText()).toContain('myForm.input.$valid = true'); }); it('should be invalid if empty', function() { setInput(''); expect(value.getText()).toEqual('value ='); expect(valid.getText()).toContain('myForm.input.$valid = false'); }); it('should be invalid if over max', function() { setInput('2015-01-01T23:59:00'); expect(value.getText()).toContain(''); expect(valid.getText()).toContain('myForm.input.$valid = false'); });
    */ 'datetime-local': createDateInputType('datetimelocal', DATETIMELOCAL_REGEXP, createDateParser(DATETIMELOCAL_REGEXP, ['yyyy', 'MM', 'dd', 'HH', 'mm', 'ss', 'sss']), 'yyyy-MM-ddTHH:mm:ss.sss'), /** * @ngdoc input * @name input[time] * * @description * Input with time validation and transformation. In browsers that do not yet support * the HTML5 time input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 * local time format (HH:mm:ss), for example: `14:57:00`. Model must be a Date object. This binding will always output a * Date object to the model of January 1, 1970, or local date `new Date(1970, 0, 1, HH, mm, ss)`. * * The model must always be a Date object, otherwise AngularJS will throw an error. * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. * * The timezone to be used to read/write the `Date` instance in the model can be defined using * {@link ng.directive:ngModelOptions#ngModelOptions-arguments ngModelOptions}. By default, * this is the timezone of the browser. * * The format of the displayed time can be adjusted with the * {@link ng.directive:ngModelOptions#ngModelOptions-arguments ngModelOptions} `timeSecondsFormat` * and `timeStripZeroSeconds`. * * @param {string} ngModel Assignable AngularJS expression to data-bind to. * @param {string=} name Property name of the form under which the control is published. * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. * This must be a valid ISO time format (HH:mm:ss). You can also use interpolation inside this * attribute (e.g. `min="{{minTime | date:'HH:mm:ss'}}"`). Note that `min` will also add * native HTML5 constraint validation. * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. * This must be a valid ISO time format (HH:mm:ss). You can also use interpolation inside this * attribute (e.g. `max="{{maxTime | date:'HH:mm:ss'}}"`). Note that `max` will also add * native HTML5 constraint validation. * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO time string the * `ngMin` expression evaluates to. Note that it does not set the `min` attribute. * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO time string the * `ngMax` expression evaluates to. Note that it does not set the `max` attribute. * @param {string=} required Sets `required` validation error key if the value is not entered. * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of * `required` when you want to data-bind to the `required` attribute. * @param {string=} ngChange AngularJS expression to be executed when input changes due to user * interaction with the input element. * * @example
    Required! Not a valid date!
    value = {{example.value | date: "HH:mm:ss"}}
    myForm.input.$valid = {{myForm.input.$valid}}
    myForm.input.$error = {{myForm.input.$error}}
    myForm.$valid = {{myForm.$valid}}
    myForm.$error.required = {{!!myForm.$error.required}}
    var value = element(by.binding('example.value | date: "HH:mm:ss"')); var valid = element(by.binding('myForm.input.$valid')); // currently protractor/webdriver does not support // sending keys to all known HTML5 input controls // for various browsers (https://github.com/angular/protractor/issues/562). function setInput(val) { // set the value of the element and force validation. var scr = "var ipt = document.getElementById('exampleInput'); " + "ipt.value = '" + val + "';" + "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; browser.executeScript(scr); } it('should initialize to model', function() { expect(value.getText()).toContain('14:57:00'); expect(valid.getText()).toContain('myForm.input.$valid = true'); }); it('should be invalid if empty', function() { setInput(''); expect(value.getText()).toEqual('value ='); expect(valid.getText()).toContain('myForm.input.$valid = false'); }); it('should be invalid if over max', function() { setInput('23:59:00'); expect(value.getText()).toContain(''); expect(valid.getText()).toContain('myForm.input.$valid = false'); });
    */ 'time': createDateInputType('time', TIME_REGEXP, createDateParser(TIME_REGEXP, ['HH', 'mm', 'ss', 'sss']), 'HH:mm:ss.sss'), /** * @ngdoc input * @name input[week] * * @description * Input with week-of-the-year validation and transformation to Date. In browsers that do not yet support * the HTML5 week input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 * week format (yyyy-W##), for example: `2013-W02`. * * The model must always be a Date object, otherwise AngularJS will throw an error. * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. * * The value of the resulting Date object will be set to Thursday at 00:00:00 of the requested week, * due to ISO-8601 week numbering standards. Information on ISO's system for numbering the weeks of the * year can be found at: https://en.wikipedia.org/wiki/ISO_8601#Week_dates * * The timezone to be used to read/write the `Date` instance in the model can be defined using * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. * * @param {string} ngModel Assignable AngularJS expression to data-bind to. * @param {string=} name Property name of the form under which the control is published. * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. * This must be a valid ISO week format (yyyy-W##). You can also use interpolation inside this * attribute (e.g. `min="{{minWeek | date:'yyyy-Www'}}"`). Note that `min` will also add * native HTML5 constraint validation. * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. * This must be a valid ISO week format (yyyy-W##). You can also use interpolation inside this * attribute (e.g. `max="{{maxWeek | date:'yyyy-Www'}}"`). Note that `max` will also add * native HTML5 constraint validation. * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO week string * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute. * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO week string * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute. * @param {string=} required Sets `required` validation error key if the value is not entered. * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of * `required` when you want to data-bind to the `required` attribute. * @param {string=} ngChange AngularJS expression to be executed when input changes due to user * interaction with the input element. * * @example
    Required! Not a valid date!
    value = {{example.value | date: "yyyy-Www"}}
    myForm.input.$valid = {{myForm.input.$valid}}
    myForm.input.$error = {{myForm.input.$error}}
    myForm.$valid = {{myForm.$valid}}
    myForm.$error.required = {{!!myForm.$error.required}}
    var value = element(by.binding('example.value | date: "yyyy-Www"')); var valid = element(by.binding('myForm.input.$valid')); // currently protractor/webdriver does not support // sending keys to all known HTML5 input controls // for various browsers (https://github.com/angular/protractor/issues/562). function setInput(val) { // set the value of the element and force validation. var scr = "var ipt = document.getElementById('exampleInput'); " + "ipt.value = '" + val + "';" + "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; browser.executeScript(scr); } it('should initialize to model', function() { expect(value.getText()).toContain('2013-W01'); expect(valid.getText()).toContain('myForm.input.$valid = true'); }); it('should be invalid if empty', function() { setInput(''); expect(value.getText()).toEqual('value ='); expect(valid.getText()).toContain('myForm.input.$valid = false'); }); it('should be invalid if over max', function() { setInput('2015-W01'); expect(value.getText()).toContain(''); expect(valid.getText()).toContain('myForm.input.$valid = false'); });
    */ 'week': createDateInputType('week', WEEK_REGEXP, weekParser, 'yyyy-Www'), /** * @ngdoc input * @name input[month] * * @description * Input with month validation and transformation. In browsers that do not yet support * the HTML5 month input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 * month format (yyyy-MM), for example: `2009-01`. * * The model must always be a Date object, otherwise AngularJS will throw an error. * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. * If the model is not set to the first of the month, the next view to model update will set it * to the first of the month. * * The timezone to be used to read/write the `Date` instance in the model can be defined using * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. * * @param {string} ngModel Assignable AngularJS expression to data-bind to. * @param {string=} name Property name of the form under which the control is published. * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. * This must be a valid ISO month format (yyyy-MM). You can also use interpolation inside this * attribute (e.g. `min="{{minMonth | date:'yyyy-MM'}}"`). Note that `min` will also add * native HTML5 constraint validation. * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. * This must be a valid ISO month format (yyyy-MM). You can also use interpolation inside this * attribute (e.g. `max="{{maxMonth | date:'yyyy-MM'}}"`). Note that `max` will also add * native HTML5 constraint validation. * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO week string * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute. * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO week string * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute. * @param {string=} required Sets `required` validation error key if the value is not entered. * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of * `required` when you want to data-bind to the `required` attribute. * @param {string=} ngChange AngularJS expression to be executed when input changes due to user * interaction with the input element. * * @example
    Required! Not a valid month!
    value = {{example.value | date: "yyyy-MM"}}
    myForm.input.$valid = {{myForm.input.$valid}}
    myForm.input.$error = {{myForm.input.$error}}
    myForm.$valid = {{myForm.$valid}}
    myForm.$error.required = {{!!myForm.$error.required}}
    var value = element(by.binding('example.value | date: "yyyy-MM"')); var valid = element(by.binding('myForm.input.$valid')); // currently protractor/webdriver does not support // sending keys to all known HTML5 input controls // for various browsers (https://github.com/angular/protractor/issues/562). function setInput(val) { // set the value of the element and force validation. var scr = "var ipt = document.getElementById('exampleInput'); " + "ipt.value = '" + val + "';" + "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; browser.executeScript(scr); } it('should initialize to model', function() { expect(value.getText()).toContain('2013-10'); expect(valid.getText()).toContain('myForm.input.$valid = true'); }); it('should be invalid if empty', function() { setInput(''); expect(value.getText()).toEqual('value ='); expect(valid.getText()).toContain('myForm.input.$valid = false'); }); it('should be invalid if over max', function() { setInput('2015-01'); expect(value.getText()).toContain(''); expect(valid.getText()).toContain('myForm.input.$valid = false'); });
    */ 'month': createDateInputType('month', MONTH_REGEXP, createDateParser(MONTH_REGEXP, ['yyyy', 'MM']), 'yyyy-MM'), /** * @ngdoc input * @name input[number] * * @description * Text input with number validation and transformation. Sets the `number` validation * error if not a valid number. * *
    * The model must always be of type `number` otherwise AngularJS will throw an error. * Be aware that a string containing a number is not enough. See the {@link ngModel:numfmt} * error docs for more information and an example of how to convert your model if necessary. *
    * * * * @knownIssue * * ### HTML5 constraint validation and `allowInvalid` * * In browsers that follow the * [HTML5 specification](https://html.spec.whatwg.org/multipage/forms.html#number-state-%28type=number%29), * `input[number]` does not work as expected with {@link ngModelOptions `ngModelOptions.allowInvalid`}. * If a non-number is entered in the input, the browser will report the value as an empty string, * which means the view / model values in `ngModel` and subsequently the scope value * will also be an empty string. * * @knownIssue * * ### Large numbers and `step` validation * * The `step` validation will not work correctly for very large numbers (e.g. 9999999999) due to * Javascript's arithmetic limitations. If you need to handle large numbers, purpose-built * libraries (e.g. https://github.com/MikeMcl/big.js/), can be included into AngularJS by * {@link guide/forms#modifying-built-in-validators overwriting the validators} * for `number` and / or `step`, or by {@link guide/forms#custom-validation applying custom validators} * to an `input[text]` element. The source for `input[number]` type can be used as a starting * point for both implementations. * * @param {string} ngModel Assignable AngularJS expression to data-bind to. * @param {string=} name Property name of the form under which the control is published. * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. * Can be interpolated. * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. * Can be interpolated. * @param {string=} ngMin Like `min`, sets the `min` validation error key if the value entered is less than `ngMin`, * but does not trigger HTML5 native validation. Takes an expression. * @param {string=} ngMax Like `max`, sets the `max` validation error key if the value entered is greater than `ngMax`, * but does not trigger HTML5 native validation. Takes an expression. * @param {string=} step Sets the `step` validation error key if the value entered does not fit the `step` constraint. * Can be interpolated. * @param {string=} ngStep Like `step`, sets the `step` validation error key if the value entered does not fit the `ngStep` constraint, * but does not trigger HTML5 native validation. Takes an expression. * @param {string=} required Sets `required` validation error key if the value is not entered. * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of * `required` when you want to data-bind to the `required` attribute. * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than * minlength. * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of * any length. * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string * that contains the regular expression body that will be converted to a regular expression * as in the ngPattern directive. * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue} * does not match a RegExp found by evaluating the AngularJS expression given in the attribute value. * If the expression evaluates to a RegExp object, then this is used directly. * If the expression evaluates to a string, then it will be converted to a RegExp * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to * `new RegExp('^abc$')`.
    * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to * start at the index of the last search's match, thus not taking the whole input value into * account. * @param {string=} ngChange AngularJS expression to be executed when input changes due to user * interaction with the input element. * * @example
    Required! Not valid number!
    value = {{example.value}}
    myForm.input.$valid = {{myForm.input.$valid}}
    myForm.input.$error = {{myForm.input.$error}}
    myForm.$valid = {{myForm.$valid}}
    myForm.$error.required = {{!!myForm.$error.required}}
    var value = element(by.binding('example.value')); var valid = element(by.binding('myForm.input.$valid')); var input = element(by.model('example.value')); it('should initialize to model', function() { expect(value.getText()).toContain('12'); expect(valid.getText()).toContain('true'); }); it('should be invalid if empty', function() { input.clear(); input.sendKeys(''); expect(value.getText()).toEqual('value ='); expect(valid.getText()).toContain('false'); }); it('should be invalid if over max', function() { input.clear(); input.sendKeys('123'); expect(value.getText()).toEqual('value ='); expect(valid.getText()).toContain('false'); });
    */ 'number': numberInputType, /** * @ngdoc input * @name input[url] * * @description * Text input with URL validation. Sets the `url` validation error key if the content is not a * valid URL. * *
    * **Note:** `input[url]` uses a regex to validate urls that is derived from the regex * used in Chromium. If you need stricter validation, you can use `ng-pattern` or modify * the built-in validators (see the {@link guide/forms Forms guide}) *
    * * @param {string} ngModel Assignable AngularJS expression to data-bind to. * @param {string=} name Property name of the form under which the control is published. * @param {string=} required Sets `required` validation error key if the value is not entered. * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of * `required` when you want to data-bind to the `required` attribute. * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than * minlength. * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of * any length. * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string * that contains the regular expression body that will be converted to a regular expression * as in the ngPattern directive. * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue} * does not match a RegExp found by evaluating the AngularJS expression given in the attribute value. * If the expression evaluates to a RegExp object, then this is used directly. * If the expression evaluates to a string, then it will be converted to a RegExp * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to * `new RegExp('^abc$')`.
    * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to * start at the index of the last search's match, thus not taking the whole input value into * account. * @param {string=} ngChange AngularJS expression to be executed when input changes due to user * interaction with the input element. * * @example
    var text = element(by.binding('url.text')); var valid = element(by.binding('myForm.input.$valid')); var input = element(by.model('url.text')); it('should initialize to model', function() { expect(text.getText()).toContain('http://google.com'); expect(valid.getText()).toContain('true'); }); it('should be invalid if empty', function() { input.clear(); input.sendKeys(''); expect(text.getText()).toEqual('text ='); expect(valid.getText()).toContain('false'); }); it('should be invalid if not url', function() { input.clear(); input.sendKeys('box'); expect(valid.getText()).toContain('false'); });
    */ 'url': urlInputType, /** * @ngdoc input * @name input[email] * * @description * Text input with email validation. Sets the `email` validation error key if not a valid email * address. * *
    * **Note:** `input[email]` uses a regex to validate email addresses that is derived from the regex * used in Chromium, which may not fulfill your app's requirements. * If you need stricter (e.g. requiring a top-level domain), or more relaxed validation * (e.g. allowing IPv6 address literals) you can use `ng-pattern` or * modify the built-in validators (see the {@link guide/forms Forms guide}). *
    * * @param {string} ngModel Assignable AngularJS expression to data-bind to. * @param {string=} name Property name of the form under which the control is published. * @param {string=} required Sets `required` validation error key if the value is not entered. * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of * `required` when you want to data-bind to the `required` attribute. * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than * minlength. * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of * any length. * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string * that contains the regular expression body that will be converted to a regular expression * as in the ngPattern directive. * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue} * does not match a RegExp found by evaluating the AngularJS expression given in the attribute value. * If the expression evaluates to a RegExp object, then this is used directly. * If the expression evaluates to a string, then it will be converted to a RegExp * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to * `new RegExp('^abc$')`.
    * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to * start at the index of the last search's match, thus not taking the whole input value into * account. * @param {string=} ngChange AngularJS expression to be executed when input changes due to user * interaction with the input element. * * @example
    Required! Not valid email!
    text = {{email.text}}
    myForm.input.$valid = {{myForm.input.$valid}}
    myForm.input.$error = {{myForm.input.$error}}
    myForm.$valid = {{myForm.$valid}}
    myForm.$error.required = {{!!myForm.$error.required}}
    myForm.$error.email = {{!!myForm.$error.email}}
    var text = element(by.binding('email.text')); var valid = element(by.binding('myForm.input.$valid')); var input = element(by.model('email.text')); it('should initialize to model', function() { expect(text.getText()).toContain('me@example.com'); expect(valid.getText()).toContain('true'); }); it('should be invalid if empty', function() { input.clear(); input.sendKeys(''); expect(text.getText()).toEqual('text ='); expect(valid.getText()).toContain('false'); }); it('should be invalid if not email', function() { input.clear(); input.sendKeys('xxx'); expect(valid.getText()).toContain('false'); });
    */ 'email': emailInputType, /** * @ngdoc input * @name input[radio] * * @description * HTML radio button. * * **Note:**
    * All inputs controlled by {@link ngModel ngModel} (including those of type `radio`) will use the * value of their `name` attribute to determine the property under which their * {@link ngModel.NgModelController NgModelController} will be published on the parent * {@link form.FormController FormController}. Thus, if you use the same `name` for multiple * inputs of a form (e.g. a group of radio inputs), only _one_ `NgModelController` will be * published on the parent `FormController` under that name. The rest of the controllers will * continue to work as expected, but you won't be able to access them as properties on the parent * `FormController`. * *
    *

    * In plain HTML forms, the `name` attribute is used to identify groups of radio inputs, so * that the browser can manage their state (checked/unchecked) based on the state of other * inputs in the same group. *

    *

    * In AngularJS forms, this is not necessary. The input's state will be updated based on the * value of the underlying model data. *

    *
    * *
    * If you omit the `name` attribute on a radio input, `ngModel` will automatically assign it a * unique name. *
    * * @param {string} ngModel Assignable AngularJS expression to data-bind to. * @param {string} value The value to which the `ngModel` expression should be set when selected. * Note that `value` only supports `string` values, i.e. the scope model needs to be a string, * too. Use `ngValue` if you need complex models (`number`, `object`, ...). * @param {string=} name Property name of the form under which the control is published. * @param {string=} ngChange AngularJS expression to be executed when input changes due to user * interaction with the input element. * @param {string} ngValue AngularJS expression to which `ngModel` will be be set when the radio * is selected. Should be used instead of the `value` attribute if you need * a non-string `ngModel` (`boolean`, `array`, ...). * * @example



    color = {{color.name | json}}
    Note that `ng-value="specialValue"` sets radio item's value to be the value of `$scope.specialValue`.
    it('should change state', function() { var inputs = element.all(by.model('color.name')); var color = element(by.binding('color.name')); expect(color.getText()).toContain('blue'); inputs.get(0).click(); expect(color.getText()).toContain('red'); inputs.get(1).click(); expect(color.getText()).toContain('green'); });
    */ 'radio': radioInputType, /** * @ngdoc input * @name input[range] * * @description * Native range input with validation and transformation. * * The model for the range input must always be a `Number`. * * IE9 and other browsers that do not support the `range` type fall back * to a text input without any default values for `min`, `max` and `step`. Model binding, * validation and number parsing are nevertheless supported. * * Browsers that support range (latest Chrome, Safari, Firefox, Edge) treat `input[range]` * in a way that never allows the input to hold an invalid value. That means: * - any non-numerical value is set to `(max + min) / 2`. * - any numerical value that is less than the current min val, or greater than the current max val * is set to the min / max val respectively. * - additionally, the current `step` is respected, so the nearest value that satisfies a step * is used. * * See the [HTML Spec on input[type=range]](https://www.w3.org/TR/html5/forms.html#range-state-(type=range)) * for more info. * * This has the following consequences for AngularJS: * * Since the element value should always reflect the current model value, a range input * will set the bound ngModel expression to the value that the browser has set for the * input element. For example, in the following input ``, * if the application sets `model.value = null`, the browser will set the input to `'50'`. * AngularJS will then set the model to `50`, to prevent input and model value being out of sync. * * That means the model for range will immediately be set to `50` after `ngModel` has been * initialized. It also means a range input can never have the required error. * * This does not only affect changes to the model value, but also to the values of the `min`, * `max`, and `step` attributes. When these change in a way that will cause the browser to modify * the input value, AngularJS will also update the model value. * * Automatic value adjustment also means that a range input element can never have the `required`, * `min`, or `max` errors. * * However, `step` is currently only fully implemented by Firefox. Other browsers have problems * when the step value changes dynamically - they do not adjust the element value correctly, but * instead may set the `stepMismatch` error. If that's the case, the AngularJS will set the `step` * error on the input, and set the model to `undefined`. * * Note that `input[range]` is not compatible with`ngMax`, `ngMin`, and `ngStep`, because they do * not set the `min` and `max` attributes, which means that the browser won't automatically adjust * the input value based on their values, and will always assume min = 0, max = 100, and step = 1. * * @param {string} ngModel Assignable AngularJS expression to data-bind to. * @param {string=} name Property name of the form under which the control is published. * @param {string=} min Sets the `min` validation to ensure that the value entered is greater * than `min`. Can be interpolated. * @param {string=} max Sets the `max` validation to ensure that the value entered is less than `max`. * Can be interpolated. * @param {string=} step Sets the `step` validation to ensure that the value entered matches the `step` * Can be interpolated. * @param {expression=} ngChange AngularJS expression to be executed when the ngModel value changes due * to user interaction with the input element. * @param {expression=} ngChecked If the expression is truthy, then the `checked` attribute will be set on the * element. **Note** : `ngChecked` should not be used alongside `ngModel`. * Checkout {@link ng.directive:ngChecked ngChecked} for usage. * * @example
    Model as range:
    Model as number:
    Min:
    Max:
    value = {{value}}
    myForm.range.$valid = {{myForm.range.$valid}}
    myForm.range.$error = {{myForm.range.$error}}
    * ## Range Input with ngMin & ngMax attributes * @example
    Model as range:
    Model as number:
    Min:
    Max:
    value = {{value}}
    myForm.range.$valid = {{myForm.range.$valid}}
    myForm.range.$error = {{myForm.range.$error}}
    */ 'range': rangeInputType, /** * @ngdoc input * @name input[checkbox] * * @description * HTML checkbox. * * @param {string} ngModel Assignable AngularJS expression to data-bind to. * @param {string=} name Property name of the form under which the control is published. * @param {expression=} ngTrueValue The value to which the expression should be set when selected. * @param {expression=} ngFalseValue The value to which the expression should be set when not selected. * @param {string=} ngChange AngularJS expression to be executed when input changes due to user * interaction with the input element. * * @example


    value1 = {{checkboxModel.value1}}
    value2 = {{checkboxModel.value2}}
    it('should change state', function() { var value1 = element(by.binding('checkboxModel.value1')); var value2 = element(by.binding('checkboxModel.value2')); expect(value1.getText()).toContain('true'); expect(value2.getText()).toContain('YES'); element(by.model('checkboxModel.value1')).click(); element(by.model('checkboxModel.value2')).click(); expect(value1.getText()).toContain('false'); expect(value2.getText()).toContain('NO'); });
    */ 'checkbox': checkboxInputType, 'hidden': noop, 'button': noop, 'submit': noop, 'reset': noop, 'file': noop }; function stringBasedInputType(ctrl) { ctrl.$formatters.push(function(value) { return ctrl.$isEmpty(value) ? value : value.toString(); }); } function textInputType(scope, element, attr, ctrl, $sniffer, $browser) { baseInputType(scope, element, attr, ctrl, $sniffer, $browser); stringBasedInputType(ctrl); } function baseInputType(scope, element, attr, ctrl, $sniffer, $browser) { var type = lowercase(element[0].type); // In composition mode, users are still inputting intermediate text buffer, // hold the listener until composition is done. // More about composition events: https://developer.mozilla.org/en-US/docs/Web/API/CompositionEvent if (!$sniffer.android) { var composing = false; element.on('compositionstart', function() { composing = true; }); // Support: IE9+ element.on('compositionupdate', function(ev) { // End composition when ev.data is empty string on 'compositionupdate' event. // When the input de-focusses (e.g. by clicking away), IE triggers 'compositionupdate' // instead of 'compositionend'. if (isUndefined(ev.data) || ev.data === '') { composing = false; } }); element.on('compositionend', function() { composing = false; listener(); }); } var timeout; var listener = function(ev) { if (timeout) { $browser.defer.cancel(timeout); timeout = null; } if (composing) return; var value = element.val(), event = ev && ev.type; // By default we will trim the value // If the attribute ng-trim exists we will avoid trimming // If input type is 'password', the value is never trimmed if (type !== 'password' && (!attr.ngTrim || attr.ngTrim !== 'false')) { value = trim(value); } // If a control is suffering from bad input (due to native validators), browsers discard its // value, so it may be necessary to revalidate (by calling $setViewValue again) even if the // control's value is the same empty value twice in a row. if (ctrl.$viewValue !== value || (value === '' && ctrl.$$hasNativeValidators)) { ctrl.$setViewValue(value, event); } }; // if the browser does support "input" event, we are fine - except on IE9 which doesn't fire the // input event on backspace, delete or cut if ($sniffer.hasEvent('input')) { element.on('input', listener); } else { var deferListener = function(ev, input, origValue) { if (!timeout) { timeout = $browser.defer(function() { timeout = null; if (!input || input.value !== origValue) { listener(ev); } }); } }; element.on('keydown', /** @this */ function(event) { var key = event.keyCode; // ignore // command modifiers arrows if (key === 91 || (15 < key && key < 19) || (37 <= key && key <= 40)) return; deferListener(event, this, this.value); }); // if user modifies input value using context menu in IE, we need "paste", "cut" and "drop" events to catch it if ($sniffer.hasEvent('paste')) { element.on('paste cut drop', deferListener); } } // if user paste into input using mouse on older browser // or form autocomplete on newer browser, we need "change" event to catch it element.on('change', listener); // Some native input types (date-family) have the ability to change validity without // firing any input/change events. // For these event types, when native validators are present and the browser supports the type, // check for validity changes on various DOM events. if (PARTIAL_VALIDATION_TYPES[type] && ctrl.$$hasNativeValidators && type === attr.type) { element.on(PARTIAL_VALIDATION_EVENTS, /** @this */ function(ev) { if (!timeout) { var validity = this[VALIDITY_STATE_PROPERTY]; var origBadInput = validity.badInput; var origTypeMismatch = validity.typeMismatch; timeout = $browser.defer(function() { timeout = null; if (validity.badInput !== origBadInput || validity.typeMismatch !== origTypeMismatch) { listener(ev); } }); } }); } ctrl.$render = function() { // Workaround for Firefox validation #12102. var value = ctrl.$isEmpty(ctrl.$viewValue) ? '' : ctrl.$viewValue; if (element.val() !== value) { element.val(value); } }; } function weekParser(isoWeek, existingDate) { if (isDate(isoWeek)) { return isoWeek; } if (isString(isoWeek)) { WEEK_REGEXP.lastIndex = 0; var parts = WEEK_REGEXP.exec(isoWeek); if (parts) { var year = +parts[1], week = +parts[2], hours = 0, minutes = 0, seconds = 0, milliseconds = 0, firstThurs = getFirstThursdayOfYear(year), addDays = (week - 1) * 7; if (existingDate) { hours = existingDate.getHours(); minutes = existingDate.getMinutes(); seconds = existingDate.getSeconds(); milliseconds = existingDate.getMilliseconds(); } return new Date(year, 0, firstThurs.getDate() + addDays, hours, minutes, seconds, milliseconds); } } return NaN; } function createDateParser(regexp, mapping) { return function(iso, previousDate) { var parts, map; if (isDate(iso)) { return iso; } if (isString(iso)) { // When a date is JSON'ified to wraps itself inside of an extra // set of double quotes. This makes the date parsing code unable // to match the date string and parse it as a date. if (iso.charAt(0) === '"' && iso.charAt(iso.length - 1) === '"') { iso = iso.substring(1, iso.length - 1); } if (ISO_DATE_REGEXP.test(iso)) { return new Date(iso); } regexp.lastIndex = 0; parts = regexp.exec(iso); if (parts) { parts.shift(); if (previousDate) { map = { yyyy: previousDate.getFullYear(), MM: previousDate.getMonth() + 1, dd: previousDate.getDate(), HH: previousDate.getHours(), mm: previousDate.getMinutes(), ss: previousDate.getSeconds(), sss: previousDate.getMilliseconds() / 1000 }; } else { map = { yyyy: 1970, MM: 1, dd: 1, HH: 0, mm: 0, ss: 0, sss: 0 }; } forEach(parts, function(part, index) { if (index < mapping.length) { map[mapping[index]] = +part; } }); var date = new Date(map.yyyy, map.MM - 1, map.dd, map.HH, map.mm, map.ss || 0, map.sss * 1000 || 0); if (map.yyyy < 100) { // In the constructor, 2-digit years map to 1900-1999. // Use `setFullYear()` to set the correct year. date.setFullYear(map.yyyy); } return date; } } return NaN; }; } function createDateInputType(type, regexp, parseDate, format) { return function dynamicDateInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter, $parse) { badInputChecker(scope, element, attr, ctrl, type); baseInputType(scope, element, attr, ctrl, $sniffer, $browser); var isTimeType = type === 'time' || type === 'datetimelocal'; var previousDate; var previousTimezone; ctrl.$parsers.push(function(value) { if (ctrl.$isEmpty(value)) return null; if (regexp.test(value)) { // Note: We cannot read ctrl.$modelValue, as there might be a different // parser/formatter in the processing chain so that the model // contains some different data format! return parseDateAndConvertTimeZoneToLocal(value, previousDate); } ctrl.$$parserName = type; return undefined; }); ctrl.$formatters.push(function(value) { if (value && !isDate(value)) { throw ngModelMinErr('datefmt', 'Expected `{0}` to be a date', value); } if (isValidDate(value)) { previousDate = value; var timezone = ctrl.$options.getOption('timezone'); if (timezone) { previousTimezone = timezone; previousDate = convertTimezoneToLocal(previousDate, timezone, true); } return formatter(value, timezone); } else { previousDate = null; previousTimezone = null; return ''; } }); if (isDefined(attr.min) || attr.ngMin) { var minVal = attr.min || $parse(attr.ngMin)(scope); var parsedMinVal = parseObservedDateValue(minVal); ctrl.$validators.min = function(value) { return !isValidDate(value) || isUndefined(parsedMinVal) || parseDate(value) >= parsedMinVal; }; attr.$observe('min', function(val) { if (val !== minVal) { parsedMinVal = parseObservedDateValue(val); minVal = val; ctrl.$validate(); } }); } if (isDefined(attr.max) || attr.ngMax) { var maxVal = attr.max || $parse(attr.ngMax)(scope); var parsedMaxVal = parseObservedDateValue(maxVal); ctrl.$validators.max = function(value) { return !isValidDate(value) || isUndefined(parsedMaxVal) || parseDate(value) <= parsedMaxVal; }; attr.$observe('max', function(val) { if (val !== maxVal) { parsedMaxVal = parseObservedDateValue(val); maxVal = val; ctrl.$validate(); } }); } function isValidDate(value) { // Invalid Date: getTime() returns NaN return value && !(value.getTime && value.getTime() !== value.getTime()); } function parseObservedDateValue(val) { return isDefined(val) && !isDate(val) ? parseDateAndConvertTimeZoneToLocal(val) || undefined : val; } function parseDateAndConvertTimeZoneToLocal(value, previousDate) { var timezone = ctrl.$options.getOption('timezone'); if (previousTimezone && previousTimezone !== timezone) { // If the timezone has changed, adjust the previousDate to the default timezone // so that the new date is converted with the correct timezone offset previousDate = addDateMinutes(previousDate, timezoneToOffset(previousTimezone)); } var parsedDate = parseDate(value, previousDate); if (!isNaN(parsedDate) && timezone) { parsedDate = convertTimezoneToLocal(parsedDate, timezone); } return parsedDate; } function formatter(value, timezone) { var targetFormat = format; if (isTimeType && isString(ctrl.$options.getOption('timeSecondsFormat'))) { targetFormat = format .replace('ss.sss', ctrl.$options.getOption('timeSecondsFormat')) .replace(/:$/, ''); } var formatted = $filter('date')(value, targetFormat, timezone); if (isTimeType && ctrl.$options.getOption('timeStripZeroSeconds')) { formatted = formatted.replace(/(?::00)?(?:\.000)?$/, ''); } return formatted; } }; } function badInputChecker(scope, element, attr, ctrl, parserName) { var node = element[0]; var nativeValidation = ctrl.$$hasNativeValidators = isObject(node.validity); if (nativeValidation) { ctrl.$parsers.push(function(value) { var validity = element.prop(VALIDITY_STATE_PROPERTY) || {}; if (validity.badInput || validity.typeMismatch) { ctrl.$$parserName = parserName; return undefined; } return value; }); } } function numberFormatterParser(ctrl) { ctrl.$parsers.push(function(value) { if (ctrl.$isEmpty(value)) return null; if (NUMBER_REGEXP.test(value)) return parseFloat(value); ctrl.$$parserName = 'number'; return undefined; }); ctrl.$formatters.push(function(value) { if (!ctrl.$isEmpty(value)) { if (!isNumber(value)) { throw ngModelMinErr('numfmt', 'Expected `{0}` to be a number', value); } value = value.toString(); } return value; }); } function parseNumberAttrVal(val) { if (isDefined(val) && !isNumber(val)) { val = parseFloat(val); } return !isNumberNaN(val) ? val : undefined; } function isNumberInteger(num) { // See http://stackoverflow.com/questions/14636536/how-to-check-if-a-variable-is-an-integer-in-javascript#14794066 // (minus the assumption that `num` is a number) // eslint-disable-next-line no-bitwise return (num | 0) === num; } function countDecimals(num) { var numString = num.toString(); var decimalSymbolIndex = numString.indexOf('.'); if (decimalSymbolIndex === -1) { if (-1 < num && num < 1) { // It may be in the exponential notation format (`1e-X`) var match = /e-(\d+)$/.exec(numString); if (match) { return Number(match[1]); } } return 0; } return numString.length - decimalSymbolIndex - 1; } function isValidForStep(viewValue, stepBase, step) { // At this point `stepBase` and `step` are expected to be non-NaN values // and `viewValue` is expected to be a valid stringified number. var value = Number(viewValue); var isNonIntegerValue = !isNumberInteger(value); var isNonIntegerStepBase = !isNumberInteger(stepBase); var isNonIntegerStep = !isNumberInteger(step); // Due to limitations in Floating Point Arithmetic (e.g. `0.3 - 0.2 !== 0.1` or // `0.5 % 0.1 !== 0`), we need to convert all numbers to integers. if (isNonIntegerValue || isNonIntegerStepBase || isNonIntegerStep) { var valueDecimals = isNonIntegerValue ? countDecimals(value) : 0; var stepBaseDecimals = isNonIntegerStepBase ? countDecimals(stepBase) : 0; var stepDecimals = isNonIntegerStep ? countDecimals(step) : 0; var decimalCount = Math.max(valueDecimals, stepBaseDecimals, stepDecimals); var multiplier = Math.pow(10, decimalCount); value = value * multiplier; stepBase = stepBase * multiplier; step = step * multiplier; if (isNonIntegerValue) value = Math.round(value); if (isNonIntegerStepBase) stepBase = Math.round(stepBase); if (isNonIntegerStep) step = Math.round(step); } return (value - stepBase) % step === 0; } function numberInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter, $parse) { badInputChecker(scope, element, attr, ctrl, 'number'); numberFormatterParser(ctrl); baseInputType(scope, element, attr, ctrl, $sniffer, $browser); var parsedMinVal; if (isDefined(attr.min) || attr.ngMin) { var minVal = attr.min || $parse(attr.ngMin)(scope); parsedMinVal = parseNumberAttrVal(minVal); ctrl.$validators.min = function(modelValue, viewValue) { return ctrl.$isEmpty(viewValue) || isUndefined(parsedMinVal) || viewValue >= parsedMinVal; }; attr.$observe('min', function(val) { if (val !== minVal) { parsedMinVal = parseNumberAttrVal(val); minVal = val; // TODO(matsko): implement validateLater to reduce number of validations ctrl.$validate(); } }); } if (isDefined(attr.max) || attr.ngMax) { var maxVal = attr.max || $parse(attr.ngMax)(scope); var parsedMaxVal = parseNumberAttrVal(maxVal); ctrl.$validators.max = function(modelValue, viewValue) { return ctrl.$isEmpty(viewValue) || isUndefined(parsedMaxVal) || viewValue <= parsedMaxVal; }; attr.$observe('max', function(val) { if (val !== maxVal) { parsedMaxVal = parseNumberAttrVal(val); maxVal = val; // TODO(matsko): implement validateLater to reduce number of validations ctrl.$validate(); } }); } if (isDefined(attr.step) || attr.ngStep) { var stepVal = attr.step || $parse(attr.ngStep)(scope); var parsedStepVal = parseNumberAttrVal(stepVal); ctrl.$validators.step = function(modelValue, viewValue) { return ctrl.$isEmpty(viewValue) || isUndefined(parsedStepVal) || isValidForStep(viewValue, parsedMinVal || 0, parsedStepVal); }; attr.$observe('step', function(val) { // TODO(matsko): implement validateLater to reduce number of validations if (val !== stepVal) { parsedStepVal = parseNumberAttrVal(val); stepVal = val; ctrl.$validate(); } }); } } function rangeInputType(scope, element, attr, ctrl, $sniffer, $browser) { badInputChecker(scope, element, attr, ctrl, 'range'); numberFormatterParser(ctrl); baseInputType(scope, element, attr, ctrl, $sniffer, $browser); var supportsRange = ctrl.$$hasNativeValidators && element[0].type === 'range', minVal = supportsRange ? 0 : undefined, maxVal = supportsRange ? 100 : undefined, stepVal = supportsRange ? 1 : undefined, validity = element[0].validity, hasMinAttr = isDefined(attr.min), hasMaxAttr = isDefined(attr.max), hasStepAttr = isDefined(attr.step); var originalRender = ctrl.$render; ctrl.$render = supportsRange && isDefined(validity.rangeUnderflow) && isDefined(validity.rangeOverflow) ? //Browsers that implement range will set these values automatically, but reading the adjusted values after //$render would cause the min / max validators to be applied with the wrong value function rangeRender() { originalRender(); ctrl.$setViewValue(element.val()); } : originalRender; if (hasMinAttr) { minVal = parseNumberAttrVal(attr.min); ctrl.$validators.min = supportsRange ? // Since all browsers set the input to a valid value, we don't need to check validity function noopMinValidator() { return true; } : // non-support browsers validate the min val function minValidator(modelValue, viewValue) { return ctrl.$isEmpty(viewValue) || isUndefined(minVal) || viewValue >= minVal; }; setInitialValueAndObserver('min', minChange); } if (hasMaxAttr) { maxVal = parseNumberAttrVal(attr.max); ctrl.$validators.max = supportsRange ? // Since all browsers set the input to a valid value, we don't need to check validity function noopMaxValidator() { return true; } : // non-support browsers validate the max val function maxValidator(modelValue, viewValue) { return ctrl.$isEmpty(viewValue) || isUndefined(maxVal) || viewValue <= maxVal; }; setInitialValueAndObserver('max', maxChange); } if (hasStepAttr) { stepVal = parseNumberAttrVal(attr.step); ctrl.$validators.step = supportsRange ? function nativeStepValidator() { // Currently, only FF implements the spec on step change correctly (i.e. adjusting the // input element value to a valid value). It's possible that other browsers set the stepMismatch // validity error instead, so we can at least report an error in that case. return !validity.stepMismatch; } : // ngStep doesn't set the setp attr, so the browser doesn't adjust the input value as setting step would function stepValidator(modelValue, viewValue) { return ctrl.$isEmpty(viewValue) || isUndefined(stepVal) || isValidForStep(viewValue, minVal || 0, stepVal); }; setInitialValueAndObserver('step', stepChange); } function setInitialValueAndObserver(htmlAttrName, changeFn) { // interpolated attributes set the attribute value only after a digest, but we need the // attribute value when the input is first rendered, so that the browser can adjust the // input value based on the min/max value element.attr(htmlAttrName, attr[htmlAttrName]); var oldVal = attr[htmlAttrName]; attr.$observe(htmlAttrName, function wrappedObserver(val) { if (val !== oldVal) { oldVal = val; changeFn(val); } }); } function minChange(val) { minVal = parseNumberAttrVal(val); // ignore changes before model is initialized if (isNumberNaN(ctrl.$modelValue)) { return; } if (supportsRange) { var elVal = element.val(); // IE11 doesn't set the el val correctly if the minVal is greater than the element value if (minVal > elVal) { elVal = minVal; element.val(elVal); } ctrl.$setViewValue(elVal); } else { // TODO(matsko): implement validateLater to reduce number of validations ctrl.$validate(); } } function maxChange(val) { maxVal = parseNumberAttrVal(val); // ignore changes before model is initialized if (isNumberNaN(ctrl.$modelValue)) { return; } if (supportsRange) { var elVal = element.val(); // IE11 doesn't set the el val correctly if the maxVal is less than the element value if (maxVal < elVal) { element.val(maxVal); // IE11 and Chrome don't set the value to the minVal when max < min elVal = maxVal < minVal ? minVal : maxVal; } ctrl.$setViewValue(elVal); } else { // TODO(matsko): implement validateLater to reduce number of validations ctrl.$validate(); } } function stepChange(val) { stepVal = parseNumberAttrVal(val); // ignore changes before model is initialized if (isNumberNaN(ctrl.$modelValue)) { return; } // Some browsers don't adjust the input value correctly, but set the stepMismatch error if (!supportsRange) { // TODO(matsko): implement validateLater to reduce number of validations ctrl.$validate(); } else if (ctrl.$viewValue !== element.val()) { ctrl.$setViewValue(element.val()); } } } function urlInputType(scope, element, attr, ctrl, $sniffer, $browser) { // Note: no badInputChecker here by purpose as `url` is only a validation // in browsers, i.e. we can always read out input.value even if it is not valid! baseInputType(scope, element, attr, ctrl, $sniffer, $browser); stringBasedInputType(ctrl); ctrl.$validators.url = function(modelValue, viewValue) { var value = modelValue || viewValue; return ctrl.$isEmpty(value) || URL_REGEXP.test(value); }; } function emailInputType(scope, element, attr, ctrl, $sniffer, $browser) { // Note: no badInputChecker here by purpose as `url` is only a validation // in browsers, i.e. we can always read out input.value even if it is not valid! baseInputType(scope, element, attr, ctrl, $sniffer, $browser); stringBasedInputType(ctrl); ctrl.$validators.email = function(modelValue, viewValue) { var value = modelValue || viewValue; return ctrl.$isEmpty(value) || EMAIL_REGEXP.test(value); }; } function radioInputType(scope, element, attr, ctrl) { var doTrim = !attr.ngTrim || trim(attr.ngTrim) !== 'false'; // make the name unique, if not defined if (isUndefined(attr.name)) { element.attr('name', nextUid()); } var listener = function(ev) { var value; if (element[0].checked) { value = attr.value; if (doTrim) { value = trim(value); } ctrl.$setViewValue(value, ev && ev.type); } }; element.on('change', listener); ctrl.$render = function() { var value = attr.value; if (doTrim) { value = trim(value); } element[0].checked = (value === ctrl.$viewValue); }; attr.$observe('value', ctrl.$render); } function parseConstantExpr($parse, context, name, expression, fallback) { var parseFn; if (isDefined(expression)) { parseFn = $parse(expression); if (!parseFn.constant) { throw ngModelMinErr('constexpr', 'Expected constant expression for `{0}`, but saw ' + '`{1}`.', name, expression); } return parseFn(context); } return fallback; } function checkboxInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter, $parse) { var trueValue = parseConstantExpr($parse, scope, 'ngTrueValue', attr.ngTrueValue, true); var falseValue = parseConstantExpr($parse, scope, 'ngFalseValue', attr.ngFalseValue, false); var listener = function(ev) { ctrl.$setViewValue(element[0].checked, ev && ev.type); }; element.on('change', listener); ctrl.$render = function() { element[0].checked = ctrl.$viewValue; }; // Override the standard `$isEmpty` because the $viewValue of an empty checkbox is always set to `false` // This is because of the parser below, which compares the `$modelValue` with `trueValue` to convert // it to a boolean. ctrl.$isEmpty = function(value) { return value === false; }; ctrl.$formatters.push(function(value) { return equals(value, trueValue); }); ctrl.$parsers.push(function(value) { return value ? trueValue : falseValue; }); } /** * @ngdoc directive * @name textarea * @restrict E * * @description * HTML textarea element control with AngularJS data-binding. The data-binding and validation * properties of this element are exactly the same as those of the * {@link ng.directive:input input element}. * * @param {string} ngModel Assignable AngularJS expression to data-bind to. * @param {string=} name Property name of the form under which the control is published. * @param {string=} required Sets `required` validation error key if the value is not entered. * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of * `required` when you want to data-bind to the `required` attribute. * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than * minlength. * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of any * length. * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue} * does not match a RegExp found by evaluating the AngularJS expression given in the attribute value. * If the expression evaluates to a RegExp object, then this is used directly. * If the expression evaluates to a string, then it will be converted to a RegExp * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to * `new RegExp('^abc$')`.
    * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to * start at the index of the last search's match, thus not taking the whole input value into * account. * @param {string=} ngChange AngularJS expression to be executed when input changes due to user * interaction with the input element. * @param {boolean=} [ngTrim=true] If set to false AngularJS will not automatically trim the input. * * @knownIssue * * When specifying the `placeholder` attribute of ` *
    {{ list | json }}
    * * * it("should split the text by newlines", function() { * var listInput = element(by.model('list')); * var output = element(by.binding('list | json')); * listInput.sendKeys('abc\ndef\nghi'); * expect(output.getText()).toContain('[\n "abc",\n "def",\n "ghi"\n]'); * }); * * * */ var ngListDirective = function() { return { restrict: 'A', priority: 100, require: 'ngModel', link: function(scope, element, attr, ctrl) { var ngList = attr.ngList || ', '; var trimValues = attr.ngTrim !== 'false'; var separator = trimValues ? trim(ngList) : ngList; var parse = function(viewValue) { // If the viewValue is invalid (say required but empty) it will be `undefined` if (isUndefined(viewValue)) return; var list = []; if (viewValue) { forEach(viewValue.split(separator), function(value) { if (value) list.push(trimValues ? trim(value) : value); }); } return list; }; ctrl.$parsers.push(parse); ctrl.$formatters.push(function(value) { if (isArray(value)) { return value.join(ngList); } return undefined; }); // Override the standard $isEmpty because an empty array means the input is empty. ctrl.$isEmpty = function(value) { return !value || !value.length; }; } }; }; angular.js-1.7.9/src/ng/directive/ngModel.js000066400000000000000000001515501356472325200207100ustar00rootroot00000000000000'use strict'; /* global VALID_CLASS: true, INVALID_CLASS: true, PRISTINE_CLASS: true, DIRTY_CLASS: true, UNTOUCHED_CLASS: true, TOUCHED_CLASS: true, PENDING_CLASS: true, addSetValidityMethod: true, setupValidity: true, defaultModelOptions: false */ var VALID_CLASS = 'ng-valid', INVALID_CLASS = 'ng-invalid', PRISTINE_CLASS = 'ng-pristine', DIRTY_CLASS = 'ng-dirty', UNTOUCHED_CLASS = 'ng-untouched', TOUCHED_CLASS = 'ng-touched', EMPTY_CLASS = 'ng-empty', NOT_EMPTY_CLASS = 'ng-not-empty'; var ngModelMinErr = minErr('ngModel'); /** * @ngdoc type * @name ngModel.NgModelController * @property {*} $viewValue The actual value from the control's view. For `input` elements, this is a * String. See {@link ngModel.NgModelController#$setViewValue} for information about when the $viewValue * is set. * * @property {*} $modelValue The value in the model that the control is bound to. * * @property {Array.} $parsers Array of functions to execute, as a pipeline, whenever * the control updates the ngModelController with a new {@link ngModel.NgModelController#$viewValue `$viewValue`} from the DOM, usually via user input. See {@link ngModel.NgModelController#$setViewValue `$setViewValue()`} for a detailed lifecycle explanation. Note that the `$parsers` are not called when the bound ngModel expression changes programmatically. The functions are called in array order, each passing its return value through to the next. The last return value is forwarded to the {@link ngModel.NgModelController#$validators `$validators`} collection. Parsers are used to sanitize / convert the {@link ngModel.NgModelController#$viewValue `$viewValue`}. Returning `undefined` from a parser means a parse error occurred. In that case, no {@link ngModel.NgModelController#$validators `$validators`} will run and the `ngModel` will be set to `undefined` unless {@link ngModelOptions `ngModelOptions.allowInvalid`} is set to `true`. The parse error is stored in `ngModel.$error.parse`. This simple example shows a parser that would convert text input value to lowercase: * ```js * function parse(value) { * if (value) { * return value.toLowerCase(); * } * } * ngModelController.$parsers.push(parse); * ``` * * @property {Array.} $formatters Array of functions to execute, as a pipeline, whenever the bound ngModel expression changes programmatically. The `$formatters` are not called when the value of the control is changed by user interaction. Formatters are used to format / convert the {@link ngModel.NgModelController#$modelValue `$modelValue`} for display in the control. The functions are called in reverse array order, each passing the value through to the next. The last return value is used as the actual DOM value. This simple example shows a formatter that would convert the model value to uppercase: * ```js * function format(value) { * if (value) { * return value.toUpperCase(); * } * } * ngModel.$formatters.push(format); * ``` * * @property {Object.} $validators A collection of validators that are applied * whenever the model value changes. The key value within the object refers to the name of the * validator while the function refers to the validation operation. The validation operation is * provided with the model value as an argument and must return a true or false value depending * on the response of that validation. * * ```js * ngModel.$validators.validCharacters = function(modelValue, viewValue) { * var value = modelValue || viewValue; * return /[0-9]+/.test(value) && * /[a-z]+/.test(value) && * /[A-Z]+/.test(value) && * /\W+/.test(value); * }; * ``` * * @property {Object.} $asyncValidators A collection of validations that are expected to * perform an asynchronous validation (e.g. a HTTP request). The validation function that is provided * is expected to return a promise when it is run during the model validation process. Once the promise * is delivered then the validation status will be set to true when fulfilled and false when rejected. * When the asynchronous validators are triggered, each of the validators will run in parallel and the model * value will only be updated once all validators have been fulfilled. As long as an asynchronous validator * is unfulfilled, its key will be added to the controllers `$pending` property. Also, all asynchronous validators * will only run once all synchronous validators have passed. * * Please note that if $http is used then it is important that the server returns a success HTTP response code * in order to fulfill the validation and a status level of `4xx` in order to reject the validation. * * ```js * ngModel.$asyncValidators.uniqueUsername = function(modelValue, viewValue) { * var value = modelValue || viewValue; * * // Lookup user by username * return $http.get('/api/users/' + value). * then(function resolved() { * //username exists, this means validation fails * return $q.reject('exists'); * }, function rejected() { * //username does not exist, therefore this validation passes * return true; * }); * }; * ``` * * @property {Array.} $viewChangeListeners Array of functions to execute whenever * a change to {@link ngModel.NgModelController#$viewValue `$viewValue`} has caused a change * to {@link ngModel.NgModelController#$modelValue `$modelValue`}. * It is called with no arguments, and its return value is ignored. * This can be used in place of additional $watches against the model value. * * @property {Object} $error An object hash with all failing validator ids as keys. * @property {Object} $pending An object hash with all pending validator ids as keys. * * @property {boolean} $untouched True if control has not lost focus yet. * @property {boolean} $touched True if control has lost focus. * @property {boolean} $pristine True if user has not interacted with the control yet. * @property {boolean} $dirty True if user has already interacted with the control. * @property {boolean} $valid True if there is no error. * @property {boolean} $invalid True if at least one error on the control. * @property {string} $name The name attribute of the control. * * @description * * `NgModelController` provides API for the {@link ngModel `ngModel`} directive. * The controller contains services for data-binding, validation, CSS updates, and value formatting * and parsing. It purposefully does not contain any logic which deals with DOM rendering or * listening to DOM events. * Such DOM related logic should be provided by other directives which make use of * `NgModelController` for data-binding to control elements. * AngularJS provides this DOM logic for most {@link input `input`} elements. * At the end of this page you can find a {@link ngModel.NgModelController#custom-control-example * custom control example} that uses `ngModelController` to bind to `contenteditable` elements. * * @example * ### Custom Control Example * This example shows how to use `NgModelController` with a custom control to achieve * data-binding. Notice how different directives (`contenteditable`, `ng-model`, and `required`) * collaborate together to achieve the desired result. * * `contenteditable` is an HTML5 attribute, which tells the browser to let the element * contents be edited in place by the user. * * We are using the {@link ng.service:$sce $sce} service here and include the {@link ngSanitize $sanitize} * module to automatically remove "bad" content like inline event listener (e.g. ``). * However, as we are using `$sce` the model can still decide to provide unsafe content if it marks * that content using the `$sce` service. * * [contenteditable] { border: 1px solid black; background-color: white; min-height: 20px; } .ng-invalid { border: 1px solid red; } angular.module('customControl', ['ngSanitize']). directive('contenteditable', ['$sce', function($sce) { return { restrict: 'A', // only activate on element attribute require: '?ngModel', // get a hold of NgModelController link: function(scope, element, attrs, ngModel) { if (!ngModel) return; // do nothing if no ng-model // Specify how UI should be updated ngModel.$render = function() { element.html($sce.getTrustedHtml(ngModel.$viewValue || '')); }; // Listen for change events to enable binding element.on('blur keyup change', function() { scope.$evalAsync(read); }); read(); // initialize // Write data to the model function read() { var html = element.html(); // When we clear the content editable the browser leaves a
    behind // If strip-br attribute is provided then we strip this out if (attrs.stripBr && html === '
    ') { html = ''; } ngModel.$setViewValue(html); } } }; }]);
    Change me!
    Required!
    it('should data-bind and become invalid', function() { if (browser.params.browser === 'safari' || browser.params.browser === 'firefox') { // SafariDriver can't handle contenteditable // and Firefox driver can't clear contenteditables very well return; } var contentEditable = element(by.css('[contenteditable]')); var content = 'Change me!'; expect(contentEditable.getText()).toEqual(content); contentEditable.clear(); contentEditable.sendKeys(protractor.Key.BACK_SPACE); expect(contentEditable.getText()).toEqual(''); expect(contentEditable.getAttribute('class')).toMatch(/ng-invalid-required/); }); *
    * * */ NgModelController.$inject = ['$scope', '$exceptionHandler', '$attrs', '$element', '$parse', '$animate', '$timeout', '$q', '$interpolate']; function NgModelController($scope, $exceptionHandler, $attr, $element, $parse, $animate, $timeout, $q, $interpolate) { this.$viewValue = Number.NaN; this.$modelValue = Number.NaN; this.$$rawModelValue = undefined; // stores the parsed modelValue / model set from scope regardless of validity. this.$validators = {}; this.$asyncValidators = {}; this.$parsers = []; this.$formatters = []; this.$viewChangeListeners = []; this.$untouched = true; this.$touched = false; this.$pristine = true; this.$dirty = false; this.$valid = true; this.$invalid = false; this.$error = {}; // keep invalid keys here this.$$success = {}; // keep valid keys here this.$pending = undefined; // keep pending keys here this.$name = $interpolate($attr.name || '', false)($scope); this.$$parentForm = nullFormCtrl; this.$options = defaultModelOptions; this.$$updateEvents = ''; // Attach the correct context to the event handler function for updateOn this.$$updateEventHandler = this.$$updateEventHandler.bind(this); this.$$parsedNgModel = $parse($attr.ngModel); this.$$parsedNgModelAssign = this.$$parsedNgModel.assign; this.$$ngModelGet = this.$$parsedNgModel; this.$$ngModelSet = this.$$parsedNgModelAssign; this.$$pendingDebounce = null; this.$$parserValid = undefined; this.$$parserName = 'parse'; this.$$currentValidationRunId = 0; this.$$scope = $scope; this.$$rootScope = $scope.$root; this.$$attr = $attr; this.$$element = $element; this.$$animate = $animate; this.$$timeout = $timeout; this.$$parse = $parse; this.$$q = $q; this.$$exceptionHandler = $exceptionHandler; setupValidity(this); setupModelWatcher(this); } NgModelController.prototype = { $$initGetterSetters: function() { if (this.$options.getOption('getterSetter')) { var invokeModelGetter = this.$$parse(this.$$attr.ngModel + '()'), invokeModelSetter = this.$$parse(this.$$attr.ngModel + '($$$p)'); this.$$ngModelGet = function($scope) { var modelValue = this.$$parsedNgModel($scope); if (isFunction(modelValue)) { modelValue = invokeModelGetter($scope); } return modelValue; }; this.$$ngModelSet = function($scope, newValue) { if (isFunction(this.$$parsedNgModel($scope))) { invokeModelSetter($scope, {$$$p: newValue}); } else { this.$$parsedNgModelAssign($scope, newValue); } }; } else if (!this.$$parsedNgModel.assign) { throw ngModelMinErr('nonassign', 'Expression \'{0}\' is non-assignable. Element: {1}', this.$$attr.ngModel, startingTag(this.$$element)); } }, /** * @ngdoc method * @name ngModel.NgModelController#$render * * @description * Called when the view needs to be updated. It is expected that the user of the ng-model * directive will implement this method. * * The `$render()` method is invoked in the following situations: * * * `$rollbackViewValue()` is called. If we are rolling back the view value to the last * committed value then `$render()` is called to update the input control. * * The value referenced by `ng-model` is changed programmatically and both the `$modelValue` and * the `$viewValue` are different from last time. * * Since `ng-model` does not do a deep watch, `$render()` is only invoked if the values of * `$modelValue` and `$viewValue` are actually different from their previous values. If `$modelValue` * or `$viewValue` are objects (rather than a string or number) then `$render()` will not be * invoked if you only change a property on the objects. */ $render: noop, /** * @ngdoc method * @name ngModel.NgModelController#$isEmpty * * @description * This is called when we need to determine if the value of an input is empty. * * For instance, the required directive does this to work out if the input has data or not. * * The default `$isEmpty` function checks whether the value is `undefined`, `''`, `null` or `NaN`. * * You can override this for input directives whose concept of being empty is different from the * default. The `checkboxInputType` directive does this because in its case a value of `false` * implies empty. * * @param {*} value The value of the input to check for emptiness. * @returns {boolean} True if `value` is "empty". */ $isEmpty: function(value) { // eslint-disable-next-line no-self-compare return isUndefined(value) || value === '' || value === null || value !== value; }, $$updateEmptyClasses: function(value) { if (this.$isEmpty(value)) { this.$$animate.removeClass(this.$$element, NOT_EMPTY_CLASS); this.$$animate.addClass(this.$$element, EMPTY_CLASS); } else { this.$$animate.removeClass(this.$$element, EMPTY_CLASS); this.$$animate.addClass(this.$$element, NOT_EMPTY_CLASS); } }, /** * @ngdoc method * @name ngModel.NgModelController#$setPristine * * @description * Sets the control to its pristine state. * * This method can be called to remove the `ng-dirty` class and set the control to its pristine * state (`ng-pristine` class). A model is considered to be pristine when the control * has not been changed from when first compiled. */ $setPristine: function() { this.$dirty = false; this.$pristine = true; this.$$animate.removeClass(this.$$element, DIRTY_CLASS); this.$$animate.addClass(this.$$element, PRISTINE_CLASS); }, /** * @ngdoc method * @name ngModel.NgModelController#$setDirty * * @description * Sets the control to its dirty state. * * This method can be called to remove the `ng-pristine` class and set the control to its dirty * state (`ng-dirty` class). A model is considered to be dirty when the control has been changed * from when first compiled. */ $setDirty: function() { this.$dirty = true; this.$pristine = false; this.$$animate.removeClass(this.$$element, PRISTINE_CLASS); this.$$animate.addClass(this.$$element, DIRTY_CLASS); this.$$parentForm.$setDirty(); }, /** * @ngdoc method * @name ngModel.NgModelController#$setUntouched * * @description * Sets the control to its untouched state. * * This method can be called to remove the `ng-touched` class and set the control to its * untouched state (`ng-untouched` class). Upon compilation, a model is set as untouched * by default, however this function can be used to restore that state if the model has * already been touched by the user. */ $setUntouched: function() { this.$touched = false; this.$untouched = true; this.$$animate.setClass(this.$$element, UNTOUCHED_CLASS, TOUCHED_CLASS); }, /** * @ngdoc method * @name ngModel.NgModelController#$setTouched * * @description * Sets the control to its touched state. * * This method can be called to remove the `ng-untouched` class and set the control to its * touched state (`ng-touched` class). A model is considered to be touched when the user has * first focused the control element and then shifted focus away from the control (blur event). */ $setTouched: function() { this.$touched = true; this.$untouched = false; this.$$animate.setClass(this.$$element, TOUCHED_CLASS, UNTOUCHED_CLASS); }, /** * @ngdoc method * @name ngModel.NgModelController#$rollbackViewValue * * @description * Cancel an update and reset the input element's value to prevent an update to the `$modelValue`, * which may be caused by a pending debounced event or because the input is waiting for some * future event. * * If you have an input that uses `ng-model-options` to set up debounced updates or updates that * depend on special events such as `blur`, there can be a period when the `$viewValue` is out of * sync with the ngModel's `$modelValue`. * * In this case, you can use `$rollbackViewValue()` to manually cancel the debounced / future update * and reset the input to the last committed view value. * * It is also possible that you run into difficulties if you try to update the ngModel's `$modelValue` * programmatically before these debounced/future events have resolved/occurred, because AngularJS's * dirty checking mechanism is not able to tell whether the model has actually changed or not. * * The `$rollbackViewValue()` method should be called before programmatically changing the model of an * input which may have such events pending. This is important in order to make sure that the * input field will be updated with the new model value and any pending operations are cancelled. * * @example * * * angular.module('cancel-update-example', []) * * .controller('CancelUpdateController', ['$scope', function($scope) { * $scope.model = {value1: '', value2: ''}; * * $scope.setEmpty = function(e, value, rollback) { * if (e.keyCode === 27) { * e.preventDefault(); * if (rollback) { * $scope.myForm[value].$rollbackViewValue(); * } * $scope.model[value] = ''; * } * }; * }]); * * *
    *

    Both of these inputs are only updated if they are blurred. Hitting escape should * empty them. Follow these steps and observe the difference:

    *
      *
    1. Type something in the input. You will see that the model is not yet updated
    2. *
    3. Press the Escape key. *
        *
      1. In the first example, nothing happens, because the model is already '', and no * update is detected. If you blur the input, the model will be set to the current view. *
      2. *
      3. In the second example, the pending update is cancelled, and the input is set back * to the last committed view value (''). Blurring the input does nothing. *
      4. *
      *
    4. *
    * *
    *
    *

    Without $rollbackViewValue():

    * * value1: "{{ model.value1 }}" *
    * *
    *

    With $rollbackViewValue():

    * * value2: "{{ model.value2 }}" *
    * *
    *
    div { display: table-cell; } div:nth-child(1) { padding-right: 30px; } *
    */ $rollbackViewValue: function() { this.$$timeout.cancel(this.$$pendingDebounce); this.$viewValue = this.$$lastCommittedViewValue; this.$render(); }, /** * @ngdoc method * @name ngModel.NgModelController#$validate * * @description * Runs each of the registered validators (first synchronous validators and then * asynchronous validators). * If the validity changes to invalid, the model will be set to `undefined`, * unless {@link ngModelOptions `ngModelOptions.allowInvalid`} is `true`. * If the validity changes to valid, it will set the model to the last available valid * `$modelValue`, i.e. either the last parsed value or the last value set from the scope. */ $validate: function() { // ignore $validate before model is initialized if (isNumberNaN(this.$modelValue)) { return; } var viewValue = this.$$lastCommittedViewValue; // Note: we use the $$rawModelValue as $modelValue might have been // set to undefined during a view -> model update that found validation // errors. We can't parse the view here, since that could change // the model although neither viewValue nor the model on the scope changed var modelValue = this.$$rawModelValue; var prevValid = this.$valid; var prevModelValue = this.$modelValue; var allowInvalid = this.$options.getOption('allowInvalid'); var that = this; this.$$runValidators(modelValue, viewValue, function(allValid) { // If there was no change in validity, don't update the model // This prevents changing an invalid modelValue to undefined if (!allowInvalid && prevValid !== allValid) { // Note: Don't check this.$valid here, as we could have // external validators (e.g. calculated on the server), // that just call $setValidity and need the model value // to calculate their validity. that.$modelValue = allValid ? modelValue : undefined; if (that.$modelValue !== prevModelValue) { that.$$writeModelToScope(); } } }); }, $$runValidators: function(modelValue, viewValue, doneCallback) { this.$$currentValidationRunId++; var localValidationRunId = this.$$currentValidationRunId; var that = this; // check parser error if (!processParseErrors()) { validationDone(false); return; } if (!processSyncValidators()) { validationDone(false); return; } processAsyncValidators(); function processParseErrors() { var errorKey = that.$$parserName; if (isUndefined(that.$$parserValid)) { setValidity(errorKey, null); } else { if (!that.$$parserValid) { forEach(that.$validators, function(v, name) { setValidity(name, null); }); forEach(that.$asyncValidators, function(v, name) { setValidity(name, null); }); } // Set the parse error last, to prevent unsetting it, should a $validators key == parserName setValidity(errorKey, that.$$parserValid); return that.$$parserValid; } return true; } function processSyncValidators() { var syncValidatorsValid = true; forEach(that.$validators, function(validator, name) { var result = Boolean(validator(modelValue, viewValue)); syncValidatorsValid = syncValidatorsValid && result; setValidity(name, result); }); if (!syncValidatorsValid) { forEach(that.$asyncValidators, function(v, name) { setValidity(name, null); }); return false; } return true; } function processAsyncValidators() { var validatorPromises = []; var allValid = true; forEach(that.$asyncValidators, function(validator, name) { var promise = validator(modelValue, viewValue); if (!isPromiseLike(promise)) { throw ngModelMinErr('nopromise', 'Expected asynchronous validator to return a promise but got \'{0}\' instead.', promise); } setValidity(name, undefined); validatorPromises.push(promise.then(function() { setValidity(name, true); }, function() { allValid = false; setValidity(name, false); })); }); if (!validatorPromises.length) { validationDone(true); } else { that.$$q.all(validatorPromises).then(function() { validationDone(allValid); }, noop); } } function setValidity(name, isValid) { if (localValidationRunId === that.$$currentValidationRunId) { that.$setValidity(name, isValid); } } function validationDone(allValid) { if (localValidationRunId === that.$$currentValidationRunId) { doneCallback(allValid); } } }, /** * @ngdoc method * @name ngModel.NgModelController#$commitViewValue * * @description * Commit a pending update to the `$modelValue`. * * Updates may be pending by a debounced event or because the input is waiting for a some future * event defined in `ng-model-options`. this method is rarely needed as `NgModelController` * usually handles calling this in response to input events. */ $commitViewValue: function() { var viewValue = this.$viewValue; this.$$timeout.cancel(this.$$pendingDebounce); // If the view value has not changed then we should just exit, except in the case where there is // a native validator on the element. In this case the validation state may have changed even though // the viewValue has stayed empty. if (this.$$lastCommittedViewValue === viewValue && (viewValue !== '' || !this.$$hasNativeValidators)) { return; } this.$$updateEmptyClasses(viewValue); this.$$lastCommittedViewValue = viewValue; // change to dirty if (this.$pristine) { this.$setDirty(); } this.$$parseAndValidate(); }, $$parseAndValidate: function() { var viewValue = this.$$lastCommittedViewValue; var modelValue = viewValue; var that = this; this.$$parserValid = isUndefined(modelValue) ? undefined : true; // Reset any previous parse error this.$setValidity(this.$$parserName, null); this.$$parserName = 'parse'; if (this.$$parserValid) { for (var i = 0; i < this.$parsers.length; i++) { modelValue = this.$parsers[i](modelValue); if (isUndefined(modelValue)) { this.$$parserValid = false; break; } } } if (isNumberNaN(this.$modelValue)) { // this.$modelValue has not been touched yet... this.$modelValue = this.$$ngModelGet(this.$$scope); } var prevModelValue = this.$modelValue; var allowInvalid = this.$options.getOption('allowInvalid'); this.$$rawModelValue = modelValue; if (allowInvalid) { this.$modelValue = modelValue; writeToModelIfNeeded(); } // Pass the $$lastCommittedViewValue here, because the cached viewValue might be out of date. // This can happen if e.g. $setViewValue is called from inside a parser this.$$runValidators(modelValue, this.$$lastCommittedViewValue, function(allValid) { if (!allowInvalid) { // Note: Don't check this.$valid here, as we could have // external validators (e.g. calculated on the server), // that just call $setValidity and need the model value // to calculate their validity. that.$modelValue = allValid ? modelValue : undefined; writeToModelIfNeeded(); } }); function writeToModelIfNeeded() { if (that.$modelValue !== prevModelValue) { that.$$writeModelToScope(); } } }, $$writeModelToScope: function() { this.$$ngModelSet(this.$$scope, this.$modelValue); forEach(this.$viewChangeListeners, function(listener) { try { listener(); } catch (e) { // eslint-disable-next-line no-invalid-this this.$$exceptionHandler(e); } }, this); }, /** * @ngdoc method * @name ngModel.NgModelController#$setViewValue * * @description * Update the view value. * * This method should be called when a control wants to change the view value; typically, * this is done from within a DOM event handler. For example, the {@link ng.directive:input input} * directive calls it when the value of the input changes and {@link ng.directive:select select} * calls it when an option is selected. * * When `$setViewValue` is called, the new `value` will be staged for committing through the `$parsers` * and `$validators` pipelines. If there are no special {@link ngModelOptions} specified then the staged * value is sent directly for processing through the `$parsers` pipeline. After this, the `$validators` and * `$asyncValidators` are called and the value is applied to `$modelValue`. * Finally, the value is set to the **expression** specified in the `ng-model` attribute and * all the registered change listeners, in the `$viewChangeListeners` list are called. * * In case the {@link ng.directive:ngModelOptions ngModelOptions} directive is used with `updateOn` * and the `default` trigger is not listed, all those actions will remain pending until one of the * `updateOn` events is triggered on the DOM element. * All these actions will be debounced if the {@link ng.directive:ngModelOptions ngModelOptions} * directive is used with a custom debounce for this particular event. * Note that a `$digest` is only triggered once the `updateOn` events are fired, or if `debounce` * is specified, once the timer runs out. * * When used with standard inputs, the view value will always be a string (which is in some cases * parsed into another type, such as a `Date` object for `input[date]`.) * However, custom controls might also pass objects to this method. In this case, we should make * a copy of the object before passing it to `$setViewValue`. This is because `ngModel` does not * perform a deep watch of objects, it only looks for a change of identity. If you only change * the property of the object then ngModel will not realize that the object has changed and * will not invoke the `$parsers` and `$validators` pipelines. For this reason, you should * not change properties of the copy once it has been passed to `$setViewValue`. * Otherwise you may cause the model value on the scope to change incorrectly. * *
    * In any case, the value passed to the method should always reflect the current value * of the control. For example, if you are calling `$setViewValue` for an input element, * you should pass the input DOM value. Otherwise, the control and the scope model become * out of sync. It's also important to note that `$setViewValue` does not call `$render` or change * the control's DOM value in any way. If we want to change the control's DOM value * programmatically, we should update the `ngModel` scope expression. Its new value will be * picked up by the model controller, which will run it through the `$formatters`, `$render` it * to update the DOM, and finally call `$validate` on it. *
    * * @param {*} value value from the view. * @param {string} trigger Event that triggered the update. */ $setViewValue: function(value, trigger) { this.$viewValue = value; if (this.$options.getOption('updateOnDefault')) { this.$$debounceViewValueCommit(trigger); } }, $$debounceViewValueCommit: function(trigger) { var debounceDelay = this.$options.getOption('debounce'); if (isNumber(debounceDelay[trigger])) { debounceDelay = debounceDelay[trigger]; } else if (isNumber(debounceDelay['default']) && this.$options.getOption('updateOn').indexOf(trigger) === -1 ) { debounceDelay = debounceDelay['default']; } else if (isNumber(debounceDelay['*'])) { debounceDelay = debounceDelay['*']; } this.$$timeout.cancel(this.$$pendingDebounce); var that = this; if (debounceDelay > 0) { // this fails if debounceDelay is an object this.$$pendingDebounce = this.$$timeout(function() { that.$commitViewValue(); }, debounceDelay); } else if (this.$$rootScope.$$phase) { this.$commitViewValue(); } else { this.$$scope.$apply(function() { that.$commitViewValue(); }); } }, /** * @ngdoc method * * @name ngModel.NgModelController#$overrideModelOptions * * @description * * Override the current model options settings programmatically. * * The previous `ModelOptions` value will not be modified. Instead, a * new `ModelOptions` object will inherit from the previous one overriding * or inheriting settings that are defined in the given parameter. * * See {@link ngModelOptions} for information about what options can be specified * and how model option inheritance works. * *
    * **Note:** this function only affects the options set on the `ngModelController`, * and not the options on the {@link ngModelOptions} directive from which they might have been * obtained initially. *
    * *
    * **Note:** it is not possible to override the `getterSetter` option. *
    * * @param {Object} options a hash of settings to override the previous options * */ $overrideModelOptions: function(options) { this.$options = this.$options.createChild(options); this.$$setUpdateOnEvents(); }, /** * @ngdoc method * * @name ngModel.NgModelController#$processModelValue * @description * * Runs the model -> view pipeline on the current * {@link ngModel.NgModelController#$modelValue $modelValue}. * * The following actions are performed by this method: * * - the `$modelValue` is run through the {@link ngModel.NgModelController#$formatters $formatters} * and the result is set to the {@link ngModel.NgModelController#$viewValue $viewValue} * - the `ng-empty` or `ng-not-empty` class is set on the element * - if the `$viewValue` has changed: * - {@link ngModel.NgModelController#$render $render} is called on the control * - the {@link ngModel.NgModelController#$validators $validators} are run and * the validation status is set. * * This method is called by ngModel internally when the bound scope value changes. * Application developers usually do not have to call this function themselves. * * This function can be used when the `$viewValue` or the rendered DOM value are not correctly * formatted and the `$modelValue` must be run through the `$formatters` again. * * @example * Consider a text input with an autocomplete list (for fruit), where the items are * objects with a name and an id. * A user enters `ap` and then selects `Apricot` from the list. * Based on this, the autocomplete widget will call `$setViewValue({name: 'Apricot', id: 443})`, * but the rendered value will still be `ap`. * The widget can then call `ctrl.$processModelValue()` to run the model -> view * pipeline again, which formats the object to the string `Apricot`, * then updates the `$viewValue`, and finally renders it in the DOM. * *
    Search Fruit:
    Model:
    {{selectedFruit | json}}
    angular.module('inputExample', []) .controller('inputController', function($scope) { $scope.items = [ {name: 'Apricot', id: 443}, {name: 'Clementine', id: 972}, {name: 'Durian', id: 169}, {name: 'Jackfruit', id: 982}, {name: 'Strawberry', id: 863} ]; }) .component('basicAutocomplete', { bindings: { items: '<', onSelect: '&' }, templateUrl: 'autocomplete.html', controller: function($element, $scope) { var that = this; var ngModel; that.$postLink = function() { ngModel = $element.find('input').controller('ngModel'); ngModel.$formatters.push(function(value) { return (value && value.name) || value; }); ngModel.$parsers.push(function(value) { var match = value; for (var i = 0; i < that.items.length; i++) { if (that.items[i].name === value) { match = that.items[i]; break; } } return match; }); }; that.selectItem = function(item) { ngModel.$setViewValue(item); ngModel.$processModelValue(); that.onSelect({item: item}); }; } });
    *
    * */ $processModelValue: function() { var viewValue = this.$$format(); if (this.$viewValue !== viewValue) { this.$$updateEmptyClasses(viewValue); this.$viewValue = this.$$lastCommittedViewValue = viewValue; this.$render(); // It is possible that model and view value have been updated during render this.$$runValidators(this.$modelValue, this.$viewValue, noop); } }, /** * This method is called internally to run the $formatters on the $modelValue */ $$format: function() { var formatters = this.$formatters, idx = formatters.length; var viewValue = this.$modelValue; while (idx--) { viewValue = formatters[idx](viewValue); } return viewValue; }, /** * This method is called internally when the bound scope value changes. */ $$setModelValue: function(modelValue) { this.$modelValue = this.$$rawModelValue = modelValue; this.$$parserValid = undefined; this.$processModelValue(); }, $$setUpdateOnEvents: function() { if (this.$$updateEvents) { this.$$element.off(this.$$updateEvents, this.$$updateEventHandler); } this.$$updateEvents = this.$options.getOption('updateOn'); if (this.$$updateEvents) { this.$$element.on(this.$$updateEvents, this.$$updateEventHandler); } }, $$updateEventHandler: function(ev) { this.$$debounceViewValueCommit(ev && ev.type); } }; function setupModelWatcher(ctrl) { // model -> value // Note: we cannot use a normal scope.$watch as we want to detect the following: // 1. scope value is 'a' // 2. user enters 'b' // 3. ng-change kicks in and reverts scope value to 'a' // -> scope value did not change since the last digest as // ng-change executes in apply phase // 4. view should be changed back to 'a' ctrl.$$scope.$watch(function ngModelWatch(scope) { var modelValue = ctrl.$$ngModelGet(scope); // if scope model value and ngModel value are out of sync // This cannot be moved to the action function, because it would not catch the // case where the model is changed in the ngChange function or the model setter if (modelValue !== ctrl.$modelValue && // checks for NaN is needed to allow setting the model to NaN when there's an asyncValidator // eslint-disable-next-line no-self-compare (ctrl.$modelValue === ctrl.$modelValue || modelValue === modelValue) ) { ctrl.$$setModelValue(modelValue); } return modelValue; }); } /** * @ngdoc method * @name ngModel.NgModelController#$setValidity * * @description * Change the validity state, and notify the form. * * This method can be called within $parsers/$formatters or a custom validation implementation. * However, in most cases it should be sufficient to use the `ngModel.$validators` and * `ngModel.$asyncValidators` collections which will call `$setValidity` automatically. * * @param {string} validationErrorKey Name of the validator. The `validationErrorKey` will be assigned * to either `$error[validationErrorKey]` or `$pending[validationErrorKey]` * (for unfulfilled `$asyncValidators`), so that it is available for data-binding. * The `validationErrorKey` should be in camelCase and will get converted into dash-case * for class name. Example: `myError` will result in `ng-valid-my-error` and `ng-invalid-my-error` * classes and can be bound to as `{{ someForm.someControl.$error.myError }}`. * @param {boolean} isValid Whether the current state is valid (true), invalid (false), pending (undefined), * or skipped (null). Pending is used for unfulfilled `$asyncValidators`. * Skipped is used by AngularJS when validators do not run because of parse errors and * when `$asyncValidators` do not run because any of the `$validators` failed. */ addSetValidityMethod({ clazz: NgModelController, set: function(object, property) { object[property] = true; }, unset: function(object, property) { delete object[property]; } }); /** * @ngdoc directive * @name ngModel * @restrict A * @priority 1 * @param {expression} ngModel assignable {@link guide/expression Expression} to bind to. * * @description * The `ngModel` directive binds an `input`,`select`, `textarea` (or custom form control) to a * property on the scope using {@link ngModel.NgModelController NgModelController}, * which is created and exposed by this directive. * * `ngModel` is responsible for: * * - Binding the view into the model, which other directives such as `input`, `textarea` or `select` * require. * - Providing validation behavior (i.e. required, number, email, url). * - Keeping the state of the control (valid/invalid, dirty/pristine, touched/untouched, validation errors). * - Setting related css classes on the element (`ng-valid`, `ng-invalid`, `ng-dirty`, `ng-pristine`, `ng-touched`, * `ng-untouched`, `ng-empty`, `ng-not-empty`) including animations. * - Registering the control with its parent {@link ng.directive:form form}. * * Note: `ngModel` will try to bind to the property given by evaluating the expression on the * current scope. If the property doesn't already exist on this scope, it will be created * implicitly and added to the scope. * * For best practices on using `ngModel`, see: * * - [Understanding Scopes](https://github.com/angular/angular.js/wiki/Understanding-Scopes) * * For basic examples, how to use `ngModel`, see: * * - {@link ng.directive:input input} * - {@link input[text] text} * - {@link input[checkbox] checkbox} * - {@link input[radio] radio} * - {@link input[number] number} * - {@link input[email] email} * - {@link input[url] url} * - {@link input[date] date} * - {@link input[datetime-local] datetime-local} * - {@link input[time] time} * - {@link input[month] month} * - {@link input[week] week} * - {@link ng.directive:select select} * - {@link ng.directive:textarea textarea} * * ## Complex Models (objects or collections) * * By default, `ngModel` watches the model by reference, not value. This is important to know when * binding inputs to models that are objects (e.g. `Date`) or collections (e.g. arrays). If only properties of the * object or collection change, `ngModel` will not be notified and so the input will not be re-rendered. * * The model must be assigned an entirely new object or collection before a re-rendering will occur. * * Some directives have options that will cause them to use a custom `$watchCollection` on the model expression * - for example, `ngOptions` will do so when a `track by` clause is included in the comprehension expression or * if the select is given the `multiple` attribute. * * The `$watchCollection()` method only does a shallow comparison, meaning that changing properties deeper than the * first level of the object (or only changing the properties of an item in the collection if it's an array) will still * not trigger a re-rendering of the model. * * ## CSS classes * The following CSS classes are added and removed on the associated input/select/textarea element * depending on the validity of the model. * * - `ng-valid`: the model is valid * - `ng-invalid`: the model is invalid * - `ng-valid-[key]`: for each valid key added by `$setValidity` * - `ng-invalid-[key]`: for each invalid key added by `$setValidity` * - `ng-pristine`: the control hasn't been interacted with yet * - `ng-dirty`: the control has been interacted with * - `ng-touched`: the control has been blurred * - `ng-untouched`: the control hasn't been blurred * - `ng-pending`: any `$asyncValidators` are unfulfilled * - `ng-empty`: the view does not contain a value or the value is deemed "empty", as defined * by the {@link ngModel.NgModelController#$isEmpty} method * - `ng-not-empty`: the view contains a non-empty value * * Keep in mind that ngAnimate can detect each of these classes when added and removed. * * @animations * Animations within models are triggered when any of the associated CSS classes are added and removed * on the input element which is attached to the model. These classes include: `.ng-pristine`, `.ng-dirty`, * `.ng-invalid` and `.ng-valid` as well as any other validations that are performed on the model itself. * The animations that are triggered within ngModel are similar to how they work in ngClass and * animations can be hooked into using CSS transitions, keyframes as well as JS animations. * * The following example shows a simple way to utilize CSS transitions to style an input element * that has been rendered as invalid after it has been validated: * *
     * //be sure to include ngAnimate as a module to hook into more
     * //advanced animations
     * .my-input {
     *   transition:0.5s linear all;
     *   background: white;
     * }
     * .my-input.ng-invalid {
     *   background: red;
     *   color:white;
     * }
     * 
    * * @example * ### Basic Usage *

    Update input to see transitions when valid/invalid. Integer is a valid value.

    *
    * * @example * ### Binding to a getter/setter * * Sometimes it's helpful to bind `ngModel` to a getter/setter function. A getter/setter is a * function that returns a representation of the model when called with zero arguments, and sets * the internal state of a model when called with an argument. It's sometimes useful to use this * for models that have an internal representation that's different from what the model exposes * to the view. * *
    * **Best Practice:** It's best to keep getters fast because AngularJS is likely to call them more * frequently than other parts of your code. *
    * * You use this behavior by adding `ng-model-options="{ getterSetter: true }"` to an element that * has `ng-model` attached to it. You can also add `ng-model-options="{ getterSetter: true }"` to * a `
    `, which will enable this behavior for all ``s within it. See * {@link ng.directive:ngModelOptions `ngModelOptions`} for more. * * The following example shows how to use `ngModel` with a getter/setter: * * @example *
    user.name = 
    angular.module('getterSetterExample', []) .controller('ExampleController', ['$scope', function($scope) { var _name = 'Brian'; $scope.user = { name: function(newName) { // Note that newName can be undefined for two reasons: // 1. Because it is called as a getter and thus called with no arguments // 2. Because the property should actually be set to undefined. This happens e.g. if the // input is invalid return arguments.length ? (_name = newName) : _name; } }; }]); *
    */ var ngModelDirective = ['$rootScope', function($rootScope) { return { restrict: 'A', require: ['ngModel', '^?form', '^?ngModelOptions'], controller: NgModelController, // Prelink needs to run before any input directive // so that we can set the NgModelOptions in NgModelController // before anyone else uses it. priority: 1, compile: function ngModelCompile(element) { // Setup initial state of the control element.addClass(PRISTINE_CLASS).addClass(UNTOUCHED_CLASS).addClass(VALID_CLASS); return { pre: function ngModelPreLink(scope, element, attr, ctrls) { var modelCtrl = ctrls[0], formCtrl = ctrls[1] || modelCtrl.$$parentForm, optionsCtrl = ctrls[2]; if (optionsCtrl) { modelCtrl.$options = optionsCtrl.$options; } modelCtrl.$$initGetterSetters(); // notify others, especially parent forms formCtrl.$addControl(modelCtrl); attr.$observe('name', function(newValue) { if (modelCtrl.$name !== newValue) { modelCtrl.$$parentForm.$$renameControl(modelCtrl, newValue); } }); scope.$on('$destroy', function() { modelCtrl.$$parentForm.$removeControl(modelCtrl); }); }, post: function ngModelPostLink(scope, element, attr, ctrls) { var modelCtrl = ctrls[0]; modelCtrl.$$setUpdateOnEvents(); function setTouched() { modelCtrl.$setTouched(); } element.on('blur', function() { if (modelCtrl.$touched) return; if ($rootScope.$$phase) { scope.$evalAsync(setTouched); } else { scope.$apply(setTouched); } }); } }; } }; }]; angular.js-1.7.9/src/ng/directive/ngModelOptions.js000066400000000000000000000535771356472325200222760ustar00rootroot00000000000000'use strict'; /* exported defaultModelOptions */ var defaultModelOptions; var DEFAULT_REGEXP = /(\s+|^)default(\s+|$)/; /** * @ngdoc type * @name ModelOptions * @description * A container for the options set by the {@link ngModelOptions} directive */ function ModelOptions(options) { this.$$options = options; } ModelOptions.prototype = { /** * @ngdoc method * @name ModelOptions#getOption * @param {string} name the name of the option to retrieve * @returns {*} the value of the option * @description * Returns the value of the given option */ getOption: function(name) { return this.$$options[name]; }, /** * @ngdoc method * @name ModelOptions#createChild * @param {Object} options a hash of options for the new child that will override the parent's options * @return {ModelOptions} a new `ModelOptions` object initialized with the given options. */ createChild: function(options) { var inheritAll = false; // make a shallow copy options = extend({}, options); // Inherit options from the parent if specified by the value `"$inherit"` forEach(options, /** @this */ function(option, key) { if (option === '$inherit') { if (key === '*') { inheritAll = true; } else { options[key] = this.$$options[key]; // `updateOn` is special so we must also inherit the `updateOnDefault` option if (key === 'updateOn') { options.updateOnDefault = this.$$options.updateOnDefault; } } } else { if (key === 'updateOn') { // If the `updateOn` property contains the `default` event then we have to remove // it from the event list and set the `updateOnDefault` flag. options.updateOnDefault = false; options[key] = trim(option.replace(DEFAULT_REGEXP, function() { options.updateOnDefault = true; return ' '; })); } } }, this); if (inheritAll) { // We have a property of the form: `"*": "$inherit"` delete options['*']; defaults(options, this.$$options); } // Finally add in any missing defaults defaults(options, defaultModelOptions.$$options); return new ModelOptions(options); } }; defaultModelOptions = new ModelOptions({ updateOn: '', updateOnDefault: true, debounce: 0, getterSetter: false, allowInvalid: false, timezone: null }); /** * @ngdoc directive * @name ngModelOptions * @restrict A * @priority 10 * * @description * This directive allows you to modify the behaviour of {@link ngModel} directives within your * application. You can specify an `ngModelOptions` directive on any element. All {@link ngModel} * directives will use the options of their nearest `ngModelOptions` ancestor. * * The `ngModelOptions` settings are found by evaluating the value of the attribute directive as * an AngularJS expression. This expression should evaluate to an object, whose properties contain * the settings. For example: `
    *
    * * *
    * ``` * * the `input` element will have the following settings * * ```js * { allowInvalid: true, updateOn: 'default', debounce: 0 } * ``` * * Notice that the `debounce` setting was not inherited and used the default value instead. * * You can specify that all undefined settings are automatically inherited from an ancestor by * including a property with key of `"*"` and value of `"$inherit"`. * * For example given the following fragment of HTML * * * ```html *
    *
    * * *
    * ``` * * the `input` element will have the following settings * * ```js * { allowInvalid: true, updateOn: 'default', debounce: 200 } * ``` * * Notice that the `debounce` setting now inherits the value from the outer `
    ` element. * * If you are creating a reusable component then you should be careful when using `"*": "$inherit"` * since you may inadvertently inherit a setting in the future that changes the behavior of your component. * * * ## Triggering and debouncing model updates * * The `updateOn` and `debounce` properties allow you to specify a custom list of events that will * trigger a model update and/or a debouncing delay so that the actual update only takes place when * a timer expires; this timer will be reset after another change takes place. * * Given the nature of `ngModelOptions`, the value displayed inside input fields in the view might * be different from the value in the actual model. This means that if you update the model you * should also invoke {@link ngModel.NgModelController#$rollbackViewValue} on the relevant input field in * order to make sure it is synchronized with the model and that any debounced action is canceled. * * The easiest way to reference the control's {@link ngModel.NgModelController#$rollbackViewValue} * method is by making sure the input is placed inside a form that has a `name` attribute. This is * important because `form` controllers are published to the related scope under the name in their * `name` attribute. * * Any pending changes will take place immediately when an enclosing form is submitted via the * `submit` event. Note that `ngClick` events will occur before the model is updated. Use `ngSubmit` * to have access to the updated model. * * ### Overriding immediate updates * * The following example shows how to override immediate updates. Changes on the inputs within the * form will update the model only when the control loses focus (blur event). If `escape` key is * pressed while the input field is focused, the value is reset to the value in the current model. * * * *
    *
    *
    *
    * *
    user.name = 
    *
    *
    * * angular.module('optionsExample', []) * .controller('ExampleController', ['$scope', function($scope) { * $scope.user = { name: 'say', data: '' }; * * $scope.cancel = function(e) { * if (e.keyCode === 27) { * $scope.userForm.userName.$rollbackViewValue(); * } * }; * }]); * * * var model = element(by.binding('user.name')); * var input = element(by.model('user.name')); * var other = element(by.model('user.data')); * * it('should allow custom events', function() { * input.sendKeys(' hello'); * input.click(); * expect(model.getText()).toEqual('say'); * other.click(); * expect(model.getText()).toEqual('say hello'); * }); * * it('should $rollbackViewValue when model changes', function() { * input.sendKeys(' hello'); * expect(input.getAttribute('value')).toEqual('say hello'); * input.sendKeys(protractor.Key.ESCAPE); * expect(input.getAttribute('value')).toEqual('say'); * other.click(); * expect(model.getText()).toEqual('say'); * }); * *
    * * ### Debouncing updates * * The next example shows how to debounce model changes. Model will be updated only 1 sec after last change. * If the `Clear` button is pressed, any debounced action is canceled and the value becomes empty. * * * *
    *
    * Name: * *
    * *
    user.name = 
    *
    *
    * * angular.module('optionsExample', []) * .controller('ExampleController', ['$scope', function($scope) { * $scope.user = { name: 'say' }; * }]); * *
    * * ### Default events, extra triggers, and catch-all debounce values * * This example shows the relationship between "default" update events and * additional `updateOn` triggers. * * `default` events are those that are bound to the control, and when fired, update the `$viewValue` * via {@link ngModel.NgModelController#$setViewValue $setViewValue}. Every event that is not listed * in `updateOn` is considered a "default" event, since different control types have different * default events. * * The control in this example updates by "default", "click", and "blur", with different `debounce` * values. You can see that "click" doesn't have an individual `debounce` value - * therefore it uses the `*` debounce value. * * There is also a button that calls {@link ngModel.NgModelController#$setViewValue $setViewValue} * directly with a "custom" event. Since "custom" is not defined in the `updateOn` list, * it is considered a "default" event and will update the * control if "default" is defined in `updateOn`, and will receive the "default" debounce value. * Note that this is just to illustrate how custom controls would possibly call `$setViewValue`. * * You can change the `updateOn` and `debounce` configuration to test different scenarios. This * is done with {@link ngModel.NgModelController#$overrideModelOptions $overrideModelOptions}. * angular.module('optionsExample', []) .component('modelUpdateDemo', { templateUrl: 'template.html', controller: function() { this.name = 'Chinua'; this.options = { updateOn: 'default blur click', debounce: { default: 2000, blur: 0, '*': 1000 } }; this.updateEvents = function() { var eventList = this.options.updateOn.split(' '); eventList.push('*'); var events = {}; for (var i = 0; i < eventList.length; i++) { events[eventList[i]] = this.options.debounce[eventList[i]]; } this.events = events; }; this.updateOptions = function() { var options = angular.extend(this.options, { updateOn: Object.keys(this.events).join(' ').replace('*', ''), debounce: this.events }); this.form.input.$overrideModelOptions(options); }; // Initialize the event form this.updateEvents(); } });
    Input: Model: {{$ctrl.name}}

    updateOn
    Option Debounce value
    {{key}}

    * * * ## Model updates and validation * * The default behaviour in `ngModel` is that the model value is set to `undefined` when the * validation determines that the value is invalid. By setting the `allowInvalid` property to true, * the model will still be updated even if the value is invalid. * * * ## Connecting to the scope * * By setting the `getterSetter` property to true you are telling ngModel that the `ngModel` expression * on the scope refers to a "getter/setter" function rather than the value itself. * * The following example shows how to bind to getter/setters: * * * *
    *
    * *
    *
    user.name = 
    *
    *
    * * angular.module('getterSetterExample', []) * .controller('ExampleController', ['$scope', function($scope) { * var _name = 'Brian'; * $scope.user = { * name: function(newName) { * return angular.isDefined(newName) ? (_name = newName) : _name; * } * }; * }]); * *
    * * * ## Programmatically changing options * * The `ngModelOptions` expression is only evaluated once when the directive is linked; it is not * watched for changes. However, it is possible to override the options on a single * {@link ngModel.NgModelController} instance with * {@link ngModel.NgModelController#$overrideModelOptions `NgModelController#$overrideModelOptions()`}. * See also the example for * {@link ngModelOptions#default-events-extra-triggers-and-catch-all-debounce-values * Default events, extra triggers, and catch-all debounce values}. * * * ## Specifying timezones * * You can specify the timezone that date/time input directives expect by providing its name in the * `timezone` property. * * * ## Formatting the value of `time` and `datetime-local` * * With the options `timeSecondsFormat` and `timeStripZeroSeconds` it is possible to adjust the value * that is displayed in the control. Note that browsers may apply their own formatting * in the user interface. * angular.module('timeExample', []) .component('timeExample', { templateUrl: 'timeExample.html', controller: function() { this.time = new Date(1970, 0, 1, 14, 57, 0); this.options = { timeSecondsFormat: 'ss', timeStripZeroSeconds: true }; this.optionChange = function() { this.timeForm.timeFormatted.$overrideModelOptions(this.options); this.time = new Date(this.time); }; } });
    Default:
    With options:
    Options:
    timeSecondsFormat:
    timeStripZeroSeconds:
    *
    * * @param {Object} ngModelOptions options to apply to {@link ngModel} directives on this element and * and its descendents. * * **General options**: * * - `updateOn`: string specifying which event should the input be bound to. You can set several * events using an space delimited list. There is a special event called `default` that * matches the default events belonging to the control. These are the events that are bound to * the control, and when fired, update the `$viewValue` via `$setViewValue`. * * `ngModelOptions` considers every event that is not listed in `updateOn` a "default" event, * since different control types use different default events. * * See also the section {@link ngModelOptions#triggering-and-debouncing-model-updates * Triggering and debouncing model updates}. * * - `debounce`: integer value which contains the debounce model update value in milliseconds. A * value of 0 triggers an immediate update. If an object is supplied instead, you can specify a * custom value for each event. For example: * ``` * ng-model-options="{ * updateOn: 'default blur', * debounce: { 'default': 500, 'blur': 0 } * }" * ``` * You can use the `*` key to specify a debounce value that applies to all events that are not * specifically listed. In the following example, `mouseup` would have a debounce delay of 1000: * ``` * ng-model-options="{ * updateOn: 'default blur mouseup', * debounce: { 'default': 500, 'blur': 0, '*': 1000 } * }" * ``` * - `allowInvalid`: boolean value which indicates that the model can be set with values that did * not validate correctly instead of the default behavior of setting the model to undefined. * - `getterSetter`: boolean value which determines whether or not to treat functions bound to * `ngModel` as getters/setters. * * * **Input-type specific options**: * * - `timezone`: Defines the timezone to be used to read/write the `Date` instance in the model for * ``, ``, ... . It understands UTC/GMT and the * continental US time zone abbreviations, but for general use, use a time zone offset, for * example, `'+0430'` (4 hours, 30 minutes east of the Greenwich meridian) * If not specified, the timezone of the browser will be used. * Note that changing the timezone will have no effect on the current date, and is only applied after * the next input / model change. * * - `timeSecondsFormat`: Defines if the `time` and `datetime-local` types should show seconds and * milliseconds. The option follows the format string of {@link date date filter}. * By default, the options is `undefined` which is equal to `'ss.sss'` (seconds and milliseconds). * The other options are `'ss'` (strips milliseconds), and `''` (empty string), which strips both * seconds and milliseconds. * Note that browsers that support `time` and `datetime-local` require the hour and minutes * part of the time string, and may show the value differently in the user interface. * {@link ngModelOptions#formatting-the-value-of-time-and-datetime-local- See the example}. * * - `timeStripZeroSeconds`: Defines if the `time` and `datetime-local` types should strip the * seconds and milliseconds from the formatted value if they are zero. This option is applied * after `timeSecondsFormat`. * This option can be used to make the formatting consistent over different browsers, as some * browsers with support for `time` will natively hide the milliseconds and * seconds if they are zero, but others won't, and browsers that don't implement these input * types will always show the full string. * {@link ngModelOptions#formatting-the-value-of-time-and-datetime-local- See the example}. * */ var ngModelOptionsDirective = function() { NgModelOptionsController.$inject = ['$attrs', '$scope']; function NgModelOptionsController($attrs, $scope) { this.$$attrs = $attrs; this.$$scope = $scope; } NgModelOptionsController.prototype = { $onInit: function() { var parentOptions = this.parentCtrl ? this.parentCtrl.$options : defaultModelOptions; var modelOptionsDefinition = this.$$scope.$eval(this.$$attrs.ngModelOptions); this.$options = parentOptions.createChild(modelOptionsDefinition); } }; return { restrict: 'A', // ngModelOptions needs to run before ngModel and input directives priority: 10, require: {parentCtrl: '?^^ngModelOptions'}, bindToController: true, controller: NgModelOptionsController }; }; // shallow copy over values from `src` that are not already specified on `dst` function defaults(dst, src) { forEach(src, function(value, key) { if (!isDefined(dst[key])) { dst[key] = value; } }); } angular.js-1.7.9/src/ng/directive/ngNonBindable.js000066400000000000000000000023741356472325200220220ustar00rootroot00000000000000'use strict'; /** * @ngdoc directive * @name ngNonBindable * @restrict AC * @priority 1000 * @element ANY * * @description * The `ngNonBindable` directive tells AngularJS not to compile or bind the contents of the current * DOM element, including directives on the element itself that have a lower priority than * `ngNonBindable`. This is useful if the element contains what appears to be AngularJS directives * and bindings but which should be ignored by AngularJS. This could be the case if you have a site * that displays snippets of code, for instance. * * @example * In this example there are two locations where a simple interpolation binding (`{{}}`) is present, * but the one wrapped in `ngNonBindable` is left alone. *
    Normal: {{1 + 2}}
    Ignored: {{1 + 2}}
    it('should check ng-non-bindable', function() { expect(element(by.binding('1 + 2')).getText()).toContain('3'); expect(element.all(by.css('div')).last().getText()).toMatch(/1 \+ 2/); });
    */ var ngNonBindableDirective = ngDirective({ terminal: true, priority: 1000 }); angular.js-1.7.9/src/ng/directive/ngOptions.js000066400000000000000000000733351356472325200213070ustar00rootroot00000000000000'use strict'; /* exported ngOptionsDirective */ /* global jqLiteRemove */ var ngOptionsMinErr = minErr('ngOptions'); /** * @ngdoc directive * @name ngOptions * @restrict A * * @description * * The `ngOptions` attribute can be used to dynamically generate a list of `` * DOM element. * * `disable`: The result of this expression will be used to disable the rendered `