I'm trying to setup a simple angularjs app using ui.router, ocLazyLoad, Foundation and angular-foundation.
The app is a multi-view app with it's components lazy loaded using ocLazyLoad. I have no problem setting up the views and associated controllers. However, I am having an issue when trying to display a Foundation alert. When I try to view my alerts (route1), I get empty alerts. And the stack trace below.
Why is angular applying the alert directive twice? A plunker is available here: http://goo.gl/lhtD0c
Error: [$compile:multidir] Multiple directives [alert, alert] asking for transclusion on:
<div class="alert-box" ng-class="(type || "")" ng-repeat="alert in alerts" type="alert.type" close="closeAlert($index)">
http://errors.angularjs.org/1.2.22/$compile/multidir?p0=alert&p1=alert&p2=t…lerts%22%20type%3D%22alert.type%22%20close%3D%22closeAlert(%24index)%22%3E
at https://code.angularjs.org/1.2.22/angular.js:78:12
at assertNoDuplicate (https://code.angularjs.org/1.2.22/angular.js:6933:15)
at applyDirectivesToNode (https://code.angularjs.org/1.2.22/angular.js:6353:13)
at https://code.angularjs.org/1.2.22/angular.js:6858:37
at https://code.angularjs.org/1.2.22/angular.js:8091:11
at wrappedCallback (https://code.angularjs.org/1.2.22/angular.js:11546:81)
at wrappedCallback (https://code.angularjs.org/1.2.22/angular.js:11546:81)
at https://code.angularjs.org/1.2.22/angular.js:11632:26
at Scope.$eval (https://code.angularjs.org/1.2.22/angular.js:12658:28)
at Scope.$digest (https://code.angularjs.org/1.2.22/angular.js:12470:31)
The body of my index is below:
<body>
<div>
<a class="button" ui-sref="route1">Route 1</a>
<a class="button" ui-sref="route2">Route 2</a>
</div>
<div ui-view></div>
</body>
The javascript associated with this page is:
var myapp = angular.module('myapp', ['ui.router', 'oc.lazyLoad', 'mm.foundation'])
myapp.config(function($stateProvider, $urlRouterProvider){
// For any unmatched url, send to /route2
$urlRouterProvider.otherwise('/route2')
$stateProvider
.state('route1', {
url: "/route1",
controller: 'Route1',
templateUrl: "route1.html",
resolve: {
loadCtrl: ['$ocLazyLoad', function($ocLazyLoad) {
return $ocLazyLoad.load({
name: 'myapp',
files: ['route1.js']
})
}]
}
})
.state('route2', {
url: "/route2",
templateUrl: "route2.html"
})
});
$(function() {
Foundation.global.namespace = '';
$(document).foundation();
})
The problems are associated with route1. Here is the route1 template.
<div ng-controller="Route1">
Route 1 - {{ message }}
<br/>
<alert ng-repeat="alert in alerts"
type="alert.type"
close="closeAlert($index)">{{alert.msg}}</alert>
</div>
and finally the route1.js -
angular.module('myapp').controller('Route1', ['$scope', function($scope) {
$scope.message = 'Hello, world!'
$scope.alerts = [
{ type: 'danger', msg: 'Oh snap! Change a few things up and try submitting again.' },
{ type: 'success round', msg: 'Well done! You successfully read this important alert message.' }
];
}]);
The problem is that it's reloading your "myapp" module dependencies.
Just config ocLazyLoad not to reload foundation like this:
$ocLazyLoadProvider.config({
loadedModules: ['mm.foundation']
});
It will be something like that:
var myapp = angular.module('myapp', ['ui.router', 'oc.lazyLoad', 'mm.foundation'])
myapp.config(function($stateProvider, $urlRouterProvider, $ocLazyLoadProvider){
// For any unmatched url, send to /route2
$urlRouterProvider.otherwise('/route2')
$ocLazyLoadProvider.config({
loadedModules: ['mm.foundation']
});
$stateProvider
.state('route1', {
url: "/route1",
controller: 'Route1',
templateUrl: "route1.html",
resolve: {
loadCtrl: ['$ocLazyLoad', function($ocLazyLoad) {
return $ocLazyLoad.load({
name: 'myapp',
files: ['route1.js']
})
}]
}
})
.state('route2', {
url: "/route2",
templateUrl: "route2.html"
})
});
$(function() {
Foundation.global.namespace = '';
$(document).foundation();
})
Replace this code
<alert ng-repeat="alert in alerts"
type="alert.type"
close="closeAlert($index)">{{alert.msg}}</alert>
by
<div ng-repeat="alert in alerts">
<alert type="alert.type" close="closeAlert($index)">{{alert.msg}}</alert>
</div>
I finally managed to make this work. The problem was that ocLazyLoad was reloading the 'myapp' module. This caused the dependencies to reload. One of the dependencies was mm.foundation which reprocessed all the directives creating a clone of each pair.
To prevent this,I did not tie the second controller to myapp. Instead I created a new app 'route1app'. You can see the updated plunker here: http://goo.gl/lhtD0c
Related
I've been struggling with getting my AngularJS app to display a view based on a template.
The issue: ui-router seems to be correctly "routing" all the files, because the template file (landing.html) is being delivered to the console as an object (see console.log(result) in main.js below). Nevertheless, the template file is not being displayed in the app where <div ui-view></div> is supposed to be.
index.html:
<!DOCTYPE html>
<html lang="en" ng-app="myApp">
##include('partials/head.html')
<body>
##include('partials/header.html')
<div ui-view></div>
##include('partials/footer.html')
</body>
</html>
main.js:
angular.module('myApp', [
'ui.router'
])
.config(['$stateProvider', '$urlRouterProvider', function($stateProvider, $urlRouterProvider) {
$stateProvider
.state('landing', {
url: '/',
controller: 'LandingCtrl as landing',
templateUrl: 'templates/landing.html'
});
$urlRouterProvider.otherwise('/landing');
}])
.run(['$http', '$templateCache', function($http, $templateCache) {
$http.get('templates/landing.html', {
cache: $templateCache
}).then(function(result) {
console.log(result);
});
}]);
My template file landing.html:
<main class="content">
##include('partials/search.html')
<h2>Show me the contents of landing.html!</h2>
</main>
I'm using grunt and made sure to have it both watch and copy the /templates into /dist. Overall the Angular app is behaving correctly (no ng errors in the console).
Also, if instead of specifying a template file (templateURL), I simply use template: <h2>Show me the contents of landing.html!</h2> in main.js then this is rendered in the view. There's something preventing a file from being rendered.
Question: Given ui-router is correctly finding and routing all files, does anyone have an idea as to why the app is simply not displaying the template file?
Edit Here is LandingCtrl.js:
(function() {
function LandingCtrl($scope, $location, $anchorScroll) {
$scope.goTo = function(id) {
$location.hash(id);
console.log($location.hash());
$anchorScroll();
};
}
angular
.module('myApp')
.controller('LandingCtrl', ['$scope', '$location', '$anchorScroll', LandingCtrl]);
})();
in your main.js file change the url of Landing State as below:
angular.module('myApp', [
'ui.router'
])
.config(['$stateProvider', '$urlRouterProvider', function($stateProvider, $urlRouterProvider) {
$stateProvider
.state('landing', {
url: '/landing',
controller: 'LandingCtrl as landing',
templateUrl: 'templates/landing.html'
});
$urlRouterProvider.otherwise('/landing');
}])
.run(['$http', '$templateCache', function($http, $templateCache) {
$http.get('templates/landing.html', {
cache: $templateCache
}).then(function(result) {
console.log(result);
});
}]);
In my main index.html file I have the following simple markup...
<body ng-app="starter" ng-controller="AppCtrl">
<div ui-view></div>
</body>
In my app.js I am using $stateProvider to create routes so I can display certain pages...
app.config(function($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise('/app');
$stateProvider
.state('app', {
url: '/app',
templateUrl: 'templates/menu.html',
controller: 'AppCtrl'
})
.state('app.accounts', {
url: '/accounts',
templateUrl: 'templates/accounts.html'
})
});
When the page loads, the first state is loaded, meaning I can see the contents of menu.html in my main index.html and the controller AppCtrl is passed to this state.
My AppCtrl loads an API that I am using on click of a button from menu.html, the API provides a UI for a user to login, and once the credentials are good, the success is called...
app.controller('AppCtrl', function($scope, $ionicModal, $timeout, $state) {
$scope.create = function() {
var linkHandler = Plaid.create({
env: 'tartan',
clientName: 'Example Project',
key: 'test_key',
product: 'connect',
onSuccess: function(token) {
$state.go('app.accounts');
},
});
linkHandler.open();
}
});
What the API does is pretty irrelevant, but as you can see, I am passing $state.go('app.accounts'); on success. But instead of changing the state to app.accounts, I believe the otherwise statement is called because all I see is the contents of the app state.
Why is this so? I've been stuck on this issue for some time now.
app.accounts is a child state of app. That means in menu.html there must be <ui-view> in order to display accounts.html.
If you don't want to display accounts.html inside menu.html, you shouldn't make accounts a child state of app:
<body ng-app="starter">
<div ui-view></div>
</body>
and
app.config(function($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise('/app');
$stateProvider
.state('app', {
url: '/app',
templateUrl: 'templates/menu.html',
controller: 'AppCtrl'
})
.state('accounts', {
url: '/accounts',
templateUrl: 'templates/accounts.html'
})
});
I'm building an application in Rails and I'm using Angular for part of the front end, generating views based on data provided in a json file. I've been following this tutorial, but I can't seem to get my controller to run and I have no idea why. Can anyone help me out?
Sample link: http://localhost:3000/events/events1
events.coffee.erb
app = angular.module('EventsApp', ['ui.router', 'templates'])
app.config [
'$stateProvider'
'$urlRouterProvider'
($stateProvider, $urlRouterProvider) ->
$stateProvider.state 'events',
url: '/events/{id}'
templateUrl: 'templates/_event.html'
controller: 'EventsCtrl'
]
app.controller 'EventsCtrl', [
'$scope'
'$stateParams'
($scope, $stateParams) ->
console.log $stateParams //this never shows up
console.log 'Sup' //this never shows up
$scope.greeting = 'Yo' //neither does this
]
events.html.erb
<div ng-app="EventsApp">
Yo! //this shows up, but not {{greeting}}
{{greeting}}
<ui-view></ui-view>
</div>
_event.html
<script type="text/ng-template" id="/event.html">
<div>
{{greeting}}
Yo!
</div>
</script>
Rails routes file:
get "events/*path" => "events#events"
Rails events_controller.rb
class EventsController < ApplicationController
layout "application"
def events
end
end
File layout:
--app
-----assets
-------javascripts
----------templates
------------_event.html
----------events.coffee.erb
------views
--------events
----------events.html.erb
Your angular code is poorly structured. I think you are mixing up your Javascript syntax with your Ruby syntax...
Try something closer to this:
angular.module('EventsApp', ['ui.router', 'templates'])
.config(['$stateProvider', '$urlRouterProvider',
function($stateProvider, $urlRouterProvider) {
$stateProvider.state('events', {
url = '/events/{id}',
templateUrl: 'templates/_event.html',
controller: 'EventsCtrl'
}
}
])
.controller('EventsCtrl', [
'$scope', '$stateParams'
function($scope, $stateParams) {
console.log($stateParams);
console.log('Sup')
$scope.greeting = 'Yo';
}
]);
I'm new to angular and I'm trying to understand nested views concept.
Based on the example provided in their documentation: https://github.com/angular-ui/ui-router/wiki/Multiple-Named-Views
//home.html
<body>
<div ui-view="header"></div>
<div ui-view="settings"></div>
<div ui-view="content"></div>
</body>
I have settings.html which has a check box. If it's checked it will load in the view(not named) the advanced settings template if not it will load the basic template
//settings.html
<input type="checkbox" ng-change="change()" ng-model="advancedSettings" />
<div ui-view></div>
so far I have defined something like this:
$stateProvider
.state('home', {
views: {
'header': {},
'settings': {
templateUrl: 'settings.html'
},
'content': {},
}
})
since I have 2 templates basicSettings.html and advancedSettings.html that I need to load in the view from settings.html based on that checkbox, I thought I have to declare something like this:
.state('settings#home.basic',(){
templateUrl: 'basicSettings.html'
});
but it's not working, instead I receive a lot of errors on console. How is the best way to implement this, without removing names from homepage views(header,settings,content), also how do I change the view based on the check box?
Thanks
There is a working plunker
Solution here could be with states defined like this:
$stateProvider
.state('home', {
abstract: true,
url: "/home",
views: {
'header': {
template: "This is HEADER"
},
'settings': {
templateUrl: 'settings.html',
controller: 'HomeCtrl',
},
'content': {
template: "This is CONTENT"
},
}
})
.state('home.basic', {
url: "/basic",
templateUrl: 'basicSettings.html'
})
.state('home.advanced', {
url: "/advanced",
templateUrl: 'advancedSettings.html'
})
we have parent state "home" and two children. These are triggered on change by 'HomeCtrl', e.g. like this:
.controller('HomeCtrl', ['$scope', '$state',
function($scope, $state) {
$scope.advancedSettings = false;
$scope.change = function(){
var childState = $scope.advancedSettings
? "home.advanced"
: "home.basic";
$state.go(childState);
}
}])
So, based on the setting, the view target "settings" and its ui-view="" (unnamed one) is filled with a child state - basic or advanced
Check it here
Does angularJS have anything in place to lazyload external JS/CSS files based on ng-controller namespaces? So that the following would append com.myApp.SomeClass.js and com.myApp.SomeClass.css to the document head?
<div ng-controller="com.myApp.SomeClass"></div>
Not yet, but it is in the works post v1.0.
Is your app so big that you need it? We have some impressively big apps, and have not run into this need yet, since the controllers are much more dense then when writing the same behavior without angular.
how about using slowscript? It's really easy to lazyload on angularjs
Example:
https://github.com/flrngel/slowscript-angular-require-lazyload
Slowscript:
https://github.com/flrngel/slowscript
core code from website
app.js
app = angular.module("mainApp", ["ui.router"]).run(function($rootScope){
$rootScope.$on('$viewContentLoaded',function(){
slowscript.execute();
});
});
app.config(function($stateProvider, $urlRouterProvider) {
$stateProvider.state("sexy", {
url: "/sexy",
views: {
"contents": {
templateUrl: "/views/test.html",
controller: "Sexy",
reloadOnSearch: true
}
}
});
});
app.controller("Sexy", function($scope) {
slowscript.queue(function(){
slowscript.$global.sexy($scope);
});
});
test.html (angular view template)
<div ng-controller="Sexy">
<input list="univ">
<datalist id="univ">
<option ng-repeat="univ in univs" value="{{univ.name}}"></option>
</datalist>
</input>
<input type="submit"></input>
<noscript src="test.js" type="text/slowscript"></noscript>
</div>
test.js
require(['angular','slowscript'],function(angular,slowscript){
slowscript.$global.sexy=(function($scope){
console.log("tada~");
$scope.univs = [{"idx": 1,"name": "asdf"}, {"idx": 2,"name": "bsdf"}];
$scope.$apply();
});
slowscript.queue_execute();
});
It not possible with stock AngularJS 1.x
However, you can use $oclazyload to defer the loading of code files (js, HTML, CSS)
$ocLazyLoad works perfectly with routers like UI-Router. It returns a promise and uses resolve property to make sure that files are loaded before the view is resolved.
angular.module('myApp', ['ui.router', 'oc.lazyLoad'])
.config(function($stateProvider, $urlRouterProvider, $ocLazyLoadProvider) {
$stateProvider
.state('stateA', {
url: '/stateA',
controller: 'StateACtrl',
templateUrl: 'stateA.html',
resolve: {
load: [ '$ocLazyLoad', function($ocLazyLoad) {
return $ocLazyLoad.load([
{
name: 'stateA',
files: [
'stateA.css',
'stateA.js',
],
},
]);
},
],
},
})
.state('stateB', {
url: '/stateB',
controller: 'StateBCtrl',
templateUrl: 'stateB.html',
resolve: {
load: [ '$ocLazyLoad', function($ocLazyLoad) {
return $ocLazyLoad.load([
{
name: 'stateB',
files: [
'stateB.css',
'stateB.js',
],
},
]);
},
],
},
});
});
I have also created a plunkr to demo a working model.
Documentation to know more about possible config options.
Hope it helps!