Dynamically parsing HTML with AngularJS - angularjs

I'm new to Angular and I have hit a small problem. I would like to set different menus depending on the user being authenticated or not. Depending on the status laravel returns different menu structures (html).
Here's my HTML for the main menu:
<div class="navbar navbar-default navbar-fixed-top" role="navigation" ng-controller="MenuCtrl">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<img src="/image">
</div>
<div class="collapse navbar-collapse">
<ul class="nav navbar-nav">
<li ng-bind-html="firstoption" ng-class="{active:isActive('/firstoption')}">{{ firstoption }}</li>
<li ng-bind-html="secondoption" ng-class="{active:isActive('/secondoption')}" class="dropdown">
{{ secondoption }}
</li>
</ul>
</div><!--/.nav-collapse -->
</div>
</div>
Heres my Controller:
The backend returns a json with the html. The reason why I want to do it like that has also to do with user rights. Depending on different rights, different options are shown in the menu.
AppControllers.controller('MenuCtrl', ['$scope', '$http', '$sce', '$location',
function ($scope, $http, $sce, $location) {
$scope.isActive = function(route) {
return route === $location.path();
}
$scope.firstoption = "";
$scope.secondoption = "";
var getMenuStructure = $http.get('/menustructure').success(function (data) {
$scope.firstoption = $sce.trustAsHtml(data.firstoption);
$scope.secondoption = $sce.trustAsHtml(data.secondoption);
});
}]);
It seems to work nearly perfectly, but the console still gives me an error:
TypeError: Object [object Object] has no method 'indexOf'
Is there a more elegant way of doing it? What am I doing wrong?
UPDATE
The problem seems to be the $sce.trustAsHtml();. When I remove it, the dropdowns don't work anymore and when I add them I get the error.

The ng-include allows you to include data from an external URL and compile it on the fly.
This will allow you to write code like this:
<ng-include src="/views/{{viewname}}.html"></ng-include>
and then in your controller, simply set (or change) the view by setting $scope.viewname, like so:
$scope.viewname = "view"
Even if you don't need to dynamically change which page is loaded, it's worth using the ng-include for simplicities sake.

I ran into this same problem. I found that $sce.getTrusted worked the same as trustAsHtml for my case. It looks like this: $sce.getTrusted($sce.HTML, someHtml)

Related

AngularJS add/removeClass does nothing?

I am trying to add and remove a class on button click. Unfortunately nothing I do seems to achieve this. The class never changes no matter what I do.
Here is what I am trying to change;
<div ng-controller="navbarCollaspeCtrl">
<nav id="mainNav" class="navbar fixed-top navbar-expand-lg navbar-transparent">
<button class="navbar-dark navbar-toggler" ng-click="changeClass()" href="" type="button" data-toggle="collapse" data-target="#navToggle" aria-controls="navToggle" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
</div>
And here is the Angular part;
.controller('navbarCollaspeCtrl', ['$scope', '$element', function($scope, $element) {
$scope.changeClass = function() {
var el = document.getElementById('mainNav');
$element.removeClass(el, "navbar-transparent");
$element.addClass(el, "navbar-primary");
console.log(el);
}
}])
The 'navbar-transparent does not get removed. And navbar-primary never gets added. Any thoughts?
There is no need to manipulate DOM elements like this for simple scenarios like this one (this is more of a jQueryish approach). Instead create a variable and bind to it to toggle the style.
Html
<nav id="mainNav" class="navbar fixed-top navbar-expand-lg" {{transparent ? 'navbar-transparent': ''}}">
JS
var myApp = angular.module('myApp',[]);
myApp.controller('navbarCollaspeCtrl', ['$scope', '$element', function($scope, $element) {
$scope.transparent = false;
}]);
You can also use ng-class directive.

Angular. Why filter invokes automatically?

I'm new in angular and I reeding A.Freeman's book "Pro Angular JS".
So I stuck in one of examples trying to understand why filter in ng-repeat is triggered.
Here is the code:
<body ng-controller="sportsStoreCtrl">
<div class="navbar navbar-inverse">
<a class="navbar-brand" href="#">SPORTS STORE</a>
</div>
<div class="panel panel-default row" ng-controller="productListCtrl">
<div class="col-xs-3">
<a ng-click="selectCategory()" class="btn btn-block btn-default btn-lg">Home</a>
<a ng-repeat="item in data.products | orderBy:'category' | unique:'category'" ng-click="selectCategory(item)" class=" btn btn-block btn-default btn-lg">
{{item}}
</a>
</div>
<div class="col-xs-8">
<div class="well" ng-repeat="item in data.products | filter:categoryFilterFn">
<h3>
<strong>{{item.name}}</strong>
<span class="pull-right label label-primary">
{{item.price | currency}}
</span>
</h3>
<span class="lead">{{item.description}}</span>
</div>
</div>
</div>
</body>
and
angular.module("sportsStore")
.controller("productListCtrl", function ($scope, $filter) {
var selectedCategory = null;
$scope.selectCategory = function (newCategory) {
selectedCategory = newCategory;
}
$scope.categoryFilterFn = function (product) {
return selectedCategory == null ||
product.category == selectedCategory;
}
});
categoryFilterFn is one that confuses me. Why it's invoking when I press catefory buttons (with selectCategory() method on ng-click) since I never call categoryFilterFn explicitly?
Answering you question - because of $digest. You don't have call categoryFilterFn directly. Your selectedCategory has changed which is used in categoryFilterFn and categoryFilterFn is bound to scope.
Not sure how I can describe it correctly but here my explanation.
There are two "independent" parts :
The repeat iterate over an array of items.
If you select an category via ng-click function you set the new category in the scope.
Here kicks the filter function in, witch ties it up.
It is triggered because a new category is selected ($digest) and "reordering" the array (like map function in plain Javascript) and the angular magic (ng-repeat) displays only items with this category.
And that's the reason why I love angular so much 🤓

AngularJS: Empty value when access in view

I'm newbie in AngularJS, I have a question, please explain to me the reason of my mistake. At first, I have a factory name CategoryParent with this function declared like this,
routerApp.factory('CategoryParent', function($http) {
var categoryParentFactory = {};
var hostCMSAPI="http://***.***.***.***:****";
// get all categories
categoryParentFactory.allCategoryParents = function() {
console.log("call get allCategoryParents");
return $http.get(hostCMSAPI+'/api/categoryparents/');
};
.......
and a controller call this function named categoryParentController:
.controller('categoryParentController', function(CategoryParent,$scope) {
console.log("cateParent ctrl");
$scope.processing=true;
$scope.dataList=[];
$scope.getAllCategoryParents=function(){
CategoryParent.allCategoryParents().success(function(response){
$scope.processing = false;
$scope.list=response;
});
I'm using ui.router like this (nested view):
.state('home.cateParentMenu',{
url:'/cateParentMenu',
templateUrl:'categoryParentTop.html',
controller:'categoryParentController',
controllerAs:'categoryParent'
})
Parent view trigger controller function here:
The Homey Page
This page demonstrates nested views.
<a ui-sref=".list" class="btn btn-primary">List</a>
<a ui-sref=".paragraph" class="btn btn-danger">Paragraph</a>
<a ui-sref=".cateParentMenu" class="btn btn-warning" ng-click="getAllCategoryParents()">cateParentMenu</a>
and last, the children view come here
data show
{{processing}}
{{list}}
{{item.cate_parent_name}}
<ul class="nav navbar-nav" >
data show
{{processing}}
{{list}}
<li data-ng-repeat="item in list.data"><a ui-sref=".detail({cate_parent_id:item.cate_parent_id})" ng-click="getById(item.cate_parent_id)" ui-sref-active="active" id="{{item.cate_parent_id}}">{{item.cate_parent_name}}</a></li>
</ul>
<div class="col-sm-6">
<div ui-view=""></div>
<!--<div ui-view="serviceRef"></div>-->
<!--<div ui-view="categoryRef"></div>-->
</div>
I set $scope.list and $scope.processing to transfer result to view. But in view I show {{list}}, nothing appears and {{processing}} = true ???
Why? please help me, many thanks to all your suggests.
Controller
Fix $scope.dataList=[]; to $scope.list=[];

Angular-Fullstack + UI router isn't routing properly

I'm attempting to route my application using UIrouter with the current yeoman-fullstack generator without mongoDB. In this example of my issue, everything is default to test and make sure the UI-router is working properly.
Mainly because I'm not understanding and missing something silly and noobish here, so I apologize.
https://github.com/DaftMonk/generator-angular-fullstack
I'm attempting to edit my routes in client/app/app.js using
'use strict';
angular.module('sr388App', [
'ngCookies',
'ngResource',
'ngSanitize',
'ui.router',
'ui.bootstrap'
])
.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
$urlRouterProvider
.otherwise('/');
$stateProvider
.state('about', {
url: '/about',
template: '<h1>Attempting to have this point at /main/about.html but this is for testing purposes</h1>'
});
$locationProvider.html5Mode(true);
});
Now, testing that, localhost:9000/about will load, but when I try to link the state to the navbar it breaks.
'use strict';
angular.module('sr388App')
.controller('NavbarCtrl', function ($scope, $location) {
$scope.menu = [{
'title': 'Home',
'link': '/',
'title': 'About',
'link': '/about'
}];
$scope.isCollapsed = true;
$scope.isActive = function(route) {
return route === $location.path();
};
});
The console error is
Uncaught SyntaxError: Duplicate data property in object literal not allowed in strict mode
Here is the navbar html
<div class="navbar navbar-default navbar-static-top" ng-controller="NavbarCtrl">
<div class="container">
<div class="navbar-header">
<button class="navbar-toggle" type="button" ng-click="isCollapsed = !isCollapsed">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
sr388
</div>
<div collapse="isCollapsed" class="navbar-collapse collapse" id="navbar-main">
<ul class="nav navbar-nav">
<li ng-repeat="item in menu" ng-class="{active: isActive(item.link)}">
<a ng-href="{{item.link}}">{{item.title}}</a>
</li>
</ul>
</div>
</div>
</div>
I'm certain my fundamental understanding of what's happening (supposed to be happening) is flawed.
I did notice the # is not being added when attempting to load states without changing URL.
<a ng-href="{{item.link}}">{{item.title}}</a>
I thought I had found the problem, and this would fix it
<a ui-sref="{{item.link}}">{{item.title}}</a>
But no dice.
Any advice/suggestions would be greatly appreciated. All I'm really trying to do is to create a simple SPA page based on this
http://i.imgur.com/sZYe15o.png
That would just simply run through a few different view states.
/#about
/#contact etc.
In ui-sref just put the state name instead of the URL/link
ui-sref="about"
Then it will work

How can create a dynamic menu depending on the user's rights, in angularjs?

There is two similar questions:
dynamic menu items using AngularJS
How can I show or hide some buttons depend on the user's rights, in angularjs?
I need to create a dynamic menu exactly like in the first similar question exept I cannot hardcode the rights in my page. An adminitrator can create custom roles and chose wich menu item this role can see.
<li class="dropdown"><img role="button" class="dropdown-toggle" data-toggle="dropdown" ng-src="{{avatarUrl}}" />
<ul class="dropdown-menu pull-right" role="menu">
<li ng-show="???"><a ng-click="action01()">Action one</a></li>
<li ng-show="???"><a ng-click="action02()">Action two</a></li>
<li ng-show="???"><a ng-click="action03()">Action tree</a></li>
<li ng-show="???"><a ng-click="action04()">Action four</a></li>
</ul>
</li>
How should I imagine my strategy?
I would create something like a HeaderController an attach to it a function that tells if a given role can do the given action. Presumably you have the ACL stored somewhere so perhaps you can create a service for it. Something like this:
controller('HeaderController', ['$scope', 'Acl', function($scope, Acl) {
$scope.roleCanDo = function(role, action) {
return Acl.roleCanDo(role, action);
}
}])
and your view would be like this:
<li class="dropdown"><img role="button" class="dropdown-toggle" data-toggle="dropdown" ng-src="{{avatarUrl}}" />
<ul class="dropdown-menu pull-right" role="menu">
<li ng-show="roleCanDo(currentUser.role, 'action01')"><a ng-click="action01()">Action one</a></li>
<li ng-show="roleCanDo(currentUser.role, 'action02')"><a ng-click="action02()">Action two</a></li>
<li ng-show="roleCanDo(currentUser.role, 'action03')"><a ng-click="action03()">Action tree</a></li>
<li ng-show="roleCanDo(currentUser.role, 'action04')"><a ng-click="action04()">Action four</a></li>
</ul>
</li>
Obviously the actual code will depend on your current system but you get the idea.
It's very simple to implement Creating dynamic menu from database data using AngularJS
Suppose we have a table in our database for navigation menus like,
Table structure for dynamic menu
Write below code for fetch menus from database
public JsonResult GetSiteMenu()
{
using (MyDatabaseEntities dc = new MyDatabaseEntities())
{
var menu = dc.SiteMenus.ToList();
return new JsonResult { Data = menu, JsonRequestBehavior = JsonRequestBehavior.AllowGet };
}
}
and here is the angularjs controller
var app = angular.module('MyApp', []);
app.controller('menuController', ['$scope', '$http', function ($scope, $http) {
$scope.SiteMenu = [];
$http.get('/home/GetSiteMenu').then(function (data) {
$scope.SiteMenu = data.data;
}, function (error) {
alert('Error');
})
}])
HTML Code
<div ng-app="MyApp">
<div ng-controller="menuController">
#* Here first of all we will create a ng-template *#
<script type="text/ng-template" id="treeMenu">
{{menu.Name}}
#* We will create submenu only when available *#
<ul ng-if="(SiteMenu | filter:{ParentID : menu.ID}).length > 0">
<li ng-repeat="menu in SiteMenu | filter:{ParentID : menu.ID}" ng-include="'treeMenu'"></li>
</ul>
</script>
<ul class="main-navigation">
#* Here we will load only top level menu *#
<li ng-repeat="menu in SiteMenu | filter:{ParentID : 0}" ng-include="'treeMenu'"></li>
</ul>
</div>
</div>

Resources