AngularJS Modal not showing up when templateUrl changes - angularjs

So far, what I have is straight off the Angular UI example:
Controller:
var ModalDemoCtrl = function ($scope, $modal) {
$scope.open = function () {
var modalInstance = $modal.open({
templateUrl: 'myModalContent.html',
controller: ModalInstanceCtrl
});
};
};
var ModalInstanceCtrl = function ($scope, $modalInstance) {
$scope.close = function () {
$modalInstance.dismiss('cancel');
};
};
And this section, which is just in sitting in the .html for the whole page this modal is on.
Html:
<script type="text/ng-template" id="myModalContent.html">
<div class="modal-header">
<h3 class="modal-title">I'm a modal!</h3>
<button class="btn btn-warning" ng-click="close()">X</button>
</div>
<div class="modal-body">
Stuff
</div>
</script>
This works just fine. But I'm trying to refactor some things out and organize my code, so I would like to move the modal html to its own file. When I do so, and try to use it as by changing the templateUrl to the path: \tmpl\myModalContent.html, it doesn't show up. The backdrop still appears and inspecting the page shows that it loaded correctly, but just won't show up.
I've tried changing the css for the modal per these suggestions with no difference.
My question is, why does this work fine if the script tag is in the main html, but not at all if it is in it's own file?
Here is a plnkr that shows what I mean. If you copy what is in the template.html and place it right above the button in the index.html file, it works...

Remove template declaration for template.html and just put raw HTML in there like this:
<!--script type="text/ng-template" id="template.html"-->
<div class="modal-body">
Hello
</div>
<div class="modal-footer">
<button class="btn btn-primary" ng-click="cancel()">OK</button>
</div>
<!--/script-->
Your plnkr worked fine with second click to the button. It'd show the modal as expected. The reason it showed up with second click is because Angular would load up the 'uncompiled' template the first time, then it compiled the template to raw HTML which is ready for your subsequent clicks.
EDIT: Also, when you put the template code right in index.html, Angular compiles the template during its initial pass through the DOM; that's why the modal seemed to work.

Well I am clearly a dummy. All I had to do was include my new file in the main view.
<div ng-include="'path-to-file.html'"></div>
Then calling it from the controller was easy, all I needed was the id (modalContent.html) as the templateUrl.
Just keep swimming, and you'll eventually get there :)

Related

Angular refresh current page - problems

home.html is a template with two fieldsets.
I use ng-show to make the first fieldset visisble when the page loads and when a user clicks a button the other fieldset is made visible and the first one hidden.
This is accomplished by using a variable 'preview' like so (this is a jsfiddle) :
<div ng-app="app">
<div ng-controller="MainController">
<fieldset ng-show="!preview">
<p>This is fieldset 1</p>
<button ng-click="toggleIt()">Toggle</button>
</fieldset>
<fieldset ng-show="preview">
<p>This is fieldset 2</p>
</fieldset>
Home
</div>
</div>
and the controller is like:
angular.module('app', []).
controller('MainController', ['$scope', function ($scope) {
$scope.preview = false;
$scope.toggleIt = function(){
$scope.preview = true;
}
$scope.returnIt = function(){
location.reload();
}
}]);
In JsFiddle it works fine but when I use the 'Home' link in my angular app which is:
Home
and this is picked up by the app.routes.js file
.when('/', {
templateUrl : 'app/views/pages/home.html',
controller: 'MainController',
controllerAs: 'main'
})
Nothing happens - fieldset 2 is still visible rather than fieldset 1.
I was expecting that $scope.preview would be set to false as this is the first line in the controller and that would make the first fieldset visible again.
When I do a browser refresh it works but not using the routing.
I have tried location.reload(); (and this works in the fiddle), but when I apply it inside the app controller I get TypeError: $location.reload is not a function.
I have tried $route.reload() which sends it into an infinite loop, and window.location.reload() which keeps reloading.
Is there a simple way I can achieve a browser-like refresh of the page via the routing?

How to show preloader inside button instead text Angular JS?

I use Angular JS library ngActivityIndicator.
I tried to show preloader inside element div instead text when button is pushed:
<div ng-activity-indicator="CircledDark">
<div ng-click="Add();" class="btn shareBtn">
<div ng-view></div>
<span>Text</span>
</div>
</div>
I posted <div ng-view></div> inside and have added directive ng-activity-indicator="CircledDark" to parent element.
When I click button, I call function that display preloader:
$activityIndicator.startAnimating();
But preloader is created outside of button:
<div ng-show="AILoading" class="ai-circled ai-indicator ai-dark-spin"></div>
This is official documantation, from here I have taken example:
https://github.com/voronianski/ngActivityIndicator#directive
How to show preloader inside button instead text Angular JS?
I think the only solution is to separate your ng-view and button, at least they do something similar in their sample page. So, in your markup you could do something like this:
<div ng-click="add()" class="btn btn-default" ng-activity-indicator="CircledDark">
<span ng-show="!AILoading">Add</span>
</div>
<div ng-view ng-show="!AILoading">{{ delayedText }}</div>
And in your controller:
$scope.delayedText = "";
$scope.add = function() {
$activityIndicator.startAnimating();
$timeout(function() {
$scope.delayedText = "Here we are!";
$activityIndicator.stopAnimating();
}, 3000);
};
Here is full Demo, you can play with CSS a little so the size of the button won't change when text is replaced by icon.

Angular-UI Bootstrap toggle buttons in AngularJS

I originally had the default Bootstrap 3.3 toggle buttons in my app. These are the grouping of buttons that mimic the behavior of checkboxes or radio buttons.
I was trying to replace them with the ones here
I paste the code into my html, and at first it looks fine:
<div class="btn-group">
<label class="btn btn-default" ng-model="checkModel.left" btn-checkbox>Left</label>
<label class="btn btn-default" ng-model="checkModel.middle" btn-checkbox>Middle</label>
<label class="btn btn-default" ng-model="checkModel.right" btn-checkbox>Right</label>
</div>
Now I want to hook up the controller. So, I create a file "elements-buttons.controller.js" with the contents of the JS from the angular-ui site:
(function() {
'use strict';
angular.module('pb.ds.elements').controller('ButtonsController',
function($log) {
var _this = this;
_this.singleModel = 1;
_this.radioModel = 'Middle';
_this.checkModel = {
left: false,
middle: true,
right: false
};
});
})();
I put the controller into my router:
.state('elements.buttons', {
url: '/elements/buttons',
templateUrl: 'modules/elements/templates/elements-buttons.html',
controller: 'ButtonsController as buttons'
})
Now, as soon as I link the controller to my index.html, the button group loses all styling:
Which makes no sense to me at all. Inspecting the code in Chrome shows it's if fact lost all its classes for some reason:
<div class="btn-group">
<label class="" ng-model="buttons.checkModel.left" btn-checkbox="">Left</label>
<label class="" ng-model="buttons.checkModel.middle" btn-checkbox="">Middle</label>
<label class="" ng-model="buttons.checkModel.right" btn-checkbox="">Right</label>
</div>
Now, since I have used "controller as" syntax, I did make sure to try adding that to the model ng-model="buttons.checkModel.right" but with or without this same thing. No controller, looks fine. Controller, boom.
EDIT/UPDATE: There are no errors in the console of any kind.
I have created a jsfiddle in order to debug your problem.
Check it here.
And Im finding no problems on it.
Key points you should check:
Be sure you are adding all neccessaries files, such as ui-bootstrap.js
ui-bootstrap.css
jquery, since this are just directives for angular, it will also need jQuery (as the original bootstrap does)
include the dependency on ui-bootstrap on you app definition, pay attention to this line -> angular.module('app', ['ui.bootstrap', 'app.controllers']);
ng-app & ng-controller on the markup. Be sure you are not forgetting this
On the fiddle, Im not using ui-router, but you should check that the controller is indeed loading (you can add a console.log there and check that the controller is loaded).
I thing with this guidelines and the jsfiddle, you should be good to go

AngularJS Trying to use ng-click with ng-switch but ng-switch is not switching my divs

AngNoob here. I have some global navigation that uses the routeProvider to swap out external html pages inside the view. Within the view i set up a list type sub navigation (created with ng-repeat) that switches out divs in the external html file. I can get it to load up the page if I set it manually in the appCtrl:
//Here I set the initial value
$scope.page = 'Comfort Homes of Athens';
But when I click on the span that has the ng-click. I get nothing. I started to think it was a scope issue but when i put just an ng-click='alert()' it does nothing either.
I have read around other posts but most seem to be putting a ng-click inside of an ng-switch rather than the reverse. and aren't using routing in their examples either. Still new to angular so maybe its something I haven't come across yet.
App HTML:
<body ng-app="app">
<header ng-include="header.url" ng-controller="nav"></header>
<article ng-view></article>
<footer ng-include="footer.url" ng-controller="nav"></footer>
<script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.2.16/angular.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.2.16/angular-route.js"></script>
<script type="text/javascript" src="js/data.js"></script>
<script type="text/javascript" src="js/model.js"></script>
</body>
External HTML File:
<div id="web" class="wrapper">
<aside class="boxModel">
<div id="controller" class="container">
<div class="topBox bluebg subNavBar"><h1 class="white">Projects</h1></div>
<div ng-controller="nav" id="controls" class="botBox whitebg">
<span ng-repeat='item in webProjects' ng-click="page='{{item.name}}'">{{item.name}}</span>
</div>
</div>
</aside><section ng-switch on="page" class="boxModel">
<div ng-switch-when="Comfort Homes of Athens" id="sandbox" class="container round box whitebg">
<h1>Here is link 1</h1>
</div>
<div ng-switch-when="Sealpak Incorporated" id="sandbox" class="container round box whitebg">
<h1>here is Link 2</h1>
</div>
</section>
</div>
JS:
var app = angular.module("app", ["ngRoute"]);
function nav($scope) {
$scope.templates = templates;
$scope.header = $scope.templates[0];
$scope.footer = $scope.templates[1];
$scope.mainNav = mainNav;
$scope.footNav = footNav;
}
app.config(function($routeProvider) {
$routeProvider.when('/',{
templateUrl: "templates/home.html",
controller: "AppCtrl"
}).when('/templates/web.html',{
templateUrl: "templates/web.html",
controller: "AppCtrl"
}).when('/templates/seo.html',{
templateUrl: "templates/seo.html",
controller: "AppCtrl"
}).otherwise({
template: "This doesn't exist!"
});
});
app.controller("AppCtrl", function($scope) {
$scope.webProjects = webProjects;
$scope.seoProjects = seoProjects;
//Here I set the initial value
$scope.page = 'Comfort Homes of Athens';
});
Unfortunately for you, ng-repeat creates child scopes which are siblings with each other and children of your parent controller (ng-controller="nav") while your <section> where ng-switch is on is not child scope of your ng-controller="nav", but AppCtrl.
You could try ng-click="$parent.$parent.page=item.name" just to understand scopes in angular.
<div id="web" class="wrapper">
<aside class="boxModel">
<div id="controller" class="container">
<div class="topBox bluebg subNavBar"><h1 class="white">Projects</h1></div>
<div ng-controller="nav" id="controls" class="botBox whitebg">
<span ng-repeat='item in webProjects' ng-click="$parent.$parent.page=item.name">{{item.name}}</span>
</div>
</div>
</aside><section ng-switch on="page" class="boxModel">
<div ng-switch-when="Comfort Homes of Athens" id="sandbox" class="container round box whitebg">
<h1>Here is link 1</h1>
</div>
<div ng-switch-when="Sealpak Incorporated" id="sandbox" class="container round box whitebg">
<h1>here is Link 2</h1>
</div>
</section>
I don't recommend using this solution as it's quite ugly. The solution of #link64 is better, but I think the inheritance of model is so implicit and creates a tightly-coupled code. Here I propose another solution which I hope is better by emitting an event:
<span ng-repeat='item in webProjects' ng-click="$emit('pageChange',item.name)">{{item.name}}</span>
I'm not sure if angular is able to resolve $emit('pageChange',item.name) expression in the template. If you run into any problems, you could write inside your controller:
<span ng-repeat='item in webProjects' ng-click="setPageChange(item.name)">{{item.name}}</span>
In your nav controller:
$scope.setPageChange = function (pageName) {
$scope.$emit("pageChange",pageName);
}
In your AppCtrl, listen to the event and update the page.
app.controller("AppCtrl", function($scope) {
$scope.webProjects = webProjects;
$scope.seoProjects = seoProjects;
//Here I set the initial value
$scope.page = 'Comfort Homes of Athens';
$scope.$on("pageChange", function (event, newPage){
$scope.page = newPage;
}
});
In addition to #KhanhTo's answer, I wanted to point you toward another tool to use instead of ngRoute; UI-Router. This is not the answer to your original question, but it is a better solution that avoids your issue entirely.
UI-Router enhances the page routing of ngRoute and is more centered around states. You transition to states that have templates and optional controllers. It emits its own events such as $stateChangeStart or $stateChangeSuccess. You can invoke these state transitions with the function command $state.go(stateName) or by a directive ui-sref="my.state({name: item.name})
UI-Router is a very powerful tool and I cannot go into all the details here but the documentation and community is great.
A simple rewrite of your code could look like the following.
Template for web.html
<div class="wrapper">
<aside class="boxModel">
<div id="controller" class="container">
<div class="topBox bluebg subNavBar"><h1 class="white">Projects</h1></div>
<div ng-controller="nav" id="controls" class="botBox whitebg">
<span ng-repeat='item in webProjects' ui-sref="app.web.page({name: {{item.name}})">
{{item.name}}
</span>
</div>
</div>
</aside>
<section class="boxModel">
<div ui-view class="container round box whitebg">
<!-- Page content will go here -->
</div>
</section>
</div>
JavaScript
app.config(function($stateProvider) {
$stateProvider
.state('app', {
abstract: true,
template: '<div ui-view></div>', //Basic template
controller: "AppCtrl",
}).state('app.home', {
templateUrl: "templates/home.html",
url: '/home'
}).state('app.web',{
templateUrl: "templates/web.html",
url: '/web'
}).state('app.web.page',{
templateUrl: "templates/page.web.html",
url: '/web/page/:name' //Note here the ':' means name will be a parameter in the url
}).state('app.seo',{
templateUrl: "templates/seo.html",
url: '/seo'
});
});
app.controller('AppCtrl', function($scope){
$scope.webProjects = webProjects;
$scope.seoProjects = seoProjects;
$scope.$on("$stateChangeStart", function (event, toState, toParams, fromState, fromParams){
if(newState.name == 'app.web.page'){
var pageName = newStateParams.name; //Variable name matches
$scope.linkText = fetchPageContent(pageName);
}
});
});
Template for page.web.html
<h1>{{linkText}}</h1>
With these changes you will be able to reuse the same instance of your controller. In addition to allowing your paging content to be more scalable.
Notes on $scopes
Every $scope has a parent except for the $rootScope. When you ask for an object in the view, it will look at its $scope to find the reference. If it does not have the reference, it will traverse up to its parent scope and look again. This occurs until you get to the $rootScope.
If you assign something to the $scope in the view, it will assign it to the current $scope as opposed to searching up the $scope chain for an existing property. That is why ng-click="model.page = ..." works; it looks up the $scope chaing for model and then assigns to the page property whereas ng-click="page = ..." assigns directly to the current $scope.
Notes on Controller re-use
To my knowledge, ngRoute does not support nested views. When you go to a new route, it will destroy the current view and controller as specified in the $routeProvider and then instantiate a new controller for the new view. UI-Router supports nested states (i.e. child states with child $scopes). This allows us to create a parent controller that can be re-used amongst all the child states.
I think this may be related to some misunderstanding of how scope works.
ng-repeat creates its own scope. When attempting to set page, angular creates it on the scope of the ng-repeat.
In your AppCtrl, create an object on the scope as follows:
$scope.model = {};
$scope.model.page = 'Comfort Homes of Athens';//Default value
On your ng-click, refer to model.page instead of just page. Angular will then traverse up the scope to find model.page instead of just create a property on the local scope of the ng-repeat.
<span ng-repeat='item in webProjects' ng-click="model.page='{{item.name}}'">{{item.name}}</span>
Also, your AppCtrl is going to be recreated every time you change pages. You should probably use a service to persist the state between page changes

Using AngularJS, how do I set $scope properties in the controller of the parent page

Thanks for looking.
I have the following markup for a modal which shares the same angular controller as it's parent page:
<!-- START Add Event Video -->
<script type="text/ng-template" id="EventVideo.html">
<div class="event-modal">
<div class="modal-header"><h3>Event Video</h3></div>
<div class="modal-body">
<p>Please enter the URL of either a <strong>YouTube</strong> or <strong>Vimeo</strong> video.</p>
<span ng-if="!Event.VideoUrlIsValid" style='color:#9f9f9f;'>This doesn't look like a valid YouTube or Vimeo Url. Your video may not work.</span>
<div class="row" ng-controller="EventCreateController">
<div pr-form-input span="12" name="videoUrl" ng-model="Event.Item.VideoUrl" placeholder="YouTube or Vimeo URL" isRequired="false" no-asterisk></div>
</div>
</div>
<div class="modal-footer"><button class="btn btn-primary" ng-click="Event.UI.EventVideoModal.Close()">Done</button></div>
</div>
</script>
<!-- END Add Event Video -->
And here is the relevant JavaScript:
EventVideoModal: {
Open: function () {
$scope.EventVideoModal = $modal.open({
templateUrl: 'EventVideo.html',
controller: 'EventCreateController',
scope: $scope
});
},
Close: function () {
$scope.EventVideoModal.close();
}
}
Please note the Event.Item.VideoUrl model reference.
The modal allows a user to set the URL of a video, and the goal is to have that set $scope.Event.Item.VideoUrl in the controller and then close the modal. The parent page and the modal both share the same controller, so I had hoped that this would work.
The modal behavior is fine (opens and closes as it should), but the $scope.Event.Item.VideoUrl property is not getting set.
Any advice is appreciated.
Problem Solved!
Thanks to Bogdan Savluk, I realized that I had a scope inheritance problem. So, removing both the explicit reference to the controller in the modal HTML as well as in the JavaScript constructor, resolved my problem:
<!-- START Add Event Video -->
<script type="text/ng-template" id="EventVideo.html">
<div class="event-modal">
<div class="modal-header"><h3>Event Video</h3></div>
<div class="modal-body">
<p>Please enter the URL of either a <strong>YouTube</strong> or <strong>Vimeo</strong> video.</p>
<span ng-if="!Event.VideoUrlIsValid" style='color:#9f9f9f;'>This doesn't look like a valid YouTube or Vimeo Url. Your video may not work.</span>
<!-- <div class="row" ng-controller="EventCreateController"> <--REMOVE THIS! -->
<div class="row">
<div pr-form-input span="12" name="videoUrl" ng-model="Event.Item.VideoUrl" placeholder="YouTube or Vimeo URL" isRequired="false" no-asterisk></div>
</div>
</div>
<div class="modal-footer"><button class="btn btn-primary" ng-click="Event.UI.EventVideoModal.Close()">Done</button></div>
</div>
</script>
<!-- END Add Event Video -->
And here is the relevant JavaScript:
EventVideoModal: {
Open: function () {
$scope.EventVideoModal = $modal.open({
templateUrl: 'EventVideo.html',
//controller: 'EventCreateController', <--REMOVE THIS!!
scope: $scope
});
},
Close: function () {
$scope.EventVideoModal.close();
}
}
If you are passing scope to $modal.open() than scope for modal would be created as child scope from passed scope... - so you will have access to all properties from it.
But in case when you are passing the same controller to it - that controller would be applied to new scope and will override all properties from parent.
So in general, as I see the only thing you need to do to achieve desired result is to remove controller from configuration passed to $modal.open() or replace it with something that is specific only for that modal.

Resources