UI Router, Passing Data From Parent Controller - angularjs

I'm attempting to refactor a website into Angular through UI-Router. On the parent component I have defined some data on the controller. How do I pass this data to the child nested routed component through UI-Router? UI-Router's resolve only works when you have a binding on data, but I don't know if it's possible to bind the parent controller data to the parent component.
const main = angular.module('main', ['ui.router']);
main.config(function($stateProvider) {
const states = [
{ name: 'parent', url: '/parent', component: 'parent' },
{ name: 'parent.child', url: '/{childUrl}', component: 'child',
resolve: {
data: function($transition$) {
// I want to pass in { name: 'Name1', content: 'Page-Long Content1' }, { name: 'Name2', content: Page-Long Content2' }
// How do I do this?
}
}
}
];
states.forEach(function(state) {
$stateProvider.state(state);
});
});
angular.module('main')
.component('parent', {
template:
'<div ng-repeat="data in $ctrl.data">' +
'<p>{{data.name}}</p>' +
'<a ui-sref="parent.child({ childUrl: data.name })" ui-sref-active="active">Child link</a>' +
'</div>' +
'<ui-view></ui-view>',
controller: function() {
this.data = [
{name: 'Name1', content: 'Page-Long Content1'},
{name: 'Name2', content: 'Page-Long Content2'}
]
}
});
angular.module('main')
.component('child', {
bindings: { data: '<' },
templateUrl: 'link.html',
});

Basically you have an unique identifier of your record in the URL. So by that you can retrieve your data once again from the dataset. For achieving the same, I'd suggest you to put your data in service and then fetch the same data in resolve of your state. Afterward apply an filter over the data and get desired record from it based on childUrl parameter of state.
angular.module('main')
.service('dataService', function(){
var dataService = this;
dataService.getData = getData;
var data = [
{name: 'Name1', content: 'Page-Long Content1'},
{name: 'Name2', content: 'Page-Long Content2'}
];
function getData(){
return data;
}
});
state
const states = [
{ name: 'parent', url: '/parent', component: 'parent' },
{ name: 'parent.child', url: '/{childUrl}', component: 'child',
resolve: {
data: function($transition$, dataService) {
let childUrl = $transition$.params('childUrl');
//in case of API call below will be changed to promise driven code.
return dataService.getData().filter((item) => item.name === childUrl)[0];
}
}
}
];
And inside a parent controller you can fetch data from service directly.
controller: function(dataService) {
this.data = dataService.getData();
}

Related

Dynamic id name

I got this template:
<ion-view view-title="MAP" name="tab-map">
<ion-content has-tabs="true" style="text-align:center;">
<div style="width:100%;height:400px;" ng-attr-id="{{'canvas_map_'+place.id}}"></div>
</ion-content>
</ion-view>
and I'm trying to set div's content dynamically by passing through controller:
angular.module('helloDynamicMap', ['ionic'])
.config(function ($stateProvider, $urlRouterProvider, $ionicConfigProvider) {
$ionicConfigProvider.navBar.alignTitle('center');
$ionicConfigProvider.tabs.position('bottom');
$ionicConfigProvider.backButton.text('').icon('ion-chevron-left');
$ionicConfigProvider.backButton.previousTitleText(false);
$stateProvider
.state('main', {
url: "/main",
controller: "PlacesCtrl",
//abstract: true,
templateUrl: 'templates/main.html',
resolve: {
resultPlaces: function (findPlace, $stateParams) {
return findPlace.all();
}
}
})
.state('place', {
url: "/place/:placeId",
//abstract: true,
templateUrl: 'templates/place-tabs.html',
controller: 'PlaceCtrl',
resolve: {
resultPlace: function (findPlace, $stateParams) {
return findPlace.get($stateParams.placeId);
}
}
})
.state('place.details', {
url: '/details',
views: {
'tab-details': {
templateUrl: 'templates/details.html'
},
}
})
.state('place.map', {
url: '/map',
views: {
'tab-map': {
templateUrl: 'templates/map.html' //,
//controller: "MapCtrl"
}
}
});
$urlRouterProvider.otherwise("/main");
})
.controller('AppCtrl', function ($scope, $state, $rootScope) {
// button back
$scope.goBack = function () {
console.log("back button");
$state.go("main");
}
})
.controller('PlacesCtrl', function ($scope, $rootScope, $state, resultPlaces) {
// Get places
$scope.places = resultPlaces;
// Button back
$scope.goTabs = function () {
$state.go("tab.map");
}
})
.controller('PlaceCtrl', function ($scope, $rootScope, $state, resultPlace) {
// Load place's data in scope's model
$scope.place = resultPlace;
var div = document.getElementById("canvas_map_" + resultPlace.id);
console.log("div", div);
// Create Map
var map;
document.addEventListener("deviceready", function () {
var div = document.getElementById("canvas_map_" + resultPlace.id);
// Initialize the map view
map = plugin.google.maps.Map.getMap(div);
// Wait until the map is ready status.
map.addEventListener(plugin.google.maps.event.MAP_READY, onMapReady);
}, false);
function onMapReady() {
var button = document.getElementById("button_" + resultPlace.id);
button.addEventListener("click", onBtnClicked, false);
}
function onBtnClicked() {
map.showDialog();
}
})
.factory('findPlace', function ($q) {
// list all places
var places = [];
places = [{
id: "1",
name: "Place A",
details: "details for Place A",
latitude: "28.472143",
longitude: "-81.469856"
}, {
id: "2",
name: "Place B",
details: "details for Place B",
latitude: "",
longitude: ""
}, {
id: "3",
name: "Place C",
details: "details for Place C",
latitude: "",
longitude: ""
}];
return {
all: function () {
var dfd = $q.defer();
// resolve promise
dfd.resolve(places);
// return promise
return dfd.promise;
},
get: function (placeId) {
for (var i = 0; i < places.length; i++) {
if (places[i].id == parseInt(placeId)) {
return places[i];
}
}
return null;
}
}
})
This code works perfectly when I use static id name as "canvas_map_01", but doesn't works when I tries to set it through scope variables.
The main question is how to set and handle div elements through dynamic id name:
// Load place's data in scope's model
$scope.place = resultPlace;
var div = document.getElementById("canvas_map_" + resultPlace.id);
console.log("div", div);
My full code is on Github, please somebody can tell me what am I doing wrong?
-Change your html from
ng-attr-id="{{'canvas_map_'+place.id}}"
To
id="{{'canvas_map_'+place.id}}"
-Change your code from
var div = document.getElementById("canvas_map_" + resultPlace.id);
To
var div = angular.element("#canvas_map_" + resultPlace.id);
Than it will work fine

Example of injecting services in Angular 1.5 components

Can anyone give an example on using services with Angular 1.5 components?
I'm trying to inject a service in an Angular 1.5 component, but it doesn't work.
I have a login component like so:
class Login {
constructor($scope, $reactive, $state, myService) {
console.log(myService.somevariable); //doesn't work
}
}
// create a module
export default angular.module(name, [
angularMeteor
]).component(name, {
templateUrl: 'imports/ui/components/${name}/${name}.html',
controllerAs: name,
controller: Login
});
My service looks like this:
angular.module(name).service("myService", function () {
this.somevariable = 'somevalue';
});
I just cant seem to be able to get the service injected in the component.What am I doing wrong?
SOLUTION:
With sebenalern's help, I got it working.
I needed a service to validate an email address using a regular expression. I did it like this:
import angular from 'angular';
import angularMeteor from 'angular-meteor';
class Validator {
validateEmail(email) {
var re = /^(([^<>()\[\]\\.,;:\s#"]+(\.[^<>()\[\]\\.,;:\s#"]+)*)|(".+"))#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
return re.test(email);
}
}
const name = 'validator';
// create a module
export default angular.module(name, [
angularMeteor
])
.service("Validator", Validator);
I then injected the service like so:
import {name as Validator} from '../../../api/services/validator'
class Login {
constructor($scope, $reactive, $state, Validator) {
'ngInject';
this.$state = $state;
$reactive(this).attach($scope);
this.Validator = Validator;
}
login() {
if(this.Validator.validateEmail(this.credentials.email)) {
// email is valid.
}
}
}
const name = 'login';
export default angular.module(name, [
angularMeteor,
Validator
]).component(name, {
templateUrl: `imports/ui/components/${name}/${name}.html`,
controllerAs: name,
controller:Login
})
Hope this helps :)
So one problem I see is you should be using the keyword this inside the constructor
this.$scope = $scope;
Another thing it is probably easier to stay away from classes and use functions:
class Login {
constructor($scope, $reactive, $state, myService) {
console.log(myService.somevariable); //doesn't work
}
}
Becomes:
angular
.module('name')
.service('myService', myService);
function myService () {
this.somevariable = 'somevalue';
}
To me it seems a lot cleaner. Also another thing about ES6 classes is
ES6 Classes are not hoisted, which will break your code if you rely on hoisting
For more info see link.
Now here is the working code I came up with:
First we declare the module:
angular.module('name', []);
Next we register our service and then create the service definition:
angular
.module('name')
.service('myService', myService);
function myService () {
this.somevariable = 'somevalue';
}
Next we do the same procedure for our controller and also we inject $scope and our service into it.
angular
.module('name')
.controller('Login', Login);
function Login($scope, myService) {
$scope.someVar = myService.somevariable;
}
Last I registered our component:
angular
.module('name')
.component('my-html', {
templateUrl: 'my-html.html',
controller: Login
});
And that is it on the javascript side.
Here is my html code:
<!DOCTYPE html>
<html lang="en-us" ng-app='name'>
<head>
<script src="//code.angularjs.org/1.5.0-rc.1/angular.js"></script>
<script src="controller.js"></script>
</head>
<body >
<h ng-controller="Login">{{ someVar }}</h>
</body>
</html>
I hope this helps!!
Here is how I am doing it and it works well. It works fine with classes. I assume you are using TypeScript.
class AdminHomeService {
consignment: IConsignment;
get: () => IConsignment;
constructor() {
this.consignment = new Consignment();
this.consignment.id = 10;
this.consignment.customer = "Customer3";
this.consignment.customerList = [{ id: 1, name: "Customer1" }, { id: 2, name: "Customer2" }, { id: 3, name: "Customer3" }];
this.consignment.shipperList = [{ key: "1", value: "Shipper1" }, { key: "2", value: "Shipper2" }, { key: "3", value: "Shipper3" }];
this.consignment.consigneeList = [{ key: "1", value: "Consignee1" }, { key: "2", value: "Consignee2" }, { key: "3", value: "Consignee3" }];
this.consignment.billingList = [{ key: "1", value: "Billing1" }, { key: "2", value: "Billing2" }, { key: "3", value: "Billing3" }];
this.consignment.carrierList = [{ key: "1", value: "Carrier1" }, { key: "2", value: "Carrier2" }, { key: "3", value: "Carrier3" }];
this.get = () => {
return this.consignment;
}
}
}
class AdminHomeComponentController {
consignment: IConsignment;
selectedCustomer: any;
static $inject = ["adminHomeService"];
constructor(private adminHomeService: AdminHomeService) {
this.consignment = new Consignment();
this.consignment = this.adminHomeService.get();
this.selectedCustomer = {};
this.selectedCustomer.selected = { "name": this.consignment.customer };
}
customerAddClick(): void {
}
}
class AdminHomeComponent implements ng.IComponentOptions {
bindings: any;
controller: any;
templateUrl: string;
$routeConfig: angular.RouteDefinition[];
constructor() {
this.bindings = {
textBinding: "#",
dataBinding: "<",
functionBinding: "&"
};
this.controller = AdminHomeComponentController;
this.templateUrl = "templates/admin.home.html";
//this.$routeConfig = [
// { path: "/admin", name: "AdminHome", component: "adminHome", useAsDefault: true }
//];
}
}
angular.module("adminHome", [])
.component("adminHome", new AdminHomeComponent())
.service("adminHomeService", AdminHomeService);
This post helped me a lot: http://almerosteyn.com/2016/02/angular15-component-typescript

How to pass data from $state.go() to the data parameter in stateprovider in angular?

I am having trouble passing some data to the stateprovider using $state.go(). Here is the sample code that we have been using.
$stateProvider.state('socialform', {
url: "/socialform?webcontent",
templateUrl: "base_template/_Sends.html?",
data: { pageTitle: 'Social & Website Publishing' },
resolve: {
callPreRenderServices: callPreRenderServices
}
});
$scope.isWebContent = function(status) {
if(status) {
$state.go('socialform', {webcontent:true});
}
else {
$state.go('socialform');
}
};
Basically, what we need to be doing is to pass a title variable to $state.go() so that it will replace the pageTitle to whatever is the value of the passed variable.
From the code above to this:
$stateProvider.state('socialform', {
url: "/socialform?webcontent",
templateUrl: "base_template/_Sends.html?",
data: { pageTitle: title },
resolve: {
callPreRenderServices: callPreRenderServices
}
});
$scope.isWebContent = function(status) {
if(status) {
$state.go('socialform', {webcontent:true, title:"some title"});
}
else {
$state.go('socialform', {title:"another title"});
}
};
You could use a service :
module.service('titleService', function() {
this.title = null;
});
// ... inject titleService in the calling controller ...
$scope.isWebContent = function(status) {
if(status) {
titleService.title = 'Some Title'
$state.go('socialform');
}
else {
titleService.title = 'Another Title'
$state.go('socialform');
}
};
Then, you can either inject it in via custom data or, via the resolve function :
// ... inject before route definition, via dependency injection
data = { title: titleService.title };
$stateProvider.state('socialform', {
url: "/socialform?webcontent",
templateUrl: "base_template/_Sends.html?",
// like this
data: data,
resolve: {
callPreRenderServices: callPreRenderServices
// Or you can resolve your title from your service
// and use pageTitle in your controller
pageTitle: ['titleService', function(titleService) {
return titleService.title;
}]
}
});
You could also pass it as a $state parameter :
$stateProvider.state('socialform', {
url: "/socialform/:webcontent/:title",
// ...
});
// ...
$state.go('socialform', {webcontent: 'something', title: 'some other thing'});

Can I group multiple objects in UI router resolve

I need to load 3 services up front in Resolve , rather than creating seperate service calls for each, will it be possible to return a object of functions
My router
.state('home.movies', {
parent: 'home',
url: '/home/movies/edit/:id',
data: {
roles: []
},
views: {
'content#': {
templateUrl: 'views/admin/activity-edit.html',
controller: 'ActivityEditController'
}
},
resolve:
titles:function (MovieService){
return {
actors: function (MovieService) {
return MovieService.getActors('','','N');
},
actresses:function (MovieService) {
return ResourceService.getActresses();
},
timemap:function(MovieService){
return MovieService.getTimemap();
}
}
}
});
})
my Controller
.controller('MovieEditController', function ($scope,titles,MovieService) {
refere is as
$scope.moviesActors=titles.actors;
It's possible with $q:
resolve: {
titles: function (MovieService, $q){
return $q.all({
actors: MovieService.getActors('','','N'),
actresses: ResourceService.getActresses(),
timemap: MovieService.getTimemap()
});
}
}
Resolved titles will look like: { actors: [], actresses: [], timemap: {} }

Issue with modifying objects that are added by Angular modal controller

I'm having issue with modifying objects that are adding through angular modal controller
I have
.controller("viewController", function($scope, $modal) {
$scope.allPosts = [
{
id: 1,
owner: "Owner 2",
profile: "images/profile.png",
title: "Book title 1",
image: null,
price: 25,
reply: 2,
fav: 1,
isFaved: false,
content: "test"
},
{
id: 2,
owner: "Owner",
profile: "images/profile2.png",
title: "Ken Follett",
image: "images/book1.jpg",
price: 20,
reply: 12,
fav: 3,
isFaved: true,
content: "The book is in nice"
}
];
$scope.addFav = function(id) {
_.each($scope.allPosts, function(post) {
if(post.id === id) {
post.isFaved = !post.isFaved;
if(post.isFaved) {
post.fav++;
$scope.myFavs.push(post);
} else {
post.fav--;
$scope.myFavs = _.reject($scope.myFavs, function(post) {
return post.id === id;
});
}
}
});
};
$scope.addPost = function() {
var modalInstance = $modal.open({
templateUrl: 'myModalContent.html',
controller: 'ModalInstanceCtrl',
resolve: {
allPosts: function(){
return $scope.allPosts;
}
}
});
};
)
.controller('ModalInstanceCtrl', function ($scope, $modalInstance, allPosts) {
$scope.postId = 50;
$scope.ok = function () {
var temp = {};
temp.id = $scope.postId;
temp.profile = "images/profile.png";
temp.title = $scope.title;
temp.type = $scope.type;
temp.price = $scope.price;
temp.reply = 0;
temp.fav = 0;
temp.isFaved = false;
temp.content = $scope.description;
$scope.allPosts.push(temp);
$scope.postId++;
$modalInstance.close();
};
});
$scope.addFav(id) function works fine with existing $scope.allPosts. However, when I add new object by using the ModalInstanceCtrl, the $scope.allPosts is updated but when it goes to $scope.addFav(id), I can not modified the new object that is pushed in to $scope.allPosts from ModalInstanceCtrl. for example I try to update the fav property in post by using
post.fav++; // console.log(post) shows the fav property is not updated. it remains at 0.
As you don't show the markup I suspect that the ModalInstanceController must be nested within the scope of the viewController. This would explain how the same allPosts is available in both controllers. However the postId will be different on each scope due to the way that javascript's prototypical inheritance works. To overcome this you could define an object on scope something like this:
$scope.posts = {
postId: 0,
allPosts: []
}
Alternatively, and even better imho, define a Posts service that encapsulates all the post behaviours and inject that into both controllers. You are then insulated from any changes to the markup that could muck up the controller inheritance.

Resources