85

I want to create an AlertFactory with Angular.factory. I defined an html template like follow

var template = "<h1>{{title}}</h1>";

Title is provided by calling controller and applied as follow

var compiled = $compile(template)(scope);
body.append(compiled);

So, how I can pass isolated scope from controller to factory? I'm using in controller follow code

AlertFactory.open($scope);

But $scope is global controller scope variable. I just want pass a small scope for factory with just title property.

Thank you.

5 Answers 5

112

You can create a new scope manually.

You can create a new scope from $rootScope if you inject it, or just from your controller scope - this shouldn't matter as you'll be making it isolated.

var alertScope = $scope.$new(true);
alertScope.title = 'Hello';

AlertFactory.open(alertScope);

The key here is passing true to $new, which accepts one parameter for isolate, which avoids inheriting scope from the parent.

More information can be found at: http://docs.angularjs.org/api/ng.$rootScope.Scope#$new

Sign up to request clarification or add additional context in comments.

5 Comments

If you create a new scope manually, you're probably also going to have to destroy it manually. It is normally better to not create scopes manually.
I've done a test, but it doesn't works. Please look at stackoverflow.com/questions/15565462/…
@MarkRajcok you mention that it is normally better to not create scopes manually. However what is the alternative if you need to create html dynamically and want to use an angular directive within that html?
Life saver! Had to do this, as I was writing a wrapper for Angular Bootstrap Modal.
When you make a child scope (isolate or not isolate), Angular automatically destroys it when its parent is destroyed. So in this example, when $scope is destroyed alertScope is automatically destroyed too. So @MarkRajcok this is a perfectly valid use case and perfectly safe.
22

If you only need to interpolate things, use the $interpolate service instead of $compile, and then you won't need a scope:

myApp.factory('myService', function($interpolate) {
    var template = "<h1>{{title}}</h1>";
    var interpolateFn = $interpolate(template);
    return {
        open: function(title) {
            var html = interpolateFn({ title: title });
            console.log(html);
            // append the html somewhere
        }
    }
});

Test controller:

function MyCtrl($scope, myService) {
    myService.open('The Title');
}

Fiddle

7 Comments

What is difference between $compile and $interpolate? $interpolate makes only text replace?
@Premier, that is my understanding. See also stackoverflow.com/a/13460295/215945 If your template contains directives, then $interpolate won't work -- you'd need to use $compile for that.
Ok thank you. It isn't enought for my task, i need a controller with complete scope.
@Premier, I suggest you try creating a fiddle or plunker of what you are trying. It is not clear where your isolate scope is coming from.
|
4

Followings are the steps:

  1. Add your HTML to the DOM by using var comiledHTML = angular.element(yourHTML);
  2. Create a new Scope if you want var newScope = $rootScope.$new();
  3. Call $comile(); function which returns link function var linkFun = $compile(comiledHTML);
  4. Bind the new scope by calling linkFun var finalTemplate = linkFun(newScope);
  5. Append finalTemplate to your DOM YourHTMLElemet.append(finalTemplate);

1 Comment

From var linkFun = $compile(comiledHTML); on step 2
2

check out my plunkr. I'm programmatically generating a widget directive with a render directive.

https://plnkr.co/edit/5T642U9AiPr6fJthbVpD?p=preview

angular
  .module('app', [])
  .controller('mainCtrl', $scope => $scope.x = 'test')
  .directive('widget', widget)
  .directive('render', render)

function widget() {
  return {
    template: '<div><input ng-model="stuff"/>I say {{stuff}}</div>'
  }
}

function render($compile) {
  return {
    template: '<button ng-click="add()">{{name}}</button><hr/>',
    link: linkFn
  }

  function linkFn(scope, elem, attr) {
    scope.name = 'Add Widget';
    scope.add = () => {
      const newScope = scope.$new(true);
      newScope.export = (data) => alert(data);
      const templ = '<div>' +
                      '<widget></widget>' +
                      '<button ng-click="export(this.stuff)">Export</button>' +
                    '</div>';
      const compiledTempl = $compile(templ)(newScope);
      elem.append(compiledTempl);
    }
  }
}

Comments

1

I assume when you are talking about an isolate scope you are talking about a directive.

Here is an example of how to do it. http://jsfiddle.net/rgaskill/PYhGb/

var app = angular.module('test',[]);

app.controller('TestCtrl', function ($scope) {
    $scope.val = 'World';
});

app.factory('AlertFactory', function () {

    return {
        doWork: function(scope) {
            scope.title = 'Fun';    
            //scope.title = scope.val;  //notice val doesn't exist in this scope
        }
    };

});

app.controller('DirCtrl', function ($scope, AlertFactory) {
    AlertFactory.doWork($scope);  
});

app.directive('titleVal',function () {
    return {
        template: '<h1>Hello {{title}}</h1>',
        restrict: 'E',
        controller: 'DirCtrl',
        scope: {
            title: '='
        },
        link: function() {

        }
    };

});

Basically, attach a controller to a directive that has defined an isolate scope. The scope injected into the directive controller will be an isolate scope. In the directive controller you can inject your AlertFactory with wich you can pass the isolate scope to.

5 Comments

grrr..plnkr.co seems to be offline right now. Hopefully the link still works when it comes back.
Mmmm i think it is't a good solution for me, i don't need a directive. I need a factory capables to show a modal alert dialog. So i need to attach template html in dom overlay and use a controller to manages data in alert view.
I would argue you shouldn't be manipulating the dom in a factory. You are mixing your view logic with your business logic. You could do exactly what you described in a directive where the dom manipulation should take place.
See Brad Green's reply to Andy regarding using a service for a modal, rather than a directive: blog.angularjs.org/2012/11/about-those-directives.html
...But for this specifc bootstrap case, I think it is best resolved with a directive. plnkr.co/edit/z4J8jH?p=preview

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.