Chaining expression binding across isolated scopes in AngularJS - javascript

How can I pass a function down through two directives with isolated scopes using expression binding?
I have two directives in a parent-child relationship:
app.directive('parentDir', function(){
return {
scope:{
parentData:'=',
changeRouteParent:'&'
},
templateUrl:'mydir/parentTemplate.html'
};
});
app.directive('childDir', function(){
return {
scope:{
dirData:'=',
changeRoute:'&'
},
templateUrl: 'mydir/childTemplate.html'
};
});
In parent template:
<div class="parentDirClass">
<a ng-click="changeRouteParent({newFoo:parentData.url})>fooLink</a>
<div child-dir ng-repeat="child in parentData.childData" dir-data="child" change-route="???"></div>
</div>
In child template:
<div class="childDirClass">
<a ng-click="changeRoute=({{newFoo:dirData.url}})">foolink</a>
</div>
Trying to use the directives together:
app.controller('exampleController', function($scope, $state){
$scope.changeRoute = function(newFoo) {
$state.go(newFoo);
};
$scope.theParentData = pData;
});
//parent data
pdata = {url:'/fooUrl',
childData:[{url:'/whatever1'},{url:'/whatever2'}, {url:'/whatever3'}]};
HTML:
<div ng-controller="exampleController">
<div parent-dir parent-data="pData" change-route-parent="changeRoute(newFoo???)"></div>
</div>
With one level of expression binding the solution seems easy as you can easily identify the variables being passed (changeRoute=({{newFoo:dirData.url}}) in the child directive) but then moving up a level I have no variable to pass. How can I continue to pass dirData.url up the chain into the example controller to call changeRoute ?

After some tinkering I found chaining can be achieved as follows --
When a bound expression is used in a template at the most-child directive:
childBoundExpression({variableFromChild:variableinIsolatedScope})
Then, starting from the parent of the most-child directive:
<div childDirective child-bound-expression="parentBoundExpression({variableToParent:variableFromChild}) ></div>
For the nth parent use the above expression and repeat until you reach the top directive/html. Eventually the name of variableToParent must be the variable that will be passed to the function on your most-parent $scope
The most important bit is that if you are passing the same variable from child to parent variableToParent and variableFromChild must be the same name.

Related

AngularJs call a scoped directive in iteself with ng-repeat doesn't take the new scope of the child

The problem is that i have called Directive1 in same Directive1 using ng-repeat , and the directive11 has a value in scope , but when calling the nested same directive with the new value, it seems that take the first value.
i tried to call the same directive inside a ng-repeat of the dirctive
in html i have this:
<accordion close-others="true">
<node function="functionCtrller(item)"
ng-repeat="item in list"
ng-model="item">
</node>
</accordion>
in my directive definition i have:
myApp.directive('node', function($compile) {
return {
restrict : 'E',
terminal : true,
scope : {
node : '=ngModel',
function: '&function'
},
link : function($scope, $element) {
if (angular.isArray($scope.node.children) && $scope.node.children.length > 0) {
$element.append('<accordion close-others="true"><node function="function(item)" ng-repeat="item in node.children" ng-model="item"></node></accordion>');
}else{
//....
}
$compile($element.contents())($scope);
} //end_link_directive
}; //end_return_directive
there isn't errors , only the new call of the function doesn't take the new item from the ng-repeat
Check out this link, it does a great job of explaining how to do what you're trying to do: https://weblogs.asp.net/dwahlin/creating-custom-angularjs-directives-part-3-isolate-scope-and-function-parameters
A quick break down is:
<!-- Passing in the function with the outer item object passed -->
<node function="functionController(item)" ...>
And then inside you're doing:
<!-- Inner node passing the function which it was passed -->
<node function="function(item)" ...>
But this is just calling the outer function without passing the appropriate information down through the directive scope function. What you want is something like this, where the inner directive knows what parameters need to be passed to it.
<!-- Please call the outer function using the parameter name item with my value item-->
<node function="function({item:item})" ... >
In the examples on the page, both take advantage of not using the real passed scope function but instead an inner function. For example:
<node function="doFunction(item)" ...>
$scope.doFunction(item) {
$scope.function({item: item});
}

ng-include doesn't render partial view everytime using $scope

inside view I'm conditionally rendering html using ng-include and ng-if
<div ng-controller="myController">
<div ng-if="myProperty == 1">
<div ng-include="'view1.html'"></div>
</div>
<div ng-if="myProperty == 2">
<div ng-include="'view2.html'"></div>
</div>
</div>
and inside controller I have $scope.myProperty which receive value inside controller using $scope injection from other js object. On this controller I have also callback function which updates $scope.myProperty every x seconds.
app.controller('myController', function ($scope) {
...
$scope.myProperty = 0; //init value
function callback() {
$scope.$apply(); // force update view
// correctly write myProperty value on every data change
console.log($scope.myProperty);
}
var otherJsObject= new myObject($scope, callback);
otherJsObject.work();
...
}
callback function correctly change myProperty value but it doesn't update inside view every time.
update:
$scope.bindUIProp = { a: $scope.myProperty};
function callback() {
$scope.$apply();
$scope.bindUIProp.a = $scope.myProperty;
console.log('Callback ' + $scope.myProperty);
console.log('drawstage ' + $scope.bindUIProp.a);
}
var otherJsObject= new myObject($scope, callback);
otherJsObject.work();
and inside view I used object property
<div ng-controller="myController">
<div ng-if="bindUIProp.a == 1">
<div ng-include="'view1.html'"></div>
</div>
<div ng-if="bindUIProp.a == 2">
<div ng-include="'view2.html'"></div>
</div>
</div>
this approach work every time when page is refreshed, parial view is not updated from view1 to view2 when scope.bindUIProp.a is changed to 2.
Instead of writing to property at root level. Write one level below.
Instead of $scope.myProperty,
use $scope.mp.myProperty
Both ng-if and ng-include create child scopes.
You are having problems due to using a primitive in your main scope. Primitives don't have inheritance so the binding is getting broken in the nested child scopes.
change it to an object:
$scope.myProperty = { someProp: 0};
Personally I rarely use ng-include because of the child scope it creates. I prefer having my own directive if all I want is to include a template.

How in one controller get value from another controller?

Have an angular application. Using ui-select.
<div class="col-md-12" ng-controller="FlatController as flat">
<form ng-submit="flat.createFlat()">
<ui-select ng-model="flat.flatData.typelocal" theme="bootstrap">
<ui-select-match placeholder="Type" id="localtype">
{{ $select.selected.type }}
</ui-select-match>
<ui-select-choices repeat="uitypelocal.type as uitypelocal in flat.typelocal1 track by $index | filter: $select.search">
<div ng-bind-html="uitypelocal.type | highlight: $select.search"></div>
</ui-select-choices>
</ui-select>
<button class="btn btn-danger">Send</button>
</form>
</div>
In form i have some inputs and selects. Now, i want to have in the select data from another controller. How can i do it?
I have a service:
angular.module('flatService', [])
.factory('Flat', function($http){
var flatFactory = {};
flatFactory.allFlats = function(){
return $http.get('/api/flats');
};
flatFactory.create = function(flatData){
return $http.post('/api/addflat', flatData);
};
return flatFactory;
})
});
If you want to call one controller into another there are five methods available
$rootScope.$emit() and $rootScope.$broadcast()
If Second controller is child ,you can use Parent child communication .
Use Services
Kind of hack - with the help of angular.element()
inject '$controller'
1. $rootScope.$emit() and $rootScope.$broadcast()
Controller and its scope can get destroyed,
but the $rootScope remains across the application, that's why we are taking $rootScope because $rootScope is parent of all scopes .
If you are performing communication from parent to child and even child wants to communicate with its siblings, you can use $broadcast
If you are performing communication from child to parent ,no siblings invovled then you can use $rootScope.$emit
HTML
<body ng-app="myApp">
<div ng-controller="ParentCtrl" class="ng-scope">
// ParentCtrl
<div ng-controller="Sibling1" class="ng-scope">
// Sibling first controller
</div>
<div ng-controller="Sibling2" class="ng-scope">
// Sibling Second controller
<div ng-controller="Child" class="ng-scope">
// Child controller
</div>
</div>
</div>
</body>
Angularjs Code
var app = angular.module('myApp',[]);//We will use it throughout the example
app.controller('Child', function($rootScope) {
$rootScope.$emit('childEmit', 'Child calling parent');
$rootScope.$broadcast('siblingAndParent');
});
app.controller('Sibling1', function($rootScope) {
$rootScope.$on('childEmit', function(event, data) {
console.log(data + ' Inside Sibling one');
});
$rootScope.$on('siblingAndParent', function(event, data) {
console.log('broadcast from child in parent');
});
});
app.controller('Sibling2', function($rootScope) {
$rootScope.$on('childEmit', function(event, data) {
console.log(data + ' Inside Sibling two');
});
$rootScope.$on('siblingAndParent', function(event, data) {
console.log('broadcast from child in parent');
});
});
app.controller('ParentCtrl', function($rootScope) {
$rootScope.$on('childEmit', function(event, data) {
console.log(data + ' Inside parent controller');
});
$rootScope.$on('siblingAndParent', function(event, data) {
console.log('broadcast from child in parent');
});
});
In above code console of $emit 'childEmit' will not call inside child siblings and It will call inside only parent, where $broadcast get called inside siblings and parent as well.This is the place where performance come into a action.$emit is preferrable, if you are using child to parent communication because it skips some dirty checks.
2. If Second controller is child, you can use Child Parent communication
Its one of the best method, If you want to do child parent communication where child wants to communicate with immediate parent then it would not need any kind $broadcast or $emit but if you want to do communication from parent to child then you have to use either service or $broadcast
For example HTML:-
<div ng-controller="ParentCtrl">
<div ng-controller="ChildCtrl">
</div>
</div>
Angularjs
app.controller('ParentCtrl', function($scope) {
$scope.value='Its parent';
});
app.controller('ChildCtrl', function($scope) {
console.log($scope.value);
});
Whenever you are using child to parent communication, Angularjs will search for a variable inside child, If it is not present inside then it will choose to see the values inside parent controller.
3.Use Services
AngularJS supports the concepts of "Seperation of Concerns" using services architecture. Services are javascript functions and are responsible to do a specific tasks only.This makes them an individual entity which is maintainable and testable.Services used to inject using Dependency Injection mecahnism of Angularjs.
Angularjs code:
app.service('communicate',function(){
this.communicateValue='Hello';
});
app.controller('ParentCtrl',function(communicate){//Dependency Injection
console.log(communicate.communicateValue+" Parent World");
});
app.controller('ChildCtrl',function(communicate){//Dependency Injection
console.log(communicate.communicateValue+" Child World");
});
It will give output Hello Child World and Hello Parent World . According to Angular docs of services Singletons – Each component dependent on a service gets a reference to the single instance generated by the service factory.
4.Kind of hack - with the help of angular.element()
This method gets scope() from the element by its Id / unique class.angular.element() method returns element and scope() gives $scope variable of another variable using $scope variable of one controller inside another is not a good practice.
HTML:-
<div id='parent' ng-controller='ParentCtrl'>{{varParent}}
<span ng-click='getValueFromChild()'>Click to get ValueFormChild</span>
<div id='child' ng-controller='childCtrl'>{{varChild}}
<span ng-click='getValueFromParent()'>Click to get ValueFormParent </span>
</div>
</div>
Angularjs:-
app.controller('ParentCtrl',function($scope){
$scope.varParent="Hello Parent";
$scope.getValueFromChild=function(){
var childScope=angular.element('#child').scope();
console.log(childScope.varChild);
}
});
app.controller('CarentCtrl',function($scope){
$scope.varChild="Hello Child";
$scope.getValueFromParent=function(){
var parentScope=angular.element('#parent').scope();
console.log(parentScope.varParent);
}
});
In above code controllers are showing their own value on Html and when you will click on text you will get values in console accordingly.If you click on parent controllers span, browser will console value of child and viceversa.
5.inject '$controller'
You can inject '$controller' service in your parent controller(MessageCtrl) and then instantiate/inject the child controller(DateCtrl) using:
$scope.childController = $controller('childController', { $scope: $scope.$new() });
Now you can access data from your child controller by calling its methods as it is a service.
Let me know if any issue.
The view's scope is determine by it's controller. However, angular allows nested view scope inheritance, meaning that a view nested inside another view has access to the parent view's scope.. for example:
<div ng-controller="OuterCtrl">
<div ng-controller="InnerCtrl">
...
</div>
<div>
the inner div would have access to the InnerCtrl's scope as well as the OuterCtrl's scope, but the outer div would only have access to the OuterCtrl's scope.
If you need data shared between non-related controllers (those that are not related by nested views), then the data should be provided by a service that can be injected into the controllers like:
app.factory('service', function() {
...
return {
data: someData
};
});
app.controller('ctrl1', [... function(..., service) {
$scope.data = service.data;
}]);
app.controller('ctrl2', [... function(..., service) {
$scope.data = service.data;
}]);
use $broadcase or $emit event like:
app.controller('ctrl1', ['$scope', '$rootScope', function($scope, $rootScope) {
$rootScope.$on("CustomEvent", function($event, data){
alert(data);
})
}]);
app.controller('ctrl2', ['$scope', function($scope) {
$scope.$emit("CustomEvent", "SecondControllerValue");
}]);

How to update parent scope in AngularJS?

I tried to update my parent scope from the child controller using two solutions but I can't make it work. apply() didn't work
HTML :
<div ng-controller="ControllerA">
<div ng-show="tab_selected == 1">Content1</div>
<div ng-show="tab_selected == 2">Content2</div>
<div ng-controller="ControllerB">
<span ng-click="updateScope()"></span>
</div>
</div>
JS :
app.controller('ControllerA', ['$scope',
function ($scope) {
$scope.tab_selected = 1;
}]);
app.controller('ControllerB', ['$scope',
function ($scope) {
$scope.updateScope = function(){
$scope.tab_selected = 2;
// $scope.apply(function(){ $scope.tab_selected = 2; });
}
}]);
You can't update primitive attributes in the parent object, only object attributes. So you need to use:
$scope.someObject= {};
$scope.someObject.tab_selected= 2;
Controllers have separate scope but there is a few way to communicate
$broadcast the event to parent scope
use service for two-way data binding i.e. http://plnkr.co/edit/Vqk1Fe?p=preview
with scope inheritance i.e. http://plnkr.co/edit/7wcMnJ?p=preview

AngularJS: Trying to programatically add directives to a view

Here is my plunk: http://plnkr.co/edit/BGD0n6gmDM3kv5akIn4l?p=info
I am trying to make a view factory of sort. Ideally my controller will place a config object into scope that the view will use to render the page. It will be used to build navigation and content. I am stuck while trying to dynamically pass directives/partial view references from this object.
Here is a isolated object from the config in my controller:
$scope.partials = [
{
name: 'Well',
method: 'showWell()',
isVisible: false,
template: '<container-well></container-well>'
}
];
The focus of this question would be the template property. I built directives to function as partial views here.
Here is an example of one of my directives:
myApp.directive('containerWell', function() {
return {
restrict: 'E',
replace: false,
templateUrl: 'containers/well.html',
scope: {}
}
});
And here is the well.html template file:
<div>
<h2 class="special">Well Types</h2>
<div class="well well-cc">
<p>Closed Well</p>
<p>CSS: .well.well-cc</p>
</div>
<div class="well well-cc open">
<p>Open Well</p>
<p>CSS: .well.well-cc.open</p>
</div>
<h3 class="alt">Wells can have different highlights applied with css classes</h3>
<div class="well well-cc highlight-warning">
<p>CSS: .well.well-cc.highlight-warning</p>
</div>
</div>
Here is the code in my view that I am failing with:
<div ng-repeat="partial in partials" ng-bind-html-unsafe="{{partial.template}}"></div>
The generated markup looks like this:
<div class="ng-scope" ng-bind-html-unsafe="<container-well></container-well>" ng-repeat="partial in partials"></div>
Its just basically adding the string tag to the attribute instead of the directive.
Basically, I would like to be able to programatically add directives to my view. I am not sure if what I am trying to do is even possible. I am not confident that passing the string equivalent of the directive is the way to go. I would love some suggestions or even some stern correction if I am being ridiculous; well not too stern, maybe something constructive ;)
Here is my plunk: http://plnkr.co/edit/BGD0n6gmDM3kv5akIn4l?p=info
Thanks,
Jordan
You have to $compile the dynamic template. See the example in the docs. I forked your plunk to demonstrate the case:
http://plnkr.co/edit/WBT9FbZmvp0Xj0LAAPzk?p=preview
The points are:
ng-bind-html-unsafe is unsuitable for this usage.
Create another directive to compile the dynamic template, just as in the example:
Compilation is actually quite easy:
MyApp.directive("myDir", function($compile) {
return {
link: function(scope, elem, attrs) {
scope.$watch(
function(scope) {
return scope.$eval(attrs.myDir);
},
function(value) {
var e = $compile(value)(scope);
elem.contents().remove();
elem.append(e);
}
);
}
};
});
Use it as:
<div ng-repeat="partial in partials">
<div my-dir="partial.template"></div>
</div>

Categories

Resources