Modify views of a UI Router template - angularjs

I'm trying to inject default data into a ui-view template.
$stateProvider.state('foo', {
template: '<div ui-view=bar></div>',
abstract: true,
views: {
bar: {
template: 'test'
}
}
});
$stateProvider.state('foo.whiz', {
// ...
});
This example doesn't work, but I hope it's enough to show you what I mean. So what I'm basically trying to do is, when I enter the state foo.whiz, which has a parent of foo, I'd like foo to inject default data into the bar ui-view. Currently it only seems you can populate named views via the child state (bar#foo)? How can I, essentially, initialize data via the route object?

I would say, there are 4 ways (maybe more) how to pass some stuff from child to parent. There is a working plunker.
Let's have these anchors/targets in the index.html:
<div ui-view="title"></div>
<div ui-view="scopeInheritance"></div>
<div ui-view="factory"></div>
<div ui-view="content"></div>
And this would be the common def of parent state 'foo'
.state('foo', {
url: '/foo',
views: {
'title' : { template: '<h3>foo - parent state title</h3>',},
'scopeInheritance' :
{
template: '<h3 ui-view="inheritance">{{parentModel.greeting}}</h3>',
controller: 'parentCtrl',
},
'factory' : { template: '<h3>{{dataHolder.message}}</h3>',},
'content' : {
template: '<div >Content of foo (parent) <ui-view /></div>',
},
},
})
I. child view replaces parent view completely
In this case, we will define different content for the view 'title' in our child state:
.state('foo.whiz', {
views: {
'title#' : {
template: '<h3>{{resolved}} - child state title</h3>',
controller : 'titleCtrl',
resolve : { resolvedData : function() { return "whiz"; }, },
},
...
So, we are using Absolute Names 'title#' to place our view into root template.
II. scope inheritance, child can fill properties of parents $scope.parentModel reference
.state('foo.whiz', {
views: {
'inheritance' :
{
controller: 'childCtrl',
},
...
Here we declare some model in parent $scope, and due to Scope Inheritance by View Hierarchy Only we can change these values in a child:
// parent declares model
.controller('parentCtrl', function($scope){
$scope.parentModel = { greeting : "Parent greets"};
...
// child changes value and does clean up
.controller('childCtrl', function($scope){
var remember = $scope.parentModel.greeting;
$scope.parentModel.greeting = "Child greets"
$scope.$on("$destroy", function (){$scope.parentModel.greeting = remember;});
...
III. factory as data holder
Simply let's inject some singleton, in angular world service/factory, as a holder of data into $rootScope
// singleton
.factory('dataHolder', function(){
return {};
});
// available everywhere
app.run(
['$rootScope', 'dataHolder',
function($rootScope, dataHolder) {
$rootScope.dataHolder = dataHolder;
...
// child can assign these, while parent will render them
.controller('childCtrl', function($scope){
$scope.dataHolder.message = "Child sent message"
Check that all here
IV. child view is nested in parent:
the most typical with UI-Router
// the anchor is inside of the parents view
.state('foo', {
views: {
'content' : { // the anchor for child
template: '<div >Content of foo (parent) <ui-view /></div>',
},
...
// child inject directily into parent
.state('foo.whiz', {
views: {
'' : {
template: '<h4>Content of whiz (Child)</h4>',
},
...
These are the basic ways how to pass data/view/messages with UI-Router (skipping the eventing system of angular at all)

Related

Update Angular child 1.5 component not working

I am using an Angular 1.5 component to build the below structure:
<parent>
<child active="model.active">
<child active="model.active">
<child active="model.active">
</parent>
My component:
(function() {
'use strict'
angular.module('module').component('child',
{
templateUrl: 'template.html',
bindings: {
active: '<'
},
controllerAs: 'model',
controller: function() {
var model = this;
model.select = function() {
model.deselectAllChildren()(model);
model.active = true;
};
}
});
})();
Parent deselectAllChildren method:
model.deselectAllChildren = function(tabModel) {
model.active = false;
};
When a user selects one of the child components, I want it to be made active and the other children inactive. So the logic makes all children inactive (to clean up previous selections) then makes the selected child active. The parent's model.active property is false by default. This works fine for the first select but subsequent selects don't work as the previously selected children don't deselect as the model.active = false change is not propagating to the child components (because the parent's model.active value is still false - but some of the children model.active equals true. This no doubt is a scope/binding issue; I feel like I need to force push model.active = false down to all children. Any ideas?
How about:
<parent>
<child active="model.active === 0" on-select="model.active=0">
<child active="model.active === 1" on-select="model.active=1">
<child active="model.active === 2" on-select="model.active=2">
</parent>
angular.module('module').component('child',
{
templateUrl: 'template.html',
bindings: {
active: '<',
onSelect: '&'
},
controllerAs: 'model',
controller: function() {
var model = this;
model.select = function() {
model.onSelect();
};
}
});

How can I pass a function with argument to a component?

I want to pass a function from the parent component to the child component and give it an argument that has also been given from the parent component to the child. (showOrHideSub="item.showOrHideSub(item.id)" ) I have tried different ways and it doesn't work.
This is my html (parent component) in which I want to use the child component tag. vm is the controller of this scope:
<li ng-repeat="item in vm.menuItems">
<menu-item-comp id="item.id" showOrHideSub="item.showOrHideSub(item.id)" />
</li>
Here is the child component template. itemVm is the controller of this component:
<div id="{{itemVm.id}}" ng-mouseover="itemVm.showOrHideSub(itemVm.id)">
<div id="itemVm.subId" class="menuItemImgText">{{ itemVm.label }}</div>
Here is the child component js:
module.component('menuItemComp', {
templateUrl: '/webapp/app/components/menu/menuItemComponent.html',
bindings: {
id: '<',
showOrHideSub: '&',
label: '<',
submenuId: '<',
},
controllerAs: 'itemVm',
controller: ['LogService', menuCtrl]
});
function menuCtrl($scope, LogService) {
var itemVm = this;
}
And here is the showOrHideSub() function in the parent controller:
vm.showOrHideSub = function (submenu) {
console.log(submenu);
switch (submenu) {
case 'menuItemDivPositions':
console.log('position');
break;
case 'menuItemDivOther':
console.log('other');
break;
}
}
I know that in directives the way to do it is by object mapping such as showOrHideSub="item.showOrHideSub({item: item.id})" but it doesn't seem to work in component.
If you're working with components, you have to do it the components way.
It looks like you have a hierarchy of components (child / parent).
Functions and attributes inside the parent can be inherited by children using require.
require: {
parent: '^^parentComponent'
}
This way, if the parent defines a function showOrHideSub, the children can call it directly using this.parent.showOrHideSub(xxx)
This is not the only way to solve your issue but this is the right way™ for the architecture you chose.
var parentComponent = {
bindings: {},
controller: ParentController,
template: `
<li ng-repeat="item in $ctrl.menuItems">
<child-component item="item"></child-component>
</li>
`
};
var childComponent = {
bindings: {
item: '<'
},
require: {
parent: '^^parentComponent'
},
controller: ChildController,
template: '<button ng-click="$ctrl.buttonClick($ctrl.item.id);">{{$ctrl.item.name}}</button>'
};
function ParentController() {
this.menuItems = [{id:1, name:"item1"},{id:2, name:"item2"}];
this.showOrHideSub = function(param) {
console.log("parent function called with param: " + param);
}
}
function ChildController() {
var vm = this;
this.buttonClick = function(id) {
vm.parent.showOrHideSub(id);
}
}
angular.module('app', []);
angular.module('app')
.component('parentComponent', parentComponent)
.component('childComponent', childComponent);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.8/angular.min.js"></script>
<div ng-app="app">
<parent-component></parent-component>
</div>
Change 'item' to 'vm' in below code. You are binding the item function 'showOrHideSub(item.id)' which doesn't exist. Here is the updated code.
<li ng-repeat="item in vm.menuItems">
<menu-item-comp id="item.id" showOrHideSub="vm.showOrHideSub(item.id)" />
</li>

ng-include in mdDialog not working

I am trying to use a single modal dialog function to house multiple templates. I send the call to create the dialog box an input and I am trying to call various ng-include files based upon that input. However, it seems that the ng-include files are never called.
Is there something I am missing?
Dialog Call
function showDialog(ev, thisItem, modalType)
{
$mdDialog.show({
controller: 'DialogController',
controllerAs: 'vm',
templateUrl: 'app/main/apps/views/templates.html',
locals:{
modalType : modalType
thisItem : thisItem
},
parent: angular.element(document.body),
targetEvent: ev,
clickOutsideToClose:true,
fullscreen: true
})
.then(function(data) {
vm.selectedRef=data;
// Call to server to update the references
}, function() {
});
};
The template that should be calling the various lower templates
<md-dialog aria-label="" id="marginDialog" class="dialogItem" ng-cloak>
<span ng-if="vm.modalType=='bibEdit'"
ng-include="app/main/apps/views/editReference.tmpl.html">
</span>
<span ng-include="app/main/apps/templates/editMargins.tmpl.html">
</span>
I can confirm that the variables reach the template and are correct and that they are correct in the controller. However, the include files are simply not called.
I've had really mixed results mixing ng-includes with angular material components, so generally I try to avoid it now.
What I would try to do in this case is write a function that determines the correct template url, and then pass that as the dialog config object:
function showDialog(ev, thisItem, modalType)
{
var template;
var getTemplate = function(){
switch(modalType){
case 'one':
template = 'dialog1.html';
break;
case 'two':
template = 'dialog2.html';
break;
}
}();
$mdDialog.show({
controller: 'DialogController',
controllerAs: 'vm',
templateUrl: template,
locals:{
modalType : modalType
thisItem : thisItem
},
parent: angular.element(document.body),
targetEvent: ev,
clickOutsideToClose:true,
fullscreen: true
})
.then(function(data) {
vm.selectedRef=data;
// Call to server to update the references
}, function() {
});
};
Here is a working codepen
Use a directive. That works inside an md-dialog.
(function() {
// Add the directive to the module
angular.module("YourApp").directive('editMargins', EditMarginsFactory);
// Factory Method
function EditMarginsFactory()
{
// Construct and return the instance
return {
restrict: 'E',
scope: false,
templateUrl: "app/main/apps/templates/editMargins.tmpl.html"
};
}
})();
Then, instead of using ng-include in your other template, just do this:
<edit-margins></edit-margins>
Note that the use of scope: false in the directive causes it to use the parent scope, the scope used by the template you added the directive to.

transcluded component cannot access its parent required controller

I have a parent component that is using transclude functionality. In its transcluded part as a default there is the child component:
export class ParentController {
// some logic here
}
angular.module('dmp').component('parentObject', {
bindings: {
},
controller: ParentController,
transclude: true,
templateUrl: 'test/parent.html'
});
}
export class ChildController {
}
angular.module('dmp').component('childObject', {
bindings: {
},
require: {
parentCtrl: '^parentObject'
},
controller: ChildController,
templateUrl: 'test/child.html'
});
}
index.html:
<parent-object>
</parent-object>
parent.html
<div ng-transclude>
<child-object></child-object>
</div>
Note that <child-object> is in the transclude part of parent object
I get the following error:
Controller 'parentObject', required by directive 'childObject', can't be found!
if I make it like this it works as expected but this is not my case.
<parent-object>
<child-object></child-object>
</parent-object>
Thanks
EDIT related to gyc comment.
If I understood correctly I can remove the <div ng-transclude> part and just use the child object without transclusion. This is ok but I want later on to say:
<parent-object>
<some-other-object></some-other-object>
</parent-Object>
And then the <child-object> will be replaced by the <some-other-object>. If I do not use transclusion this will not happen and the <child-object> will remain.
try change this in the childObject definition
require: {
parentController: '^parentObject'
}
maybe parentCtrl is not a defined alias for parentController
camelcased components are dash delimited as dom-elements, so it shoud look like this:
angular.module('dmp').component('childObject', {
bindings: {
},
require: {
parentCtrl: '^parent-object'
},
controller: ChildController,
templateUrl: 'test/child.html'
});

Angularjs pass ui-router controller $scope to data

i want to pass a pageTitle data from ui-router based on a $stateParams and then update the title of the page using a directive
ui-router example:
.state('index.something.detail', {
url: '/{type}/{else}',
views: {
'': {
templateUrl: 'tempalte.html',
controller: function($scope, $stateParams) {
if ($stateParams.type == "param") {
$scope.title = "param";
}
}
}
},
data: {
pageTitle: 'Something {{title}}', //NOT WORKING
},
ncyBreadcrumb: {
label: 'Sometgin {{title}}', //IT WORKS
parent: 'index.something'
}
})
directive example:
function pageTitle($rootScope, $timeout) {
return {
link: function(scope, element) {
var listener = function(event, toState, toParams, fromState, fromParams) {
// Default title - load on Dashboard 1
var title = 'APP';
// Create your own title pattern
if (toState.data && toState.data.pageTitle) title = 'APP | ' + toState.data.pageTitle;
$timeout(function() {
element.text(title);
});
};
$rootScope.$on('$stateChangeStart', listener);
}
}
};
angular-breadcrumb pass the data ok
Something param
but in the pageTitle directive i get
Something {{title}}
how can i pass the $scope of the ui-router state?
Thanks!
We cannot pass this via data setting. The reason is, that the string param in data {} is just a string, not a view template. We would need to compile it and pass a scope.
But there is a better solution. Check this working example
Let's have index.html like this:
<H1><ui-view name="title" /></H1>
<ul>
<li><a ui-sref="parent">parent</a>
<li><a ui-sref="parent.child">parent.child</a>
</ul>
<div ui-view=""></div>
And our state can now inject stuff into two locations:
.state('state name', {
...
views : {
"" : {
// default view
}
"title" : {
template : "parent is setting title inside of index.html"
}
}
})
Check this here in action
Also, observe the UI-Router default sample application (doing the same trick) here:
http://angular-ui.github.io/ui-router/sample/#/
The most important part of the code is Contact state def, there is a small snippet:
// You can have unlimited children within a state. Here is a second child
// state within the 'contacts' parent state.
.state('contacts.detail', {
...
views: {
// So this one is targeting the unnamed view within the parent state's template.
'': {
...
},
// This one is targeting the ui-view="hint" within the unnamed root, aka index.html.
// This shows off how you could populate *any* view within *any* ancestor state.
'hint#': {
template: 'This is contacts.detail populating the "hint" ui-view'
},
// This one is targeting the ui-view="menuTip" within the parent state's template.
'menuTip': {

Resources