I've got an aside which will be displayed as a pop-up modal. The user will be able to interact with the modal and choose one of either two options.
If the user chooses to reject the offer I would simply like to hide the pop up modal and the overlay.
I can hide the modal - the ng-show works perfectly fine. However, the overlay(which is a separate element, sitting in a separate part in the codebase) does not respect the updated scope value even though they are using the same directive and the value is coming from a singleton. How do I make sure that they are both in sync?
Here's the modal
<aside data-ng-show="!customerRejectedOffer" data-pi-browser-update class="md-modal md-show pi-modal-message modal-effect-2">
<div class="pi-modal-content">
<h4>Title</h4>
<div>
<p>Copy</p>
<ul class="flush--left nav--no-style-type">
<li>
<button class="pi-modal-content-button wb-btn wb-btn--secondary">
<a class="pi-modal-content-link" href="#" title="Upgrade Button">
<admin:keyvalues key="datedbrowser.iebutton"/></a>
</button>
</li>
<li><button class="pi-modal-content-button pi-modal-content-button__refusal" data-ng-click="rejectOffer()"><a class="pi-modal-content-link" title=""></a></button></li>
</ul>
<button data-ng-click="rejectOffer()" class="pi-modal-content-close md-close">X</button>
</div>
</div>
</aside>
To manage the show/hide values as well as to handle the click event I have set up a directive.
(function() {
'use strict';
angular
.module('pi.common.browserupdateMessage', ['pi.common.storage'])
.directive('piBrowserUpdate', browserUpdate);
function browserUpdate(SharedScopeUtility) {
return {
link: browserUpdateLink
};
function browserUpdateLink(scope) {
scope.customerRejectedOffer = SharedScopeUtility.hasAcceptedUpgrade;
console.log(scope);
scope.customerRejectedOffer = customerRejectedOffer;
function rejectOffer()() {
SharedScopeUtility.hasAcceptedUpgrade = true;
scope.customerRejectedOffer = SharedScopeUtility.hasAcceptedUpgrade;
}
}
}
}());
As you can see in the directive, I am setting the initial value of customerRejectedOffer from a service I created called SharedScopeUtility.
(function() {
'use strict';
angular.module('pi.app.sharedscopeutility', [
'ngResource'
])
.factory('SharedScopeUtility', function (){
var hasAcceptedUpgrade = false;
var _service = {
hasAcceptedUpgrade: hasAcceptedUpgrade
};
return _service;
});
}());
When the user clicks the "Rejected Offer Button" I fire off the rejectOffer() function which you can see in the directive.
This function updates the value on the service from false to true and sets a new value on the scope called customerRejectedOffer which I then use in my ng-show. This works just fine for the pop-up modal. However, the overlay element:
<div data-pi-browser-update data-ng-click="rejectOffer()" data-ng-show="!customerRejectedOffer" class="pi-modal-overlay"></div>
Which uses the exact same directive as well as value for it's ng-show but remains visible.
I created a service for this because I wanted the rejectedOffer boolean to come from a singleton, however that still doesn't give me any joy.
Help, please :-)
Most importantly, Have a look at this plunker. The data-ng-show should listen directly to the SharedScopeUtility.hasAcceptedUpgrade service flag. Reference the SharedScopeUtility service on the scope - scope.SharedScopeUtility = SharedScopeUtility.
Your'e returning an empty function here - is it a mistake?:
function rejectOffer()()
rejectOffer() should also be defined on the scope - scope.rejectOffer = function(){}
I've seen cases where directives needed a $timeout to propagate scope changes - but you can avoid that by referencing a service as suggested above.
Good luck and let me know if that helps!
Related
I have an SPA written in AngularJS. The main page uses an ng-include attribute to determine which view to load. This is set in JavaScript when someone clicks on a menu which is contained within the main page. However, I've come across a situation where I need to load a different view by clicking a button within another view, essentially replacing it.
I'm trying to figure out how to do this and from what I've researched, I have to use $rootScope and either an $emit or $broadcast call in the child view and a $rootScope.$on method to detect this event.
The thing is, this doesn't seem to work. I have set my breakpoints and stepped through the code, but I always get this error:
Error: [ngModel:datefmt] http://errors.angularjs.org/1.5.7/ngModel/datefmt?p0=2009-07-21T00%3A00%3A00
Here's the code in my parent page controller:
$rootScope.$on('viewChanged', function () {
var menuItem = {
template: 'customerOrders.html' // will be eventually dynamic
};
navigate(menuItem);
});
function navigate(menuItem) {
$scope.activeMenuItem = menuItem;
}
<div data-ng-include="activeMenuItem.template"></div>
In the child page controller:
function changeSelectedView(viewTemplate) {
$rootScope.$emit('selectedViewChanged', viewTemplate);
}
Obviously I'm doing something wrong here. How do I accomplish what I want, or is there a completely different way to do this?
you can use ng-route to work between views. check https://docs.angularjs.org/api/ngRoute/provider/$routeProvider
First of all, the event name in the $emit function and in the $on function did not match, so I made that fix.
function navigateToNewTemplate(event, viewTemplate) {
var menuItem = {
template: buildTemplateUrl(viewTemplate)
};
navigate(menuItem);
}
$rootScope.$on('selectedViewChanged', navigateToNewTemplate);
function changeSelectedView(viewTemplate) {
$rootScope.$emit('selectedViewChanged', viewTemplate);
}
It seems my li elements in angularjs directive not responding clicking event.
HTML:
<my-selbg>
<ul>
<li ng-repeat="bgimage in bgimages"><img src={{bgimage}} width="85" height="82" dir={{bgimage}}></li>
</ul>
</my-selbg>
JS:
var mlwcApp = angular.module('mlwcApp', [])
.controller('BgImagesListController', function($scope, $http) {
$http.get("http://localhost:8080/webcontent/bg_images").success(function(response) {
$scope.bgimages = response;
});
})
.directive('myselbg', function(){
return {
restrict: 'E',
scope: true,
link: function(scope, element, attrs){
var elementOne = angular.element(element.children[1]);
var elementTwo = angular.element(element.children[2]);
var elementThree = angular.element(element.children[3]);
setUpBGImg = function(){
console.log('link function');
};
$(elementOne).on('click', setUpBGImg);
$(elementTwo).on('click', setUpBGImg);
$(elementThree).on('click', setUpBGImg);
}
};
});
I have 3 li elements and clicking any of them dose not hit the code in link function. Anyone has idea?
You're new to angular, by the looks of it.
First off, before going any further - your directive will not even bind at all in the state it is in. You've got an element directive (which is fine, though if I were you I'd make it an attribute directive by restricting on A, which allows you to then apply it to the list rather than an element above it) named myselbg in your code. However, your markup is set as my-selbg, which would then look for the angular directive mySelbg, which does not exist.
In addition to this, your directive will evaluate before the list is rendered (thanks to the order of priority in execution). You have two choices to go around this:
You can do something like this: https://jsfiddle.net/a01n3srw/1/ . Really not recommended - I am using $timeout in order to evaluate code after the current refresh cycle is done, at which point the list fully exists
You can use the simple ngClick angular core directive in order to make this easy. Added bonus, when your function that you evaluate starts modifying scope, you won't shoot yourself in the foot using the previous method and having to use $apply
I have the below HTML that displays a spinner until a list of tags displays for an application. However, the spinner continues if there are no events that exist either.
I'd like the spinner to disappear and the input just be blank or return a message to the user that there are no tags for this app. Not sure how to do that whilst keeping the code clean!
HTML:
<div ng-show="noun === 'Tag'">
<div class="spinner" ng-hide="loadedTags()">
<i class="fa fa-spinner fa-pulse"></i>
</div>
<div ng-show="loadedTags()">
<select ol-filter-select="tags" ng-model="tagName">
<option ng-value="name" ng-repeat="name in tagNames()" ng-bind="name" ng-selected="tagName === name">
</option>
</select>
</div>
</div>
Controller:
$scope.loadedTags = function() {
return !_.isEmpty($scope.tags);
};
View:
Spinner keeps spinning if there's no tags...
I think you need to show more of the code that is actually loading the tags. Are you loading the tags from the server via $http.get or something similar? If so, then typically the way this would be done is to have a boolean scope variable that specifically represents the fact that loading is currently in progress. That variable would control whether the spinner is visible. For example, I have common code that looks like this:
Constructor for Angular controller:
$scope.loadingInProgress = true;
$http.get(myUrl)
.then(function(result) {
// My success function
$scope.tags = result.data;
$scope.loadingInProgress = false;
},
function() {
// My failure function
$scope.loadingInProgress = false;
});
HTML:
<div class="spinner" ng-show="loadingInProgress">
<i class="fa fa-spinner fa-pulse"></i>
</div>
It seems like you're trying to avoid an extra scope variable to represent the loading state, so you're trying to calculate the loading state based on the results of the load. But that's not really reliable. The results of the load could be anything, and doesn't really indicate whether loading is in progress.
The above should work, assuming you're using underscore.js. You're sure the controller is in the same scope as that bit of html?
Maybe try the following:
$scope.loadedTags = function() {
return $scope.tags.length > 0;
};
loadedTags() will be evaluated only on digest cycle, not every time $scope.tags is changed.
Instead, create variable $scope.isVisible watch on $scope.tags to update isVisible state.
Then, your ng-show=isVisible.
I'm looking to have a function run every time an angular directive updates. In my case, I have an array of modal configurations that get used on a modal markup template.
Every time the template is used to generate a modal due to a change in the model, I want to run a positionModal() method.
scope.$watch in the link function doesn't seem to notice when I change the model, and I cant think of any other way of doing this. I tried a post-link function thinking that the compile function would get called when the directive was applied, but that doesn't seem to work either. Here is my example controller:
MyApp.controller("ModalController", function () {
//Define scope vars
$scope.modals = [];
$scope.$on("modalTrigger", function (event, settings) {
$scope.modals.push(settings);
});
});
Note: I've simplified the controller here- know that it DOES work.
Here is the template code:
<div class="modalParent" ng-controller="ModalController">
<div id="{{modal.id}}" class="modal" ng-class="modal.type" ng-repeat="modal in modals">
<div class="content">
<h2 ng-show="modal.title">{{modal.title}}</h2>
<p>{{modal.message}}</p>
<button>{{modal.button}}</button>
</div>
</div>
</div>
The directive is currently like this:
MyApp.directive("modalParent", function () {
var positionModals = function (element) {
element.find(".modal .content").each(function () {
//position logic here
});
};
return {
restrict: "C",
compile: function (tElement) {
positionModals(tElement);
}
};
});
Note: Also simplified for the purposes here.
The positionModals() call works when the first modal gets pushed to the array. After that, it stops working.
I've tried using the linking function as well, same result. scope.$watch(modals, function(){...}) does not work.
Can somebody help me figure out what I'm doing wrong?
Figured it out!
I was applying the directive to the parent, ".modalParent".
The ng-repeated element in this case is the modal itself ".modal".
You would want the directive to run on elements that get updates as the model changes, because then the linking function will get called each time the element is instantiated, rather than sitting and watching the parent and trying to update from there.
Hope this helps somebody.
Instead of calling like this, my approach is to write this in the services and inject that services in the controller wherever you want to get that function or the data to be notified as,
Services.js
as.service("xyzservice",function(factoryname){
//here the code goes...
})
Now inject in Controller,
ac.controller("controllername",function(xyzservice){
})
ac.controller("controllername",function(servicename){
})
ac.controller("controllername",function(xyzservice){
})
Here we have injected it in the two controller, we can get it.
I want to create a really simple confirmation box using UI-modal, which I have successfully used to make complicated modals that load their template and controller from external files in the past.
It's so simple though that I don't want to rely on external template and controller files, just a simple box with a close button which is somehow wired up to a controller declared directly on the modal instance.
Here is what I have tried unsuccessfully...
var modalInstance = $modal.open({
template: "<div>Message goes here...<button ng-click='cancel()'>Continue</button></div>",
controller: function(){
$scope.cancel = function(){
alert("Cancelled");
};
}
});
Looks like you need to inject $scope into your controller function
controller: function($scope){
The scope of the modal template is not the same as the scope in the controller that you've defined the modal instance in.
The reason you're not getting undefined errors is $scope is a closure variable so adding .cancel() to it works just fine. But, since it isn't the same scope of the modal, so the ng-click doesn't see a .cancel() on its scope.
I replicated in this jsbin: http://jsbin.com/gejuxije/1/edit
Edit:
Since you mentioned you didn't want external files for a template, here's a demo of how to define the template for the modal inside the template of the view it is used on.
http://jsbin.com/gejuxije/2/edit
You can put html inside of an inline script...
<script type="text/ng-template" id="myModalTemplateName.html"></script>
The value you pass to 'template' needs to be valid HTML, and ideally should contain the appropriate modal CSS classes.
You may also need to pass in the scope for the controller.
var modalInstance = $modal.open({
scope:$scope,
template: "<div>Message goes here...<button ng-click='cancel()'>Continue</button></div>",
controller: function(){
$scope.cancel = function(){
alert("Cancelled");
};
}
});
In general I have not had to do this, but since you are defining the controller in the open method it may be necessary. According to the docs it should create a new scope as a child of rootScope, but I suspect your mileage is varying. I wish the instructions on the website were a little more informative on this topic.
You may also want to try $close and $dismiss. I've never tried them, but since you are not having luck with the scope variable these might be what you need.
I am just trying to do something similar and stumbled across this. I know it's old but it might help someone.
Simply put
modalInstance.close();
in the cancel function