Angular Parent Scope Variable not changing - javascript

I have a parent controller where I set instantiate an object called links. I assign a property with a value that I want to change within another function. However when I set the variable in the instagramModel the links.imagesa doesn't get updated.
I print the value out in the console and the parentscope doesn't get updated. I have thought I followed the rules of prototypical inheritance.
Why is $scope.links.imagesa not updating?
.controller('HomeCtrl', function HomeController($scope, titleService, config, $sails, $timeout, $upload, leafletData, $modal, $log) {
$scope.links = {};
$scope.links.imagesa = "This should change";
$scope.instagramModal = function (size) {
var modalInstance = $modal.open({
templateUrl: 'instagramModal.html',
controller: 'InstagramModalInstanceCtrl',
size: size,
resolve: {
items: function () {
return $sails.get("/instagram/self").success(function (response) {
return response.data;
}).error(function (response) {
console.log('error');
});
}
}
});
modalInstance.result.then(function (selectedItem) {
$scope.links.imagesa = "wept";
}, function () {
$log.info('Modal dismissed at: ' + new Date());
});
};
$scope.ask = function () {
console.log($scope.links.imagesa);
};
});

If you want the parent's scope to get updated, then you must use $scope.$parent.links.imagesa since the changes made in child scope are not reflected in the parent scope directly.

I had the HomeCtrl instantiated in the UI Router and also on the template page using ng-controller. This messed up the scope.

Angular UI's modals use $rootScope by default. See documentation at "http://angular-ui.github.io/bootstrap/#/modal"
You can pass a scope parameter with a custom scope when you open the modal – e.g. scope: $scope if you want to pass the parent scope. The modal controller will create a sub-scope from that scope, so you will only be able to use it for your initial values.
Hence, if you want to update any value, keep the object/data in rootScope.

Related

How do I reach $rootScope from a component?

I am very new to AngularJS/Ionic/Cordova programming and am trying to handle the visibility of a component using a global variable, so it can be hidden or shown from other components. I am creating the variable when calling the run function, assigning it to $rootScope.
app.run(function($rootScope, $ionicPlatform) {
$ionicPlatform.ready(function() {
// Some Ionic/Cordova stuff...
// My global variable.
$rootScope.visible = true;
});
})
My component is:
function MyComponentController($rootScope, $scope) {
var self = this;
self.visible = $rootScope.visible;
alert(self.visible);
}
angular.module('myapp')
.component('myComponent', {
templateUrl: 'my-component.template.html',
controller: MyComponentController
});
And the template:
<div ng-if="$ctrl.visible">
<!-- ... -->
</div>
However the alert message always shows "undefined". What am I missing?
$rootScope.visible isn't watched when being assigned as self.visible = $rootScope.visible. And it is undefined at the moment when component controller is instantiated.
It can be
function MyComponentController($rootScope, $scope) {
var self = this;
$scope.$watch(function () { return $rootScope.visible }, function (val) {
self.visible = val;
});
}
By the way, it is likely available as $scope.$parent.visible and can be bound in template as ng-if="$parent.visible", but this is antipattern that is strongly discouraged.
There may be better approaches:
top-level AppController and <my-component ng-if="visible">, so the component doesn't have to control its own visibility
broadcasting it with scope events, $rootScope.$broadcast('visibility:myComponent')
using a service as event bus (that's where RxJS may be helpful)
using a router to control the visibility of views, possibly with route/state resolver (this is the best way)
How about change self to $scope like this:
function MyComponentController($rootScope, $scope) {
$scope.visible = $rootScope.visible;
alert($scope.visible);
}

Testing the controller passed to an Angular Material Dialog instance

First off, I am trying to unit test the controller that is being passed to an Angular Material Dialog instance.
As a general question, does it make more sense to test such a controller separately, or by actually invoking$mdDialog.show()?
I am attempting the first method, but I'm running into some issues, mostly related to how Angular Material binds the "locals" to the controller.
Here is the code that I am using to invoke the dialog in my source code, which works as expected:
$mdDialog.show({
controller: 'DeviceDetailController',
controllerAs: 'vm',
locals: {deviceId: "123"},
bindToController: true,
templateUrl: 'admin/views/deviceDetail.html',
parent: angular.element(document.body),
targetEvent: event
});
I don't believe the docs have been updated, but as of version 0.9.0 or so, the locals are available to the controller at the time the constructor function is called (see this issue on Github). Here is a stripped-down version of the controller constructor function under test, so you can see why I need the variable to be passed in and available when the controller is "instantiated":
function DeviceDetailController(devicesService) {
var vm = this;
vm.device = {};
// vm.deviceId = null; //this field is injected when the dialog is created, if there is one. For some reason I can't pre-assign it to null.
activate();
//////////
function activate() {
if (vm.deviceId != null) {
loadDevice();
}
}
function loadDevice() {
devicesService.getDeviceById(vm.deviceId)
.then(function(data) {
vm.device = data.collection;
};
}
}
I am trying to test that the device is assigned to vm.device when a deviceId is passed in to the constructor function before it is invoked.
The test (jasmine and sinon, run by karma):
describe('DeviceDetailController', function() {
var $controllerConstructor, scope, mockDevicesService;
beforeEach(module("admin"));
beforeEach(inject(function ($controller, $rootScope) {
mockDevicesService = sinon.stub({
getDeviceById: function () {}
});
$controllerConstructor = $controller;
scope = $rootScope.$new();
}));
it('should get a device from devicesService if passed a deviceId', function() {
var mockDeviceId = 3;
var mockDevice = {onlyIWouldHaveThis: true};
var mockDeviceResponse = {collection: [mockDevice]};
var mockDevicePromise = {
then: function (cb) {
cb(mockDeviceResponse);
}
};
var mockLocals = {deviceId: mockDeviceId, $scope: scope};
mockDevicesService.getDeviceById.returns(mockDevicePromise);
var ctrlConstructor = $controllerConstructor('DeviceDetailController as vm', mockLocals, true);
angular.extend(ctrlConstructor.instance, mockLocals);
ctrlConstructor();
expect(scope.vm.deviceId).toBe(mockDeviceId);
expect(scope.vm.device).toEqual(mockDevice);
});
});
When I run this, the first assertion passes and the second one fails ("Expected Object({ }) to equal Object({ onlyIWouldHaveThis: true })."), which shows me that deviceId is being injected into the controller's scope, but apparently not in time for the if clause in the activate() method to see it.
You will notice that I am trying to mimic the basic procedure that Angular Material uses by calling $controller() with the third argument set to 'true', which causes $controller() to return the controller constructor function, as opposed to the resulting controller. I should then be able to extend the constructor with my local variables (just as Angular Material does in the code linked to above), and then invoke the constructor function to instantiate the controller.
I have tried a number of things, including passing an isolate scope to the controller by calling $rootScope.$new(true), to no effect (I actually can't say I fully understand isolate scope, but $mdDialog uses it by default).
Any help is appreciated!
The first thing I would try would be to lose the 'as vm' from your call to $controller. You can just use the return value for your expect rather than testing scope.
Try this:
var ctrlConstructor = $controllerConstructor('DeviceDetailController', mockLocals, true);
angular.extend(ctrlConstructor.instance, mockLocals);
var vm = ctrlConstructor();
expect(vm.deviceId).toBe(mockDeviceId);
expect(vm.device).toEqual(mockDevice);

Angular directive isolate scope to parent binding undefined

I'm using (the awesome) Restangular and i'm running into something that forces me to use scope.$parent (not awesome), and i don't want to use that. It seems even though my controller is the parent scope to my directive's scope, the = isolated scope binding is evaluated before my parent controller is executed.
With the following HTML:
<div ng-controller="myController">
<div x-my-directive x-some-value="parentValue"></div>
</div>
And the following directive:
myApp.directive("myDirective", function () {
return {
restrict: 'A',
link: function (scope, elem) {
console.log(scope.someValue); // Logs 'undefined' :(
},
scope: {
someValue: "="
}
}
});
And the following controller:
myApp.controller("myController", function($scope, allMyValues) {
allMyValues.getList().then(function(parentValue){
$scope.parentValue = parentValue;
});
}
As shown in my directives link function, evaluating a scope property that should have been bound to my parent's scope property returns undefined. However when i change my directives link function to the following:
myApp.directive("myDirective", function () {
return {
restrict: 'A',
link: function (scope, elem) {
setTimeout(function() {
console.log(scope.someValue); // Logs '{1: number_1, 2: number_2}'
}, 2000);
},
scope: {
someValue: "="
}
}
});
How do i go about resolving this??
Thanks
that should helps:
myApp.controller("myController", function($scope, allMyValues) {
//add this line
$scope.parentValue={};
allMyValues.getList().then(function(parentValue){
$scope.parentValue = parentValue;
});
}
$scope.parentValue not exist until your request is resolved so add line like below to your code
sample demo http://jsbin.com/komikitado/1/edit
Looks like you are waiting for a promise to resolve before assigning the value to the scope.
There are a few ways you might handle this.
One way is to try moving the Restangular call to a resolve function for the view which holds the controller. Then you get access to the resolved data directly as an injection in your controllers
Another way might be to just assign the promise directly to the scope and then in the linking function wait for a resolution.
scope.someValue.then(function(value) { console.log(value); });

Trigger a function in a child directive from it's parent [angularJS]

So I totally do this in reverse all the time when using the directive property require: '^ParentCtrl' inside the child directive. Using require to then call the parent function; however, I need to do this in reverse.
Question:
How do I trigger FROM a parent directive the execution of a function IN a child directive.
Note:
1. Child Directive has no function is inside a link:
2. essentially I want a reverse require.
Parent Directive:
'use strict';
angular.module('carouselApp')
.directive('waCarousel', function() {
return {
templateUrl: 'views/carousel/wa.carousel.html',
controller: function($scope) {
var self = this;
// this function is being called based on how many pages there are
self.carouselElLoaded = function(result) {
var count = 1;
Carousel.params.pageRenderedLength += count;
//when all the pages are loaded
if (Carousel.params.pageRenderedLength === Carousel.params.pageLength) {
Carousel.params.carouselReady = true;
// !!!!!!!! Trigger will go here!!!!!!!!!//
ChildCtrl.drawHotspots(); // (**for placement only**)
} else {
Carousel.params.carouselReady = false;
}
};
}
}
})
Child Directive:
'use strict';
angular.module('carouselApp')
.directive('waHotspots', function() {
return {
require: '^waCarousel',
link: function (scope, element, attrs, ctrl) {
//call this directive based on how
scope.drawHotspots = function () {...};
}
})
This is possible by having the parent controller talk to the child controller through a well defined API, that you create. The idea is that you want to maintain loose coupling between the parent and the child directive by having each respective controller knowing as little about each other as possible, but still have enough knowledge to get the job done.
To achieve this, require the parent directive from the child directive, and let the child directive register itself with parent's controller:
Child directive:
require: '^parentDirective',
controller: function(){
this.someFunc = function() {...}
},
link: function(scope,element,attr, parentCtrl){
parentCtrl.register(element);
}
Then in your parent directive, implement the register function, and get the child's controller, and call the child's function when needed:
Parent directive:
controller: function(){
var childCtrl = undefined;
this.register = function (element) {
childCtrl = element.controller();
}
this.callChildFunc = function (){
childCtrl.someFunc();
}
},
link: function (scope,element){
var ctrl = element.controller();
ctrl.callChildFunc();
}
You could always trigger it via a $watch. Just pass in the parent scope value that you want to watch and change it's value.
Parent:
$scope.drawHotspots = false;
Template:
waHotspots the-trigger="drawHotspots"....
Child Directive:
localTrigger: '#' // Receive the value to watch
scope.$watch('localTrigger',function() {
// call drawHotspots if value is set to true
});
Its on old topic but I came here today so might others ...
I think the best approche is to use a Service
angular.module('App').service('SomeService', [SomeService]);
Then inject the service into both the parent and child ...
controller : ['$rootScope', '$scope','SomeService', SomeDirectiveController],
Use the service to talk to each other ...
In their controllers SomeService.setParent(this) and SomeService.setChild(this)
Service would have a field to hold the references :
this.parentCtrl = null;
this.childCtrl = null;//or [] in-case you have multiple childs!
Somewhere in the parent : SomeService.childCtrl.someFunctionInChild()
Or if you want a restricted access , in service make the fields private :
var parentCtrl = null;
var childCtrl = null;//or [] in-case you have multiple childs of the same type!
this.callUserFunc = function(param){childCtrl.someFunctionInChild(param)};
And Somewhere in the parent : SomeService.callUserFunc(myparam)

How to set a variable in different controller in AngularJS?

I'd like to do simple notifications in angular. Here is the code I've written.
http://pastebin.com/zYZtntu8
The question is:
Why if I add a new alert in hasAlerts() method it works, but if I add a new alert in NoteController it doesn't. I've tried something with $scope.$watch but it also doesn't work or I've done something wrong.
How can I do that?
Check out this plnkr I made a while back
http://plnkr.co/edit/ABQsAxz1bNi34ehmPRsF?p=preview
I show a couple of ways controllers can use data from services, in particular the first two show how to do it without a watch which is generally a more efficient way to go:
// Code goes here
angular.module("myApp", []).service("MyService", function($q) {
var serviceDef = {};
//It's important that you use an object or an array here a string or other
//primitive type can't be updated with angular.copy and changes to those
//primitives can't be watched.
serviceDef.someServiceData = {
label: 'aValue'
};
serviceDef.doSomething = function() {
var deferred = $q.defer();
angular.copy({
label: 'an updated value'
}, serviceDef.someServiceData);
deferred.resolve(serviceDef.someServiceData);
return deferred.promise;
}
return serviceDef;
}).controller("MyCtrl", function($scope, MyService) {
//Using a data object from the service that has it's properties updated async
$scope.sharedData = MyService.someServiceData;
}).controller("MyCtrl2", function($scope, MyService) {
//Same as above just has a function to modify the value as well
$scope.sharedData = MyService.someServiceData;
$scope.updateValue = function() {
MyService.doSomething();
}
}).controller("MyCtrl3", function($scope, MyService) {
//Shows using a watch to see if the service data has changed during a digest
//if so updates the local scope
$scope.$watch(function(){ return MyService.someServiceData }, function(newVal){
$scope.sharedData = newVal;
})
$scope.updateValue = function() {
MyService.doSomething();
}
}).controller("MyCtrl4", function($scope, MyService) {
//This option relies on the promise returned from the service to update the local
//scope, also since the properties of the object are being updated not the object
//itself this still stays "in sync" with the other controllers and service since
//really they are all referring to the same object.
MyService.doSomething().then(function(newVal) {
$scope.sharedData = newVal;
});
});
The notable thing here I guess is that I use angular.copy to re-use the same object that's created in the service instead of assigning a new object or array to that property. Since it's the same object if you reference that object from your controllers and use it in any data-binding situation (watches or {{}} interpolation in the view) will see the changes to the object.

Categories

Resources