Why isn't my directive rendering any bound elements? - javascript

This follows from my question Recursion with ng-repeat in Angular, but is no way a duplicate. Now I know how to do that, but am asking why my current solution to the problem isn't working.
I'm busy adapting the huge Metronic Angular theme, and my first task is to dynamically load side menu items from a REST api. The container for the side meny, in the main index.html page (only page used: doing spa) looks like this:
<div data-ng-include="'tpl/sidebar.html'" data-ng-controller="SidebarController" class="page-sidebar-wrapper"></div>
Then sidebar.html looks like this:
<div class="page-sidebar navbar-collapse collapse">
<ul class="page-sidebar-menu" data-keep-expanded="false" data-auto-scroll="true" data-slide-speed="200"
ng-class="{'page-sidebar-menu-closed': settings.layout.pageSidebarClosed}"
rsng-menuItem="menuItems">
</ul>
</div>
You'll notice my custom directive, rsng-menuItem, used for recursing a menu tree:
angular.module("ReflexPortalApp").directive("rsngMenuItem", ["$parse", function ($parse) {
return {
restrict: "A",
scope: true,
templateUrl: "/tpl/sidebar/menu-item.html",
link: function (scope, element, attrs) {
scope.List = $parse(attrs.rsngMenuItem)(scope);
}
}
}]);
and it's template looks like this:
<li ng-repeat="item in List" ng-class="{\'nav-item\': !item.IsHeading, \'start\': item.IsStart}">
<a href="{{item.Href}}" ng-class="{\'nav-link nav-toggle\': item.subItems && item.subItems.length > 0}">
<i ng-if="{{item.Icon && item.Icon.length > 0}}" class="icon-{{item.Icon}}"></i>
<span class="title">{{item.Text}}</span>
<span ng-if="{{item.DecorClass || item.DecorText}}" class="{{item.DecorClass}}">{{item.DecorText}}</span>
</a>
<ul rsng-menuItem="item.subItems" class="sub-menu"></ul>
</li>
In SideBarController1, the menu items do load successfully from the REST API, and are assigned to themenuItems` scope property, so they should be available for binding.
Lastly, a menu item looks like this in JSON:
{
"IsDisabled": "0",
"seq": 2,
"Href": "javascript:;",
"Id": "2",
"IsStart": "0",
"IsNavItem": "1",
"DecorText": null,
"ApiCall": null,
"Index": 3,
"Text": "Accounting",
"Icon": "settings",
"ParentId": null,
"DecorClass": "arrow",
"subItems": [
{
"DecorClass": "arrow",
"ParentId": "2",
"Icon": "wrench",
"Text": "Statement",
"Index": 1,
"ApiCall": "statement&CustID=4446&statementdate=2016-05-01",
"DecorText": null,
"IsNavItem": "0",
"IsStart": "0",
"Id": "3",
"Href": "list",
"seq": 1,
"IsDisabled": "0"
}
]
}
When I started, and didn't have a clue how to do recursion in templates, I simply used a recursive code loop and jQuery to 'manually' construct and populate all the elements for each menu item, and that worked fine, but writing HTML with code literals stinks.
Now I simply get an empty side menu, with no errors in the console (gotta love that part of Angular), and was wondering if I'm doing anything obviously wrong.

The problem lies in your directive-name, angular simply doesn't recognize your directive because it doesn't deal well with uppercase letters in attribute-names. Rename rsng-menuItem to rsng-menu-item.
However, this won't work because you can't just recursively nest directives in angular, it will end in an infinite loop.
There are multiple solutions for this, take a look at Recursion in Angular directives and
https://github.com/marklagendijk/angular-recursion

Related

Displaying Nested Objects (Numeric) After Making Service Call

I'm currently having an issue accessing nested objects that are referred to through numbers. I made a service call to retrieve a JSON object, and then mapped each field to another object. I'll be using this object to display each field in the HTML.
My problem is occurring when I reach the nested objects. Here's an example :
{
"name": {
"title": "mr",
"first": "something",
"last": "something"
},
"role": "something",
"projects": {
"0": {
"title": "something",
"account": "something",
"steps": {
"total": 30,
"completed": 28
},
"dueDate": "2021-07-19 09:00:00",
"status": "fine"
},
"1": {
"title": "something",
"account": "something",
"steps": {
"total": 10,
"completed": 5
},
"dueDate": "2021-07-20 09:00:00",
"status": "fine"
},
}
}
The projects field gets tricky when trying to display all projects in the HTML. At the moment, I've created a person variable initialized to an empty array, and I add all of the fields from t he subscription to it. To solve this problem, I figured I should create a separate variable such as projects: any = []; and then set it to a new field in the person variable. Then iterate through it using an *ngFor, and display every project. Something like this
<li *ngFor="let project of person.projects">
{{ project }}
</li>
However that approach still doesn't reach the nested fields. How do I access the numbered objects here, and generally iterate through all of the nested fields?
Any advice will be very helpful. Thank you in advance.
I think you are looking for the KeyValuePipe
You would use it like this:
<li *ngFor="let item of person.projects | keyvalue">
{{ item.value }}
</li>
But based on your comment on doing this for more nested values, it might be worth flattening the data a bit in the component to simplify the template logic.

Accessing grandparent ng-repeat object when the parent ng-repeat is another object

I want to access the grandparent object of a ng-repeat item in angular.
I am struggling to make this happen.
As you can see from below I want to display the text of the parent object.
This is a watered down version, I am looking for a way to make it work like the top example or a reason as to why it will not work.
Here is the DOM.
<div ng-app ng-controller="MyCtrl">
<!-- this is the one i cannot get working -->
<div>
<div class="copyData" ng-repeat="copy in copyDatabase">
<div ng-repeat="header in masterHeaders">
<div ng-repeat="test in copy.Translations">
set 1: {{test.LanguageAbreviation}}
</div>
</div>
<div>
<!-- this one does work but i need the item from the masterHeaders -->
<div class="copyData" ng-repeat="copy in copyDatabase">
<!-- <div ng-repeat="header in masterHeaders"> -->
<div ng-repeat="test in copy.Translations">
set 2 : {{test.LanguageAbreviation}}
</div>
<!--</div>-->
</div>
</div>
Here is the JSON Object
function MyCtrl($scope) {
$scope.copyDatabase = {
"data": {
"Language": "English",
"Translations": [{
"LanguageID": 308,
"LanguageName": "Arabic - Libya",
"LanguageAbreviation": "ar-LY",
"AvailableLanguages": [{
"ID": 308,
"Name": "Arabic - Libya"
}]
}, {
"LanguageID": 307,
"LanguageName": "Arabic - Egypt",
"LanguageAbreviation": "ar-EG",
"AvailableLanguages": [{
"ID": 307,
"Name": "Arabic - Egypt"
}]
}]
}
}
}
I have also made a JS fiddle for you guys.
http://jsfiddle.net/u7hk0qvj/5/
Seeing your jsfiddle, you have a base mistake, Angular is not finding your MyCtrl. You need to declare as an angular component.
You are creating a global funcion MyCtrl, but this isn't inside the angular context, for example:
var myApp = angular.module('myApp',[]);
myApp.controller('GreetingController', ['$scope', function($scope) {
$scope.greeting = 'Hola!';
}]);
You can see more info here
Need to have the :
$scope.masterHeaders = {
"home": "home",
"contact": "contact"
}
Object also in the control, If angular does not find the object it will not load any of the DOM elements In the ng-repeat.
Working fiddle
http://jsfiddle.net/u7hk0qvj/8/

How to get related object in angular JS template?

It should be noted that I'm very new to Angular.
I have the following JSON I get from Django Rest Framework:
api/movie/1
{
"id": 1,
"name": "Mr & Mrs Smith",
"actors": [
1,
2,
]
}
api/actor/1
{
"id": 1,
"name": "Angelina Jolie",
}
api/actor/2
{
"id": 2,
"name": "Brad Pitt",
}
I made a movie detail page following the Angular tutorial, using $resource.
This is the movie-detail.component.js
angular.
module('movieDetail').
component('movieDetail', {
templateUrl: 'static/partials/movie-detail/movie-detail.template.html',
controller: [ '$routeParams', 'Movie',
function MovieDetailController($routeParams, Movie) {
var self = this;
self.movie= Movie.get({movieId: $routeParams.movieId})
}
]
});
In the movie HTML template, I have access to the "actors" id via "$ctrl.movie.actors". But I can't figure out if there is a way to use this id to ask the server for the 'actor' object. Something like :
Actor.get({id}) to incorporate them in the movie details template.
TL;DR
What I can do now :
Actors:
<ul>
<ling-repeat="actor in $ctrl.movie.actors"> {{actor}} </li>
</ul>
result:
Actors
1
2
And I want something along the lines of :
Actors:
<ul>
<ling-repeat="actor in $ctrl.movie.actors"> {{actor.name}} </li>
</ul>
result:
Actors
Angelina Jolie
Brad Pitt
How can this be achieved ?
Thank you for any replies!
First of All you are using Angularjs 2 and Angularjs 2 is way different then Angularjs 1.*.
In this case its more javascript object manipulation rather Angularjs tricks
you can try below ways to achieve this
option1:
if you are able to edit your rest call in Django, change your code to return the movie and actor details in one js like this
{
"id": 1,
"name": "Mr & Mrs Smith",
"actors": [
1: {"id": 1,"name": "Angelina Jolie"},
2: {"id": 1,"name": "Angelina Jolie"},
]
}
And try to ng-repeat for you actor object under movie. or you can separate both the calls as your requirement.
option2:
You need to do a lazy loading at the time of loading. once your movie object is loaded, you need to call actor rest url to get all the actor object required and append them in the existing object if you are unable to make a single rest call.then in the template you can show actor detail as above.

Populate AngularJS {{expression}} within ng-repeat using a second array

I'm completely rebuilding my website (originally hacked together with Wordpress) using Laravel and AngularJS. It's been a massive learning experience and I think I'm nearly there but for one problem.
On my site 'schemes' (or courses) are made up of 'units' which are made up of 'lessons'. Retrieving this data is fine, using Eloquent I retrieve valid JSON like this made up example...
[
{
"id": "1", //Scheme Id
"title": "Sports",
"description": "This is a Sports course!",
"units": [
{
"id": "1",
"title": "Tennis",
"lessons": [
{
"id": "6",
"title": "Serving"
},
{
"id": "7",
"title": "Hitting the ball with top-spin"
}
]
},
{
"id": "2",
"title": "Athletics",
"lessons": [
{
"id": "1",
"title": "Long Jump"
},
{
"id": "2",
"title": "Hurdling Technique"
}
]
},
{
"id": "4",
"title": "Golf",
"lessons": [
{
"id": "4",
"title": "Pitching"
},
{
"id": "5",
"title": "Putting"
}
]
}
]
}
....
]
Separately I have a simple array of completed lesson ids for a particular user like this...
[2, 6, 8, 9] ///User has completed lessons with ids of 2,6,8 and 9
In my view I'm using nested ng-repeat loops like so...
...
<div ng-controller="SchemesController">
<div ng-repeat="scheme in schemes">
<h1>{{scheme.title}}</h1>
<div ng-repeat="unit in scheme.units">
<h3>{{unit.title}}</h3>
<div ng-repeat="lesson in unit.lessons">
<div>{{lesson.title}}: {{status}}</div>
</div>
</div>
</div>
</div><!--[end of ng-controller="SchemesController"]-->
....
SchemesController (v simple!) looks like this...
var app = angular.module('schemesApp', []);
app.controller('SchemesController', function($scope){
$scope.schemes=jsonData;
});
The problem is I have no idea how to populate the {{status}} field which I want to state simply 'Complete' or 'Incomplete. I investigated whether I could somehow add this info to my original array like this...
"lessons": [
{
"id": "6",
"title": "Serving",
"status": "Complete" //populated somehow
},
{
"id": "7",
"title": "Hitting the ball with top-spin",
}
]
but I got nowhere slowly. Is there a way to do this (I've played around with underscore.js and felt this could help?).
Or do I populate {{status}} from creating and calling a javascript function?!?
ANY help that anyone could offer would be incredible. I'm a school teacher and for some sadistic reason I find a bit of programming/web design a fun use of my spare time so I apologise if this is a stupid question. THANKS in advance!!!
btw if anyone has a better 'title' for this question then please let me know.
I'm assuming you don't need to persist the status back to the database...
This is where you're having the problem:
<div>{{lesson.title}}: {{status}}</div>
You really don't need to store the status in your data model, because it's just used for presentation purposes.
Let's say your array of completed lessons is defined like this:
$scope.completedLessons = [1, 2, 3, 4, 5] // Or however you'd assign it
You need to create a function in your scope like this:
$scope.isLessonCompleted = function(lessonId) {
return $scope.completedLessons.indexOf(lessonId) > -1;
};
Then you need to change the html from above to this:
<div>{{lesson.title}}: {{isLessonCompleted(lesson.id) && 'Complete' || 'Incomplete'}}</div>
If lessons are also a model and each lesson should have a status, which isn't a column/field in your table but is something you'll add logic to determine, you could add a custom model accessor by adding the following to your models/Lesson.php:
// Append custom accessor attributes
protected $appends = ['status'];
public function getStatusAttribute() {
// Add logic here
return 'Complete';
}
This way, when you use Eloquent to retrieve your data, you'll also see a status attribute as part of the object, so you could then access it as usual $lesson->status (PHP) or lesson.status (JS).
For more information, see the official Laravel documentation on accessors and mutators

Angular $http.get

I'm trying to show all lists and the tasks associated with each list. In my controller I have:
$http.get('api/list/').success(function (data) {
$scope.lists = data;
$scope.tasks = data[0].Task;
});
This works for the first item but of course data[0].Task needs to be dynamic. The problem I'm having is that this is being called once for each list. I tried using a variable but it gets reset to it's original value. I've also tried using a callback but no luck. I'm not sure what I'm overlooking or if I'm going about it all wrong.
Your best bet is to wrap the http.get in a factory and let it return new representations of your Lists that have the tasks in them. This way you get new references and it won't overwrite your existing objects. Essentially, you want the http.get to return new List objects in its success resolution.
After that, the controller gets the promise resolution, takes the new list object, and binds it into something thats on the scope. This will filter through to the rest of the page and let you preserve existing lists/tasks for the life of the page.
Your GET api/list/ request you probably return something like this:
[
{
"id": 1,
"name": "List #1",
"tasks": [
{
"id": 1,
"name": "Task #1 on List #1"
},
{
"id": 2,
"name": "Task #2 on List #1"
},
{
"id": 3,
"name": "Task #3 on List #1"
}
]
},
{
"id": 2,
"name": "List #2",
"tasks": [
{
"id": 1,
"name": "Task #1 on List #2"
},
{
"id": 2,
"name": "Task #2 on List #2"
},
{
"id": 3,
"name": "Task #3 on List #2"
}
]
}
]
This is assuming that you always want to return the associated tasks in an api/list/ command.
You then only need to call this once every time you want to refresh all lists and all tasks.
You should have a single controller which is bound to a view, in which your $http.get is called. It should set $scope.lists = data on success.
In your view you simply need two nested ng-repeat tags. For example, you could use unordered lists:
<div ng-controller="ListsController">
<ul>
<li ng-repeat="list in lists">
<ul>
<li ng-repeat="task in list.tasks">
</li>
</ul>
</li>
</ul>
</div>
I haven't used angular but I'm pretty sure this is all you need to do. A single AJAX call will populate a <li> element for each list, with nested <li> elements for each task belonging to that list.
If each list has a task and you want a list of them you can do this:
$scope.tasks = data.map(function(obj){return obj.task;})
map creates an array based on what the function returns for each list.

Categories

Resources