I am receiving object data from one JSON request, similar to this:
{
"slides": [
{
"image": "http://lorempizza.com/380/240",
"title": "Slide aa",
"description": "I owe you the description"
},{
"image": "http://lorempizza.com/380/240",
"title": "Slide bb",
"description": "I owe you the description"
}
],
"title": "Interesting object",
"description": "Lorem ipsum dolor sit amet consectetur."
}
I want to use two different views. One for the slides and another one for the title and description of the object, everything on the same page. Why? Because I'm using ui views to display them.
index.html
<div ui-view="slider"></div><!-- Here is the slider -->
<main ui-view role="main"></main><!-- Here is title and desc -->
The problem is here:
slider.html
<div ng-controller="InterestingObjectCtrl">
<!-- Display slider -->
</div>
main.html
<div ng-controller="InterestingObjectCtrl">
<!-- Display title and desc -->
</div>
InterestingObjectCtrl.js
InterestingService.get()
.success(function(data) {
$timeout(function() {
$scope.$apply(function() {
$scope.interestingObject = data;
});
});
})
The InterestingObjectCtrl controller will load twice the same JSON making useless HTTP requests.
What would be the right way or "angular way" to solve this?
Setting a flag (like if ($scope.requestAlreadyMade) return;) will definitely won't work.
The HTTP requests may not be a big deal caching the $http service, but that is far from the point of this problem.
Calling the controller as a wrapper, like the following example, will make it be loaded from every page on the website, which is even worst.
<div ng-controller="InterestingObjectCtrl">
<div ui-view="slider"></div><!-- Here is the slider -->
<main ui-view role="main"></main><!-- Here is title and desc -->
</div>
<div ng-controller="MainCtrl">
<div ui-view="slider"></div><!-- Here is the slider -->
<main ui-view role="main"></main><!-- Here is title and desc -->
</div>
load data once in MainCtrl and breadcast it to child controllers:
InterestingService.get()
.success(function(data) {
$scope.$broadcast('dataloaded', data);
});
InterestingObjectCtrl.js
$scope.$on('dataloaded', function (data) {
// do what you want
});
Related
A few months ago I've be created the topic: Try to render two templates in same View (Ui-Router), where I asked about how to render multiple views in the same page. My objective with this was created a web app as an desktop one, with views to minimize, maximize, close and stuff like that.
Well, my app is ready but I'm getting a problem, when I up my app to the production some computers are taking a long time to render all the Views. In the image bellow we can se a lot of requisitions that server take to return my templatesURL's.
There is a way to avoid this ? I was looking for an lazy load to templateURL but I Didn't find any. :(
This plunkr was the approach what I used. I have only one state for all my Views (My current app.config has 103 Views):
routerApp.config(function($stateProvider) {
$stateProvider.state('mainState', {
views: {
'CompanyView': {
templateUrl: 'Company.html'
},
'PeopleView': {
templateUrl: 'People.html'
},
.....
....
}
})
});
Introduction
The way you approached the solution is the cause of the problem you're facing, because you have too many views for a single state, it'll end up having to load all of them in order to set that state, so every time you access your state, ui-router has to load every template in order to set the views. It might not cause problem for a few number of templates, but, for larger numbers like yours it is definitely an issue.
Ng-Templates
You can try to cache your templates in your page using <script type="text/ng-template"... in order to prevent the loading time, it's a good practice by the way. Usually it's part of the production build optimization, load all templates in the template cache, so that the application load time decreases significantly provided that you don't have to wait for an http call to load a page. It will indeed increase the performance in your case, but I don't have a benchmark that ensure if it'd be enough for your scenario.
Component Based Solution
Anyhow, you can always implement interface components to behave the way you want, optimized in such a way that it doesn't have to load one hundred templates to show a single panel for the user.
My suggestion is, instead of using ui-router, use a component based solution, create a directive component to hold the panel content of each window and its behavior; and use a controller to manage the state of opened and closed panels, holding and managing each opened panel in a list and so on. For example:
<nav>
<button ng-click="openPanel({title: 'My Panel Title', templateUrl: 'myPanel.html'>">
Open myPanel
</button>
<nav>
<main>
<panel ng-repeat="panel in openedPanels"></panel>
</main>
The following snippet implements this approach using bootstrap 4 css, each panel is a bootstrap card, and it has a list of panels it can open and on click of a nav list it adds the respective panel to the opened panels list where angularjs can render it on the html using ng-repeat. This way, only the opened window will be rendered, therefore, only the opened window template will be loaded.
Disclaimer: This is a very simple example implemented not using the best practices available out there. If you intend to use this approach you should implement it based on your application to fit better the needs of your architecture, this one is not a complete functional component, it's just an example for the sake of the demonstration.
angular.module('app', [])
.controller('PanelsCtrl', function($scope) {
// available windows to be opened
$scope.panels = [
{ title: 'Window 1', templateUrl: 'window1.html' },
{ title: 'Window 2', templateUrl: 'window2.html' }];
// all currently opened panels
$scope.openedPanels = [];
// opens a panel (a.k.a, adds a panel
// to the opened panels list)
$scope.openPanel = function(panel) {
if ($scope.openedPanels.indexOf(panel) === -1)
$scope.openedPanels.push(panel);
};
// close a panel (a.k.a, removes a panel
// from the opened panels list)
$scope.onClosePanel = function(panel) {
$scope.openedPanels.splice($scope.openedPanels.indexOf(panel), 1);
};
})
.directive('window', function($templateRequest, $templateCache, $compile) {
return {
restrict: 'E',
scope: {
panel: '=',
onClosePanel: '&'
},
template: `
<div class="card">
<h4 class="card-header">
<span>{{ panel.title }}</span>
<button
ng-click="onClosePanel(panel)"
type="button"
class="close"
data-dismiss="modal"
aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</h4>
<div class="card-body">
<ng-include src="panel.templateUrl"></ng-include>
</div>
</div>
`
}
})
// example controlelr to be used with ng-controller
.controller('Window1Ctrl', function($scope) {
$scope.window1Prop = 'This is a property from Window1Ctrl'
})
#import 'https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/css/bootstrap.min.css'
<div ng-app="app">
<div class="container" ng-controller="PanelsCtrl">
<div class="row">
<div class="col-sm-3">
<ul class="nav flex-column">
<li class="nav-item" ng-repeat="panel in panels">
<a class="nav-link active" href="#" ng-click="openPanel(panel)">
{{ panel.title }}
</a>
</li>
</ul>
</div>
<div class="col-sm-9">
<window ng-repeat="panel in openedPanels" panel="panel" on-close-panel="onClosePanel(panel)">
</window>
</div>
</div>
</div>
<!-- NG-TEMPLATES -->
<script type="text/ng-template" id="window1.html">
<div ng-controller="Window1Ctrl">
<b>{{panel.title}}</b>
<h5>window1Prop: {{ window1Prop }}</p>
</div>
</script>
<script type="text/ng-template" id="window2.html">
<em>{{panel.title}}</em>
</script>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.0/angular.js"></script>
Basically, I am trying to figure out a way to return a selected value (say, from a modal) to a specific model, when that model may be dynamically generated via ng-repeat.
So this is what my data looks like:
{
"title": "Example Title",
"enabled": true,
"thumbnailImage": "file1.png",
"content": [{
"order": 0,
"type": "wysiwyg",
"content": "<div>This is wysiwyg content!</div>"
},
{
"order": 1,
"type": "image",
"content": "file2.png"
}],
"id": 1
}
Now, the user can keep generating additional content by clicking a button that adds a new object with their selected type to the 'content' array. So an article may have one content image, or a hundred, or none (but will always have the 'thumbnailImage').
Using https://github.com/nervgh/angular-file-upload, I have added a modal which allows users to either upload a new image file, or select an existing image from the server. So my HTML looks a bit like this (for the ng-repeat content - similar for thumbnailImage):
<div class="Control">
<label>Content</label>
<div class="panel panel-default" ng-repeat="block in article.content">
<div class="panel-body">
<!-- ... other content types here ... -->
<div ng-if="block.type == 'image'">
<input type="text"
ng-model="block.content"
ng-if="block.type == 'image'"
class="form-control"
>
<a ng-click="selectImage()" class="btn btn-primary">Select image</a>
<button type="button" ng-if="!!activeImage" ng-click="block.content = activeImage" class="btn btn-secondary">Insert image</button>
</div>
</div>
</div>
</div>
selectImage() opens the modal, populates it with all the files already uploaded to the server, and has a drag-and-drop section allowing users to upload new images.
The modal looks like this:
<div ng-if="showFileManager" nv-file-drop uploader="uploader">
<div class="row">
<div ng-repeat="file in files" class="col-3">
<img ng-src="/{{ file.container }}/{{ file.name }}" ng-click="setActiveImage( file )">
</div>
</div>
<div ng-show="uploader.isHTML5">
<div nv-file-over uploader="uploader">
Drag and drop images to upload
</div>
</div>
</div>
What I am trying to do is on selecting an image, instantly populate the text input related to the 'selectImage()' button.
At the moment, clicking an image fires 'setActiveImage()', which is:
$scope.setActiveImage = function( file ) {
var filePath = 'http://localhost:3000/uploads/' + file.container + '/' + file.name;
$scope.activeImage = filePath;
};
And then the 'Insert image' button next to the 'Select image' button under each image updates the model. However, I'm hoping to be able to do that in the modal so all the user has to do is click the 'Close' modal button and the field is populated.
Does this make sense, and is it possible?
The way I get a specific item of ng-repeat is to pass it to a function in my view.
<html element ... useSelectedFile(file)></html element>
Then in your controller:
$scope.useSelectedFile = useSelectedFile;
function useSelectedFile(file){
//do stuff
//call function
//set scope item in view to file
}
Trying to understand filers in AngularJS. I have the following code seeding articles from JSON to the home page.
<div ng-repeat='article in articlesList | filter:filters'>
<span>{{article.category}}</span>
<h1>{{article.title}}</h1>
<p>{{article.short_desc}}</p>
</div>
I also have navigation, which I want to act as a filter, so instead of going to a new page, it will just filter article out from home page by category.
<ul>
<li>
<a ng-click="filters.category = 'home'">Home</a>
</li>
<li>
<a ng-click="filters.category = 'activity'">Home</a>
</li>
</ul>
My JSON looks like this
{
"article1": {
"id":"1",
"category": "home",
"title": "Lorem ipsum dolor sit amet, consectetur adipisicing elit.",
"short_desc": "Nemo enim ipsam voluptatem quia voluptas sit",
"images": [
"img/article-img1.jpg"
]
},
"article2": {
"id":"2",
"category": "activity",
"title": "Lorem ipsum dolor sit amet, consectetur adipisicing elit.",
"short_desc": "Nemo enim ipsam voluptatem quia voluptas sit",
"images": [
"img/article-img2.jpg"
]
},
}
I am fetching data from it with this controller
app.controller('HomeCtrl', function($scope, $http) {
$http.get('pages/articles.json')
.success(function(data) {
$scope.filters = {}
$scope.articlesList = data;
})
.error(function(data) {
alert('Something is wrong with JSON')
})
});
I am trying to figure out the right way to set up the Filter. Looking for a solution or some article dealing with similar problem.
Or maybe filtering is not the best option for this task. I appreciate any suggestions/best practices.
I kept making edits to my comment, so I figured I would just make an answer to the question.
In order to use your filter, you're going to need to do something like this:
<div ng-repeat='article in articlesList | filter:categoryFilter'>
<span>{{article.category}}</span>
<h1>{{article.title}}</h1>
<p>{{article.short_desc}}</p>
</div>
And then, in your navigation bar (assuming they have the same scope, if not then the answer would be mildly different), you would do something akin to this:
<ul>
<li>
Home
</li>
<li>
Press
</li>
</ul>
I don't have any data to test this out thoroughly. If it doesn't work, please provide a few (~5) objects in the JSON array so that I can test it out on a plunkr. Also the HTML in your body tag would help greatly.
Here is the API documentation for filters, with a few examples that may help you understand filters a little more.
EDIT
Since you updated your information, I got This Plunkr working.
I'll break each part down:
HTML
<!DOCTYPE html>
<html ng-app="home">
<head>
<script data-require="angular.js#*" data-semver="1.3.0-beta.5" src="https://code.angularjs.org/1.3.0-beta.5/angular.js"></script>
<link rel="stylesheet" href="style.css" />
<script src="script.js"></script>
</head>
<body ng-controller="dataCtrl">
<nav>
<a ng-click="categoryFilter.category='Home'">Home</a>
<a ng-click="categoryFilter.category='Activity'">Activity</a>
<a ng-click="resetCategoryFilter()">Reset</a>
<span style="float:right;"><input type="text" ng-model="categoryFilter.title"></span>
</nav>
Your Current Filters Applied Are: {{categoryFilter}}
<div ng-repeat="item in data | filter:categoryFilter">
{{item.title}}
</div>
</body>
</html>
Now for the HTML, you'll see that I have just 1 controller being set. This makes it so that all of these items are in the same parent. If you have a wrapper DIV, I suggest putting the ng-controller on that div.
You'll notice that I didn't put the elements in an <ul> or <li> tags. That's because I forgot. It should still work inside them.
That being said, You'll notice my code uses the $scope variable throughout. You seem to have a basic working knowledge of the $scope variable, so I'll leave it mostly alone. The one thing is the function I added to the scope, the resetCategoryFilters() function. It's a simple enough function: it takes all of the keys and sets their values to undefined so that the filters object is reset to base. I initialized the categoryFilters in the scope because... well I'm not sure, it just bothers me if I don't.
Javascript
// Code goes here
var app = angular.module('home', []);
app.controller('dataCtrl',function($scope){
$scope.categoryFilter = {
category:undefined,
title:undefined,
short_desc:undefined
};
$scope.resetCategoryFilter = function(){
for(var x in $scope.categoryFilter){
$scope.categoryFilter[x]=undefined;
}
}
$scope.data = [
{
"id":"1",
"category": "Home",
"title": "20 Useful Things around your House",
"short_desc": "Nemo enim ipsam voluptatem quia voluptas sit",
"images": [
"img/article-img1.jpg"
]
},
{
"id":"2",
"category": "Activity",
"title": "Carpe the Diem",
"short_desc": "Nemo enim ipsam voluptatem quia voluptas sit",
"images": [
"img/article-img2.jpg"
]
}
];//Here, instead of hard-coding the data like I did, use the $http service to get the values
});
You'll notice where I simply hard-coded the data. Since I was using Plunkr, I could have set that data off in a different page, but it was just easier to have it there. Use the $http service and the .get method to populate the $scope.data array.
Remarks About Your Javascript
I noticed that the indexes in the JSON you were giving were hard coded. I deleted them, making the data object an Array of objects, as opposed to an object of objects. Easy to loop through an array, difficult to loop through an object.
Hopefully this answers your question.
I am using data model having "edit" field. And based on "edit" value i either use text box or label. I want that once user click "ok" after edit then text box should change to label. However somehow it is not working. Following is jsfiddle example. Please help.
http://jsfiddle.net/dilipkumar2k6/zEdY8/2/`">JSFiddleLink
I've modified your Fiddle, you have to change it to something like this
<div ng-app="" ng-controller="controller" id="contentsDivID">
<div ng-repeat="chapter in chapters">
<ng:switch on="chapter.edit">
<div ng:switch-when="true">
<input type="text" ng-model="chapter.title" ng-model="chapter.title" />
<a ng-click="addChapter($index);"><i class="icon-ok"></i></a>
</div>
<div ng:switch-when="undefined">
<label>{{chapter.title}}</label>
</div>
</ng:switch>
</div>
</div>
Then your JavaScript code to this
function controller($scope)
{
$scope.chapters=[
{
"_id": "567456746",
"title": "Growth and Development",
"type": "section",
"edit":true
},
{
"_id": "34563465345",
"title": "Links between Areas of Development",
"type": "section"
},
{
"_id": "8776545645",
"title": "Characteristics of Development",
"thumbnail": "/xopus/images/templates/media-1-top-right.png",
"type": "section"
}
];
$scope.addChapter = function(index) {
$scope.chapters[index].edit = undefined;
}
}
You may want to checkout angular concepts documentation - it should give you some highlight on how angular tracks changes to the model.
I have this scenario, I am loading part of HTML (which has AngularJS directives) dynamically via script and I see AngularJS is not getting activated.
here is the example I am looking at. Is there anyway I can tell AngularJS to start bind on document ready? Loading an aspx page containing this widget1 content via a iframe seems to work but I am trying to avoid iframe and use client side script.
Appreciate any help.
<body ng-app>
main content page
<br />
<!-- widget1 -->
<b>Widget1</b>
<div id="widget1">
<!-- load below div via jquery/or any method from a remote html file-->
<script type="text/javascript">
$("div#widget1").load("/widgetsfolder/widget1.htm");
</script>
</div>
widget.htm file has below content.
<div ng-controller="TodoCtrl">
Total todo items: {{getTotalItems()}}
<ul class="unstyled">
<li ng-repeat="todo in todos">
<input type="checkbox" ng-model="todo.done" />
<span class="done-{{todo.done}}">{{todo.text}} </span></li>
</ul>
</div>
my controller code below.
`function TodoCtrl($scope) {
$scope.totalItems = 4;
debugger;
$scope.todos = [{
text: 'learn angularjs',
done: false
}, {
text: 'implement angularjs',
done: false
}, {
text: 'something else',
done: false
}, ];
$scope.getTotalItems = function () {
return $scope.todos.length;
}
}`
sample code here
http://jsfiddle.net/devs/RGfp4/
Apero's answer describes what is going on. I believe you are going to want to use ng-include. Your html would look something like this:
<body ng-app>
main content page
<br />
<!-- widget1 -->
<b>Widget1</b>
<div ng-include="'/widgetsfolder/widget1.htm'">
</div>
</body>
AngularJS evaluates the scope and renders the page after it is loaded.
Here, your js script loads the widget html but after Angular already compiled the scope etc.
I believe this will not work this way.
You can use angulars ngINclude to fetch outside documents, but I don't suggest it, it can be buggy. You can get the partials using either $http or $resource, this will fetch the data and compile the angular directives inside.
If you want to load the script using some other method, you can store the data as a string inside the controller and use a $compile directive in order to execute the angular code inside it.