AngularJS - Isolate scope binding to parent scope - angularjs

If I have an html element in my "parent" page like so:
<div ng-include"'Go-To-Child-Page.html'" />
Any my child/include page is like so:
<some-directive two-way-binding="$parent.SomeParentScope"></some-directive>
Why is this not working for my directive? Or better yet, how do I make it work?
app.directive ('someDirective', function(){
return {
retrict: 'E',
replace: true,
scope: {
myBinding : "=twoWayBinding", <- this is what is not working
},
template: '<select ng-model="myBinding" ng-options="myType.Description for myType in myTypes"></select>'
};
}
Edit Update:
Why did I post this question?
After completing a very lengthy form, I immediately noticed how I had quite a number of similar controls that the coder in me said I should abstract out. One of those was the select control. Two scenarios were involved with this control:
(1) Where the user had to choose a filter before the select control was populated; and
(2) Where the code pre-defined the filter for the select control.
The solutions for both those scenarios are shown below. I hope this helps everyone because I truly enjoy using Angular and the directive functionality it provides to create "Html-magic" is amazing.

You seem to be doing a lot of things unnecessarily, but that might be because I'm misunderstanding your goal.
I've fixed your plnkr here: http://plnkr.co/edit/FqpzC5p1Ogm4attYiArV?p=preview
The basic changes that were necessary seem to be:
Pass the selected filter (Rear/Front) into your directive
Replace ngOptions with ngRepeat and a filter
There's really no need for your directive to have a controller (and generally most directive's should use a linker function). I stripped out some bits to make it simpler, but you can still wire up $scope.filterTypes as you were (pulling available Types from $scope.myTypes) and it'll still work the same.
Update
Since you didn't spell out all of your requirements, I may be missing some, but this implementation is what I gathered you are looking for:
http://plnkr.co/edit/pHgJ84Z35q5jxCpq3FHC?p=preview
It's got dynamic filtering, it's not unnecessarily using a controller, it's got two-way binding. The only problem is that it's referencing the "Description" field (as your original was). You can work that in to be configurable in HTML if you like.

Scenario 1 : Let the user filter:
Filter:
<input type="radio" ng-model="choice.type" value="Rear"> Rear
<input type="radio" ng-model="choice.type" value="Front"> Front
<br>
Select:
<name-value-select selected-item="selected.item" choice="choice.type" options="axleTypes"></name-value-select>
Scenario 2 : pre-filter in code:
<name-value-select preselected-filter="Front" selected-item="selected.item" options="axleTypes"></name-value-select>
The directive for both scenarios:
.directive('nameValueSelect', function () {
return {
replace: true,
restrict: "E",
scope: {
selectedItem: "=",
choice: "=",
options: "=",
preselectedFilter: "#"
},
controller: function ($scope) {
$scope.$watch('choice', function (selectedType) {
$scope.selectedItem = ''; // clear selection
$scope.filterTypes = [];
angular.forEach($scope.options, function (type) {
if ($scope.preselectedFilter !== undefined) {
selectedType = $scope.preselectedFilter;
}
if (type.Type === selectedType) {
this.push(type);
}
}, $scope.filterTypes);
$scope.filterTypes.sort(function (a, b) {
return a.Description.localeCompare(b.Description);
});
});
},
template: '<select ng-model="selectedItem" ng-options="o.Description for o in filterTypes"><option value="" selected="selected">Please Select </option></select>'
};
});
And the proverbial obligatory plunker:
http://plnkr.co/edit/tnXgPKADfr5Okvj8oV2S

Related

directive's scope inside ng-repeat angularjs

I'm trying to understand directive's scope inside ng-repeat, still wondering if it comes from ng-repeat but it looks like.
Here is my code
directive.js
myApp.directive('dirSample', function () {
return {
template: '<input type="text" ng-model="name" />',
replace: true,
restrict: 'AE'
}
});
mainController.js
angular.controller('mainController',function($scope){
$scope.name = 'name'
});
index.htm
<div ng-repeat="i in [1, 2, 3, 4]">
<dir-sample></dir-sample>
</div>
<dir-sample></dir-sample>
<dir-sample></dir-sample>
When i make a change in one of the last two directives (which are not inside ng-repeat) it works well, changes on one are reflected on the other.
Problem :
1 - if i change an input value of a directive generated by ng-repeat , changes are not reflected anywhere else.
2 - if i change value of input on one of the two last directives , the directives inside ng-repeat change too, but if touch ( change input value ) of any directive , changes will not be reflected on that directive but will keep being reflected on the other directives.
Can someone please explain why the scope has that behavior ?
Thanks.
Binding primitives is tricky, as is explained here: Understanding scopes. It has to with how Javascript works. Your 'name' variable will get shadowed once it is altered within the ng-repeat block. The suggested fix (from the link above):
This issue with primitives can be easily avoided by following the
"best practice" of always have a '.' in your ng-models
They also provide a link to a video explaining exactly this problem: AngularJS MTV Meetup
So a fix looks like this:
app.controller('mainController',function($scope){
$scope.attr= {}
$scope.attr.name = 'name'
});
app.directive('dirSample', function () {
return {
template: '<input type="text" ng-model="attr.name" />',
replace: true,
restrict: 'AE'
}
});

Is one of these the more "Angular" way of communicating with a directive?

I asked this question on the Programmer's stack exchange, but didn't get any replies, so I thought I'd try my luck here...
I am working on a project where I would like to encapsulate a directive library and distribute it to other developers to use. I would like to keep the changes to the model within this encapsulated code, so I don't really want the dev's changing the scope variables outside of the lib.
In my code, I have 2 different approaches to communicating with my lib from the parent controller.
The first theory is to create a lib that contains a directive and a service. The parent controller would call the service, which would handle all of the changes to the lib model and the directive would react depending on these changes.
The second theory is to put all the functions to change the model in the directive itself and call the directive scope on the parent to make the changes.
Here is a plunker that shows what I'm asking in better detail. It's a simple example, but illustrates the 2 different methods.
http://plnkr.co/edit/CR350Vx7NiHs5tkjNWZL?p=preview
I'm leaning towards the second method as it just seems cleaner to implement from a development scenario.
Any advice from the Angular experts out there?
Plunker Html:
<body ng-app="myApp">
This is Example 1 - Using a service to modify directive
<div ng-controller="Example1Ctrl">
<example1-directive ng-model='example1Model'></example1-directive>
<br>
<br>
<input type="button" value="Change Example 1" ng-click='changeExample1()' />
</div>
<br>
<br>
This is Example 2 - Modifying directive in the scope of the directive
<div ng-controller="Example2Ctrl">
<example2-directive ng-model='example2Model'></example2-directive>
<br>
<br>
<input type="button" value="Change Example 2" ng-click='changeExample2()' />
</div>
</body>
Plunker js
var app = angular.module("myApp", []);
//--------------------------------------------------
//-------- This is example 1
//--------------------------------------------------
app.controller("Example1Ctrl", function($scope, example1Svc) {
$scope.example1Model = {
value: "Example 1 - Original Value"
}
$scope.changeExample1 = function() {
example1Svc.change($scope.example1Model, "Example 1 - Changed Value");
}
});
/// This part would be encapsulated in a lib
app.directive("example1Directive", function() {
return {
restrict: "E",
scope: {
model: "=ngModel"
},
template: "{{model.value}}"
}
});
app.service("example1Svc", function() {
this.change = function(example1Model, newValue) {
example1Model.value = newValue;
}
})
// End lib
//--------------------------------------------------
//-------- This is example 2
//--------------------------------------------------
app.controller("Example2Ctrl", function($scope, example1Svc) {
$scope.example2Model = {
value: "Example 2 - Original Value"
}
$scope.changeExample2 = function() {
$scope.example2Model.change("Example 2 - Changed Value");
}
});
/// This part would be encapsulated in a lib
app.directive("example2Directive", function() {
return {
restrict: "E",
scope: {
model: "=ngModel"
},
template: "{{model.value}}",
controller: function ($scope) {
$scope.model.change = function(newValue) {
$scope.model.value = newValue;
}
}
}
});
// end lib
I'm somewhat confused by your example #1. What does exampleSvc.change do?
Example #2 definitely goes against MVVM best practice, as it couples the controller with the view. Controllers (of views) should be view-agnostic. They should only change the ViewModel to reflect the current state of the app. The View would then react (however the View chooses to) to changes in the ViewModel.
In particular, these lines "offend" the best practice in my mind:
$scope.model.change = function(newValue) {
$scope.model.value = newValue;
}
Now your controller relies on the view to define what the function does (or whether it is defined to begin with). Also, what if another directive decides to change the .change function?
EDIT:
Take a look at this SO question, and in particular an answer by Mark.
EDIT #2:
There is an interesting case for when some event needs to reach whichever directives or child controllers that could be interested. Use $scope.$broadcast (in controller) and $scope.$on (in directive) to handle. Here's a plunker
I'm going to agree with #New Dev and add a couple more thoughts. If you're building a directive library you don't want to also bundle controllers that the consumer of the library has to use. Your directives should be more or less self contained and provide enough of an api to be extensible and potentially used in other directives.
What does this mean? Your directives may want to define a controller so that they can be injected into other directives. E.g.
//your library
directiveModule.directive("example1Directive", function() {
return {
controller: function($scope, $element, $attrs) {
...
},
...
}
});
-
//application
app.directive("appDirective", function() {
return {
require: '?example1Directive',
link: function(scope, element, attrs, example1Directive) {
...
}
});
You may also want to specify various options that can be set on your directive with parameters e.g.
<div example1-directive="{opt1: 'val', opt2: scopeProp}"></div>
Your directive would then need to parse the the attribute and execute on the scope to generate the options. There's lots more you can do, I'd suggest taking a look at ngmodules.org and see what other people are doing.

How to write re-usable HTML components with AngularJS

New to Angular and, so far, I'm loving it but the learning curve seems pretty steep. What I want to do is wrap up a bit of simple business logic and build some re-usable DOM components to template common areas of my system. Specifically I am writing a simple survey application that has different question types. My goal is to get to the point that while I am in an ng-repeat block I can do something like this:
<div ng-repeat="question in questions">
<show-question></show-question>
</div>
Ideally I want to wrap all of the logic into that one statement to switch on question type and then pull from templateUrl for different HTML sets. So if a question.type = "text" it would pull the templateUrl of "templates/textQuestion.html" and be able to inject scope into that template file as it produces the DOM element.
Big question is, am I going about this correctly AT ALL? Is a directive the way to go, should I even try to do this all in one directive/tag? I am open to being schooled on this!
Small question is, if I am going the right direction, what is the correct implementation?
I have already tried putting some logic inside my directives like IF and SWITCH, but that doesn't appear to be valid.
Any and all (constructive) help is welcome.
Thanks all!
It's called a directive. There's a complete guide here: http://docs.angularjs.org/guide/directive
It allows you to make custom attributes, elements, CSS classes, and comments that turn into components.
I wouldn't pull separate templates for each question type, I'd use a different directive for each question type. Then you can switch between them using a parent directive.
Here is what a directive that loads different directives might look like:
app.directive('question', function($compile){
"use strict";
return{
restrict: 'E',
replace: true,
link: function(scope, element, attrs){
var render = function(){
var template = "";
switch(attrs.type){
case "truefalse":
template = '<truefalse></truefalse>';
break;
case "multiplechoice":
template = '<multiplechoice></multiplechoice>';
break;
case "essay":
template = '<essay></essay>';
break;
}
element.html(template);
$compile(element.contents())(scope);
}
attrs.$observe('type', function(value) {
render();
});
render();
}
};
});
Now you could use this as such:
<question ng-repeat="question in questions" type="question.type" ></question>
Assuming you had a directive for each type of question, you'd get different directives rendered. This is sort of similar to using ng-if or different templates or whatever but I like it more because I also get re-usable one-off components.
So if your scope variable questions has all the info for each question like
$scope.questions = [
{ type: 'input',
prompt: 'name'
}
];
Then you might have some html that looks like
<div question="question" ng-repeat="question in questions"></div>
And have a directive that looks something like
app.directive('question', function() {
return {
scope: {
question: '=' // Creates 2 way data binding with the object you passed in as attribute by the same name
},
link: function($scope, $element, $attrs) {
$scope.question; // This will be equal the object you passed in
// { type: 'input', prompt: 'name' }
// You can modify the dom or whatever here
}
};
});
If you want to have different prepared templates then you can inject the $templateCache into your directive
app.directive('question', function($templateCache) {
and then call them in your link function
link: function($scope, $element, $attrs) {
var template = $templateCache.get('path/to/template.html');
// and append it to the element
$element.append(template);
}
You'll have to play around with it a bit, but that's half the fun. Good luck!

What's the generalizable way to get values out of a directive?

I'm writing a little threaded discussion board in angular. I want to use hallo.js for my inline editor (or something similar, the problem doesn't actually depend on hallo).
Here's the relevant snippet from the template
<div ng-show="post.editing" ng-bind-html="post.body" edit-hallo class="col-xs-8"></div>
<div ng-show="!post.editing" ng-bind-html="post.body" class="col-xs-8"></div>
Here's my directive:
Appl.directive('editHallo', function () {
return {
restrict: 'AC',
scope: true,
link: function(scope, element, attr) {
element
.hallo({
plugins: {
'halloformat': {"bold": true, "italic": true, "strikethrough": true, "underline": true},
'halloheadings': [1,2,3],
'hallojustify' : {},
}
});
element.bind('hallomodified', function(event, data) {
scope.post.body = data.content;
});
}
};
});
This all works just fine, but the hack is right there at the end - when there's a hallomodified event, I manually say, scope.post.body = data.content which not only feels like a hack, it means this only works when there's a post.body item that I'm editing, and therefore doesn't work well if I want to repurpose this for the profile editor or whatever.
So my question is: how should I refactor this so that the relevant two-way binding works? I tried a few things that seemed obvious, such as putting a app-model="post.body" in the div, and then doing an isolate scope with =, but that wasn't getting me anywhere. Ideally, I'd pass in the appropriate model using an ng-model directive, but that seems to have changed sometime between when all the directive examples I found online were created and angular 1.2.0.
There's been some time I don't use AngularJS.
But I think the best way would be to change the scope to something like:
scope:{ngModel:'='}
or
scope:{attribute:'='}
That way it should make a two data binding. One with ng-model on first case, or attribute on second.
Then you can just do this when event happens:
scope.$apply(function(){
scope.ngModel=newValue;
})
The apply will be needed so angular can call digest cycle again and update the view.
More info, I think this can help:
http://docs.angularjs.org/guide/directive

How to update attribute on element via directive using angularjs

I have a pretty simple case in AngularJS where:
<select ng-repeat="el in elms" disabled="disabled" remove-disable>
<option>make a selection</option>
</select>
Initially my select is empty and so I added the disable attr to avoid having people click on it.
When the ajax call is completed and the select renders the list of options I want to remove the disable attribute.
It looks straight forward, right? but all I have seen is approaches using $watch and not for exactly this case.
I'm approaching it from a jQuery point of view where an looking at the DOM after the ajax call, finding the element and removing the attr. like this:
$('select').removeAttr('disabled');
Unfortunately I don't want to do jQuery, I want to do it with a directive, since that is what is for. the angular folks say that all DOM manipulations should be done via directives so I will like to know just how.
enrollmentModule.directive('removeDisable', function () {
return {
restrict: 'A',
scope: {
ngModel : '='
},
link: function (scope, element, attrs) {
console.log('no people yet');
if (element[0].complete) {
console.log('element finish rendering');
};
scope.$watch(attrs.ngModel, function () {
console.log('agents arrived');
});
}
};
});
AngularJS has a ngDisabled directive that you can use to make the link between the state of the list and an expression :
<select ng-repeat="el in elms" ng-disabled="elms.length == 0">
<option>make a selection</option>
</select>

Resources