I'm in the process of converting a rather big application written in AngularJS to an Angular application using the #angular/upgrade module. For now, every component from AngularJS is running as expected in the Angular app. The app is being bootstrapped like this:
export class AppModule {
constructor(
private upgradeModule: UpgradeModule,
) {}
ngDoBootstrap() {
this.upgradeModule.bootstrap(
document.body,
['myApp'],
{ strictDi: true },
)
}
}
However, I've started to rewrite components in Angular and I've run into problems trying to get those downgraded components to work. I'm using ui-router to map components to states like this:
function config($stateProvider) {
$stateProvider.state('some-state', {
parent: 'app',
views: {
content: {
component: 'someComponent'
}
},
url: '/some-state'
})
}
The rewritten component is being downgraded like this:
angular
.module('myApp')
.directive(
'someComponent',
downgradeComponent({ component: SomeComponent }),
)
SomeComponent is just a regular Angular component.
It shows up fine, the template is being rendered and everything, however, change detection doesn't seem to be working. The change detector seems to run once shortly after the view is being rendered, after that however, it doesn't ever run again unless i call it manually. NgZone.isInAngularZone() returns false if run anywhere within the downgraded component, I'm not sure if this is intended or not. If I inject ChangeDetectorRef and run detectChanges on any methods that change the fields of the component, the updates are being detected and rendered just fine. However, it would be way too much work in the long run to append an (input)="changeDetectorRef.detectChanges()" to every input, and call detectChanges() in pretty much any function that does anything.
Is there anything I am missing? If I haven't provided enough information, I can try to get a minimal reproduction of the problem running, this would probably be a bit of work though, so I want to ask like this first.
Ok, so the answer turned out to be something really simple. While I upgraded everything individually, I copied over most of the old index.html, which still contained ng-app="myApp" on the body tag.
This caused the AngularJS app to bootstrap itself in the HTML instead of the Angular app handling the bootstrapping, which prevented the AngularJS app from running in the NgZone. I'm leaving this here for anyone who makes the same mistake. In short, make sure you've removed the old ng-app-tag from your index.html.
Related
In a big application, i now need to access some data (json api call) from asynchronously (before, those data were accessed synchronously).
I would like not to have to change components implementation to now handle this async behaviour (too risky).
I thought about $routeProvider's "resolve" feature (with promises) that helps to abstract this async behaviour out from the components/controllers.
Unfortunately i am absolutely not using routing in any way.
Is there an implementation for that? If not, where could i start?
EDIT:
how i was before (jsonData were not loaded synchronously but it was transparent thanks to systemJS features, SystemJS, Which we are throwing in garbage now, hence the question):
https://plnkr.co/edit/BSreVYNAr7sijYwEpb5G?p=preview
How i am now:
https://plnkr.co/edit/WnX0ynOqkZsmEzIxl1SI?p=preview
Watch the console to see an example of the problems that can occur now.
I kind of made it work by going like that, but i'm not completely satisfied with it (watch the config block):
https://plnkr.co/edit/P9Rv6rnboFlaFZ0OARNG?p=preview
$provide.value('$provide', $provide);
$routeProvider.otherwise({
template: '<test></test>',
controller: function ($provide, myJsonData1) {
$provide.value('myJsonData', myJsonData1);
},
resolve: {
myJsonData1: function(){
return getMyJsonData();
}
}
});
Hope this helps :)
I have 2 states in coffeescript...
stateProvider.state 'test',
...
resolve:
user: (LongRunning)->
LongRunning.authenticate().then ->
console.log("We are authenticated!")
stateProvider.state 'test.child',
...
resolve:
other: (AfterAuth)->
AfterAuth.soSomethingWithAuth().then ->
console.log("We are done!")
Of course this doesn't work because the child resolve is kicked off before the parent's auth method has been resolved. This means the second function won't be authenticated and cause the entire state to fail.
Now it doesn't need to necessarily be part of the State path but it needs to be fully completed by the time the resolve functions are called.
How would I make sure the function from parent is fully resolved before calling the method in child?
Bad (?) Solution
The one answer I have been able to come up with is to use the manual bootstrap process. However, this is tedious since I would need to rewire all my services. Is there anyway I can do it inside Angular?
Do you use AngularUI Router for Angular 2 or AngularJS? I think that is AngularJS on the fact that you use coffeescript and AngularUI Router. This is Angular tag not AngularJS.
Anyway in AngularUI Router one resolver can depends on another one. Something like that:
function firstStep($stateParams, resolveStatus) {
return $q.resolve();
}
resolve: {
firstStep: firstStep,
secondStep: ['firstStep', function (firstStep) {
...
}]
}
Having trouble with always rendering google maps in my Ionic app. When I first land on a view from a list of items on the previous view, the map always renders in its complete state. However, if I go back to the previous view and tap a different business, or even the same one, it appears as if the map is only rendering 25% of the complete map. I'm having this issue on both the emulator and on my iPhone.
Example
Code
getData.getBusinesses()
.then(function(data) {
// get businesses data from getData factory
})
.then(function(data) {
// get businesses photo from getData factory
})
.then(function(data) {
// get some other business stuff
})
.then(function() {
// get reviews for current business from separate async call in reviews factory
})
.then(function() {
// instantiate our map
var map = new GoogleMap($scope.business.name, $scope.business.addr1, $scope.business.city, $scope.business.state, $scope.business.zip, $scope.business.lat, $scope.business.long);
map.initialize();
})
.then(function() {
// okay, hide loading icon and show view now
},
function(err) {
// log an error if something goes wrong
});
What doesn't make sense to me is that I'm using this exact code for a website equivalent of the app, yet the maps fully load in the browser every time. The maps also fully load when I do an ionic serve and test the app in Chrome. I did also try returning the map and initializing it in a following promise, but to no avail.
I've also tried using angular google maps, but the same issue is occurring. I think I might want to refactor my gmaps.js (where I'm creating the Google Maps function) into a directive, but I don't know if that will actually fix anything (seeing as angular google maps had the same rendering issue).
I don't think the full code is necessary, but if you need to see more let me know.
EDIT
It seems that wrapping my map call in a setTimeout for 100ms always renders the map now. So I guess the new question is, what's the angular way of doing this?
I'm seeing similar issues with ng-map in Ionic. I have a map inside of a tab view and upon switching tabs away from the map view and back again, I would often see the poorly rendered and greyed out map as you describe above. Two things that I did that may help fix your issue:
Try using $state.go('yourStateHere', {}, {reload: true}); to get back to your view. The reload: true seemed to help re-render the map properly when the map was within the tab's template.
After wrapping the map in my own directive, I found the same thing happening again and wasn't able to fix it with the first suggestion. To fix it this time, I started with #Fernando's suggestion and added his suggested $ionicView.enter event to my directive's controller. When that didn't work, I instead added a simple ng-if="vm.displayMap" directive to the <ng-map> directive and added the following code to add it to the DOM on controller activation and remove it from the DOM right before leaving the view.
function controller($scope) {
vm.displayMap = true;
$scope.$on('$ionicView.beforeLeave', function(){
vm.displayMap = false;
});
}
Hope that helps.
don't use setTimeout on this!
You need to understand that the map is conflicting with the container size or something (example: map is loading while ionic animation is running, like swiping).
Once you understand this, you need to set map after view is completely rendered.
Try this on your controller:
$scope.$on('$ionicView.enter', function(){
var map = new GoogleMap($scope.business.name,
$scope.business.addr1, $scope.business.city,
$scope.business.state, $scope.business.zip,
$scope.business.lat, $scope.business.long);
map.initialize();
});
I'm building a site. First I made myself familiar with AngularJS in general and tried to make sense of all the new things for me: grunt, livereload, angularjs... I soon realized some mistakes I made and started building a new project from the ground up.
When I was building the site whilst on the grunt server. It displayed me more messages than it is now with gulp. I'm not sure why is that?
For example, since I was moving some existing code from my "study-project" to a new clean-start-gulp-project. I forgot to add one service: FlashService - responsible for displaying simple messages on my site.
And I was already using that as a dependency in another service. But for some reason all I was presented was a blank site with no messages in the console. Whereas in my previous project I always got messages like "No such provider found" or something similar. Can anybody help me make debuging easier?
Some other things have changed also. I changed the general folder structure of the code and I'm trying to modularize my new app. Currently I can't think of anything else that might even be remotely related.
My current folder structure is like this:
src/app
app.js
/services
auth-service.js
flash-service.js
Some important parts from my code:
app.js
angular.module('myapp', [
...
'myapp.home'
]).config(function ($stateProvider) {
$stateProvider.state('main', {
'abstract': true,
resolve: {
promiseUser: ['AuthService', function (AuthService) { AuthService.promiseUser; }]
},
template: '<div ui-view></div>'
});
})
AuthService
angular.module('myapp').service('AuthService', ['FlashService', function (FlashService){
// As soon as I created the missing service 'FlashService' everything started working.
// Or when I simply removed the FlashService from dependencies.
// What I don't understand is why didn't I get any errors in the console?
}
Since I already spent too much time looking the bug in a wrong place I can only imagine that some other messages might also not make it into the console. This would eventually make debuging quite hard.
What am I missing here? Why didn't I get any errors in the console?
Edit:
The first project was generated using Yeoman and for the second one I used gulp slush. Don't think this will help, but I figured might be worth mentioning.
Edit2
Found out that if I change angular.js and add console.log(message) inside the first minError function, I almost get what I want.
function minErr(module) {
...
console.log(message);
return new Error(message);
}
Now I just need to figure out an alternative way to do this or what was different with yeoman generated application and why I am not getting these messages by default:
[$injector:unpr] Unknown provider: aProvider <- a <- AuthService
http://errors.angularjs.org/1.2.25/$injector/unpr?p0=aProvider%20%3C-%20a%20%3C-%20AuthService
Loading modules with Angular if you want error messages always try and catch. My guess that gulp slush is not generating a try/catch scenario for loading modules while yeoman does. The difference between gulp and grunt shouldn't matter so long as you are trying to catch the error. The thing with gulp is that it is all process based some of something failed in the process sometimes the process will just sit there or just stop all together.
Disclaimer: I have never generated a project with slush only yeoman.
Okay, so I'll try to explain what I figured out.
This had nothing to do with gulp nor grunt. Also the directory structure was completely irrelevant.
First, let's take a look at some of the most important parts of both config files.
current:
angular.module('myapp', [])
.config(function ($stateProvider) {
$stateProvider.state('main', {
'abstract': true,
resolve: {
promiseUser: ['AuthService', function (AuthService) { AuthService.promiseUser; }]
},
template: '<div ui-view></div>'
});
})
...
before:
angular.module('myapp', [])
.config(function ($stateProvider) {
$stateProvider.state('main', {
'abstract': true,
resolve: {
promiseUser: ['AuthService', function (AuthService) { AuthService.promiseUser; }]
},
template: '<div ui-view></div>'
});
})
...
.run(['AuthService', function (AuthService) { ... }]);
...
So the first part is the same in both cases. What was different before is that I also used AuthService inside a run block.
I was checking that on state change the authenticated user had permissions to access the new state.
So what happened inside the resolve stayed inside the resolve! But what happened inside the run block was displayed in the console.
By modifying these parts of the code - both projects displayed the same errors and had the same behavior.
I am trying to start history with backbone however I get the error:
Cannot call method 'start' of undefined
Here is a link to the full code : http://pastebin.com/pNsYghgE
I have jquery, underscore, and backbone js include before this code so I would imagine this should work based off the documentation. I am using backbone 0.9.2. What am I doing wring here?
EDIT: ANSWER
I want not creating an instance of my routers so I added this code to before I called Backbone.history.start():
//initialize all routes
_(this.modules()).each(function(module, moduleName)
{
_(module.routers).each(function(router, routerName)
{
new router();
});
});
Backbone.history can only be started after one or more routers have been defined with routes:
http://backbonejs.org/docs/backbone.html#section-113
You can see here, that the Backbone.history object is created when routes are defined. I don't see any routers or routes being defined in the posted code, so I'm guessing that this is the problem.
When the line is executed Backbone is still not loaded..
Use the
$(function() {
// ...
});
For this part of the code as you have done for other blocks.
It seams okey, can you provide more code, and the part of the html you load jquery, undescore and backbone.
inspect the backbone object before: Backbone.history.start(this.options.historyOptions);
Did you create routers before trying to start the history? (Derick Bailey)