What's the difference between ngModel.$modelValue and ngModel.$viewValue - angularjs

I have the following ckEditor directive. At the bottom are two variations that I have seen from examples on how to set the data in the editor:
app.directive('ckEditor', [function () {
return {
require: '?ngModel',
link: function ($scope, elm, attr, ngModel) {
var ck = null;
var config = attr.editorSize;
if (config == 'wide') {
ck = CKEDITOR.replace(elm[0], { customConfig: 'config-wide.js' });
} else {
ck = CKEDITOR.replace(elm[0], { customConfig: 'config-narrow.js' });
}
function updateModel() {
$scope.$apply(function () {
ngModel.$setViewValue(ck.getData());
});
}
$scope.$on('modalObjectSet', function (e, modalData) {
// force a call to render
ngModel.$render();
});
ck.on('change', updateModel);
ck.on('mode', updateModel);
ck.on('key', updateModel);
ck.on('dataReady', updateModel);
ck.on('instanceReady', function () {
ngModel.$render();
});
ck.on('insertElement', function () {
setTimeout(function () {
$scope.$apply(function () {
ngModel.$setViewValue(ck.getData());
});
}, 1000);
});
ngModel.$render = function (value) {
ck.setData(ngModel.$modelValue);
};
ngModel.$render = function (value) {
ck.setData(ngModel.$viewValue);
};
}
};
}])
Can someone tell me what is the difference between:
ck.setData(ngModel.$modelValue);
ck.setData(ngModel.$viewValue);
And which should I use. I looked at the angular documentation and it says:
$viewValue
Actual string value in the view.
$modelValue
The value in the model, that the control is bound to.
I have no idea what the author meant when he wrote this in the document :-(

You are looking at the correct documentation, but it might just be that you're a little confused. The $modelValue and $viewValue have one distinct difference. It is this:
As you already noted above:
$viewValue: Actual string (or Object) value in the view.
$modelValue: The value in the model, that the control is bound to.
I'm going to assume that your ngModel is referring to an <input /> element...? So your <input> has a string value that it displays to the user, right? But the actual model might be some other version of that string. For example, the input might be showing the string '200' but the <input type="number"> (for example) will actually contain a model value of 200 as an integer. So the string representation that you "view" in the <input> is the ngModel.$viewValue and the numeric representation will be the ngModel.$modelValue.
Another example would be a <input type="date"> where the $viewValue would be something like Jan 01, 2000 and the $modelValue would be an actual javascript Date object that represents that date string. Does that make sense?
I hope that answers your question.

You can see things like this :
$modelValue is your external API, that is to say, something exposed to your controller.
$viewValue is your internal API, you should use it only internally.
When editing $viewValue, the render method won't be called, because it is the "rendered model". You will have to do it manually, whereas the rendering method will be called automatically upon $modelValue modifications.
However, the information will remain consistent, thanks to $formatters and $parsers :
If you change $viewValue, $parsers will translate it back to
$modelValue.
If you change $modelValue, $formatters will
convert it to $viewValue.

Angular has to keep track of two views of ngModel data- there's the data as seen by the DOM(browser) and then there's Angular's processed representation of those values. The $viewValue is the DOM side value. So, for example, in an <input> the $viewValue is what the user typed in to their browser.
Once someone types something in <input> then $viewValue is processed by $parsers and turned into Angular's view of the value which is called $modelValue.
So you can think of $modelValue being angular's processed version of the value, the value you see in the model, while $viewValue is the raw version.
To take this one step further imagine we do something that changes the $modelValue. Angular sees this change and calls $formatters to create an updated $viewValue (based on the new $modelValue) to be sent to the DOM.

Related

Set value in $scope and use $parsers on it

I have a field in $scope (i'm new in angularjs terminology), that is linked in html:
<input type="text" ng-model="phone" format-phone/>
Format-phone is a custom directive that adds a parser for a view value:
.directive('formatPhone', () => ({
require: '?ngModel',
link: (scope, elem, attrs, ctrl) => {
if (!ctrl) return;
ctrl.$parsers.unshift(viewValue => {
var phone = viewValue.substring(0, 12).replace(/* some custom replace */);
elem.val(phone.replace(/* another custom replace */)));
return phone;
});
}
}))
That works perfectly and modify model and view values as intended. I want to add default value to the model in controller code, so i added this line:
$scope.phone = '1112223344';
But this value is not handled by parser in directive and stays raw until i edit it first time. Can i explicitly call parser on view/model value from controller without writing another initialization directive? Or maybe i can set view value that will be automatically handled by parser?
UPD: Thanks to #potatopeelings i made it better, but this solution looks real bad. Do i have any alternative for calling formatter from parser?
ctrl.$formatters.unshift(modelValue => {
modelValue; // 123456
return modelValue.replace(...); // View value is now (123)45-6
});
ctrl.$parsers.unshift(viewValue => {
var phone = viewValue.replace(...); // (123)456 -> 123456
elem.val(ctrl.$formatters[0](phone)); // Element value is now (123)45-6
return phone; // Model value is now 123456
});
parsers are meant to convert view values to model values and formatters are used to convert model values to view values.
You need to insert a formatter that converts your (initialized) model value to the view value you need.

How to use the last valid modelValue if a model becomes invalid?

I'm working on an application that saves changes automatically when the user changes something, for example the value of an input field. I have written a autosave directive that is added to all form fields that should trigger save events automatically.
template:
<input ng-model="fooCtrl.name" autosave>
<input ng-model="fooCtrl.email" autosave>
directive:
.directive('autosave', ['$parse', function ($parse) {
return {
restrict: 'A',
require: 'ngModel',
link: function (scope, element, attrs, ngModel) {
function saveIfModelChanged () {
// save object containing name and email to server ...
}
ngModel.$viewChangeListeners.push(function () {
saveIfModelChanged();
});
}
};
}]);
So far, this all works fine for me. However, when I add validation into the mix, for example validating the input field to be a valid email address, the modelValue is set to undefined as soon as the viewValue is changed to an invalid email address.
What I would like to do is this: Remember the last valid modelValue and use this when autosaving. If the user changes the email address to be invalid, the object containing name and email should still be saved to the server. Using the current valid name and the last valid email.
I started out by saving the last valid modelValue like this:
template with validation added:
<input type="email" ng-model="fooCtrl.name" autosave required>
<input ng-model="fooCtrl.email" autosave required>
directive with saving lastModelValue:
.directive('autosave', ['$parse', function ($parse) {
return {
restrict: 'A',
require: 'ngModel',
link: function (scope, element, attrs, ngModel) {
var lastModelValue;
function saveIfModelChanged () {
// remeber last valid modelValue
if (ngModel.$valid) {
lastModelValue = ngModel.$modelValue;
}
// save object containing current or last valid
// name and email to server ...
}
ngModel.$viewChangeListeners.push(function () {
saveIfModelChanged();
});
}
};
}]);
My question is, how to use lastModelValue while saving, but preserving the invalid value in the view?
EDIT:
Another possibility, as suggested by Jugnu below, would be wrapping and manipulating the build in validators.
I tried to following: wrap all existing validators and remember the last valid value, to restore it if validations fails:
Object.keys(ngModel.$validators).forEach(function(validatorName, index) {
var validator = ngModel.$validators[validatorName];
ngModel.$validators[validatorName] = createWrapper(validatorName, validator, ngModel);
});
function createWrapper(validatorName, validator, ngModel){
var lastValid;
return function (modelValue){
var result = validator(modelValue);
if(result) {
lastValid = modelValue;
}else{
// what to do here? maybe asign the value like this:
// $parse(attrs.ngModel).assign(scope, lastValid);
}
return result;
};
}
But I'm not sure how to continue with this approach either. Can I set the model value without AngularJS kicking in and try to validate that newly set value?
I have created a simple directive that serves as a wrapper on the ng-model directive and will keep always the latest valid model value. It's called valid-ng-model and should replace the usage of ng-model on places where you want to have the latest valid value.
I've created an example use case here, I hope you will like it. Any ideas for improvements are welcomed.
This is the implementation code for valid-ng-model directive.
app.directive('validNgModel', function ($compile) {
return {
terminal: true,
priority: 1000,
scope: {
validNgModel: '=validNgModel'
},
link: function link(scope, element, attrs) {
// NOTE: add ngModel directive with custom model defined on the isolate scope
scope.customNgModel = angular.copy(scope.validNgModel);
element.attr('ng-model', 'customNgModel');
element.removeAttr('valid-ng-model');
// NOTE: recompile the element without this directive
var compiledElement = $compile(element)(scope);
var ngModelCtrl = compiledElement.controller('ngModel');
// NOTE: Synchronizing (inner ngModel -> outside valid model)
scope.$watch('customNgModel', function (newModelValue) {
if (ngModelCtrl.$valid) {
scope.validNgModel = newModelValue;
}
});
// NOTE: Synchronizing (outside model -> inner ngModel)
scope.$watch('validNgModel', function (newOutsideModelValue) {
scope.customNgModel = newOutsideModelValue;
});
}
};
});
Edit: directive implementation without isolate scope: Plunker.
Since you are sending the entire object for each field modification, you have to keep the last valid state of that entire object somewhere. Use case I have in mind:
You have a valid object { name: 'Valid', email: 'Valid' }.
You change the name to invalid; the autosave directive placed at the name input knows its own last valid value, so the correct object gets sent.
You change the email to invalid too. The autosave directive placed at the email input knows its own last valid value but NOT that of name. If the last known good values are not centralized, an object like { name: 'inalid', email: 'Valid' } will be sent.
So the suggestion:
Keep a sanitized copy of the object you are editing. By sanitized I mean that any invalid initial values should be replaced by valid pristine ones (e.g. zeros, nulls etc). Expose that copy as a controller member, e.g. fooCtrl.lastKnowngood.
Let autosave know the last known good state, e.g. as:
<input ng-model="fooCtrl.email" autosave="fooCtrl.lastKnowngood" required />
Keep the last known good local value in that object; utilize the ng-model expression, e.g. as:
var lastKnownGoodExpr = $parse(attrs.autosave);
var modelExpr = $parse(attrs.ngModel);
function saveIfModelChanged () {
var lastKnownGood = lastKnownGoodExpr(scope);
if (ngModel.$valid) {
// trick here; explanation later
modelExpr.assign({fooCtrl: lastKnownGood}, ngModel.$modelValue);
}
// send the lastKnownGood object to the server!!!
}
Send the lastKnownGood object.
The trick, its shortcomings and how can it be improved: When setting the local model value to the lastKnownGood object you use a context object different than the current scope; this object assumes that the controller is called fooCtrl (see the line modelExpr.assign({fooCtrl: lastKnownGood}, ...)). If you want a more general directive, you may want to pass the root as a different attribute, e.g.:
<input ng-model="fooCtrl.email" autosave="fooCtrl.lastKnowngood" required
autosave-fake-root="fooCtrl" />
You may also do some parsing of the ng-model expression yourself to determine the first component, e.g. substring 0 → 1st occurence of the dot (again simplistic).
Another shortcoming is how you handle more complex paths (in the general case), e.g. fooCtrl.persons[13].address['home'].street - but that seems not to be your use case.
By the way, this:
ngModel.$viewChangeListeners.push(function () {
saveIfModelChanged();
});
can be simplified as:
ngModel.$viewChangeListeners.push(saveIfModelChanged);
Angular default validators will only assign value to model if its valid email address.To overcome that you will need to override default validators.
For more reference see : https://docs.angularjs.org/guide/forms#modifying-built-in-validators
You can create a directive that will assign invalide model value to some scope variable and then you can use it.
I have created a small demo for email validation but you can extend it to cover all other validator.
Here is fiddle : http://plnkr.co/edit/EwuyRI5uGlrGfyGxOibl?p=preview

ngModel and How it is Used

I am just getting started with angular and ran into the directive below. I read a few tutorials already and am reading some now, but I really don't understand what "require: ngModel" does, mainly because I have no idea what ngModel does overall. Now, if I am not insane, it's the same directive that provides two way binding (the whole $scope.blah = "blah blah" inside ctrl, and then {{blah}} to show 'blah blah' inside an html element controlled by directive.
That doesn't help me here. Furthermore, I don't understand what "model: '#ngModel' does. #ngModel implies a variable on the parents scope, but ngModel isn't a variable there.
tl;dr:
What does "require: ngModel" do?
What does "model : '#ngModel'" do?
*auth is a service that passes profile's dateFormat property (irrelevant to q)
Thanks in advance for any help.
angular.module('app').directive('directiveDate', function($filter, auth) {
return {
require: 'ngModel',
scope: {
model : '#ngModel',
search: '=?search'
},
restrict: 'E',
replace: true,
template: '<span>{{ search }}</span>',
link: function($scope) {
$scope.set = function () {
$scope.text = $filter('date')($scope.model, auth.profile.dateFormat );
$scope.search = $scope.text;
};
$scope.$watch( function(){ return $scope.model; }, function () {
$scope.set();
}, true );
//update if locale changes
$scope.$on('$localeChangeSuccess', function () {
$scope.set();
});
}
};
});
ngModel is an Angular directive responsible for data-binding. Through its controller, ngModelController, it's possible to create directives that render and/or update the model.
Take a look at the following code. It's a very simple numeric up and down control. Its job is to render the model and update it when the user clicks on the + and - buttons.
app.directive('numberInput', function() {
return {
require: 'ngModel',
restrict: 'E',
template: '<span></span><button>+</button><button>-</button>',
link: function(scope, element, attrs, ngModelCtrl) {
var span = element.find('span'),
plusButton = element.find('button').eq(0),
minusButton = element.find('button').eq(1);
ngModelCtrl.$render = function(value) {
updateValue();
};
plusButton.on('click', function() {
ngModelCtrl.$setViewValue(ngModelCtrl.$modelValue + 1);
updateValue();
});
minusButton.on('click', function() {
ngModelCtrl.$setViewValue(ngModelCtrl.$modelValue - 1);
updateValue();
});
function updateValue(value) {
span.html(ngModelCtrl.$modelValue);
}
}
};
});
Working Plunker
Since it interacts with the model, we can use ngModelController. To do that, we use the require option to tell Angular we want it to inject that controller into the link function as its fourth argument. Now, ngModelController has a vast API and I won't get into much detail here. All we need for this example are two methods, $render and $setViewValue, and one property, $modelValue.
$render and $setViewValue are two ways of the same road. $render is called by Angular every time the model changes elsewhere so the directive can (re)render it, and $setViewValue should be called by the directive every time the user does something that should change the model's value. And $modelValue is the current value of the model. The rest of the code is pretty much self-explanatory.
Finally, ngModelController has an arguably shortcoming: it doesn't work well with "reference" types (arrays, objects, etc). So if you have a directive that binds to, say, an array, and that array later changes (for instance, an item is added), Angular won't call $render and the directive won't know it should update the model representation. The same is true if your directive adds/removes an item to/from the array and call $setViewValue: Angular won't update the model because it'll think nothing has changed (although the array's content has changed, its reference remains the same).
This should get you started. I suggest that you read the ngModelController documentation and the official guide on directives so you can understand better how this all works.
P.S: The directive you have posted above isn't using ngModelController at all, so the require: 'ngModel' line is useless. It's simply accessing the ng-model attribute to get its value.

How to implement an ng-change for a custom directive

I have a directive with a template like
<div>
<div ng-repeat="item in items" ng-click="updateModel(item)">
<div>
My directive is declared as:
return {
templateUrl: '...',
restrict: 'E',
require: '^ngModel',
scope: {
items: '=',
ngModel: '=',
ngChange: '&'
},
link: function postLink(scope, element, attrs)
{
scope.updateModel = function(item)
{
scope.ngModel = item;
scope.ngChange();
}
}
}
I would like to have ng-change called when an item is clicked and the value of foo has been changed already.
That is, if my directive is implemented as:
<my-directive items=items ng-model="foo" ng-change="bar(foo)"></my-directive>
I would expect to call bar when the value of foo has been updated.
With code given above, ngChange is successfully called, but it is called with the old value of foo instead of the new updated value.
One way to solve the problem is to call ngChange inside a timeout to execute it at some point in the future, when the value of foo has been already changed. But this solution make me loose control over the order in which things are supposed to be executed and I assume that there should be a more elegant solution.
I could also use a watcher over foo in the parent scope, but this solution doesn't really give an ngChange method to be implmented and I have been told that watchers are great memory consumers.
Is there a way to make ngChange be executed synchronously without a timeout or a watcher?
Example: http://plnkr.co/edit/8H6QDO8OYiOyOx8efhyJ?p=preview
If you require ngModel you can just call $setViewValue on the ngModelController, which implicitly evaluates ng-change. The fourth parameter to the linking function should be the ngModelCtrl. The following code will make ng-change work for your directive.
link : function(scope, element, attrs, ngModelCtrl){
scope.updateModel = function(item) {
ngModelCtrl.$setViewValue(item);
}
}
In order for your solution to work, please remove ngChange and ngModel from isolate scope of myDirective.
Here's a plunk: http://plnkr.co/edit/UefUzOo88MwOMkpgeX07?p=preview
tl;dr
In my experience you just need to inherit from the ngModelCtrl. the ng-change expression will be automatically evaluated when you use the method ngModelCtrl.$setViewValue
angular.module("myApp").directive("myDirective", function(){
return {
require:"^ngModel", // this is important,
scope:{
... // put the variables you need here but DO NOT have a variable named ngModel or ngChange
},
link: function(scope, elt, attrs, ctrl){ // ctrl here is the ngModelCtrl
scope.setValue = function(value){
ctrl.$setViewValue(value); // this line will automatically eval your ng-change
};
}
};
});
More precisely
ng-change is evaluated during the ngModelCtrl.$commitViewValue() IF the object reference of your ngModel has changed. the method $commitViewValue() is called automatically by $setViewValue(value, trigger) if you do not use the trigger argument or have not precised any ngModelOptions.
I specified that the ng-change would be automatically triggered if the reference of the $viewValue changed. When your ngModel is a string or an int, you don't have to worry about it. If your ngModel is an object and your just changing some of its properties, then $setViewValue will not eval ngChange.
If we take the code example from the start of the post
scope.setValue = function(value){
ctrl.$setViewValue(value); // this line will automatically evalyour ng-change
};
scope.updateValue = function(prop1Value){
var vv = ctrl.$viewValue;
vv.prop1 = prop1Value;
ctrl.$setViewValue(vv); // this line won't eval the ng-change expression
};
After some research, it seems that the best approach is to use $timeout(callback, 0).
It automatically launches a $digest cycle just after the callback is executed.
So, in my case, the solution was to use
$timeout(scope.ngChange, 0);
This way, it doesn't matter what is the signature of your callback, it will be executed just as you defined it in the parent scope.
Here is the plunkr with such changes: http://plnkr.co/edit/9MGptJpSQslk8g8tD2bZ?p=preview
Samuli Ulmanen and lucienBertin's answers nail it, although a bit of further reading in the AngularJS documentation provides further advise on how to handle this (see https://docs.angularjs.org/api/ng/type/ngModel.NgModelController).
Specifically in the cases where you are passing objects to $setViewValue(myObj). AngularJS Documentatation states:
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.
For my specific case, my model is a moment date object, so I must clone the object first before then calling setViewValue. I am lucky here as moment provides a simple clone method: var b = moment(a);
link : function(scope, elements, attrs, ctrl) {
scope.updateModel = function (value) {
if (ctrl.$viewValue == value) {
var copyOfObject = moment(value);
ctrl.$setViewValue(copyOfObject);
}
else
{
ctrl.$setViewValue(value);
}
};
}
The fundamental issue here is that the underlying model does not get updated until the digest cycle that happens after scope.updateModel has finished executing. If the ngChange function requires details of the update that is being made then those details can be made available explicitly to ngChange, rather than relying on the model updating having been previously applied.
This can be done by providing a map of local variable names to values when calling ngChange. In this scenario, you can mapping the new value of the model to a name which can be referenced in the ng-change expression.
For example:
scope.updateModel = function(item)
{
scope.ngModel = item;
scope.ngChange({newValue: item});
}
In the HTML:
<my-directive ng-model="foo" items=items ng-change="bar(newValue)"></my-directive>
See: http://plnkr.co/edit/4CQBEV1S2wFFwKWbWec3?p=preview

AngularJS setting model value from directive and calling a parent scope function holds on to the previous value inside that function

js fiddle http://jsfiddle.net/suras/JzaV9/4/
This is my directive
'use strict';
barterApp.directive('autosuggest', function($timeout, $http) {
return {
restrict: "E",
scope: {
modelupdate:"=",
suggestions:"=",
urlsend:"#"
},
template: '<ul><li ng-repeat="suggest in suggestions" ng-click="updateModel(suggest)">{{suggest}}</li></ul>',
link: function (scope, element) {
scope.$watch('modelupdate', function() {
$timeout(function(){
$http.post(scope.urlsend, {q:scope.modelupdate}).then(function(data){
scope.suggestions = data.data;
console.log(data.data);
});
}, 3000);
});
scope.updateModel = function(value){
scope.modelupdate = value;
scope.$parent.getBookInfo();
}
}
};
});
controller is
barterApp.controller('openLibraryCtrl', ['$scope','$http',function ($scope,$http) {
$scope.title = "";
$scope.getBookInfo = function(value){
if($scope.title == "" || $scope.title == " ") //here title is 'r'(previous value)
{
return;
}
$http.get('/book_info.json?q='+$scope.title).then(function(res){
if(Object.keys(res).length !== 0)
{
data = res.data
console.log(data);
}
});
}
//here title is 'rails' (updated value from directive).
//( used a watch function here on model update
// and checked it but inside getBookInfo it is still 'r' )
}]);
in the update model function i set the model value and call the getBookInfo function on parent scope. but the thing here is when (this is a autocomplete) i enter the value in a input field that contains ng-model say for example 'r' then triggers the watch and i get suggestions from a post url (lets say "rails", "rock") and show it through the template as in the directive. when i click one of the suggestions (say 'rails') it triggers the updatemodel function in directive and sets the model value. its fine upto this but when i call the getBookInfo function in parent scope then $scope.title is 'r' inside the function (i checked with console log outside the function the model value was updated correctly as 'rails' ). again when i click 'rock' the model value inside getBookInfo is 'rails'.
i have no clue whats going on. (i also tested with watch function in controller the model gets updated correctly but the function call to getBookInfo holds back to the previous value)
view
<form ng-controller="openLibraryController">
<input type="text" ng-model="title" id="title" name="book[title]" />
<autosuggest modelupdate = "title" suggestions = "book_suggestions" urlsend="/book_suggestions.json"> </autosuggest>
</form>
I didn't look deep into it, but I suspect (with a high degree of confidence) that the parent scope has not been updated at the time of calling getBookInfo() (since we are still in the middle of a $digest cycle).
Not-so-good Solution 1:
You could immediately update the parent scope as well (e.g. scope.$parent.title = ...), but this is clearly a bad idea (for the same reasons as nr 2, but even more so).
Not-so-good Solution 2:
You could pass the new title as a parameter to getBookInfo().
Both solutions result in mixing controller code with directive code and creating a tight coupling between your components, which as a result become less reusable and less testable.
Not-so-bad Solution:
You could watch over the title and call getBookInfo() whenever it changes:
$scope.$watch('title', function (newValue, oldValue) {
getBookInfo();
});
This would be fine, except for the fact that it is totally unnecessary.
Better Solution:
Angular is supposed to take care of all that keep-in-sync stuff for us and it actually does. You don't have given much context on what is the purpose of calling getBookInfo(), but I am guessing you intend to update the view with some info on the selected book.
In that case you could just bind it to an element (using ng-bind) and Angular will make sure it is executed properly and timely.
E.g.:
<div>Book info: <span ng-bind="getBookInfo()"></span></div>
Further more, the autosuggest directive doesn't have to know anything about it. It should only care about displaying suggestions, manipulating the DOM (if necessary) and updating the specified model property (e.g. title) whenever a suggestion is clicked. What you do with the updated value should be none of its business.
(BTW, ideally the suggestions should be provided by a service.)
Below is a modified example (based on your code) that solves the problem. As stated above there are several methods of solving the problem, I just feel this one tobe cleaner and more aligned to the "Angular way":
Book title: <input type="text" ng-model="book.title" />
<autosuggest modelupdate="book.title"
suggestions="book.suggest()"></autosuggest>
Book info: <span ng-bind="book.getInfo()"></span>
Just by looking at the HTML (without knowing what is in JS), one can easily tell what is going on:
There is a text-field bound to book.title.
There is a custom autosuggest thingy that offers suggestions provided by book.suggest() and updates book.title.
There is a span that displays info about the book.
The corresponding directive looks like this:
app.directive('autosuggest', function() {
return {
restrict: 'E',
scope: {
modelupdate: '=',
suggestions: '&'
},
template:
'<ul><li ng-repeat="suggest in suggestions()" ' +
'ng-click="modelupdated = suggest">' +
'{{suggest}}</li></ul>'
};
});
As you can see, all the directive knows about is how to retrieve suggestions and what to update.
Note that the same directive can be used with any type of "suggestables" (even ones that don't have getBookInfo()); just pass in the right attributes (modelupdated, suggestions).
Note also, that we could remove the autosuggest element and the app would continue to work as expected (no suggestions of cource) without any further modification in HTML or JS (while in your version the book info would have stopped updating).
You can find the full version of this short demo here.

Resources