AngularJS: Changing Routes Based on Drop-Down Not Working - angularjs

I am working on a project that will ultimately be responsive and the navigation will collapse down to a select control, so I found an article on Google about this. I learned that the ng-change does not fire an angular event but it was suggested that adding a ng-click to the option tags would replace that void.
As I built my POC, the first thing I realized is that my copying-and-pasting the go function into each meant the design wasn't DRY (so alarms began sounding off in my head) and probably means this is not the right way to do this.
I continue to build what I understood from the article and it doesn't change the routes.
I built a plunker.
Here's the HTML:
<!DOCTYPE html>
<html ng-app="myApp">
<head>
<meta charset="utf-8" />
<title>Proof of Concept</title>
<link data-require="bootstrap-css#*" data-semver="3.1.1" rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css" />
<script>document.write('<base href="' + document.location + '" />');</script>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<form>
<select name="naver" id="naver">
<option value="home" ng-click="go('/')">Home</option>
<option value="first" ng-click="go('/first')">First</option>
<option value="second" ng-click="go('/second')">Second</option>
</select>
</form>
<div ng-view=""></div>
<script data-require="angular.js#1.2.x" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.16/angular.min.js" data-semver="1.2.16"></script>
<script data-require="angular-route#*" data-semver="1.2.14" src="http://code.angularjs.org/1.2.14/angular-route.js"></script>
<script data-require="jquery#*" data-semver="2.0.3" src="http://code.jquery.com/jquery-2.0.3.min.js"></script>
<script data-require="bootstrap#*" data-semver="3.1.1" src="//netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js"></script>
<script src="app.js"></script>
</body>
</html>
and the script:
var app = angular.module("myApp", ['ngRoute']);
app.config(function($routeProvider){
$routeProvider
.when('/', {
templateUrl: "home.html",
controller: 'HomeController'
})
.when('/first', {
templateUrl: "first.html",
controller: 'FirstController'
})
.when('/second', {
templateUrl: "second.html",
controller: 'SecondController'
})
.otherwise({ redirectTo: '/' });
});
app.controller('HomeController', function($scope, $location){
$scope.go = function( path ){
$location.path(path);
}
});
app.controller('FirstController', function($scope, $location){
$scope.go = function( path ){
$location.path(path);
}
});
app.controller('SecondController', function($scope, $location){
$scope.go = function( path ){
$location.path(path);
}
});
Any help is greatly appreciated!

You indeed can be more DRY than that. I would:
Remove all go() functions from the views' controllers.
Create a new controller (e.g. NavCtrl) for the navigation "bar".
<form ng-controller="NavCtrl">
Remove the ngClick directives from the <option> elements (since they don't seem to have any effect - at least in Chrome).
Add an ngModel to the <select> element to keep track of the selected page/view.
Add an ngChange listener to the <select> element to trigger a "redirection" every time the selected page/view changes.
<select id="naver" name="naver" ng-model="currentPage" ng-change="go(currentPage)">
<option value="home">Home</option>
<option value="first">First</option>
<option value="second">Second</option>
</select>
Define the go() function inside the aforementioned NavCtrl controller.
app.controller('NavCtrl', function NavCtrl($location, $scope) {
$scope.currentPage = 'home';
$scope.go = function go(page) {
$location.path('/' + page);
};
});
See, also, this short demo.

The go() function cannot be called, since the select control is out of the scope of the page controllers.
Try this instead:
plunkr

To Complement the above answers, i have created a directive which will switch routes based on the value selected.
please find the plunk here:
plunkr
Here is the code:
`app.controller('mainController', function($rootScope, $scope) {
$scope.testArray = [{
'option': 'home'
}, {
'option': 'first'
}];
$scope.testModel = $scope.testArray[0].option;
});
app.directive('selectDirective', function() {
return {
scope: {
testModel: '=',
testArray: '=',
go: '&'
},
require: 'ngModel',
template: `<select name="testModel" ng-model="testModel" value="option.option" ng-change="Model(testModel)" ng-options="option.option for option in testArray">{{option.option}}</option>
<option value="" selected="selected">Select an Item</option>
</select>`,
replace: true,
controller: function($scope, $location) {
$scope.Model = function(page) {
$location.path('/' + page.option);
}
}
}
});`
HTML:
'<test-directive dat-options="testArray" dat-heldmodel="testModel"></test-directive>
<div>Selected: {{testModel}}</div>'

Related

Make two layouts share the same $scope

I want to propose two layouts (ie, horizontal and vertical) for my contents. So switching in the selector will lead automatically to the corresponding layout. Here is the JSBin:
<html ng-app="flapperNews">
<head>
<script src="https://code.jquery.com/jquery.min.js"></script>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet" type="text/css" />
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.7/angular.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-router/0.3.2/angular-ui-router.js"></script>
<script type="text/ng-template" id="horizontal.tpl">
<textarea ng-model="one"></textarea>, <textarea ng-model="two"></textarea>
<br><br>{{one}}+{{two}}
</script>
<script type="text/ng-template" id="vertical.tpl">
<textarea ng-model="one"></textarea><br><textarea ng-model="two"></textarea>
<br><br>{{one}}+{{two}}
</script>
<script>
var app = angular.module('flapperNews', ['ui.router']);
app.config(['$stateProvider', function ($stateProvider) {
$stateProvider
.state('entry', {
url: '/',
params: { tpl: 'vertical' },
templateUrl: function (params) {
return params.tpl + ".tpl"
}
})
}]);
app.controller('MainCtrl', ['$scope', '$state', function ($scope, $state) {
$scope.one = "one";
$scope.two = "two";
$scope.layouts = ["horizontal", "vertical"];
$scope.$watch('layout', function () {
$state.go('entry', {tpl: $scope.layout});
})
}])
</script>
</head>
<body ng-controller="MainCtrl">
<select ng-model="layout" ng-init="layout = 'horizontal' || layouts[0].value" ng-options="x for x in layouts"></select>
<br><br>
<ui-view></ui-view>
</body>
</html>
However, with the above code, each time we change the view, $scope.one and $scope.two are reset to their initial values. I would hope the change in their textarea would remain regardless of the change of layout.
Does anyone know how to solve this?
Easy sharing same data between different views by using factories (AngularJS factory documentation). Try this example, it uses a simple factory named myFactory to share data between controllers. This also does work on the same controller as in your case.
var myApp = angular.module("myApp",[ "ui.router"]);
myApp.config(function ($stateProvider, $urlRouterProvider){
$stateProvider.state("state1", {
url: "#",
template: '<p>{{ aValue }}</p><button ng-click="bindValue(\'its me\')">Bind value</button>',
controller: "myController"
}).state("state2", {
url: "#",
template: '<p>{{ aValue }}</p><button ng-click="bindValue(\'its me\')">Bind value</button>',
controller: "myController"
});
});
myApp.controller( "myController", function($scope, myFactory) {
$scope.aValue = myFactory.myValue;
$scope.bindValue = function (value) {
$scope.aValue = value;
myFactory.myValue = value;
}
});
myApp.factory('myFactory', function () {
return {
myValue: ''
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.13/angular.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/angular-ui-router/0.2.8/angular-ui-router.min.js"></script>
<div ng-app="myApp">
<nav>
<a ui-sref="state1">State 1</a>
<a ui-sref="state2">State 2</a>
</nav>
<div ui-view></div>
</div>
I think that you should use nested views - you can define main controller on parent route state and define two nested states corresponding to two views. This way parent controller will remain (it's not re-initialised when child states are switched) and only nested states views will be changed. Something like this:
$stateProvider
.state('myState', {
url: '/test/',
template: '<div ui-view></div>',
controller: function() {
//main controller shared by child states
...
}
})
.state('myState.view1', {
url: '/test/view1'
templateUrl: 'tpl-1.hmtl',
...
})
.state('myState.view2', {
url: '/test/view2'
templateUrl: 'tpl-2.hmtl',
...
})

Angular add dynamic html(directive) with object as parameter

Doesn't sound like it would be a big deal but I don't know what to google for to solve this. I want to be able to fetch an object with $http and then render that info with the help of a directive.
JS:
angular.module("test", []);
angular.module("test").directive('myTest', function() {
return {
templateUrl: 'myTest.html'
};
});
angular.module("test").controller("myCtrl", function($http, $compile){
var vm = this;
vm.name = "Viktor";
vm.country = "Sweden";
vm.origin = "controller";
vm.click = function(){
$http.get("data"+Math.floor((Math.random() * 2) + 1)+".json").success(function(data){
$("body").append($compile("<my-test></my-test>")(data));
})
}
})
Template:
<div>
<div>My name is: {{vm.name}}</div>
<div>I live in : {{vm.country}}</div>
<div>Source origin : {{vm.origin}}</div>
</div>
index.html
<!DOCTYPE html>
<html ng-app="test">
<head>
<script data-require="jquery#*" data-semver="2.1.4" src="http://code.jquery.com/jquery-2.1.4.min.js"></script>
<script data-require="angular.js#*" data-semver="1.4.7" src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.7/angular.min.js"></script>
<link rel="stylesheet" href="style.css" />
<script src="app.js"></script>
</head>
<body ng-controller="myCtrl as vm">
<button ng-click="vm.click()">Add panel</button>
<my-test></my-test>
</body>
</html>
Plunker: http://embed.plnkr.co/YcG9ZFxuR3PYBYASjzm2/preview
I just can't edit your plnkr file.
But bindToController should help you. It should be somethind like this, I create a controller for the directive :
angular.module("test").directive('myTest', function() {
return {
restrict: 'E',
scope: {},
templateUrl: 'myTest.html',
controller: function(){},
controllerAs: 'ctrl',
bindToController: {
name: '=',
country: '=',
origin: '='}
}
});
Also I change the alias of the controller in the template :
<div>
<div>My name is: {{ctrl.name}}</div>
<div>I live in : {{ctrl.country}}</div>
<div>Source origin : {{ctrl.origin}}</div>
<input ng-model="ctrl.name"/>
</div>
Here is a working example modified on your plunkr : plunkr.
I am still stuck in the jQuery way of thinking, I guess I found out how to do it the Angular way. Just to add the newly fetched item to a list and then render the list. Not what I was originally aiming for but that is probably the prettiest way.
And if one now HAVE to add new items instead of redrawing the whole list, I guess one could send in the object as a json-string or set scope variables on the directive for each parameter in the object.
Plunker: http://plnkr.co/edit/5ElTIx?p=preview
Html:
<body ng-controller="myCtrl as vm">
<button ng-click="vm.click()">Add panel</button>
<my-test ng-repeat="panel in vm.panels" obj="panel"></my-test>
</body>
Js:
angular.module("test").controller("myCtrl", function($http, $compile){
var vm = this;
vm.name = "Viktor";
vm.country = "Sweden";
vm.origin = "controller";
vm.panels = [{
name:"Viktor2",
origin:"Controller array",
country:"Sweden"
}];
vm.click = function(){
$http.get("data"+Math.floor((Math.random() * 2) + 1)+".json").success(function(data){
vm.panels.push({
name:data.name,
origin:data.origin,
country:data.country
})
})
}
})

$location.path is not redirecting the document as expected

I have a very simple program where I am trying to load a page in a using a dropdown menu. But for some reason, the $location.path is not working.
Here is my code.
HTML
<!DOCTYPE html>
<html lang="en">
<head>
<title>AngularJS Routing example</title>
</head>
<body ng-app="sampleApp">
<div>
<div>
<div ng-controller="RouteController">
<select ng-model="opt.selector" ng-options="opt as opt.label for opt in options">
</select>
<button ng-click="selectFn()">Go</button>
</div>
<div>
<div ng-view></div>
</div>
</div>
</div>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js"></script>
<script src="app.js"></script>
</body>
</html>
JS
var sampleApp = angular.module('sampleApp', []);
sampleApp .config(['$routeProvider','$locationProvider',
function($routeProvider) {
$routeProvider.
when('/one', {
templateUrl: '1.html',
//controller: 'AddOrderController'
}).
when('/two', {
templateUrl: '2.html',
//controller: 'ShowOrdersController'
}).
otherwise({
redirectTo: '/index.html'
});
}]);
sampleApp.controller('RouteController', function($scope){
$scope.options=[
{label: '1', url:'#one'},
{label: '2', url:'#two'}
];
$scope.selectFn = function($locationProvider){
$location.path($scope.opt.selector.url));
}
});
Could someone tell me where I am going wrong?
I am getting the following error:
ReferenceError: $location is not defined
at Object.$scope.selectFn
You need to inject $location into your controller so that the singleton can be used inside the controller. You are (I think trying to inject it int a function in your controller?) which won't work the way you are using it right now. If you want to do it the way you are doing right now:
sampleApp.controller('RouteController', function($scope, $location){
$scope.options=[
{label: '1', url:'#one'},
{label: '2', url:'#two'}
];
$scope.selectFn = function(){
$location.path($scope.opt.selector.url));
}
});
Although I highly recommend using the $window service to redirect

Switching Angular view not updating variables

I'm just building out a simple app to learn AngularJS and having trouble updating variables when switching views. Here's what I have so far:
function routeConfig($stateProvider, $urlRouterProvider) {
$stateProvider
.state('home', {
url: '/',
templateUrl: 'app/main/main.html',
controller: 'MainController',
controllerAs: 'main'
})
.state('team', {
url: '/team',
templateUrl: 'app/main/team.html',
controller: 'MainController',
controllerAs: 'main'
})
$urlRouterProvider.otherwise('/');
}
Here's part of my controller:
function MainController($timeout, webDevTec, toastr, $resource, $scope) {
var vm = this;
var GetTeam = $resource('https://apisite.com/api_endpoint/:teamId', {teamId: '#id'});
vm.teamName = '';
function getTeamInfo(id) {
var teamObj = GetTeam.get({teamId: id});
$timeout(function(){
vm.teamName = teamObj["name"];
},100)
};
vm.getTeamInfo = getTeamInfo;
}
Then in my main.html I call getTeamInfo with a ng-click:
<ul class="list-group">
<li class="list-group-item" ng-repeat="team in main.teams" ng-click="main.getTeamInfo(team.id)">{{ team.name }}</li>
</ul>
Clicking on that link will take you to team.html:
<div class="row">
<div class="col-sm-12">
<h3>{{ main.teamName }}</h3>
<ul class="list-group">
. . .
</ul>
</div>
</div>
For some reason "main.teamName" is not updating. I've tried the $scope.$apply(function(){vm.teamName = teamObj["name"]} approach as well with no luck. I also did 'console.log(teamObj["name"])' before vm.teamName and 'console.log(vm.teamName)' after to see if I get the expected results and I do. I just have no idea now why it's not updating the new view.
Thank you for your insight, patience, and time!
UPDATE 1
I also tried using $scope on my variables ($scope.teamName) and using $scope.$apply(function(){$scope.teamName = teamObj["name"]}) with no luck.
UPDATE 2
I also tried called $scope.$apply(); after 'vm.teamName = teamObj["name"]' with no luck
It looks like teamObj is not populated yet at the point when you assign vm.teamName
You would make your life so much easier if you just reference teamObj rather than creating a new property.
I made a plunker based on a modified version of your code to show a possible implementation. I couldn't get it to work using the controllerAs syntax and I'm not entirely sure why (possibly because of some issues related to sharing a controller; not sure). Anyway, hopefully it will be of some help to you.
DEMO
app.js
var app = angular.module('plunker', ['ui.router', 'ngResource']);
app.controller('MainController', MainController);
app.config(routeConfig);
function MainController($timeout, $scope, $resource) {
// mock data
var GetTeam = $resource('http://demo7592070.mockable.io/teams/:teamId', {teamId: '#id'});
//var GetTeam = $resource('https://apisite.com/api_endpoint/:teamId', {teamId: '#id'});
$scope.teamName = 'undefined';
$scope.getTeamInfo = getTeamInfo;
function getTeamInfo(id) {
var teamObj = GetTeam.get({teamId: id});
$scope.teamName = teamObj.name;
$scope.teamObj = teamObj;
};
}
function routeConfig($stateProvider, $urlRouterProvider) {
$stateProvider
.state('home', {
url: '/',
templateUrl: 'main.html',
controller: 'MainController'
})
.state('team', {
url: '/team',
templateUrl: 'team.html',
controller: 'MainController'
});
console.log('ROUTECONFIG');
$urlRouterProvider.otherwise('/');
}
index.html
<!DOCTYPE html>
<html ng-app="plunker">
<head>
<meta charset="utf-8" />
<title>AngularJS Plunker</title>
<script>document.write('<base href="' + document.location + '" />');</script>
<link rel="stylesheet" href="style.css" />
<!-- JS (load angular, ui-router, and our custom js file) -->
<script src="http://code.angularjs.org/1.2.13/angular.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/angular-ui-router/0.2.8/angular-ui-router.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.2/angular-resource.js"></script>
<script src="app.js"></script>
</head>
<body>
<a ui-sref="home">home</a>
<a ui-sref="team">team</a>
<div ui-view=""></div>
</body>
</html>
main.html
<h1>Home</h1>
<pre>$scope.teamName => {{teamName}}</pre>
<pre>$scope.teamObj => {{teamObj}}</pre>
<pre>$scope.teamObj.name => {{teamObj.name}}</pre>
<button ng-click="getTeamInfo(1)">Get Team 1</button>
team.html
<h1>Team</h1>
<pre>$scope.teamName => {{teamName}}</pre>
<pre>$scope.teamObj => {{teamObj}}</pre>
<pre>$scope.teamObj.name => {{teamObj.name}}</pre>
<button ng-click="getTeamInfo(2)">Get Team 2</button>

AngularJS - nesting of partials and templates doesn't work

I'm trying to implement the solution offered by ProLoser in link
in my Plunk. My problem is that whenever I press a link instead of opening in a sub-view below the links it overrides the entire view.
I need to understand how to solve this problem.
My flow is like that: index.html -> content.html (ng-view) -> link1/2/3.html (using ng-include).
My layout:
Index.html:
<!DOCTYPE html>
<html ng-app="webApp">
<head>
<link rel="stylesheet" href="style.css" />
<script data-require="angular.js#1.0.7" data-semver="1.0.7" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.js"></script>
<script src="app.js"></script>
</head>
<body>
<header>This is header</Header>
<div class="content" ng-view>
</div>
</body>
</html>
content.html:
<div>
<h1>This is Content brought to you by ngView</h1>
<br>
link1
link 2
link 3
<ng-include src="'/sub/'+link + '.html' "></ng-include>
</div>
My code:
var webApp = angular.module('webApp', []);
//router logic
webApp.config(['$routeProvider', function($routeProvider) {
$routeProvider.
when('/', {
templateUrl: 'content.html',
controller: 'MainCtrl'
})
.when('/sub/:link', {
controller: 'LinkCtrl'
})
.otherwise({redirectTo: '/'});
}]);
//controllers
webApp.controller ('MainCtrl', function ($scope, $routeParams) {
$scope.link = $routeParams.link
});
You don't have a LinkCtrl to handle the links, it should work if you change:
.when('/sub/:link', {
controller: 'LinkCtrl'
})
to
.when('/sub/:link', {
templateUrl: 'content.html',
controller: 'MainCtrl'
})
And edit the line:
<ng-include src="'/sub/'+link + '.html' "></ng-include>
to:
<ng-include src="link + '.html'"></ng-include>

Resources