From e65928eecb3443943e3f9431e394e9bc59787952 Mon Sep 17 00:00:00 2001 From: Martin Staffa <mjstaffa@googlemail.com> Date: Mon, 5 Jun 2017 20:23:12 +0200 Subject: [PATCH] docs($compile): add more info about optional bindings This centralizes the info about optional bindings. Also adds more examples to the $compile:iscp error. Closes #15989 Closes #16025 --- docs/content/error/$compile/iscp.ngdoc | 16 +++++---- src/ng/compile.js | 45 ++++++++++++++++++++++---- 2 files changed, 47 insertions(+), 14 deletions(-) diff --git a/docs/content/error/$compile/iscp.ngdoc b/docs/content/error/$compile/iscp.ngdoc index 78f82f2f6c61..8153e44d4a05 100644 --- a/docs/content/error/$compile/iscp.ngdoc +++ b/docs/content/error/$compile/iscp.ngdoc @@ -10,13 +10,15 @@ myModule.directive('directiveName', function factory() { return { ... scope: { - 'attrName': '@', // OK - 'attrName2': '=localName', // OK - 'attrName3': '<?localName', // OK - 'attrName4': ' = name', // OK - 'attrName5': 'name', // ERROR: missing mode @&= - 'attrName6': 'name=', // ERROR: must be prefixed with @&= - 'attrName7': '=name?', // ERROR: ? must come directly after the mode + 'localName': '@', // OK + 'localName2': '&attr', // OK + 'localName3': '<?attr', // OK + 'localName4': ' = attr', // OK + 'localName5': ' =*attr', // OK + 'localName6': 'attr', // ERROR: missing mode @&=< + 'localName7': 'attr=', // ERROR: must be prefixed with @&=< + 'localName8': '=attr?', // ERROR: ? must come directly after the mode + 'localName9': '<*' // ERROR: * is only valid with = } ... } diff --git a/src/ng/compile.js b/src/ng/compile.js index a2d9730c3920..ef2e65ad379d 100644 --- a/src/ng/compile.js +++ b/src/ng/compile.js @@ -303,21 +303,22 @@ * name. Given `<my-component my-attr="parentModel">` and the isolate scope definition `scope: { * localModel: '=myAttr' }`, the property `localModel` on the directive's scope will reflect the * value of `parentModel` on the parent scope. Changes to `parentModel` will be reflected in - * `localModel` and vice versa. Optional attributes should be marked as such with a question mark: - * `=?` or `=?attr`. If the binding expression is non-assignable, or if the attribute isn't - * optional and doesn't exist, an exception ({@link error/$compile/nonassign `$compile:nonassign`}) - * will be thrown upon discovering changes to the local value, since it will be impossible to sync - * them back to the parent scope. By default, the {@link ng.$rootScope.Scope#$watch `$watch`} + * `localModel` and vice versa. If the binding expression is non-assignable, or if the attribute + * isn't optional and doesn't exist, an exception + * ({@link error/$compile/nonassign `$compile:nonassign`}) will be thrown upon discovering changes + * to the local value, since it will be impossible to sync them back to the parent scope. + * + * By default, the {@link ng.$rootScope.Scope#$watch `$watch`} * method is used for tracking changes, and the equality check is based on object identity. * However, if an object literal or an array literal is passed as the binding expression, the * equality check is done by value (using the {@link angular.equals} function). It's also possible * to watch the evaluated value shallowly with {@link ng.$rootScope.Scope#$watchCollection - * `$watchCollection`}: use `=*` or `=*attr` (`=*?` or `=*?attr` if the attribute is optional). + * `$watchCollection`}: use `=*` or `=*attr` * * * `<` or `<attr` - set up a one-way (one-directional) binding between a local scope property and an * expression passed via the attribute `attr`. The expression is evaluated in the context of the * parent scope. If no `attr` name is specified then the attribute name is assumed to be the same as the - * local name. You can also make the binding optional by adding `?`: `<?` or `<?attr`. + * local name. * * For example, given `<my-component my-attr="parentModel">` and directive definition of * `scope: { localModel:'<myAttr' }`, then the isolated scope property `localModel` will reflect the @@ -347,6 +348,36 @@ * and values into the expression wrapper fn. For example, if the expression is `increment(amount)` * then we can specify the amount value by calling the `localFn` as `localFn({amount: 22})`. * + * All 4 kinds of bindings (`@`, `=`, `<`, and `&`) can be made optional by adding `?` to the expression. + * The marker must come after the mode and before the attribute name. + * See the {@link error/$compile/iscp Invalid Isolate Scope Definition error} for definition examples. + * This is useful to refine the interface directives provide. + * One subtle difference between optional and non-optional happens **when the binding attribute is not + * set**: + * - the binding is optional: the property will not be defined + * - the binding is not optional: the property is defined + * + * ```js + *app.directive('testDir', function() { + return { + scope: { + notoptional: '=', + optional: '=?', + }, + bindToController: true, + controller: function() { + this.$onInit = function() { + console.log(this.hasOwnProperty('notoptional')) // true + console.log(this.hasOwnProperty('optional')) // false + } + } + } + }) + *``` + * + * + * ##### Combining directives with different scope defintions + * * In general it's possible to apply more than one directive to one element, but there might be limitations * depending on the type of scope required by the directives. The following points will help explain these limitations. * For simplicity only two directives are taken into account, but it is also applicable for several directives: