Two-way communication between parent directive and child directive in AngularJS - javascript

I need to pass in a value from the parent directive to the child directive, update it, then pass it back. When I pass in the parent function, and create a two-way binding, I get the error:
scope.updateItem is not a function
How can I accomplish this?
Parent directive template:
<div>
<custom-phrases item-to-update="item" update-item="updateItem"></custom-phrases>
</div>
Parent Directive JavaScript:
angular
.module('app')
.directive('itemlist',
function($rootScope, $state) {
return {
restrict: 'EA',
templateUrl: 'directives/cms/itemlist/itemlist.tpl.html',
scope: {
},
link: function(scope, element) {
// This will be called from child directive with updated item
scope.updateItem = function(item) {
console.log('Updating item from itemlist', item);
};
},
};
});
Child Directive JavaScript:
angular
.module('app')
.directive('customPhrases',
function($rootScope) {
return {
restrict: 'AE',
scope: {
itemToUpdate: '=',
updateItem: '=',
},
templateUrl: 'directives/cms/customPhrases/custom_phrases_directive.tpl.html',
link: function(scope, element) {
// do stuff to scope.itemToUpdate...
// then pass it back to parent directive
scope.updateItem(scope.itemToUpdate);
...
Note: I've also tried & to bind a function:
<div>
<custom-phrases item-to-update="item" update-item="updateItem(item)"></custom-phrases>
</div>
Then in child directive, changed this to:
scope: {
itemToUpdate: '=',
updateItem: '&',
},
If I console log scope.updateItem I get
TypeError: scope.updateItem is not a function

index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Nested Directives</title>
<link rel="stylesheet" href="./../../../lib/bootstrap/css/bootstrap.min.css">
<style>
body{
margin: 5px 10px;
}
</style>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.5.8/angular.min.js"></script>
<script src="app.js"></script>
</head>
<body ng-app="app">
<div class="continer">
<my-tabs>
<!-- first pane starts-->
<my-pane title="Home">
<!-- home page contains jumbotron-->
<div class="jumbotron">
<h1>Home Page</h1>
<p>This is a simple hero unit, a simple jumbotron-style component for calling extra attention to featured content or information.</p>
<p><a class="btn btn-primary btn-lg" href="#">Learn more</a></p>
</div>
<!-- home page jumbotron ends-->
</my-pane>
<!-- first pane ends-->
<!-- second pane starts-->
<my-pane title="Login">
<!-- login form start-->
<form class="form-horizontal">
<div class="form-group">
<label for="inputEmail3" class="col-sm-2 control-label">Email</label>
<div class="col-sm-10">
<input type="email" class="form-control" id="inputEmail3" placeholder="Email">
</div>
</div>
<div class="form-group">
<label for="inputPassword3" class="col-sm-2 control-label">Password</label>
<div class="col-sm-10">
<input type="password" class="form-control" id="inputPassword3" placeholder="Password">
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-default">Sign in</button>
</div>
</div>
</form>
<!-- login form ends-->
</my-pane>
<!-- second pane ends-->
</my-tabs>
</div>
</body>
</html>
app.js
var app = angular.module('app', []);
app.directive('myTabs', function() {
return {
restrict: 'E',
transclude: true,
scope: {},
controller: 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 {
// ^^ prefix means that this directive searches for the controller on its parents
require: '^^myTabs',
restrict: 'E',
transclude: true,
scope: {
title: '#'
},
link: function(scope, element, attrs, tabsCtrl) {
tabsCtrl.addPane(scope);
},
templateUrl: 'my-pane.html'
};
});
my-pane.html
<div class="tab-pane" ng-show="selected">
<h4>{{title}}</h4>
<hr/>
<div ng-transclude></div>
</div>
my-tabs.html
<div class="tabbable">
<ul class="nav nav-tabs nav-inverse">
<li ng-repeat="pane in panes" ng-class="{active:pane.selected}">
{{pane.title}}
</li>
</ul>
<div class="tab-content" ng-transclude></div>
</div>

Related

How to add an inner wrapper to an element in angular?

I want an angular diretive to add an inner wrapper to a DOM element. Unfortunately it's not wrapping but replacing the inner part of the element. (see plunker)
So I have this html snippet:
<body ng-app="plunker">
<div class="outer" wrapp-my-content>
<label>Name: </label>
<input type="text" ng-model="name" />
<p>Hello {{name}}</p>
</div>
</body>
The directive should change this into
<body ng-app="plunker">
<div class="outer" wrapp-my-content>
<div class="inner-wrapper">
<label>Name: </label>
<input type="text" ng-model="name" />
<p>Hello {{name}}</p>
</div>
</div>
</body>
But what I get is
<body ng-app="plunker">
<div class="outer" wrapp-my-content>
<div class="inner-wrapper">
</div>
</div>
</body>
Directive Code:
var app = angular.module('plunker', []);
app.directive('wrappMyContent', function() {
return {
restrict: 'A',
transclude: true,
replace: true,
link: function(scope, element) {
var innerWrapper = angular.element('<div class="inner-wrapper" ng-transclude></div>');
element.prepend(innerWrapper);
}
}
});
How can I fix that?
You've mixed up ng-transclude and custom transclude by link:
1. Use template of directive (demo):
var app = angular.module('plunker', []);
//Recommended angular-way
app.directive('wrappMyContent', function() {
return {
restrict: 'A',
transclude: true,
template:'<div class="inner-wrapper" ng-transclude></div>',
link: function(scope, element) {
}
}
});
2. Do transclude by custom link (demo) :
var app = angular.module('plunker', []);
//Equals transclude by your self
app.directive('wrappMyContent', function($compile) {
return {
restrict: 'A',
scope:true,
link: function(scope, element) {
var innerContent = element.html();
var innerWrapper = angular.element('<div class="inner-wrapper"></div>').append(innerContent);
//Do compile work here.
$compile(innerWrapper)(scope.$parent)
element.empty().append(innerWrapper);
}
}
});
Use a template for your '<div class="inner-wrapper" ng-transclude>' part instead of just making an element and prepending it... the ng-transclude directive won't be processed unless it's compiled which the template will be.

Working with Angular Material

This is a small project just for learning purposes.
The objective is that when I click in the + button in front of the name of the game, the AngularJS material popup show up. Its working, but I want it to work for the different games. With a different HTML template correspondent to the game I click.
Here is my project.
https://github.com/hseleiro/myApp
index.html
<div ng-view>
</div>
assets/js/mainApp.js
var stuffApp = angular.module('myApp', ['ngAnimate','ngRoute','ngMaterial','ui.bootstrap.tpls','ui.bootstrap']);
stuffApp.config(function ($routeProvider) {
$routeProvider
// route for the home page
.when('/',
{
templateUrl: 'pages/home.html',
controller: 'mainController',
})
.when('/games',
{
templateUrl: 'pages/games.html',
controller: 'mainController'
})
})
stuffApp.controller('mainController', function ($scope,$mdDialog){
$scope.query = {}
$scope.queryBy = '$'
$scope.games = [
{
"name" : "BloodBorne",
"consola" : "Playstation 4"
},
{
"name" : "Mass Efect 3",
"consola" : "Xbox 360"
},
{
"name" : "Pro Evolution Soccer 6",
"consola" : "Xbox 360"
}
];
$scope.showAdvanced = function(ev) {
$mdDialog.show({
controller: DialogController,
templateUrl: 'pages/dialog1.tmpl.html',
targetEvent: ev,
clickOutsideToClose:true
})
};
function DialogController($scope, $mdDialog) {
$scope.answer = function(answer) {
$mdDialog.hide(answer);
};
}
});
pages/games.html
<div class="container">
<div class="row">
<div class="titulo">
<div class="col-sm-4"></div>
<div class="col-sm-4">
<h1><i class="fa fa-gamepad fa-4x"></i></h1>
</div>
<div class="col-sm-4"></div>
</div>
</div>
<div class="row">
<div class="col-sm-1">
<div class="search_icon">
<h4><i class="fa fa-search fa-4x"></i></h4>
</div>
</div>
<div class="col-sm-11">
<div class="search_bar">
<div><input class="form-control" ng-model="query[queryBy]" /></div>
</div>
</div>
</div>
<hr>
<div>
<table class="table">
<tr>
<th>Nome</th>
<th>Consola</th>
</tr>
<tr ng-repeat="game in games | filter:query">
<td>{{game.name}}<md-button class="md-primary" ng-click="showAdvanced($event)">
<i class="fa fa-plus"></i>
</md-button></td>
<td>{{game.consola}}</td>
</tr>
</table>
</div>
</div>
pages/dialog1.tmpl.html
<md-dialog aria-label="">
<form>
<md-dialog-content class="sticky-container">
<md-subheader class="md-sticky-no-effect"></md-subheader>
<div>
<p>
Test
</p>
<img style="margin: auto; max-width: 100%;" alt="Lush mango tree" src="assets/img/bloodborne.jpg">
<p>
Test
</p>
<p>
Test
</p>
</div>
</md-dialog-content>
<div class="md-actions" layout="row">
<span flex></span>
<md-button ng-click="answer('useful')" class="md-primary">
Close
</md-button>
</div>
</form>
</md-dialog>
Could some one help me ? Thanks
Sounds like you need to parameterize the controller and template you pass to $mdDialog.
For example:
$scope.showAdvanced = function(ev, popupIdentifier) {
$mdDialog.show({
controller: popupIdentifier + 'Controller',
templateUrl: 'pages/' + popupIdentifier + '.tmpl.html',
targetEvent: ev,
clickOutsideToClose:true
})
};
and then simply call the function with whatever popup content you need:
$scope.showAdvanced($event, 'Game1');

angular: calling/triigering directive method from parent scope

in my app I defined a directive which implement a simple slider with a next/prev/goto methods.
The slider then is within an html snipet managed by another controller.
The problem is that the last slide contains a form, so if the submit is ok than I would like to go to the next slide.
In old javascript I would have passed a callback to the submit method in order to apply that callback.
I made the same thing. Is this the best / angular style way to do it?
Javascript (I omitted some detail):
.directive("sfCarousel", function() {
return {
scope: true,
restrict: 'A',
controller: function($scope) {
var slides = $scope.slides = [];
var currentIndex = $scope.currentIndex = -1;
$scope.next = function() {
//mynextfunction...
}
},
link: function(scope, element, attrs) {
console.log("sf-carousel");
}
}
})
.directive("sfCarouselItem", function() {
return {
scope: true,
require: '^sfCarousel',
link: function(scope, element, attrs, sfCarouselController) {
console.log("sf-carousel-item");
sfCarouselController.addSlide(scope);
}
}
})
.controller("mycontroller", ['$scope', function($scope) {
$scope.submit = function (callback) {
//if submit is ok then
callback.apply(null, []);
}
}])
HTML:
<div sf-carousel >
<div sf-carousel-item ng-class="{'active':active}" >
<div>my first slide</div>
<div sf-label="get-started.submit" ng-click="next()" ></div>
</div>
<div sf-carousel-item ng-class="{'active':active}" >
<form>
<!--here my form-->
<button type="submit" ng-click="submit(next)">submit and go</button>
</form>
</div>
<div sf-carousel-item ng-class="{'active':active}" >
<div>my last slide</div>
<!--other things-->
</div>
</div>
You can pass the function that you what to run on the parent controller.
// directive sfCarousel
return {
transclude: true,
template: '<div class="sf-carousel" ng-transclude></div>',
// ...
};
// directive sfCarouselItem
return {
transclude: true,
scope: {
func: '&',
// ...
},
template: '<div class="sf-carousel-item" ng-transclude></div>',
}
// controller html
<div sf-carousel >
<div sf-carousel-item ng-class="{'active':active}" >
<div>my first slide</div>
<div sf-label="get-started.submit" ng-click="next()" ></div>
</div>
<div sf-carousel-item func="next()" ng-class="{'active':active}" >
<form>
<!--here my form-->
<button type="submit" ng-click="func({})">submit and go</button>
</form>
</div>
<div sf-carousel-item ng-class="{'active':active}" >
<div>my last slide</div>
<!--other things-->
</div>
</div>

Create a list of items built using angularjs

I would like to create a list of items built in Directive and share through controllers.
Here is my example in plunker: Example of my code
Here is my code in js:
var app = angular.module('app', []);
app.controller("BrunchesCtrl", function ($scope, $log) {
$scope.brunches = [];
$scope.addNew = function () {
$scope.brunches.push({ address: "" });
};
$scope.$watch('brunch.address', function(value) {
$scope.address = value;
$log.info($scope.address);
});
$scope.$watch(function() { return $scope.brunches.length; }, function() {
$scope.brunches = $scope.brunches.map(function (brunch) {
return brunch;
});
});
});
app.directive('brunchesView', function ($compile) {
return {
restrict: 'E',
templateUrl: 'brunch.html',
controller: 'BrunchesCtrl',
replace: true,
link: function (scope, element, attrs, controller) {
}
};
});
app.directive('businessSubmit', function ($log, $compile, formManagerService) {
return {
restrict: 'AE',
controller: 'BrunchesCtrl',
link: function (scope, element, attrs, controller) {
formManagerService.init();
var hasError;
element.on('click', function (e) {
e.preventDefault();
$log.info("brunches: \n");
$log.info(scope.brunches);
});
}
};
});
Here is an HTML:
<!DOCTYPE html>
<div class="controls">
<a class="btn btn-danger" id="addBrunch" data-ng-click="addNew()">
<i class="icon-plus-sign"></i>
add new...
</a>
</div>
</div>
<brunches-view class="well" data-ng-repeat="brunch in brunches">{{brunch}}</brunches-view>
<br/>
<p class="well">
JSON: {{brunches | json}}
</p>
<div class="control-group">
<div class="controls">
<a class="btn btn-primary" href="#" data-business-submit>
<i class="icon-save"></i>
Save...
</a>
</div>
</div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.0/angular.min.js"></script>
<script src="//netdna.bootstrapcdn.com/bootstrap/3.0.3/js/bootstrap.min.js"></script>
<script src="script.js"></script>
And here is the template:
<div class="fc-border-separate">
<div class="control-group">
<label class="control-label">Address</label>
<div class="controls">
<input type="text" class="span6 m-wrap"
name="Address" data-ng-model="address"
value="{{address}}" />
</div>
</div>
</div>
The final thing I want to save the whole data inside the BusinessSubmit directive.
Help please...
Several issues with your code.
First, your ng-model for the <input> is set to address, but the object you are wanting to bind it to a brunch object that has an address property. Important to realize that ng-repeat will create a child scope for every repeated item
<input data-ng-model="brunch.address"/>
Another issue is you are assigning the parent controller to a directive as well. Important to understand that controllers are singletons so if you use controller more than once , each is a separate instance. Therefore nesting the parent controller in a directive makes no sense.
DEMO- [ng-model] fix
If you want the data shared with other controllers you should set up a service that holds the brunches data by injecting it into whatever controllers will need access

angular.js two directives, second one does not execute

I have two directives defined in an angular.js module. The HTML element that is declared first executes its directive, but the second HTML element that uses the other directive does not execute it.
Given this HTML:
<div ng-app="myApp">
<div ng-controller="PlayersCtrl">
<div primary text="{{primaryText}}"/>
<div secondary text="{{secondaryText}}"/>
</div>
</div>
and this angular.js code:
var myApp = angular.module('myApp', []);
function PlayersCtrl($scope) {
$scope.primaryText = "Players";
$scope.secondaryText = "the best player list";
}
myApp.directive('primary', function(){
return {
scope: {
text: '#'
},
template: '<h1>{{text}}</h1>',
link: function(scope, element, attrs){
console.log('primary directive');
}
};
});
myApp.directive('secondary', function(){
return {
scope: {
text: '#'
},
template: '<h3>{{text}}</h3>',
link: function(scope, element, attrs){
console.log('secondary directive');
}
};
});
The resulting HTML is only the "primary" directive, and the "secondary" directive does not render:
<div ng-app="myApp" class="ng-scope">
<div ng-controller="PlayersCtrl" class="ng-scope">
<div primary="" text="Players" class="ng-isolate-scope ng-scope">
<h1 class="ng-binding">Players</h1>
</div>
</div>
</div>
The console output verifies this as well, as only the "primary directive" text is output.
Then if I switch the order of the primary and secondary elements, the secondary directive is executed and the primary directive is not:
<!-- reversed elements -->
<div secondary text="{{secondaryText}}"/>
<div primary text="{{primaryText}}"/>
<!-- renders this HTML (secondary, no primary) -->
<div ng-app="myApp" class="ng-scope">
<div ng-controller="PlayersCtrl" class="ng-scope">
<div secondary="" text="the best player list" class="ng-isolate-scope ng-scope">
<h3 class="ng-binding">the best player list</h3>
</div>
</div>
</div>
Why is this? What am I doing wrong?
div's are not void elements and require a closing tag.
<div ng-app="myApp">
<div ng-controller="PlayersCtrl">
<div primary text="{{primaryText}}"></div>
<div secondary text="{{secondaryText}}"></div>
</div>
</div>
Example

Categories