AngularJS directive with a Swiffy instance throws error on route navigation - angularjs

Setup
I have a directive that takes a path to a json file as attribute value, loads the json, then instantiates Swiffy:
angular.module('myApp')
.directive('swiffy', function ($http) {
return {
restrict: 'A',
scope: {},
link: function postLink($scope, $element, attrs) {
var stage;
// Listen to angular destroy
$scope.$on('$destroy', function() {
if(stage) {
stage.destroy();
stage = null;
}
});
// Load swiffy json
$http({
method: 'GET',
url: attrs.swiffy
}).success(function(data, status, headers, config) {
stage = new swiffy.Stage( $element[0], data );
stage.start();
}).error(function(data, status, headers, config) {
});
}
};
});
The markup:
<div swiffy="my-animation.json"></div>
I also have a basic routing setup:
angular
.module('myApp', [
'ngCookies',
'ngResource',
'ngSanitize',
'ngRoute'
])
.config(function ($routeProvider) {
$routeProvider
.when('/', {
templateUrl: 'views/main.html',
controller: 'MainCtrl'
})
.when('/info', {
templateUrl: 'views/info.html',
controller: 'InfoCtrl'
})
.otherwise({
redirectTo: '/'
});
});
The controllers here are empty.
Problem
The json file loads as it should and the Swiffy svg is created just fine. But when i navigate away from a view that has a swiffy directive, angular throws an error and the whole app breaks:
TypeError: Cannot read property '1' of null
at annotate (angular.js:3179:24)
at Object.invoke (angular.js:3846:21)
at angular.js:5580:43
at Array.forEach (native)
at forEach (angular.js:323:11)
at Object.<anonymous> (angular.js:5578:13)
at Object.invoke (angular.js:3869:17)
at angular.js:3711:37
at Object.getService [as get] (angular.js:3832:39)
at addDirective (angular.js:6631:51)
The error is thrown after the '$destroy' event has triggered in the directive, so i know that stage.destroy() has run on the Swiffy object.
The angularjs function that throws the error can be found here https://github.com/angular/bower-angular/blob/7ae38b4a0cfced157e3486a0d6e2d299601723bb/angular.js#L3179
As far as i can tell, annotate() is trying to read the parameters on an anonymous function and fails. I have no errors if i remove the Swiffy instantiation so the errors have to be a result of creating the Swiffy object.
I'm using:
AngularJS 1.2.16
Swiffy runtime version 6.0.2
So far I've tried:
updating to AngularJS version 1.2.17-build.111+sha.19d7a12. (It contains an update to the annotate function but that doesn't fix the problem)
removed 'strict mode' from directive.
removed stage.destroy()
I'd rather not make any changes to the angular.js source (I tried to make angular skip anonymous functions but that broke even more things) and the swiffy runtime is not available un-minified so i'm not sure what is going on in there. Any ideas would be great, thanks.

Related

Angular route resolve not passing to controller

For some reason whatever i do i cannot get my data to the controller no matter what i do, i keep getting this error
Error: [$injector:unpr] Unknown provider: initDataProvider <- initData <- PackingScanController
first file
var Application = angular.module('ReporterApplication', ['ngRoute']);
Application.config(['$routeProvider', '$interpolateProvider',
function($routeProvider, $interpolateProvider) {
$interpolateProvider.startSymbol('<%');
$interpolateProvider.endSymbol('%>');
$routeProvider
.when('/packing/scan.html', {
controller: 'PackingScanController',
templateUrl: 'packing/scan.html',
resolve: {
initData : function () {
return "shite";
}
}
}) etc more code
second file
Application.controller('PackingScanController', ['$scope', '$http', 'initData', function($scope, $http, initData) {
var packer = this;
$scope.packedToday = initData;
The posted code is all right, you are injecting initData properly with resolve route block. However you are probably using explicit ngController in you route template. You don't want it, and of course in this case there is no initData service available which results in error you are getting.
Solution is simple: just remove
ng-controller="PackingScanController"
from your packing/scan.html template and it will work fine.
Explicit controller binding is not needed in this case since template is already bound properly to controller instance created behind the scene by $route service, with all necessary dependencies properly injected.

ui-router with customer controller and ng-strict-di

so, following best practice I've started using ng-strict-di. It's worked well so far, but I have hit the following problem using ui-router
// nested list with custom controller
.state('dashboard.list', {
url: '/list',
templateUrl: 'partials/dashboard-list.html',
controller: function($scope) {
$scope.dogs = ['Bernese', 'Husky', 'Goldendoodle'];
}
})
this causes angular to barf with the "Error: error:strictdi
Explicit annotation required" error.
I know that I should be using the inline bracket notation, or $inject, but obviously can't put it in this code as is.
I was thinking that I could declare the controller in another part of the script, with $inject and then just reference it in the code ?
function GoodController1($scope) {
}
GoodController1.$inject = ["$scope"];
and then
// nested list with custom controller
.state('dashboard.list', {
url: '/list',
templateUrl: 'partials/dashboard-list.html',
controller: GoodController1
})
would this work ? Are there any problems with this approach ?
There are no problems, with this approach. I am using typescript, and the generated syntax of controlelr class is almost the same as yours.
Here is a working plunker
...
// the contoller funciton to be instantiated
// by angular using new
var GoodController1 = function($scope){
$scope.title = "good title";
};
// set of dependencies
// (in typescript that would be a static property)
GoodController1.$inject = ["$scope"];
// before angular 2.0, this is the must
// we still have to register controller in the module
app
.controller('GoodController1', GoodController1)
...
and later in state:
.state('good', {
url: "/good",
templateUrl: 'tpl.html',
controller: "GoodController1",
})
check it here

Angular initializing data in $routeProvider using resolve

I have a AngularJS application and have a requirement to initialize data from a REST API before the controller initializes. I use the "resolve" in the routeProvider and also injected the relevant value in the controller in order to make this data available. The code snippets are as follows:
RouteProvider code snippet:
myApp.config(function($routeProvider) {
$routeProvider
....
.when('/account', {
templateUrl : path + 'admin/js/pages/inputs/account.html',
controller : 'mainController',
resolve: {
data: function() {
return $http.get(api_path + 'dashboard/get_accounts');
}
}
})
myApp.controller('mainController', function($scope,$http, data, $routeParams, DataService) {
...
console.log(data);
}
The console is supposed display the data by I get the following error " Error: [$injector:unpr] Unknown provider: dataProvider <- data "
Your help much appreciated.
It's because the data provider has not instantiated yet and it is instantiating the controller before the provider is ready, coming through as an undefined and unknown provider.
Try something like this that returns a promise:
myApp.config(function($routeProvider, $q) {
$routeProvider, $q
....
.when('/account', {
templateUrl : path + 'admin/js/pages/inputs/account.html',
controller : 'mainController',
resolve: {
data: function() {
return $q.all($http.get(api_path + 'dashboard/get_accounts'));
}
}
})
Now, the controller won't instantiate until the promise has resolved completely. As per the documentation for $routeProvider and how it handles promises in the resolve.
$routeProvider on Angular's website
resolve - {Object.=} - An optional map of dependencies which should be injected into the controller. If any of these dependencies are promises, the router will wait for them all to be resolved or one to be rejected before the controller is instantiated. If all the promises are resolved successfully, the values of the resolved promises are injected and $routeChangeSuccess event is fired. If any of the promises are rejected the $routeChangeError event is fired.

AngularJS - simple routing not working

I have a route with parameter like this
var app = angular.module("myapp", ["ngRoute"]);
app.config(['$routeProvider', '$locationProvider', function ($routeProvider, $locationProvider) {
$locationProvider.hashPrefix("!").html5Mode(true);
$routeProvider
.when('/sukien', { templateUrl: '/app/views/sukien/index.html' , controller: 'eventCtrl' })
.when('/sukien/:id', { templateUrl: function (params) { return '/app/views/sukien/index.html?id=' + params.id }, controller: 'eventCtrl' })
}])
why /sukien works and /sukien/:id doesn't ? indeed, angularjs seems not to understand what it is. "Uncaught TypeError: undefined is not a function"
/sukien/333 => failed to work.
You are mixing template url, state of your application and search parameters it seems.
The templateUrl tells angularjs where to look for the html file : it will very unlikely depend on the params.id and be set via a function, but will rather be a constant.
It has nothing to see with the url that the user sees in their browser.
For example : '/app/views/sukien/suiken.html'
The url the user sees will be something like :
.../suiken/1223445
And you can then access the id in your controller via $routeParams.id

Angular ui-router fails to resolve named dependencies

I recently migrated from ui-router 0.0.1 to 0.2.0. Since the migration, ui-router fails to resolve named dependencies that needs to be injected into a view's controller. Here's the sample code which works fine with ver 0.0.1 but fails in ver 0.2.0
angular.module( 'sample.test', [
'ui.router',
'i18nService'
])
.config(function config($stateProvider) {
$stateProvider.state( 'mystate', {
url: '/mystate',
resolve: {i18n: 'i18nService'},
views: {
'main': {
controller: 'MyCtrl',
templateUrl: 'templates/my.tpl.html'
}
}
});
})
.controller('MyCtrl', ['i18n', function(i18n) {
// fails to resolve i18n
}]);
i18nService is a simple service that return a promise
angular.module('i18nService', [])
.factory('i18nService', ['$http', '$q', function($http, $q) {
var deferred = $q.defer();
$http.get('..').then(..);
return deferred.promise;
}]);
I get the error "Unknown provider: i18nProvider <- i18n" when using v0.2.0
If i change the resolve config to:
resolve: {
i18n: function(i18nService) {
return i18nService
}
},
everything works fine. Is this an expected behaviour, or am I missing some configuration?
Here's the plunker: http://plnkr.co/edit/johqGn1CgefDVKGzIt6q?p=preview
This is a bug that was fixed last month:
https://github.com/angular-ui/ui-router/commit/4cdadcf46875698aee6c3684cc32f2a0ce553c45
I don't believe it's in any currently released version, but you could either get the latest from github or make the change yourself in your js file. It's simply changing key to value in that one line (you can see it in the github commit).
A workarround is to just not change the name for now.... do
resolve :{
i18nService: 'i18nService'
}
Then inject i18nService to your controller instead of i18n. It's a bit of a hack, but it does work (it injects the resolved service not the promise).

Resources