Hello Angular experts,
I have to preload a certain dataset (factory call to the database) to the controller. I don't use angular views so stateProvider or routeProvider cannot be used to resolve. Basically I need the dataset readily available before loading the controller.
Is there a way to achieve this?
I have a controller and a view. The view also has a widget. The widget has an attribute that expects a dataset. By the time the controller is done fetching data the view is already rendered so the widget input parameters are empty. So I need the widget dataset to be filled much before getting to the controller. By the way the app.run solution doesn't work as there is a promise involved.
You can't say as before loading controller, i correct it with before binding controller
angularjs has app.run and i think you know it, it work just when application run (first time) and every time you refresh it.
var app = angular.module("app", []);
app.run(function($http, $rootScope) {
$http.get("url").then(function(response){
console.log(response.data)
$rootScope.data = response.data; // as global scope
})
})
app.controller('ctrl', function($scope) {
$scope.$watch('data', function(newValue, oldValue) {
if(newValue){
console.log(newValue) // you will get `rootscope.data` when it's ready
}
})
})
You can add factory to the app run too.
please fill free to ask question.
Based on your comment, what you are trying to do shouldn't require any changes to the Controller.
What you are actually trying to do is delay rendering of the widget component until you have data that the widget needs. This is commonly handled by ng-if in your HTML.
For Example (pseudocode):
<div ng-if="myData">
<Widget input="myData"></Widget>
</div>
Angular will not render this div until myData has a value, and all your Controller has to do is make sure myData is added to $scope.
Related
Does anyone know why my $scope.userData isn't rendering in my View when using angular-route and Angular's $location service together?
My Situation:
In the code example below, I have an index page which iterates through users, allUsers, using angular's ng-repeat, as follows: ng-repeat="user in allUsers [note, for simplicity, I have left out the function in my controller snippet below that delivers allUsers, but is working correctly].
For each user, there is an edit button, which should run the $scope.findOne function, passing in the user itself as a parameter.
In my $scope.findOne function, a new partial is loaded using the $location service, and $scope.userData is updated to reflect this user. I want to access $scope.userData now on the new partial (my problem).
Problem:
The new partial loads, the user object seems to pass along fine, however $scope.userData becomes inaccessible on the front-end.
PseudoCode (What I'd Like):
// Iterate through allUsers (`user in allUsers`)
// When Edit button is clicked for `user`:
// Fire off `findOne` function passing in `user`
// Set `$scope.userData` to `user`
// Load `html/edit.html` partial
// Show `$scope.userData` on the page (should be `user` info):
PseudoCode (What's Happening Instead):
// Iterates through allUsers (`user in allUsers`)
// When Edit button is clicked:
// Fires off `findOne` function and passes `user`
// Sets $scope.userData` (console logging it after shows changes)
// Loads `html/edit.html`
// **DOES NOT show `$scope.userData` and appears empty.**
Angular Module Setup:
Both the /index.html page and the /edit.html page appear to be setup correctly with the appropriate controller assignments:
// Define Module:
var app = angular.module('app', ['ngRoute']);
// Define Routes:
app.config(function($routeProvider) {
$routeProvider
.when('/', {
templateUrl: 'html/index.html', // root route partial
controller: 'userController',
})
.when('/edit/:id', {
templateUrl: 'html/edit.html', // edit page partial
controller: 'userController',
})
.otherwise({
redirectTo: '/',
})
});
Angular Controller:
My angular controller (allUsers function now shown) shows the findOne function I'm trying to access, along with the use of the $location service:
app.controller('userController', ['$scope', 'userFactory', '$location', function($scope, userFactory, $location) {
$scope.findOne = function(myUser) {
$scope.userData = myUser;
console.log($scope.userData); // shows changes...(?)
$location.url('/edit/' + myUser._id); // redirects and partial loads OK...
};
}]);
In the following Index HTML example, clicking the Edit button triggers the $scope.findOne function fine, redirects the page and updates $scope.userData (console logs OK), but on the partial itself, $scope.userData does not seem to reflect the changes (what gives?):
Index Page HTML:
<div ng-repeat="user in allUsers">
<h1>{{user.username}}</h1>
<button ng-click="findOne(user)">Edit</button> <!-- runs findOne -->
</div>
Edit Partial HTML:
<h2>Edit:</h2>
{{userData}} <!-- $scope.userData displays nothing? -->
Summary:
Why does {{userData}} in my Edit Partial HTML not show anything?
I found a solution to my needs by creating a new controller, and having this other controller take over when the /edit.html page is loaded. The controller queries the DB on page load for the user based on req.params.id. This worked and I was able to carry on, but I have two separate controllers, when instead, I'd like to manage all CRUD operations from a single controller for my Users...(is this bad practice?)
I had initially tried the solution above, but got stuck with the $scope issue as outlined here in this write up. Any ideas what I'm doing wrong in getting my above data to render in my View? Your time and patience reading is genuinely valued! Thank you Stack Community!
My guess (mostly b/c I use angular-ui-router) is that when you navigate to the edit partial, a new controller is being instantiated, so the data on the $scope is lost.
Here are two common approaches I've seen:
Do what you described above to query the server for the user data using route params. Consider enabling the caching option for $http so it doesn't have to hit the server each time... but then you need to invalidate the cache when the data changes
Create a service that queries the server for the list of users. This service makes the user list available to the controller, and returns an individual user for editing.
I am new in angular js. I am working with web Sql in a test project for learning purpose. My insert operation is working fine. My problem is when i fetch all the records then i can't store it in $scope.todos=results.rows
My code is
tx.transaction(sql,[],myCallback)
function myCallback(tx,results)
{
console.log(results.rows); // shows objects in console
$scope.todos=results.rows;
}
In view the todos is not working with ng-repeat.
It might help to show all of your controller, or service. If it's a controller make sure you included the $scope parameter for example:
.controller('MyController', function ($scope){
}))
If you are doing this inside a service, save it in a variable inside the service, then from a controller where the "$scope" is available include your service then call your variable containing the results. This will also be helpful if you need to share those results with other controllers at a later time.
.service('MyDataService',function(){
var todos;
return {
.... set and get functions to access variable ...
getTodos: function() {
return todos;
}
};
});
And in controller
.controller('MyController', function ($scope, MyDataService){
$scope.todos = MyDataService.getTodos()
}))
There are also ways to get the $scope working in a service but I don't think it's a great idea.
According to the below image:
I want to improve components communication method....I think this way is not efficient.
When clicking tabsetComponent to emit event, then parent controller catch this event, changing rootScope variable. Using $watch rootScope variable in tableComponent to trigger http fetch data function...
Could anyone has better and efficient way to communicate sibling component?
The accepted AngularJS method for communication between components is using component attributes for communication.
<div ng-controller="rootCtrl as vm">
<tab-set-component tsc-click="vm.fn($event, data)">
</tab-set-component>
<table-component="vm.tableData">
</table-component>
</div>
For more information on defining component attributes, see AngularJS Comprehensive Directive API -- isolate scope
Best practices
Only use .$broadcast(), .$emit() and .$on() for atomic events
Events that are relevant globally across the entire app (such as a user authenticating or the app closing). If you want events specific to modules, services or widgets you should consider Services, Directive Controllers, or 3rd Party Libs
$scope.$watch() should replace the need for events
Injecting services and calling methods directly is also useful for direct communication
Directives are able to directly communicate with each other through directive-controllers
-- AngularJS Wiki Best Practices
Controller Example
In your html, you use vm.fn that came from root controller right? So your advice is it should call the click method defined root controller, the click method will trigger http request function defined on the rootScope, then get table component datas, then bind the datas on table component attribute.
As example:
angular.module("myApp", []);
angular.module("myApp").controller("rootCtrl", function($http) {
var vm = this;
vm.tableData = { /* initial data */ };
//click handler
vm.fn = function(event, url) {
$http.get(url).then (function onFulfilled(response) {
vm.tableData = response.data;
}).catch (function onRejected(response) {
console.log(response.status);
});
};
});
The above example avoids cluttering $rootScope. All the business logic and data is contained in the controller.
The controller sets the initial data for the table-component, receives click events from the tab-set-component, makes HTTP requests, handles errors, and updates the data to the table-component.
UPDATE -- Using Expression Binding
Another approach is using expression binding to communicate events:
<header-component view="root.view" on-view-change="root.view = $event.view">
</header-component>
<main-component view="root.view"></main-component>
For more information, see SO: How to pass data between sibling components in angular, not using $scope
With version 1.5.3, AngularJS added the $onChanges life-cycle hook to the $compile service.
app.component("mainComponent", {
template: "<p>{{$ctrl.count}}",
bindings: {view: '<'},
controller: function() {
this.count = 0;
this.$onChanges = function(changesObj) {
if (changesObj.view) {
this.count++;
console.log(changesObj.view.currentValue);
console.log(changesObj.view.previousValue);
console.log(changes)bj.view.isFirstChanged());
};
};
}
});
For more information, see AngularJS Comprehensive Directive API Reference -- Life-cycle hooks
See also SO: AngularJs 1.5 - Component does not support Watchers, what is the work around?
How do I get a dynamically loaded template and controller injected? After loading my .html partial and .js controller, I would assume the next step is $injector? But how do I use it? something like this...?
My progress so far:
http://plnkr.co/edit/rB5zteYTZ2L1WB5RzlHg
data is returned from a $http.get()
var $injector = angular.injector(['ng']);
$injector.invoke(function($rootScope, $compile, $document) {
$compile(data)($rootScope);
$rootScope.$digest();
});
What format does the Controller.js file need to be in for the injector/compiler to wire it up correctly? Can I simply do what I did in Plunker?
Controller1.js
app.controller('Controller1', function ($scope, $famous) {
console.log("Inside Controller1");
});
Note: I am specifically trying to avoid using requirejs, ng-route, ui-route, ocLazyLoad, etc. I would like to understand the very basics of what these packages accomplish for routing and dynamic loading of a view/controller.
I am not sure if I totally understand your question but it looks like you want to load views and controllers dynamically. I am using a combination of angular ui router and angularAMD. It works very smoothly and with that approach you get a nice separation and on-demand loading.
From angular ui router webpage:
templateUrl: "partials/state1.list.html",
controller: function($scope) {
$scope.items = ["A", "List", "Of", "Items"];
}
With that configuration the specified controller will get loaded and connected to the state1.list.html.
Does that help?
I've seen this and this but it seems like there might be a simpler way.
In my view I have several menu options that are controlled through permissioning - i.e., not everyone can see a "Dashboard" view. So in my menu option in my view I have something like the following:
<li ng-show="validatePermission('Dashboard')">Dashboard</li>
In my controller I have a validatePermission method defined where it is looking at the permissions of the current user. For example:
$scope.validatePermission = function(objectName) {
if $scope.allPermissions......
Also in my controller I'm loading those permissions via an $http call:
$http.get('permissions/' + userid + '.json').success(function(data) {
$scope.allPermissions = data;....
The issue is that $scope.allPermissions doesn't get loaded before the view makes the call to validatePermission. How can I wait for allPermissions to be loaded before the view renders?
You ask:
How can I wait for allPermissions to be loaded before the view renders?
To prevent the entire view from rendering, you must use resolve. You don't have to use the promise library though, since $http returns a promise:
var app = angular.module('app');
app.config(function ($routeProvider) {
$routeProvider
.when('/', {
templateUrl : 'template.html',
controller : 'MyCtrl',
resolve : MyCtrl.resolve
});
});
function MyCtrl ($scope, myHttpResponse) {
// controller logic
}
MyCtrl.resolve = {
myHttpResponse : function($http) {
return $http({
method: 'GET',
url: 'http://example.com'
})
.success(function(data, status) {
// Probably no need to do anything here.
})
.error(function(data, status){
// Maybe add an error message to a service here.
// In this case your $http promise was rejected automatically and the view won't render.
});
}
}
But if you simply want to hide the dashboard <li>, then do as Joe Gauterin suggested. Here's a very simple example plunkr if you need it.
Have the validatedPermission function return false when allPermissions hasn't been loaded. That way the element with your ng-show won't be displayed until allPermissions has been loaded.
Alternatively, put an ng-show="allPermissions" on the enclosing <ul> or <ol>.
You can also specify on your routecontroller a resolve object that will wait for that object to resolve prior to rendering that route.
From the angular docs: https://docs.angularjs.org/api/ngRoute/provider/$routeProvider
resolve - {Object.=} - An optional map of dependencies which should be injected into the controller. If any of these dependencies are promises, they will be resolved and converted to a value before the controller is instantiated and the $routeChangeSuccess event is fired. The map object is:
key – {string}: a name of a dependency to be injected into the controller.
factory - {string|function}: If string then it is an alias for a service. Otherwise if function, then it is injected and the return value is treated as the dependency. If the result is a promise, it is resolved before its value is injected into the controller.
A google group reference as well: https://groups.google.com/forum/#!topic/angular/QtO8QoxSjYw
I encountered an similar situation, you might also want to take a quick look at
http://docs.angularjs.org/api/ng/directive/ngCloak
if you're still seeing a "flicker" effect.
As per the angularjs documentation:
The ngCloak directive is used to prevent the Angular html template from being briefly displayed by the browser in its raw (uncompiled) form while your application is loading. Use this directive to avoid the undesirable flicker effect caused by the html template display.
Wrapping the code in ng-if fixed the issue for me:
<div ng-if="dependentObject">
<!-- code for dependentObject goes here -->
</div>